@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.
- package/README.md +17 -8
- package/README.zh-CN.md +17 -8
- package/dist/bin/internal-cli.js +1 -1
- package/dist/lib/internal/cli-route-inventory.js +1 -1
- package/dist/lib/internal/task-artifact.js +1 -1
- package/dist/lib/internal/task-finalization.js +41 -1
- package/dist/lib/internal/task-ledger.js +13 -5
- package/dist/lib/internal/task-operation-registry.js +1 -0
- package/dist/lib/platform/completion-backfill.js +63 -17
- package/dist/lib/sandbox/control/server.js +26 -143
- package/dist/lib/sandbox/task-cleanup.js +13 -20
- package/dist/lib/task/artifact-command.js +11 -4
- package/dist/lib/task/artifact-lifecycle.js +217 -85
- package/dist/lib/task/artifact-operations.js +1 -26
- package/dist/lib/task/artifact-receipts.js +4 -1
- package/dist/lib/task/artifact-schema.js +1 -0
- package/dist/lib/task/artifact-selection.js +25 -0
- package/dist/lib/task/capabilities.js +158 -39
- package/dist/lib/task/commands/status.js +9 -2
- package/dist/lib/task/control-authority.js +3 -2
- package/dist/lib/task/decision-intents.js +12 -0
- package/dist/lib/task/delivery.js +1 -43
- package/dist/lib/task/events.js +80 -64
- package/dist/lib/task/finalization.js +301 -143
- package/dist/lib/task/ledger-intents.js +157 -15
- package/dist/lib/task/lifecycle-path.js +75 -0
- package/dist/lib/task/lifecycle.js +10 -0
- package/dist/lib/task/local-artifact-finalization.js +1 -20
- package/dist/lib/task/orchestration.js +17 -7
- package/dist/lib/task/review-finalization.js +3 -16
- package/dist/lib/task/rework-intent.js +70 -4
- package/dist/lib/task/verification-engine.js +20 -30
- package/dist/lib/task/verification.js +1 -0
- package/dist/lib/task/workflow-warning-intents.js +21 -6
- package/dist/lib/task/workspace-snapshot.js +7 -2
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/internal/cli-route-inventory.ts +1 -1
- package/lib/internal/task-artifact.ts +1 -1
- package/lib/internal/task-finalization.ts +40 -1
- package/lib/internal/task-ledger.ts +12 -5
- package/lib/internal/task-operation-registry.ts +1 -0
- package/lib/platform/completion-backfill.ts +78 -16
- package/lib/sandbox/control/server.ts +21 -146
- package/lib/sandbox/task-cleanup.ts +12 -20
- package/lib/task/artifact-command.ts +14 -4
- package/lib/task/artifact-lifecycle.ts +236 -85
- package/lib/task/artifact-operations.ts +1 -26
- package/lib/task/artifact-receipts.ts +4 -1
- package/lib/task/artifact-schema.ts +1 -0
- package/lib/task/artifact-selection.ts +53 -0
- package/lib/task/capabilities.ts +154 -40
- package/lib/task/commands/status.ts +11 -2
- package/lib/task/control-authority.ts +3 -1
- package/lib/task/decision-intents.ts +9 -0
- package/lib/task/delivery.ts +1 -40
- package/lib/task/events.ts +82 -64
- package/lib/task/finalization.ts +269 -111
- package/lib/task/ledger-intents.ts +155 -15
- package/lib/task/lifecycle-path.ts +98 -0
- package/lib/task/lifecycle.ts +13 -0
- package/lib/task/local-artifact-finalization.ts +2 -25
- package/lib/task/orchestration.ts +26 -8
- package/lib/task/review-finalization.ts +3 -16
- package/lib/task/rework-intent.ts +75 -5
- package/lib/task/verification-engine.ts +19 -30
- package/lib/task/verification.ts +2 -1
- package/lib/task/workflow-warning-intents.ts +18 -6
- package/lib/task/workspace-snapshot.ts +3 -2
- package/package.json +1 -1
- package/templates/.agents/rules/review-handshake.en.md +4 -4
- package/templates/.agents/rules/review-handshake.zh-CN.md +4 -4
- package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -4
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -4
- package/templates/.agents/skills/code-task/SKILL.en.md +14 -8
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +18 -13
- package/templates/.agents/skills/code-task/reference/code-rules.en.md +2 -2
- package/templates/.agents/skills/code-task/reference/code-rules.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +4 -4
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +4 -4
- package/templates/.agents/skills/code-task/reference/report-template.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -10
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +13 -10
- package/templates/.agents/skills/review-analysis/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/review-plan/SKILL.en.md +5 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +5 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +18 -2
- package/templates/.agents/templates/task.en.md +2 -2
- package/templates/.agents/templates/task.zh-CN.md +2 -2
- package/templates/.agents/workflows/bug-fix.en.yaml +11 -6
- package/templates/.agents/workflows/bug-fix.zh-CN.yaml +11 -6
- package/templates/.agents/workflows/feature-development.en.yaml +11 -6
- package/templates/.agents/workflows/feature-development.zh-CN.yaml +11 -6
- package/templates/.agents/workflows/refactoring.en.yaml +11 -6
- package/templates/.agents/workflows/refactoring.zh-CN.yaml +11 -6
- package/dist/lib/sandbox/control/finalization-response.js +0 -33
- 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
|
|
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"*.
|
|
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,
|
|
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
|
-
**
|
|
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`
|
|
197
|
-
| `
|
|
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
|
|
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"
|
|
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
|
|
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
|
-
|
|
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`
|
|
196
|
-
| `
|
|
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
|
|
package/dist/bin/internal-cli.js
CHANGED
|
@@ -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
|
|
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', '--
|
|
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', '
|
|
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
|
|
17
|
-
const names = [...message.matchAll(/(?:^|[^A-Za-z0-9_-])(
|
|
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
|
-
|
|
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
|
|
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,
|
|
136
|
+
for (const { warning, artifact } of matching.matches) {
|
|
91
137
|
metadata ??= captureTaskWriteMetadata();
|
|
92
138
|
const resolution = [
|
|
93
|
-
`
|
|
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
|