@fitlab-ai/agent-infra 0.9.3 → 0.9.4

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 (175) hide show
  1. package/README.md +2 -0
  2. package/README.zh-CN.md +2 -0
  3. package/bin/cli.ts +12 -0
  4. package/dist/bin/cli.js +14 -0
  5. package/dist/bin/internal-cli.js +134 -123
  6. package/dist/lib/agent-clients/adapter.js +92 -2
  7. package/dist/lib/{sandbox/credentials.js → agent-clients/adapters/claude-code-credentials.js} +4 -14
  8. package/dist/lib/agent-clients/adapters/claude-code-refresh.js +85 -0
  9. package/dist/lib/agent-clients/adapters/claude-code-sandbox.js +247 -0
  10. package/dist/lib/agent-clients/adapters/claude-code.js +17 -3
  11. package/dist/lib/agent-clients/adapters/opencode-sandbox.js +108 -0
  12. package/dist/lib/agent-clients/adapters/opencode.js +15 -3
  13. package/dist/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  14. package/dist/lib/agent-clients/registry.js +9 -1
  15. package/dist/lib/internal/sandbox-control.js +24 -0
  16. package/dist/lib/internal/task-activity.js +45 -13
  17. package/dist/lib/internal/task-ledger.js +12 -3
  18. package/dist/lib/internal/task-lifecycle.js +19 -1
  19. package/dist/lib/internal/task-orchestration.js +122 -18
  20. package/dist/lib/platform/github-client.js +19 -2
  21. package/dist/lib/platform/pr-checks.js +1 -1
  22. package/dist/lib/platform/pull-requests.js +47 -1
  23. package/dist/lib/platform/releases.js +13 -5
  24. package/dist/lib/process-data/index.js +239 -0
  25. package/dist/lib/process-data/normalize.js +198 -0
  26. package/dist/lib/process-data/privacy.js +33 -0
  27. package/dist/lib/process-data/reconcile.js +95 -0
  28. package/dist/lib/process-data/repair.js +58 -0
  29. package/dist/lib/process-data/sources.js +345 -0
  30. package/dist/lib/process-data/store.js +282 -0
  31. package/dist/lib/process-data/types.js +2 -0
  32. package/dist/lib/run/index.js +4 -3
  33. package/dist/lib/run/tui.js +7 -8
  34. package/dist/lib/sandbox/agent-client-reconciler.js +26 -2
  35. package/dist/lib/sandbox/capture.js +7 -2
  36. package/dist/lib/sandbox/commands/create.js +109 -228
  37. package/dist/lib/sandbox/commands/enter.js +15 -47
  38. package/dist/lib/sandbox/commands/list-running.js +16 -6
  39. package/dist/lib/sandbox/commands/ls.js +10 -6
  40. package/dist/lib/sandbox/commands/rebuild.js +4 -3
  41. package/dist/lib/sandbox/commands/refresh.js +4 -84
  42. package/dist/lib/sandbox/commands/rm.js +24 -3
  43. package/dist/lib/sandbox/commands/show.js +12 -3
  44. package/dist/lib/sandbox/commands/start.js +7 -4
  45. package/dist/lib/sandbox/config.js +2 -0
  46. package/dist/lib/sandbox/constants.js +6 -0
  47. package/dist/lib/sandbox/control/client.js +44 -0
  48. package/dist/lib/sandbox/control/protocol.js +35 -0
  49. package/dist/lib/sandbox/control/server.js +109 -0
  50. package/dist/lib/sandbox/dockerfile.js +36 -13
  51. package/dist/lib/sandbox/mounts.js +17 -4
  52. package/dist/lib/sandbox/recovery.js +128 -5
  53. package/dist/lib/sandbox/redaction.js +12 -0
  54. package/dist/lib/sandbox/runtimes/base.dockerfile +24 -74
  55. package/dist/lib/sandbox/workspace-identity.js +95 -0
  56. package/dist/lib/sandbox/workspace-view.js +103 -0
  57. package/dist/lib/task/activity-intent.js +257 -51
  58. package/dist/lib/task/commit-finalization.js +267 -0
  59. package/dist/lib/task/commit-intent.js +165 -0
  60. package/dist/lib/task/decision-intents.js +31 -17
  61. package/dist/lib/task/delegation-receipts.js +2 -2
  62. package/dist/lib/task/implementation-inputs.js +54 -7
  63. package/dist/lib/task/ledger-intents.js +35 -1
  64. package/dist/lib/task/orchestration.js +363 -28
  65. package/dist/lib/task/resolve-ref.js +41 -1
  66. package/lib/agent-clients/adapter.ts +126 -2
  67. package/lib/{sandbox/credentials.ts → agent-clients/adapters/claude-code-credentials.ts} +3 -18
  68. package/lib/agent-clients/adapters/claude-code-refresh.ts +128 -0
  69. package/lib/agent-clients/adapters/claude-code-sandbox.ts +295 -0
  70. package/lib/agent-clients/adapters/claude-code.ts +21 -3
  71. package/lib/agent-clients/adapters/opencode-sandbox.ts +136 -0
  72. package/lib/agent-clients/adapters/opencode.ts +18 -3
  73. package/lib/agent-clients/adapters/runtimes/claude-code.dockerfile +65 -0
  74. package/lib/agent-clients/registry.ts +9 -1
  75. package/lib/internal/sandbox-control.ts +24 -0
  76. package/lib/internal/task-activity.ts +50 -14
  77. package/lib/internal/task-ledger.ts +12 -3
  78. package/lib/internal/task-lifecycle.ts +22 -1
  79. package/lib/internal/task-orchestration.ts +81 -11
  80. package/lib/platform/github-client.ts +20 -2
  81. package/lib/platform/pr-checks.ts +1 -1
  82. package/lib/platform/pull-requests.ts +49 -1
  83. package/lib/platform/releases.ts +16 -4
  84. package/lib/process-data/index.ts +224 -0
  85. package/lib/process-data/normalize.ts +202 -0
  86. package/lib/process-data/privacy.ts +41 -0
  87. package/lib/process-data/reconcile.ts +110 -0
  88. package/lib/process-data/repair.ts +64 -0
  89. package/lib/process-data/sources.ts +354 -0
  90. package/lib/process-data/store.ts +315 -0
  91. package/lib/process-data/types.ts +135 -0
  92. package/lib/run/index.ts +5 -3
  93. package/lib/run/tui.ts +11 -8
  94. package/lib/sandbox/agent-client-reconciler.ts +44 -2
  95. package/lib/sandbox/capture.ts +9 -2
  96. package/lib/sandbox/commands/create.ts +126 -260
  97. package/lib/sandbox/commands/enter.ts +13 -59
  98. package/lib/sandbox/commands/list-running.ts +23 -6
  99. package/lib/sandbox/commands/ls.ts +22 -6
  100. package/lib/sandbox/commands/rebuild.ts +4 -3
  101. package/lib/sandbox/commands/refresh.ts +4 -126
  102. package/lib/sandbox/commands/rm.ts +24 -3
  103. package/lib/sandbox/commands/show.ts +29 -3
  104. package/lib/sandbox/commands/start.ts +6 -4
  105. package/lib/sandbox/config.ts +4 -0
  106. package/lib/sandbox/constants.ts +8 -0
  107. package/lib/sandbox/control/client.ts +55 -0
  108. package/lib/sandbox/control/protocol.ts +75 -0
  109. package/lib/sandbox/control/server.ts +117 -0
  110. package/lib/sandbox/dockerfile.ts +66 -13
  111. package/lib/sandbox/mounts.ts +24 -4
  112. package/lib/sandbox/recovery.ts +163 -6
  113. package/lib/sandbox/redaction.ts +14 -0
  114. package/lib/sandbox/runtimes/base.dockerfile +24 -74
  115. package/lib/sandbox/tool-types.ts +16 -0
  116. package/lib/sandbox/workspace-identity.ts +123 -0
  117. package/lib/sandbox/workspace-view.ts +143 -0
  118. package/lib/task/activity-intent.ts +350 -61
  119. package/lib/task/commit-finalization.ts +356 -0
  120. package/lib/task/commit-intent.ts +230 -0
  121. package/lib/task/decision-intents.ts +32 -12
  122. package/lib/task/delegation-receipts.ts +2 -2
  123. package/lib/task/implementation-inputs.ts +62 -8
  124. package/lib/task/ledger-intents.ts +43 -3
  125. package/lib/task/orchestration.ts +501 -33
  126. package/lib/task/resolve-ref.ts +46 -1
  127. package/package.json +1 -1
  128. package/templates/.agents/rules/README.en.md +1 -1
  129. package/templates/.agents/rules/README.zh-CN.md +1 -1
  130. package/templates/.agents/rules/human-decision-context.en.md +11 -2
  131. package/templates/.agents/rules/human-decision-context.zh-CN.md +11 -2
  132. package/templates/.agents/rules/pr-checks-commands.en.md +1 -1
  133. package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
  134. package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +2 -2
  135. package/templates/.agents/rules/pr-checks-commands.zh-CN.md +1 -1
  136. package/templates/.agents/rules/review-handshake.zh-CN.md +1 -1
  137. package/templates/.agents/rules/task-management.en.md +2 -0
  138. package/templates/.agents/rules/task-management.zh-CN.md +2 -0
  139. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  140. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
  141. package/templates/.agents/skills/commit/SKILL.en.md +21 -11
  142. package/templates/.agents/skills/commit/SKILL.zh-CN.md +27 -11
  143. package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +49 -0
  144. package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +55 -0
  145. package/templates/.agents/skills/commit/reference/task-status-update.en.md +4 -4
  146. package/templates/.agents/skills/commit/reference/task-status-update.zh-CN.md +4 -4
  147. package/templates/.agents/skills/complete-task/SKILL.en.md +1 -1
  148. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +1 -1
  149. package/templates/.agents/skills/create-pr/SKILL.en.md +4 -4
  150. package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +4 -4
  151. package/templates/.agents/skills/create-task/reference/context-capture.en.md +34 -0
  152. package/templates/.agents/skills/create-task/reference/context-capture.zh-CN.md +34 -0
  153. package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
  154. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
  155. package/templates/.agents/skills/review-code/reference/output-templates.en.md +1 -1
  156. package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +1 -1
  157. package/templates/.agents/skills/review-pr/SKILL.en.md +36 -8
  158. package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +36 -8
  159. package/templates/.agents/skills/review-pr/reference/report-template.en.md +1 -1
  160. package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +1 -1
  161. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +67 -50
  162. package/templates/.agents/skills/watch-pr/SKILL.en.md +4 -4
  163. package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +4 -4
  164. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +2 -2
  165. package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +2 -2
  166. package/templates/.agents/templates/task.en.md +1 -1
  167. package/templates/.agents/templates/task.zh-CN.md +1 -1
  168. package/templates/.claude/commands/watch-pr.en.md +1 -1
  169. package/templates/.claude/commands/watch-pr.zh-CN.md +1 -1
  170. package/templates/.claude/rules/tool-preferences.en.md +20 -0
  171. package/templates/.claude/rules/tool-preferences.zh-CN.md +20 -0
  172. package/templates/.opencode/commands/watch-pr.en.md +1 -1
  173. package/templates/.opencode/commands/watch-pr.zh-CN.md +1 -1
  174. package/templates/.gemini/commands/_project_/review-pr.en.toml +0 -10
  175. package/templates/.gemini/commands/_project_/review-pr.zh-CN.toml +0 -10
@@ -6,9 +6,10 @@ const USAGE = `Usage: agent-infra-internal task-ledger <task-ref> <intent> [inte
6
6
  const FLAGS = {
7
7
  '--stage': 'stage', '--review-artifact': 'reviewArtifact', '--ordinal': 'ordinal',
8
8
  '--severity': 'severity', '--evidence': 'evidence', '--id': 'id', '--round': 'round',
9
- '--status': 'status', '--artifact': 'artifact'
9
+ '--status': 'status', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation'
10
10
  };
11
11
  const NUMERIC = new Set(['ordinal', 'round']);
12
+ const BOOLEAN = new Set(['needsImplementation']);
12
13
  function usageFailure(message) {
13
14
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LEDGER_PAYLOAD_INVALID', message } })}\n`);
14
15
  process.stderr.write(USAGE);
@@ -55,8 +56,12 @@ function taskLedger(args = []) {
55
56
  usageFailure(`option '${flag}' requires a value`);
56
57
  return;
57
58
  }
59
+ if (BOOLEAN.has(key) && value !== 'true' && value !== 'false') {
60
+ usageFailure(`option '${flag}' must be true or false`);
61
+ return;
62
+ }
58
63
  seen.add(flag);
59
- values[key] = NUMERIC.has(key) ? Number(value) : value;
64
+ values[key] = NUMERIC.has(key) ? Number(value) : BOOLEAN.has(key) ? value === 'true' : value;
60
65
  }
61
66
  const required = {
62
67
  'finding-upsert': ['stage', 'reviewArtifact', 'ordinal', 'severity', 'evidence'],
@@ -66,7 +71,11 @@ function taskLedger(args = []) {
66
71
  'decision-upsert': ['id', 'stage', 'artifact'],
67
72
  'stage-status': ['stage']
68
73
  };
69
- const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind]]);
74
+ const optional = {
75
+ 'finding-review': ['needsImplementation'],
76
+ 'decision-upsert': ['needsImplementation']
77
+ };
78
+ const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind], ...(optional[kind] ?? [])]);
70
79
  const unexpected = Object.keys(values).find((key) => !allowed.has(key));
71
80
  const missing = required[kind].find((key) => values[key] === undefined);
72
81
  if (unexpected) {
@@ -1,5 +1,7 @@
1
1
  import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.js';
2
2
  import { applyTaskLifecycle, lifecycleIntentCatalog } from '../task/lifecycle.js';
3
+ import { resolveTaskRef } from '../task/resolve-ref.js';
4
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
3
5
  const USAGE = `Usage: agent-infra-internal task-lifecycle <N | TASK-id> <intent> --agent <agent> [intent flags] [--dry-run]\n\nIntents: ${lifecycleIntentCatalog.join(', ')}\n`;
4
6
  const FLAGS = {
5
7
  '--agent': 'agent', '--reason': 'reason', '--unblock-condition': 'unblockCondition',
@@ -56,7 +58,23 @@ function taskLifecycle(args = []) {
56
58
  return;
57
59
  }
58
60
  values.agent = agent;
59
- const result = applyTaskLifecycle(values);
61
+ const resolved = resolveTaskRef(String(values.taskRef));
62
+ if (!resolved.ok) {
63
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: resolved.code, message: resolved.message } })}\n`);
64
+ process.exitCode = 1;
65
+ return;
66
+ }
67
+ let result;
68
+ try {
69
+ result = withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-lifecycle.${String(values.intent)}`, () => applyTaskLifecycle(values));
70
+ }
71
+ catch (error) {
72
+ if (!(error instanceof TaskExecutionLockError))
73
+ throw error;
74
+ process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: error.code, message: error.message } })}\n`);
75
+ process.exitCode = 1;
76
+ return;
77
+ }
60
78
  process.stdout.write(`${JSON.stringify(result)}\n`);
61
79
  if (result.status === 'failed')
62
80
  process.exitCode = 1;
@@ -1,7 +1,7 @@
1
1
  import { normalizeAgentToken, AGENT_USAGE_HINT } from '../agent-clients/tokens.js';
2
- import { activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, beginOrResumeOrchestration, completeCommitOrchestrationStage, pauseOrchestration, prepareOrchestrationDelegation, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusOrchestration } from '../task/orchestration.js';
2
+ import { abortCommitIntent, activateMatchingOrchestrationDelegation, activateOrchestrationDelegation, advanceOrchestration, beginCommitIntent, beginOrResumeOrchestration, checkpointCommitIntent, completeCommitIntent, pauseOrchestration, prepareOrchestrationDelegation, recoverCommitIntent, routeOrchestration, sealMatchingOrchestrationDelegation, sealOrchestrationDelegation, statusCommitIntent, statusOrchestration } from '../task/orchestration.js';
3
3
  import { isAgentClientId } from '../agent-clients/types.js';
4
- const USAGE = 'Usage: agent-infra-internal task-orchestration <task-ref|auto> <begin-or-resume|route|prepare|hook-start|stage-completed|hook-stop|advance|pause|status> [options]\n';
4
+ const USAGE = 'Usage: agent-infra-internal task-orchestration <task-ref|auto> <begin-or-resume|route|prepare|hook-start|hook-stop|advance|pause|status|commit-begin|commit-checkpoint|commit-complete|commit-recover|commit-abort|commit-status> [options]\n';
5
5
  function usageFailure(message) {
6
6
  process.stdout.write(`${JSON.stringify({
7
7
  status: 'failed', changed: false,
@@ -20,21 +20,35 @@ function taskOrchestration(args = []) {
20
20
  return;
21
21
  }
22
22
  const [taskRef, intent] = args;
23
- if (!['begin-or-resume', 'route', 'prepare', 'hook-start', 'stage-completed', 'hook-stop', 'advance', 'pause', 'status'].includes(intent)) {
23
+ if (![
24
+ 'begin-or-resume', 'route', 'prepare', 'hook-start', 'hook-stop', 'advance', 'pause', 'status',
25
+ 'commit-begin', 'commit-checkpoint', 'commit-complete', 'commit-recover', 'commit-abort', 'commit-status'
26
+ ].includes(intent)) {
24
27
  usageFailure(`unknown intent '${intent}'`);
25
28
  return;
26
29
  }
27
30
  const values = {};
28
31
  const seen = new Set();
32
+ let orchestrated = false;
29
33
  for (let index = 2; index < args.length; index += 1) {
30
34
  const flag = args[index];
35
+ if (flag === '--orchestrated') {
36
+ if (seen.has(flag)) {
37
+ usageFailure(`duplicate option '${flag}'`);
38
+ return;
39
+ }
40
+ seen.add(flag);
41
+ orchestrated = true;
42
+ continue;
43
+ }
31
44
  if (![
32
45
  '--max-steps', '--executor-model', '--executor-reasoning-effort',
33
46
  '--reviewer-model', '--reviewer-reasoning-effort',
34
47
  '--client', '--requested-model', '--requested-reasoning-effort', '--parent-id', '--before-fingerprint',
35
48
  '--native-agent', '--child-id', '--spawn-mode', '--actual-model', '--actual-reasoning-effort',
36
49
  '--model-fallback-reason', '--reasoning-effort-fallback-reason',
37
- '--exit-code', '--after-fingerprint', '--changed-paths', '--code', '--message', '--recoverable', '--agent'
50
+ '--exit-code', '--after-fingerprint', '--changed-paths', '--code', '--message', '--recoverable', '--agent',
51
+ '--baseline-head', '--token', '--kind', '--head', '--remote', '--ref', '--expected-head'
38
52
  ].includes(flag)) {
39
53
  usageFailure(`unknown option '${flag}'`);
40
54
  return;
@@ -51,13 +65,116 @@ function taskOrchestration(args = []) {
51
65
  }
52
66
  values[flag] = value;
53
67
  }
68
+ const commitIntentFlags = {
69
+ 'commit-begin': ['--agent', '--baseline-head', '--orchestrated'],
70
+ 'commit-checkpoint': ['--token', '--kind', '--head', '--remote', '--ref'],
71
+ 'commit-complete': ['--token', '--agent'],
72
+ 'commit-recover': ['--agent'],
73
+ 'commit-abort': ['--token', '--expected-head'],
74
+ 'commit-status': []
75
+ };
76
+ if (intent in commitIntentFlags) {
77
+ const allowed = commitIntentFlags[intent];
78
+ const invalid = [...seen].find((flag) => !allowed.includes(flag));
79
+ if (invalid) {
80
+ usageFailure(`intent '${intent}' does not accept '${invalid}'`);
81
+ return;
82
+ }
83
+ }
84
+ else {
85
+ const commitOnly = [...seen].find((flag) => [
86
+ '--orchestrated', '--baseline-head', '--token', '--kind', '--head', '--remote', '--ref', '--expected-head'
87
+ ].includes(flag));
88
+ if (commitOnly) {
89
+ usageFailure(`intent '${intent}' does not accept '${commitOnly}'`);
90
+ return;
91
+ }
92
+ }
54
93
  const requireValues = (flags) => flags.find((flag) => values[flag] === undefined);
55
94
  if (values['--client'] !== undefined && !isAgentClientId(values['--client'])) {
56
95
  usageFailure(`unknown client '${values['--client']}'`);
57
96
  return;
58
97
  }
59
98
  let result;
60
- if (intent === 'begin-or-resume') {
99
+ if (intent === 'commit-begin') {
100
+ const missing = requireValues(['--agent', '--baseline-head']);
101
+ if (missing) {
102
+ usageFailure(`intent 'commit-begin' requires '${missing}'`);
103
+ return;
104
+ }
105
+ const agent = normalizeAgentToken(values['--agent']);
106
+ if (!agent) {
107
+ usageFailure(`invalid --agent '${values['--agent']}': ${AGENT_USAGE_HINT}`);
108
+ return;
109
+ }
110
+ result = beginCommitIntent(taskRef, {
111
+ agent, orchestrated, baselineHead: values['--baseline-head']
112
+ });
113
+ }
114
+ else if (intent === 'commit-checkpoint') {
115
+ const missing = requireValues(['--token', '--kind', '--head']);
116
+ if (missing) {
117
+ usageFailure(`intent 'commit-checkpoint' requires '${missing}'`);
118
+ return;
119
+ }
120
+ if (!['committed', 'pushed'].includes(values['--kind'])) {
121
+ usageFailure('--kind must be committed or pushed');
122
+ return;
123
+ }
124
+ if (values['--kind'] === 'pushed') {
125
+ const pushedMissing = requireValues(['--remote', '--ref']);
126
+ if (pushedMissing) {
127
+ usageFailure(`pushed checkpoint requires '${pushedMissing}'`);
128
+ return;
129
+ }
130
+ }
131
+ else if (values['--remote'] !== undefined || values['--ref'] !== undefined) {
132
+ usageFailure('committed checkpoint does not accept --remote or --ref');
133
+ return;
134
+ }
135
+ result = checkpointCommitIntent(taskRef, {
136
+ token: values['--token'], kind: values['--kind'],
137
+ head: values['--head'], remote: values['--remote'], ref: values['--ref']
138
+ });
139
+ }
140
+ else if (intent === 'commit-complete') {
141
+ const missing = requireValues(['--token', '--agent']);
142
+ if (missing) {
143
+ usageFailure(`intent 'commit-complete' requires '${missing}'`);
144
+ return;
145
+ }
146
+ const agent = normalizeAgentToken(values['--agent']);
147
+ if (!agent) {
148
+ usageFailure(`invalid --agent '${values['--agent']}': ${AGENT_USAGE_HINT}`);
149
+ return;
150
+ }
151
+ result = completeCommitIntent(taskRef, { token: values['--token'], agent });
152
+ }
153
+ else if (intent === 'commit-recover') {
154
+ const missing = requireValues(['--agent']);
155
+ if (missing) {
156
+ usageFailure(`intent 'commit-recover' requires '${missing}'`);
157
+ return;
158
+ }
159
+ const agent = normalizeAgentToken(values['--agent']);
160
+ if (!agent) {
161
+ usageFailure(`invalid --agent '${values['--agent']}': ${AGENT_USAGE_HINT}`);
162
+ return;
163
+ }
164
+ result = recoverCommitIntent(taskRef, { agent });
165
+ }
166
+ else if (intent === 'commit-abort') {
167
+ const missing = requireValues(['--token', '--expected-head']);
168
+ if (missing) {
169
+ usageFailure(`intent 'commit-abort' requires '${missing}'`);
170
+ return;
171
+ }
172
+ result = abortCommitIntent(taskRef, { token: values['--token'], expectedHead: values['--expected-head'] });
173
+ }
174
+ else if (intent === 'commit-status') {
175
+ result = statusCommitIntent(taskRef);
176
+ }
177
+ else if (intent === 'begin-or-resume') {
61
178
  const missing = requireValues(['--client']);
62
179
  if (missing) {
63
180
  usageFailure(`intent 'begin-or-resume' requires '${missing}'`);
@@ -160,19 +277,6 @@ function taskOrchestration(args = []) {
160
277
  });
161
278
  }
162
279
  }
163
- else if (intent === 'stage-completed') {
164
- const missing = requireValues(['--agent']);
165
- if (missing) {
166
- usageFailure(`intent 'stage-completed' requires '${missing}'`);
167
- return;
168
- }
169
- const agent = normalizeAgentToken(values['--agent']);
170
- if (!agent) {
171
- usageFailure(`invalid --agent '${values['--agent']}': ${AGENT_USAGE_HINT}`);
172
- return;
173
- }
174
- result = completeCommitOrchestrationStage(taskRef, agent);
175
- }
176
280
  else if (intent === 'advance') {
177
281
  result = advanceOrchestration(taskRef);
178
282
  }
@@ -2,6 +2,14 @@ import process from 'node:process';
2
2
  import spawn from 'cross-spawn';
3
3
  import semver from 'semver';
4
4
  const MINIMUM_GITHUB_CLI_VERSION = '2.16.0';
5
+ const GITHUB_CLI_MAX_BUFFER = 64 * 1024 * 1024;
6
+ const ERROR_DETAIL_LIMIT = 4096;
7
+ function boundedFailureDetail(result) {
8
+ const diagnostic = `${result.stderr}\n${result.error?.message || ''}`.trim() || result.stdout.trim();
9
+ return diagnostic.length <= ERROR_DETAIL_LIMIT
10
+ ? diagnostic
11
+ : `${diagnostic.slice(0, ERROR_DETAIL_LIMIT)}… [truncated]`;
12
+ }
5
13
  function defaultRunner(args, options) {
6
14
  const command = process.env.AGENT_INFRA_GH_BIN || 'gh';
7
15
  let prefix = [];
@@ -16,6 +24,7 @@ function defaultRunner(args, options) {
16
24
  encoding: 'utf8',
17
25
  env: process.env,
18
26
  input: options.input,
27
+ maxBuffer: GITHUB_CLI_MAX_BUFFER,
19
28
  shell: false
20
29
  });
21
30
  return {
@@ -37,7 +46,15 @@ function defaultSleep(delayMs) {
37
46
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, delayMs);
38
47
  }
39
48
  function classifyGitHubFailure(result) {
40
- const detail = `${result.stderr}\n${result.stdout}\n${result.error?.message || ''}`.trim();
49
+ const errorCode = result.error && 'code' in result.error ? result.error.code : undefined;
50
+ if (errorCode === 'ENOBUFS') {
51
+ return {
52
+ code: 'PLATFORM_OUTPUT_TOO_LARGE',
53
+ message: 'GitHub CLI output exceeded the configured limit',
54
+ retryable: false
55
+ };
56
+ }
57
+ const detail = boundedFailureDetail(result);
41
58
  const lower = detail.toLowerCase();
42
59
  if (/\b401\b|bad credentials|authentication required|not logged into/.test(lower)) {
43
60
  return { code: 'AUTH_REQUIRED', message: detail || 'GitHub authentication is required', retryable: false };
@@ -54,7 +71,7 @@ function classifyGitHubFailure(result) {
54
71
  if (/\b422\b|validation failed/.test(lower)) {
55
72
  return { code: 'PLATFORM_REQUEST_INVALID', message: detail || 'GitHub rejected the request', retryable: false };
56
73
  }
57
- if (result.error && 'code' in result.error && result.error.code === 'ENOENT') {
74
+ if (errorCode === 'ENOENT') {
58
75
  return { code: 'PLATFORM_DEPENDENCY_MISSING', message: detail || 'GitHub CLI is unavailable', retryable: false };
59
76
  }
60
77
  return { code: 'PLATFORM_REQUEST_FAILED', message: detail || 'GitHub request failed', retryable: false };
@@ -85,7 +85,7 @@ registerPlatformCapabilities('github', {
85
85
  inspectRequiredChecks({ client, repository, number, cwd }) {
86
86
  const github = client || createGitHubClient();
87
87
  const inspected = github.json([
88
- 'pr', 'checks', String(number), '--repo', repository, '--required',
88
+ 'pr', 'checks', String(number), '--repo', repository,
89
89
  '--json', 'name,state,bucket,link,workflow,startedAt,completedAt'
90
90
  ], { cwd });
91
91
  return inspected.ok
@@ -11,6 +11,8 @@ import { inspectPlatformIssue } from './issues.js';
11
11
  import { planPullRequestMetadata } from './pull-request-metadata.js';
12
12
  import { platformResult } from './types.js';
13
13
  import { inspectCompletionArtifacts } from '../task/finalization-artifacts.js';
14
+ import { inspectCreatePrCommitGate } from '../task/commit-finalization.js';
15
+ import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
14
16
  function result(status, taskId, issueNumber, prNumber, overrides = {}) {
15
17
  return {
16
18
  ...platformResult(status),
@@ -612,7 +614,7 @@ function resolveExternalPullRequest(taskRef, options) {
612
614
  error: null
613
615
  }), { mode: 'external', authorization: selected.source, candidates: selected.candidates, eligible: selected.eligible, selected: selected.selected });
614
616
  }
615
- function createPlatformPullRequest(taskRef, options) {
617
+ function createPlatformPullRequestUnlocked(taskRef, options) {
616
618
  const base = resolvedContext(taskRef, options);
617
619
  if (!base.ok)
618
620
  return base.output;
@@ -622,6 +624,17 @@ function createPlatformPullRequest(taskRef, options) {
622
624
  });
623
625
  if (base.prNumber)
624
626
  return inspectPlatformPullRequest(taskRef, options);
627
+ const gate = inspectCreatePrCommitGate(base.resolved.taskDir, base.resolved.repoRoot, base.resolved.taskId);
628
+ if (!gate.allowed)
629
+ return result('blocked', base.resolved.taskId, base.issueNumber, null, {
630
+ platform: base.context.platform,
631
+ capabilities: base.context.capabilities,
632
+ error: {
633
+ code: gate.code,
634
+ message: `${gate.message}; action=${gate.action}`,
635
+ retryable: gate.action === 'rerun-commit' || gate.action === 'rerun-review-code'
636
+ }
637
+ });
625
638
  const located = locatePullRequest(base, options.head, options.base);
626
639
  if (!located.ok && located.error.code === 'PR_IDENTITY_AMBIGUOUS')
627
640
  return result('failed', base.resolved.taskId, base.issueNumber, null, {
@@ -689,6 +702,39 @@ function createPlatformPullRequest(taskRef, options) {
689
702
  operations: [{ name: created ? 'pr:create' : 'pr:reuse', status: created ? 'applied' : 'no-op', reasonCode: null }, { name: 'task:bind-pr', status: 'applied', reasonCode: null }], error: null
690
703
  });
691
704
  }
705
+ function createPlatformPullRequest(taskRef, options) {
706
+ const base = resolvedContext(taskRef, options);
707
+ if (!base.ok)
708
+ return base.output;
709
+ if (!options.base || !options.head || !options.title.trim() || !options.body.trim()) {
710
+ return result('failed', base.resolved.taskId, base.issueNumber, base.prNumber, {
711
+ error: { code: 'PR_PAYLOAD_INVALID', message: 'base, head, title and body are required', retryable: false }
712
+ });
713
+ }
714
+ if (base.prNumber)
715
+ return inspectPlatformPullRequest(taskRef, options);
716
+ try {
717
+ return withTaskExecutionLock(base.resolved.repoRoot, base.resolved.taskId, 'platform-pr.create', () => createPlatformPullRequestUnlocked(taskRef, options));
718
+ }
719
+ catch (error) {
720
+ if (error instanceof TaskExecutionLockError) {
721
+ return result('blocked', base.resolved.taskId, base.issueNumber, null, {
722
+ platform: base.context.platform,
723
+ capabilities: base.context.capabilities,
724
+ error: { code: error.code, message: error.message, retryable: true }
725
+ });
726
+ }
727
+ return result('failed', base.resolved.taskId, base.issueNumber, null, {
728
+ platform: base.context.platform,
729
+ capabilities: base.context.capabilities,
730
+ error: {
731
+ code: 'PR_CREATE_FAILED',
732
+ message: error instanceof Error ? error.message : String(error),
733
+ retryable: false
734
+ }
735
+ });
736
+ }
737
+ }
692
738
  function syncPlatformPullRequest(taskRef, options) {
693
739
  const base = resolvedContext(taskRef, options);
694
740
  if (!base.ok)
@@ -55,9 +55,17 @@ function reconcileReleaseMilestones(version, options = {}) {
55
55
  const major = Number(match[1]);
56
56
  const minor = Number(match[2]);
57
57
  const patch = Number(match[3]);
58
- const ensure = [`${major}.${minor}.${patch + 1}`, `${major}.${minor}.x`];
59
- if (patch === 0)
60
- ensure.push(`${major}.${minor + 1}.0`, `${major}.${minor + 1}.x`);
58
+ const nextPatch = `${major}.${minor}.${patch + 1}`;
59
+ const currentLine = `${major}.${minor}.x`;
60
+ const ensure = [
61
+ { title: nextPatch, description: `Issues that we want to release in v${nextPatch}.` },
62
+ { title: currentLine, description: `Issues that we want to resolve in ${major}.${minor} line.` }
63
+ ];
64
+ if (patch === 0) {
65
+ const nextMinor = `${major}.${minor + 1}.0`;
66
+ const nextLine = `${major}.${minor + 1}.x`;
67
+ ensure.push({ title: nextMinor, description: `Issues that we want to release in v${nextMinor}.` }, { title: nextLine, description: `Issues that we want to resolve in ${major}.${minor + 1} line.` });
68
+ }
61
69
  const operations = [];
62
70
  const current = milestones.find((item) => item.title === version);
63
71
  if (current && current.state !== 'closed') {
@@ -66,11 +74,11 @@ function reconcileReleaseMilestones(version, options = {}) {
66
74
  }
67
75
  else
68
76
  operations.push({ name: 'close-milestone', title: version, status: 'no-op' });
69
- for (const title of ensure) {
77
+ for (const { title, description } of ensure) {
70
78
  if (milestones.some((item) => item.title === title))
71
79
  operations.push({ name: 'ensure-milestone', title, status: 'no-op' });
72
80
  else {
73
- const created = client.json(['api', '--method', 'POST', `repos/${context.platform.repository}/milestones`, '-f', `title=${title}`], { cwd: options.cwd });
81
+ const created = client.json(['api', '--method', 'POST', `repos/${context.platform.repository}/milestones`, '-f', `title=${title}`, '-f', `description=${description}`], { cwd: options.cwd });
74
82
  operations.push({ name: 'ensure-milestone', title, status: created.ok ? 'applied' : 'failed' });
75
83
  }
76
84
  }
@@ -0,0 +1,239 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { execFileSync } from 'node:child_process';
4
+ import { detectRepoRoot } from '../task/resolve-ref.js';
5
+ import { normalizeObjects } from './normalize.js';
6
+ import { reconcileRecords } from './reconcile.js';
7
+ import { readAppliedOverlays, repairSnapshot } from './repair.js';
8
+ import { collectGitHubObjects, collectLocalObjects } from './sources.js';
9
+ import { canonicalJsonBytes, createObjectStore, publishSnapshot, verifySnapshot } from './store.js';
10
+ const USAGE = `Usage: ai data <command> [options]
11
+
12
+ Commands:
13
+ capture [--source all|local|github] [--root <dir>] [--include-excerpts]
14
+ verify <snapshot-id> [--root <dir>]
15
+ audit <snapshot-id> [--root <dir>] [--format json|text]
16
+ repair <snapshot-id> [--root <dir>] [--apply]
17
+ export <snapshot-id> [--root <dir>] [--repairs none|applied] [--output <file|->]
18
+ `;
19
+ function parseArgs(args, valueOptions, booleanOptions) {
20
+ const positionals = [];
21
+ const options = new Map();
22
+ for (let index = 0; index < args.length; index += 1) {
23
+ const arg = args[index];
24
+ if (!arg.startsWith('--')) {
25
+ positionals.push(arg);
26
+ continue;
27
+ }
28
+ if (booleanOptions.has(arg)) {
29
+ if (options.has(arg))
30
+ throw new Error(`duplicate option: ${arg}`);
31
+ options.set(arg, true);
32
+ continue;
33
+ }
34
+ if (!valueOptions.has(arg))
35
+ throw new Error(`unknown option: ${arg}`);
36
+ const value = args[index + 1];
37
+ if (!value || value.startsWith('--'))
38
+ throw new Error(`missing value for ${arg}`);
39
+ if (options.has(arg))
40
+ throw new Error(`duplicate option: ${arg}`);
41
+ options.set(arg, value);
42
+ index += 1;
43
+ }
44
+ return { positionals, options };
45
+ }
46
+ function dataRoot(repoRoot, requested) {
47
+ const workspace = path.resolve(repoRoot, '.agents', 'workspace');
48
+ const resolved = path.resolve(repoRoot, requested ?? path.join('.agents', 'workspace', 'process-data'));
49
+ const relative = path.relative(repoRoot, resolved);
50
+ if (!relative || relative.startsWith('..') || path.isAbsolute(relative))
51
+ throw new Error('data root must be inside the repository');
52
+ const taskRoots = ['active', 'blocked', 'completed', 'archive'].map((state) => path.join(workspace, state));
53
+ if (resolved === workspace || taskRoots.some((taskRoot) => resolved === taskRoot || resolved.startsWith(`${taskRoot}${path.sep}`))) {
54
+ throw new Error('data root cannot be the workspace root or a task directory');
55
+ }
56
+ let current = resolved;
57
+ while (current !== repoRoot) {
58
+ if (fs.existsSync(current) && fs.lstatSync(current).isSymbolicLink())
59
+ throw new Error('data root cannot traverse a symlink');
60
+ current = path.dirname(current);
61
+ }
62
+ return resolved;
63
+ }
64
+ function repositoryIdentity(repoRoot) {
65
+ try {
66
+ return execFileSync('git', ['config', '--get', 'remote.origin.url'], { cwd: repoRoot, encoding: 'utf8' }).trim() || path.basename(repoRoot);
67
+ }
68
+ catch {
69
+ return path.basename(repoRoot);
70
+ }
71
+ }
72
+ function writeJson(value) {
73
+ process.stdout.write(`${JSON.stringify(value)}\n`);
74
+ }
75
+ function optionString(parsed, name) {
76
+ const value = parsed.options.get(name);
77
+ return typeof value === 'string' ? value : undefined;
78
+ }
79
+ function capture(args) {
80
+ const parsed = parseArgs(args, new Set(['--source', '--root']), new Set(['--include-excerpts']));
81
+ if (parsed.positionals.length > 0)
82
+ throw new Error('capture does not accept positional arguments');
83
+ const scope = optionString(parsed, '--source') ?? 'all';
84
+ if (!['all', 'local', 'github'].includes(scope))
85
+ throw new Error('--source must be all, local, or github');
86
+ const repoRoot = detectRepoRoot();
87
+ const root = dataRoot(repoRoot, optionString(parsed, '--root'));
88
+ const observedFrom = new Date().toISOString();
89
+ const objects = [];
90
+ const endpoints = [];
91
+ let repository = repositoryIdentity(repoRoot);
92
+ if (scope === 'all' || scope === 'local') {
93
+ const local = collectLocalObjects(repoRoot, { includeExcerpts: parsed.options.has('--include-excerpts') });
94
+ if (!local.ok) {
95
+ writeJson({ ok: false, error: local.error });
96
+ return 2;
97
+ }
98
+ objects.push(...local.value);
99
+ }
100
+ if (scope === 'all' || scope === 'github') {
101
+ const github = collectGitHubObjects(repoRoot);
102
+ if (!github.ok) {
103
+ writeJson({ ok: false, error: github.error });
104
+ return 2;
105
+ }
106
+ objects.push(...github.value.objects);
107
+ endpoints.push(...github.value.endpoints);
108
+ repository = github.value.repository;
109
+ }
110
+ const store = createObjectStore(root);
111
+ for (const object of objects) {
112
+ if (object.content !== undefined && object.disposition?.state === 'included') {
113
+ const stored = store.put(Buffer.from(object.content, 'utf8'));
114
+ if (stored.sha256 !== object.sha256)
115
+ throw new Error(`source hash mismatch: ${object.sourceIdentity}`);
116
+ }
117
+ }
118
+ const records = normalizeObjects(objects);
119
+ const reconciliation = reconcileRecords(records, scope);
120
+ const observedTo = new Date().toISOString();
121
+ const published = publishSnapshot(root, {
122
+ scope: scope,
123
+ repository,
124
+ observedFrom,
125
+ observedTo,
126
+ objects: objects.map(({ content: _content, ...object }) => object),
127
+ endpoints,
128
+ excerptsEnabled: parsed.options.has('--include-excerpts'),
129
+ records,
130
+ quality: reconciliation.findings,
131
+ repairs: reconciliation.repairs
132
+ });
133
+ writeJson({ ok: true, snapshotId: published.snapshotId, scope, created: published.created, objects: objects.length, records: records.length });
134
+ return 0;
135
+ }
136
+ function verify(args) {
137
+ const parsed = parseArgs(args, new Set(['--root']), new Set());
138
+ if (parsed.positionals.length !== 1)
139
+ throw new Error('verify requires one snapshot id');
140
+ const repoRoot = detectRepoRoot();
141
+ const result = verifySnapshot(dataRoot(repoRoot, optionString(parsed, '--root')), parsed.positionals[0]);
142
+ writeJson(result);
143
+ return result.ok ? 0 : 1;
144
+ }
145
+ function audit(args) {
146
+ const parsed = parseArgs(args, new Set(['--root', '--format']), new Set());
147
+ if (parsed.positionals.length !== 1)
148
+ throw new Error('audit requires one snapshot id');
149
+ const format = optionString(parsed, '--format') ?? 'text';
150
+ if (!['json', 'text'].includes(format))
151
+ throw new Error('--format must be json or text');
152
+ const repoRoot = detectRepoRoot();
153
+ const root = dataRoot(repoRoot, optionString(parsed, '--root'));
154
+ const verified = verifySnapshot(root, parsed.positionals[0]);
155
+ if (!verified.ok || !verified.snapshotPath || !verified.manifest) {
156
+ writeJson(verified);
157
+ return 1;
158
+ }
159
+ const quality = JSON.parse(fs.readFileSync(path.join(verified.snapshotPath, 'quality.json'), 'utf8'));
160
+ const records = fs.readFileSync(path.join(verified.snapshotPath, 'normalized.jsonl'), 'utf8').split('\n').filter(Boolean).map((line) => {
161
+ const { schema: _schema, ...record } = JSON.parse(line);
162
+ return record;
163
+ });
164
+ const recomputed = reconcileRecords(records, verified.manifest.scope);
165
+ if (!canonicalJsonBytes(recomputed.findings).equals(canonicalJsonBytes(quality.findings))) {
166
+ writeJson({ ok: false, error: { code: 'QUALITY_MISMATCH', message: 'stored quality baseline does not match recomputed findings' } });
167
+ return 1;
168
+ }
169
+ if (format === 'json')
170
+ writeJson({ ok: true, snapshotId: parsed.positionals[0], findings: recomputed.findings });
171
+ else
172
+ process.stdout.write(`Snapshot ${parsed.positionals[0]}: ${quality.findings.length} findings\n`);
173
+ return 0;
174
+ }
175
+ function repair(args) {
176
+ const parsed = parseArgs(args, new Set(['--root']), new Set(['--apply']));
177
+ if (parsed.positionals.length !== 1)
178
+ throw new Error('repair requires one snapshot id');
179
+ const repoRoot = detectRepoRoot();
180
+ const result = repairSnapshot(dataRoot(repoRoot, optionString(parsed, '--root')), parsed.positionals[0], parsed.options.has('--apply'));
181
+ writeJson({ ok: true, ...result });
182
+ return 0;
183
+ }
184
+ function exportSnapshot(args) {
185
+ const parsed = parseArgs(args, new Set(['--root', '--repairs', '--output']), new Set());
186
+ if (parsed.positionals.length !== 1)
187
+ throw new Error('export requires one snapshot id');
188
+ const repairMode = optionString(parsed, '--repairs') ?? 'none';
189
+ if (!['none', 'applied'].includes(repairMode))
190
+ throw new Error('--repairs must be none or applied');
191
+ const output = optionString(parsed, '--output') ?? '-';
192
+ const repoRoot = detectRepoRoot();
193
+ const root = dataRoot(repoRoot, optionString(parsed, '--root'));
194
+ const verified = verifySnapshot(root, parsed.positionals[0]);
195
+ if (!verified.ok || !verified.snapshotPath) {
196
+ writeJson(verified);
197
+ return 1;
198
+ }
199
+ let content = fs.readFileSync(path.join(verified.snapshotPath, 'normalized.jsonl'), 'utf8');
200
+ if (repairMode === 'applied')
201
+ content += readAppliedOverlays(root, parsed.positionals[0]);
202
+ if (output === '-')
203
+ process.stdout.write(content);
204
+ else {
205
+ const destination = path.resolve(repoRoot, output);
206
+ const relative = path.relative(repoRoot, destination);
207
+ if (!relative || relative.startsWith('..') || path.isAbsolute(relative))
208
+ throw new Error('export output must be inside the repository');
209
+ fs.writeFileSync(destination, content, { flag: 'wx', mode: 0o600 });
210
+ writeJson({ ok: true, snapshotId: parsed.positionals[0], output: relative });
211
+ }
212
+ return 0;
213
+ }
214
+ async function cmdData(args) {
215
+ const [command, ...rest] = args;
216
+ if (!command || command === '--help' || command === '-h' || command === 'help') {
217
+ process.stdout.write(USAGE);
218
+ return command ? 0 : 1;
219
+ }
220
+ try {
221
+ switch (command) {
222
+ case 'capture': return capture(rest);
223
+ case 'verify': return verify(rest);
224
+ case 'audit': return audit(rest);
225
+ case 'repair': return repair(rest);
226
+ case 'export': return exportSnapshot(rest);
227
+ default:
228
+ process.stderr.write(`Unknown data command: ${command}\n`);
229
+ process.stdout.write(USAGE);
230
+ return 1;
231
+ }
232
+ }
233
+ catch (error) {
234
+ writeJson({ ok: false, error: { code: 'DATA_COMMAND_FAILED', message: error instanceof Error ? error.message : String(error) } });
235
+ return 1;
236
+ }
237
+ }
238
+ export { cmdData, dataRoot };
239
+ //# sourceMappingURL=index.js.map