@fitlab-ai/agent-infra 0.11.6 → 0.11.7

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.
Files changed (115) hide show
  1. package/dist/lib/git/primary-worktree.js +58 -0
  2. package/dist/lib/platform/pull-requests.js +29 -3
  3. package/dist/lib/sandbox/control/server.js +6 -2
  4. package/dist/lib/server/config.js +7 -27
  5. package/dist/lib/server/daemon.js +4 -3
  6. package/dist/lib/server/process-control.js +4 -2
  7. package/dist/lib/server/runner.js +4 -3
  8. package/dist/lib/task/artifact-lifecycle.js +42 -10
  9. package/dist/lib/task/artifact-receipts.js +62 -22
  10. package/dist/lib/task/capabilities.js +22 -4
  11. package/dist/lib/task/completion-facts.js +56 -0
  12. package/dist/lib/task/create-service.js +6 -2
  13. package/dist/lib/task/create.js +46 -82
  14. package/dist/lib/task/events.js +268 -195
  15. package/dist/lib/task/finalization-handoff.js +2 -1
  16. package/dist/lib/task/invalidation-command.js +4 -2
  17. package/dist/lib/task/invalidation.js +38 -9
  18. package/dist/lib/task/local-artifact-finalization.js +17 -3
  19. package/dist/lib/task/qualification-audit.js +23 -105
  20. package/dist/lib/task/review-finalization.js +7 -0
  21. package/dist/lib/task/review-identity.js +69 -0
  22. package/dist/lib/task/sections.js +6 -5
  23. package/dist/lib/task/task-execution-lock.js +6 -3
  24. package/dist/lib/task/verification-engine.js +14 -63
  25. package/dist/lib/task/verification.js +35 -38
  26. package/dist/lifecycle-build-manifest.json +3 -3
  27. package/lib/git/primary-worktree.ts +62 -0
  28. package/lib/platform/pull-requests.ts +27 -3
  29. package/lib/sandbox/control/server.ts +5 -2
  30. package/lib/server/config.ts +7 -29
  31. package/lib/server/daemon.ts +5 -3
  32. package/lib/server/process-control.ts +4 -2
  33. package/lib/server/runner.ts +7 -4
  34. package/lib/server/server.schema.json +0 -4
  35. package/lib/task/artifact-lifecycle.ts +35 -11
  36. package/lib/task/artifact-receipts.ts +55 -15
  37. package/lib/task/capabilities.ts +17 -3
  38. package/lib/task/completion-facts.ts +66 -0
  39. package/lib/task/create-service.ts +7 -3
  40. package/lib/task/create.ts +44 -71
  41. package/lib/task/events.ts +248 -182
  42. package/lib/task/finalization-handoff.ts +2 -1
  43. package/lib/task/invalidation-command.ts +4 -2
  44. package/lib/task/invalidation.ts +35 -10
  45. package/lib/task/local-artifact-finalization.ts +13 -3
  46. package/lib/task/qualification-audit.ts +21 -114
  47. package/lib/task/review-finalization.ts +7 -0
  48. package/lib/task/review-identity.ts +85 -0
  49. package/lib/task/sections.ts +6 -6
  50. package/lib/task/task-execution-lock.ts +7 -3
  51. package/lib/task/verification-engine.ts +13 -86
  52. package/lib/task/verification.ts +34 -38
  53. package/package.json +1 -1
  54. package/templates/.agents/rules/README.en.md +1 -0
  55. package/templates/.agents/rules/README.zh-CN.md +1 -0
  56. package/templates/.agents/rules/decision-qualification.en.md +4 -3
  57. package/templates/.agents/rules/decision-qualification.zh-CN.md +4 -3
  58. package/templates/.agents/rules/validation-output.en.md +19 -0
  59. package/templates/.agents/rules/validation-output.zh-CN.md +19 -0
  60. package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -1
  61. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
  62. package/templates/.agents/skills/block-task/SKILL.en.md +2 -0
  63. package/templates/.agents/skills/block-task/SKILL.zh-CN.md +1 -1
  64. package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -0
  65. package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +1 -1
  66. package/templates/.agents/skills/code-task/SKILL.en.md +3 -1
  67. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +2 -2
  68. package/templates/.agents/skills/code-task/reference/report-template.en.md +4 -8
  69. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +4 -8
  70. package/templates/.agents/skills/commit/SKILL.en.md +1 -1
  71. package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
  72. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +1 -1
  73. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +1 -1
  74. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -0
  75. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -0
  76. package/templates/.agents/skills/create-pr/SKILL.en.md +2 -0
  77. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +1 -1
  78. package/templates/.agents/skills/create-task/SKILL.en.md +2 -0
  79. package/templates/.agents/skills/create-task/SKILL.zh-CN.md +1 -1
  80. package/templates/.agents/skills/import-codescan/SKILL.en.md +2 -0
  81. package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
  82. package/templates/.agents/skills/import-dependabot/SKILL.en.md +2 -0
  83. package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +1 -1
  84. package/templates/.agents/skills/import-issue/SKILL.en.md +2 -0
  85. package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +1 -1
  86. package/templates/.agents/skills/plan-task/SKILL.en.md +3 -1
  87. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +3 -3
  88. package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -1
  89. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -1
  90. package/templates/.agents/skills/review-analysis/reference/report-template.en.md +4 -8
  91. package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +4 -8
  92. package/templates/.agents/skills/review-code/SKILL.en.md +13 -12
  93. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +11 -12
  94. package/templates/.agents/skills/review-code/config/verify.en.json +1 -3
  95. package/templates/.agents/skills/review-code/config/verify.zh-CN.json +1 -3
  96. package/templates/.agents/skills/review-code/reference/output-templates.en.md +23 -46
  97. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +27 -48
  98. package/templates/.agents/skills/review-code/reference/report-template.en.md +8 -11
  99. package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +8 -11
  100. package/templates/.agents/skills/review-code/reference/review-criteria.en.md +2 -2
  101. package/templates/.agents/skills/review-code/reference/review-criteria.zh-CN.md +2 -2
  102. package/templates/.agents/skills/review-plan/SKILL.en.md +3 -1
  103. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -1
  104. package/templates/.agents/skills/review-plan/reference/report-template.en.md +5 -9
  105. package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +5 -9
  106. package/templates/.agents/skills/review-pr/SKILL.en.md +2 -0
  107. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +2 -0
  108. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
  109. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
  110. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  111. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  112. package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -0
  113. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +1 -1
  114. package/templates/.agents/templates/task.en.md +2 -2
  115. package/templates/.agents/templates/task.zh-CN.md +2 -2
@@ -48,9 +48,10 @@ export function parseTaskCreateResult(value) {
48
48
  if (!['applied', 'no-op', 'degraded', 'failed', 'blocked'].includes(result.status)
49
49
  || typeof result.changed !== 'boolean'
50
50
  || !result.task || typeof result.task !== 'object' || Array.isArray(result.task)
51
- || !exactKeys(result.task, ['id', 'shortId'])
51
+ || !exactKeys(result.task, ['id', 'shortId', 'state'])
52
52
  || !validTaskReference(result.task.id)
53
53
  || !validTaskReference(result.task.shortId)
54
+ || ![null, 'active', 'blocked', 'completed', 'archive'].includes(result.task.state)
54
55
  || !Array.isArray(result.operations)
55
56
  || !result.operations.every((operation) => {
56
57
  if (!operation || typeof operation !== 'object' || Array.isArray(operation))
@@ -93,7 +94,7 @@ export function taskCreateFailure(error, control) {
93
94
  return {
94
95
  status: error.retryable ? 'blocked' : 'failed',
95
96
  changed: false,
96
- task: { id: null, shortId: null },
97
+ task: { id: null, shortId: null, state: null },
97
98
  issue: null,
98
99
  operations: [],
99
100
  warnings: [],
@@ -156,6 +157,9 @@ async function createTask(value, options) {
156
157
  const operations = [{
157
158
  name: 'task:local', status: local.status, reasonCode: null
158
159
  }];
160
+ if (local.status === 'no-op' && local.task.state !== 'active') {
161
+ return { status: 'no-op', changed: false, task: local.task, issue: null, operations, warnings: [], error: null };
162
+ }
159
163
  let issue = null;
160
164
  const created = await dependencies.createIssue(local.task.id, { cwd: options.repoRoot, agent: candidate.agent });
161
165
  operations.push(...platformOperations('platform-create', created.operations));
@@ -1,5 +1,6 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import fs from 'node:fs';
3
+ import os from 'node:os';
3
4
  import path from 'node:path';
4
5
  import { isValidAgentInfraVersion, VERSION } from '../version.js';
5
6
  import { loadShortIdByTaskId, mutateShortIdRegistry } from './short-id.js';
@@ -7,6 +8,8 @@ import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-
7
8
  import { readDeliveryDefaults, validateBaseRef, validateRemote } from './delivery-target.js';
8
9
  import { buildUnboundFact, encodePrDeliveryFact } from './pr-delivery-fact.js';
9
10
  import { CANDIDATE_COLUMNS, CONSTRAINT_COLUMNS, parseTaskQualification } from './qualification-audit.js';
11
+ import { enumerateAllTaskDirs } from './resolve-ref.js';
12
+ import { parseTaskFrontmatter } from './frontmatter.js';
10
13
  const AGENTS = ['claude', 'codex', 'antigravity', 'opencode', 'cursor'];
11
14
  const TYPES = ['feature', 'bugfix', 'refactor', 'docs', 'chore'];
12
15
  const PRIORITIES = ['Urgent', 'High', 'Medium', 'Low'];
@@ -292,10 +295,27 @@ function ensureRealDirectory(directory, code) {
292
295
  }
293
296
  fs.mkdirSync(directory, { mode: 0o700 });
294
297
  }
295
- function withCreateLock(repoRoot, workspaceRoot, operation) {
296
- const lockRoot = path.join(workspaceRoot, '.task-create.lock');
297
- fs.mkdirSync(lockRoot, { recursive: true, mode: 0o700 });
298
- assertRealDirectory(lockRoot, 'TASK_CREATE_LOCK_FAILED');
298
+ function createLockRoot(project) {
299
+ const root = path.join(os.homedir(), '.agent-infra');
300
+ const directories = [root, path.join(root, 'run'), path.join(root, 'run', project), path.join(root, 'run', project, 'task-create-locks')];
301
+ for (const directory of directories) {
302
+ if (fs.existsSync(directory)) {
303
+ assertRealDirectory(directory, 'TASK_CREATE_LOCK_FAILED');
304
+ continue;
305
+ }
306
+ try {
307
+ fs.mkdirSync(directory, { mode: 0o700 });
308
+ }
309
+ catch (error) {
310
+ if (error.code !== 'EEXIST')
311
+ throw error;
312
+ assertRealDirectory(directory, 'TASK_CREATE_LOCK_FAILED');
313
+ }
314
+ }
315
+ return directories.at(-1);
316
+ }
317
+ function withCreateLock(repoRoot, project, operation) {
318
+ const lockRoot = createLockRoot(project);
299
319
  let callbackError;
300
320
  try {
301
321
  return withTaskExecutionLock(repoRoot, 'task-create', 'task-create', () => {
@@ -306,7 +326,7 @@ function withCreateLock(repoRoot, workspaceRoot, operation) {
306
326
  callbackError = error;
307
327
  throw error;
308
328
  }
309
- }, { lockRoot });
329
+ }, { lockRoot, identityScope: `task-create\0${project}` });
310
330
  }
311
331
  catch (error) {
312
332
  if (callbackError !== undefined)
@@ -318,45 +338,24 @@ function withCreateLock(repoRoot, workspaceRoot, operation) {
318
338
  throw new Error(`TASK_CREATE_LOCK_FAILED: ${message}`);
319
339
  }
320
340
  }
321
- function writeReceipt(receiptPath, value) {
322
- const temporary = `${receiptPath}.tmp.${process.pid}.${randomUUID()}`;
323
- fs.writeFileSync(temporary, `${JSON.stringify(value)}\n`, { flag: 'wx', mode: 0o600 });
324
- try {
325
- fs.renameSync(temporary, receiptPath);
326
- }
327
- catch (error) {
328
- try {
329
- fs.unlinkSync(temporary);
330
- }
331
- catch { /* best effort */ }
332
- throw error;
333
- }
334
- }
335
- function recoverPublishedTask(activeRoot, repoRoot, keyDigest, candidateDigest) {
336
- for (const name of fs.readdirSync(activeRoot)) {
337
- if (!/^TASK-\d{8}-\d{6}$/.test(name))
338
- continue;
339
- const taskDir = path.join(activeRoot, name);
340
- const stat = fs.lstatSync(taskDir);
341
- if (!stat.isDirectory() || stat.isSymbolicLink())
342
- continue;
343
- const taskMd = path.join(taskDir, 'task.md');
344
- if (!fs.existsSync(taskMd))
345
- continue;
341
+ function recoverPublishedTask(repoRoot, keyDigest, candidateDigest) {
342
+ for (const task of enumerateAllTaskDirs(repoRoot)) {
343
+ const taskMd = path.join(task.taskDir, 'task.md');
346
344
  const taskStat = fs.lstatSync(taskMd);
347
345
  if (!taskStat.isFile() || taskStat.isSymbolicLink())
346
+ throw new Error('TASK_CREATE_TASK_INVALID');
347
+ const frontmatter = parseTaskFrontmatter(fs.readFileSync(taskMd, 'utf8'));
348
+ if (frontmatter.task_create_key_digest !== keyDigest)
348
349
  continue;
349
- const content = fs.readFileSync(taskMd, 'utf8');
350
- if (!content.includes(`task_create_key_digest: ${keyDigest}`))
351
- continue;
352
- if (!content.includes(`task_create_candidate_digest: ${candidateDigest}`)) {
350
+ if (frontmatter.task_create_candidate_digest !== candidateDigest) {
353
351
  throw new Error('TASK_CREATE_IDEMPOTENCY_CONFLICT');
354
352
  }
355
- const shortId = loadShortIdByTaskId(repoRoot).get(name)
356
- ?? mutateShortIdRegistry(repoRoot, name, 'alloc').shortId;
357
- if (!shortId)
358
- throw new Error('TASK_CREATE_RECEIPT_INVALID');
359
- return { taskId: name, shortId };
353
+ const shortId = task.state === 'active'
354
+ ? loadShortIdByTaskId(repoRoot).get(task.taskId) ?? mutateShortIdRegistry(repoRoot, task.taskId, 'alloc').shortId
355
+ : null;
356
+ if (task.state === 'active' && !shortId)
357
+ throw new Error('TASK_CREATE_SHORT_ID_FAILED');
358
+ return { taskId: task.taskId, shortId, state: task.state };
360
359
  }
361
360
  return null;
362
361
  }
@@ -376,30 +375,10 @@ function createLocalTask(value, options) {
376
375
  const canonical = canonicalTaskCreateCandidate(candidate);
377
376
  const candidateDigest = sha256(canonical);
378
377
  const keyDigest = sha256(candidate.idempotencyKey);
379
- const receipts = path.join(workspaceRoot, '.task-create');
380
- return withCreateLock(repoRoot, workspaceRoot, () => {
381
- if (fs.existsSync(receipts))
382
- assertRealDirectory(receipts, 'TASK_CREATE_RECEIPT_INVALID');
383
- const receiptPath = path.join(receipts, `${keyDigest}.json`);
384
- if (fs.existsSync(receiptPath)) {
385
- const stat = fs.lstatSync(receiptPath);
386
- if (!stat.isFile() || stat.isSymbolicLink())
387
- throw new Error('TASK_CREATE_RECEIPT_INVALID');
388
- const receipt = JSON.parse(fs.readFileSync(receiptPath, 'utf8'));
389
- if (receipt.candidateDigest !== candidateDigest)
390
- throw new Error('TASK_CREATE_IDEMPOTENCY_CONFLICT');
391
- if (!receipt.taskId || !receipt.shortId || !fs.existsSync(path.join(activeRoot, receipt.taskId, 'task.md'))) {
392
- throw new Error('TASK_CREATE_RECEIPT_INVALID');
393
- }
394
- return { status: 'no-op', changed: false, task: { id: receipt.taskId, shortId: receipt.shortId } };
395
- }
396
- const recovered = recoverPublishedTask(activeRoot, repoRoot, keyDigest, candidateDigest);
378
+ return withCreateLock(repoRoot, projectConfig.project, () => {
379
+ const recovered = recoverPublishedTask(repoRoot, keyDigest, candidateDigest);
397
380
  if (recovered) {
398
- ensureRealDirectory(receipts, 'TASK_CREATE_RECEIPT_INVALID');
399
- writeReceipt(receiptPath, {
400
- version: 1, candidateDigest, taskId: recovered.taskId, shortId: recovered.shortId, status: 'recovered'
401
- });
402
- return { status: 'no-op', changed: false, task: { id: recovered.taskId, shortId: recovered.shortId } };
381
+ return { status: 'no-op', changed: false, task: { id: recovered.taskId, shortId: recovered.shortId, state: recovered.state } };
403
382
  }
404
383
  const base = options.now?.() ?? new Date();
405
384
  let taskDate = new Date(base.getTime());
@@ -418,32 +397,17 @@ function createLocalTask(value, options) {
418
397
  agentInfraVersion, keyDigest, candidateDigest
419
398
  });
420
399
  validateRenderedQualification(rendered, candidate);
421
- ensureRealDirectory(receipts, 'TASK_CREATE_RECEIPT_INVALID');
422
400
  const temporary = path.join(workspaceRoot, `.task-create.tmp.${process.pid}.${randomUUID()}`);
423
401
  fs.mkdirSync(temporary, { mode: 0o700 });
424
402
  fs.writeFileSync(path.join(temporary, 'task.md'), rendered, { flag: 'wx', mode: 0o600 });
425
403
  const destination = path.join(activeRoot, taskId);
426
404
  fs.renameSync(temporary, destination);
427
405
  let shortId;
428
- try {
429
- const allocated = mutateShortIdRegistry(repoRoot, taskId, 'alloc');
430
- if (!allocated.shortId)
431
- throw new Error('TASK_CREATE_SHORT_ID_FAILED');
432
- shortId = allocated.shortId;
433
- }
434
- catch (error) {
435
- fs.rmSync(destination, { recursive: true, force: true });
436
- throw error;
437
- }
438
- try {
439
- writeReceipt(receiptPath, { version: 1, candidateDigest, taskId, shortId, status: 'created' });
440
- }
441
- catch (error) {
442
- mutateShortIdRegistry(repoRoot, taskId, 'release');
443
- fs.rmSync(destination, { recursive: true, force: true });
444
- throw error;
445
- }
446
- return { status: 'applied', changed: true, task: { id: taskId, shortId } };
406
+ const allocated = mutateShortIdRegistry(repoRoot, taskId, 'alloc');
407
+ if (!allocated.shortId)
408
+ throw new Error('TASK_CREATE_SHORT_ID_FAILED');
409
+ shortId = allocated.shortId;
410
+ return { status: 'applied', changed: true, task: { id: taskId, shortId, state: 'active' } };
447
411
  });
448
412
  }
449
413
  export { canonicalTaskCreateCandidate, createLocalTask, validateTaskCreateCandidate };