karajan-code 1.47.0 → 1.48.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karajan-code",
3
- "version": "1.47.0",
3
+ "version": "1.48.0",
4
4
  "description": "Local multi-agent coding orchestrator with TDD, SonarQube, and code review pipeline",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0",
package/src/hu/store.js CHANGED
@@ -90,7 +90,9 @@ export function updateStoryStatus(batch, storyId, status, extra = {}) {
90
90
  const story = batch.stories.find(s => s.id === storyId);
91
91
  if (!story) throw new Error(`Story ${storyId} not found`);
92
92
  story.status = status;
93
- story.updated_at = new Date().toISOString();
93
+ const now = new Date().toISOString();
94
+ story.statusChangedAt = now;
95
+ story.updated_at = now;
94
96
  Object.assign(story, extra);
95
97
  return story;
96
98
  }
@@ -114,13 +114,30 @@ async function runSingleHu({ storyId, batch, batchSessionId, runIterationFn, emi
114
114
 
115
115
  updateStoryStatus(batch, story.id, HU_STATUS.CODING);
116
116
  await saveHuBatch(batchSessionId, batch);
117
+ emitProgress(emitter, makeEvent("hu:status-change", { ...eventBase, stage: "hu-sub-pipeline" }, {
118
+ message: `HU ${story.id} status → coding`,
119
+ detail: { huId: story.id, status: HU_STATUS.CODING, timestamp: new Date().toISOString() }
120
+ }));
117
121
 
118
122
  try {
119
123
  const iterResult = await runIterationFn(huTask);
120
124
  const approved = Boolean(iterResult?.approved);
121
125
 
126
+ // --- Transition to reviewing (post-coder, pre-reviewer evaluation) ---
127
+ updateStoryStatus(batch, story.id, HU_STATUS.REVIEWING);
128
+ await saveHuBatch(batchSessionId, batch);
129
+ emitProgress(emitter, makeEvent("hu:status-change", { ...eventBase, stage: "hu-sub-pipeline" }, {
130
+ message: `HU ${story.id} status → reviewing`,
131
+ detail: { huId: story.id, status: HU_STATUS.REVIEWING, timestamp: new Date().toISOString() }
132
+ }));
133
+
122
134
  if (approved) {
123
135
  updateStoryStatus(batch, story.id, HU_STATUS.DONE);
136
+ await saveHuBatch(batchSessionId, batch);
137
+ emitProgress(emitter, makeEvent("hu:status-change", { ...eventBase, stage: "hu-sub-pipeline" }, {
138
+ message: `HU ${story.id} status → done`,
139
+ detail: { huId: story.id, status: HU_STATUS.DONE, timestamp: new Date().toISOString() }
140
+ }));
124
141
  emitProgress(emitter, makeEvent("hu:end", { ...eventBase, stage: "hu-sub-pipeline" }, {
125
142
  status: "ok",
126
143
  message: `HU ${story.id} completed successfully`,
@@ -129,6 +146,11 @@ async function runSingleHu({ storyId, batch, batchSessionId, runIterationFn, emi
129
146
  return { huId: story.id, approved: true, result: iterResult };
130
147
  } else {
131
148
  updateStoryStatus(batch, story.id, HU_STATUS.FAILED);
149
+ await saveHuBatch(batchSessionId, batch);
150
+ emitProgress(emitter, makeEvent("hu:status-change", { ...eventBase, stage: "hu-sub-pipeline" }, {
151
+ message: `HU ${story.id} status → failed`,
152
+ detail: { huId: story.id, status: HU_STATUS.FAILED, timestamp: new Date().toISOString() }
153
+ }));
132
154
  emitProgress(emitter, makeEvent("hu:end", { ...eventBase, stage: "hu-sub-pipeline" }, {
133
155
  status: "fail",
134
156
  message: `HU ${story.id} failed`,
@@ -138,6 +160,11 @@ async function runSingleHu({ storyId, batch, batchSessionId, runIterationFn, emi
138
160
  }
139
161
  } catch (err) {
140
162
  updateStoryStatus(batch, story.id, HU_STATUS.FAILED);
163
+ await saveHuBatch(batchSessionId, batch);
164
+ emitProgress(emitter, makeEvent("hu:status-change", { ...eventBase, stage: "hu-sub-pipeline" }, {
165
+ message: `HU ${story.id} status → failed`,
166
+ detail: { huId: story.id, status: HU_STATUS.FAILED, timestamp: new Date().toISOString() }
167
+ }));
141
168
  emitProgress(emitter, makeEvent("hu:end", { ...eventBase, stage: "hu-sub-pipeline" }, {
142
169
  status: "fail",
143
170
  message: `HU ${story.id} threw: ${err.message}`,
@@ -523,6 +523,10 @@ async function becariaIncrementalPush({ config, session, gitCtx, task, logger, r
523
523
  const pushResult = await incrementalPush({ gitCtx, task, logger, session });
524
524
  if (!pushResult) return;
525
525
 
526
+ // Accumulate commits for PG card lifecycle tracking
527
+ const { accumulateCommit } = await import("./planning-game/pipeline-adapter.js");
528
+ for (const c of pushResult.commits) accumulateCommit(session, c);
529
+
526
530
  session.becaria_commits = [...(session.becaria_commits ?? []), ...pushResult.commits];
527
531
  await saveSession(session);
528
532
 
@@ -539,6 +543,10 @@ async function becariaCreateEarlyPr({ config, session, emitter, eventBase, gitCt
539
543
  const earlyPr = await earlyPrCreation({ gitCtx, task, logger, session, stageResults });
540
544
  if (!earlyPr) return;
541
545
 
546
+ // Accumulate commits for PG card lifecycle tracking
547
+ const { accumulateCommit } = await import("./planning-game/pipeline-adapter.js");
548
+ for (const c of earlyPr.commits) accumulateCommit(session, c);
549
+
542
550
  session.becaria_pr_number = earlyPr.prNumber;
543
551
  session.becaria_pr_url = earlyPr.prUrl;
544
552
  session.becaria_commits = earlyPr.commits;
@@ -740,6 +748,13 @@ async function handlePostLoopStages({ config, session, emitter, eventBase, coder
740
748
 
741
749
  async function finalizeApprovedSession({ config, gitCtx, task, logger, session, stageResults, emitter, eventBase, budgetSummary, pgCard, pgProject, review, i }) {
742
750
  const gitResult = await finalizeGitAutomation({ config, gitCtx, task, logger, session, stageResults });
751
+
752
+ // Accumulate final commits for PG card lifecycle tracking
753
+ if (gitResult?.commits?.length) {
754
+ const { accumulateCommit } = await import("./planning-game/pipeline-adapter.js");
755
+ for (const c of gitResult.commits) accumulateCommit(session, c);
756
+ }
757
+
743
758
  if (stageResults.planner?.ok) {
744
759
  stageResults.planner.completedSteps = [...(stageResults.planner.steps ?? [])];
745
760
  }
@@ -94,6 +94,7 @@ export async function handlePgDecomposition({ triageResult, pgTaskId, pgProject,
94
94
 
95
95
  /**
96
96
  * Mark PG card as "To Validate" on approved session completion.
97
+ * Merges commits from gitResult and session.pg_commits (accumulated during pipeline).
97
98
  */
98
99
  export async function markPgCardToValidate({ pgCard, pgProject, config, session, gitResult, logger }) {
99
100
  if (!pgCard || !pgProject) return;
@@ -101,9 +102,21 @@ export async function markPgCardToValidate({ pgCard, pgProject, config, session,
101
102
  try {
102
103
  const { updateCard } = await import("./client.js");
103
104
  const { buildCompletionUpdates } = await import("./adapter.js");
105
+
106
+ // Merge commits: session.pg_commits (accumulated) + gitResult.commits (final)
107
+ const accumulatedCommits = session.pg_commits || [];
108
+ const finalCommits = gitResult?.commits || [];
109
+ const seenHashes = new Set(accumulatedCommits.map(c => c.hash));
110
+ const mergedCommits = [...accumulatedCommits];
111
+ for (const c of finalCommits) {
112
+ if (!seenHashes.has(c.hash)) {
113
+ mergedCommits.push(c);
114
+ }
115
+ }
116
+
104
117
  const pgUpdates = buildCompletionUpdates({
105
118
  approved: true,
106
- commits: gitResult?.commits || [],
119
+ commits: mergedCommits,
107
120
  startDate: session.pg_card?.startDate || session.created_at,
108
121
  codeveloper: config.planning_game?.codeveloper || null
109
122
  });
@@ -156,6 +169,25 @@ export function buildHuStoriesFromPgCard(pgCard) {
156
169
  return [{ id: storyId, text: storyText }];
157
170
  }
158
171
 
172
+ /**
173
+ * Accumulate a commit into the session's pg_commits array.
174
+ * Called after each successful commitAll during the iteration loop.
175
+ * Best-effort: never throws.
176
+ *
177
+ * @param {object} session - Pipeline session (mutated)
178
+ * @param {{ hash: string, message: string, date?: string, author?: string }} commitInfo
179
+ */
180
+ export function accumulateCommit(session, commitInfo) {
181
+ if (!session || !commitInfo?.hash) return;
182
+ if (!session.pg_commits) session.pg_commits = [];
183
+ session.pg_commits.push({
184
+ hash: commitInfo.hash,
185
+ message: commitInfo.message || "",
186
+ date: commitInfo.date || new Date().toISOString(),
187
+ author: commitInfo.author || "BecarIA"
188
+ });
189
+ }
190
+
159
191
  // --- Internal helpers ---
160
192
 
161
193
  async function markPgCardInProgress({ pgTaskId, pgProject, config, logger }) {