@renseiai/agentfactory 0.8.16 → 0.8.17

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.
@@ -29,7 +29,7 @@ const FIELD = {
29
29
  commitsPresent: {
30
30
  type: 'commits_present',
31
31
  label: 'Commits on branch',
32
- backstopCapable: false,
32
+ backstopCapable: true,
33
33
  },
34
34
  workResult: {
35
35
  type: 'work_result',
@@ -89,7 +89,7 @@ describe('validateCompletion', () => {
89
89
  expect(result.missingFields).toContain('branch_pushed');
90
90
  expect(result.backstopRecoverable).toContain('branch_pushed');
91
91
  });
92
- it('marks commits_present as not backstop-capable', () => {
92
+ it('marks commits_present as backstop-capable', () => {
93
93
  const contract = getCompletionContract('development');
94
94
  const outputs = {
95
95
  prUrl: 'https://github.com/org/repo/pull/1',
@@ -98,8 +98,8 @@ describe('validateCompletion', () => {
98
98
  };
99
99
  const result = validateCompletion(contract, outputs);
100
100
  expect(result.satisfied).toBe(false);
101
- expect(result.manualRequired).toContain('commits_present');
102
- expect(result.backstopRecoverable).not.toContain('commits_present');
101
+ expect(result.backstopRecoverable).toContain('commits_present');
102
+ expect(result.manualRequired).not.toContain('commits_present');
103
103
  });
104
104
  it('marks satisfied for QA with work result passed', () => {
105
105
  const contract = getCompletionContract('qa');
@@ -1 +1 @@
1
- {"version":3,"file":"session-backstop.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/session-backstop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C,OAAO,KAAK,EAEV,cAAc,EACd,kBAAkB,EAClB,0BAA0B,EAC1B,cAAc,EACf,MAAM,2BAA2B,CAAA;AAWlC,gDAAgD;AAChD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,YAAY,CAAA;IACnB,iEAAiE;IACjE,aAAa,EAAE,OAAO,CAAA;IACtB,sDAAsD;IACtD,YAAY,EAAE,OAAO,CAAA;IACrB,2CAA2C;IAC3C,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,cAAc,GAAG,cAAc,CA8BzE;AAMD,uCAAuC;AACvC,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,cAAc,EACnB,OAAO,CAAC,EAAE,eAAe,GACxB,iBAAiB,CAkGnB;AAED,oCAAoC;AACpC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAAA;IACnC,OAAO,EAAE,cAAc,CAAA;IACvB,UAAU,EAAE,0BAA0B,GAAG,IAAI,CAAA;IAC7C,QAAQ,EAAE,cAAc,CAAA;IACxB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AA8QD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,CA8C9E"}
1
+ {"version":3,"file":"session-backstop.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/session-backstop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C,OAAO,KAAK,EAEV,cAAc,EACd,kBAAkB,EAClB,0BAA0B,EAC1B,cAAc,EACf,MAAM,2BAA2B,CAAA;AAWlC,gDAAgD;AAChD,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,YAAY,CAAA;IACnB,iEAAiE;IACjE,aAAa,EAAE,OAAO,CAAA;IACtB,sDAAsD;IACtD,YAAY,EAAE,OAAO,CAAA;IACrB,2CAA2C;IAC3C,gBAAgB,EAAE,OAAO,CAAA;CAC1B;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,cAAc,GAAG,cAAc,CA8BzE;AAMD,uCAAuC;AACvC,MAAM,WAAW,eAAe;IAC9B,iFAAiF;IACjF,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,oEAAoE;IACpE,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,cAAc,EACnB,OAAO,CAAC,EAAE,eAAe,GACxB,iBAAiB,CAwHnB;AAED,oCAAoC;AACpC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAAA;IACnC,OAAO,EAAE,cAAc,CAAA;IACvB,UAAU,EAAE,0BAA0B,GAAG,IAAI,CAAA;IAC7C,QAAQ,EAAE,cAAc,CAAA;IACxB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAyUD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,GAAG,IAAI,CA8C9E"}
@@ -91,6 +91,27 @@ export function runBackstop(ctx, options) {
91
91
  const actions = [];
92
92
  for (const fieldType of validation.backstopRecoverable) {
93
93
  switch (fieldType) {
94
+ case 'commits_present': {
95
+ // Auto-commit uncommitted changes for code-producing work types only
96
+ if (!ctx.agent.worktreePath) {
97
+ actions.push({
98
+ field: 'commits_present',
99
+ action: 'skipped — no worktree path',
100
+ success: false,
101
+ });
102
+ break;
103
+ }
104
+ if (options?.dryRun) {
105
+ actions.push({ field: 'commits_present', action: 'would auto-commit uncommitted changes', success: false, detail: 'dry-run' });
106
+ break;
107
+ }
108
+ const commitResult = backstopCommitChanges(ctx.agent.worktreePath, ctx.agent.identifier);
109
+ actions.push(commitResult);
110
+ if (commitResult.success) {
111
+ outputs.commitsPresent = true;
112
+ }
113
+ break;
114
+ }
94
115
  case 'branch_pushed': {
95
116
  if (options?.dryRun) {
96
117
  actions.push({ field: 'branch_pushed', action: 'would push branch', success: false, detail: 'dry-run' });
@@ -310,6 +331,58 @@ function backstopPushBranch(worktreePath) {
310
331
  };
311
332
  }
312
333
  }
334
+ function backstopCommitChanges(worktreePath, identifier) {
335
+ try {
336
+ // Check if there are actually uncommitted changes
337
+ const status = execSync('git status --porcelain', {
338
+ cwd: worktreePath,
339
+ encoding: 'utf-8',
340
+ timeout: 10000,
341
+ }).trim();
342
+ if (status.length === 0) {
343
+ return {
344
+ field: 'commits_present',
345
+ action: 'skipped — no uncommitted changes to commit',
346
+ success: false,
347
+ detail: 'Worktree is clean',
348
+ };
349
+ }
350
+ // Stage all changes and commit
351
+ execSync('git add -A', {
352
+ cwd: worktreePath,
353
+ encoding: 'utf-8',
354
+ timeout: 30000,
355
+ });
356
+ execSync(`git commit -m "feat: ${identifier} (auto-committed by session backstop)"`, {
357
+ cwd: worktreePath,
358
+ encoding: 'utf-8',
359
+ timeout: 30000,
360
+ env: {
361
+ ...process.env,
362
+ // Ensure commit succeeds even without global git config
363
+ GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME ?? 'AgentFactory Backstop',
364
+ GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL ?? 'backstop@agentfactory.dev',
365
+ GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME ?? 'AgentFactory Backstop',
366
+ GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL ?? 'backstop@agentfactory.dev',
367
+ },
368
+ });
369
+ const changedFiles = status.split('\n').length;
370
+ return {
371
+ field: 'commits_present',
372
+ action: `auto-committed ${changedFiles} file(s)`,
373
+ success: true,
374
+ detail: `${changedFiles} file(s) committed for ${identifier}`,
375
+ };
376
+ }
377
+ catch (error) {
378
+ return {
379
+ field: 'commits_present',
380
+ action: 'failed to auto-commit changes',
381
+ success: false,
382
+ detail: error instanceof Error ? error.message : String(error),
383
+ };
384
+ }
385
+ }
313
386
  function backstopCreatePR(agent, options) {
314
387
  const worktreePath = agent.worktreePath;
315
388
  if (!worktreePath) {
@@ -188,6 +188,71 @@ describe('runBackstop', () => {
188
188
  expect(result.validation.satisfied).toBe(false);
189
189
  expect(result.backstop.remainingGaps).toContain('comment_posted');
190
190
  });
191
+ it('auto-commits uncommitted changes for development work type', () => {
192
+ // collectSessionOutputs: no commits ahead of main, branch not pushed
193
+ mockExecSync
194
+ // --- collectSessionOutputs ---
195
+ .mockReturnValueOnce('SUP-123\n') // hasCommits: git branch
196
+ .mockReturnValueOnce('0\n') // hasCommits: rev-list (no commits)
197
+ .mockReturnValueOnce('SUP-123\n') // isBranchPushed: git branch
198
+ .mockReturnValueOnce('') // isBranchPushed: ls-remote (not pushed)
199
+ // --- backstopCommitChanges ---
200
+ .mockReturnValueOnce(' M src/foo.ts\n?? src/bar.ts\n') // git status --porcelain
201
+ .mockReturnValueOnce('') // git add -A
202
+ .mockReturnValueOnce('') // git commit
203
+ // --- backstopPushBranch ---
204
+ .mockReturnValueOnce('SUP-123\n') // git branch
205
+ .mockReturnValueOnce('') // git push
206
+ // --- backstopCreatePR ---
207
+ .mockReturnValueOnce('SUP-123\n') // git branch
208
+ .mockReturnValueOnce('[]') // gh pr list
209
+ .mockReturnValueOnce('https://github.com/org/repo/pull/99\n'); // gh pr create
210
+ const ctx = createSessionContext({
211
+ agent: createMockAgent({
212
+ workType: 'development',
213
+ worktreePath: '/tmp/worktree',
214
+ }),
215
+ });
216
+ const result = runBackstop(ctx);
217
+ const commitAction = result.backstop.actions.find(a => a.field === 'commits_present');
218
+ expect(commitAction).toBeDefined();
219
+ expect(commitAction.success).toBe(true);
220
+ expect(commitAction.action).toContain('auto-committed');
221
+ const pushAction = result.backstop.actions.find(a => a.field === 'branch_pushed');
222
+ expect(pushAction).toBeDefined();
223
+ expect(pushAction.success).toBe(true);
224
+ const prAction = result.backstop.actions.find(a => a.field === 'pr_url');
225
+ expect(prAction).toBeDefined();
226
+ expect(prAction.success).toBe(true);
227
+ });
228
+ it('does not auto-commit for non-code-producing work types', () => {
229
+ // QA work type has no commits_present requirement — backstop should not commit
230
+ const ctx = createSessionContext({
231
+ agent: createMockAgent({ workType: 'qa', worktreePath: '/tmp/worktree' }),
232
+ commentPosted: true,
233
+ });
234
+ const result = runBackstop(ctx);
235
+ const commitAction = result.backstop.actions.find(a => a.field === 'commits_present');
236
+ expect(commitAction).toBeUndefined();
237
+ });
238
+ it('skips auto-commit in dry-run mode', () => {
239
+ mockExecSync
240
+ .mockReturnValueOnce('SUP-123\n') // hasCommits: git branch
241
+ .mockReturnValueOnce('0\n') // hasCommits: rev-list
242
+ .mockReturnValueOnce('SUP-123\n') // isBranchPushed: git branch
243
+ .mockReturnValueOnce(''); // isBranchPushed: ls-remote
244
+ const ctx = createSessionContext({
245
+ agent: createMockAgent({
246
+ workType: 'development',
247
+ worktreePath: '/tmp/worktree',
248
+ }),
249
+ });
250
+ const result = runBackstop(ctx, { dryRun: true });
251
+ const commitAction = result.backstop.actions.find(a => a.field === 'commits_present');
252
+ expect(commitAction).toBeDefined();
253
+ expect(commitAction.success).toBe(false);
254
+ expect(commitAction.detail).toBe('dry-run');
255
+ });
191
256
  it('reports satisfied for backlog-creation with sub-issues', () => {
192
257
  const ctx = createSessionContext({
193
258
  agent: createMockAgent({ workType: 'backlog-creation' }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@renseiai/agentfactory",
3
- "version": "0.8.16",
3
+ "version": "0.8.17",
4
4
  "type": "module",
5
5
  "description": "Multi-agent fleet management for coding agents — orchestrator, providers, crash recovery",
6
6
  "author": "Rensei AI (https://rensei.ai)",
@@ -56,7 +56,7 @@
56
56
  "@types/node": "^22.5.4",
57
57
  "typescript": "^5.7.3",
58
58
  "vitest": "^3.2.3",
59
- "@renseiai/create-agentfactory-app": "0.8.16"
59
+ "@renseiai/create-agentfactory-app": "0.8.17"
60
60
  },
61
61
  "scripts": {
62
62
  "build": "tsc",