@fitlab-ai/agent-infra 0.11.11 → 0.11.13

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 (154) hide show
  1. package/dist/bin/internal-cli.js +2 -2
  2. package/dist/lib/agent-clients/adapter.js +7 -1
  3. package/dist/lib/agent-clients/adapters/claude-code.js +5 -1
  4. package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +78 -63
  5. package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +114 -0
  6. package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +447 -0
  7. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-client.js +130 -0
  8. package/dist/lib/agent-clients/adapters/codex-lifecycle/controller-operation.js +131 -0
  9. package/dist/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.js +2 -2
  10. package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +48 -1
  11. package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +124 -62
  12. package/dist/lib/{internal/codex-sandbox-controller.js → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.js} +3 -3
  13. package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +3 -5
  14. package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +211 -213
  15. package/dist/lib/agent-clients/adapters/codex-orchestration.js +468 -0
  16. package/dist/lib/agent-clients/adapters/codex.js +40 -1
  17. package/dist/lib/agent-clients/adapters/index.js +2 -10
  18. package/dist/lib/agent-clients/tokens.js +3 -2
  19. package/dist/lib/internal/agent-client.js +132 -72
  20. package/dist/lib/internal/cli-route-inventory.js +2 -2
  21. package/dist/lib/internal/sandbox-control.js +1 -81
  22. package/dist/lib/internal/task-finalization.js +26 -19
  23. package/dist/lib/internal/task-lifecycle.js +1 -1
  24. package/dist/lib/internal/task-operation-registry.js +1 -1
  25. package/dist/lib/internal/task-validate.js +1 -1
  26. package/dist/lib/platform/github-client.js +33 -3
  27. package/dist/lib/platform/issues.js +18 -6
  28. package/dist/lib/runtime/agent-runtime.js +6 -29
  29. package/dist/lib/sandbox/commands/control-status.js +1 -0
  30. package/dist/lib/sandbox/commands/create.js +7 -2
  31. package/dist/lib/sandbox/commands/ls.js +4 -5
  32. package/dist/lib/sandbox/commands/rm.js +1 -1
  33. package/dist/lib/sandbox/control/agent-operation.js +34 -0
  34. package/dist/lib/sandbox/control/client.js +14 -201
  35. package/dist/lib/sandbox/control/executor.js +12 -125
  36. package/dist/lib/sandbox/control/lifecycle.js +66 -4
  37. package/dist/lib/sandbox/control/protocol.js +15 -54
  38. package/dist/lib/sandbox/control/server.js +216 -281
  39. package/dist/lib/sandbox/control/task-view.js +0 -13
  40. package/dist/lib/sandbox/mounts.js +9 -1
  41. package/dist/lib/sandbox/recovery.js +31 -10
  42. package/dist/lib/sandbox/removal.js +28 -5
  43. package/dist/lib/sandbox/workspace-identity.js +18 -6
  44. package/dist/lib/sandbox/workspace-view.js +56 -19
  45. package/dist/lib/task/commands/ls.js +3 -3
  46. package/dist/lib/task/control-authority.js +56 -264
  47. package/dist/lib/task/control-recovery.js +2 -3
  48. package/dist/lib/task/create.js +2 -2
  49. package/dist/lib/task/delegation-receipts.js +74 -161
  50. package/dist/lib/task/events.js +88 -44
  51. package/dist/lib/task/finalization.js +35 -6
  52. package/dist/lib/task/lifecycle.js +274 -115
  53. package/dist/lib/task/orchestration.js +201 -194
  54. package/dist/lib/task/runtime-paths.js +10 -0
  55. package/dist/lib/task/short-id.js +37 -27
  56. package/dist/lib/task/verification-engine.js +32 -26
  57. package/dist/lib/task/workspace-snapshot.js +1 -1
  58. package/dist/lifecycle-build-manifest.json +3 -3
  59. package/lib/agent-clients/adapter.ts +39 -0
  60. package/lib/agent-clients/adapters/claude-code.ts +5 -1
  61. package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +105 -80
  62. package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +147 -0
  63. package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +510 -0
  64. package/lib/agent-clients/adapters/codex-lifecycle/controller-client.ts +158 -0
  65. package/lib/agent-clients/adapters/codex-lifecycle/controller-context.ts +1 -1
  66. package/lib/agent-clients/adapters/codex-lifecycle/controller-operation.ts +151 -0
  67. package/lib/{sandbox/control → agent-clients/adapters/codex-lifecycle}/controller-registration.ts +4 -4
  68. package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +43 -0
  69. package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +8 -5
  70. package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +190 -65
  71. package/lib/{internal/codex-sandbox-controller.ts → agent-clients/adapters/codex-lifecycle/sandbox-controller-cli.ts} +2 -2
  72. package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +3 -5
  73. package/lib/agent-clients/adapters/codex-lifecycle/store.ts +270 -224
  74. package/lib/agent-clients/adapters/codex-orchestration.ts +632 -0
  75. package/lib/agent-clients/adapters/codex.ts +44 -1
  76. package/lib/agent-clients/adapters/index.ts +7 -20
  77. package/lib/agent-clients/tokens.ts +4 -3
  78. package/lib/internal/agent-client.ts +142 -73
  79. package/lib/internal/cli-route-inventory.ts +2 -2
  80. package/lib/internal/sandbox-control.ts +0 -97
  81. package/lib/internal/task-finalization.ts +30 -18
  82. package/lib/internal/task-lifecycle.ts +1 -1
  83. package/lib/internal/task-operation-registry.ts +1 -1
  84. package/lib/internal/task-validate.ts +1 -1
  85. package/lib/platform/github-client.ts +34 -3
  86. package/lib/platform/issues.ts +18 -6
  87. package/lib/runtime/agent-runtime.ts +6 -41
  88. package/lib/sandbox/commands/control-status.ts +1 -0
  89. package/lib/sandbox/commands/create.ts +7 -1
  90. package/lib/sandbox/commands/ls.ts +4 -5
  91. package/lib/sandbox/commands/rm.ts +1 -1
  92. package/lib/sandbox/control/agent-operation.ts +49 -0
  93. package/lib/sandbox/control/client.ts +20 -254
  94. package/lib/sandbox/control/executor.ts +13 -136
  95. package/lib/sandbox/control/lifecycle.ts +67 -4
  96. package/lib/sandbox/control/protocol.ts +22 -66
  97. package/lib/sandbox/control/server.ts +21 -95
  98. package/lib/sandbox/control/task-view.ts +0 -19
  99. package/lib/sandbox/mounts.ts +10 -1
  100. package/lib/sandbox/recovery.ts +38 -17
  101. package/lib/sandbox/removal.ts +33 -7
  102. package/lib/sandbox/workspace-identity.ts +16 -6
  103. package/lib/sandbox/workspace-view.ts +50 -18
  104. package/lib/task/commands/ls.ts +3 -3
  105. package/lib/task/control-authority.ts +66 -378
  106. package/lib/task/control-recovery.ts +3 -4
  107. package/lib/task/create.ts +3 -3
  108. package/lib/task/delegation-receipts.ts +87 -231
  109. package/lib/task/events.ts +100 -42
  110. package/lib/task/finalization.ts +37 -7
  111. package/lib/task/lifecycle.ts +257 -109
  112. package/lib/task/orchestration.ts +260 -237
  113. package/lib/task/runtime-paths.ts +12 -0
  114. package/lib/task/short-id.ts +33 -40
  115. package/lib/task/verification-engine.ts +30 -25
  116. package/lib/task/workspace-snapshot.ts +1 -1
  117. package/package.json +1 -1
  118. package/templates/.agents/hooks/lifecycle-delegation.js +1 -25
  119. package/templates/.agents/rules/README.en.md +2 -1
  120. package/templates/.agents/rules/README.zh-CN.md +2 -1
  121. package/templates/.agents/rules/next-step-output.en.md +6 -19
  122. package/templates/.agents/rules/next-step-output.zh-CN.md +6 -19
  123. package/templates/.agents/rules/strategy-pattern.en.md +25 -0
  124. package/templates/.agents/rules/strategy-pattern.zh-CN.md +25 -0
  125. package/templates/.agents/skills/analyze-task/SKILL.en.md +1 -1
  126. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +1 -1
  127. package/templates/.agents/skills/check-task/SKILL.en.md +1 -1
  128. package/templates/.agents/skills/check-task/SKILL.zh-CN.md +1 -1
  129. package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
  130. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +1 -1
  131. package/templates/.agents/skills/code-task/reference/code-rules.en.md +4 -0
  132. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +4 -0
  133. package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +0 -1
  134. package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +0 -1
  135. package/templates/.agents/skills/complete-task/SKILL.en.md +5 -29
  136. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +5 -26
  137. package/templates/.agents/skills/plan-task/SKILL.en.md +7 -4
  138. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +7 -4
  139. package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
  140. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +1 -1
  141. package/templates/.agents/skills/review-code/SKILL.en.md +1 -1
  142. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +1 -1
  143. package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
  144. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +1 -1
  145. package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
  146. package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
  147. package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
  148. package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
  149. package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +0 -546
  150. package/dist/lib/internal/codex-lifecycle.js +0 -385
  151. package/dist/lib/task/codex-orchestration.js +0 -206
  152. package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +0 -776
  153. package/lib/internal/codex-lifecycle.ts +0 -390
  154. package/lib/task/codex-orchestration.ts +0 -276
@@ -0,0 +1,12 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { resolveTaskRef } from './resolve-ref.ts';
4
+
5
+ export function resolveTaskRuntimeRoot(
6
+ taskRef: string,
7
+ options: Readonly<{ repoRoot?: string }> = {}
8
+ ): string {
9
+ const resolved = resolveTaskRef(taskRef, options);
10
+ if (!resolved.ok) throw new Error(`${resolved.code}: ${resolved.message}`);
11
+ return path.join(fs.realpathSync.native(resolved.taskDir), '.runtime');
12
+ }
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { parseTypedTaskFrontmatter } from './frontmatter.ts';
3
4
 
4
5
  const REGISTRY_NAME = '.short-ids.json';
5
6
  const LOCK_NAME = '.short-ids.json.lock';
@@ -159,43 +160,35 @@ function resolveShortIdReadOnly(
159
160
  const taskId = ids[key];
160
161
  if (!taskId) {
161
162
  const message = Object.keys(ids).length === 0
162
- ? `short id '${key}' not found; active task registry is empty.`
163
- : `short id '${key}' not found in active task registry (it may have been cleaned up after archival; check 'task-short-id.js list').`;
163
+ ? `short id '${key}' not found; task short-id registry is empty.`
164
+ : `short id '${key}' not found in task short-id registry.`;
164
165
  return shortIdFailure('SHORT_ID_NOT_FOUND', message);
165
166
  }
166
- const taskMdPath = path.join(
167
- repoRoot,
168
- '.agents',
169
- 'workspace',
170
- 'active',
171
- taskId,
172
- 'task.md'
173
- );
174
- if (!fs.existsSync(taskMdPath)) {
175
- const remainingActiveEntries = Object.values(ids).filter((candidateTaskId) =>
176
- fs.existsSync(
177
- path.join(
178
- repoRoot,
179
- '.agents',
180
- 'workspace',
181
- 'active',
182
- candidateTaskId,
183
- 'task.md'
184
- )
185
- )
186
- );
187
- const message = remainingActiveEntries.length === 0
188
- ? `short id '${key}' not found; active task registry is empty.`
189
- : `short id '${key}' not found in active task registry (it may have been cleaned up after archival; check 'task-short-id.js list').`;
167
+ const activeTask = path.join(repoRoot, '.agents', 'workspace', 'active', taskId, 'task.md');
168
+ const completedTask = path.join(repoRoot, '.agents', 'workspace', 'completed', taskId, 'task.md');
169
+ let isValidCompletedTask = false;
170
+ try {
171
+ const task = parseTypedTaskFrontmatter(fs.readFileSync(completedTask, 'utf8'));
172
+ isValidCompletedTask = task.id === taskId && task.status === 'completed';
173
+ } catch { /* a mapping is valid only while its task content is present */ }
174
+ if (!fs.existsSync(activeTask) && !isValidCompletedTask) {
190
175
  return shortIdFailure(
191
176
  'SHORT_ID_STALE',
192
- message,
177
+ `short id '${key}' points to no active or completed task content.`,
193
178
  taskId
194
179
  );
195
180
  }
196
181
  return { ok: true, taskId };
197
182
  }
198
183
 
184
+ function hasRetainedCompletedTask(activeDir: string, taskId: string): boolean {
185
+ try {
186
+ const taskPath = path.join(path.dirname(activeDir), 'completed', taskId, 'task.md');
187
+ const task = parseTypedTaskFrontmatter(fs.readFileSync(taskPath, 'utf8'));
188
+ return task.id === taskId && task.status === 'completed';
189
+ } catch { return false; }
190
+ }
191
+
199
192
  type RegistrySchema = {
200
193
  version: number;
201
194
  ids: Record<string, string>;
@@ -339,7 +332,8 @@ function mutateShortIdRegistryAt(
339
332
  const original = JSON.stringify(registry.ids);
340
333
  if (pruneOrphans) {
341
334
  for (const [key, candidate] of Object.entries(registry.ids)) {
342
- if (candidate !== taskId && !fs.existsSync(path.join(activeDir, candidate, 'task.md'))) delete registry.ids[key];
335
+ if (candidate !== taskId && !fs.existsSync(path.join(activeDir, candidate, 'task.md'))
336
+ && !hasRetainedCompletedTask(activeDir, candidate)) delete registry.ids[key];
343
337
  }
344
338
  }
345
339
  const existing = Object.entries(registry.ids).find(([, candidate]) => candidate === taskId);
@@ -406,28 +400,27 @@ function executeShortIdCommand(request: ShortIdCommandRequest): ShortIdCommandRe
406
400
  if (normalized.kind !== 'shortId') {
407
401
  throw Object.assign(new Error(`invalid short id format '${request.argument}'`), { code: 'SHORT_ID_FORMAT_INVALID' });
408
402
  }
409
- return withRegistryLock(request.activeDir, () => {
410
- const registry = readRegistryStrict(registryPath, request.shortIdLength);
411
- let changed = false;
412
- for (const [key, taskId] of Object.entries(registry.ids)) {
413
- if (!fs.existsSync(path.join(request.activeDir, taskId, 'task.md'))) { delete registry.ids[key]; changed = true; }
414
- }
415
- if (changed) writeRegistry(registryPath, registry);
416
- const taskId = registry.ids[normalized.value];
417
- if (!taskId) throw Object.assign(new Error(`short id '${normalized.value}' not found in active task registry`), { code: 'SHORT_ID_NOT_FOUND' });
418
- return { status: changed ? 'applied' : 'no-op', changed, output: taskId, error: null };
419
- });
403
+ const registry = readRegistryStrict(registryPath, request.shortIdLength);
404
+ const taskId = registry.ids[normalized.value];
405
+ if (!taskId) throw Object.assign(new Error(`short id '${normalized.value}' not found in task short-id registry`), { code: 'SHORT_ID_NOT_FOUND' });
406
+ if (!fs.existsSync(path.join(request.activeDir, taskId, 'task.md'))
407
+ && !hasRetainedCompletedTask(request.activeDir, taskId)) {
408
+ throw Object.assign(new Error(`short id '${normalized.value}' points to a task without active or completed task content`), { code: 'SHORT_ID_STALE' });
409
+ }
410
+ return { status: 'no-op', changed: false, output: taskId, error: null };
420
411
  }
421
412
  const registry = readRegistryStrict(registryPath, request.shortIdLength);
422
413
  if (request.verify) {
423
414
  const active = fs.existsSync(request.activeDir)
424
415
  ? fs.readdirSync(request.activeDir).filter((entry) => /^TASK-\d{8}-\d{6}$/.test(entry) && fs.existsSync(path.join(request.activeDir, entry, 'task.md')))
425
416
  : [];
417
+ const retained = Object.values(registry.ids).filter((taskId) => hasRetainedCompletedTask(request.activeDir, taskId));
418
+ const validTasks = new Set([...active, ...retained]);
426
419
  const registered = new Map<string, string[]>();
427
420
  for (const [key, taskId] of Object.entries(registry.ids)) registered.set(taskId, [...(registered.get(taskId) ?? []), key]);
428
421
  const diff = {
429
422
  missing_in_registry: active.filter((taskId) => !registered.has(taskId)).map((taskId) => ({ taskId })),
430
- orphans_in_registry: Object.entries(registry.ids).filter(([, taskId]) => !active.includes(taskId)).map(([key, taskId]) => ({ key, taskId })),
423
+ orphans_in_registry: Object.entries(registry.ids).filter(([, taskId]) => !validTasks.has(taskId)).map(([key, taskId]) => ({ key, taskId })),
431
424
  duplicate_registry_keys: [...registered.entries()].filter(([, keys]) => keys.length > 1).map(([taskId, keys]) => ({ taskId, keys }))
432
425
  };
433
426
  const clean = Object.values(diff).every((items) => items.length === 0);
@@ -43,6 +43,7 @@ import { manualValidationFinalSummaryProjectionMatches } from "./manual-validati
43
43
  import { readManualValidationCompletion } from "./manual-validation-completion.ts";
44
44
  import { sha256File } from "./artifact-receipts.ts";
45
45
  import { inspectArtifactDirectory } from "./artifact-lifecycle.ts";
46
+ import { getAgentClientAdapter } from "../agent-clients/registry.ts";
46
47
  import { parseArtifactName } from "./artifact-name.ts";
47
48
  import { summaryCommentState } from "../platform/pr-summary.ts";
48
49
  import { taskIssueIdentity } from "../platform/task-identities.ts";
@@ -147,7 +148,7 @@ async function runCheck(type: any, context: any, shared: any): Promise<any> {
147
148
  }
148
149
 
149
150
  function checkOrchestrationState({ taskDir }: any): any {
150
- const file = path.join(taskDir, 'orchestration.json');
151
+ const file = path.join(taskDir, '.runtime', 'orchestration.json');
151
152
  const stat = safeStat(file);
152
153
  if (!stat?.isFile()) return failResult('orchestration-state', 'orchestration.json is missing');
153
154
  let run: OrchestrationRun | null;
@@ -160,16 +161,14 @@ function checkOrchestrationState({ taskDir }: any): any {
160
161
  if (!run || !['paused', 'completed'].includes(run.status)) {
161
162
  return failResult('orchestration-state', `Expected paused or completed run, received '${run?.status ?? 'missing'}'`);
162
163
  }
164
+ const adapterEvidenceError = validateAdapterReceiptEvidence(run);
165
+ if (adapterEvidenceError) return failResult('orchestration-state', adapterEvidenceError);
163
166
  if (run.status === 'paused' && (!run.pause?.code || !run.pause?.message)) {
164
167
  return failResult('orchestration-state', 'Paused run requires a stable pause code and message');
165
168
  }
166
169
  if (run.status === 'completed') {
167
- if (run.completionEvidence != null) {
168
- const evidenceError = validateCleanCompletionEvidence(run);
169
- if (evidenceError) return failResult('orchestration-state', evidenceError);
170
- } else if (run.pendingDelegation !== null || !run.commitAuthorization?.consumedAt) {
171
- return failResult('orchestration-state', 'Completed run must consume commit authorization and clear pending delegation');
172
- }
170
+ const evidenceError = validateCleanCompletionEvidence(run);
171
+ if (evidenceError) return failResult('orchestration-state', evidenceError);
173
172
  } else if (run.completionEvidence != null) {
174
173
  return failResult('orchestration-state', 'Clean completion evidence requires a completed run');
175
174
  } else if (run.pendingDelegation?.status === 'sealed') {
@@ -206,17 +205,26 @@ function validateCleanCompletionEvidence(run: OrchestrationRun): string | null {
206
205
  if (
207
206
  run.status !== 'completed'
208
207
  || run.pendingDelegation !== null
209
- || run.commitAuthorization?.issuedAt !== null
210
- || run.commitAuthorization?.consumedAt !== null
211
208
  || run.receipts.some((receipt) => receipt.stage === 'commit')
212
209
  ) {
213
- return 'Clean completion evidence conflicts with commit delegation state';
210
+ return 'Clean completion evidence conflicts with pending delegation or commit receipt state';
211
+ }
212
+ return null;
213
+ }
214
+
215
+ function validateAdapterReceiptEvidence(run: OrchestrationRun): string | null {
216
+ const receipts = [...run.receipts, ...(run.pendingDelegation ? [run.pendingDelegation] : [])];
217
+ for (const receipt of receipts) {
218
+ const validate = getAgentClientAdapter(receipt.client).orchestrationAdapter?.validateReceiptEvidence;
219
+ if (validate && !validate(receipt)) {
220
+ return `Receipt '${receipt.id}' has invalid evidence from its agent adapter`;
221
+ }
214
222
  }
215
223
  return null;
216
224
  }
217
225
 
218
226
  function checkOrchestrationEvidence({ taskDir }: any): any {
219
- const file = path.join(taskDir, 'orchestration.json');
227
+ const file = path.join(taskDir, '.runtime', 'orchestration.json');
220
228
  const stat = safeStat(file);
221
229
  if (!stat?.isFile()) return failResult('orchestration-evidence', 'orchestration.json is missing');
222
230
  let run: OrchestrationRun | null;
@@ -227,6 +235,8 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
227
235
  return failResult('orchestration-evidence', `Invalid orchestration.json: ${message}`);
228
236
  }
229
237
  if (!run) return failResult('orchestration-evidence', 'orchestration.json is missing');
238
+ const adapterEvidenceError = validateAdapterReceiptEvidence(run);
239
+ if (adapterEvidenceError) return failResult('orchestration-evidence', adapterEvidenceError);
230
240
  // readRun validates the persisted schema, receipt identities and client provenance.
231
241
  const policy = run.modelPolicy;
232
242
  if (run.completionEvidence != null) {
@@ -249,20 +259,22 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
249
259
  }
250
260
  const activated = ['activated', 'stage-completed', 'sealed', 'consumed'].includes(receipt.status);
251
261
  if (activated) {
252
- const isClaudeCode = receipt.client === 'claude-code';
253
- if (!isClaudeCode) {
262
+ const evidencePolicy = getAgentClientAdapter(receipt.client).delegationEvidence;
263
+ if (evidencePolicy.actualModelRequired !== false) {
254
264
  if (!exactText(receipt.actualModel)) {
255
265
  return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has no host-observed actual model`);
256
266
  }
267
+ }
268
+ if (evidencePolicy.actualReasoningEffortRequired !== false) {
257
269
  if (!exactText(receipt.actualReasoningEffort)) {
258
270
  return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has no host-observed actual reasoning effort`);
259
271
  }
260
272
  }
261
- if (!isClaudeCode && receipt.spawnMode !== 'fresh') {
273
+ if (evidencePolicy.spawnModeRequired !== false && receipt.spawnMode !== 'fresh') {
262
274
  return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has invalid fresh delegation identity`);
263
275
  }
264
276
  if (receipt.actualModel != null && receipt.actualModel !== receipt.requestedModel) {
265
- if (!isClaudeCode && !exactText(receipt.modelFallbackReason)) {
277
+ if (evidencePolicy.fallbackReasonRequired !== false && !exactText(receipt.modelFallbackReason)) {
266
278
  return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' model fallback is not justified`);
267
279
  }
268
280
  }
@@ -273,7 +285,7 @@ function checkOrchestrationEvidence({ taskDir }: any): any {
273
285
  return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' has an unrelated model fallback reason`);
274
286
  }
275
287
  if (receipt.actualReasoningEffort != null && receipt.actualReasoningEffort !== receipt.requestedReasoningEffort) {
276
- if (!isClaudeCode && !exactText(receipt.reasoningEffortFallbackReason)) {
288
+ if (evidencePolicy.fallbackReasonRequired !== false && !exactText(receipt.reasoningEffortFallbackReason)) {
277
289
  return failResult('orchestration-evidence', `Receipt '${receipt.id ?? '(unknown)'}' reasoning-effort fallback is not justified`);
278
290
  }
279
291
  }
@@ -790,7 +802,6 @@ function checkActivityLog({ taskDir, config, skillName, artifactFile }: any): an
790
802
  const entries = section.entries;
791
803
  if (entries.length === 0) return failResult("activity-log", "Activity Log has no entries");
792
804
 
793
- let previousTimestamp = "";
794
805
  let latestAction = "";
795
806
  let latestTimestamp = "";
796
807
  const doneActions: string[] = [];
@@ -798,14 +809,8 @@ function checkActivityLog({ taskDir, config, skillName, artifactFile }: any): an
798
809
  for (const entry of entries) {
799
810
  if (!entry.note.trim()) return failResult("activity-log", "Activity Log entry note is required");
800
811
  const { time: timestamp, step: action } = entry;
801
- if (previousTimestamp && timestamp < previousTimestamp) {
802
- return failResult("activity-log", "Activity Log timestamps are not in ascending order");
803
- }
804
-
805
- previousTimestamp = timestamp;
806
- // Ascending order is checked over every entry, but a `[started]` marker is
807
- // not a terminal action: keep latestAction/latestTimestamp on the most
808
- // recent done entry so expected_action_pattern sees it.
812
+ // A `[started]` marker is not a terminal action: keep latestAction/latestTimestamp
813
+ // on the most recent done entry so expected_action_pattern sees it.
809
814
  if (!ACTIVITY_LOG_STARTED_RE.test(action)) {
810
815
  latestTimestamp = timestamp;
811
816
  latestAction = action;
@@ -67,7 +67,7 @@ function taskFileFingerprints(stateRoot: string, taskId: string | null): TaskFil
67
67
  return;
68
68
  }
69
69
  if (!stat.isFile() && !stat.isSymbolicLink()) return;
70
- if (relative.startsWith('.agents/workspace/active/') && path.posix.basename(relative) === 'orchestration.json') return;
70
+ if (/^\.agents\/workspace\/(?:active|blocked|completed)\/TASK-[0-9]{8}-[0-9]{6}\/\.runtime\/orchestration\.json$/u.test(relative)) return;
71
71
  const content = stat.isSymbolicLink()
72
72
  ? Buffer.from(fs.readlinkSync(absolute))
73
73
  : fs.readFileSync(absolute);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fitlab-ai/agent-infra",
3
- "version": "0.11.11",
3
+ "version": "0.11.13",
4
4
  "description": "Bootstrap tool for AI multi-tool collaboration infrastructure — works with Claude Code, Codex, Antigravity CLI, and OpenCode",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -16,28 +16,6 @@ function internalCliInvocation(args) {
16
16
  return { command: 'agent-infra-internal', commandArgs: args, shell: process.platform === 'win32' };
17
17
  }
18
18
 
19
- function findCapabilityRef(value, depth = 0) {
20
- if (depth > 5 || value == null) return '';
21
- if (typeof value === 'string') {
22
- const match = /agent-infra-codex-capability:([A-Za-z0-9_-]{20,})/.exec(value);
23
- return match?.[1] || '';
24
- }
25
- if (Array.isArray(value)) {
26
- for (const entry of value) {
27
- const found = findCapabilityRef(entry, depth + 1);
28
- if (found) return found;
29
- }
30
- return '';
31
- }
32
- if (typeof value === 'object') {
33
- for (const entry of Object.values(value)) {
34
- const found = findCapabilityRef(entry, depth + 1);
35
- if (found) return found;
36
- }
37
- }
38
- return '';
39
- }
40
-
41
19
  let input = '';
42
20
  process.stdin.setEncoding('utf8');
43
21
  process.stdin.on('data', (chunk) => {
@@ -77,11 +55,10 @@ process.stdin.on('end', () => {
77
55
  process.exit(1);
78
56
  }
79
57
  const managedAgent = /^agent-infra-lifecycle-(executor|reviewer)$/.test(String(nativeAgent || ''));
80
- const capabilityRef = hook === 'post-tool' ? findCapabilityRef(toolResponse) : '';
81
58
  const parentReconcile = hook === 'post-tool'
82
59
  && toolName === 'collaborationwait_agent'
83
60
  && toolResponse.timed_out !== true;
84
- if (!managedAgent && !parentReconcile && !capabilityRef) process.exit(0);
61
+ if (!managedAgent && !parentReconcile) process.exit(0);
85
62
  const hookDefinitionHash = createHash('sha256').update(readFileSync(codexHooks)).digest('hex');
86
63
  const normalized = {
87
64
  sessionId: String(event.session_id || ''),
@@ -96,7 +73,6 @@ process.stdin.on('end', () => {
96
73
  taskName: String(toolInput.task_name || ''),
97
74
  transcriptPath: String(event.transcript_path || '')
98
75
  };
99
- if (capabilityRef) normalized.capabilityRef = capabilityRef;
100
76
  const args = ['codex-lifecycle', 'hook-event', '--event', hook, '--bridge', 'true'];
101
77
  try {
102
78
  const { command, commandArgs, shell } = internalCliInvocation(args);
@@ -9,7 +9,7 @@ so you can quickly find "which ones to read" without opening each file.
9
9
  ## General Principles
10
10
 
11
11
  - [`no-mid-flow-questions.md`](no-mid-flow-questions.md) — Question boundaries, error diagnosis order, and autonomous recovery during SKILL runs.
12
- - [`next-step-output.md`](next-step-output.md) — "Next step" output rules: task short-id rendering and the `Completed at` trailer.
12
+ - [`next-step-output.md`](next-step-output.md) — "Next step" output rules: task short-id rendering, pending human decisions, and workflow warnings.
13
13
  - [`version-stamp.md`](version-stamp.md) — How and when to stamp `agent_infra_version`.
14
14
  - [`debugging-guide.md`](debugging-guide.md) — Structured debugging flow: gather evidence → form hypothesis → verify hypothesis → fix the root cause; no blind patch-and-retry.
15
15
  - [`compatibility-policy.md`](compatibility-policy.md) — Compatibility by exception: admission evidence, implementation boundaries, exit conditions, and lifecycle review requirements.
@@ -17,6 +17,7 @@ so you can quickly find "which ones to read" without opening each file.
17
17
  - [`validation-output.md`](validation-output.md) — User-facing validation summaries, failure diagnostics, soft warnings, and consumer boundaries.
18
18
  - [`sync-content-generation.md`](sync-content-generation.md) — Producer-side Markdown constraints for task and lifecycle content synchronized to Issues.
19
19
  - [`decision-qualification.md`](decision-qualification.md) — Constraint, candidate, human-confirmation, and six-artifact qualification-audit contract.
20
+ - [`strategy-pattern.md`](strategy-pattern.md) — Identify shared flows and different behaviors, then use interfaces and the Strategy Pattern to isolate implementation differences.
20
21
 
21
22
  ## Issue / PR
22
23
 
@@ -8,7 +8,7 @@
8
8
  ## 通用准则
9
9
 
10
10
  - [`no-mid-flow-questions.md`](no-mid-flow-questions.md) — SKILL 执行期提问边界、错误诊断顺序和自主恢复原则。
11
- - [`next-step-output.md`](next-step-output.md) — 「下一步」输出规则:任务短号渲染与 `Completed at` 收尾行。
11
+ - [`next-step-output.md`](next-step-output.md) — 「下一步」输出规则:任务短号、人工裁决提示与工作流告警。
12
12
  - [`version-stamp.md`](version-stamp.md) — `agent_infra_version` 版本戳的取值命令与写入时机。
13
13
  - [`debugging-guide.md`](debugging-guide.md) — 结构化调试流程:收集证据→形成假设→验证假设→修复根因,禁止盲目改代码重试。
14
14
  - [`compatibility-policy.md`](compatibility-policy.md) — 兼容性默认关闭:准入证据、实现边界、退出条件与生命周期审查要求。
@@ -16,6 +16,7 @@
16
16
  - [`validation-output.md`](validation-output.md) — 面向用户的校验状态摘要、失败诊断、软警告及消费边界。
17
17
  - [`sync-content-generation.md`](sync-content-generation.md) — 同步到 Issue 的任务和生命周期 Markdown 生成约束。
18
18
  - [`decision-qualification.md`](decision-qualification.md) — 约束、候选、人工确认和六类 artifact 资格审计契约。
19
+ - [`strategy-pattern.md`](strategy-pattern.md) — 识别共用流程和不同行为,通过接口与策略模式隔离实现差异。
19
20
 
20
21
  ## Issue / PR
21
22
 
@@ -1,11 +1,10 @@
1
1
  # Next-Step Output Rule
2
2
 
3
- This file defines four **independent** rules for a skill's "notify-user / Next steps" output (the 3rd applies to review-* only); read this file before rendering the final output and apply whichever rules apply:
3
+ This file defines three **independent** rules for a skill's "notify-user / Next steps" output (the 2nd applies to review-* only); read this file before rendering the final output and apply whichever rules apply:
4
4
 
5
5
  1. **Next-step output structure**: how "Next steps" commands and the "Task info" block present the task ID (placeholders / short-id lookup / fallback).
6
- 2. **Agent output trailing line (Completed at)**: the **very last line** of user-facing output, **independent of the "Next steps" block**, applying to normal / error / early-return paths alike.
7
- 3. **Pending human-decision pre-block**: applies only to `review-analysis` / `review-plan` / `review-code` when this stage has pending rulings (`{h} > 0`) — expand the pending items before the "Next steps" commands and prompt to resolve them first.
8
- 4. **Workflow Warnings output block**: applies when task.md has `status=open` rows in `## Workflow Warnings` — print the warning summary after all normal information and "Next steps" commands, and before `Completed at`.
6
+ 2. **Pending human-decision pre-block**: applies only to `review-analysis` / `review-plan` / `review-code` when this stage has pending rulings (`{h} > 0`) — expand the pending items before the "Next steps" commands and prompt to resolve them first.
7
+ 3. **Workflow Warnings output block**: applies when task.md has `status=open` rows in `## Workflow Warnings` — print the warning summary after all normal information and "Next steps" commands.
9
8
 
10
9
  ## Placeholder semantics
11
10
 
@@ -34,7 +33,7 @@ agent-infra-internal agent-client next-steps \
34
33
  - Pass the next skill name for the already-selected scenario. Include `--task-ref` only when that command needs a task reference.
35
34
  - Include `--version` only for a versioned release command. It must start with a digit and satisfy `semver.valid(raw) === raw`; never pass an action, complete command, shell fragment, whitespace, or a `v` / `V` / `=` prefix.
36
35
  - The helper lists enabled built-in clients, followed by selected sandbox custom tools with a valid `invoke` field in `sandbox.tools.ids` order.
37
- - Insert non-empty stdout verbatim at `{next-step-commands}` below the current "Next steps" heading. When stdout is empty, omit the client command block while still rendering reminders, warnings, and `Completed at`.
36
+ - Insert non-empty stdout verbatim at `{next-step-commands}` below the current "Next steps" heading. When stdout is empty, omit the client command block while still rendering reminders and warnings.
38
37
  - If the helper writes stderr or exits non-zero, use the current skill's error path and stop; never fall back to a hard-coded client table.
39
38
  - A complex skill selects one scenario first and invokes the helper once for that scenario; it must not pre-render every branch.
40
39
 
@@ -69,21 +68,9 @@ process.stdout.write(hit?hit[0]:full);
69
68
 
70
69
  Short ids are always rendered as zero-padded bare digits `NN`. The removed `#NN` syntax must neither be generated nor accepted, avoiding bash comment interpretation.
71
70
 
72
- ## Agent output trailing line (Completed at)
73
-
74
- This section is a standalone rule, **co-equal with the next-step output structure** and **not part of the "Next steps" block**. Every skill that renders user-facing output must append the completion-time line as the **very last line** of that output — including **complete-task, which renders no next-step commands**, and **error / early-return paths** where a precondition is unmet. This lets users scanning across tmux windows tell at a glance which agent finished most recently:
75
-
76
- ```text
77
- Completed at: YYYY-MM-DD HH:mm:ss
78
- ```
79
-
80
- - Value command (local timezone, no offset): `date "+%Y-%m-%d %H:%M:%S"`
81
- - Position: it must be the last line of the entire user-facing output, after all "Next steps" commands. If a scenario has a conditional reminder line after the commands (e.g. the manual-validation reminder), the completion line goes after that reminder.
82
- - This line is for terminal scanning only; it is never written to any artifact file or Issue/PR comment. The single source of truth for completion time remains the Activity Log in task.md.
83
-
84
71
  ## Workflow Warnings Output Block
85
72
 
86
- If the current task's `## Workflow Warnings` / `## 工作流告警` table has any `status=open` row, the skill's final user output must append a summary block after all normal information and "Next steps" commands, and before `Completed at`. When no open warning exists, do not render this block.
73
+ If the current task's `## Workflow Warnings` / `## 工作流告警` table has any `status=open` row, the skill's final user output must append a summary block after all normal information and "Next steps" commands. When no open warning exists, do not render this block.
87
74
 
88
75
  Format:
89
76
 
@@ -96,7 +83,7 @@ Use `[ACTION REQUIRED]` when any open row has `severity=ACTION_REQUIRED`; otherw
96
83
 
97
84
  ## Pending human-decision pre-block (review-* only, when {h} > 0)
98
85
 
99
- This section is a **third standalone rule, co-equal with the two above**, used only by the "notify-user / report conclusion" step of `review-analysis` / `review-plan` / `review-code`.
86
+ This section is the **second independent rule**, used only by the "notify-user / report conclusion" step of `review-analysis` / `review-plan` / `review-code`.
100
87
 
101
88
  `{h}` has the same meaning as the count line in each review skill's `reference/output-templates.md`: the number of rows in task.md `## 审查分歧账本` (Review Disagreement Ledger) for **this stage** (`stage ∈ {analysis|plan|code}`) whose `status = needs-human-decision` — **pending items only, excluding rows already `human-decided`**.
102
89
 
@@ -1,11 +1,10 @@
1
1
  # 下一步输出规则
2
2
 
3
- 本文件定义 skill「告知用户 / 下一步」输出的四类**相互独立**的规则(第 3 类仅 review-* 适用);渲染最终输出前先读取本文件并落实其中适用的规则:
3
+ 本文件定义 skill「告知用户 / 下一步」输出的三类**相互独立**的规则(第 2 类仅 review-* 适用);渲染最终输出前先读取本文件并落实其中适用的规则:
4
4
 
5
5
  1. **下一步输出结构**:「下一步」命令与「任务信息」段如何呈现任务 ID 形态(占位符 / 取短号 / 回退)。
6
- 2. **Agent 输出收尾行(Completed at)**:面向用户输出的**绝对最后一行**,**独立于「下一步」块**,正常 / 错误 / 早退路径都适用。
7
- 3. **人工裁决待办前置块**:仅 `review-analysis` / `review-plan` / `review-code`,且本阶段存在待裁决项(`{h} > 0`)时适用——在「下一步」命令前展开待裁决项并提示先完成裁决。
8
- 4. **Workflow Warnings 输出块**:当前 task.md 存在 `status=open` 的 `## 工作流告警` 行时适用——在所有常规信息和「下一步」命令之后、`Completed at` 之前输出告警摘要。
6
+ 2. **人工裁决待办前置块**:仅 `review-analysis` / `review-plan` / `review-code`,且本阶段存在待裁决项(`{h} > 0`)时适用——在「下一步」命令前展开待裁决项并提示先完成裁决。
7
+ 3. **Workflow Warnings 输出块**:当前 task.md 存在 `status=open` 的 `## 工作流告警` 行时适用——在所有常规信息和「下一步」命令之后输出告警摘要。
9
8
 
10
9
  ## 占位符语义
11
10
 
@@ -34,7 +33,7 @@ agent-infra-internal agent-client next-steps \
34
33
  - 静态传入当前已选场景的下一 skill 名;只有命令需要任务引用时才传 `--task-ref`。
35
34
  - 只有版本化发布命令需要版本时才传 `--version`;值必须是以数字开头且 `semver.valid(raw) === raw` 的规范 SemVer。不得传 action、完整命令、shell 片段、空白或 `v` / `V` / `=` 前缀。
36
35
  - helper 展示 `.agents/.airc.json` 中 `agentClients` 已启用的内建客户端,并按 `sandbox.tools.ids` 顺序追加具有有效 `invoke` 的已选自定义沙箱工具。
37
- - 将非空 stdout 原样放入当前“下一步”标题后的 `{next-step-commands}`;stdout 为空时省略整个客户端命令块,但继续输出提醒、告警和 `Completed at`。
36
+ - 将非空 stdout 原样放入当前“下一步”标题后的 `{next-step-commands}`;stdout 为空时省略整个客户端命令块,但继续输出提醒和告警。
38
37
  - helper 写 stderr 或返回非零时,按当前 skill 的错误路径报告并停止;禁止回退到硬编码客户端表。
39
38
  - 复杂 skill 先选定唯一场景,再只为该场景调用一次 helper,不得提前渲染所有分支。
40
39
 
@@ -69,21 +68,9 @@ process.stdout.write(hit?hit[0]:full);
69
68
 
70
69
  短号统一渲染为零填充裸数字 `NN`。已移除的 `#NN` 语法不得生成或接受,避免 bash 将其解释为注释。
71
70
 
72
- ## Agent 输出收尾行(Completed at)
73
-
74
- 本节是与「下一步输出结构」**并列的独立规则**,不隶属于「下一步」块。任何向用户渲染输出的 skill 都必须在面向用户输出的**绝对最后一行**追加完成时间收尾行——包括**声明「不渲染下一步命令」的 complete-task**,以及前置条件未满足而提前 return 的**错误 / 早退路径**。便于用户在 tmux 多窗口扫视时一眼判断各 Agent 的完成先后:
75
-
76
- ```text
77
- Completed at: YYYY-MM-DD HH:mm:ss
78
- ```
79
-
80
- - 取值命令(本地时区、不带偏移):`date "+%Y-%m-%d %H:%M:%S"`
81
- - 位置:必须是整段面向用户输出的最后一行,排在所有「下一步」命令之后。若某场景在命令之后还有条件性提醒行(如 manual-validation 提醒),收尾行排在该提醒行之后。
82
- - 该行只用于终端扫视,不写入任何产物文件或 Issue/PR 评论;完成时刻的单一事实源仍是 task.md 的 Activity Log。
83
-
84
71
  ## Workflow Warnings 输出块
85
72
 
86
- 若当前任务的 `## 工作流告警` / `## Workflow Warnings` 表中存在 `status=open` 行,skill 最终输出必须在所有常规信息和「下一步」命令之后、`Completed at` 之前追加摘要块;无 open 告警时不输出本块。
73
+ 若当前任务的 `## 工作流告警` / `## Workflow Warnings` 表中存在 `status=open` 行,skill 最终输出必须在所有常规信息和「下一步」命令之后追加摘要块;无 open 告警时不输出本块。
87
74
 
88
75
  格式:
89
76
 
@@ -96,7 +83,7 @@ Completed at: YYYY-MM-DD HH:mm:ss
96
83
 
97
84
  ## 人工裁决待办前置块(review-* 专用,{h} > 0 时)
98
85
 
99
- 本节是与上面两类规则**并列的第三类独立规则**,仅 `review-analysis` / `review-plan` / `review-code` 的「向用户汇报结论」步骤使用。
86
+ 本节是与下一步输出结构并列的第二类规则,仅 `review-analysis` / `review-plan` / `review-code` 的「向用户汇报结论」步骤使用。
100
87
 
101
88
  `{h}` 含义与各 review 技能 `reference/output-templates.md` 计数行一致:task.md `## 审查分歧账本` 中**本阶段**(`stage ∈ {analysis|plan|code}`)`status = needs-human-decision` 的行数——**只含待裁决项,不含已 `human-decided`**。
102
89
 
@@ -0,0 +1,25 @@
1
+ # Strategy Pattern
2
+
3
+ This rule applies during technical design and implementation. When behavior differs by platform, client, protocol, workflow, business rule, or algorithm, use interfaces to isolate implementations so shared flows can reuse them instead of creating a dedicated path for every scenario.
4
+
5
+ ## Design Steps
6
+
7
+ 1. **Separate shared flows from different implementations**: identify common steps and the behavior that differs by platform, client, protocol, workflow, business rule, algorithm, or lifecycle.
8
+ 2. **Define the interface contract**: specify the inputs, outputs, error semantics, and invocation timing shared by implementations. The common flow depends on the interface, not a concrete implementation.
9
+ 3. **Apply the Strategy Pattern**: provide an implementation for each behavior and select a strategy at registration or assembly. The common flow orchestrates shared steps without spreading conditions that identify concrete implementations.
10
+ 4. **Verify substitutability**: a new implementation should connect through registration or assembly. Write shared tests for the interface contract and the necessary tests for strategy selection and full assembly.
11
+
12
+ ## Implementation Requirements
13
+
14
+ - When the requirement clearly includes multiple behaviors, prefer the Strategy Pattern even if there is currently one implementation; the interface should express their stable shared contract.
15
+ - When adding behavior, prefer adding a strategy implementation and registering or assembling it in one place. Do not copy a full business flow or repeat the same branch across callers.
16
+ - If branches that identify implementations are already scattered across paths, consolidate those decisions at strategy selection and make callers depend on the interface.
17
+ - Reuse existing interfaces and domain terms. Do not rename code to apply a pattern or add interfaces to stable behavior that has no meaningful alternatives.
18
+ - Keep stable, single-purpose behavior direct when there is no evidence it will vary. Do not mechanically add interfaces, configuration, or extension points.
19
+
20
+ ## Design Review Questions
21
+
22
+ - Which steps are shared by all implementations, and which behaviors differ by platform, client, or business rule?
23
+ - Does the interface express the contract shared by the implementations?
24
+ - Can a new behavior be added by implementing a strategy and assembling it in one place? If several call paths must change, is centralized strategy selection missing?
25
+ - Can the same interface tests validate the shared contract while strategy differences are tested independently?
@@ -0,0 +1,25 @@
1
+ # 策略模式
2
+
3
+ 本规则适用于方案设计和代码实现。当平台、客户端、协议、工作流、业务规则或算法存在多种行为时,用接口隔离这些实现,让共用流程能够复用不同实现,避免每种场景都形成一条专用链路。
4
+
5
+ ## 设计步骤
6
+
7
+ 1. **区分共用流程和不同实现**:找出所有实现共同执行的步骤,以及会因平台、客户端、协议、工作流、业务规则、算法或生命周期而不同的行为。
8
+ 2. **定义接口契约**:明确不同实现的输入、输出、错误语义和调用时机。共用流程依赖接口,不直接调用某个具体实现。
9
+ 3. **应用策略模式**:为每种行为提供独立实现,在注册或装配处选择策略。共用流程负责编排共同步骤,不散布识别具体实现的条件分支。
10
+ 4. **验证实现可替换**:新增实现应能通过注册或装配接入;为接口约定编写共享测试,并为策略选择和整体装配编写必要测试。
11
+
12
+ ## 实现要求
13
+
14
+ - 需求明确包含多种行为时,优先使用策略模式,即使当前只有一个实现;接口应表达这些实现之间稳定的共同约定。
15
+ - 新增行为时,优先增加一个策略实现并在统一位置注册或装配,不复制完整业务链,也不在多个调用方重复增加同类分支。
16
+ - 如果识别不同实现的分支已经散落在多条路径,先把这些判断收敛到策略选择处,再让调用方依赖接口。
17
+ - 复用现有接口和领域词汇;不要为了套用模式而改名,也不要为没有不同行为的稳定逻辑增加接口。
18
+ - 对稳定、单一且没有证据表明会变化的逻辑,保持直接实现,不机械添加接口、配置或扩展点。
19
+
20
+ ## 设计审查问题
21
+
22
+ - 所有实现共用哪些流程?哪些行为因平台、客户端或业务规则而不同?
23
+ - 接口是否表达了不同实现共同遵守的约定?
24
+ - 新实现能否只新增策略并在一个位置装配?如果必须修改多处调用链,是否缺少统一的策略选择?
25
+ - 共用约定能否通过同一套接口测试验证,不同策略的差异能否独立验证?
@@ -120,7 +120,7 @@ Runs after Step 0 state check and Step 3 (questioning is an external-state actio
120
120
  2. If `start_date` is empty, write today (`date +%F`), then run `agent-infra-internal task-event {task-id} analyze.awaiting-input --agent {standard-agent-token} --question {question_count}` so the core updates base metadata and Activity Log.
121
121
  3. Issue sync (when a valid `platform_issue_identity` exists, skip on any failure): run `agent-infra-internal platform-comment sync {task-id} --kind task --agent {standard-agent-token}` to update only the **task comment**; keep the `status` label at `pending-design-work`; do **not** publish an analysis artifact comment.
122
122
  4. Verification (replaces the step 8 artifact gate): `agent-infra-internal task-verify {task-id} analyze.awaiting-input --format text` (the early-exit set `current_step: requirement-analysis` and wrote `start_date`, so it should pass); also keep `rg -n 'Analyze Task \(Brainstorming\)' .agents/workspace/active/{task-id}/task.md` and the task-comment sync evidence. Do **not** run the artifact gate, nor `check activity-log` / `check platform-sync` (both bind to the analysis artifact path).
123
- 5. User output: show only the current **single question** plus how to answer/continue (re-trigger `analyze-task {task-ref}` with the answer), and append the `Completed at` line per `.agents/rules/next-step-output.md`.
123
+ 5. User output: show only the current **single question** plus how to answer/continue (re-trigger `analyze-task {task-ref}` with the answer).
124
124
  6. **STOP** and wait for the answer. The next trigger returns to this step.
125
125
 
126
126
  ### 5. Perform Requirements Analysis
@@ -123,7 +123,7 @@ agent-infra-internal task-event {task-id} analyze.started --agent {standard-agen
123
123
  2. 若 `start_date` 为空,写入当日日期(`date +%F`);随后执行 `agent-infra-internal task-event {task-id} analyze.awaiting-input --agent {standard-agent-token} --question {question_count}`,由核心统一更新基础 frontmatter 和 Activity Log。
124
124
  3. Issue 同步(存在有效 `platform_issue_identity` 时,任一失败跳过):调用 `agent-infra-internal platform-comment sync {task-id} --kind task --agent {standard-agent-token}` 更新 **task 评论**;`status` label 维持 `pending-design-work`;**不**发布分析产物评论。
125
125
  4. 校验(替代步骤 8 的 artifact gate):`agent-infra-internal task-verify {task-id} analyze.awaiting-input --format text`(早退已置 `current_step: requirement-analysis` 且已写入 `start_date`,预期通过);并保留 `rg -n 'Analyze Task \(Brainstorming\)' .agents/workspace/active/{task-id}/task.md` 与 task 评论同步证据。**不**跑 artifact gate,也不跑 `check activity-log` / `check platform-sync`(二者绑定分析产物路径)。
126
- 5. 用户输出:只展示当前**单个问题** + 如何回答/继续(再次触发 `analyze-task {task-ref}` 并附答案),并按 `.agents/rules/next-step-output.md` 在末行追加 `Completed at`。
126
+ 5. 用户输出:只展示当前**单个问题** + 如何回答/继续(再次触发 `analyze-task {task-ref}` 并附答案)。
127
127
  6. **STOP**,等待回答。下一次触发回到本步骤。
128
128
 
129
129
  ### 5. 执行需求分析
@@ -70,7 +70,7 @@ When the action is non-empty, perform only this literal action-to-skill conversi
70
70
  | `code` | `code-task` |
71
71
  | any other action | same-named skill |
72
72
 
73
- When the action is empty, do not invoke the helper; state that no next skill is currently recommended. Before rendering the final output, read `.agents/rules/next-step-output.md`; invoke `agent-infra-internal agent-client next-steps --skill {next-skill} --task-ref {task-ref}` and render stdout verbatim as `{next-step-commands}`. Append the `Completed at` trailing line last.
73
+ When the action is empty, do not invoke the helper; state that no next skill is currently recommended. Before rendering the final output, read `.agents/rules/next-step-output.md`; invoke `agent-infra-internal agent-client next-steps --skill {next-skill} --task-ref {task-ref}` and render stdout verbatim as `{next-step-commands}`.
74
74
 
75
75
  ## Notes
76
76
 
@@ -70,7 +70,7 @@ ai task status --task {task-id}
70
70
  | `code` | `code-task` |
71
71
  | 其他 action | 同名 skill |
72
72
 
73
- `action` 为空时不调用 helper,只说明当前没有可推荐的下一技能。渲染最终输出前先读取 `.agents/rules/next-step-output.md`;helper 命令为 `agent-infra-internal agent-client next-steps --skill {next-skill} --task-ref {task-ref}`,stdout 原样作为 `{next-step-commands}` 下一步命令。最后追加 `Completed at` 收尾行。
73
+ `action` 为空时不调用 helper,只说明当前没有可推荐的下一技能。渲染最终输出前先读取 `.agents/rules/next-step-output.md`;helper 命令为 `agent-infra-internal agent-client next-steps --skill {next-skill} --task-ref {task-ref}`,stdout 原样作为 `{next-step-commands}` 下一步命令。
74
74
 
75
75
  ## 注意事项
76
76
 
@@ -187,4 +187,4 @@ Present the verification summary per `.agents/rules/validation-output.md`, retai
187
187
 
188
188
  Use `reference/output-template.md` (or `reference/fix-mode.md` in fix mode) and render the selected next-step commands through the shared helper. The local checkpoint is required for this skill; do not invoke the standalone `commit` skill, push, or create a PR here.
189
189
 
190
- > Before rendering the final output, read `.agents/rules/next-step-output.md` and apply both of its rules: (1) render `{task-ref}` in the "Next steps" commands as the current task's short id `NN` (see that file for lookup and fallback), while other `{task-id}` placeholders (report titles, paths) keep the full TASK-id form; (2) append the `Completed at` line as the very last line of the user-facing output (this applies to every user-facing output — success, error, and early-return paths alike, not only the success path).
190
+ > Before rendering the final output, read `.agents/rules/next-step-output.md`; render `{task-ref}` in the "Next steps" commands as the current task's short id `NN` (see that file for lookup and fallback), while other `{task-id}` placeholders (report titles, paths) keep the full TASK-id form.
@@ -241,7 +241,7 @@ agent-infra-internal task-verify {task-id} code.completed --artifact {code-artif
241
241
 
242
242
  > 渲染下一步前先读取 `.agents/rules/next-step-output.md`,并按 `reference/output-template.md` 或 `reference/fix-mode.md` 的已选场景调用统一 helper。
243
243
 
244
- > 渲染最终输出前先读取 `.agents/rules/next-step-output.md` 并落实其两类规则:(1) 「下一步」命令的 `{task-ref}` 渲染为当前任务短号 `NN`(取值与回退见该文件),其他 `{task-id}` 占位(报告标题、路径)保持完整 TASK-id 形式;(2) 在面向用户输出的绝对最后一行追加 `Completed at` 收尾行(成功、错误、早退等任何面向用户输出都适用,不限于校验通过的成功态)。
244
+ > 渲染最终输出前先读取 `.agents/rules/next-step-output.md`,并将「下一步」命令的 `{task-ref}` 渲染为当前任务短号 `NN`(取值与回退见该文件);其他 `{task-id}` 占位(报告标题、路径)保持完整 TASK-id 形式。
245
245
 
246
246
  ## 完成检查清单
247
247