Skip to content

Commit 6191f13

Browse files
authored
Merge pull request #172 from rostilos/1.5.6-rc
1.5.6 rc
2 parents 9979904 + 0cf12f0 commit 6191f13

63 files changed

Lines changed: 5573 additions & 77 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ CodeCrow supports multiple version control systems. The AI analysis engine is th
3434
| `/ask <question>` ||||
3535
| `/analyze` ||||
3636
| `/summarize` ||||
37+
| `/qa-doc` ||||
3738

3839
### Dashboard & Issue Management
3940

@@ -50,6 +51,8 @@ These features are platform-independent and available through the CodeCrow web U
5051
| Project Analytics | Aggregated severity breakdown, analysis history, and branch health |
5152
| AI Model Selection | Choose your LLM provider and model (OpenRouter, Anthropic, Google, OpenAI) |
5253
| Workspace & Team Management | Roles (Owner, Admin, Member, Viewer), member invites, ownership transfer |
54+
| Task Management (Jira) | Connect Jira Cloud to link PRs with tasks for QA documentation |
55+
| QA Auto-Documentation | AI-generated QA docs posted as Jira comments after each analysis |
5356
| Two-Factor Authentication | TOTP-based 2FA for sensitive operations |
5457

5558
### Setup Methods
@@ -105,7 +108,8 @@ The RAG pipeline (codebase indexing for context-aware reviews) provides enhanced
105108
- **Context-Aware Reviews**: Powered by a custom RAG (Retrieval-Augmented Generation) pipeline using Qdrant vector storage.
106109
- **Incremental Analysis**: Only scans changed code to keep feedback fast and cost-efficient.
107110
- **Multi-Tenant Architecture**: Securely manage multiple teams and projects from a single dashboard.
108-
- **Interactive Commands**: Command CodeCrow directly from PR comments using `/ask`, `/analyze`, and `/summarize`.
111+
- **Interactive Commands**: Command CodeCrow directly from PR comments using `/ask`, `/analyze`, `/summarize`, and `/qa-doc`.
112+
- **QA Auto-Documentation**: Automatically generate QA testing documentation from PR analysis and post it to linked Jira tickets. Task IDs are auto-detected from branch names, PR titles, or PR descriptions — or you can specify one explicitly with `/qa-doc PROJ-123`.
109113
- **Issue Lifecycle**: Automatic tracking of resolved vs. open issues across analyses with deterministic and AI-based reconciliation.
110114
- **Bring Your Own Model**: Connect your preferred LLM provider — OpenRouter, Anthropic, Google, or OpenAI.
111115

deployment/docker-compose.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,26 @@ services:
2424
retries: 5
2525
start_period: 30s
2626

27+
pgadmin:
28+
image: dpage/pgadmin4:9
29+
container_name: codecrow-pgadmin
30+
environment:
31+
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin@localhost}
32+
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:?PGADMIN_DEFAULT_PASSWORD must be set in .env}
33+
PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: "True"
34+
PGADMIN_DISABLE_POSTFIX: "True"
35+
PGADMIN_LISTEN_PORT: 80
36+
ports:
37+
- "127.0.0.1:5050:80"
38+
depends_on:
39+
postgres:
40+
condition: service_healthy
41+
volumes:
42+
- pgadmin_data:/var/lib/pgadmin
43+
networks:
44+
- codecrow-network
45+
restart: unless-stopped
46+
2747
redis:
2848
image: redis:7-alpine
2949
container_name: codecrow-redis
@@ -207,8 +227,10 @@ services:
207227
CODECROW_INTERNAL_SECRET: ${INTERNAL_API_SECRET:?INTERNAL_API_SECRET must be set in .env}
208228
QDRANT_API_KEY: ${QDRANT_API_KEY:?QDRANT_API_KEY must be set in .env}
209229
REDIS_URL: redis://redis:6379/1
230+
#UVICORN_WORKERS: 1
210231
ports:
211232
- "127.0.0.1:8001:8001"
233+
#- "127.0.0.1:5678:5678"
212234
depends_on:
213235
redis:
214236
condition: service_healthy
@@ -220,6 +242,7 @@ services:
220242
condition: service_healthy
221243
networks:
222244
- codecrow-network
245+
#command: ["python", "-m", "debugpy", "--listen", "0.0.0.0:5678", "main.py"]
223246
volumes:
224247
- source_code_tmp:/tmp
225248
- rag_logs:/app/logs
@@ -274,6 +297,9 @@ volumes:
274297
qdrant_data:
275298
name: qdrant_data
276299
driver: local
300+
pgadmin_data:
301+
name: pgadmin_data
302+
driver: local
277303
web_logs:
278304
name: web_logs
279305
driver: local

java-ecosystem/libs/analysis-engine/src/main/java/org/rostilos/codecrow/analysisengine/util/DiffParsingUtils.java

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,15 +149,21 @@ public static int mapLineNumber(int oldLine, List<int[]> hunks, String fileDiff)
149149
* Walk a hunk body line-by-line to map an old line number to its new position.
150150
* <p>
151151
* Context lines advance both counters; {@code -} lines advance only old;
152-
* {@code +} lines advance only new. If the target old line was deleted,
153-
* returns the original line number unchanged.
152+
* {@code +} lines advance only new.
153+
* <p>
154+
* If the target old line was <b>deleted</b>, returns the new-file line number
155+
* of the next surviving (context or added) line after the deletion block.
156+
* This anchors the issue to the nearest relevant context in the new file
157+
* rather than leaving it at a stale old position.
158+
* Falls back to {@code hunkNewStart} if no surviving line is found.
154159
*/
155160
static int mapLineInsideHunk(int targetOldLine, int hunkOldStart,
156161
int hunkNewStart, String fileDiff) {
157162
String[] lines = fileDiff.split("\\r?\\n");
158163
int oldCursor = hunkOldStart;
159164
int newCursor = hunkNewStart;
160165
boolean inTargetHunk = false;
166+
boolean targetWasDeleted = false;
161167

162168
for (String line : lines) {
163169
Matcher hm = HUNK_HEADER.matcher(line);
@@ -178,19 +184,39 @@ static int mapLineInsideHunk(int targetOldLine, int hunkOldStart,
178184

179185
if (line.startsWith("-")) {
180186
if (oldCursor == targetOldLine) {
181-
return targetOldLine;
187+
// Target line was deleted — don't return the old number.
188+
// Continue scanning to find the next surviving line
189+
// (context or "+" line) and return its new position.
190+
targetWasDeleted = true;
182191
}
183192
oldCursor++;
184193
} else if (line.startsWith("+")) {
194+
if (targetWasDeleted) {
195+
// First added/surviving line after the deletion — anchor here
196+
return newCursor;
197+
}
185198
newCursor++;
186199
} else {
200+
// Context line (unchanged)
201+
if (targetWasDeleted) {
202+
// First surviving context line after the deletion — anchor here
203+
return newCursor;
204+
}
187205
if (oldCursor == targetOldLine) {
188206
return newCursor;
189207
}
190208
oldCursor++;
191209
newCursor++;
192210
}
193211
}
212+
213+
// If we flagged the line as deleted but found no surviving line after it
214+
// (e.g., deletion was at the very end of the hunk), return the current
215+
// new-cursor position which points to the end of the hunk in the new file.
216+
if (targetWasDeleted) {
217+
return newCursor > hunkNewStart ? newCursor - 1 : hunkNewStart;
218+
}
219+
194220
return targetOldLine;
195221
}
196222
}

java-ecosystem/libs/core/src/main/java/module-info.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,12 @@
140140

141141
opens org.rostilos.codecrow.core.model.reconcile
142142
to org.hibernate.orm.core, spring.beans, spring.context, spring.core;
143+
144+
// Task-management integration (QA Auto-Documentation)
145+
exports org.rostilos.codecrow.core.model.taskmanagement;
146+
exports org.rostilos.codecrow.core.persistence.repository.taskmanagement;
147+
exports org.rostilos.codecrow.core.dto.taskmanagement;
148+
149+
opens org.rostilos.codecrow.core.model.taskmanagement
150+
to org.hibernate.orm.core, spring.beans, spring.context, spring.core;
143151
}

java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/dto/project/ProjectDTO.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.rostilos.codecrow.core.model.project.config.CommentCommandsConfig;
66
import org.rostilos.codecrow.core.model.project.config.ProjectConfig;
77
import org.rostilos.codecrow.core.model.project.config.ProjectRulesConfig;
8+
import org.rostilos.codecrow.core.model.project.config.QaAutoDocConfig;
89
import org.rostilos.codecrow.core.model.project.config.RagConfig;
910
import org.rostilos.codecrow.core.model.vcs.VcsConnection;
1011
import org.rostilos.codecrow.core.model.vcs.VcsRepoInfo;
@@ -36,7 +37,8 @@ public record ProjectDTO(
3637
Long qualityGateId,
3738
Integer maxAnalysisTokenLimit,
3839
Boolean useMcpTools,
39-
ProjectRulesConfigDTO projectRulesConfig) {
40+
ProjectRulesConfigDTO projectRulesConfig,
41+
QaAutoDocConfigDTO qaAutoDocConfig) {
4042
public static ProjectDTO fromProject(Project project) {
4143
Long vcsConnectionId = null;
4244
String vcsConnectionType = null;
@@ -139,6 +141,12 @@ public static ProjectDTO fromProject(Project project) {
139141
projectRulesConfigDTO = ProjectRulesConfigDTO.fromConfig(config.projectRules());
140142
}
141143

144+
// Get QA auto-doc config
145+
QaAutoDocConfigDTO qaAutoDocConfigDTO = null;
146+
if (config != null && config.qaAutoDoc() != null) {
147+
qaAutoDocConfigDTO = QaAutoDocConfigDTO.fromConfig(config.qaAutoDoc());
148+
}
149+
142150
return new ProjectDTO(
143151
project.getId(),
144152
project.getName(),
@@ -164,7 +172,8 @@ public static ProjectDTO fromProject(Project project) {
164172
project.getQualityGate() != null ? project.getQualityGate().getId() : null,
165173
maxAnalysisTokenLimit,
166174
useMcpTools,
167-
projectRulesConfigDTO);
175+
projectRulesConfigDTO,
176+
qaAutoDocConfigDTO);
168177
}
169178

170179
public record DefaultBranchStats(
@@ -252,4 +261,30 @@ public record CustomRuleDTO(
252261
boolean enabled,
253262
int priority
254263
) {}
264+
265+
/**
266+
* DTO for QA auto-documentation configuration.
267+
*/
268+
public record QaAutoDocConfigDTO(
269+
boolean enabled,
270+
Long taskManagementConnectionId,
271+
String taskIdPattern,
272+
String taskIdSource,
273+
String templateMode,
274+
String customTemplate
275+
) {
276+
public static QaAutoDocConfigDTO fromConfig(QaAutoDocConfig config) {
277+
if (config == null) {
278+
return new QaAutoDocConfigDTO(false, null, null, null, null, null);
279+
}
280+
return new QaAutoDocConfigDTO(
281+
config.enabled(),
282+
config.taskManagementConnectionId(),
283+
config.taskIdPattern(),
284+
config.taskIdSource() != null ? config.taskIdSource().name() : null,
285+
config.templateMode() != null ? config.templateMode().name() : null,
286+
config.customTemplate()
287+
);
288+
}
289+
}
255290
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package org.rostilos.codecrow.core.dto.taskmanagement;
2+
3+
import jakarta.validation.constraints.NotNull;
4+
import jakarta.validation.constraints.Size;
5+
6+
/**
7+
* Request DTO for updating QA auto-documentation configuration on a project.
8+
*/
9+
public record QaAutoDocConfigRequest(
10+
@NotNull(message = "Enabled flag is required")
11+
Boolean enabled,
12+
13+
Long taskManagementConnectionId,
14+
15+
/** Regex pattern for extracting task ID from PR metadata */
16+
@Size(max = 256, message = "Task ID pattern must be at most 256 characters")
17+
String taskIdPattern,
18+
19+
/** Where to look for the task ID: BRANCH_NAME, PR_TITLE, PR_DESCRIPTION */
20+
String taskIdSource,
21+
22+
/** Template mode: RAW, BASE, CUSTOM */
23+
String templateMode,
24+
25+
/** Custom template text (only for CUSTOM mode, max 5000 chars) */
26+
@Size(max = 5000, message = "Custom template must be at most 5000 characters")
27+
String customTemplate
28+
) {
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.rostilos.codecrow.core.dto.taskmanagement;
2+
3+
import jakarta.validation.constraints.NotBlank;
4+
import jakarta.validation.constraints.NotNull;
5+
import jakarta.validation.constraints.Size;
6+
7+
/**
8+
* Request DTO for creating or updating a task management connection.
9+
* <p>
10+
* Provider-specific credential requirements are validated at the service layer,
11+
* since different providers need different credential fields:
12+
* <ul>
13+
* <li>Jira Cloud: {@code email} + {@code apiToken}</li>
14+
* <li>Jira Data Center (future): {@code apiToken} only (Personal Access Token)</li>
15+
* </ul>
16+
*/
17+
public record TaskManagementConnectionRequest(
18+
@NotBlank(message = "Connection name is required")
19+
@Size(max = 256, message = "Connection name must be at most 256 characters")
20+
String connectionName,
21+
22+
@NotNull(message = "Provider type is required")
23+
String providerType,
24+
25+
@NotBlank(message = "Base URL is required")
26+
@Size(max = 512, message = "Base URL must be at most 512 characters")
27+
String baseUrl,
28+
29+
/** Required for Jira Cloud; may be null for other providers. */
30+
String email,
31+
32+
/** API token (Jira Cloud) or Personal Access Token (Jira Data Center). */
33+
String apiToken
34+
) {
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.rostilos.codecrow.core.dto.taskmanagement;
2+
3+
import java.time.OffsetDateTime;
4+
5+
/**
6+
* Response DTO for task management connection details.
7+
* Excludes sensitive credential data.
8+
*/
9+
public record TaskManagementConnectionResponse(
10+
Long id,
11+
String connectionName,
12+
String providerType,
13+
String status,
14+
String baseUrl,
15+
/** Masked email (e.g. "j***@example.com") — never returns raw credentials */
16+
String maskedEmail,
17+
OffsetDateTime createdAt,
18+
OffsetDateTime updatedAt
19+
) {
20+
}

java-ecosystem/libs/core/src/main/java/org/rostilos/codecrow/core/model/job/JobType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public enum JobType {
1313
ASK_COMMAND,
1414
ANALYZE_COMMAND,
1515
REVIEW_COMMAND,
16+
QA_DOC_COMMAND,
1617
// Ignored comment events (not CodeCrow commands)
1718
IGNORED_COMMENT
1819
}

0 commit comments

Comments
 (0)