@fitlab-ai/agent-infra 0.11.3 → 0.11.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 (102) hide show
  1. package/README.md +17 -8
  2. package/README.zh-CN.md +17 -8
  3. package/dist/bin/internal-cli.js +1 -1
  4. package/dist/lib/internal/cli-route-inventory.js +1 -1
  5. package/dist/lib/internal/task-artifact.js +1 -1
  6. package/dist/lib/internal/task-finalization.js +41 -1
  7. package/dist/lib/internal/task-ledger.js +13 -5
  8. package/dist/lib/internal/task-operation-registry.js +1 -0
  9. package/dist/lib/platform/completion-backfill.js +63 -17
  10. package/dist/lib/sandbox/control/server.js +26 -143
  11. package/dist/lib/sandbox/task-cleanup.js +13 -20
  12. package/dist/lib/task/artifact-command.js +11 -4
  13. package/dist/lib/task/artifact-lifecycle.js +217 -85
  14. package/dist/lib/task/artifact-operations.js +1 -26
  15. package/dist/lib/task/artifact-receipts.js +4 -1
  16. package/dist/lib/task/artifact-schema.js +1 -0
  17. package/dist/lib/task/artifact-selection.js +25 -0
  18. package/dist/lib/task/capabilities.js +158 -39
  19. package/dist/lib/task/commands/status.js +9 -2
  20. package/dist/lib/task/control-authority.js +3 -2
  21. package/dist/lib/task/decision-intents.js +12 -0
  22. package/dist/lib/task/delivery.js +1 -43
  23. package/dist/lib/task/events.js +80 -64
  24. package/dist/lib/task/finalization.js +301 -143
  25. package/dist/lib/task/ledger-intents.js +157 -15
  26. package/dist/lib/task/lifecycle-path.js +75 -0
  27. package/dist/lib/task/lifecycle.js +10 -0
  28. package/dist/lib/task/local-artifact-finalization.js +1 -20
  29. package/dist/lib/task/orchestration.js +17 -7
  30. package/dist/lib/task/review-finalization.js +3 -16
  31. package/dist/lib/task/rework-intent.js +70 -4
  32. package/dist/lib/task/verification-engine.js +20 -30
  33. package/dist/lib/task/verification.js +1 -0
  34. package/dist/lib/task/workflow-warning-intents.js +21 -6
  35. package/dist/lib/task/workspace-snapshot.js +7 -2
  36. package/dist/lifecycle-build-manifest.json +3 -3
  37. package/lib/internal/cli-route-inventory.ts +1 -1
  38. package/lib/internal/task-artifact.ts +1 -1
  39. package/lib/internal/task-finalization.ts +40 -1
  40. package/lib/internal/task-ledger.ts +12 -5
  41. package/lib/internal/task-operation-registry.ts +1 -0
  42. package/lib/platform/completion-backfill.ts +78 -16
  43. package/lib/sandbox/control/server.ts +21 -146
  44. package/lib/sandbox/task-cleanup.ts +12 -20
  45. package/lib/task/artifact-command.ts +14 -4
  46. package/lib/task/artifact-lifecycle.ts +236 -85
  47. package/lib/task/artifact-operations.ts +1 -26
  48. package/lib/task/artifact-receipts.ts +4 -1
  49. package/lib/task/artifact-schema.ts +1 -0
  50. package/lib/task/artifact-selection.ts +53 -0
  51. package/lib/task/capabilities.ts +154 -40
  52. package/lib/task/commands/status.ts +11 -2
  53. package/lib/task/control-authority.ts +3 -1
  54. package/lib/task/decision-intents.ts +9 -0
  55. package/lib/task/delivery.ts +1 -40
  56. package/lib/task/events.ts +82 -64
  57. package/lib/task/finalization.ts +269 -111
  58. package/lib/task/ledger-intents.ts +155 -15
  59. package/lib/task/lifecycle-path.ts +98 -0
  60. package/lib/task/lifecycle.ts +13 -0
  61. package/lib/task/local-artifact-finalization.ts +2 -25
  62. package/lib/task/orchestration.ts +26 -8
  63. package/lib/task/review-finalization.ts +3 -16
  64. package/lib/task/rework-intent.ts +75 -5
  65. package/lib/task/verification-engine.ts +19 -30
  66. package/lib/task/verification.ts +2 -1
  67. package/lib/task/workflow-warning-intents.ts +18 -6
  68. package/lib/task/workspace-snapshot.ts +3 -2
  69. package/package.json +1 -1
  70. package/templates/.agents/rules/review-handshake.en.md +4 -4
  71. package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
  72. package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -4
  73. package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -4
  74. package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
  75. package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
  76. package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
  77. package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
  78. package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
  79. package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
  80. package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
  81. package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
  82. package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
  83. package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
  84. package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
  85. package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
  86. package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
  87. package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
  88. package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
  89. package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
  90. package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
  91. package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
  92. package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
  93. package/templates/.agents/templates/task.en.md +2 -2
  94. package/templates/.agents/templates/task.zh-CN.md +2 -2
  95. package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
  96. package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
  97. package/templates/.agents/workflows/feature-development.en.yaml +11 -6
  98. package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
  99. package/templates/.agents/workflows/refactoring.en.yaml +11 -6
  100. package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
  101. package/dist/lib/sandbox/control/finalization-response.js +0 -33
  102. package/lib/sandbox/control/finalization-response.ts +0 -35
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <strong>From issue to merged PR in 11 commands.</strong> Define a requirement, let AI handle analysis, planning, coding, and three-stage review — you only step in when it matters.
12
+ <strong>From issue to merged PR through a risk-selected lifecycle.</strong> Every task starts with analysis, and every implementation receives an independent code review.
13
13
  </p>
14
14
 
15
15
  <p align="center">
@@ -51,14 +51,22 @@ Once initialized, open the project in your AI TUI and install the latest skills:
51
51
 
52
52
  > AI reads `.agents/.airc.json`, auto-locates the installed template root, and syncs the latest skill manifests, managed files, and registry deterministically via `sync-templates.js`.
53
53
 
54
- **Scenario**: Issue #42 reports *"Login API returns 500 when email contains a plus sign"*. Here is the full fix lifecycle — AI does the heavy lifting, you stay in control:
54
+ **Scenario**: Issue #42 reports *"Login API returns 500 when email contains a plus sign"*. Every task starts with analysis, which records one canonical path from observable facts:
55
+
56
+ | Path | Stages | Use when |
57
+ |------|--------|----------|
58
+ | Streamlined | `analyze → code → review-code` | Scope and acceptance are clear, with no separate design or document-audit decision |
59
+ | Standard | `analyze → plan → code → review-code` | The implementation needs an explicit cross-module contract, data flow, or test strategy |
60
+ | Full | `analyze → review-analysis → plan → review-plan → code → review-code` | Concrete acceptance disputes, costly interface/schema/migration choices, or real external/security boundaries require independent review |
61
+
62
+ File count, module count, or a merely possible risk does not select the full path. A later finding can return work to analysis, planning, or code when new evidence justifies it; independent `review-code` remains part of every path.
63
+
64
+ Suppose the analysis selects the **standard path** because the application-layer boundary needs an explicit plan:
55
65
 
56
66
  ```bash
57
67
  /import-issue 42 # AI reads the issue, creates a task, extracts requirements
58
- /analyze-task --task <task-id> # AI scans the codebase, finds the root cause, writes analysis.md
59
- /review-analysis --task <task-id> # An isolated reviewer checks the analysis
68
+ /analyze-task --task <task-id> # AI scans the codebase, records the selected path, writes analysis.md
60
69
  /plan-task --task <task-id> # AI proposes a fix plan
61
- /review-plan --task <task-id> # A fresh isolated reviewer checks the plan
62
70
  ```
63
71
 
64
72
  > **You review the plan and reply in natural language:**
@@ -79,7 +87,7 @@ Just fix it at the application layer in LoginService.
79
87
  /complete-task --task <task-id> # task archived
80
88
  ```
81
89
 
82
- **11 commands. 1 natural-language correction. From issue to merged PR.** That is the entire SOP — programming can have a standard operating procedure too.
90
+ **One analysis-selected path, one natural-language correction, and an independent code review.** The lifecycle keeps the evidence and review depth proportional to the task.
83
91
 
84
92
  Every command above works the same way in Claude Code, Codex, Antigravity CLI, and OpenCode. Switch tools mid-task — the workflow state follows. For what each skill does under the hood, see [Built-in AI Skills](./docs/en/skills.md).
85
93
 
@@ -193,8 +201,9 @@ The most-used lifecycle commands, in delivery order. The command prefix varies b
193
201
  |---------|---------|
194
202
  | `create-task` / `import-issue` | Start a task from a description or a GitHub Issue |
195
203
  | `run-task` | Resume the lifecycle with fresh isolated executors/reviewers when the selected client exposes verified actual model/effort evidence; Codex now has an experimental Hooks + App Server evidence channel, but orchestration remains disabled until that channel is connected to delegation receipts |
196
- | `analyze-task` → `review-analysis` | Capture scope and risks, then review the analysis |
197
- | `plan-task` → `review-plan` | Design the approach, then review the plan |
204
+ | `analyze-task` | Capture scope and risks, then select the streamlined, standard, or full path |
205
+ | `review-analysis` | Review the analysis when the selected full path requires an independent requirement checkpoint |
206
+ | `plan-task` / `review-plan` | Design the approach on standard/full paths; independently review it only on the full path |
198
207
  | `code-task` → `review-code` | Implement and test; `code-task` creates a local checkpoint, then a structured review checks that checkpoint |
199
208
  | `create-pr` → `complete-task` | Publish the approved checkpoint to the task-bound target branch, then archive after merge and final gates |
200
209
 
package/README.zh-CN.md CHANGED
@@ -9,7 +9,7 @@
9
9
  </p>
10
10
 
11
11
  <p align="center">
12
- <strong>从 Issue 到合并 PR,只需 11 条命令。</strong> 定义需求,让 AI 完成分析、方案设计、编码与三阶段审查 —— 你只需在关键节点介入。
12
+ <strong>从 Issue 到合并 PR,由风险选择相称的生命周期。</strong> 每个任务都先分析,每份实现都接受独立代码审查。
13
13
  </p>
14
14
 
15
15
  <p align="center">
@@ -50,14 +50,22 @@ agent-infra 的目标就是把这层共享基础设施标准化。它为所有
50
50
 
51
51
  > AI 读取 `.agents/.airc.json`,自动定位已安装的模板根目录,并通过 `sync-templates.js` 确定性地同步最新的 skill 清单、managed 文件和注册表。
52
52
 
53
- **场景**:Issue #42 报告 *"登录接口在邮箱包含加号时返回 500"*。以下是完整的修复流程 —— AI 执行主要工作,你掌控方向:
53
+ **场景**:Issue #42 报告 *"登录接口在邮箱包含加号时返回 500"*。每个任务都从分析开始,并由分析根据可观察事实记录一条规范路径:
54
+
55
+ | 路径 | 阶段 | 适用条件 |
56
+ |------|------|----------|
57
+ | 精简路径 | `analyze → code → review-code` | 范围和验收明确,不需要独立设计或文档审计决策 |
58
+ | 标准路径 | `analyze → plan → code → review-code` | 实现需要明确跨模块契约、数据流或测试策略 |
59
+ | 完整路径 | `analyze → review-analysis → plan → review-plan → code → review-code` | 存在具体的验收争议、高代价接口/schema/迁移选择,或真实外部/安全边界,需要独立审查 |
60
+
61
+ 文件数量、模块数量或可能存在的风险不能单独触发完整路径。后续 finding 只有在新证据成立时才会把工作返回分析、方案或实现阶段;每条路径都保留独立 `review-code`。
62
+
63
+ 假设分析认为应用层边界需要明确方案,因此选择**标准路径**:
54
64
 
55
65
  ```bash
56
66
  /import-issue 42 # AI 读取 Issue,创建任务,提取需求
57
- /analyze-task --task <task-id> # AI 扫描代码库,定位根因,输出 analysis.md
58
- /review-analysis --task <task-id> # 隔离 reviewer 审查分析产物
67
+ /analyze-task --task <task-id> # AI 扫描代码库、记录所选路径,输出 analysis.md
59
68
  /plan-task --task <task-id> # AI 提出修复方案
60
- /review-plan --task <task-id> # fresh 隔离 reviewer 审查方案
61
69
  ```
62
70
 
63
71
  > **你审查方案后用自然语言回复:**
@@ -78,7 +86,7 @@ agent-infra 的目标就是把这层共享基础设施标准化。它为所有
78
86
  /complete-task --task <task-id> # 任务归档
79
87
  ```
80
88
 
81
- **11 条命令,1 次自然语言纠正,从 Issue 到合并 PR。** 这就是完整的 SOP —— 编程也可以有标准作业流程。
89
+ **一条由分析选择的路径、一次自然语言纠正和一次独立代码审查。** 生命周期会让证据与审查深度和任务风险相称。
82
90
 
83
91
  以上每条命令在 Claude Code、Codex、Antigravity CLI、OpenCode 中完全通用。任务进行到一半切换工具,工作流状态照常延续。每个 skill 背后做了什么,见 [内置 AI Skills](./docs/zh-CN/skills.md)。
84
92
 
@@ -192,8 +200,9 @@ ai agent-client configure
192
200
  |------|------|
193
201
  | `create-task` / `import-issue` | 从描述或 GitHub Issue 创建任务 |
194
202
  | `run-task` | 当所选客户端能提供已验证的 actual model/effort 证据时,用 fresh 隔离 executor/reviewer 续跑生命周期;Codex 现已具备实验性的 Hooks + App Server 证据通道,但在该通道接入 delegation receipt 前仍不会启用 orchestration |
195
- | `analyze-task` → `review-analysis` | 明确范围与风险,再审查分析 |
196
- | `plan-task` → `review-plan` | 设计实现路径,再审查方案 |
203
+ | `analyze-task` | 明确范围与风险,再选择精简、标准或完整路径 |
204
+ | `review-analysis` | 仅在完整路径需要独立需求检查点时审查分析 |
205
+ | `plan-task` / `review-plan` | 标准和完整路径需要设计方案;只有完整路径独立审查方案 |
197
206
  | `code-task` → `review-code` | 实现并测试;`code-task` 创建本地 checkpoint,再由结构化审查检视该 checkpoint |
198
207
  | `create-pr` → `complete-task` | 将已通过审查的 checkpoint 发布到任务绑定目标分支,并在合并和最终门禁通过后归档 |
199
208
 
@@ -50,7 +50,7 @@ function taskControlTransportFailure(message, code = 'TASK_CONTROL_TRANSPORT_INV
50
50
  let controlRouted = false;
51
51
  if (!taskViewGuardFailed && !markerlessHelp && (taskControlCommand || taskWorkflowCommand || manualValidationWorkflowCommand)) {
52
52
  const transport = resolveSandboxControlTransport(process.env, {
53
- localWorkflow: command === 'task-orchestration' || taskWorkflowCommand || manualValidationWorkflowCommand
53
+ localWorkflow: command === 'task-orchestration' || command === 'task-finalization' || taskWorkflowCommand || manualValidationWorkflowCommand
54
54
  });
55
55
  switch (transport.kind) {
56
56
  case 'fail-closed': {
@@ -50,7 +50,7 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
50
50
  'pr-review-grade': ['decide', 'resolve-host', 'verify-artifact'],
51
51
  'platform-checks': ['inspect', 'watch', 'resolve-run', 'logs'],
52
52
  'task-context': ['resolve'],
53
- 'task-ledger': ['decision-next-id', 'stage-status', 'finding-upsert', 'finding-respond', 'finding-review', 'decision-upsert', 'rework-intent-upsert'],
53
+ 'task-ledger': ['decision-next-id', 'stage-status', 'finding-upsert', 'finding-respond', 'finding-review', 'decision-upsert', 'rework-intent-upsert', 'rework-intent-rebuild'],
54
54
  'task-warning': ['list', 'add', 'set-status'],
55
55
  'task-activity': ['pr-review-inspect', 'pr-review-start', 'pr-review-complete', 'pr-review-terminate'],
56
56
  'task-artifact': ['inspect', 'init', 'preflight', 'finalize-local'],
@@ -1,6 +1,6 @@
1
1
  import { parseArtifactCommand, executeArtifactCommand } from '../task/artifact-command.js';
2
2
  import { ensureInternalHandlerRoute } from './cli-route-inventory.js';
3
- const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family>\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id> preflight|finalize-local --family <analysis|plan|code> --artifact <artifact>\n\nInspect, initialize, preflight, or finalize a workflow artifact without changing task state.\n`;
3
+ const USAGE = `Usage: agent-infra-internal task-artifact <N | TASK-id> inspect --family <family> [--source-finding <id> --source-artifact <name> --source-sha256 <digest>]\n agent-infra-internal task-artifact <N | TASK-id> init --family <family> --artifact <artifact> [--locale <zh-CN|en>]\n agent-infra-internal task-artifact <N | TASK-id> preflight|finalize-local --family <analysis|plan|code> --artifact <artifact>\n\nInspect, initialize, preflight, or finalize workflow artifact facts.\n`;
4
4
  function taskArtifact(args = []) {
5
5
  if (!ensureInternalHandlerRoute('task-artifact', args))
6
6
  return;
@@ -1,6 +1,9 @@
1
1
  import { createDirectHostExecutionContext, dispatchTaskControlOperation, parseTaskControlOperation } from '../task/control-authority.js';
2
- import { applyTaskFinalization } from '../task/finalization.js';
2
+ import { applyTaskFinalization, prepareTaskFinalization } from '../task/finalization.js';
3
3
  import { detectRepoRoot, resolveTaskRef } from '../task/resolve-ref.js';
4
+ import { verifyTaskEvent } from '../task/verification.js';
5
+ import { resolveSandboxControlTransport } from './task-operation-registry.js';
6
+ import { requestSandboxTaskFinalization, SandboxControlClientError } from '../sandbox/control/client.js';
4
7
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
5
8
  const USAGE = 'Usage: agent-infra-internal task-finalization <N | TASK-id> complete --agent <agent>\n';
6
9
  function envelope(status, changed, accepted, result, error) {
@@ -59,7 +62,44 @@ async function taskFinalization(args = []) {
59
62
  ...operation,
60
63
  request: { ...operation.request, taskRef: resolved.taskId }
61
64
  };
65
+ const transport = resolveSandboxControlTransport(process.env, { localWorkflow: true });
66
+ if (transport.kind === 'sandbox-local') {
67
+ const prepared = await prepareTaskFinalization(boundOperation.request, {
68
+ repoRoot,
69
+ preflight: (request, options) => verifyTaskEvent({ ...request, event: 'complete-task.hard-preflight' }, options)
70
+ });
71
+ if (prepared.status !== 'prepared') {
72
+ process.stdout.write(envelope(prepared.status, prepared.changed, true, prepared, prepared.error));
73
+ process.exitCode = exitCode(prepared.status);
74
+ return;
75
+ }
76
+ try {
77
+ const response = requestSandboxTaskFinalization({ agent: boundOperation.request.agent });
78
+ process.stdout.write(response.stdout);
79
+ process.stderr.write(response.stderr);
80
+ process.exitCode = response.exitCode ?? 1;
81
+ return;
82
+ }
83
+ catch (error) {
84
+ const detail = error instanceof SandboxControlClientError
85
+ ? error.detail
86
+ : { code: 'SANDBOX_CONTROL_CLIENT_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false };
87
+ process.stdout.write(envelope(detail.retryable ? 'blocked' : 'failed', false, false, null, detail));
88
+ process.exitCode = detail.retryable ? 2 : 1;
89
+ return;
90
+ }
91
+ }
62
92
  const result = await dispatchTaskControlOperation(createDirectHostExecutionContext({ repoRoot }), boundOperation);
93
+ if (result.status === 'prepared') {
94
+ const detail = {
95
+ code: 'TASK_FINALIZATION_DIRECT_COMMIT_REQUIRED',
96
+ message: 'direct finalization prepared external work without committing the lifecycle transition',
97
+ retryable: false
98
+ };
99
+ process.stdout.write(envelope('failed', result.changed, true, result, detail));
100
+ process.exitCode = 1;
101
+ return;
102
+ }
63
103
  process.stdout.write(envelope(result.status, result.changed, true, result, result.error));
64
104
  process.exitCode = exitCode(result.status);
65
105
  }
@@ -5,17 +5,18 @@ import { LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, isReviewSt
5
5
  import { resolveTaskRef } from '../task/resolve-ref.js';
6
6
  import { TaskExecutionLockError, withTaskExecutionLock } from '../task/task-execution-lock.js';
7
7
  import { ensureInternalHandlerRoute, internalHandlerRoute } from './cli-route-inventory.js';
8
- const USAGE = `Usage: agent-infra-internal task-ledger <task-ref> <intent> [intent flags] [--dry-run]\n\nIntents: finding-upsert, finding-respond, finding-review, decision-next-id, decision-upsert, rework-intent-upsert, stage-status\n`;
8
+ const USAGE = `Usage: agent-infra-internal task-ledger <task-ref> <intent> [intent flags] [--dry-run]\n\nIntents: finding-upsert, finding-respond, finding-review, decision-next-id, decision-upsert, rework-intent-upsert, rework-intent-rebuild, stage-status\n`;
9
9
  const FLAGS = {
10
10
  '--stage': 'stage', '--review-artifact': 'reviewArtifact', '--ordinal': 'ordinal',
11
11
  '--severity': 'severity', '--evidence': 'evidence', '--id': 'id', '--round': 'round',
12
12
  '--status': 'status', '--artifact': 'artifact', '--needs-implementation': 'needsImplementation',
13
13
  '--intent-id': 'intentId', '--finding-id': 'findingId', '--source-artifact': 'sourceArtifact',
14
- '--source-sha256': 'sourceSha256', '--target': 'target',
14
+ '--source-sha256': 'sourceSha256', '--classification': 'classification',
15
15
  '--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
16
16
  };
17
17
  const NUMERIC = new Set(['ordinal', 'round']);
18
18
  const BOOLEAN = new Set(['needsImplementation']);
19
+ const REWORK_CLASSIFICATIONS = new Set(['scope-or-requirement', 'design', 'implementation', 'human-decision', 'insufficient-evidence']);
19
20
  function usageFailure(message) {
20
21
  process.stdout.write(`${JSON.stringify({ status: 'failed', changed: false, error: { code: 'LEDGER_PAYLOAD_INVALID', message } })}\n`);
21
22
  process.stderr.write(USAGE);
@@ -40,6 +41,7 @@ async function taskLedger(args = []) {
40
41
  internalHandlerRoute('task-ledger', 'decision-next-id', kind),
41
42
  internalHandlerRoute('task-ledger', 'decision-upsert', kind),
42
43
  internalHandlerRoute('task-ledger', 'rework-intent-upsert', kind),
44
+ internalHandlerRoute('task-ledger', 'rework-intent-rebuild', kind),
43
45
  internalHandlerRoute('task-ledger', 'stage-status', kind)
44
46
  ].some(Boolean)) {
45
47
  usageFailure(`unknown ledger intent '${kind}'`);
@@ -85,14 +87,16 @@ async function taskLedger(args = []) {
85
87
  'finding-review': ['id', 'status', 'evidence'],
86
88
  'decision-next-id': [],
87
89
  'decision-upsert': ['id', 'stage', 'artifact'],
88
- 'rework-intent-upsert': ['intentId', 'findingId', 'sourceArtifact', 'sourceSha256', 'target'],
90
+ 'rework-intent-upsert': ['intentId', 'findingId', 'sourceArtifact', 'sourceSha256', 'classification'],
91
+ 'rework-intent-rebuild': [],
89
92
  'stage-status': ['stage']
90
93
  };
91
94
  const optional = {
92
95
  'finding-review': ['needsImplementation'],
93
- 'decision-upsert': ['needsImplementation']
96
+ 'decision-upsert': ['needsImplementation'],
97
+ 'rework-intent-rebuild': ['findingId', 'sourceArtifact', 'sourceSha256', 'classification']
94
98
  };
95
- const overrideFields = kind === 'stage-status' || kind === 'decision-next-id' || kind === 'rework-intent-upsert'
99
+ const overrideFields = kind === 'stage-status' || kind === 'decision-next-id' || kind === 'rework-intent-upsert' || kind === 'rework-intent-rebuild'
96
100
  ? []
97
101
  : ['overrideTicket', 'overrideTarget', 'overrideScope'];
98
102
  const allowed = new Set(['kind', 'taskRef', 'dryRun', ...required[kind], ...(optional[kind] ?? []), ...overrideFields]);
@@ -111,6 +115,10 @@ async function taskLedger(args = []) {
111
115
  usageFailure(dryRunConflict.message);
112
116
  return;
113
117
  }
118
+ if (values.classification !== undefined && !REWORK_CLASSIFICATIONS.has(String(values.classification))) {
119
+ usageFailure(`invalid rework classification '${values.classification}'`);
120
+ return;
121
+ }
114
122
  if (kind === 'stage-status') {
115
123
  if (!isReviewStage(String(values.stage))) {
116
124
  usageFailure(`stage-status requires a valid review stage`);
@@ -85,6 +85,7 @@ function internalTaskRoutes() {
85
85
  descriptor('internal', 'task-ledger', 'finding-review', 'task-bound', 'progress'),
86
86
  descriptor('internal', 'task-ledger', 'decision-upsert', 'task-bound', 'progress'),
87
87
  descriptor('internal', 'task-ledger', 'rework-intent-upsert', 'task-bound', 'progress'),
88
+ descriptor('internal', 'task-ledger', 'rework-intent-rebuild', 'task-bound', 'progress'),
88
89
  descriptor('internal', 'task-warning', 'list', 'task-bound', 'diagnostic'),
89
90
  descriptor('internal', 'task-warning', 'add', 'task-bound', 'progress'),
90
91
  descriptor('internal', 'task-warning', 'set-status', 'task-bound', 'progress'),
@@ -13,21 +13,70 @@ import { taskIssueIdentity } from './task-identities.js';
13
13
  function result(base, artifacts, warnings) {
14
14
  return { ...base, artifacts, warnings };
15
15
  }
16
- function excludedPrReview(taskDir, message) {
17
- const names = [...message.matchAll(/(?:^|[^A-Za-z0-9_-])(pr-review(?:-r[1-9]\d*)?\.md)(?=$|[^A-Za-z0-9_.-])/g)]
16
+ function referencedBackfillArtifact(taskDir, message) {
17
+ const names = [...message.matchAll(/(?:^|[^A-Za-z0-9_-])([a-z][a-z-]*(?:-r[1-9]\d*)?\.md)(?=$|[^A-Za-z0-9_.-])/g)]
18
18
  .map((match) => match[1]);
19
19
  if (names.length !== 1)
20
- return null;
20
+ return { status: 'ignored', artifact: null, error: null };
21
21
  const parsed = parseArtifactName(names[0]);
22
- if (!parsed || parsed.family !== 'pr-review')
23
- return null;
22
+ if (!parsed || (parsed.family !== 'pr-review' && !COMPLETION_BACKFILL_FAMILIES.includes(parsed.family))) {
23
+ return { status: 'ignored', artifact: null, error: null };
24
+ }
24
25
  const artifactPath = path.join(taskDir, parsed.name);
25
26
  try {
26
27
  const stat = fs.lstatSync(artifactPath);
27
- return stat.isFile() && !stat.isSymbolicLink() ? parsed.name : null;
28
+ if (stat.isSymbolicLink())
29
+ return {
30
+ status: 'failed', artifact: null,
31
+ error: { code: 'ARTIFACT_TOPOLOGY_CONFLICT', message: `SYMBOLIC_LINK: symbolic links are not workflow artifacts: ${parsed.name}`, retryable: false }
32
+ };
33
+ if (!stat.isFile())
34
+ return {
35
+ status: 'failed', artifact: null,
36
+ error: { code: 'ARTIFACT_TOPOLOGY_CONFLICT', message: `NON_REGULAR_FILE: artifact is not a regular file: ${parsed.name}`, retryable: false }
37
+ };
38
+ fs.accessSync(artifactPath, fs.constants.R_OK);
39
+ return { status: 'ready', artifact: parsed.name, error: null };
40
+ }
41
+ catch (error) {
42
+ return {
43
+ status: 'failed', artifact: null,
44
+ error: { code: 'ARTIFACT_READ_FAILED', message: `Unable to inspect referenced artifact '${parsed.name}': ${error instanceof Error ? error.message : String(error)}`, retryable: false }
45
+ };
46
+ }
47
+ }
48
+ function matchingBackfillWarnings(taskDir, content) {
49
+ const matches = [];
50
+ for (const warning of getOpenWorkflowWarnings(content)) {
51
+ if (warning.step !== 'complete-task' || warning.code !== 'COMMENT_SYNC_FAILED' || warning.target !== 'artifact')
52
+ continue;
53
+ const reference = referencedBackfillArtifact(taskDir, warning.message);
54
+ if (reference.status === 'failed')
55
+ return { status: 'failed', matches: [], error: reference.error };
56
+ if (reference.status === 'ready')
57
+ matches.push({ warning, artifact: reference.artifact });
58
+ }
59
+ return { status: 'ready', matches, error: null };
60
+ }
61
+ function inspectCompletionBackfillEligibility(taskRef, options = {}) {
62
+ const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
63
+ if (!resolved.ok)
64
+ return {
65
+ status: 'failed', eligible: false,
66
+ error: { code: resolved.code, message: resolved.message, retryable: false }
67
+ };
68
+ try {
69
+ const content = fs.readFileSync(resolved.taskMdPath, 'utf8');
70
+ const matching = matchingBackfillWarnings(resolved.taskDir, content);
71
+ if (matching.status === 'failed')
72
+ return { status: 'failed', eligible: false, error: matching.error };
73
+ return { status: 'ready', eligible: matching.matches.length > 0, error: null };
28
74
  }
29
- catch {
30
- return null;
75
+ catch (error) {
76
+ return {
77
+ status: 'failed', eligible: false,
78
+ error: { code: 'TASK_READ_FAILED', message: error instanceof Error ? error.message : String(error), retryable: false }
79
+ };
31
80
  }
32
81
  }
33
82
  async function backfillCompletionComments(taskRef, options) {
@@ -46,6 +95,9 @@ async function backfillCompletionComments(taskRef, options) {
46
95
  }
47
96
  }), [], []);
48
97
  const initialContent = fs.readFileSync(resolved.taskMdPath, 'utf8');
98
+ const matching = matchingBackfillWarnings(resolved.taskDir, initialContent);
99
+ if (matching.status === 'failed')
100
+ return result(platformResult('failed', { error: matching.error }), [], []);
49
101
  const issueIdentity = taskIssueIdentity(parseTaskFrontmatter(initialContent));
50
102
  if (!issueIdentity)
51
103
  return result(platformResult('no-op', {
@@ -79,18 +131,12 @@ async function backfillCompletionComments(taskRef, options) {
79
131
  error: latest.error
80
132
  }), artifacts, []);
81
133
  }
82
- const matching = getOpenWorkflowWarnings(initialContent).flatMap((warning) => {
83
- if (warning.step !== 'complete-task' || warning.code !== 'COMMENT_SYNC_FAILED' || warning.target !== 'artifact')
84
- return [];
85
- const excluded = excludedPrReview(resolved.taskDir, warning.message);
86
- return excluded ? [{ warning, excluded }] : [];
87
- });
88
134
  const warnings = [];
89
135
  let metadata = null;
90
- for (const { warning, excluded } of matching) {
136
+ for (const { warning, artifact } of matching.matches) {
91
137
  metadata ??= captureTaskWriteMetadata();
92
138
  const resolution = [
93
- `excluded=${excluded}`,
139
+ `artifact=${artifact}`,
94
140
  `completionFamilies=${COMPLETION_BACKFILL_FAMILIES.join(',')}`,
95
141
  `synchronized=${inventory.artifacts.map((artifact) => artifact.name).join(',') || 'none'}`,
96
142
  `completedAt=${metadata.timestamp}`
@@ -119,5 +165,5 @@ async function backfillCompletionComments(taskRef, options) {
119
165
  error: null
120
166
  }), artifacts, warnings);
121
167
  }
122
- export { backfillCompletionComments };
168
+ export { backfillCompletionComments, inspectCompletionBackfillEligibility };
123
169
  //# sourceMappingURL=completion-backfill.js.map