schub 0.1.25 → 0.1.26

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.
@@ -31796,9 +31796,6 @@ __export(exports_schema, {
31796
31796
  templates: () => templates,
31797
31797
  sessions: () => sessions,
31798
31798
  repos: () => repos,
31799
- proposals: () => proposals,
31800
- proposal_statuses: () => proposal_statuses,
31801
- proposal_files: () => proposal_files,
31802
31799
  projects: () => projects,
31803
31800
  project_repos: () => project_repos,
31804
31801
  project_docs: () => project_docs,
@@ -33172,34 +33169,10 @@ var ticket_statuses = sqliteTable("ticket_statuses", {
33172
33169
  created_at: text("created_at").notNull(),
33173
33170
  updated_at: text("updated_at").notNull()
33174
33171
  });
33175
- var proposal_statuses = sqliteTable("proposal_statuses", {
33176
- id: text("id").primaryKey(),
33177
- project_id: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
33178
- name: text("name").notNull(),
33179
- sort_order: integer2("sort_order").notNull(),
33180
- is_default: integer2("is_default").notNull(),
33181
- created_at: text("created_at").notNull(),
33182
- updated_at: text("updated_at").notNull()
33183
- });
33184
- var proposals = sqliteTable("proposals", {
33185
- id: text("id").primaryKey(),
33186
- shorthand: text("shorthand").notNull(),
33187
- project_id: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
33188
- repo_id: text("repo_id").references(() => repos.id, { onDelete: "set null" }),
33189
- input: text("input"),
33190
- status: text("status").notNull().default("draft"),
33191
- archived: integer2("archived").notNull().default(0),
33192
- staged: integer2("staged").notNull().default(0),
33193
- created_at: text("created_at").notNull(),
33194
- updated_at: text("updated_at").notNull()
33195
- }, (table) => [uniqueIndex("proposals_project_shorthand_idx").on(table.project_id, table.shorthand)]);
33196
33172
  var tickets = sqliteTable("tickets", {
33197
33173
  id: text("id").primaryKey(),
33198
33174
  shorthand: text("shorthand").notNull(),
33199
33175
  project_id: text("project_id").notNull().references(() => projects.id, { onDelete: "cascade" }),
33200
- proposal_id: text("proposal_id").references(() => proposals.id, {
33201
- onDelete: "set null"
33202
- }),
33203
33176
  status_id: text("status_id").references(() => ticket_statuses.id, {
33204
33177
  onDelete: "set null"
33205
33178
  }),
@@ -33293,12 +33266,6 @@ var project_doc_files = sqliteTable("project_doc_files", {
33293
33266
  file_id: text("file_id").notNull().references(() => files.id, { onDelete: "cascade" }),
33294
33267
  created_at: text("created_at").notNull()
33295
33268
  }, (table) => [uniqueIndex("project_doc_files_project_doc_file_idx").on(table.project_doc_id, table.file_id)]);
33296
- var proposal_files = sqliteTable("proposal_files", {
33297
- id: text("id").primaryKey(),
33298
- proposal_id: text("proposal_id").notNull().references(() => proposals.id, { onDelete: "cascade" }),
33299
- file_id: text("file_id").notNull().references(() => files.id, { onDelete: "cascade" }),
33300
- created_at: text("created_at").notNull()
33301
- });
33302
33269
  var ticket_files = sqliteTable("ticket_files", {
33303
33270
  id: text("id").primaryKey(),
33304
33271
  ticket_id: text("ticket_id").notNull().references(() => tickets.id, { onDelete: "cascade" }),
@@ -33319,7 +33286,8 @@ var templates = sqliteTable("templates", {
33319
33286
  }),
33320
33287
  name: text("name").notNull(),
33321
33288
  template_type: text("template_type").notNull(),
33322
- content: text("content").notNull(),
33289
+ file_id: text("file_id").notNull().references(() => files.id),
33290
+ is_default: integer2("is_default").notNull().default(0),
33323
33291
  created_at: text("created_at").notNull(),
33324
33292
  updated_at: text("updated_at").notNull()
33325
33293
  });
@@ -33622,7 +33590,6 @@ var sessionSelectSchema = createSelectSchema(sessions, {
33622
33590
  });
33623
33591
  var ticketTagSelectSchema = createSelectSchema(ticket_tags);
33624
33592
  var workspaceSelectSchema = createSelectSchema(workspaces);
33625
- var proposalSelectSchema = createSelectSchema(proposals);
33626
33593
  var ticketAttemptSchema = exports_external.object({
33627
33594
  id: exports_external.string(),
33628
33595
  label: exports_external.string(),
@@ -33813,7 +33780,6 @@ var createFilesService = (db, storageRoot) => {
33813
33780
  }).where(eq(files.id, fileId)).run();
33814
33781
  return updated;
33815
33782
  };
33816
- const listForProposal = (proposalId) => db.select({ file: files }).from(proposal_files).innerJoin(files, eq(proposal_files.file_id, files.id)).where(eq(proposal_files.proposal_id, proposalId)).orderBy(proposal_files.created_at).all().map((row) => row.file);
33817
33783
  const listForProjectDocs = (projectDocId) => db.select({ file: files }).from(project_doc_files).innerJoin(files, eq(project_doc_files.file_id, files.id)).where(eq(project_doc_files.project_doc_id, projectDocId)).orderBy(project_doc_files.created_at).all().map((row) => row.file);
33818
33784
  const attachToProjectDocs = (projectDocId, fileId) => {
33819
33785
  const existing = get(fileId);
@@ -33838,26 +33804,6 @@ var createFilesService = (db, storageRoot) => {
33838
33804
  db.delete(project_doc_files).where(and(eq(project_doc_files.project_doc_id, projectDocId), eq(project_doc_files.file_id, fileId))).run();
33839
33805
  return true;
33840
33806
  };
33841
- const attachToProposal = (proposalId, fileId) => {
33842
- const existing = get(fileId);
33843
- if (!existing)
33844
- return null;
33845
- const link = {
33846
- id: crypto.randomUUID(),
33847
- proposal_id: proposalId,
33848
- file_id: fileId,
33849
- created_at: nowTimestamp()
33850
- };
33851
- db.insert(proposal_files).values(link).run();
33852
- return existing;
33853
- };
33854
- const detachFromProposal = (proposalId, fileId) => {
33855
- const existing = db.select().from(proposal_files).where(and(eq(proposal_files.proposal_id, proposalId), eq(proposal_files.file_id, fileId))).get();
33856
- if (!existing)
33857
- return false;
33858
- db.delete(proposal_files).where(and(eq(proposal_files.proposal_id, proposalId), eq(proposal_files.file_id, fileId))).run();
33859
- return true;
33860
- };
33861
33807
  const listForTicket = (ticketId) => db.select({ file: files }).from(ticket_files).innerJoin(files, eq(ticket_files.file_id, files.id)).where(eq(ticket_files.ticket_id, ticketId)).orderBy(ticket_files.created_at).all().map((row) => row.file);
33862
33808
  const attachToTicket = (ticketId, fileId) => {
33863
33809
  const existing = get(fileId);
@@ -33888,9 +33834,6 @@ var createFilesService = (db, storageRoot) => {
33888
33834
  listForProjectDocs,
33889
33835
  attachToProjectDocs,
33890
33836
  detachFromProjectDocs,
33891
- listForProposal,
33892
- attachToProposal,
33893
- detachFromProposal,
33894
33837
  listForTicket,
33895
33838
  attachToTicket,
33896
33839
  detachFromTicket
@@ -34597,15 +34540,6 @@ var DEFAULT_TICKET_STATUSES = [
34597
34540
  column_actions: []
34598
34541
  }
34599
34542
  ];
34600
- var DEFAULT_PROPOSAL_STATUSES = [
34601
- { name: "draft", is_default: 1 },
34602
- { name: "pending review", is_default: 0 },
34603
- { name: "wip", is_default: 0 },
34604
- { name: "in-review", is_default: 0 },
34605
- { name: "accepted", is_default: 0 },
34606
- { name: "rejected", is_default: 0 },
34607
- { name: "done", is_default: 0 }
34608
- ];
34609
34543
  var DEFAULT_TICKET_TAGS = [
34610
34544
  { name: "bug", color: "red" },
34611
34545
  { name: "feature", color: "blue" },
@@ -34668,15 +34602,6 @@ var createProjectsService = (db) => {
34668
34602
  created_at: timestamp,
34669
34603
  updated_at: timestamp
34670
34604
  }))).run();
34671
- db.insert(proposal_statuses).values(DEFAULT_PROPOSAL_STATUSES.map((status, index) => ({
34672
- id: crypto.randomUUID(),
34673
- project_id: project.id,
34674
- name: status.name,
34675
- sort_order: index + 1,
34676
- is_default: status.is_default,
34677
- created_at: timestamp,
34678
- updated_at: timestamp
34679
- }))).run();
34680
34605
  db.insert(ticket_tags).values(DEFAULT_TICKET_TAGS.map((tag) => ({
34681
34606
  id: crypto.randomUUID(),
34682
34607
  project_id: project.id,
@@ -34729,165 +34654,9 @@ var createProjectsService = (db) => {
34729
34654
  return { list, get, getSnapshot, getSnapshotPatch, subscribe, create, update, remove };
34730
34655
  };
34731
34656
 
34732
- // ../schub-db/src/services/proposal-statuses.ts
34733
- var createProposalStatusesService = (db) => {
34734
- const list = (projectId) => db.select().from(proposal_statuses).where(eq(proposal_statuses.project_id, projectId)).orderBy(proposal_statuses.sort_order).all();
34735
- const get = (projectId, statusId) => {
34736
- const status = db.select().from(proposal_statuses).where(and(eq(proposal_statuses.project_id, projectId), eq(proposal_statuses.id, statusId))).get();
34737
- return status ?? null;
34738
- };
34739
- const getByName = (projectId, statusName) => {
34740
- const status = db.select().from(proposal_statuses).where(and(eq(proposal_statuses.project_id, projectId), eq(proposal_statuses.name, statusName))).get();
34741
- return status ?? null;
34742
- };
34743
- const getDefault = (projectId) => {
34744
- const explicitDefault = db.select().from(proposal_statuses).where(and(eq(proposal_statuses.project_id, projectId), eq(proposal_statuses.is_default, 1))).orderBy(proposal_statuses.sort_order).get();
34745
- if (explicitDefault) {
34746
- return explicitDefault;
34747
- }
34748
- return list(projectId)[0] ?? null;
34749
- };
34750
- return { list, get, getByName, getDefault };
34751
- };
34752
-
34753
- // ../schub-db/src/services/proposals.ts
34754
- var nowTimestamp4 = () => new Date().toISOString();
34755
- var parseShorthandSequence = (shorthand) => {
34756
- const match2 = shorthand.match(/^[CP](\d+)/i);
34757
- if (!match2) {
34758
- return null;
34759
- }
34760
- return Number.parseInt(match2[1], 10);
34761
- };
34762
- var formatShorthand = (sequence) => `P${sequence.toString().padStart(4, "0")}`;
34763
- var normalizeProposalShorthand = (shorthand) => {
34764
- const sequence = parseShorthandSequence(shorthand);
34765
- if (sequence === null) {
34766
- throw new Error(`Invalid proposal shorthand '${shorthand}'.`);
34767
- }
34768
- return formatShorthand(sequence);
34769
- };
34770
- var createProposalsService = (db) => {
34771
- const getDefaultStatusName = (projectId) => {
34772
- const explicitDefault = db.select({ name: proposal_statuses.name }).from(proposal_statuses).where(and(eq(proposal_statuses.project_id, projectId), eq(proposal_statuses.is_default, 1))).orderBy(proposal_statuses.sort_order).get();
34773
- if (explicitDefault) {
34774
- return explicitDefault.name;
34775
- }
34776
- const firstStatus = db.select({ name: proposal_statuses.name }).from(proposal_statuses).where(eq(proposal_statuses.project_id, projectId)).orderBy(proposal_statuses.sort_order).get();
34777
- return firstStatus?.name ?? "draft";
34778
- };
34779
- const assertKnownStatus = (projectId, status) => {
34780
- const normalizedStatus = status.trim().toLowerCase();
34781
- const exists = db.select({ name: proposal_statuses.name }).from(proposal_statuses).where(eq(proposal_statuses.project_id, projectId)).all().some((candidate) => candidate.name.trim().toLowerCase() === normalizedStatus);
34782
- if (!exists) {
34783
- throw new Error(`Proposal status '${status}' does not exist for project '${projectId}'.`);
34784
- }
34785
- };
34786
- const allocateProposalShorthand = () => {
34787
- return db.transaction((tx) => {
34788
- const priorShorthands = tx.select({ shorthand: proposals.shorthand }).from(proposals).all();
34789
- const maxSequence = priorShorthands.reduce((max, proposal) => {
34790
- if (!proposal.shorthand) {
34791
- return max;
34792
- }
34793
- const parsed = parseShorthandSequence(proposal.shorthand);
34794
- if (parsed === null) {
34795
- return max;
34796
- }
34797
- return Math.max(max, parsed);
34798
- }, 0);
34799
- return formatShorthand(maxSequence + 1);
34800
- });
34801
- };
34802
- const list = (projectId) => db.select().from(proposals).where(eq(proposals.project_id, projectId)).orderBy(proposals.created_at).all();
34803
- const get = (projectId, proposalId) => {
34804
- const proposal = db.select().from(proposals).where(and(eq(proposals.project_id, projectId), eq(proposals.id, proposalId))).get();
34805
- return proposal ?? null;
34806
- };
34807
- const create = (projectId, input) => {
34808
- const timestamp = nowTimestamp4();
34809
- const shorthand = allocateProposalShorthand();
34810
- const defaultStatus = getDefaultStatusName(projectId);
34811
- const proposal = {
34812
- id: crypto.randomUUID(),
34813
- shorthand,
34814
- project_id: projectId,
34815
- repo_id: input.repo_id ?? null,
34816
- input: input.input ?? null,
34817
- status: defaultStatus,
34818
- archived: 0,
34819
- staged: input.staged ?? 0,
34820
- created_at: timestamp,
34821
- updated_at: timestamp
34822
- };
34823
- db.insert(proposals).values(proposal).run();
34824
- return proposal;
34825
- };
34826
- const createWithShorthand = (projectId, shorthand, input) => {
34827
- const timestamp = nowTimestamp4();
34828
- const normalizedShorthand = normalizeProposalShorthand(shorthand);
34829
- const defaultStatus = getDefaultStatusName(projectId);
34830
- const proposal = {
34831
- id: crypto.randomUUID(),
34832
- shorthand: normalizedShorthand,
34833
- project_id: projectId,
34834
- repo_id: null,
34835
- input: input.input ?? null,
34836
- status: defaultStatus,
34837
- archived: 0,
34838
- staged: input.staged ?? 0,
34839
- created_at: timestamp,
34840
- updated_at: timestamp
34841
- };
34842
- db.insert(proposals).values(proposal).run();
34843
- return proposal;
34844
- };
34845
- const update = (projectId, proposalId, input) => {
34846
- const existing = get(projectId, proposalId);
34847
- if (!existing)
34848
- return null;
34849
- const proposalInput = input.input === undefined ? existing.input : input.input;
34850
- const repo_id = input.repo_id === undefined ? existing.repo_id : input.repo_id;
34851
- const status = input.status ?? existing.status;
34852
- const archived = input.archived === undefined ? existing.archived : input.archived;
34853
- const staged = input.staged === undefined ? existing.staged : input.staged;
34854
- if (input.status !== undefined) {
34855
- assertKnownStatus(projectId, status);
34856
- }
34857
- const updated = {
34858
- ...existing,
34859
- repo_id,
34860
- input: proposalInput,
34861
- status,
34862
- archived,
34863
- staged,
34864
- updated_at: nowTimestamp4()
34865
- };
34866
- db.update(proposals).set({
34867
- repo_id: updated.repo_id,
34868
- input: updated.input,
34869
- status: updated.status,
34870
- archived: updated.archived,
34871
- staged: updated.staged,
34872
- updated_at: updated.updated_at
34873
- }).where(and(eq(proposals.project_id, projectId), eq(proposals.id, proposalId))).run();
34874
- return updated;
34875
- };
34876
- const remove = (projectId, proposalId) => {
34877
- const existing = get(projectId, proposalId);
34878
- if (!existing)
34879
- return false;
34880
- const timestamp = nowTimestamp4();
34881
- db.update(tickets).set({ proposal_id: null, updated_at: timestamp }).where(and(eq(tickets.project_id, projectId), eq(tickets.proposal_id, proposalId))).run();
34882
- db.delete(proposals).where(and(eq(proposals.project_id, projectId), eq(proposals.id, proposalId))).run();
34883
- return true;
34884
- };
34885
- return { list, get, create, createWithShorthand, update, remove };
34886
- };
34887
-
34888
34657
  // ../schub-db/src/services/repos.ts
34889
34658
  import { basename } from "node:path";
34890
- var nowTimestamp5 = () => new Date().toISOString();
34659
+ var nowTimestamp4 = () => new Date().toISOString();
34891
34660
  var normalizeRepoPath = (value) => value.replace(/[\\/]+$/, "");
34892
34661
  var deriveRepoName = (repoPath) => {
34893
34662
  const normalized = normalizeRepoPath(repoPath);
@@ -34910,7 +34679,7 @@ var createReposService = (db) => {
34910
34679
  return row?.repo ?? null;
34911
34680
  };
34912
34681
  const upsertRepo = (input) => {
34913
- const timestamp = nowTimestamp5();
34682
+ const timestamp = nowTimestamp4();
34914
34683
  const displayName = resolveDisplayName(input.display_name);
34915
34684
  const pathValue = normalizeRepoPath(input.path);
34916
34685
  const name = deriveRepoName(pathValue);
@@ -34949,13 +34718,13 @@ var createReposService = (db) => {
34949
34718
  const updated = {
34950
34719
  ...existing,
34951
34720
  display_name: displayName,
34952
- updated_at: nowTimestamp5()
34721
+ updated_at: nowTimestamp4()
34953
34722
  };
34954
34723
  db.update(repos).set({ display_name: updated.display_name, updated_at: updated.updated_at }).where(eq(repos.id, repoId)).run();
34955
34724
  return updated;
34956
34725
  };
34957
34726
  const addToProject = (projectId, input) => {
34958
- const timestamp = nowTimestamp5();
34727
+ const timestamp = nowTimestamp4();
34959
34728
  const repo = upsertRepo({ path: input.git_repo_path, display_name: input.display_name });
34960
34729
  const existingLink = db.select().from(project_repos).where(and(eq(project_repos.project_id, projectId), eq(project_repos.repo_id, repo.id))).get();
34961
34730
  if (!existingLink) {
@@ -34983,7 +34752,7 @@ var createReposService = (db) => {
34983
34752
  };
34984
34753
 
34985
34754
  // ../schub-db/src/services/sessions.ts
34986
- var nowTimestamp6 = () => new Date().toISOString();
34755
+ var nowTimestamp5 = () => new Date().toISOString();
34987
34756
  var SESSION_STATUSES = ["in_progress", "completed", "failed"];
34988
34757
  var normalizeSessionStatus = (status) => {
34989
34758
  if (!status)
@@ -35032,7 +34801,7 @@ var createSessionsService = (db) => {
35032
34801
  return session ? formatSession(session) : null;
35033
34802
  };
35034
34803
  const create = (input) => {
35035
- const timestamp = nowTimestamp6();
34804
+ const timestamp = nowTimestamp5();
35036
34805
  const session = {
35037
34806
  id: input.id?.trim() || crypto.randomUUID(),
35038
34807
  title: input.title,
@@ -35074,7 +34843,7 @@ var createSessionsService = (db) => {
35074
34843
  agent,
35075
34844
  agent_session_id: agentSessionId,
35076
34845
  content,
35077
- updated_at: nowTimestamp6()
34846
+ updated_at: nowTimestamp5()
35078
34847
  };
35079
34848
  db.update(sessions).set({
35080
34849
  title: updated.title,
@@ -35101,21 +34870,106 @@ var createSessionsService = (db) => {
35101
34870
  };
35102
34871
 
35103
34872
  // ../schub-db/src/services/templates.ts
35104
- var nowTimestamp7 = () => new Date().toISOString();
34873
+ var nowTimestamp6 = () => new Date().toISOString();
35105
34874
  var createTemplatesService = (db) => {
35106
- const list = () => db.select().from(templates).orderBy(templates.created_at).all();
34875
+ const list = () => db.select({
34876
+ id: templates.id,
34877
+ project_id: templates.project_id,
34878
+ name: templates.name,
34879
+ template_type: templates.template_type,
34880
+ file_id: templates.file_id,
34881
+ is_default: templates.is_default,
34882
+ created_at: templates.created_at,
34883
+ updated_at: templates.updated_at,
34884
+ file_name: files.file_name,
34885
+ storage_path: files.storage_path,
34886
+ mime_type: files.mime_type,
34887
+ size_bytes: files.size_bytes
34888
+ }).from(templates).innerJoin(files, eq(templates.file_id, files.id)).orderBy(templates.created_at).all();
34889
+ const listByProject = (projectId) => db.select({
34890
+ id: templates.id,
34891
+ project_id: templates.project_id,
34892
+ name: templates.name,
34893
+ template_type: templates.template_type,
34894
+ file_id: templates.file_id,
34895
+ is_default: templates.is_default,
34896
+ created_at: templates.created_at,
34897
+ updated_at: templates.updated_at,
34898
+ file_name: files.file_name,
34899
+ storage_path: files.storage_path,
34900
+ mime_type: files.mime_type,
34901
+ size_bytes: files.size_bytes
34902
+ }).from(templates).innerJoin(files, eq(templates.file_id, files.id)).where(eq(templates.project_id, projectId)).orderBy(templates.created_at).all();
35107
34903
  const get = (templateId) => {
35108
- const template = db.select().from(templates).where(eq(templates.id, templateId)).get();
35109
- return template ?? null;
34904
+ const row = db.select({
34905
+ id: templates.id,
34906
+ project_id: templates.project_id,
34907
+ name: templates.name,
34908
+ template_type: templates.template_type,
34909
+ file_id: templates.file_id,
34910
+ is_default: templates.is_default,
34911
+ created_at: templates.created_at,
34912
+ updated_at: templates.updated_at,
34913
+ file_name: files.file_name,
34914
+ storage_path: files.storage_path,
34915
+ mime_type: files.mime_type,
34916
+ size_bytes: files.size_bytes
34917
+ }).from(templates).innerJoin(files, eq(templates.file_id, files.id)).where(eq(templates.id, templateId)).get();
34918
+ return row ?? null;
34919
+ };
34920
+ const getDefault = (projectId, templateType) => {
34921
+ const row = db.select({
34922
+ id: templates.id,
34923
+ project_id: templates.project_id,
34924
+ name: templates.name,
34925
+ template_type: templates.template_type,
34926
+ file_id: templates.file_id,
34927
+ is_default: templates.is_default,
34928
+ created_at: templates.created_at,
34929
+ updated_at: templates.updated_at,
34930
+ file_name: files.file_name,
34931
+ storage_path: files.storage_path,
34932
+ mime_type: files.mime_type,
34933
+ size_bytes: files.size_bytes
34934
+ }).from(templates).innerJoin(files, eq(templates.file_id, files.id)).where(and(eq(templates.project_id, projectId), eq(templates.template_type, templateType), eq(templates.is_default, 1))).get();
34935
+ return row ?? null;
34936
+ };
34937
+ const getByName = (name, projectId) => {
34938
+ const row = db.select({
34939
+ id: templates.id,
34940
+ project_id: templates.project_id,
34941
+ name: templates.name,
34942
+ template_type: templates.template_type,
34943
+ file_id: templates.file_id,
34944
+ is_default: templates.is_default,
34945
+ created_at: templates.created_at,
34946
+ updated_at: templates.updated_at,
34947
+ file_name: files.file_name,
34948
+ storage_path: files.storage_path,
34949
+ mime_type: files.mime_type,
34950
+ size_bytes: files.size_bytes
34951
+ }).from(templates).innerJoin(files, eq(templates.file_id, files.id)).where(and(eq(templates.name, name), eq(templates.project_id, projectId))).get();
34952
+ return row ?? null;
34953
+ };
34954
+ const checkNameUniqueness = (name, projectId, excludeId) => {
34955
+ if (!projectId)
34956
+ return;
34957
+ const existing = db.select({ id: templates.id }).from(templates).where(and(eq(templates.name, name), eq(templates.project_id, projectId))).get();
34958
+ if (existing && existing.id !== excludeId) {
34959
+ throw new Error(`Duplicate template name '${name}' in project '${projectId}'.`);
34960
+ }
35110
34961
  };
35111
34962
  const create = (input) => {
35112
- const timestamp = nowTimestamp7();
34963
+ const projectId = input.project_id ?? null;
34964
+ checkNameUniqueness(input.name, projectId);
34965
+ const timestamp = nowTimestamp6();
35113
34966
  const template = {
35114
34967
  id: crypto.randomUUID(),
35115
- project_id: input.project_id ?? null,
34968
+ project_id: projectId,
35116
34969
  name: input.name,
35117
34970
  template_type: input.template_type,
35118
- content: input.content,
34971
+ file_id: input.file_id,
34972
+ is_default: input.is_default ?? 0,
35119
34973
  created_at: timestamp,
35120
34974
  updated_at: timestamp
35121
34975
  };
@@ -35123,39 +34977,42 @@ var createTemplatesService = (db) => {
35123
34977
  return template;
35124
34978
  };
35125
34979
  const update = (templateId, input) => {
35126
- const existing = get(templateId);
34980
+ const existing = db.select().from(templates).where(eq(templates.id, templateId)).get();
35127
34981
  if (!existing)
35128
34982
  return null;
35129
- const project_id = input.project_id === undefined ? existing.project_id : input.project_id ?? null;
34983
+ const projectId = input.project_id === undefined ? existing.project_id : input.project_id ?? null;
34984
+ checkNameUniqueness(input.name, projectId, templateId);
35130
34985
  const updated = {
35131
34986
  ...existing,
35132
- project_id,
34987
+ project_id: projectId,
35133
34988
  name: input.name,
35134
34989
  template_type: input.template_type,
35135
- content: input.content,
35136
- updated_at: nowTimestamp7()
34990
+ file_id: input.file_id,
34991
+ is_default: input.is_default ?? existing.is_default,
34992
+ updated_at: nowTimestamp6()
35137
34993
  };
35138
34994
  db.update(templates).set({
35139
34995
  project_id: updated.project_id,
35140
34996
  name: updated.name,
35141
34997
  template_type: updated.template_type,
35142
- content: updated.content,
34998
+ file_id: updated.file_id,
34999
+ is_default: updated.is_default,
35143
35000
  updated_at: updated.updated_at
35144
35001
  }).where(eq(templates.id, templateId)).run();
35145
35002
  return updated;
35146
35003
  };
35147
35004
  const remove = (templateId) => {
35148
- const existing = get(templateId);
35005
+ const existing = db.select().from(templates).where(eq(templates.id, templateId)).get();
35149
35006
  if (!existing)
35150
35007
  return false;
35151
35008
  db.delete(templates).where(eq(templates.id, templateId)).run();
35152
35009
  return true;
35153
35010
  };
35154
- return { list, get, create, update, remove };
35011
+ return { list, listByProject, get, getDefault, getByName, create, update, remove };
35155
35012
  };
35156
35013
 
35157
35014
  // ../schub-db/src/services/ticket-statuses.ts
35158
- var nowTimestamp8 = () => new Date().toISOString();
35015
+ var nowTimestamp7 = () => new Date().toISOString();
35159
35016
  var TICKET_STATUS_COLORS = ["gray", "blue", "cyan", "green", "yellow", "orange", "red", "purple", "pink"];
35160
35017
  var TICKET_COLUMN_ACTIONS = ["archive_all"];
35161
35018
  var isAllowedTicketStatusColor = (color) => TICKET_STATUS_COLORS.includes(color);
@@ -35209,7 +35066,7 @@ var createTicketStatusesService = (db) => {
35209
35066
  assertValidTicketColumnActions(input.column_actions ?? []);
35210
35067
  const normalizedName = normalizeTicketStatusName(input.name);
35211
35068
  assertValidTicketStatusName(normalizedName);
35212
- const timestamp = nowTimestamp8();
35069
+ const timestamp = nowTimestamp7();
35213
35070
  const columnActions = normalizeTicketColumnActions(input.column_actions ?? []);
35214
35071
  const status = {
35215
35072
  id: crypto.randomUUID(),
@@ -35251,7 +35108,7 @@ var createTicketStatusesService = (db) => {
35251
35108
  can_create: input.can_create,
35252
35109
  can_attempt_on_drop: input.can_attempt_on_drop,
35253
35110
  column_actions: JSON.stringify(columnActions),
35254
- updated_at: nowTimestamp8()
35111
+ updated_at: nowTimestamp7()
35255
35112
  };
35256
35113
  db.update(ticket_statuses).set({
35257
35114
  name: updated.name,
@@ -35282,9 +35139,9 @@ var createTicketStatusesService = (db) => {
35282
35139
  }
35283
35140
  if (!remaining.some((status) => status.is_default === 1)) {
35284
35141
  db.update(ticket_statuses).set({ is_default: 0 }).where(eq(ticket_statuses.project_id, projectId)).run();
35285
- db.update(ticket_statuses).set({ is_default: 1, updated_at: nowTimestamp8() }).where(eq(ticket_statuses.id, defaultStatus.id)).run();
35142
+ db.update(ticket_statuses).set({ is_default: 1, updated_at: nowTimestamp7() }).where(eq(ticket_statuses.id, defaultStatus.id)).run();
35286
35143
  }
35287
- db.update(tickets).set({ status_id: defaultStatus.id, updated_at: nowTimestamp8() }).where(eq(tickets.status_id, statusId)).run();
35144
+ db.update(tickets).set({ status_id: defaultStatus.id, updated_at: nowTimestamp7() }).where(eq(tickets.status_id, statusId)).run();
35288
35145
  db.delete(ticket_statuses).where(and(eq(ticket_statuses.project_id, projectId), eq(ticket_statuses.id, statusId))).run();
35289
35146
  return { ok: true, reassignedTo: defaultStatus.id };
35290
35147
  };
@@ -35292,7 +35149,7 @@ var createTicketStatusesService = (db) => {
35292
35149
  };
35293
35150
 
35294
35151
  // ../schub-db/src/services/ticket-tags.ts
35295
- var nowTimestamp9 = () => new Date().toISOString();
35152
+ var nowTimestamp8 = () => new Date().toISOString();
35296
35153
  var normalizeTagName = (name) => name.trim().toLowerCase().replace(/[\s-]+/g, "_");
35297
35154
  var createTicketTagsService = (db) => {
35298
35155
  const list = (projectId) => db.select().from(ticket_tags).where(eq(ticket_tags.project_id, projectId)).orderBy(ticket_tags.name).all();
@@ -35301,7 +35158,7 @@ var createTicketTagsService = (db) => {
35301
35158
  return tag ?? null;
35302
35159
  };
35303
35160
  const create = (projectId, input) => {
35304
- const timestamp = nowTimestamp9();
35161
+ const timestamp = nowTimestamp8();
35305
35162
  const tag = {
35306
35163
  id: crypto.randomUUID(),
35307
35164
  project_id: projectId,
@@ -35321,7 +35178,7 @@ var createTicketTagsService = (db) => {
35321
35178
  ...existing,
35322
35179
  name: normalizeTagName(input.name),
35323
35180
  color: input.color,
35324
- updated_at: nowTimestamp9()
35181
+ updated_at: nowTimestamp8()
35325
35182
  };
35326
35183
  db.update(ticket_tags).set({
35327
35184
  name: updated.name,
@@ -35367,7 +35224,7 @@ var createTicketTagsService = (db) => {
35367
35224
  id: crypto.randomUUID(),
35368
35225
  ticket_id: ticketId,
35369
35226
  ticket_tag_id: tagId,
35370
- created_at: nowTimestamp9()
35227
+ created_at: nowTimestamp8()
35371
35228
  }))).run();
35372
35229
  });
35373
35230
  return listIdsForTicket(ticketId);
@@ -35376,7 +35233,7 @@ var createTicketTagsService = (db) => {
35376
35233
  };
35377
35234
 
35378
35235
  // ../schub-db/src/services/tickets.ts
35379
- var nowTimestamp10 = () => new Date().toISOString();
35236
+ var nowTimestamp9 = () => new Date().toISOString();
35380
35237
  var formatTicketId = (sequence) => `TK${sequence.toString().padStart(4, "0")}`;
35381
35238
  var parseTicketSequence = (value) => {
35382
35239
  const match2 = value.match(/^TK(\d+)$/i);
@@ -35443,13 +35300,12 @@ var createTicketsService = (db) => {
35443
35300
  throw new Error("Parent ticket not found");
35444
35301
  }
35445
35302
  }
35446
- const timestamp = nowTimestamp10();
35303
+ const timestamp = nowTimestamp9();
35447
35304
  const shorthand = allocateTicketShorthand();
35448
35305
  const ticket = {
35449
35306
  id: crypto.randomUUID(),
35450
35307
  shorthand,
35451
35308
  project_id: input.project_id,
35452
- proposal_id: input.proposal_id ?? null,
35453
35309
  status_id: input.status_id ?? null,
35454
35310
  parent_id: input.parent_id ?? null,
35455
35311
  title: input.title ?? null,
@@ -35472,7 +35328,7 @@ var createTicketsService = (db) => {
35472
35328
  const existing = get(ticketId);
35473
35329
  if (!existing)
35474
35330
  return null;
35475
- if (input.parent_id !== undefined) {
35331
+ if (input.parent_id !== undefined && input.parent_id !== existing.parent_id) {
35476
35332
  validateParentId(existing.id, input.parent_id, get);
35477
35333
  }
35478
35334
  const title = input.title === undefined ? existing.title : input.title;
@@ -35481,7 +35337,6 @@ var createTicketsService = (db) => {
35481
35337
  const parallelizable = input.parallelizable === undefined ? existing.parallelizable : input.parallelizable;
35482
35338
  const complexity = input.complexity === undefined ? existing.complexity : input.complexity;
35483
35339
  const status_id = input.status_id === undefined ? existing.status_id : input.status_id;
35484
- const proposal_id = input.proposal_id === undefined ? existing.proposal_id : input.proposal_id;
35485
35340
  const parent_id = input.parent_id === undefined ? existing.parent_id : input.parent_id;
35486
35341
  const blocked_reason = input.blocked_reason === undefined ? existing.blocked_reason : input.blocked_reason;
35487
35342
  const depends_on = input.depends_on === undefined ? existing.depends_on : input.depends_on;
@@ -35495,13 +35350,12 @@ var createTicketsService = (db) => {
35495
35350
  parallelizable,
35496
35351
  complexity,
35497
35352
  status_id,
35498
- proposal_id,
35499
35353
  parent_id,
35500
35354
  blocked_reason,
35501
35355
  depends_on,
35502
35356
  archived,
35503
35357
  staged,
35504
- updated_at: nowTimestamp10()
35358
+ updated_at: nowTimestamp9()
35505
35359
  };
35506
35360
  db.update(tickets).set({
35507
35361
  title: updated.title,
@@ -35510,7 +35364,6 @@ var createTicketsService = (db) => {
35510
35364
  parallelizable: updated.parallelizable,
35511
35365
  complexity: updated.complexity,
35512
35366
  status_id: updated.status_id,
35513
- proposal_id: updated.proposal_id,
35514
35367
  parent_id: updated.parent_id,
35515
35368
  blocked_reason: updated.blocked_reason,
35516
35369
  depends_on: updated.depends_on,
@@ -35525,105 +35378,12 @@ var createTicketsService = (db) => {
35525
35378
  if (!existing)
35526
35379
  return false;
35527
35380
  db.update(tickets).set({ parent_id: null }).where(eq(tickets.parent_id, existing.id)).run();
35528
- db.update(tickets).set({ deleted_at: nowTimestamp10(), updated_at: nowTimestamp10() }).where(eq(tickets.id, existing.id)).run();
35529
- return true;
35530
- };
35531
- const getForProposal = (projectId, proposalId, ticketId) => {
35532
- const ticket = db.select().from(tickets).where(and(eq(tickets.project_id, projectId), eq(tickets.proposal_id, proposalId), or(eq(tickets.id, ticketId), eq(tickets.shorthand, ticketId)), isNull(tickets.deleted_at))).get();
35533
- return ticket ?? null;
35534
- };
35535
- const listByProposal = (projectId, proposalId) => db.select().from(tickets).where(and(eq(tickets.project_id, projectId), eq(tickets.proposal_id, proposalId), isNull(tickets.deleted_at))).orderBy(tickets.created_at).all();
35536
- const createForProposal = (projectId, proposalId, input) => {
35537
- if (input.parent_id) {
35538
- const parent = get(input.parent_id);
35539
- if (!parent) {
35540
- throw new Error("Parent ticket not found");
35541
- }
35542
- }
35543
- const timestamp = nowTimestamp10();
35544
- const shorthand = allocateTicketShorthand();
35545
- const ticket = {
35546
- id: crypto.randomUUID(),
35547
- shorthand,
35548
- project_id: projectId,
35549
- proposal_id: proposalId,
35550
- status_id: input.status_id ?? null,
35551
- parent_id: input.parent_id ?? null,
35552
- title: input.title ?? null,
35553
- input: input.input ?? null,
35554
- priority: input.priority ?? null,
35555
- parallelizable: input.parallelizable ?? null,
35556
- complexity: input.complexity ?? null,
35557
- blocked_reason: input.blocked_reason ?? null,
35558
- depends_on: input.depends_on ?? null,
35559
- archived: input.archived ?? 0,
35560
- staged: input.staged ?? 0,
35561
- deleted_at: null,
35562
- created_at: timestamp,
35563
- updated_at: timestamp
35564
- };
35565
- db.insert(tickets).values(ticket).run();
35566
- return ticket;
35567
- };
35568
- const updateForProposal = (projectId, proposalId, ticketId, input) => {
35569
- const existing = getForProposal(projectId, proposalId, ticketId);
35570
- if (!existing)
35571
- return null;
35572
- if (input.parent_id !== undefined) {
35573
- validateParentId(existing.id, input.parent_id, get);
35574
- }
35575
- const title = input.title === undefined ? existing.title : input.title;
35576
- const ticketInput = input.input === undefined ? existing.input : input.input;
35577
- const priority = input.priority === undefined ? existing.priority : input.priority;
35578
- const parallelizable = input.parallelizable === undefined ? existing.parallelizable : input.parallelizable;
35579
- const complexity = input.complexity === undefined ? existing.complexity : input.complexity;
35580
- const status_id = input.status_id === undefined ? existing.status_id : input.status_id;
35581
- const parent_id = input.parent_id === undefined ? existing.parent_id : input.parent_id;
35582
- const blocked_reason = input.blocked_reason === undefined ? existing.blocked_reason : input.blocked_reason;
35583
- const depends_on = input.depends_on === undefined ? existing.depends_on : input.depends_on;
35584
- const archived = input.archived === undefined ? existing.archived : input.archived;
35585
- const staged = input.staged === undefined ? existing.staged : input.staged;
35586
- const updated = {
35587
- ...existing,
35588
- title,
35589
- input: ticketInput,
35590
- priority,
35591
- parallelizable,
35592
- complexity,
35593
- status_id,
35594
- parent_id,
35595
- blocked_reason,
35596
- depends_on,
35597
- archived,
35598
- staged,
35599
- updated_at: nowTimestamp10()
35600
- };
35601
- db.update(tickets).set({
35602
- title: updated.title,
35603
- input: updated.input,
35604
- priority: updated.priority,
35605
- parallelizable: updated.parallelizable,
35606
- complexity: updated.complexity,
35607
- status_id: updated.status_id,
35608
- parent_id: updated.parent_id,
35609
- blocked_reason: updated.blocked_reason,
35610
- depends_on: updated.depends_on,
35611
- archived: updated.archived,
35612
- staged: updated.staged,
35613
- updated_at: updated.updated_at
35614
- }).where(and(eq(tickets.project_id, projectId), eq(tickets.proposal_id, proposalId), eq(tickets.id, existing.id))).run();
35615
- return updated;
35616
- };
35617
- const removeForProposal = (projectId, proposalId, ticketId) => {
35618
- const existing = getForProposal(projectId, proposalId, ticketId);
35619
- if (!existing)
35620
- return false;
35621
- db.update(tickets).set({ deleted_at: nowTimestamp10(), updated_at: nowTimestamp10() }).where(and(eq(tickets.project_id, projectId), eq(tickets.proposal_id, proposalId), eq(tickets.id, existing.id))).run();
35381
+ db.update(tickets).set({ deleted_at: nowTimestamp9(), updated_at: nowTimestamp9() }).where(eq(tickets.id, existing.id)).run();
35622
35382
  return true;
35623
35383
  };
35624
35384
  const save = (input) => {
35625
35385
  const existing = get(input.id);
35626
- const timestamp = nowTimestamp10();
35386
+ const timestamp = nowTimestamp9();
35627
35387
  const normalizedShorthand = input.shorthand ? normalizeTicketShorthand(input.shorthand) : normalizeTicketShorthand(input.id);
35628
35388
  if (existing) {
35629
35389
  db.update(tickets).set({
@@ -35634,7 +35394,6 @@ var createTicketsService = (db) => {
35634
35394
  parallelizable: input.parallelizable ?? existing.parallelizable,
35635
35395
  complexity: input.complexity ?? existing.complexity,
35636
35396
  status_id: input.status_id ?? existing.status_id,
35637
- proposal_id: input.proposal_id ?? existing.proposal_id,
35638
35397
  parent_id: input.parent_id ?? existing.parent_id,
35639
35398
  blocked_reason: input.blocked_reason ?? existing.blocked_reason,
35640
35399
  depends_on: input.depends_on ?? existing.depends_on,
@@ -35651,7 +35410,6 @@ var createTicketsService = (db) => {
35651
35410
  parallelizable: input.parallelizable ?? existing.parallelizable,
35652
35411
  complexity: input.complexity ?? existing.complexity,
35653
35412
  status_id: input.status_id ?? existing.status_id,
35654
- proposal_id: input.proposal_id ?? existing.proposal_id,
35655
35413
  parent_id: input.parent_id ?? existing.parent_id,
35656
35414
  blocked_reason: input.blocked_reason ?? existing.blocked_reason,
35657
35415
  depends_on: input.depends_on ?? existing.depends_on,
@@ -35664,7 +35422,6 @@ var createTicketsService = (db) => {
35664
35422
  id: UUID_PATTERN.test(input.id) ? input.id : crypto.randomUUID(),
35665
35423
  shorthand: normalizedShorthand ?? allocateTicketShorthand(),
35666
35424
  project_id: input.project_id,
35667
- proposal_id: input.proposal_id ?? null,
35668
35425
  status_id: input.status_id ?? null,
35669
35426
  parent_id: input.parent_id ?? null,
35670
35427
  title: input.title ?? null,
@@ -35690,16 +35447,12 @@ var createTicketsService = (db) => {
35690
35447
  create,
35691
35448
  update,
35692
35449
  remove,
35693
- save,
35694
- listByProposal,
35695
- createForProposal,
35696
- updateForProposal,
35697
- removeForProposal
35450
+ save
35698
35451
  };
35699
35452
  };
35700
35453
 
35701
35454
  // ../schub-db/src/services/workspace-artifacts.ts
35702
- var nowTimestamp11 = () => new Date().toISOString();
35455
+ var nowTimestamp10 = () => new Date().toISOString();
35703
35456
  var createWorkspaceArtifactsService = (db) => {
35704
35457
  const list = (ticketId) => db.select({
35705
35458
  id: workspace_artifacts.id,
@@ -35717,7 +35470,7 @@ var createWorkspaceArtifactsService = (db) => {
35717
35470
  return artifact ?? null;
35718
35471
  };
35719
35472
  const upsertByPath = (ticketId, input) => {
35720
- const timestamp = nowTimestamp11();
35473
+ const timestamp = nowTimestamp10();
35721
35474
  const existing = db.select().from(workspace_artifacts).where(and(eq(workspace_artifacts.ticket_id, ticketId), eq(workspace_artifacts.relative_path, input.relative_path))).get();
35722
35475
  if (existing) {
35723
35476
  db.update(workspace_artifacts).set({ file_id: input.file_id }).where(eq(workspace_artifacts.id, existing.id)).run();
@@ -35762,7 +35515,7 @@ var createWorkspaceArtifactsService = (db) => {
35762
35515
  };
35763
35516
 
35764
35517
  // ../schub-db/src/services/workspaces.ts
35765
- var nowTimestamp12 = () => new Date().toISOString();
35518
+ var nowTimestamp11 = () => new Date().toISOString();
35766
35519
  var formatWorkspaceShorthand = (sequence) => `A${sequence.toString().padStart(4, "0")}`;
35767
35520
  var parseWorkspaceShorthand = (value) => {
35768
35521
  if (!value) {
@@ -35833,7 +35586,7 @@ var createWorkspacesService = (db) => {
35833
35586
  }).run();
35834
35587
  };
35835
35588
  const create = (input) => {
35836
- const timestamp = nowTimestamp12();
35589
+ const timestamp = nowTimestamp11();
35837
35590
  const workspaceShorthand = input.workspace_shorthand === undefined || input.workspace_shorthand === null ? null : normalizeWorkspaceShorthand(input.workspace_shorthand);
35838
35591
  const workspace = {
35839
35592
  id: input.id?.trim() || crypto.randomUUID(),
@@ -35864,7 +35617,7 @@ var createWorkspacesService = (db) => {
35864
35617
  worktree_path: input.worktree_path === undefined ? existing.worktree_path ?? null : input.worktree_path,
35865
35618
  workspace_shorthand: resolveWorkspaceShorthandForUpdate(input.workspace_shorthand, existing.workspace_shorthand),
35866
35619
  ticket_id: input.ticket_id === undefined ? existing.ticket_id : input.ticket_id,
35867
- updated_at: nowTimestamp12()
35620
+ updated_at: nowTimestamp11()
35868
35621
  };
35869
35622
  db.update(workspaces).set({
35870
35623
  name: updated.name,
@@ -35907,10 +35660,8 @@ var createDbServices = (config2 = {}) => {
35907
35660
  const docs = createDocsService();
35908
35661
  const files2 = createFilesService(db, storageRoot);
35909
35662
  const gitDiff = createGitDiffService();
35910
- const proposalStatuses = createProposalStatusesService(db);
35911
35663
  const projects2 = createProjectsService(db);
35912
35664
  const projectDocs = createProjectDocsService(db, files2);
35913
- const proposals2 = createProposalsService(db);
35914
35665
  const repos2 = createReposService(db);
35915
35666
  const sessions2 = createSessionsService(db);
35916
35667
  const settings = createSettingsService(config2.settingsPath);
@@ -35925,10 +35676,8 @@ var createDbServices = (config2 = {}) => {
35925
35676
  docs,
35926
35677
  files: files2,
35927
35678
  gitDiff,
35928
- proposalStatuses,
35929
35679
  projects: projects2,
35930
35680
  projectDocs,
35931
- proposals: proposals2,
35932
35681
  repos: repos2,
35933
35682
  sessions: sessions2,
35934
35683
  settings,
@@ -36413,13 +36162,6 @@ var fileParamsSchema = exports_external.object({
36413
36162
  var fileQuerySchema = exports_external.object({
36414
36163
  project_id: exports_external.string()
36415
36164
  });
36416
- var proposalParamsSchema = exports_external.object({
36417
- proposal_id: exports_external.string()
36418
- });
36419
- var proposalFileParamsSchema = exports_external.object({
36420
- proposal_id: exports_external.string(),
36421
- file_id: exports_external.string()
36422
- });
36423
36165
  var attachFileSchema = exports_external.object({
36424
36166
  file_id: exports_external.string()
36425
36167
  });
@@ -36427,51 +36169,6 @@ var updateFileSchema = exports_external.object({
36427
36169
  content: exports_external.string()
36428
36170
  });
36429
36171
 
36430
- // ../schub-api/src/features/files/endpoints/attach-proposal-file.ts
36431
- var attachProposalFileRoute = (path5) => createRoute({
36432
- method: "post",
36433
- path: path5,
36434
- tags: ["Files"],
36435
- summary: "Attach file to proposal",
36436
- request: {
36437
- params: proposalParamsSchema,
36438
- body: {
36439
- content: {
36440
- "application/json": {
36441
- schema: attachFileSchema
36442
- }
36443
- }
36444
- }
36445
- },
36446
- responses: {
36447
- 201: {
36448
- description: "File attached",
36449
- content: {
36450
- "application/json": {
36451
- schema: apiSuccessSchema(fileSchema)
36452
- }
36453
- }
36454
- },
36455
- 404: {
36456
- description: "Not found",
36457
- content: {
36458
- "application/json": {
36459
- schema: apiErrorSchema
36460
- }
36461
- }
36462
- }
36463
- }
36464
- });
36465
- var attachProposalFile = (c) => {
36466
- const { proposal_id } = c.req.valid("param");
36467
- const payload = c.req.valid("json");
36468
- const attached = c.get("services").files.attachToProposal(proposal_id, payload.file_id);
36469
- if (!attached) {
36470
- return c.json(errorResponse("File not found"), 404);
36471
- }
36472
- return c.json(okResponse(attached), 201);
36473
- };
36474
-
36475
36172
  // ../schub-api/src/features/files/endpoints/delete-file.ts
36476
36173
  var deleteFileRoute = (path5) => createRoute({
36477
36174
  method: "delete",
@@ -36504,38 +36201,6 @@ var deleteFile = (c) => {
36504
36201
  return c.body(null, 204);
36505
36202
  };
36506
36203
 
36507
- // ../schub-api/src/features/files/endpoints/detach-proposal-file.ts
36508
- var detachProposalFileRoute = (path5) => createRoute({
36509
- method: "delete",
36510
- path: path5,
36511
- tags: ["Files"],
36512
- summary: "Detach file from proposal",
36513
- request: {
36514
- params: proposalFileParamsSchema
36515
- },
36516
- responses: {
36517
- 204: {
36518
- description: "File detached"
36519
- },
36520
- 404: {
36521
- description: "Not found",
36522
- content: {
36523
- "application/json": {
36524
- schema: apiErrorSchema
36525
- }
36526
- }
36527
- }
36528
- }
36529
- });
36530
- var detachProposalFile = (c) => {
36531
- const { proposal_id, file_id } = c.req.valid("param");
36532
- const removed = c.get("services").files.detachFromProposal(proposal_id, file_id);
36533
- if (!removed) {
36534
- return c.json(errorResponse("File attachment not found"), 404);
36535
- }
36536
- return c.body(null, 204);
36537
- };
36538
-
36539
36204
  // ../schub-api/src/features/files/endpoints/get-file.ts
36540
36205
  import { readFile as readFile3 } from "node:fs/promises";
36541
36206
  var getFileRoute = (path5) => createRoute({
@@ -36602,32 +36267,6 @@ var listFiles = (c) => {
36602
36267
  return c.json(okResponse(files2), 200);
36603
36268
  };
36604
36269
 
36605
- // ../schub-api/src/features/files/endpoints/list-proposal-files.ts
36606
- var listProposalFilesRoute = (path5) => createRoute({
36607
- method: "get",
36608
- path: path5,
36609
- tags: ["Files"],
36610
- summary: "List proposal files",
36611
- request: {
36612
- params: proposalParamsSchema
36613
- },
36614
- responses: {
36615
- 200: {
36616
- description: "Proposal files list",
36617
- content: {
36618
- "application/json": {
36619
- schema: apiSuccessSchema(exports_external.array(fileSchema))
36620
- }
36621
- }
36622
- }
36623
- }
36624
- });
36625
- var listProposalFiles = (c) => {
36626
- const { proposal_id } = c.req.valid("param");
36627
- const files2 = c.get("services").files.listForProposal(proposal_id);
36628
- return c.json(okResponse(files2), 200);
36629
- };
36630
-
36631
36270
  // ../schub-api/src/features/files/endpoints/update-file.ts
36632
36271
  var updateFileRoute = (path5) => createRoute({
36633
36272
  method: "put",
@@ -36747,16 +36386,6 @@ var createFileRoutes = () => {
36747
36386
  routes.openapi(deleteRoute, deleteFile);
36748
36387
  return routes;
36749
36388
  };
36750
- var createProposalFileRoutes = () => {
36751
- const routes = new OpenAPIHono;
36752
- const listForProposalRoute = listProposalFilesRoute("/");
36753
- routes.openapi(listForProposalRoute, listProposalFiles);
36754
- const attachRoute = attachProposalFileRoute("/");
36755
- routes.openapi(attachRoute, attachProposalFile);
36756
- const detachRoute = detachProposalFileRoute("/:file_id");
36757
- routes.openapi(detachRoute, detachProposalFile);
36758
- return routes;
36759
- };
36760
36389
 
36761
36390
  // ../schub-api/src/features/filesystem/helpers/filesystem.ts
36762
36391
  import { readdir, stat } from "node:fs/promises";
@@ -36989,18 +36618,23 @@ var createImageRoutes = () => {
36989
36618
  return routes;
36990
36619
  };
36991
36620
 
36621
+ // ../schub-api/src/features/project-template-assets/endpoints/list-project-template-assets.ts
36622
+ import { readFileSync as readFileSync5 } from "node:fs";
36623
+
36992
36624
  // ../schub-api/src/features/project-template-assets/helpers/template-assets.ts
36993
36625
  import { existsSync as existsSync3, readFileSync as readFileSync4 } from "node:fs";
36994
36626
  import { dirname as dirname4, join as join6, resolve as resolve3 } from "node:path";
36995
36627
  import { fileURLToPath as fileURLToPath2 } from "node:url";
36996
36628
 
36997
36629
  // ../schub-api/src/features/project-template-assets/schemas.ts
36998
- var PROJECT_TEMPLATE_TYPES = ["template", "skill"];
36630
+ var PROJECT_TEMPLATE_TYPES = ["artifact", "ticket-template", "skill"];
36999
36631
  var projectTemplateAssetSchema = exports_external.object({
37000
36632
  id: exports_external.string(),
37001
36633
  project_id: exports_external.string().nullable(),
37002
36634
  name: exports_external.string(),
37003
36635
  template_type: exports_external.enum(PROJECT_TEMPLATE_TYPES),
36636
+ file_id: exports_external.string(),
36637
+ is_default: exports_external.number(),
37004
36638
  content: exports_external.string(),
37005
36639
  created_at: exports_external.string(),
37006
36640
  updated_at: exports_external.string()
@@ -37018,50 +36652,48 @@ var updateProjectTemplateAssetSchema = exports_external.object({
37018
36652
 
37019
36653
  // ../schub-api/src/features/project-template-assets/helpers/template-assets.ts
37020
36654
  var DEFAULT_PROJECT_TEMPLATE_ASSETS = [
36655
+ { name: "Ticket", template_type: "ticket-template", source_path: "templates/ticket-template.md", is_default: 1 },
36656
+ { name: "Proposal", template_type: "ticket-template", source_path: "templates/proposal-template.md", is_default: 0 },
36657
+ { name: "ADR", template_type: "artifact", source_path: "templates/adr-template.md", is_default: 0 },
36658
+ { name: "Cookbook", template_type: "artifact", source_path: "templates/cookbook-template.md", is_default: 0 },
36659
+ { name: "Review", template_type: "artifact", source_path: "templates/review-me-template.md", is_default: 0 },
36660
+ { name: "Q&A", template_type: "artifact", source_path: "templates/q&a-template.md", is_default: 0 },
37021
36661
  {
37022
- name: "create-proposal/proposal-template.md",
37023
- template_type: "template",
37024
- source_path: "templates/create-proposal/proposal-template.md"
37025
- },
37026
- {
37027
- name: "create-proposal/cookbook-template.md",
37028
- template_type: "template",
37029
- source_path: "templates/create-proposal/cookbook-template.md"
37030
- },
37031
- {
37032
- name: "create-proposal/adr-template.md",
37033
- template_type: "template",
37034
- source_path: "templates/create-proposal/adr-template.md"
36662
+ name: "create-proposal/SKILL.md",
36663
+ template_type: "skill",
36664
+ source_path: "skills/create-proposal/SKILL.md",
36665
+ is_default: 0
37035
36666
  },
37036
36667
  {
37037
- name: "create-ticket/ticket-template.md",
37038
- template_type: "template",
37039
- source_path: "templates/create-ticket/ticket-template.md"
36668
+ name: "create-ticket/SKILL.md",
36669
+ template_type: "skill",
36670
+ source_path: "skills/create-ticket/SKILL.md",
36671
+ is_default: 0
37040
36672
  },
37041
36673
  {
37042
- name: "create-tasks/task-template.md",
37043
- template_type: "template",
37044
- source_path: "templates/create-tasks/task-template.md"
36674
+ name: "create-sub-tickets/SKILL.md",
36675
+ template_type: "skill",
36676
+ source_path: "skills/create-sub-tickets/SKILL.md",
36677
+ is_default: 0
37045
36678
  },
37046
36679
  {
37047
- name: "review-proposal/review-me-template.md",
37048
- template_type: "template",
37049
- source_path: "templates/review-proposal/review-me-template.md"
36680
+ name: "implement-ticket/SKILL.md",
36681
+ template_type: "skill",
36682
+ source_path: "skills/implement-ticket/SKILL.md",
36683
+ is_default: 0
37050
36684
  },
37051
36685
  {
37052
- name: "review-proposal/q&a-template.md",
37053
- template_type: "template",
37054
- source_path: "templates/review-proposal/q&a-template.md"
36686
+ name: "refine-ticket/SKILL.md",
36687
+ template_type: "skill",
36688
+ source_path: "skills/refine-ticket/SKILL.md",
36689
+ is_default: 0
37055
36690
  },
37056
- { name: "create-proposal/SKILL.md", template_type: "skill", source_path: "skills/create-proposal/SKILL.md" },
37057
- { name: "create-ticket/SKILL.md", template_type: "skill", source_path: "skills/create-ticket/SKILL.md" },
37058
36691
  {
37059
- name: "create-sub-tickets/SKILL.md",
36692
+ name: "update-documentation/SKILL.md",
37060
36693
  template_type: "skill",
37061
- source_path: "skills/create-sub-tickets/SKILL.md"
37062
- },
37063
- { name: "implement-ticket/SKILL.md", template_type: "skill", source_path: "skills/implement-ticket/SKILL.md" },
37064
- { name: "review-proposal/SKILL.md", template_type: "skill", source_path: "skills/review-proposal/SKILL.md" }
36694
+ source_path: "skills/update-documentation/SKILL.md",
36695
+ is_default: 0
36696
+ }
37065
36697
  ];
37066
36698
  var templateTypeOrder = new Map(PROJECT_TEMPLATE_TYPES.map((type, index) => [type, index]));
37067
36699
  var isProjectTemplateType = (value) => PROJECT_TEMPLATE_TYPES.includes(value);
@@ -37079,15 +36711,16 @@ var loadBundledTemplateAssets = () => {
37079
36711
  if (!schubRoot)
37080
36712
  return [];
37081
36713
  return DEFAULT_PROJECT_TEMPLATE_ASSETS.flatMap((asset) => {
37082
- const storagePath = join6(schubRoot, asset.source_path);
37083
- if (!existsSync3(storagePath)) {
36714
+ const sourcePath = join6(schubRoot, asset.source_path);
36715
+ if (!existsSync3(sourcePath)) {
37084
36716
  return [];
37085
36717
  }
37086
36718
  return [
37087
36719
  {
37088
36720
  name: asset.name,
37089
36721
  template_type: asset.template_type,
37090
- content: readFileSync4(storagePath, "utf8")
36722
+ is_default: asset.is_default,
36723
+ data: Buffer.from(readFileSync4(sourcePath, "utf8"))
37091
36724
  }
37092
36725
  ];
37093
36726
  });
@@ -37109,11 +36742,18 @@ var seedMissingAssets = (context, projectId) => {
37109
36742
  const key = `${asset.template_type}:${asset.name}`;
37110
36743
  if (existingKeys.has(key))
37111
36744
  return;
36745
+ const file2 = services.files.upload({
36746
+ project_id: projectId,
36747
+ file_name: asset.name,
36748
+ file_kind: "template",
36749
+ data: asset.data
36750
+ });
37112
36751
  services.templates.create({
37113
36752
  project_id: projectId,
37114
36753
  name: asset.name,
37115
36754
  template_type: asset.template_type,
37116
- content: asset.content
36755
+ file_id: file2.id,
36756
+ is_default: asset.is_default
37117
36757
  });
37118
36758
  });
37119
36759
  };
@@ -37154,11 +36794,16 @@ var listProjectTemplateAssets = (c) => {
37154
36794
  return c.json(errorResponse("Project not found"), 404);
37155
36795
  }
37156
36796
  seedMissingAssets(c, project_id);
37157
- const assets = sortAssets(services.templates.list().filter((asset) => asset.project_id === project_id && isProjectTemplateType(asset.template_type)));
36797
+ const templates2 = sortAssets(services.templates.list().filter((asset) => asset.project_id === project_id && isProjectTemplateType(asset.template_type)));
36798
+ const assets = templates2.map((asset) => ({
36799
+ ...asset,
36800
+ content: readFileSync5(asset.storage_path, "utf8")
36801
+ }));
37158
36802
  return c.json(okResponse(assets), 200);
37159
36803
  };
37160
36804
 
37161
36805
  // ../schub-api/src/features/project-template-assets/endpoints/update-project-template-asset.ts
36806
+ import { readFileSync as readFileSync6 } from "node:fs";
37162
36807
  var updateProjectTemplateAssetRoute = (path5) => createRoute({
37163
36808
  method: "put",
37164
36809
  path: path5,
@@ -37205,16 +36850,12 @@ var updateProjectTemplateAsset = (c) => {
37205
36850
  if (!existing || existing.project_id !== project_id || !isProjectTemplateType(existing.template_type)) {
37206
36851
  return c.json(errorResponse("Template asset not found"), 404);
37207
36852
  }
37208
- const updated = services.templates.update(asset_id, {
37209
- project_id,
37210
- name: existing.name,
37211
- template_type: existing.template_type,
37212
- content
37213
- });
36853
+ services.files.update(existing.file_id, { data: Buffer.from(content) });
36854
+ const updated = services.templates.get(asset_id);
37214
36855
  if (!updated) {
37215
36856
  return c.json(errorResponse("Template asset not found"), 404);
37216
36857
  }
37217
- return c.json(okResponse(updated), 200);
36858
+ return c.json(okResponse({ ...updated, content: readFileSync6(updated.storage_path, "utf8") }), 200);
37218
36859
  };
37219
36860
 
37220
36861
  // ../schub-api/src/features/project-template-assets/routes.ts
@@ -37572,642 +37213,43 @@ var streamProjects = (upgradeWebSocket) => upgradeWebSocket((c) => {
37572
37213
  onOpen: (_event, ws) => {
37573
37214
  const snapshot = services.projects.getSnapshotPatch();
37574
37215
  ws.send(JSON.stringify({ JsonPatch: snapshot }));
37575
- ws.send(JSON.stringify({ Ready: true }));
37576
- unsubscribe = services.projects.subscribe((patches) => {
37577
- ws.send(JSON.stringify({ JsonPatch: patches }));
37578
- });
37579
- },
37580
- onMessage: () => {},
37581
- onClose: () => {
37582
- if (unsubscribe) {
37583
- unsubscribe();
37584
- unsubscribe = null;
37585
- }
37586
- }
37587
- };
37588
- });
37589
-
37590
- // ../schub-api/src/features/projects/endpoints/update-project.ts
37591
- var updateProjectRoute = (path5) => createRoute({
37592
- method: "put",
37593
- path: path5,
37594
- tags: ["Projects"],
37595
- summary: "Update project",
37596
- request: {
37597
- params: projectIdParamsSchema2,
37598
- body: {
37599
- content: {
37600
- "application/json": {
37601
- schema: updateProjectSchema
37602
- }
37603
- }
37604
- }
37605
- },
37606
- responses: {
37607
- 200: {
37608
- description: "Project updated",
37609
- content: {
37610
- "application/json": {
37611
- schema: apiSuccessSchema(projectSchema)
37612
- }
37613
- }
37614
- },
37615
- 404: {
37616
- description: "Not found",
37617
- content: {
37618
- "application/json": {
37619
- schema: apiErrorSchema
37620
- }
37621
- }
37622
- }
37623
- }
37624
- });
37625
- var updateProject = (c) => {
37626
- const { id } = c.req.valid("param");
37627
- const payload = c.req.valid("json");
37628
- const project = c.get("services").projects.update(id, payload);
37629
- if (!project) {
37630
- return c.json(errorResponse("Project not found"), 404);
37631
- }
37632
- return c.json(okResponse(project), 200);
37633
- };
37634
-
37635
- // ../schub-api/src/features/projects/routes.ts
37636
- var createProjectRoutes = (upgradeWebSocket) => {
37637
- const routes = new OpenAPIHono;
37638
- const listRoute = listProjectsRoute("/");
37639
- routes.openapi(listRoute, listProjects);
37640
- const createRoute2 = createProjectRoute("/");
37641
- routes.openapi(createRoute2, createProject);
37642
- const getRoute = getProjectRoute("/:id");
37643
- routes.openapi(getRoute, getProject);
37644
- const updateRoute = updateProjectRoute("/:id");
37645
- routes.openapi(updateRoute, updateProject);
37646
- const deleteRoute = deleteProjectRoute("/:id");
37647
- routes.openapi(deleteRoute, deleteProject);
37648
- const listReposRoute = listProjectRepositoriesRoute("/:id/repositories");
37649
- routes.openapi(listReposRoute, listProjectRepositories);
37650
- const addRepoRoute = addProjectRepositoryRoute("/:id/repositories");
37651
- routes.openapi(addRepoRoute, addProjectRepository);
37652
- const getRepoRoute = getProjectRepositoryRoute("/:project_id/repositories/:repo_id");
37653
- routes.openapi(getRepoRoute, getProjectRepository);
37654
- const deleteRepoRoute = deleteProjectRepositoryRoute("/:project_id/repositories/:repo_id");
37655
- routes.openapi(deleteRepoRoute, deleteProjectRepository);
37656
- const streamRoute = streamProjectsRoute("/stream/ws");
37657
- routes.openapi(streamRoute, streamProjects(upgradeWebSocket));
37658
- const searchRoute = searchProjectRoute("/:id/search");
37659
- routes.openapi(searchRoute, searchProject);
37660
- const openEditorRoute = openProjectEditorRoute("/:id/open-editor");
37661
- routes.openapi(openEditorRoute, openProjectEditor);
37662
- return routes;
37663
- };
37664
-
37665
- // ../schub-api/src/features/tickets/helpers/ticket-responses.ts
37666
- import { readFileSync as readFileSync5 } from "node:fs";
37667
- var readTicketContent = (services, ticketId) => {
37668
- const files2 = services.files.listForTicket(ticketId);
37669
- const contentFile = files2.find((f) => f.file_kind === "ticket");
37670
- if (!contentFile)
37671
- return null;
37672
- try {
37673
- return readFileSync5(contentFile.storage_path, "utf8");
37674
- } catch {
37675
- return null;
37676
- }
37677
- };
37678
- var uploadTicketContent = (services, projectId, ticketId, content) => {
37679
- const existingFiles = services.files.listForTicket(ticketId);
37680
- const contentFile = existingFiles.find((f) => f.file_kind === "ticket");
37681
- const contentBuffer = Buffer.from(content, "utf8");
37682
- if (contentFile) {
37683
- services.files.update(contentFile.id, { data: contentBuffer });
37684
- } else {
37685
- const file2 = services.files.upload({
37686
- project_id: projectId,
37687
- file_name: "ticket.md",
37688
- file_kind: "ticket",
37689
- data: contentBuffer
37690
- });
37691
- services.files.attachToTicket(ticketId, file2.id);
37692
- }
37693
- };
37694
- var buildAttemptPrompt = (ticketShorthand, ticketContent, fallback) => {
37695
- const content = ticketContent?.trim();
37696
- const body = content && content.length > 0 ? content : fallback;
37697
- return `Implement ticket ${ticketShorthand}:
37698
- ${body}`;
37699
- };
37700
- var buildTicketAttempts = (services, ticketId) => {
37701
- const workspaces2 = services.workspaces.list(ticketId);
37702
- return workspaces2.flatMap((workspace) => {
37703
- const sessions2 = services.sessions.list(workspace.id);
37704
- const session = sessions2[sessions2.length - 1];
37705
- if (!session) {
37706
- return [];
37707
- }
37708
- return [
37709
- {
37710
- id: workspace.id,
37711
- label: workspace.name,
37712
- shorthand: workspace.workspace_shorthand,
37713
- session_id: session.id,
37714
- updated_at: session.updated_at
37715
- }
37716
- ];
37717
- });
37718
- };
37719
- var buildTicketResponse = (services, ticket) => {
37720
- if (!ticket) {
37721
- return null;
37722
- }
37723
- const attempts = buildTicketAttempts(services, ticket.id);
37724
- const title = ticket.title || extractTitle(null, ticket.id);
37725
- const content = readTicketContent(services, ticket.id);
37726
- const tag_ids = services.ticketTags.listIdsForTicket(ticket.id);
37727
- const children = services.tickets.listChildren(ticket.id);
37728
- const sub_tickets = children.map((child) => ({
37729
- id: child.id,
37730
- shorthand: child.shorthand,
37731
- title: child.title || child.shorthand,
37732
- status_id: child.status_id
37733
- }));
37734
- const artifact_count = services.workspaceArtifacts.list(ticket.id).length;
37735
- return { ...ticket, title, content, attempts, tag_ids, sub_tickets, artifact_count };
37736
- };
37737
- var buildTicketResponseOrFallback = (services, ticket) => {
37738
- return buildTicketResponse(services, ticket) ?? {
37739
- ...ticket,
37740
- title: ticket.title || ticket.id,
37741
- content: null,
37742
- attempts: [],
37743
- tag_ids: []
37744
- };
37745
- };
37746
- var buildTicketListResponse = (services, tickets2) => {
37747
- const tagIdsByTicket = services.ticketTags.listIdsForTickets(tickets2.map((ticket) => ticket.id));
37748
- return tickets2.map((ticket) => {
37749
- const attempts = buildTicketAttempts(services, ticket.id);
37750
- const title = ticket.title || ticket.id;
37751
- const content = readTicketContent(services, ticket.id);
37752
- const tag_ids = tagIdsByTicket.get(ticket.id) ?? [];
37753
- return { ...ticket, title, content, attempts, tag_ids };
37754
- });
37755
- };
37756
-
37757
- // ../schub-api/src/features/proposals/schemas.ts
37758
- var proposalSchema = exports_external.object({
37759
- id: exports_external.string(),
37760
- shorthand: exports_external.string(),
37761
- display_title: exports_external.string().optional(),
37762
- project_id: exports_external.string(),
37763
- repo_id: exports_external.string().nullable(),
37764
- input: exports_external.string().nullable(),
37765
- status: exports_external.string(),
37766
- archived: exports_external.number().int(),
37767
- created_at: exports_external.string(),
37768
- updated_at: exports_external.string()
37769
- });
37770
- var ticketSchema = exports_external.object({
37771
- id: exports_external.string(),
37772
- project_id: exports_external.string(),
37773
- proposal_id: exports_external.string().nullable(),
37774
- status_id: exports_external.string().nullable(),
37775
- depends_on: exports_external.string().nullable(),
37776
- input: exports_external.string().nullable(),
37777
- priority: exports_external.string().nullable(),
37778
- parallelizable: exports_external.string().nullable(),
37779
- title: exports_external.string(),
37780
- content: exports_external.string().nullable(),
37781
- complexity: exports_external.enum(["low", "medium", "high"]).nullable().optional(),
37782
- archived: exports_external.number().int(),
37783
- created_at: exports_external.string(),
37784
- updated_at: exports_external.string()
37785
- });
37786
- var proposalProjectParamsSchema = exports_external.object({
37787
- project_id: exports_external.string()
37788
- });
37789
- var proposalParamsSchema2 = exports_external.object({
37790
- project_id: exports_external.string(),
37791
- proposal_id: exports_external.string()
37792
- });
37793
- var proposalTicketParamsSchema = exports_external.object({
37794
- project_id: exports_external.string(),
37795
- proposal_id: exports_external.string(),
37796
- ticket_id: exports_external.string()
37797
- });
37798
- var createProposalSchema = exports_external.object({
37799
- repo_id: exports_external.string().nullable().optional(),
37800
- input: exports_external.string().nullable().optional()
37801
- });
37802
- var updateProposalSchema = exports_external.object({
37803
- repo_id: exports_external.string().nullable().optional(),
37804
- input: exports_external.string().nullable().optional(),
37805
- status: exports_external.string().optional(),
37806
- archived: exports_external.number().int().min(0).max(1).optional()
37807
- });
37808
- var createProposalTicketSchema = exports_external.object({
37809
- content: exports_external.string().nullable().optional(),
37810
- input: exports_external.string().nullable().optional(),
37811
- priority: exports_external.string().nullable().optional(),
37812
- parallelizable: exports_external.string().nullable().optional(),
37813
- complexity: exports_external.enum(["low", "medium", "high"]).nullable().optional(),
37814
- status_id: exports_external.string().nullable().optional(),
37815
- depends_on: exports_external.string().nullable().optional()
37816
- });
37817
- var updateProposalTicketSchema = createProposalTicketSchema;
37818
-
37819
- // ../schub-api/src/features/proposals/endpoints/convert-proposal-to-ticket.ts
37820
- var convertProposalToTicketRoute = (path5) => createRoute({
37821
- method: "post",
37822
- path: path5,
37823
- tags: ["Proposals"],
37824
- summary: "Convert proposal to ticket",
37825
- request: {
37826
- params: proposalParamsSchema2
37827
- },
37828
- responses: {
37829
- 201: {
37830
- description: "Proposal converted to ticket",
37831
- content: {
37832
- "application/json": {
37833
- schema: apiSuccessSchema(ticketSchema)
37834
- }
37835
- }
37836
- },
37837
- 404: {
37838
- description: "Not found",
37839
- content: {
37840
- "application/json": {
37841
- schema: apiErrorSchema
37842
- }
37843
- }
37844
- },
37845
- 409: {
37846
- description: "Proposal already has tickets",
37847
- content: {
37848
- "application/json": {
37849
- schema: apiErrorSchema
37850
- }
37851
- }
37852
- }
37853
- }
37854
- });
37855
- var convertProposalToTicket = (c) => {
37856
- const { project_id, proposal_id } = c.req.valid("param");
37857
- const services = c.get("services");
37858
- const proposal = services.proposals.get(project_id, proposal_id);
37859
- if (!proposal) {
37860
- return c.json(errorResponse("Proposal not found"), 404);
37861
- }
37862
- const existingTickets = services.tickets.listByProposal(project_id, proposal_id);
37863
- if (existingTickets.length > 0) {
37864
- return c.json(errorResponse("Proposal already has tickets"), 409);
37865
- }
37866
- const statuses = services.ticketStatuses.list(project_id);
37867
- const defaultStatus2 = statuses.find((status) => status.is_default === 1) ?? statuses[0] ?? null;
37868
- const rawContent = `# ${proposal.shorthand}`;
37869
- const title = extractTitle(rawContent, proposal.shorthand);
37870
- const ticket = services.tickets.create({
37871
- project_id,
37872
- title,
37873
- status_id: defaultStatus2?.id ?? null,
37874
- proposal_id: null
37875
- });
37876
- uploadTicketContent(services, project_id, ticket.id, rawContent);
37877
- services.proposals.remove(project_id, proposal_id);
37878
- return c.json(okResponse({ ...ticket, title, content: rawContent }), 201);
37879
- };
37880
-
37881
- // ../schub-api/src/features/proposals/endpoints/create-proposal.ts
37882
- var createProposalRoute = (path5) => createRoute({
37883
- method: "post",
37884
- path: path5,
37885
- tags: ["Proposals"],
37886
- summary: "Create proposal",
37887
- request: {
37888
- params: proposalProjectParamsSchema,
37889
- body: {
37890
- content: {
37891
- "application/json": {
37892
- schema: createProposalSchema
37893
- }
37894
- }
37895
- }
37896
- },
37897
- responses: {
37898
- 201: {
37899
- description: "Proposal created",
37900
- content: {
37901
- "application/json": {
37902
- schema: apiSuccessSchema(proposalSchema)
37903
- }
37904
- }
37905
- }
37906
- }
37907
- });
37908
- var createProposal = (c) => {
37909
- const { project_id } = c.req.valid("param");
37910
- const payload = c.req.valid("json");
37911
- const proposal = c.get("services").proposals.create(project_id, payload);
37912
- return c.json(okResponse(proposal), 201);
37913
- };
37914
-
37915
- // ../schub-api/src/features/proposals/endpoints/create-proposal-ticket.ts
37916
- var createProposalTicketRoute = (path5) => createRoute({
37917
- method: "post",
37918
- path: path5,
37919
- tags: ["Proposals"],
37920
- summary: "Create proposal ticket",
37921
- request: {
37922
- params: proposalParamsSchema2,
37923
- body: {
37924
- content: {
37925
- "application/json": {
37926
- schema: createProposalTicketSchema
37927
- }
37928
- }
37929
- }
37930
- },
37931
- responses: {
37932
- 201: {
37933
- description: "Proposal ticket created",
37934
- content: {
37935
- "application/json": {
37936
- schema: apiSuccessSchema(ticketSchema)
37937
- }
37938
- }
37939
- }
37940
- }
37941
- });
37942
- var createProposalTicket = (c) => {
37943
- const { project_id, proposal_id } = c.req.valid("param");
37944
- const payload = c.req.valid("json");
37945
- const services = c.get("services");
37946
- const title = extractTitle(payload.content, "untitled");
37947
- const { content: rawContent, ...ticketPayload } = payload;
37948
- const ticket = services.tickets.createForProposal(project_id, proposal_id, { ...ticketPayload, title });
37949
- if (rawContent) {
37950
- uploadTicketContent(services, project_id, ticket.id, rawContent);
37951
- }
37952
- return c.json(okResponse({ ...ticket, title, content: rawContent ?? null }), 201);
37953
- };
37954
-
37955
- // ../schub-api/src/features/proposals/endpoints/delete-proposal.ts
37956
- var deleteProposalRoute = (path5) => createRoute({
37957
- method: "delete",
37958
- path: path5,
37959
- tags: ["Proposals"],
37960
- summary: "Delete proposal",
37961
- request: {
37962
- params: proposalParamsSchema2
37963
- },
37964
- responses: {
37965
- 204: {
37966
- description: "Proposal removed"
37967
- },
37968
- 404: {
37969
- description: "Not found",
37970
- content: {
37971
- "application/json": {
37972
- schema: apiErrorSchema
37973
- }
37974
- }
37975
- }
37976
- }
37977
- });
37978
- var deleteProposal = (c) => {
37979
- const { project_id, proposal_id } = c.req.valid("param");
37980
- const removed = c.get("services").proposals.remove(project_id, proposal_id);
37981
- if (!removed) {
37982
- return c.json(errorResponse("Proposal not found"), 404);
37983
- }
37984
- return c.body(null, 204);
37985
- };
37986
-
37987
- // ../schub-api/src/features/proposals/endpoints/delete-proposal-ticket.ts
37988
- var deleteProposalTicketRoute = (path5) => createRoute({
37989
- method: "delete",
37990
- path: path5,
37991
- tags: ["Proposals"],
37992
- summary: "Delete proposal ticket",
37993
- request: {
37994
- params: proposalTicketParamsSchema
37995
- },
37996
- responses: {
37997
- 204: {
37998
- description: "Proposal ticket removed"
37999
- },
38000
- 404: {
38001
- description: "Not found",
38002
- content: {
38003
- "application/json": {
38004
- schema: apiErrorSchema
38005
- }
38006
- }
38007
- }
38008
- }
38009
- });
38010
- var deleteProposalTicket = (c) => {
38011
- const { project_id, proposal_id, ticket_id } = c.req.valid("param");
38012
- const removed = c.get("services").tickets.removeForProposal(project_id, proposal_id, ticket_id);
38013
- if (!removed) {
38014
- return c.json(errorResponse("Ticket not found"), 404);
38015
- }
38016
- return c.body(null, 204);
38017
- };
38018
-
38019
- // ../schub-api/src/features/proposals/endpoints/get-proposal.ts
38020
- var getProposalRoute = (path5) => createRoute({
38021
- method: "get",
38022
- path: path5,
38023
- tags: ["Proposals"],
38024
- summary: "Get proposal",
38025
- request: {
38026
- params: proposalParamsSchema2
38027
- },
38028
- responses: {
38029
- 200: {
38030
- description: "Proposal",
38031
- content: {
38032
- "application/json": {
38033
- schema: apiSuccessSchema(proposalSchema)
38034
- }
38035
- }
38036
- },
38037
- 404: {
38038
- description: "Not found",
38039
- content: {
38040
- "application/json": {
38041
- schema: apiErrorSchema
38042
- }
38043
- }
38044
- }
38045
- }
38046
- });
38047
- var getProposal = (c) => {
38048
- const { project_id, proposal_id } = c.req.valid("param");
38049
- const proposal = c.get("services").proposals.get(project_id, proposal_id);
38050
- if (!proposal) {
38051
- return c.json(errorResponse("Proposal not found"), 404);
38052
- }
38053
- return c.json(okResponse(proposal), 200);
38054
- };
38055
-
38056
- // ../schub-api/src/features/proposals/endpoints/list-proposal-tickets.ts
38057
- var listProposalTicketsRoute = (path5) => createRoute({
38058
- method: "get",
38059
- path: path5,
38060
- tags: ["Proposals"],
38061
- summary: "List proposal tickets",
38062
- request: {
38063
- params: proposalParamsSchema2
38064
- },
38065
- responses: {
38066
- 200: {
38067
- description: "Proposal tickets list",
38068
- content: {
38069
- "application/json": {
38070
- schema: apiSuccessSchema(ticketSchema.array())
38071
- }
38072
- }
38073
- }
38074
- }
38075
- });
38076
- var listProposalTickets = (c) => {
38077
- const { project_id, proposal_id } = c.req.valid("param");
38078
- const tickets2 = c.get("services").tickets.listByProposal(project_id, proposal_id).filter((t) => t.staged !== 1);
38079
- const services = c.get("services");
38080
- const ticketsWithTitle = tickets2.map((ticket) => ({
38081
- ...ticket,
38082
- title: ticket.title || ticket.id,
38083
- content: readTicketContent(services, ticket.id)
38084
- }));
38085
- return c.json(okResponse(ticketsWithTitle), 200);
38086
- };
38087
-
38088
- // ../schub-api/src/features/proposals/endpoints/list-proposals.ts
38089
- import { readFileSync as readFileSync6 } from "node:fs";
38090
- var extractTitleFromMarkdown = (content) => {
38091
- const headerMatch = content.match(/^#+\s+(.*)$/m);
38092
- if (headerMatch?.[1]) {
38093
- return headerMatch[1].trim();
38094
- }
38095
- const body = content.replace(/^---[\s\S]*?---/, "").trim();
38096
- const firstLine = body.split(`
38097
- `)[0]?.trim() ?? "";
38098
- if (firstLine.length > 0) {
38099
- return firstLine.length > 60 ? `${firstLine.slice(0, 57)}...` : firstLine;
38100
- }
38101
- return "";
38102
- };
38103
- var resolveProposalDisplayTitle = (c, proposalId, shorthand) => {
38104
- const proposalFile = c.get("services").files.listForProposal(proposalId).find((file2) => file2.file_name.trim().toLowerCase() === "proposal.md");
38105
- if (!proposalFile) {
38106
- return shorthand;
38107
- }
38108
- try {
38109
- const content = readFileSync6(proposalFile.storage_path, "utf8");
38110
- const title = extractTitleFromMarkdown(content);
38111
- return title || shorthand;
38112
- } catch {
38113
- return shorthand;
38114
- }
38115
- };
38116
- var listProposalsRoute = (path5) => createRoute({
38117
- method: "get",
38118
- path: path5,
38119
- tags: ["Proposals"],
38120
- summary: "List proposals",
38121
- request: {
38122
- params: proposalProjectParamsSchema
38123
- },
38124
- responses: {
38125
- 200: {
38126
- description: "Proposals list",
38127
- content: {
38128
- "application/json": {
38129
- schema: apiSuccessSchema(proposalSchema.array())
38130
- }
38131
- }
38132
- }
38133
- }
38134
- });
38135
- var listProposals = (c) => {
38136
- const { project_id } = c.req.valid("param");
38137
- const proposals2 = c.get("services").proposals.list(project_id).filter((p) => p.staged !== 1 && p.archived !== 1);
38138
- return c.json(okResponse(proposals2.map((proposal) => ({
38139
- ...proposal,
38140
- display_title: resolveProposalDisplayTitle(c, proposal.id, proposal.shorthand)
38141
- }))), 200);
38142
- };
38143
-
38144
- // ../schub-api/src/features/proposals/endpoints/update-proposal.ts
38145
- var updateProposalRoute = (path5) => createRoute({
38146
- method: "put",
38147
- path: path5,
38148
- tags: ["Proposals"],
38149
- summary: "Update proposal",
38150
- request: {
38151
- params: proposalParamsSchema2,
38152
- body: {
38153
- content: {
38154
- "application/json": {
38155
- schema: updateProposalSchema
38156
- }
38157
- }
38158
- }
38159
- },
38160
- responses: {
38161
- 200: {
38162
- description: "Proposal updated",
38163
- content: {
38164
- "application/json": {
38165
- schema: apiSuccessSchema(proposalSchema)
38166
- }
38167
- }
37216
+ ws.send(JSON.stringify({ Ready: true }));
37217
+ unsubscribe = services.projects.subscribe((patches) => {
37218
+ ws.send(JSON.stringify({ JsonPatch: patches }));
37219
+ });
38168
37220
  },
38169
- 404: {
38170
- description: "Not found",
38171
- content: {
38172
- "application/json": {
38173
- schema: apiErrorSchema
38174
- }
37221
+ onMessage: () => {},
37222
+ onClose: () => {
37223
+ if (unsubscribe) {
37224
+ unsubscribe();
37225
+ unsubscribe = null;
38175
37226
  }
38176
37227
  }
38177
- }
37228
+ };
38178
37229
  });
38179
- var updateProposal = (c) => {
38180
- const { project_id, proposal_id } = c.req.valid("param");
38181
- const payload = c.req.valid("json");
38182
- const proposal = c.get("services").proposals.update(project_id, proposal_id, payload);
38183
- if (!proposal) {
38184
- return c.json(errorResponse("Proposal not found"), 404);
38185
- }
38186
- return c.json(okResponse(proposal), 200);
38187
- };
38188
37230
 
38189
- // ../schub-api/src/features/proposals/endpoints/update-proposal-ticket.ts
38190
- var updateProposalTicketRoute = (path5) => createRoute({
37231
+ // ../schub-api/src/features/projects/endpoints/update-project.ts
37232
+ var updateProjectRoute = (path5) => createRoute({
38191
37233
  method: "put",
38192
37234
  path: path5,
38193
- tags: ["Proposals"],
38194
- summary: "Update proposal ticket",
37235
+ tags: ["Projects"],
37236
+ summary: "Update project",
38195
37237
  request: {
38196
- params: proposalTicketParamsSchema,
37238
+ params: projectIdParamsSchema2,
38197
37239
  body: {
38198
37240
  content: {
38199
37241
  "application/json": {
38200
- schema: updateProposalTicketSchema
37242
+ schema: updateProjectSchema
38201
37243
  }
38202
37244
  }
38203
37245
  }
38204
37246
  },
38205
37247
  responses: {
38206
37248
  200: {
38207
- description: "Proposal ticket updated",
37249
+ description: "Project updated",
38208
37250
  content: {
38209
37251
  "application/json": {
38210
- schema: apiSuccessSchema(ticketSchema)
37252
+ schema: apiSuccessSchema(projectSchema)
38211
37253
  }
38212
37254
  }
38213
37255
  },
@@ -38221,50 +37263,43 @@ var updateProposalTicketRoute = (path5) => createRoute({
38221
37263
  }
38222
37264
  }
38223
37265
  });
38224
- var updateProposalTicket = (c) => {
38225
- const { project_id, proposal_id, ticket_id } = c.req.valid("param");
37266
+ var updateProject = (c) => {
37267
+ const { id } = c.req.valid("param");
38226
37268
  const payload = c.req.valid("json");
38227
- const services = c.get("services");
38228
- const { content: rawContent, ...ticketPayload } = payload;
38229
- const titleUpdate = rawContent !== undefined ? { title: extractTitle(rawContent, "untitled") } : {};
38230
- const ticket = services.tickets.updateForProposal(project_id, proposal_id, ticket_id, {
38231
- ...ticketPayload,
38232
- ...titleUpdate
38233
- });
38234
- if (!ticket) {
38235
- return c.json(errorResponse("Ticket not found"), 404);
38236
- }
38237
- if (rawContent) {
38238
- uploadTicketContent(services, project_id, ticket.id, rawContent);
37269
+ const project = c.get("services").projects.update(id, payload);
37270
+ if (!project) {
37271
+ return c.json(errorResponse("Project not found"), 404);
38239
37272
  }
38240
- const title = ticket.title || ticket.id;
38241
- const content = rawContent ?? readTicketContent(services, ticket.id);
38242
- return c.json(okResponse({ ...ticket, title, content }), 200);
37273
+ return c.json(okResponse(project), 200);
38243
37274
  };
38244
37275
 
38245
- // ../schub-api/src/features/proposals/routes.ts
38246
- var createProposalRoutes = () => {
37276
+ // ../schub-api/src/features/projects/routes.ts
37277
+ var createProjectRoutes = (upgradeWebSocket) => {
38247
37278
  const routes = new OpenAPIHono;
38248
- const listRoute = listProposalsRoute("/");
38249
- routes.openapi(listRoute, listProposals);
38250
- const createRoute2 = createProposalRoute("/");
38251
- routes.openapi(createRoute2, createProposal);
38252
- const getRoute = getProposalRoute("/:proposal_id");
38253
- routes.openapi(getRoute, getProposal);
38254
- const updateRoute = updateProposalRoute("/:proposal_id");
38255
- routes.openapi(updateRoute, updateProposal);
38256
- const deleteRoute = deleteProposalRoute("/:proposal_id");
38257
- routes.openapi(deleteRoute, deleteProposal);
38258
- const convertRoute = convertProposalToTicketRoute("/:proposal_id/convert-to-ticket");
38259
- routes.openapi(convertRoute, convertProposalToTicket);
38260
- const listTicketsRoute = listProposalTicketsRoute("/:proposal_id/tickets");
38261
- routes.openapi(listTicketsRoute, listProposalTickets);
38262
- const createTicketRoute = createProposalTicketRoute("/:proposal_id/tickets");
38263
- routes.openapi(createTicketRoute, createProposalTicket);
38264
- const updateTicketRoute = updateProposalTicketRoute("/:proposal_id/tickets/:ticket_id");
38265
- routes.openapi(updateTicketRoute, updateProposalTicket);
38266
- const deleteTicketRoute = deleteProposalTicketRoute("/:proposal_id/tickets/:ticket_id");
38267
- routes.openapi(deleteTicketRoute, deleteProposalTicket);
37279
+ const listRoute = listProjectsRoute("/");
37280
+ routes.openapi(listRoute, listProjects);
37281
+ const createRoute2 = createProjectRoute("/");
37282
+ routes.openapi(createRoute2, createProject);
37283
+ const getRoute = getProjectRoute("/:id");
37284
+ routes.openapi(getRoute, getProject);
37285
+ const updateRoute = updateProjectRoute("/:id");
37286
+ routes.openapi(updateRoute, updateProject);
37287
+ const deleteRoute = deleteProjectRoute("/:id");
37288
+ routes.openapi(deleteRoute, deleteProject);
37289
+ const listReposRoute = listProjectRepositoriesRoute("/:id/repositories");
37290
+ routes.openapi(listReposRoute, listProjectRepositories);
37291
+ const addRepoRoute = addProjectRepositoryRoute("/:id/repositories");
37292
+ routes.openapi(addRepoRoute, addProjectRepository);
37293
+ const getRepoRoute = getProjectRepositoryRoute("/:project_id/repositories/:repo_id");
37294
+ routes.openapi(getRepoRoute, getProjectRepository);
37295
+ const deleteRepoRoute = deleteProjectRepositoryRoute("/:project_id/repositories/:repo_id");
37296
+ routes.openapi(deleteRepoRoute, deleteProjectRepository);
37297
+ const streamRoute = streamProjectsRoute("/stream/ws");
37298
+ routes.openapi(streamRoute, streamProjects(upgradeWebSocket));
37299
+ const searchRoute = searchProjectRoute("/:id/search");
37300
+ routes.openapi(searchRoute, searchProject);
37301
+ const openEditorRoute = openProjectEditorRoute("/:id/open-editor");
37302
+ routes.openapi(openEditorRoute, openProjectEditor);
38268
37303
  return routes;
38269
37304
  };
38270
37305
 
@@ -39699,7 +38734,7 @@ var listProfilesRoute = (path5) => createPlaceholderRoute("System", { method: "g
39699
38734
  var listProfiles = createPlaceholderHandler();
39700
38735
 
39701
38736
  // ../schub-api/src/features/system/helpers/opencode-skills.ts
39702
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "node:fs";
38737
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "node:fs";
39703
38738
  import { homedir as homedir5 } from "node:os";
39704
38739
  import { join as join9 } from "node:path";
39705
38740
  var skillPathSuffix = "/SKILL.md";
@@ -39733,10 +38768,11 @@ var installOpencodeSkill = (homeDirectory, skill) => {
39733
38768
  if (!skillName) {
39734
38769
  return null;
39735
38770
  }
38771
+ const content = readFileSync7(skill.storage_path, "utf8");
39736
38772
  const skillDirectory = join9(homeDirectory, ".opencode", "skills", skillName);
39737
38773
  const skillPath = join9(skillDirectory, "SKILL.md");
39738
38774
  mkdirSync3(skillDirectory, { recursive: true });
39739
- writeFileSync3(skillPath, skill.content, "utf8");
38775
+ writeFileSync3(skillPath, content, "utf8");
39740
38776
  return {
39741
38777
  name: skillName,
39742
38778
  path: skillPath
@@ -39874,15 +38910,24 @@ var createEditorRoutes = () => {
39874
38910
  return routes;
39875
38911
  };
39876
38912
 
38913
+ // ../schub-api/src/features/templates/endpoints/create-template.ts
38914
+ import { readFileSync as readFileSync8 } from "node:fs";
38915
+
39877
38916
  // ../schub-api/src/features/templates/schemas.ts
39878
38917
  var templateSchema = exports_external.object({
39879
38918
  id: exports_external.string(),
39880
38919
  project_id: exports_external.string().nullable(),
39881
38920
  name: exports_external.string(),
39882
38921
  template_type: exports_external.string(),
38922
+ file_id: exports_external.string(),
38923
+ is_default: exports_external.number(),
39883
38924
  content: exports_external.string(),
39884
38925
  created_at: exports_external.string(),
39885
- updated_at: exports_external.string()
38926
+ updated_at: exports_external.string(),
38927
+ file_name: exports_external.string(),
38928
+ storage_path: exports_external.string(),
38929
+ mime_type: exports_external.string().nullable(),
38930
+ size_bytes: exports_external.number()
39886
38931
  });
39887
38932
  var templateParamsSchema = exports_external.object({
39888
38933
  template_id: exports_external.string()
@@ -39891,7 +38936,8 @@ var createTemplateSchema = exports_external.object({
39891
38936
  project_id: exports_external.string().nullable().optional(),
39892
38937
  name: exports_external.string().min(1),
39893
38938
  template_type: exports_external.string().min(1),
39894
- content: exports_external.string().min(1)
38939
+ file_id: exports_external.string().min(1),
38940
+ is_default: exports_external.number().optional()
39895
38941
  });
39896
38942
  var updateTemplateSchema = createTemplateSchema;
39897
38943
 
@@ -39918,13 +38964,30 @@ var createTemplateRoute = (path5) => createRoute({
39918
38964
  schema: apiSuccessSchema(templateSchema)
39919
38965
  }
39920
38966
  }
38967
+ },
38968
+ 409: {
38969
+ description: "Duplicate name",
38970
+ content: {
38971
+ "application/json": {
38972
+ schema: apiErrorSchema
38973
+ }
38974
+ }
39921
38975
  }
39922
38976
  }
39923
38977
  });
39924
38978
  var createTemplate = (c) => {
39925
38979
  const payload = c.req.valid("json");
39926
- const template = c.get("services").templates.create(payload);
39927
- return c.json(okResponse(template), 201);
38980
+ try {
38981
+ const services = c.get("services");
38982
+ const created = services.templates.create(payload);
38983
+ const template = services.templates.get(created.id);
38984
+ return c.json(okResponse({ ...template, content: readFileSync8(template.storage_path, "utf8") }), 201);
38985
+ } catch (error48) {
38986
+ if (error48 instanceof Error && error48.message.includes("Duplicate template name")) {
38987
+ return c.json(errorResponse(error48.message), 409);
38988
+ }
38989
+ throw error48;
38990
+ }
39928
38991
  };
39929
38992
 
39930
38993
  // ../schub-api/src/features/templates/endpoints/delete-template.ts
@@ -39960,6 +39023,7 @@ var deleteTemplate = (c) => {
39960
39023
  };
39961
39024
 
39962
39025
  // ../schub-api/src/features/templates/endpoints/get-template.ts
39026
+ import { readFileSync as readFileSync9 } from "node:fs";
39963
39027
  var getTemplateRoute = (path5) => createRoute({
39964
39028
  method: "get",
39965
39029
  path: path5,
@@ -39993,10 +39057,11 @@ var getTemplate = (c) => {
39993
39057
  if (!template) {
39994
39058
  return c.json(errorResponse("Template not found"), 404);
39995
39059
  }
39996
- return c.json(okResponse(template), 200);
39060
+ return c.json(okResponse({ ...template, content: readFileSync9(template.storage_path, "utf8") }), 200);
39997
39061
  };
39998
39062
 
39999
39063
  // ../schub-api/src/features/templates/endpoints/list-templates.ts
39064
+ import { readFileSync as readFileSync10 } from "node:fs";
40000
39065
  var listTemplatesRoute = (path5) => createRoute({
40001
39066
  method: "get",
40002
39067
  path: path5,
@@ -40015,10 +39080,15 @@ var listTemplatesRoute = (path5) => createRoute({
40015
39080
  });
40016
39081
  var listTemplates = (c) => {
40017
39082
  const templates2 = c.get("services").templates.list();
40018
- return c.json(okResponse(templates2), 200);
39083
+ const withContent = templates2.map((t) => ({
39084
+ ...t,
39085
+ content: readFileSync10(t.storage_path, "utf8")
39086
+ }));
39087
+ return c.json(okResponse(withContent), 200);
40019
39088
  };
40020
39089
 
40021
39090
  // ../schub-api/src/features/templates/endpoints/update-template.ts
39091
+ import { readFileSync as readFileSync11 } from "node:fs";
40022
39092
  var updateTemplateRoute = (path5) => createRoute({
40023
39093
  method: "put",
40024
39094
  path: path5,
@@ -40050,17 +39120,34 @@ var updateTemplateRoute = (path5) => createRoute({
40050
39120
  schema: apiErrorSchema
40051
39121
  }
40052
39122
  }
39123
+ },
39124
+ 409: {
39125
+ description: "Duplicate name",
39126
+ content: {
39127
+ "application/json": {
39128
+ schema: apiErrorSchema
39129
+ }
39130
+ }
40053
39131
  }
40054
39132
  }
40055
39133
  });
40056
39134
  var updateTemplate = (c) => {
40057
39135
  const { template_id } = c.req.valid("param");
40058
39136
  const payload = c.req.valid("json");
40059
- const template = c.get("services").templates.update(template_id, payload);
40060
- if (!template) {
40061
- return c.json(errorResponse("Template not found"), 404);
39137
+ try {
39138
+ const services = c.get("services");
39139
+ const updated = services.templates.update(template_id, payload);
39140
+ if (!updated) {
39141
+ return c.json(errorResponse("Template not found"), 404);
39142
+ }
39143
+ const template = services.templates.get(template_id);
39144
+ return c.json(okResponse({ ...template, content: readFileSync11(template.storage_path, "utf8") }), 200);
39145
+ } catch (error48) {
39146
+ if (error48 instanceof Error && error48.message.includes("Duplicate template name")) {
39147
+ return c.json(errorResponse(error48.message), 409);
39148
+ }
39149
+ throw error48;
40062
39150
  }
40063
- return c.json(okResponse(template), 200);
40064
39151
  };
40065
39152
 
40066
39153
  // ../schub-api/src/features/templates/routes.ts
@@ -40484,6 +39571,99 @@ var createTicketTagRoutes = () => {
40484
39571
  return routes;
40485
39572
  };
40486
39573
 
39574
+ // ../schub-api/src/features/tickets/helpers/ticket-responses.ts
39575
+ import { readFileSync as readFileSync12 } from "node:fs";
39576
+ var readTicketContent = (services, ticketId) => {
39577
+ const files2 = services.files.listForTicket(ticketId);
39578
+ const contentFile = files2.find((f) => f.file_kind === "ticket");
39579
+ if (!contentFile)
39580
+ return null;
39581
+ try {
39582
+ return readFileSync12(contentFile.storage_path, "utf8");
39583
+ } catch {
39584
+ return null;
39585
+ }
39586
+ };
39587
+ var uploadTicketContent = (services, projectId, ticketId, content) => {
39588
+ const existingFiles = services.files.listForTicket(ticketId);
39589
+ const contentFile = existingFiles.find((f) => f.file_kind === "ticket");
39590
+ const contentBuffer = Buffer.from(content, "utf8");
39591
+ if (contentFile) {
39592
+ services.files.update(contentFile.id, { data: contentBuffer });
39593
+ } else {
39594
+ const file2 = services.files.upload({
39595
+ project_id: projectId,
39596
+ file_name: "ticket.md",
39597
+ file_kind: "ticket",
39598
+ data: contentBuffer
39599
+ });
39600
+ services.files.attachToTicket(ticketId, file2.id);
39601
+ }
39602
+ };
39603
+ var buildAttemptPrompt = (ticketShorthand, ticketContent, fallback) => {
39604
+ const content = ticketContent?.trim();
39605
+ const body = content && content.length > 0 ? content : fallback;
39606
+ return `Implement ticket ${ticketShorthand}:
39607
+ ${body}`;
39608
+ };
39609
+ var buildTicketAttempts = (services, ticketId) => {
39610
+ const workspaces2 = services.workspaces.list(ticketId);
39611
+ return workspaces2.flatMap((workspace) => {
39612
+ const sessions2 = services.sessions.list(workspace.id);
39613
+ const session = sessions2[sessions2.length - 1];
39614
+ if (!session) {
39615
+ return [];
39616
+ }
39617
+ return [
39618
+ {
39619
+ id: workspace.id,
39620
+ label: workspace.name,
39621
+ shorthand: workspace.workspace_shorthand,
39622
+ session_id: session.id,
39623
+ updated_at: session.updated_at,
39624
+ worktree_path: workspace.worktree_path ?? null
39625
+ }
39626
+ ];
39627
+ });
39628
+ };
39629
+ var buildTicketResponse = (services, ticket) => {
39630
+ if (!ticket) {
39631
+ return null;
39632
+ }
39633
+ const attempts = buildTicketAttempts(services, ticket.id);
39634
+ const title = ticket.title || extractTitle(null, ticket.id);
39635
+ const content = readTicketContent(services, ticket.id);
39636
+ const tag_ids = services.ticketTags.listIdsForTicket(ticket.id);
39637
+ const children = services.tickets.listChildren(ticket.id);
39638
+ const sub_tickets = children.map((child) => ({
39639
+ id: child.id,
39640
+ shorthand: child.shorthand,
39641
+ title: child.title || child.shorthand,
39642
+ status_id: child.status_id
39643
+ }));
39644
+ const artifact_count = services.workspaceArtifacts.list(ticket.id).length;
39645
+ return { ...ticket, title, content, attempts, tag_ids, sub_tickets, artifact_count };
39646
+ };
39647
+ var buildTicketResponseOrFallback = (services, ticket) => {
39648
+ return buildTicketResponse(services, ticket) ?? {
39649
+ ...ticket,
39650
+ title: ticket.title || ticket.id,
39651
+ content: null,
39652
+ attempts: [],
39653
+ tag_ids: []
39654
+ };
39655
+ };
39656
+ var buildTicketListResponse = (services, tickets2) => {
39657
+ const tagIdsByTicket = services.ticketTags.listIdsForTickets(tickets2.map((ticket) => ticket.id));
39658
+ return tickets2.map((ticket) => {
39659
+ const attempts = buildTicketAttempts(services, ticket.id);
39660
+ const title = ticket.title || ticket.id;
39661
+ const content = readTicketContent(services, ticket.id);
39662
+ const tag_ids = tagIdsByTicket.get(ticket.id) ?? [];
39663
+ return { ...ticket, title, content, attempts, tag_ids };
39664
+ });
39665
+ };
39666
+
40487
39667
  // ../schub-api/src/features/tickets/schemas.ts
40488
39668
  var ticketQuerySchema = exports_external.object({
40489
39669
  project_id: exports_external.string(),
@@ -41686,8 +40866,130 @@ var markTicketAttemptSeenRoute = (path5) => createPlaceholderRoute("Workspaces",
41686
40866
  var markTicketAttemptSeen = createPlaceholderHandler();
41687
40867
 
41688
40868
  // ../schub-api/src/features/workspaces/endpoints/merge-ticket-attempt.ts
41689
- var mergeTicketAttemptRoute = (path5) => createPlaceholderRoute("Workspaces", { method: "post", path: path5 });
41690
- var mergeTicketAttempt = createPlaceholderHandler();
40869
+ import { spawnSync as spawnSync6 } from "node:child_process";
40870
+ var mergeResultSchema = exports_external.discriminatedUnion("status", [
40871
+ exports_external.object({
40872
+ status: exports_external.literal("merged"),
40873
+ filesChanged: exports_external.number(),
40874
+ commitMessage: exports_external.string()
40875
+ }),
40876
+ exports_external.object({
40877
+ status: exports_external.literal("conflict"),
40878
+ conflictFiles: exports_external.array(exports_external.string())
40879
+ })
40880
+ ]);
40881
+ var mergeTicketAttemptRoute = (path5) => createRoute({
40882
+ method: "post",
40883
+ path: path5,
40884
+ tags: ["Workspaces"],
40885
+ summary: "Merge ticket attempt into the repo's current branch",
40886
+ request: {
40887
+ params: ticketAttemptIdParamsSchema
40888
+ },
40889
+ responses: {
40890
+ 200: {
40891
+ description: "Merge completed (merged or conflict)",
40892
+ content: {
40893
+ "application/json": {
40894
+ schema: apiSuccessSchema(mergeResultSchema)
40895
+ }
40896
+ }
40897
+ },
40898
+ 400: {
40899
+ description: "Workspace has no worktree or branch",
40900
+ content: { "application/json": { schema: apiErrorSchema } }
40901
+ },
40902
+ 404: {
40903
+ description: "Workspace or repo not found",
40904
+ content: { "application/json": { schema: apiErrorSchema } }
40905
+ },
40906
+ 500: {
40907
+ description: "Merge failed",
40908
+ content: { "application/json": { schema: apiErrorSchema } }
40909
+ }
40910
+ }
40911
+ });
40912
+ var git = (cwd, args) => spawnSync6("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
40913
+ var gitError = (result) => result.stderr?.trim() || result.stdout?.trim() || "unknown error";
40914
+ var autoCommitWorktree2 = (worktreePath, label) => {
40915
+ const status = git(worktreePath, ["status", "--porcelain"]);
40916
+ if (status.status !== 0)
40917
+ return;
40918
+ const changedFiles = status.stdout.split(`
40919
+ `).filter((line) => line.trim().length > 0);
40920
+ if (changedFiles.length === 0)
40921
+ return;
40922
+ const addResult = git(worktreePath, ["add", "-A"]);
40923
+ if (addResult.status !== 0)
40924
+ return;
40925
+ git(worktreePath, ["commit", "-m", `agent: uncommitted work for ${label}`]);
40926
+ };
40927
+ var mergeTicketAttempt = async (c) => {
40928
+ const { id } = c.req.valid("param");
40929
+ const services = c.get("services");
40930
+ const workspace = services.workspaces.get(id);
40931
+ if (!workspace) {
40932
+ return c.json(errorResponse("Workspace not found"), 404);
40933
+ }
40934
+ if (!workspace.worktree_path) {
40935
+ return c.json(errorResponse("Workspace has no worktree"), 400);
40936
+ }
40937
+ if (!workspace.branch) {
40938
+ return c.json(errorResponse("Workspace has no branch"), 400);
40939
+ }
40940
+ if (!workspace.repo_id) {
40941
+ return c.json(errorResponse("Workspace has no associated repository"), 400);
40942
+ }
40943
+ const repo = services.repos.get(workspace.repo_id);
40944
+ if (!repo) {
40945
+ return c.json(errorResponse("Repository not found"), 404);
40946
+ }
40947
+ const gitRootResult = git(repo.path, ["rev-parse", "--show-toplevel"]);
40948
+ if (gitRootResult.status !== 0) {
40949
+ return c.json(errorResponse("Repository is not a git repository"), 400);
40950
+ }
40951
+ const gitRoot = gitRootResult.stdout.trim();
40952
+ const ticket = workspace.ticket_id ? services.tickets.get(workspace.ticket_id) : null;
40953
+ const ticketLabel = ticket?.shorthand ?? "unknown";
40954
+ const attemptLabel = workspace.workspace_shorthand ?? "unknown";
40955
+ try {
40956
+ autoCommitWorktree2(workspace.worktree_path, ticketLabel);
40957
+ const mergeResult = git(gitRoot, ["merge", "--squash", workspace.branch]);
40958
+ if (mergeResult.status !== 0) {
40959
+ const statusResult = git(gitRoot, ["diff", "--name-only", "--diff-filter=U"]);
40960
+ const conflictFiles = statusResult.stdout.split(`
40961
+ `).filter(Boolean);
40962
+ git(gitRoot, ["reset", "--merge"]);
40963
+ if (conflictFiles.length > 0) {
40964
+ return c.json(okResponse({ status: "conflict", conflictFiles }), 200);
40965
+ }
40966
+ throw new Error(`Merge failed: ${gitError(mergeResult)}`);
40967
+ }
40968
+ const diffResult = git(gitRoot, ["diff", "--cached", "--name-only"]);
40969
+ const filesChanged = diffResult.stdout.split(`
40970
+ `).filter(Boolean).length;
40971
+ const commitMessage = `merge: ${ticketLabel}/${attemptLabel}`;
40972
+ const commitResult = git(gitRoot, ["commit", "-m", commitMessage]);
40973
+ if (commitResult.status !== 0) {
40974
+ throw new Error(`Commit failed: ${gitError(commitResult)}`);
40975
+ }
40976
+ if (ticket) {
40977
+ const doneStatus = services.ticketStatuses.list(ticket.project_id).find((s) => s.name.toLowerCase() === "done");
40978
+ if (doneStatus) {
40979
+ services.tickets.save({
40980
+ id: ticket.id,
40981
+ project_id: ticket.project_id,
40982
+ status_id: doneStatus.id,
40983
+ staged: 0
40984
+ });
40985
+ }
40986
+ }
40987
+ return c.json(okResponse({ status: "merged", filesChanged, commitMessage }), 200);
40988
+ } catch (error48) {
40989
+ const message = error48 instanceof Error ? error48.message : "Merge failed";
40990
+ return c.json(errorResponse(message), 500);
40991
+ }
40992
+ };
41691
40993
 
41692
40994
  // ../schub-api/src/features/workspaces/endpoints/open-ticket-attempt-editor.ts
41693
40995
  var openTicketAttemptEditorRoute = (path5) => createPlaceholderRoute("Workspaces", { method: "post", path: path5 });
@@ -41836,12 +41138,10 @@ var createRoutes = (options) => {
41836
41138
  routes.route("/mcp-config", createMcpConfigRoutes());
41837
41139
  routes.route("/profiles", createProfileRoutes());
41838
41140
  routes.route("/projects", createProjectRoutes(options.upgradeWebSocket));
41839
- routes.route("/projects/:project_id/proposals", createProposalRoutes());
41840
41141
  routes.route("/projects/:project_id/template-assets", createProjectTemplateAssetRoutes());
41841
41142
  routes.route("/projects/:project_id/ticket-statuses", createTicketStatusRoutes());
41842
41143
  routes.route("/projects/:project_id/ticket-tags", createTicketTagRoutes());
41843
41144
  routes.route("/projects/:projectId/docs", createProjectDocsRoutes());
41844
- routes.route("/proposals/:proposal_id/files", createProposalFileRoutes());
41845
41145
  routes.route("/repos", createRepoRoutes());
41846
41146
  routes.route("/sessions", createSessionRoutes(options.upgradeWebSocket));
41847
41147
  routes.route("/sessions/:session_id/queue", createSessionQueueRoutes());