@straiffi/archon 1.5.0 → 1.6.1
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.
- package/dist/client/assets/GitHubActionsDialog-BLJ3yCeK.js +1 -0
- package/dist/client/assets/TestsDialog-ar_RUzA3.js +4 -0
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-BG4rc0pD.js → architectureDiagram-3BPJPVTR-cWqD9YRv.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-BifkewDx.js → blockDiagram-GPEHLZMM-B8yyMr1c.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-DEYVUDCs.js → c4Diagram-AAUBKEIU-B_yL8Tbe.js} +1 -1
- package/dist/client/assets/channel-1Ezn2R-x.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-DD50YIcA.js → chunk-2J33WTMH-BF8hfYEU.js} +1 -1
- package/dist/client/assets/{chunk-3OPIFGDE-DLbwwbdv.js → chunk-3OPIFGDE-BPDdyRDK.js} +1 -1
- package/dist/client/assets/{chunk-5ZQYHXKU-BW-r3gXJ.js → chunk-5ZQYHXKU-DEi4bnIa.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-B5C_ENX7.js → chunk-727SXJPM-BTXHOTJi.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-BUT3wmDg.js → chunk-AQP2D5EJ-D2TAJwxM.js} +1 -1
- package/dist/client/assets/{chunk-CSCIHK7Q-BO7wWVh_.js → chunk-CSCIHK7Q-Bdgg7p9T.js} +1 -1
- package/dist/client/assets/{chunk-KSCS5N6A-DUNFfKB2.js → chunk-KSCS5N6A-Cey3fOI1.js} +1 -1
- package/dist/client/assets/{chunk-L5ZTLDWV-SYJQkU8_.js → chunk-L5ZTLDWV-BSNGBgBH.js} +1 -1
- package/dist/client/assets/{chunk-LZXEDZCA-DSVJIylK.js → chunk-LZXEDZCA-B8qP34jM.js} +2 -2
- package/dist/client/assets/{chunk-ND2GUHAM-CxL5ue9A.js → chunk-ND2GUHAM-COde7fTi.js} +1 -1
- package/dist/client/assets/{chunk-NZK2D7GU-BwxeYeqR.js → chunk-NZK2D7GU-C62VVWrt.js} +1 -1
- package/dist/client/assets/{chunk-O5CBEL6O-Covi8YcB.js → chunk-O5CBEL6O-_20MzuKm.js} +1 -1
- package/dist/client/assets/{chunk-WU5MYG2G-DlQoHSB3.js → chunk-WU5MYG2G-C4G-J1jx.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-DrjiwEWN.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DWHyuysv.js +1 -0
- package/dist/client/assets/{dagre-BM42HDAG-CuNExSrh.js → dagre-BM42HDAG-jLUWHvr3.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-Bk7EYe6Z.js → diagram-2AECGRRQ-B6D2TKLb.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-koHJCfAk.js → diagram-5GNKFQAL-bFJLLDWx.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-DI1Ecx54.js → diagram-KO2AKTUF-Bp01SL5f.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-CmFk1MNf.js → diagram-LMA3HP47-DwoeZFg9.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-YTiDn39n.js → diagram-OG6HWLK6-CjRd_tge.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-DyWU43DX.js → erDiagram-TEJ5UH35-DzfIVd9j.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-DgbU15ZJ.js → flowDiagram-I6XJVG4X-Dq04zf9A.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-CT39xvdk.js → ganttDiagram-6RSMTGT7-D5f16YYP.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DfK_1xRj.js → gitGraphDiagram-PVQCEYII-CCD1CXBG.js} +1 -1
- package/dist/client/assets/index-DI9Vi2-3.js +265 -0
- package/dist/client/assets/index-DSap5tSX.css +2 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-C7z_7194.js → infoDiagram-5YYISTIA-Bde4Z0o8.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-DhJ7h17n.js → ishikawaDiagram-YF4QCWOH-CJ89ymGL.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-CtkhoMAA.js → journeyDiagram-JHISSGLW-Dw_sScmo.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-Bcas7cg9.js → kanban-definition-UN3LZRKU-DfWOcnxV.js} +1 -1
- package/dist/client/assets/{line-WO_ReaCl.js → line-CUQIbS7r.js} +1 -1
- package/dist/client/assets/{mermaid-parser.core-WmRuf19A.js → mermaid-parser.core-d287AjoM.js} +1 -1
- package/dist/client/assets/{mermaid.core-BMdju4xN.js → mermaid.core-QqY2ZNMl.js} +3 -3
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-DLxsUQmn.js → mindmap-definition-RKZ34NQL-Bz3Yc70v.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-D-oaWpEg.js → pieDiagram-4H26LBE5-BjMDHR6y.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-B9krq8I-.js → quadrantDiagram-W4KKPZXB-BWDqf0X1.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-m2VnH3vv.js → requirementDiagram-4Y6WPE33-kspE4S60.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-bCF9Ot9-.js → sankeyDiagram-5OEKKPKP-EuLhHbDR.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-C-4zDJcn.js → sequenceDiagram-3UESZ5HK-BvXG3DJh.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-CksbJK59.js → stateDiagram-AJRCARHV-B-D0PYkH.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-f36jgr8A.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-DDxpdMfo.js → timeline-definition-PNZ67QCA-CJnod6Jh.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-BfQM7_zD.js → vennDiagram-CIIHVFJN-CBh3tqZu.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-CuixXlAX.js → wardleyDiagram-YWT4CUSO-BBQOjYBd.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-53YB-tf6.js → xychartDiagram-2RQKCTM6-C8gU1LNj.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/server/db.js +288 -3
- package/dist/server/db.js.map +1 -1
- package/dist/server/index.js +323 -97
- package/dist/server/index.js.map +1 -1
- package/dist/server/lib/git.js +18 -0
- package/dist/server/lib/git.js.map +1 -1
- package/dist/server/lib/models.js +2 -38
- package/dist/server/lib/models.js.map +1 -1
- package/dist/server/lib/projectMemory.js +1073 -276
- package/dist/server/lib/projectMemory.js.map +1 -1
- package/dist/server/lib/projectMemoryCursor.js +27 -0
- package/dist/server/lib/projectMemoryCursor.js.map +1 -0
- package/dist/server/lib/projectMemoryEvents.js +24 -0
- package/dist/server/lib/projectMemoryEvents.js.map +1 -0
- package/dist/server/lib/projectMemoryLimits.js +61 -0
- package/dist/server/lib/projectMemoryLimits.js.map +1 -0
- package/dist/server/lib/projectMemoryPrompt.js +299 -110
- package/dist/server/lib/projectMemoryPrompt.js.map +1 -1
- package/dist/server/lib/projectMemoryScan.js +461 -493
- package/dist/server/lib/projectMemoryScan.js.map +1 -1
- package/dist/server/lib/projectMemorySelections.js +97 -0
- package/dist/server/lib/projectMemorySelections.js.map +1 -0
- package/dist/server/lib/projectMemorySuggestions.js +19 -4
- package/dist/server/lib/projectMemorySuggestions.js.map +1 -1
- package/dist/server/lib/projectMemoryTransfer.js +635 -83
- package/dist/server/lib/projectMemoryTransfer.js.map +1 -1
- package/dist/server/lib/projectPathGlob.js +40 -0
- package/dist/server/lib/projectPathGlob.js.map +1 -0
- package/dist/server/lib/projects.js +50 -0
- package/dist/server/lib/projects.js.map +1 -1
- package/dist/server/lib/specReviewContext.js +2 -1
- package/dist/server/lib/specReviewContext.js.map +1 -1
- package/dist/server/lib/tickets.js +6 -0
- package/dist/server/lib/tickets.js.map +1 -1
- package/dist/server/workers/build.js +6 -2
- package/dist/server/workers/build.js.map +1 -1
- package/dist/server/workers/chat.js +9 -6
- package/dist/server/workers/chat.js.map +1 -1
- package/dist/server/workers/followUp.js +15 -8
- package/dist/server/workers/followUp.js.map +1 -1
- package/dist/server/workers/plan.js +61 -11
- package/dist/server/workers/plan.js.map +1 -1
- package/dist/server/workers/planFollowUp.js +3 -0
- package/dist/server/workers/planFollowUp.js.map +1 -1
- package/dist/server/workers/review.js +26 -11
- package/dist/server/workers/review.js.map +1 -1
- package/dist/server/workers/specReview.js +10 -0
- package/dist/server/workers/specReview.js.map +1 -1
- package/dist/server/workers/specSession.js +5 -2
- package/dist/server/workers/specSession.js.map +1 -1
- package/package.json +1 -1
- package/dist/client/assets/GitHubActionsDialog-CQMqcAiI.js +0 -1
- package/dist/client/assets/TestsDialog-iGukzvw2.js +0 -5
- package/dist/client/assets/channel-gy-JdvF3.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-B1Xs78Ge.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-BU_Soq1k.js +0 -1
- package/dist/client/assets/index-BCqJfUGA.css +0 -2
- package/dist/client/assets/index-C_XaTJyb.js +0 -261
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-BvbN4CmJ.js +0 -1
package/dist/server/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import { resolveChatSessionTarget } from './lib/chatTargets.js';
|
|
|
18
18
|
import { createChatMessage, getChatMessage, updateChatMessageContent } from './lib/chatMessages.js';
|
|
19
19
|
import { attachChatTicketProposalsToMessageContent } from './lib/chatTicketProposals.js';
|
|
20
20
|
import { createCorsOriginResolver, parseCorsOrigins } from './lib/cors.js';
|
|
21
|
-
import { abortGitOperation, branchExistsLocally, commitGitChanges, createWorktreeAsync, deleteWorktree, getBundleReviewDiffForCwd, getGitDiffData, getGitDiffFiles, getGitStatus, getGitUnifiedDiff, getProjectBranch, getProjectBranchSuggestions, getSelectedGitUnifiedDiff, listUnpushedCommitsForCwd, pullGitBranch, pushCurrentBranch, rebaseGitBranchOntoBase, resolveExistingWorktreePath, resolveWorktreePath, summarizeTicketDiffFiles, switchProjectBranch, syncWorktreeFilesAsync, undoLatestUnpushedCommitForCwd } from './lib/git.js';
|
|
21
|
+
import { abortGitOperation, branchExistsLocally, commitGitChanges, createWorktreeAsync, deleteWorktree, getBundleReviewDiffForCwd, getGitDiffData, getGitDiffFiles, getGitStatus, getGitUnifiedDiff, getProjectBranch, getProjectBranchSuggestions, getSelectedGitUnifiedDiff, hasKnownBranchRef, listUnpushedCommitsForCwd, pullGitBranch, pushCurrentBranch, rebaseGitBranchOntoBase, resolveExistingWorktreePath, resolveWorktreePath, summarizeTicketDiffFiles, switchProjectBranch, syncWorktreeFilesAsync, undoLatestUnpushedCommitForCwd } from './lib/git.js';
|
|
22
22
|
import config from './lib/config.js';
|
|
23
23
|
import { autoConfigProject, ProjectAutoConfigError } from './lib/projectAutoConfig.js';
|
|
24
24
|
import { analyzeRepoPath, createInitialCommit, ensureInitialCommitForWorktrees, initializeGitRepo, isInitialCommitRequiredError, } from './lib/projectRepos.js';
|
|
@@ -34,9 +34,12 @@ import { prepareTicketForBuild } from './lib/buildFlow.js';
|
|
|
34
34
|
import { getBundlePlanMutationBlockerById, shouldBlockPlanForBundleMutationById, } from './lib/bundleActivity.js';
|
|
35
35
|
import { getDiscoveredModels, normalizeModelId, normalizeTicketModelId } from './lib/models.js';
|
|
36
36
|
import { upgradeTool, UpgradeInProgressError } from './lib/toolUpgrade.js';
|
|
37
|
-
import { acceptProjectMemorySuggestion, applyProjectMemorySuggestionToDecision, archiveProjectConvention, archiveProjectDecision, createProjectConvention, createProjectDecision, dismissProjectMemorySuggestion, getProjectMemoryById, getProjectMemorySuggestionById, isProjectMemoryEnabled, listProjectConventions, listLatestSuccessfulProjectContextArtifacts, listProjectDecisions, listProjectMemories, listProjectMemorySuggestions, restoreProjectConvention, restoreProjectDecision, serializeProjectMemoryFromConventionValue, serializeProjectMemoryFromDecisionValue, serializeProjectMemoryFromSuggestionValue, supersedeProjectDecision, updateProjectConvention, updateProjectDecision, validateProjectConventionPayload, validateProjectDecisionPayload, } from './lib/projectMemory.js';
|
|
37
|
+
import { acceptProjectMemorySuggestion, applyProjectMemorySuggestionToDecision, applyProjectMemorySuggestionToConvention, archiveProjectConvention, archiveProjectDecision, createProjectConvention, createProjectDecision, dismissProjectMemorySuggestion, getProjectMemoryById, getProjectMemorySuggestionById, isProjectMemoryEnabled, listProjectConventions, listLatestSuccessfulProjectContextArtifacts, listProjectDecisions, listProjectMemories, listProjectMemorySuggestions, restoreProjectConvention, restoreProjectDecision, serializeProjectMemoryFromConventionValue, serializeProjectMemoryFromDecisionValue, serializeProjectMemoryFromSuggestionValue, supersedeProjectDecision, supersedeProjectDecisionFromSuggestion, updateProjectConvention, updateProjectDecision, validateProjectConventionPayload, validateProjectDecisionPayload, } from './lib/projectMemory.js';
|
|
38
38
|
import { selectRelevantProjectMemory } from './lib/projectMemoryPrompt.js';
|
|
39
|
-
import { getProjectContextResponse, startProjectContextScan } from './lib/projectMemoryScan.js';
|
|
39
|
+
import { getProjectContextResponse, getProjectContextScanDetail, getProjectContextScanHistory, startProjectContextScan, } from './lib/projectMemoryScan.js';
|
|
40
|
+
import { listProjectMemoryEvents } from './lib/projectMemoryEvents.js';
|
|
41
|
+
import { getProjectMemorySelectionForAgentRun, listProjectMemoryUsage } from './lib/projectMemorySelections.js';
|
|
42
|
+
import { decodeProjectMemoryCursor } from './lib/projectMemoryCursor.js';
|
|
40
43
|
import { getProjectFileSuggestions } from './lib/projectFileSuggestions.js';
|
|
41
44
|
import { updateProjectTargetSelection } from './lib/projectTargets.js';
|
|
42
45
|
import { exportProjectMemoryArchive, importProjectMemoryArchive } from './lib/projectMemoryTransfer.js';
|
|
@@ -68,7 +71,7 @@ import { isTicketAgentSettingsPayload, updateTicketAgentSettings, } from './lib/
|
|
|
68
71
|
import { resolveBundleReviewOwner, transitionBundleToReview } from './lib/bundleReviewOperations.js';
|
|
69
72
|
import { getActiveBundleStageChangeHead, undoTicketStage } from './lib/ticketUndo.js';
|
|
70
73
|
import { closeAllTerminalSessions, createProjectTerminalSession, createTerminalSessionForWorkspace, destroyTerminalSessionById, registerTerminalSocketHandlers, } from './lib/terminal.js';
|
|
71
|
-
import { countBundleTickets, createTicketRecord, createBundle, deleteBundle, ensureProjectRootBundle, autoParkTicketIfStale, autoParkSettledBundleTicket, isBundleSettledForAutoPark, clearAutoParkDismissalIfNeeded, assignTicketDoneOrder, assignTicketLaneOrder, getBundle, getBundleByName, getBundleByBranch, getBundleRepresentativeTicketContext, isProjectRootBundle, getTicket, isBundledTicket, listTicketsInRunContext, listBundles, listBoardTicketEnrichment, listTickets, resolveTicketBranch, resolveTicketTool, updateBundleAutomationConfig, } from './lib/tickets.js';
|
|
74
|
+
import { countBundleTickets, createTicketRecord, createBundle, deleteBundle, ensureProjectRootBundle, autoParkTicketIfStale, autoParkSettledBundleTicket, isBundleSettledForAutoPark, clearAutoParkDismissalIfNeeded, assignTicketDoneOrder, assignTicketLaneOrder, getBundle, getBundleByName, getBundleByBranch, getBundleRepresentativeTicketContext, isProjectRootBundle, getTicket, isBundledTicket, listTicketsInRunContext, listBundles, listBoardTicketEnrichment, listTickets, resolveTicketBranch, resolveTicketTool, updateBundleAutomationConfig, updateBundleIdentity, } from './lib/tickets.js';
|
|
72
75
|
import { createTicketMessage, upsertTicketDescriptionMessage } from './lib/ticketMessages.js';
|
|
73
76
|
import { listAgentRunsForParent } from './lib/agentRuns.js';
|
|
74
77
|
import { HttpError } from './lib/httpError.js';
|
|
@@ -92,7 +95,7 @@ const sanitizeProjectMemoryExportFileName = (projectName) => {
|
|
|
92
95
|
.toLowerCase()
|
|
93
96
|
.replace(/[^a-z0-9]+/g, '-')
|
|
94
97
|
.replace(/^-+|-+$/g, '');
|
|
95
|
-
return `${normalized || 'project'}-archon-memory-
|
|
98
|
+
return `${normalized || 'project'}-archon-memory-v5.json`;
|
|
96
99
|
};
|
|
97
100
|
const requireExistingProject = (projectId) => {
|
|
98
101
|
const project = getProjectById(projectId);
|
|
@@ -424,19 +427,35 @@ const uniqueAutoBundleName = (projectId) => {
|
|
|
424
427
|
}
|
|
425
428
|
return candidate;
|
|
426
429
|
};
|
|
427
|
-
const hasBundleBranchCaseConflict = (branch, projectId) => {
|
|
430
|
+
const hasBundleBranchCaseConflict = (branch, projectId, excludedBundleId) => {
|
|
428
431
|
const normalizedBranch = branch.toLowerCase();
|
|
429
|
-
return listBundles(projectId).some(bundle => bundle.
|
|
432
|
+
return listBundles(projectId).some(bundle => bundle.id !== excludedBundleId
|
|
433
|
+
&& bundle.branch !== branch
|
|
434
|
+
&& bundle.branch.toLowerCase() === normalizedBranch);
|
|
430
435
|
};
|
|
431
|
-
const uniqueBundleBranchName = (name, projectId) => {
|
|
436
|
+
const uniqueBundleBranchName = (name, projectId, options = {}) => {
|
|
437
|
+
if (options.currentBundle && name === options.currentBundle.name) {
|
|
438
|
+
return options.currentBundle.branch;
|
|
439
|
+
}
|
|
432
440
|
const base = slugify(name);
|
|
433
441
|
let branch = base;
|
|
434
442
|
let suffix = 2;
|
|
435
|
-
|
|
443
|
+
const conflictsWithBranch = (candidate) => {
|
|
444
|
+
const bundleConflict = getBundleByBranch(candidate, projectId);
|
|
445
|
+
if (bundleConflict && bundleConflict.id !== options.currentBundle?.id) {
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
if (!options.project) {
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
return hasKnownBranchRef(candidate, options.project)
|
|
452
|
+
|| Boolean(resolveExistingWorktreePath(candidate, options.project));
|
|
453
|
+
};
|
|
454
|
+
while (conflictsWithBranch(branch)) {
|
|
436
455
|
branch = `${base}-${suffix}`;
|
|
437
456
|
suffix += 1;
|
|
438
457
|
}
|
|
439
|
-
if (hasBundleBranchCaseConflict(branch, projectId)) {
|
|
458
|
+
if (hasBundleBranchCaseConflict(branch, projectId, options.currentBundle?.id)) {
|
|
440
459
|
return null;
|
|
441
460
|
}
|
|
442
461
|
return branch;
|
|
@@ -1218,6 +1237,24 @@ const getBundleRecentCommitSubjects = (project, bundle) => {
|
|
|
1218
1237
|
return [];
|
|
1219
1238
|
}
|
|
1220
1239
|
};
|
|
1240
|
+
const resolveBundleRenameEligibility = (project, bundle, tickets = listBundleTickets(project.id, bundle.id)) => {
|
|
1241
|
+
if (isProjectRootBundle(bundle)) {
|
|
1242
|
+
return { eligible: false, reason: 'project_root' };
|
|
1243
|
+
}
|
|
1244
|
+
if (tickets.some(ticket => ticket.state !== 'plan')) {
|
|
1245
|
+
return { eligible: false, reason: 'tickets_not_plan' };
|
|
1246
|
+
}
|
|
1247
|
+
if (tickets.some(ticket => ticket.agent_status === 'running' || ticket.run_setup_status === 'preparing')) {
|
|
1248
|
+
return { eligible: false, reason: 'agent_running' };
|
|
1249
|
+
}
|
|
1250
|
+
if (resolveExistingWorktreePath(bundle.branch, project)) {
|
|
1251
|
+
return { eligible: false, reason: 'worktree_exists' };
|
|
1252
|
+
}
|
|
1253
|
+
if (hasKnownBranchRef(bundle.branch, project)) {
|
|
1254
|
+
return { eligible: false, reason: 'branch_exists' };
|
|
1255
|
+
}
|
|
1256
|
+
return { eligible: true, reason: null };
|
|
1257
|
+
};
|
|
1221
1258
|
const serializeBundleDetails = (project, bundle, extras = {}) => {
|
|
1222
1259
|
const serializedBundle = serializeBundleRow(project.id, bundle);
|
|
1223
1260
|
if (!serializedBundle) {
|
|
@@ -1234,6 +1271,7 @@ const serializeBundleDetails = (project, bundle, extras = {}) => {
|
|
|
1234
1271
|
const bundleReviewState = getBundleReviewState(project.id, tickets);
|
|
1235
1272
|
return {
|
|
1236
1273
|
bundle: serializedBundle,
|
|
1274
|
+
rename_eligibility: resolveBundleRenameEligibility(project, bundle, tickets),
|
|
1237
1275
|
tickets,
|
|
1238
1276
|
execution_order_ticket_ids: tickets.map(ticket => ticket.id),
|
|
1239
1277
|
latest_session_ticket_id: latestSessionTicket?.id ?? null,
|
|
@@ -3220,8 +3258,42 @@ app.patch('/bundles/:id', (req, res) => {
|
|
|
3220
3258
|
if (!bundle) {
|
|
3221
3259
|
return res.status(404).json({ error: 'Bundle not found' });
|
|
3222
3260
|
}
|
|
3261
|
+
const hasNameUpdate = hasOwn(req.body, 'name');
|
|
3223
3262
|
if (isProjectRootBundle(bundle)) {
|
|
3224
|
-
return res.status(409).json({
|
|
3263
|
+
return res.status(409).json({
|
|
3264
|
+
error: hasNameUpdate
|
|
3265
|
+
? 'Project root bundles cannot be renamed'
|
|
3266
|
+
: 'Project root bundle automation cannot be overridden',
|
|
3267
|
+
});
|
|
3268
|
+
}
|
|
3269
|
+
let nextName = bundle.name;
|
|
3270
|
+
let nextBranch = bundle.branch;
|
|
3271
|
+
if (hasNameUpdate) {
|
|
3272
|
+
if (typeof req.body.name !== 'string' || !req.body.name.trim()) {
|
|
3273
|
+
return res.status(400).json({ error: 'Bundle name must be a non-empty string' });
|
|
3274
|
+
}
|
|
3275
|
+
nextName = req.body.name.trim();
|
|
3276
|
+
if (nextName !== bundle.name) {
|
|
3277
|
+
const nameConflict = getBundleByName(nextName, result.project.id);
|
|
3278
|
+
if (nameConflict && nameConflict.id !== bundle.id) {
|
|
3279
|
+
return res.status(409).json({ error: 'Bundle name already exists in this project' });
|
|
3280
|
+
}
|
|
3281
|
+
const eligibility = resolveBundleRenameEligibility(result.project, bundle);
|
|
3282
|
+
if (!eligibility.eligible) {
|
|
3283
|
+
return res.status(409).json({
|
|
3284
|
+
error: 'Bundle cannot be renamed in its current state',
|
|
3285
|
+
reason: eligibility.reason,
|
|
3286
|
+
});
|
|
3287
|
+
}
|
|
3288
|
+
const branch = uniqueBundleBranchName(nextName, result.project.id, {
|
|
3289
|
+
currentBundle: bundle,
|
|
3290
|
+
project: result.project,
|
|
3291
|
+
});
|
|
3292
|
+
if (!branch) {
|
|
3293
|
+
return res.status(409).json({ error: 'Bundle branch already exists in this project with different casing' });
|
|
3294
|
+
}
|
|
3295
|
+
nextBranch = branch;
|
|
3296
|
+
}
|
|
3225
3297
|
}
|
|
3226
3298
|
let automationDepth = bundle.automation_depth ?? null;
|
|
3227
3299
|
if (hasOwn(req.body, 'automation_depth')) {
|
|
@@ -3251,12 +3323,31 @@ app.patch('/bundles/:id', (req, res) => {
|
|
|
3251
3323
|
return res.status(400).json({ error: 'automation_review_fix_enabled must be a boolean' });
|
|
3252
3324
|
}
|
|
3253
3325
|
}
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3326
|
+
let updated = null;
|
|
3327
|
+
try {
|
|
3328
|
+
updated = db.transaction(() => {
|
|
3329
|
+
if (nextName !== bundle.name) {
|
|
3330
|
+
updateBundleIdentity({
|
|
3331
|
+
bundleId: bundle.id,
|
|
3332
|
+
projectId: result.project.id,
|
|
3333
|
+
name: nextName,
|
|
3334
|
+
branch: nextBranch,
|
|
3335
|
+
});
|
|
3336
|
+
}
|
|
3337
|
+
return updateBundleAutomationConfig({
|
|
3338
|
+
bundleId: bundle.id,
|
|
3339
|
+
projectId: result.project.id,
|
|
3340
|
+
automationDepth,
|
|
3341
|
+
automationReviewFixEnabled,
|
|
3342
|
+
});
|
|
3343
|
+
})();
|
|
3344
|
+
}
|
|
3345
|
+
catch (error) {
|
|
3346
|
+
if (error instanceof Error && 'code' in error && String(error.code).startsWith('SQLITE_CONSTRAINT')) {
|
|
3347
|
+
return res.status(409).json({ error: 'Bundle name or branch already exists in this project' });
|
|
3348
|
+
}
|
|
3349
|
+
throw error;
|
|
3350
|
+
}
|
|
3260
3351
|
listBundleTickets(result.project.id, bundle.id).forEach(ticket => {
|
|
3261
3352
|
io.emit('ticket:updated', ticket);
|
|
3262
3353
|
});
|
|
@@ -4415,7 +4506,7 @@ app.post('/projects', (req, res) => {
|
|
|
4415
4506
|
return res.status(400).json({ error: validation.error });
|
|
4416
4507
|
}
|
|
4417
4508
|
const projectCount = Number(db.prepare('SELECT COUNT(*) AS count FROM projects').get()?.count ?? 0);
|
|
4418
|
-
const { name, repo_path, linked_project_ids, run_setup, run_services, test_commands, run_ide, preview_service_name, preview_path, preview_capability_mode, helper_model, helper_variant, commit_message_rules, auto_park_stale_tickets, memory_enabled, worktree_sync, spec_aware_code_review_enabled, automation_depth, automation_review_fix_enabled, automation_review_severity, automation_review_max_iterations, } = validation.values;
|
|
4509
|
+
const { name, repo_path, linked_project_ids, run_setup, run_services, test_commands, run_ide, preview_service_name, preview_path, preview_capability_mode, helper_model, helper_variant, commit_message_rules, auto_park_stale_tickets, memory_enabled, scan_include_globs_json, scan_exclude_globs_json, worktree_sync, spec_aware_code_review_enabled, automation_depth, automation_review_fix_enabled, automation_review_severity, automation_review_max_iterations, } = validation.values;
|
|
4419
4510
|
if (!repo_path) {
|
|
4420
4511
|
return res.status(400).json({ error: 'repo_path must be a non-empty string' });
|
|
4421
4512
|
}
|
|
@@ -4445,6 +4536,8 @@ app.post('/projects', (req, res) => {
|
|
|
4445
4536
|
commit_message_rules,
|
|
4446
4537
|
auto_park_stale_tickets,
|
|
4447
4538
|
memory_enabled,
|
|
4539
|
+
scan_include_globs_json,
|
|
4540
|
+
scan_exclude_globs_json,
|
|
4448
4541
|
worktree_sync,
|
|
4449
4542
|
spec_aware_code_review_enabled,
|
|
4450
4543
|
automation_depth,
|
|
@@ -4452,8 +4545,8 @@ app.post('/projects', (req, res) => {
|
|
|
4452
4545
|
automation_review_severity,
|
|
4453
4546
|
automation_review_max_iterations
|
|
4454
4547
|
)
|
|
4455
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
4456
|
-
`).run(id, name, normalizedRepoPath, run_setup, run_services, test_commands, run_ide ?? null, preview_service_name ?? null, preview_path ?? null, preview_capability_mode ?? null, helper_model ?? null, helper_variant ?? null, commit_message_rules ?? null, auto_park_stale_tickets ?? 0, memory_enabled ?? 0, worktree_sync ?? null, spec_aware_code_review_enabled ?? 0, automation_depth ?? 'manual', automation_review_fix_enabled ?? 0, automation_review_severity ?? 'medium', automation_review_max_iterations ?? 3);
|
|
4548
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
4549
|
+
`).run(id, name, normalizedRepoPath, run_setup, run_services, test_commands, run_ide ?? null, preview_service_name ?? null, preview_path ?? null, preview_capability_mode ?? null, helper_model ?? null, helper_variant ?? null, commit_message_rules ?? null, auto_park_stale_tickets ?? 0, memory_enabled ?? 0, scan_include_globs_json ?? null, scan_exclude_globs_json ?? null, worktree_sync ?? null, spec_aware_code_review_enabled ?? 0, automation_depth ?? 'manual', automation_review_fix_enabled ?? 0, automation_review_severity ?? 'medium', automation_review_max_iterations ?? 3);
|
|
4457
4550
|
replaceProjectLinks(id, linked_project_ids ?? []);
|
|
4458
4551
|
ensureProjectRootBundle(id);
|
|
4459
4552
|
if (projectCount === 0) {
|
|
@@ -4478,6 +4571,9 @@ app.get('/projects/:id/conventions', (req, res) => {
|
|
|
4478
4571
|
const includeArchived = req.query.include_archived === 'true';
|
|
4479
4572
|
return res.json({ conventions: listProjectConventions(result.project.id, { includeArchived }) });
|
|
4480
4573
|
});
|
|
4574
|
+
const sendProjectMemoryValidationError = (res, validation) => {
|
|
4575
|
+
return res.status(validation.code === 'field_too_large' ? 413 : 400).json(validation);
|
|
4576
|
+
};
|
|
4481
4577
|
app.post('/projects/:id/conventions', (req, res) => {
|
|
4482
4578
|
const result = requireProjectMemoryProject(req.params.id);
|
|
4483
4579
|
if ('error' in result) {
|
|
@@ -4485,7 +4581,7 @@ app.post('/projects/:id/conventions', (req, res) => {
|
|
|
4485
4581
|
}
|
|
4486
4582
|
const validation = validateProjectConventionPayload(req.body);
|
|
4487
4583
|
if ('error' in validation) {
|
|
4488
|
-
return res
|
|
4584
|
+
return sendProjectMemoryValidationError(res, validation);
|
|
4489
4585
|
}
|
|
4490
4586
|
const created = createProjectConvention(result.project.id, {
|
|
4491
4587
|
title: validation.values.title ?? '',
|
|
@@ -4493,6 +4589,8 @@ app.post('/projects/:id/conventions', (req, res) => {
|
|
|
4493
4589
|
instruction: validation.values.instruction ?? '',
|
|
4494
4590
|
rationale: validation.values.rationale ?? null,
|
|
4495
4591
|
stages_json: validation.values.stages_json ?? null,
|
|
4592
|
+
path_globs_json: validation.values.path_globs_json ?? null,
|
|
4593
|
+
component_tags_json: validation.values.component_tags_json ?? null,
|
|
4496
4594
|
priority: validation.values.priority ?? 'normal',
|
|
4497
4595
|
injection_mode: validation.values.injection_mode ?? 'relevant',
|
|
4498
4596
|
});
|
|
@@ -4505,7 +4603,7 @@ app.patch('/projects/:id/conventions/:conventionId', (req, res) => {
|
|
|
4505
4603
|
}
|
|
4506
4604
|
const validation = validateProjectConventionPayload(req.body, { partial: true });
|
|
4507
4605
|
if ('error' in validation) {
|
|
4508
|
-
return res
|
|
4606
|
+
return sendProjectMemoryValidationError(res, validation);
|
|
4509
4607
|
}
|
|
4510
4608
|
const updated = updateProjectConvention(result.project.id, req.params.conventionId, validation.values);
|
|
4511
4609
|
if (!updated) {
|
|
@@ -4519,21 +4617,21 @@ app.post('/projects/:id/conventions/:conventionId/archive', (req, res) => {
|
|
|
4519
4617
|
return res.status(result.status).json({ error: result.error });
|
|
4520
4618
|
}
|
|
4521
4619
|
const archived = archiveProjectConvention(result.project.id, req.params.conventionId);
|
|
4522
|
-
if (!archived) {
|
|
4523
|
-
return res.status(
|
|
4620
|
+
if (archived.status !== 200 || !archived.memory) {
|
|
4621
|
+
return res.status(archived.status).json({ error: archived.error ?? 'Convention lifecycle transition failed' });
|
|
4524
4622
|
}
|
|
4525
|
-
return res.json(archived);
|
|
4623
|
+
return res.json(archived.memory);
|
|
4526
4624
|
});
|
|
4527
4625
|
app.post('/projects/:id/conventions/:conventionId/restore', (req, res) => {
|
|
4528
4626
|
const result = requireProjectMemoryProject(req.params.id);
|
|
4529
4627
|
if ('error' in result) {
|
|
4530
4628
|
return res.status(result.status).json({ error: result.error });
|
|
4531
4629
|
}
|
|
4532
|
-
const restored = restoreProjectConvention(result.project.id, req.params.conventionId);
|
|
4533
|
-
if (!restored) {
|
|
4534
|
-
return res.status(
|
|
4630
|
+
const restored = restoreProjectConvention(result.project.id, req.params.conventionId, req.body?.confirm_conflicts === true);
|
|
4631
|
+
if (restored.status !== 200 || !restored.memory) {
|
|
4632
|
+
return res.status(restored.status).json({ ...restored, error: restored.error ?? 'Convention lifecycle transition failed' });
|
|
4535
4633
|
}
|
|
4536
|
-
return res.json(restored);
|
|
4634
|
+
return res.json(restored.memory);
|
|
4537
4635
|
});
|
|
4538
4636
|
app.get('/projects/:id/decisions', (req, res) => {
|
|
4539
4637
|
const result = requireProjectMemoryProject(req.params.id);
|
|
@@ -4550,7 +4648,7 @@ app.post('/projects/:id/decisions', (req, res) => {
|
|
|
4550
4648
|
}
|
|
4551
4649
|
const validation = validateProjectDecisionPayload(req.body);
|
|
4552
4650
|
if ('error' in validation) {
|
|
4553
|
-
return res
|
|
4651
|
+
return sendProjectMemoryValidationError(res, validation);
|
|
4554
4652
|
}
|
|
4555
4653
|
const created = createProjectDecision(result.project.id, {
|
|
4556
4654
|
title: validation.values.title ?? '',
|
|
@@ -4558,6 +4656,9 @@ app.post('/projects/:id/decisions', (req, res) => {
|
|
|
4558
4656
|
decision: validation.values.decision ?? '',
|
|
4559
4657
|
rationale: validation.values.rationale ?? null,
|
|
4560
4658
|
implications_json: validation.values.implications_json ?? null,
|
|
4659
|
+
stages_json: validation.values.stages_json ?? null,
|
|
4660
|
+
path_globs_json: validation.values.path_globs_json ?? null,
|
|
4661
|
+
component_tags_json: validation.values.component_tags_json ?? null,
|
|
4561
4662
|
});
|
|
4562
4663
|
return res.status(201).json(created);
|
|
4563
4664
|
});
|
|
@@ -4568,7 +4669,7 @@ app.patch('/projects/:id/decisions/:decisionId', (req, res) => {
|
|
|
4568
4669
|
}
|
|
4569
4670
|
const validation = validateProjectDecisionPayload(req.body, { partial: true });
|
|
4570
4671
|
if ('error' in validation) {
|
|
4571
|
-
return res
|
|
4672
|
+
return sendProjectMemoryValidationError(res, validation);
|
|
4572
4673
|
}
|
|
4573
4674
|
const updated = updateProjectDecision(result.project.id, req.params.decisionId, validation.values);
|
|
4574
4675
|
if (!updated) {
|
|
@@ -4582,32 +4683,39 @@ app.post('/projects/:id/decisions/:decisionId/archive', (req, res) => {
|
|
|
4582
4683
|
return res.status(result.status).json({ error: result.error });
|
|
4583
4684
|
}
|
|
4584
4685
|
const archived = archiveProjectDecision(result.project.id, req.params.decisionId);
|
|
4585
|
-
if (!archived) {
|
|
4586
|
-
return res.status(
|
|
4686
|
+
if (archived.status !== 200 || !archived.memory) {
|
|
4687
|
+
return res.status(archived.status).json({ error: archived.error ?? 'Decision lifecycle transition failed' });
|
|
4587
4688
|
}
|
|
4588
|
-
return res.json(archived);
|
|
4689
|
+
return res.json(archived.memory);
|
|
4589
4690
|
});
|
|
4590
4691
|
app.post('/projects/:id/decisions/:decisionId/supersede', (req, res) => {
|
|
4591
4692
|
const result = requireProjectMemoryProject(req.params.id);
|
|
4592
4693
|
if ('error' in result) {
|
|
4593
4694
|
return res.status(result.status).json({ error: result.error });
|
|
4594
4695
|
}
|
|
4595
|
-
const
|
|
4596
|
-
|
|
4597
|
-
|
|
4696
|
+
const replacementDecisionId = typeof req.body?.replacement_decision_id === 'string' ? req.body.replacement_decision_id.trim() : '';
|
|
4697
|
+
let superseded;
|
|
4698
|
+
try {
|
|
4699
|
+
superseded = supersedeProjectDecision(result.project.id, req.params.decisionId, replacementDecisionId);
|
|
4598
4700
|
}
|
|
4599
|
-
|
|
4701
|
+
catch (error) {
|
|
4702
|
+
return res.status(500).json({ error: getErrorMessage(error) });
|
|
4703
|
+
}
|
|
4704
|
+
if (superseded.status !== 200 || !superseded.memory) {
|
|
4705
|
+
return res.status(superseded.status).json({ error: superseded.error ?? 'Decision lifecycle transition failed' });
|
|
4706
|
+
}
|
|
4707
|
+
return res.json(superseded.memory);
|
|
4600
4708
|
});
|
|
4601
4709
|
app.post('/projects/:id/decisions/:decisionId/restore', (req, res) => {
|
|
4602
4710
|
const result = requireProjectMemoryProject(req.params.id);
|
|
4603
4711
|
if ('error' in result) {
|
|
4604
4712
|
return res.status(result.status).json({ error: result.error });
|
|
4605
4713
|
}
|
|
4606
|
-
const restored = restoreProjectDecision(result.project.id, req.params.decisionId);
|
|
4607
|
-
if (!restored) {
|
|
4608
|
-
return res.status(
|
|
4714
|
+
const restored = restoreProjectDecision(result.project.id, req.params.decisionId, req.body?.confirm_conflicts === true);
|
|
4715
|
+
if (restored.status !== 200 || !restored.memory) {
|
|
4716
|
+
return res.status(restored.status).json({ ...restored, error: restored.error ?? 'Decision lifecycle transition failed' });
|
|
4609
4717
|
}
|
|
4610
|
-
return res.json(restored);
|
|
4718
|
+
return res.json(restored.memory);
|
|
4611
4719
|
});
|
|
4612
4720
|
app.get('/projects/:id/memory/relevant', (req, res) => {
|
|
4613
4721
|
const result = requireProjectMemoryProject(req.params.id);
|
|
@@ -4623,6 +4731,7 @@ app.get('/projects/:id/memory/relevant', (req, res) => {
|
|
|
4623
4731
|
return res.status(404).json({ error: 'Ticket not found' });
|
|
4624
4732
|
}
|
|
4625
4733
|
return res.json(selectRelevantProjectMemory(result.project.id, {
|
|
4734
|
+
flow: 'ticket_preview',
|
|
4626
4735
|
stage: ticket.state,
|
|
4627
4736
|
title: ticket.title,
|
|
4628
4737
|
description: ticket.description,
|
|
@@ -4645,7 +4754,7 @@ app.post('/projects/:id/memory/import', (req, res) => {
|
|
|
4645
4754
|
}
|
|
4646
4755
|
const imported = importProjectMemoryArchive(result.project.id, req.body ?? null);
|
|
4647
4756
|
if ('error' in imported) {
|
|
4648
|
-
return res.status(400).json(
|
|
4757
|
+
return res.status(imported.kind === 'limit' ? 413 : 400).json(imported);
|
|
4649
4758
|
}
|
|
4650
4759
|
return res.json(imported.value);
|
|
4651
4760
|
});
|
|
@@ -4673,6 +4782,29 @@ app.get('/projects/:id/context', (req, res) => {
|
|
|
4673
4782
|
}
|
|
4674
4783
|
return res.json(getProjectContextResponse(result.project.id));
|
|
4675
4784
|
});
|
|
4785
|
+
app.get('/projects/:id/context/scans', (req, res) => {
|
|
4786
|
+
const result = requireProjectMemoryProject(req.params.id);
|
|
4787
|
+
if ('error' in result) {
|
|
4788
|
+
return res.status(result.status).json({ error: result.error });
|
|
4789
|
+
}
|
|
4790
|
+
const limit = req.query.limit == null ? 20 : Number(req.query.limit);
|
|
4791
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 50) {
|
|
4792
|
+
return res.status(400).json({ error: 'limit must be an integer between 1 and 50' });
|
|
4793
|
+
}
|
|
4794
|
+
const cursor = typeof req.query.cursor === 'string' ? decodeProjectMemoryCursor(req.query.cursor) : null;
|
|
4795
|
+
if (req.query.cursor != null && !cursor) {
|
|
4796
|
+
return res.status(400).json({ error: 'cursor must be a valid project memory cursor' });
|
|
4797
|
+
}
|
|
4798
|
+
return res.json(getProjectContextScanHistory(result.project.id, { limit, cursor }));
|
|
4799
|
+
});
|
|
4800
|
+
app.get('/projects/:id/context/scans/:scanId', (req, res) => {
|
|
4801
|
+
const result = requireProjectMemoryProject(req.params.id);
|
|
4802
|
+
if ('error' in result) {
|
|
4803
|
+
return res.status(result.status).json({ error: result.error });
|
|
4804
|
+
}
|
|
4805
|
+
const detail = getProjectContextScanDetail(result.project.id, req.params.scanId);
|
|
4806
|
+
return detail ? res.json(detail) : res.status(404).json({ error: 'Project context scan not found' });
|
|
4807
|
+
});
|
|
4676
4808
|
app.get('/projects/:id/context/artifacts', (req, res) => {
|
|
4677
4809
|
const result = requireProjectMemoryProject(req.params.id);
|
|
4678
4810
|
if ('error' in result) {
|
|
@@ -4711,6 +4843,9 @@ app.get('/projects/:id/memories', (req, res) => {
|
|
|
4711
4843
|
if (chatSessionId && !getProjectChatSession(result.project.id, chatSessionId)) {
|
|
4712
4844
|
return res.status(404).json({ error: 'Chat session not found' });
|
|
4713
4845
|
}
|
|
4846
|
+
if (specSessionId && !getProjectSpecSession(result.project.id, specSessionId)) {
|
|
4847
|
+
return res.status(404).json({ error: 'Spec session not found' });
|
|
4848
|
+
}
|
|
4714
4849
|
const includeGeneratedContext = req.query.include_generated_context !== 'false';
|
|
4715
4850
|
return res.json({
|
|
4716
4851
|
memories: listProjectMemories(result.project.id, {
|
|
@@ -4719,6 +4854,7 @@ app.get('/projects/:id/memories', (req, res) => {
|
|
|
4719
4854
|
sources: sourceQuery.values,
|
|
4720
4855
|
ticketId: ticketId || null,
|
|
4721
4856
|
chatSessionId: chatSessionId || null,
|
|
4857
|
+
specSessionId: specSessionId || null,
|
|
4722
4858
|
}),
|
|
4723
4859
|
generated_context: includeGeneratedContext
|
|
4724
4860
|
? {
|
|
@@ -4728,6 +4864,60 @@ app.get('/projects/:id/memories', (req, res) => {
|
|
|
4728
4864
|
: null,
|
|
4729
4865
|
});
|
|
4730
4866
|
});
|
|
4867
|
+
app.get('/projects/:id/memory/agent-runs/:agentRunId/selection', (req, res) => {
|
|
4868
|
+
const result = requireProjectMemoryProject(req.params.id);
|
|
4869
|
+
if ('error' in result) {
|
|
4870
|
+
return res.status(result.status).json({ error: result.error });
|
|
4871
|
+
}
|
|
4872
|
+
const selection = getProjectMemorySelectionForAgentRun(result.project.id, req.params.agentRunId);
|
|
4873
|
+
return selection ? res.json({ selection }) : res.status(404).json({ error: 'Memory selection not found' });
|
|
4874
|
+
});
|
|
4875
|
+
app.get('/projects/:id/memory/items/:kind/:memoryId/usage', (req, res) => {
|
|
4876
|
+
const result = requireProjectMemoryProject(req.params.id);
|
|
4877
|
+
if ('error' in result) {
|
|
4878
|
+
return res.status(result.status).json({ error: result.error });
|
|
4879
|
+
}
|
|
4880
|
+
if (req.params.kind !== 'decision' && req.params.kind !== 'convention') {
|
|
4881
|
+
return res.status(400).json({ error: 'kind must be one of decision or convention' });
|
|
4882
|
+
}
|
|
4883
|
+
const memory = getProjectMemoryById(result.project.id, req.params.memoryId);
|
|
4884
|
+
if (!memory || memory.kind !== req.params.kind || memory.state === 'suggested') {
|
|
4885
|
+
return res.status(404).json({ error: 'Memory not found' });
|
|
4886
|
+
}
|
|
4887
|
+
const limit = req.query.limit == null ? 20 : Number(req.query.limit);
|
|
4888
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 50) {
|
|
4889
|
+
return res.status(400).json({ error: 'limit must be an integer between 1 and 50' });
|
|
4890
|
+
}
|
|
4891
|
+
const cursor = typeof req.query.cursor === 'string' ? decodeProjectMemoryCursor(req.query.cursor) : null;
|
|
4892
|
+
if (req.query.cursor != null && !cursor) {
|
|
4893
|
+
return res.status(400).json({ error: 'cursor must be a valid project memory cursor' });
|
|
4894
|
+
}
|
|
4895
|
+
const page = listProjectMemoryUsage(result.project.id, req.params.kind, memory.id, { limit, cursor });
|
|
4896
|
+
return res.json({ usage: page.usage, next_cursor: page.nextCursor });
|
|
4897
|
+
});
|
|
4898
|
+
app.get('/projects/:id/memory/items/:kind/:memoryId/history', (req, res) => {
|
|
4899
|
+
const result = requireProjectMemoryProject(req.params.id);
|
|
4900
|
+
if ('error' in result) {
|
|
4901
|
+
return res.status(result.status).json({ error: result.error });
|
|
4902
|
+
}
|
|
4903
|
+
if (req.params.kind !== 'decision' && req.params.kind !== 'convention') {
|
|
4904
|
+
return res.status(400).json({ error: 'kind must be one of decision or convention' });
|
|
4905
|
+
}
|
|
4906
|
+
const memory = getProjectMemoryById(result.project.id, req.params.memoryId);
|
|
4907
|
+
if (!memory || memory.kind !== req.params.kind || memory.state === 'suggested') {
|
|
4908
|
+
return res.status(404).json({ error: 'Memory not found' });
|
|
4909
|
+
}
|
|
4910
|
+
const limit = req.query.limit == null ? 20 : Number(req.query.limit);
|
|
4911
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 50) {
|
|
4912
|
+
return res.status(400).json({ error: 'limit must be an integer between 1 and 50' });
|
|
4913
|
+
}
|
|
4914
|
+
const cursor = typeof req.query.cursor === 'string' ? decodeProjectMemoryCursor(req.query.cursor) : null;
|
|
4915
|
+
if (req.query.cursor != null && !cursor) {
|
|
4916
|
+
return res.status(400).json({ error: 'cursor must be a valid project memory cursor' });
|
|
4917
|
+
}
|
|
4918
|
+
const page = listProjectMemoryEvents(result.project.id, req.params.kind, memory.id, { limit, cursor });
|
|
4919
|
+
return res.json({ events: page.events, next_cursor: page.nextCursor });
|
|
4920
|
+
});
|
|
4731
4921
|
app.post('/projects/:id/memories', (req, res) => {
|
|
4732
4922
|
const result = requireProjectMemoryProject(req.params.id);
|
|
4733
4923
|
if ('error' in result) {
|
|
@@ -4740,7 +4930,7 @@ app.post('/projects/:id/memories', (req, res) => {
|
|
|
4740
4930
|
if (kind === 'decision') {
|
|
4741
4931
|
const validation = validateProjectDecisionPayload(req.body ?? {});
|
|
4742
4932
|
if ('error' in validation) {
|
|
4743
|
-
return res
|
|
4933
|
+
return sendProjectMemoryValidationError(res, validation);
|
|
4744
4934
|
}
|
|
4745
4935
|
const created = createProjectDecision(result.project.id, {
|
|
4746
4936
|
title: validation.values.title ?? '',
|
|
@@ -4748,6 +4938,9 @@ app.post('/projects/:id/memories', (req, res) => {
|
|
|
4748
4938
|
decision: validation.values.decision ?? '',
|
|
4749
4939
|
rationale: validation.values.rationale ?? null,
|
|
4750
4940
|
implications_json: validation.values.implications_json ?? null,
|
|
4941
|
+
stages_json: validation.values.stages_json ?? null,
|
|
4942
|
+
path_globs_json: validation.values.path_globs_json ?? null,
|
|
4943
|
+
component_tags_json: validation.values.component_tags_json ?? null,
|
|
4751
4944
|
});
|
|
4752
4945
|
if (!created) {
|
|
4753
4946
|
return res.status(500).json({ error: 'Unable to create the project decision' });
|
|
@@ -4756,7 +4949,7 @@ app.post('/projects/:id/memories', (req, res) => {
|
|
|
4756
4949
|
}
|
|
4757
4950
|
const validation = validateProjectConventionPayload(req.body ?? {});
|
|
4758
4951
|
if ('error' in validation) {
|
|
4759
|
-
return res
|
|
4952
|
+
return sendProjectMemoryValidationError(res, validation);
|
|
4760
4953
|
}
|
|
4761
4954
|
const created = createProjectConvention(result.project.id, {
|
|
4762
4955
|
title: validation.values.title ?? '',
|
|
@@ -4764,6 +4957,8 @@ app.post('/projects/:id/memories', (req, res) => {
|
|
|
4764
4957
|
instruction: validation.values.instruction ?? '',
|
|
4765
4958
|
rationale: validation.values.rationale ?? null,
|
|
4766
4959
|
stages_json: validation.values.stages_json ?? null,
|
|
4960
|
+
path_globs_json: validation.values.path_globs_json ?? null,
|
|
4961
|
+
component_tags_json: validation.values.component_tags_json ?? null,
|
|
4767
4962
|
priority: validation.values.priority ?? 'normal',
|
|
4768
4963
|
injection_mode: validation.values.injection_mode ?? 'relevant',
|
|
4769
4964
|
});
|
|
@@ -4787,7 +4982,7 @@ app.patch('/projects/:id/memories/:memoryId', (req, res) => {
|
|
|
4787
4982
|
if (memory.kind === 'decision') {
|
|
4788
4983
|
const validation = validateProjectDecisionPayload(req.body ?? {}, { partial: true });
|
|
4789
4984
|
if ('error' in validation) {
|
|
4790
|
-
return res
|
|
4985
|
+
return sendProjectMemoryValidationError(res, validation);
|
|
4791
4986
|
}
|
|
4792
4987
|
const updated = updateProjectDecision(result.project.id, memory.id, validation.values);
|
|
4793
4988
|
if (!updated) {
|
|
@@ -4797,7 +4992,7 @@ app.patch('/projects/:id/memories/:memoryId', (req, res) => {
|
|
|
4797
4992
|
}
|
|
4798
4993
|
const validation = validateProjectConventionPayload(req.body ?? {}, { partial: true });
|
|
4799
4994
|
if ('error' in validation) {
|
|
4800
|
-
return res
|
|
4995
|
+
return sendProjectMemoryValidationError(res, validation);
|
|
4801
4996
|
}
|
|
4802
4997
|
const updated = updateProjectConvention(result.project.id, memory.id, validation.values);
|
|
4803
4998
|
if (!updated) {
|
|
@@ -4814,21 +5009,21 @@ app.post('/projects/:id/memories/:memoryId/archive', (req, res) => {
|
|
|
4814
5009
|
if (!memory) {
|
|
4815
5010
|
return res.status(404).json({ error: 'Memory not found' });
|
|
4816
5011
|
}
|
|
4817
|
-
if (memory.
|
|
5012
|
+
if (memory.state === 'suggested' || memory.state === 'dismissed' || memory.state === 'applied_to_existing') {
|
|
4818
5013
|
return res.status(400).json({ error: 'Only saved memories can be archived' });
|
|
4819
5014
|
}
|
|
4820
5015
|
if (memory.kind === 'decision') {
|
|
4821
5016
|
const updated = archiveProjectDecision(result.project.id, memory.id);
|
|
4822
|
-
if (!updated) {
|
|
4823
|
-
return res.status(
|
|
5017
|
+
if (updated.status !== 200 || !updated.memory) {
|
|
5018
|
+
return res.status(updated.status).json({ error: updated.error ?? 'Memory lifecycle transition failed' });
|
|
4824
5019
|
}
|
|
4825
|
-
return res.json({ memory: serializeProjectMemoryFromDecisionValue(updated) });
|
|
5020
|
+
return res.json({ memory: serializeProjectMemoryFromDecisionValue(updated.memory) });
|
|
4826
5021
|
}
|
|
4827
5022
|
const updated = archiveProjectConvention(result.project.id, memory.id);
|
|
4828
|
-
if (!updated) {
|
|
4829
|
-
return res.status(
|
|
5023
|
+
if (updated.status !== 200 || !updated.memory) {
|
|
5024
|
+
return res.status(updated.status).json({ error: updated.error ?? 'Memory lifecycle transition failed' });
|
|
4830
5025
|
}
|
|
4831
|
-
return res.json({ memory: serializeProjectMemoryFromConventionValue(updated) });
|
|
5026
|
+
return res.json({ memory: serializeProjectMemoryFromConventionValue(updated.memory) });
|
|
4832
5027
|
});
|
|
4833
5028
|
app.post('/projects/:id/memories/:memoryId/restore', (req, res) => {
|
|
4834
5029
|
const result = requireProjectMemoryProject(req.params.id);
|
|
@@ -4839,21 +5034,21 @@ app.post('/projects/:id/memories/:memoryId/restore', (req, res) => {
|
|
|
4839
5034
|
if (!memory) {
|
|
4840
5035
|
return res.status(404).json({ error: 'Memory not found' });
|
|
4841
5036
|
}
|
|
4842
|
-
if (memory.
|
|
5037
|
+
if (memory.state === 'suggested' || memory.state === 'dismissed' || memory.state === 'applied_to_existing') {
|
|
4843
5038
|
return res.status(400).json({ error: 'Only saved memories can be restored' });
|
|
4844
5039
|
}
|
|
4845
5040
|
if (memory.kind === 'decision') {
|
|
4846
|
-
const updated = restoreProjectDecision(result.project.id, memory.id);
|
|
4847
|
-
if (!updated) {
|
|
4848
|
-
return res.status(
|
|
5041
|
+
const updated = restoreProjectDecision(result.project.id, memory.id, req.body?.confirm_conflicts === true);
|
|
5042
|
+
if (updated.status !== 200 || !updated.memory) {
|
|
5043
|
+
return res.status(updated.status).json({ ...updated, error: updated.error ?? 'Memory lifecycle transition failed' });
|
|
4849
5044
|
}
|
|
4850
|
-
return res.json({ memory: serializeProjectMemoryFromDecisionValue(updated) });
|
|
5045
|
+
return res.json({ memory: serializeProjectMemoryFromDecisionValue(updated.memory) });
|
|
4851
5046
|
}
|
|
4852
|
-
const updated = restoreProjectConvention(result.project.id, memory.id);
|
|
4853
|
-
if (!updated) {
|
|
4854
|
-
return res.status(
|
|
5047
|
+
const updated = restoreProjectConvention(result.project.id, memory.id, req.body?.confirm_conflicts === true);
|
|
5048
|
+
if (updated.status !== 200 || !updated.memory) {
|
|
5049
|
+
return res.status(updated.status).json({ ...updated, error: updated.error ?? 'Memory lifecycle transition failed' });
|
|
4855
5050
|
}
|
|
4856
|
-
return res.json({ memory: serializeProjectMemoryFromConventionValue(updated) });
|
|
5051
|
+
return res.json({ memory: serializeProjectMemoryFromConventionValue(updated.memory) });
|
|
4857
5052
|
});
|
|
4858
5053
|
app.post('/projects/:id/memories/:memoryId/supersede', (req, res) => {
|
|
4859
5054
|
const result = requireProjectMemoryProject(req.params.id);
|
|
@@ -4864,14 +5059,21 @@ app.post('/projects/:id/memories/:memoryId/supersede', (req, res) => {
|
|
|
4864
5059
|
if (!memory) {
|
|
4865
5060
|
return res.status(404).json({ error: 'Memory not found' });
|
|
4866
5061
|
}
|
|
4867
|
-
if (memory.
|
|
5062
|
+
if (memory.kind !== 'decision' || memory.state === 'suggested' || memory.state === 'dismissed' || memory.state === 'applied_to_existing') {
|
|
4868
5063
|
return res.status(400).json({ error: 'Only saved decisions can be superseded' });
|
|
4869
5064
|
}
|
|
4870
|
-
const
|
|
4871
|
-
|
|
4872
|
-
|
|
5065
|
+
const replacementDecisionId = typeof req.body?.replacement_decision_id === 'string' ? req.body.replacement_decision_id.trim() : '';
|
|
5066
|
+
let updated;
|
|
5067
|
+
try {
|
|
5068
|
+
updated = supersedeProjectDecision(result.project.id, memory.id, replacementDecisionId);
|
|
4873
5069
|
}
|
|
4874
|
-
|
|
5070
|
+
catch (error) {
|
|
5071
|
+
return res.status(500).json({ error: getErrorMessage(error) });
|
|
5072
|
+
}
|
|
5073
|
+
if (updated.status !== 200 || !updated.memory) {
|
|
5074
|
+
return res.status(updated.status).json({ error: updated.error ?? 'Memory lifecycle transition failed' });
|
|
5075
|
+
}
|
|
5076
|
+
return res.json({ memory: serializeProjectMemoryFromDecisionValue(updated.memory) });
|
|
4875
5077
|
});
|
|
4876
5078
|
app.post('/projects/:id/memories/:memoryId/accept', (req, res) => {
|
|
4877
5079
|
const result = requireProjectMemoryProject(req.params.id);
|
|
@@ -4886,11 +5088,14 @@ app.post('/projects/:id/memories/:memoryId/accept', (req, res) => {
|
|
|
4886
5088
|
? validateProjectDecisionPayload(req.body ?? {}, { partial: true })
|
|
4887
5089
|
: validateProjectConventionPayload(req.body ?? {}, { partial: true });
|
|
4888
5090
|
if ('error' in validation) {
|
|
4889
|
-
return res
|
|
5091
|
+
return sendProjectMemoryValidationError(res, validation);
|
|
4890
5092
|
}
|
|
4891
|
-
const accepted = acceptProjectMemorySuggestion(result.project.id, req.params.memoryId, validation.values);
|
|
5093
|
+
const accepted = acceptProjectMemorySuggestion(result.project.id, req.params.memoryId, validation.values, req.body?.confirm_conflicts === true);
|
|
4892
5094
|
if ('error' in accepted) {
|
|
4893
|
-
return res.status(accepted.status).json({
|
|
5095
|
+
return res.status(accepted.status).json({ ...accepted, suggestion: accepted.suggestion ?? null });
|
|
5096
|
+
}
|
|
5097
|
+
if (!('decision' in accepted) && !('convention' in accepted)) {
|
|
5098
|
+
return res.status(500).json({ error: 'Unable to accept the suggestion' });
|
|
4894
5099
|
}
|
|
4895
5100
|
const memory = 'decision' in accepted
|
|
4896
5101
|
? serializeProjectMemoryFromDecisionValue(accepted.decision)
|
|
@@ -4905,11 +5110,12 @@ app.post('/projects/:id/memories/:memoryId/dismiss', (req, res) => {
|
|
|
4905
5110
|
if ('error' in result) {
|
|
4906
5111
|
return res.status(result.status).json({ error: result.error });
|
|
4907
5112
|
}
|
|
4908
|
-
const
|
|
4909
|
-
|
|
4910
|
-
|
|
5113
|
+
const reason = typeof req.body?.reason === 'string' ? req.body.reason : null;
|
|
5114
|
+
const dismissed = dismissProjectMemorySuggestion(result.project.id, req.params.memoryId, reason);
|
|
5115
|
+
if ('error' in dismissed) {
|
|
5116
|
+
return res.status(dismissed.status).json({ error: dismissed.error, suggestion: dismissed.suggestion ?? null });
|
|
4911
5117
|
}
|
|
4912
|
-
const memory = serializeProjectMemoryFromSuggestionValue(dismissed);
|
|
5118
|
+
const memory = serializeProjectMemoryFromSuggestionValue(dismissed.suggestion);
|
|
4913
5119
|
if (!memory) {
|
|
4914
5120
|
return res.status(400).json({ error: 'Only suggested memories can be dismissed' });
|
|
4915
5121
|
}
|
|
@@ -4924,18 +5130,17 @@ app.post('/projects/:id/memories/:memoryId/apply-to-existing', (req, res) => {
|
|
|
4924
5130
|
if (!suggestion) {
|
|
4925
5131
|
return res.status(404).json({ error: 'Memory not found' });
|
|
4926
5132
|
}
|
|
4927
|
-
if (suggestion.kind !== 'decision') {
|
|
4928
|
-
return res.status(400).json({ error: 'Only decision suggestions can update an existing memory' });
|
|
4929
|
-
}
|
|
4930
5133
|
const decisionId = typeof req.body?.memory_id === 'string' ? req.body.memory_id.trim() : '';
|
|
4931
5134
|
if (!decisionId) {
|
|
4932
5135
|
return res.status(400).json({ error: 'memory_id is required' });
|
|
4933
5136
|
}
|
|
4934
|
-
const validation = validateProjectDecisionPayload(req.body ?? {}, { partial: true });
|
|
5137
|
+
const validation = suggestion.kind === 'decision' ? validateProjectDecisionPayload(req.body ?? {}, { partial: true }) : validateProjectConventionPayload(req.body ?? {}, { partial: true });
|
|
4935
5138
|
if ('error' in validation) {
|
|
4936
|
-
return res
|
|
5139
|
+
return sendProjectMemoryValidationError(res, validation);
|
|
4937
5140
|
}
|
|
4938
|
-
const updated =
|
|
5141
|
+
const updated = suggestion.kind === 'decision'
|
|
5142
|
+
? applyProjectMemorySuggestionToDecision(result.project.id, req.params.memoryId, decisionId, validation.values)
|
|
5143
|
+
: applyProjectMemorySuggestionToConvention(result.project.id, req.params.memoryId, decisionId, validation.values);
|
|
4939
5144
|
if ('error' in updated) {
|
|
4940
5145
|
return res.status(updated.status).json({ error: updated.error, suggestion: updated.suggestion ?? null });
|
|
4941
5146
|
}
|
|
@@ -4989,11 +5194,11 @@ app.post('/projects/:id/memory/suggestions/:suggestionId/accept', (req, res) =>
|
|
|
4989
5194
|
? validateProjectDecisionPayload(req.body ?? {}, { partial: true })
|
|
4990
5195
|
: validateProjectConventionPayload(req.body ?? {}, { partial: true });
|
|
4991
5196
|
if ('error' in validation) {
|
|
4992
|
-
return res
|
|
5197
|
+
return sendProjectMemoryValidationError(res, validation);
|
|
4993
5198
|
}
|
|
4994
|
-
const accepted = acceptProjectMemorySuggestion(result.project.id, req.params.suggestionId, validation.values);
|
|
5199
|
+
const accepted = acceptProjectMemorySuggestion(result.project.id, req.params.suggestionId, validation.values, req.body?.confirm_conflicts === true);
|
|
4995
5200
|
if ('error' in accepted) {
|
|
4996
|
-
return res.status(accepted.status).json({
|
|
5201
|
+
return res.status(accepted.status).json({ ...accepted, suggestion: accepted.suggestion ?? null });
|
|
4997
5202
|
}
|
|
4998
5203
|
return res.json(accepted);
|
|
4999
5204
|
});
|
|
@@ -5006,18 +5211,17 @@ app.post('/projects/:id/memory/suggestions/:suggestionId/update-existing', (req,
|
|
|
5006
5211
|
if (!suggestion) {
|
|
5007
5212
|
return res.status(404).json({ error: 'Suggestion not found' });
|
|
5008
5213
|
}
|
|
5009
|
-
|
|
5010
|
-
return res.status(400).json({ error: 'Only decision suggestions can update an existing memory' });
|
|
5011
|
-
}
|
|
5012
|
-
const decisionId = typeof req.body?.decision_id === 'string' ? req.body.decision_id.trim() : '';
|
|
5214
|
+
const decisionId = typeof req.body?.memory_id === 'string' ? req.body.memory_id.trim() : typeof req.body?.decision_id === 'string' ? req.body.decision_id.trim() : '';
|
|
5013
5215
|
if (!decisionId) {
|
|
5014
5216
|
return res.status(400).json({ error: 'decision_id is required' });
|
|
5015
5217
|
}
|
|
5016
|
-
const validation = validateProjectDecisionPayload(req.body ?? {}, { partial: true });
|
|
5218
|
+
const validation = suggestion.kind === 'decision' ? validateProjectDecisionPayload(req.body ?? {}, { partial: true }) : validateProjectConventionPayload(req.body ?? {}, { partial: true });
|
|
5017
5219
|
if ('error' in validation) {
|
|
5018
|
-
return res
|
|
5220
|
+
return sendProjectMemoryValidationError(res, validation);
|
|
5019
5221
|
}
|
|
5020
|
-
const updated =
|
|
5222
|
+
const updated = suggestion.kind === 'decision'
|
|
5223
|
+
? applyProjectMemorySuggestionToDecision(result.project.id, req.params.suggestionId, decisionId, validation.values)
|
|
5224
|
+
: applyProjectMemorySuggestionToConvention(result.project.id, req.params.suggestionId, decisionId, validation.values);
|
|
5021
5225
|
if ('error' in updated) {
|
|
5022
5226
|
return res.status(updated.status).json({ error: updated.error, suggestion: updated.suggestion ?? null });
|
|
5023
5227
|
}
|
|
@@ -5028,11 +5232,31 @@ app.post('/projects/:id/memory/suggestions/:suggestionId/dismiss', (req, res) =>
|
|
|
5028
5232
|
if ('error' in result) {
|
|
5029
5233
|
return res.status(result.status).json({ error: result.error });
|
|
5030
5234
|
}
|
|
5031
|
-
const
|
|
5032
|
-
|
|
5033
|
-
|
|
5235
|
+
const reason = typeof req.body?.reason === 'string' ? req.body.reason : null;
|
|
5236
|
+
const dismissed = dismissProjectMemorySuggestion(result.project.id, req.params.suggestionId, reason);
|
|
5237
|
+
if ('error' in dismissed) {
|
|
5238
|
+
return res.status(dismissed.status).json({ error: dismissed.error, suggestion: dismissed.suggestion ?? null });
|
|
5239
|
+
}
|
|
5240
|
+
return res.json(dismissed.suggestion);
|
|
5241
|
+
});
|
|
5242
|
+
app.post('/projects/:id/memory/suggestions/:suggestionId/supersede', (req, res) => {
|
|
5243
|
+
const result = requireProjectMemoryProject(req.params.id);
|
|
5244
|
+
if ('error' in result) {
|
|
5245
|
+
return res.status(result.status).json({ error: result.error });
|
|
5034
5246
|
}
|
|
5035
|
-
|
|
5247
|
+
const targetDecisionId = typeof req.body?.decision_id === 'string' ? req.body.decision_id.trim() : '';
|
|
5248
|
+
if (!targetDecisionId) {
|
|
5249
|
+
return res.status(400).json({ error: 'decision_id is required' });
|
|
5250
|
+
}
|
|
5251
|
+
const validation = validateProjectDecisionPayload(req.body ?? {}, { partial: true });
|
|
5252
|
+
if ('error' in validation) {
|
|
5253
|
+
return sendProjectMemoryValidationError(res, validation);
|
|
5254
|
+
}
|
|
5255
|
+
const superseded = supersedeProjectDecisionFromSuggestion(result.project.id, req.params.suggestionId, targetDecisionId, validation.values);
|
|
5256
|
+
if ('error' in superseded) {
|
|
5257
|
+
return res.status(superseded.status).json({ error: superseded.error, suggestion: superseded.suggestion ?? null });
|
|
5258
|
+
}
|
|
5259
|
+
return res.json(superseded);
|
|
5036
5260
|
});
|
|
5037
5261
|
app.get('/projects/:id/branch', (req, res) => {
|
|
5038
5262
|
const project = getProjectById(req.params.id);
|
|
@@ -5434,7 +5658,7 @@ app.patch('/projects/:id', (req, res) => {
|
|
|
5434
5658
|
});
|
|
5435
5659
|
}
|
|
5436
5660
|
}
|
|
5437
|
-
const { name, repo_path, linked_project_ids, run_setup, run_services, test_commands, run_ide, preview_service_name, preview_path, preview_capability_mode, helper_model, helper_variant, commit_message_rules, auto_park_stale_tickets, memory_enabled, worktree_sync, spec_aware_code_review_enabled, automation_depth, automation_review_fix_enabled, automation_review_severity, automation_review_max_iterations, } = validation.values;
|
|
5661
|
+
const { name, repo_path, linked_project_ids, run_setup, run_services, test_commands, run_ide, preview_service_name, preview_path, preview_capability_mode, helper_model, helper_variant, commit_message_rules, auto_park_stale_tickets, memory_enabled, scan_include_globs_json, scan_exclude_globs_json, worktree_sync, spec_aware_code_review_enabled, automation_depth, automation_review_fix_enabled, automation_review_severity, automation_review_max_iterations, } = validation.values;
|
|
5438
5662
|
db.prepare(`
|
|
5439
5663
|
UPDATE projects SET
|
|
5440
5664
|
name = ?,
|
|
@@ -5451,6 +5675,8 @@ app.patch('/projects/:id', (req, res) => {
|
|
|
5451
5675
|
commit_message_rules = ?,
|
|
5452
5676
|
auto_park_stale_tickets = ?,
|
|
5453
5677
|
memory_enabled = ?,
|
|
5678
|
+
scan_include_globs_json = ?,
|
|
5679
|
+
scan_exclude_globs_json = ?,
|
|
5454
5680
|
worktree_sync = ?,
|
|
5455
5681
|
spec_aware_code_review_enabled = ?,
|
|
5456
5682
|
automation_depth = ?,
|
|
@@ -5459,7 +5685,7 @@ app.patch('/projects/:id', (req, res) => {
|
|
|
5459
5685
|
automation_review_max_iterations = ?,
|
|
5460
5686
|
updated_at = CURRENT_TIMESTAMP
|
|
5461
5687
|
WHERE id = ?
|
|
5462
|
-
`).run(name ?? project.name, repo_path ?? project.repo_path, hasOwn(validation.values, 'run_setup') ? run_setup : project.run_setup, hasOwn(validation.values, 'run_services') ? run_services : project.run_services, hasOwn(validation.values, 'test_commands') ? test_commands : project.test_commands, hasOwn(validation.values, 'run_ide') ? run_ide : project.run_ide, hasOwn(validation.values, 'preview_service_name') ? preview_service_name : project.preview_service_name, hasOwn(validation.values, 'preview_path') ? preview_path : project.preview_path, hasOwn(validation.values, 'preview_capability_mode') ? preview_capability_mode : project.preview_capability_mode, hasOwn(validation.values, 'helper_model') ? helper_model : project.helper_model, hasOwn(validation.values, 'helper_variant') ? helper_variant : project.helper_variant, hasOwn(validation.values, 'commit_message_rules') ? commit_message_rules : project.commit_message_rules, hasOwn(validation.values, 'auto_park_stale_tickets') ? auto_park_stale_tickets : project.auto_park_stale_tickets, hasOwn(validation.values, 'memory_enabled') ? memory_enabled : project.memory_enabled, hasOwn(validation.values, 'worktree_sync') ? worktree_sync : project.worktree_sync, hasOwn(validation.values, 'spec_aware_code_review_enabled') ? spec_aware_code_review_enabled : project.spec_aware_code_review_enabled, hasOwn(validation.values, 'automation_depth') ? automation_depth : project.automation_depth, hasOwn(validation.values, 'automation_review_fix_enabled') ? automation_review_fix_enabled : project.automation_review_fix_enabled, hasOwn(validation.values, 'automation_review_severity') ? automation_review_severity : project.automation_review_severity, hasOwn(validation.values, 'automation_review_max_iterations') ? automation_review_max_iterations : project.automation_review_max_iterations, req.params.id);
|
|
5688
|
+
`).run(name ?? project.name, repo_path ?? project.repo_path, hasOwn(validation.values, 'run_setup') ? run_setup : project.run_setup, hasOwn(validation.values, 'run_services') ? run_services : project.run_services, hasOwn(validation.values, 'test_commands') ? test_commands : project.test_commands, hasOwn(validation.values, 'run_ide') ? run_ide : project.run_ide, hasOwn(validation.values, 'preview_service_name') ? preview_service_name : project.preview_service_name, hasOwn(validation.values, 'preview_path') ? preview_path : project.preview_path, hasOwn(validation.values, 'preview_capability_mode') ? preview_capability_mode : project.preview_capability_mode, hasOwn(validation.values, 'helper_model') ? helper_model : project.helper_model, hasOwn(validation.values, 'helper_variant') ? helper_variant : project.helper_variant, hasOwn(validation.values, 'commit_message_rules') ? commit_message_rules : project.commit_message_rules, hasOwn(validation.values, 'auto_park_stale_tickets') ? auto_park_stale_tickets : project.auto_park_stale_tickets, hasOwn(validation.values, 'memory_enabled') ? memory_enabled : project.memory_enabled, hasOwn(validation.values, 'scan_include_globs_json') ? scan_include_globs_json : project.scan_include_globs_json, hasOwn(validation.values, 'scan_exclude_globs_json') ? scan_exclude_globs_json : project.scan_exclude_globs_json, hasOwn(validation.values, 'worktree_sync') ? worktree_sync : project.worktree_sync, hasOwn(validation.values, 'spec_aware_code_review_enabled') ? spec_aware_code_review_enabled : project.spec_aware_code_review_enabled, hasOwn(validation.values, 'automation_depth') ? automation_depth : project.automation_depth, hasOwn(validation.values, 'automation_review_fix_enabled') ? automation_review_fix_enabled : project.automation_review_fix_enabled, hasOwn(validation.values, 'automation_review_severity') ? automation_review_severity : project.automation_review_severity, hasOwn(validation.values, 'automation_review_max_iterations') ? automation_review_max_iterations : project.automation_review_max_iterations, req.params.id);
|
|
5463
5689
|
if (hasOwn(validation.values, 'linked_project_ids')) {
|
|
5464
5690
|
replaceProjectLinks(project.id, linked_project_ids ?? []);
|
|
5465
5691
|
}
|