@fitlab-ai/agent-infra 0.12.1 → 0.12.2
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 +0 -2
- package/README.zh-CN.md +0 -2
- package/dist/bin/internal-cli.js +2 -17
- package/dist/lib/agent-clients/adapter.js +0 -13
- package/dist/lib/agent-clients/adapters/antigravity-cli.js +0 -5
- package/dist/lib/agent-clients/adapters/claude-code-credentials.js +0 -17
- package/dist/lib/agent-clients/adapters/claude-code.js +0 -9
- package/dist/lib/agent-clients/adapters/codex.js +0 -52
- package/dist/lib/agent-clients/adapters/opencode.js +0 -5
- package/dist/lib/agent-clients/adapters/traecli.js +0 -5
- package/dist/lib/agent-clients/config.js +19 -54
- package/dist/lib/agent-clients/registry.js +1 -12
- package/dist/lib/agent-clients/schema.js +0 -18
- package/dist/lib/agent-clients/tokens.js +1 -2
- package/dist/lib/agent-clients/types.js +0 -1
- package/dist/lib/decide.js +2 -0
- package/dist/lib/git/worktree-identity.js +1 -9
- package/dist/lib/internal/agent-client.js +2 -62
- package/dist/lib/internal/cli-route-inventory.js +1 -5
- package/dist/lib/internal/manual-validation.js +18 -10
- package/dist/lib/internal/task-create.js +1 -1
- package/dist/lib/internal/task-event.js +1 -8
- package/dist/lib/internal/task-finalization.js +0 -1
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-operation-registry.js +4 -20
- package/dist/lib/internal/task-review.js +4 -4
- package/dist/lib/platform/comment-safety.js +1 -7
- package/dist/lib/platform/in-label-sync.js +1 -14
- package/dist/lib/platform/issue-comments.js +1 -16
- package/dist/lib/platform/issues.js +1 -1
- package/dist/lib/platform/operation-coordinator.js +4 -2
- package/dist/lib/platform/pr-change-report.js +1 -4
- package/dist/lib/platform/pr-checks.js +2 -8
- package/dist/lib/platform/provider-validation.js +0 -20
- package/dist/lib/platform/pull-requests.js +73 -84
- package/dist/lib/platform/releases.js +1 -43
- package/dist/lib/platform/verification-required.js +0 -2
- package/dist/lib/platform/verification-sync.js +1 -79
- package/dist/lib/process-data/normalize.js +1 -185
- package/dist/lib/process-data/privacy.js +1 -16
- package/dist/lib/process-data/sources.js +2 -279
- package/dist/lib/render.js +0 -12
- package/dist/lib/run/index.js +15 -10
- package/dist/lib/run/skills.js +0 -3
- package/dist/lib/sandbox/commands/create.js +40 -53
- package/dist/lib/sandbox/commands/list-running.js +0 -18
- package/dist/lib/sandbox/commands/start.js +7 -8
- package/dist/lib/sandbox/constants.js +0 -6
- package/dist/lib/sandbox/control/client.js +0 -1
- package/dist/lib/sandbox/control/container-identity.js +1 -16
- package/dist/lib/sandbox/control/executor.js +0 -4
- package/dist/lib/sandbox/control/identity-sentinel.js +0 -7
- package/dist/lib/sandbox/control/lifecycle.js +21 -37
- package/dist/lib/sandbox/control/native-file-lock.js +0 -3
- package/dist/lib/sandbox/control/server.js +18 -38
- package/dist/lib/sandbox/control/state.js +1 -9
- package/dist/lib/sandbox/dockerfile.js +0 -6
- package/dist/lib/sandbox/engine.js +0 -14
- package/dist/lib/sandbox/engines/docker-exact-discovery.js +1 -4
- package/dist/lib/sandbox/engines/wsl2-paths.js +0 -3
- package/dist/lib/sandbox/recovery.js +24 -26
- package/dist/lib/sandbox/removal.js +11 -519
- package/dist/lib/sandbox/task-resolver.js +0 -41
- package/dist/lib/sandbox/tools.js +1 -4
- package/dist/lib/sandbox/workspace-view.js +1 -2
- package/dist/lib/server/display.js +0 -6
- package/dist/lib/task/activity-intent.js +45 -40
- package/dist/lib/task/activity-log.js +9 -16
- package/dist/lib/task/artifact-command.js +1 -1
- package/dist/lib/task/artifact-lifecycle.js +61 -63
- package/dist/lib/task/artifact-name.js +0 -6
- package/dist/lib/task/artifact-operations.js +3 -3
- package/dist/lib/task/capabilities.js +4 -5
- package/dist/lib/task/commands/log.js +0 -6
- package/dist/lib/task/commands/status.js +17 -60
- package/dist/lib/task/commit-operation.js +28 -39
- package/dist/lib/task/completion-facts.js +56 -43
- package/dist/lib/task/control-authority.js +23 -256
- package/dist/lib/task/control-recovery.js +3 -5
- package/dist/lib/task/create.js +2 -4
- package/dist/lib/task/decision-intents.js +8 -9
- package/dist/lib/task/delivery-target.js +1 -40
- package/dist/lib/task/events.js +84 -107
- package/dist/lib/task/finalization.js +44 -8
- package/dist/lib/task/ledger-intents.js +1 -1
- package/dist/lib/task/lifecycle-execution.js +38 -33
- package/dist/lib/task/lifecycle.js +49 -41
- package/dist/lib/task/local-artifact-finalization.js +1 -4
- package/dist/lib/task/manual-validation-evidence.js +1 -37
- package/dist/lib/task/platform-operation-recovery.js +67 -24
- package/dist/lib/task/pr-delivery-fact.js +2 -5
- package/dist/lib/task/qualification-confirmation.js +5 -7
- package/dist/lib/task/resolve-ref.js +1 -25
- package/dist/lib/task/review-command.js +2 -2
- package/dist/lib/task/review-finalization.js +1 -21
- package/dist/lib/task/review-fingerprint.js +0 -4
- package/dist/lib/task/secure-fs.js +0 -56
- package/dist/lib/task/short-id.js +1 -39
- package/dist/lib/task/skill-scope.js +0 -1
- package/dist/lib/task/verification-engine.js +58 -282
- package/dist/lib/task/verification.js +6 -3
- package/dist/lib/task/write.js +11 -1
- package/lib/agent-clients/adapter.ts +0 -76
- package/lib/agent-clients/adapters/antigravity-cli.ts +0 -5
- package/lib/agent-clients/adapters/claude-code-credentials.ts +0 -19
- package/lib/agent-clients/adapters/claude-code.ts +0 -9
- package/lib/agent-clients/adapters/codex.ts +0 -56
- package/lib/agent-clients/adapters/opencode.ts +0 -5
- package/lib/agent-clients/adapters/traecli.ts +0 -5
- package/lib/agent-clients/config.ts +20 -61
- package/lib/agent-clients/registry.ts +1 -34
- package/lib/agent-clients/schema.ts +0 -18
- package/lib/agent-clients/tokens.ts +1 -2
- package/lib/agent-clients/types.ts +0 -15
- package/lib/decide.ts +1 -0
- package/lib/git/worktree-identity.ts +0 -10
- package/lib/internal/agent-client.ts +2 -70
- package/lib/internal/cli-route-inventory.ts +1 -4
- package/lib/internal/manual-validation.ts +16 -9
- package/lib/internal/task-create.ts +0 -1
- package/lib/internal/task-event.ts +1 -5
- package/lib/internal/task-finalization.ts +0 -1
- package/lib/internal/task-lifecycle.ts +3 -5
- package/lib/internal/task-operation-registry.ts +3 -19
- package/lib/internal/task-review.ts +4 -4
- package/lib/platform/comment-safety.ts +0 -10
- package/lib/platform/github-provider.ts +0 -2
- package/lib/platform/in-label-sync.ts +0 -16
- package/lib/platform/issue-comments.ts +0 -21
- package/lib/platform/issues.ts +0 -2
- package/lib/platform/operation-coordinator.ts +4 -2
- package/lib/platform/pr-change-report.ts +0 -5
- package/lib/platform/pr-checks.ts +1 -10
- package/lib/platform/pr-review.ts +1 -1
- package/lib/platform/provider-bridge.ts +0 -1
- package/lib/platform/provider-loader.ts +1 -5
- package/lib/platform/provider-validation.ts +1 -49
- package/lib/platform/pull-requests.ts +64 -99
- package/lib/platform/releases.ts +1 -40
- package/lib/platform/verification-required.ts +0 -2
- package/lib/platform/verification-sync.ts +0 -94
- package/lib/process-data/normalize.ts +1 -186
- package/lib/process-data/privacy.ts +2 -19
- package/lib/process-data/sources.ts +3 -283
- package/lib/render.ts +0 -15
- package/lib/run/index.ts +13 -7
- package/lib/run/skills.ts +0 -4
- package/lib/sandbox/commands/create.ts +37 -74
- package/lib/sandbox/commands/list-running.ts +0 -19
- package/lib/sandbox/commands/start.ts +7 -10
- package/lib/sandbox/constants.ts +0 -8
- package/lib/sandbox/control/client.ts +0 -1
- package/lib/sandbox/control/container-identity.ts +1 -16
- package/lib/sandbox/control/executor.ts +1 -5
- package/lib/sandbox/control/identity-sentinel.ts +0 -8
- package/lib/sandbox/control/lifecycle.ts +18 -51
- package/lib/sandbox/control/native-file-lock.ts +0 -8
- package/lib/sandbox/control/protocol.ts +0 -2
- package/lib/sandbox/control/server.ts +13 -39
- package/lib/sandbox/control/state.ts +1 -10
- package/lib/sandbox/dockerfile.ts +0 -11
- package/lib/sandbox/engine.ts +0 -17
- package/lib/sandbox/engines/docker-exact-discovery.ts +1 -5
- package/lib/sandbox/engines/wsl2-paths.ts +0 -4
- package/lib/sandbox/recovery.ts +24 -26
- package/lib/sandbox/removal.ts +11 -696
- package/lib/sandbox/shell.ts +1 -1
- package/lib/sandbox/task-resolver.ts +0 -48
- package/lib/sandbox/tools.ts +1 -5
- package/lib/sandbox/workspace-view.ts +2 -4
- package/lib/server/display.ts +0 -14
- package/lib/server/process-control.ts +1 -1
- package/lib/task/activity-intent.ts +36 -44
- package/lib/task/activity-log.ts +9 -22
- package/lib/task/artifact-command.ts +1 -1
- package/lib/task/artifact-lifecycle.ts +58 -67
- package/lib/task/artifact-name.ts +0 -7
- package/lib/task/artifact-operations.ts +3 -3
- package/lib/task/capabilities.ts +5 -6
- package/lib/task/commands/log.ts +0 -7
- package/lib/task/commands/status.ts +14 -79
- package/lib/task/commit-operation.ts +29 -51
- package/lib/task/completion-facts.ts +45 -42
- package/lib/task/control-authority.ts +27 -317
- package/lib/task/control-recovery.ts +2 -5
- package/lib/task/create.ts +2 -5
- package/lib/task/decision-intents.ts +5 -5
- package/lib/task/delivery-target.ts +0 -40
- package/lib/task/events.ts +89 -123
- package/lib/task/finalization.ts +41 -9
- package/lib/task/ledger-intents.ts +1 -1
- package/lib/task/lifecycle-execution.ts +37 -89
- package/lib/task/lifecycle.ts +48 -44
- package/lib/task/local-artifact-finalization.ts +0 -7
- package/lib/task/manual-validation-evidence.ts +1 -47
- package/lib/task/platform-operation-recovery.ts +50 -26
- package/lib/task/pr-delivery-fact.ts +2 -11
- package/lib/task/qualification-confirmation.ts +6 -6
- package/lib/task/resolve-ref.ts +0 -23
- package/lib/task/review-command.ts +2 -2
- package/lib/task/review-finalization.ts +1 -27
- package/lib/task/review-fingerprint.ts +0 -8
- package/lib/task/secure-fs.ts +0 -42
- package/lib/task/short-id.ts +0 -43
- package/lib/task/skill-scope.ts +0 -1
- package/lib/task/verification-engine.ts +56 -287
- package/lib/task/verification.ts +5 -5
- package/lib/task/write.ts +12 -1
- package/package.json +2 -2
- package/templates/.agents/README.en.md +1 -3
- package/templates/.agents/README.zh-CN.md +0 -2
- package/templates/.agents/rules/README.en.md +0 -1
- package/templates/.agents/rules/README.zh-CN.md +0 -1
- package/templates/.agents/rules/commit-and-pr.en.md +1 -1
- package/templates/.agents/rules/commit-and-pr.zh-CN.md +1 -1
- package/templates/.agents/rules/no-mid-flow-questions.en.md +0 -10
- package/templates/.agents/rules/no-mid-flow-questions.zh-CN.md +0 -10
- package/templates/.agents/rules/pr-sync.en.md +1 -1
- package/templates/.agents/rules/pr-sync.zh-CN.md +1 -1
- package/templates/.agents/rules/task-management.en.md +3 -3
- package/templates/.agents/rules/task-management.zh-CN.md +3 -3
- package/templates/.agents/skills/analyze-task/SKILL.en.md +3 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/cancel-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/commit/SKILL.en.md +2 -2
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +6 -6
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +6 -6
- package/templates/.agents/skills/plan-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.en.md +5 -5
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-pr/SKILL.en.md +4 -4
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +3 -75
- package/templates/.agents/templates/task.en.md +1 -0
- package/templates/.agents/templates/task.zh-CN.md +1 -0
- package/templates/.claude/settings.json +0 -24
- package/templates/.codex/hooks.json +0 -50
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +0 -582
- package/dist/lib/agent-clients/adapters/codex-lifecycle/binding.js +0 -114
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +0 -237
- package/dist/lib/agent-clients/adapters/codex-lifecycle/cli.js +0 -443
- package/dist/lib/agent-clients/adapters/codex-lifecycle/evidence.js +0 -236
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +0 -292
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +0 -267
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +0 -466
- package/dist/lib/internal/task-orchestration.js +0 -71
- package/dist/lib/platform/verification.js +0 -6
- package/dist/lib/run/prompt.js +0 -2
- package/dist/lib/sandbox/engines/authority.js +0 -289
- package/dist/lib/task/delegation-receipts.js +0 -410
- package/dist/lib/task/orchestration.js +0 -1233
- package/dist/lib/task/verification-checks.js +0 -9
- package/dist/lib/task/workspace-snapshot.js +0 -121
- package/dist/lifecycle-build-manifest.json +0 -6
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +0 -688
- package/lib/agent-clients/adapters/codex-lifecycle/binding.ts +0 -147
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +0 -349
- package/lib/agent-clients/adapters/codex-lifecycle/cli.ts +0 -506
- package/lib/agent-clients/adapters/codex-lifecycle/evidence.ts +0 -385
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +0 -41
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +0 -418
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +0 -354
- package/lib/agent-clients/adapters/codex-orchestration.ts +0 -630
- package/lib/internal/task-orchestration.ts +0 -74
- package/lib/platform/verification.ts +0 -11
- package/lib/run/prompt.ts +0 -1
- package/lib/sandbox/engines/authority.ts +0 -365
- package/lib/task/delegation-receipts.ts +0 -547
- package/lib/task/orchestration.ts +0 -1651
- package/lib/task/verification-checks.ts +0 -10
- package/lib/task/workspace-snapshot.ts +0 -144
- package/templates/.agents/hooks/lifecycle-delegation.js +0 -140
- package/templates/.agents/rules/lifecycle-orchestration.en.md +0 -44
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +0 -44
- package/templates/.agents/skills/run-task/SKILL.en.md +0 -29
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +0 -29
- package/templates/.agents/skills/run-task/config/verify.en.json +0 -10
- package/templates/.agents/skills/run-task/config/verify.zh-CN.json +0 -10
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +0 -24
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +0 -24
- package/templates/.claude/agents/agent-infra-lifecycle-executor.en.md +0 -6
- package/templates/.claude/agents/agent-infra-lifecycle-executor.zh-CN.md +0 -6
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.en.md +0 -6
- package/templates/.claude/agents/agent-infra-lifecycle-reviewer.zh-CN.md +0 -6
- package/templates/.claude/commands/run-task.en.md +0 -8
- package/templates/.claude/commands/run-task.zh-CN.md +0 -8
- package/templates/.codex/agents/agent-infra-lifecycle-executor.en.toml +0 -3
- package/templates/.codex/agents/agent-infra-lifecycle-executor.zh-CN.toml +0 -3
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.en.toml +0 -3
- package/templates/.codex/agents/agent-infra-lifecycle-reviewer.zh-CN.toml +0 -3
- package/templates/.opencode/commands/run-task.en.md +0 -11
- package/templates/.opencode/commands/run-task.zh-CN.md +0 -11
|
@@ -7,8 +7,8 @@ import { inspectPlatformCommentOperation, syncPlatformComment } from '../platfor
|
|
|
7
7
|
import { parseDeliverySummaryContent, renderDeliverySummaryContent, parseCancelSummaryContent, renderCancelSummaryContent } from '../platform/issue-summary-content.ts';
|
|
8
8
|
import { enumerateArtifacts } from './artifacts.ts';
|
|
9
9
|
import { inspectPlatformIssue, syncPlatformIssue } from '../platform/issues.ts';
|
|
10
|
-
import
|
|
11
|
-
import
|
|
10
|
+
import '../platform/context.ts';
|
|
11
|
+
import '../platform/provider-bridge.ts';
|
|
12
12
|
import { inLabelMappingDigest } from '../platform/in-label-sync.ts';
|
|
13
13
|
import { bindPlatformPullRequest, recoverCreatedPullRequest, syncPlatformPullRequest } from '../platform/pull-requests.ts';
|
|
14
14
|
import { replayPullRequestSummary } from '../platform/pr-summary.ts';
|
|
@@ -21,11 +21,12 @@ import { operationId } from './platform-operation-journal.ts';
|
|
|
21
21
|
import { canonicalizeSummaryBody } from '../platform/comment-safety.ts';
|
|
22
22
|
import { platformResult } from '../platform/types.ts';
|
|
23
23
|
import type { PlatformResult } from '../platform/types.ts';
|
|
24
|
+
import { mergeOperationWarnings, type OperationWarning } from './operation-outcome.ts';
|
|
24
25
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
25
26
|
import { readPlatformOperationJournal, recordPlatformOperation } from './platform-operation-journal.ts';
|
|
26
|
-
import
|
|
27
|
+
import '../platform/provider-validation.ts';
|
|
27
28
|
|
|
28
|
-
type RecoveryOptions = Readonly<{ agent: string; client?: PlatformClient; cwd?: string; limit?: number; attempts?: number; excludeId?: string }>;
|
|
29
|
+
type RecoveryOptions = Readonly<{ agent: string; client?: PlatformClient; cwd?: string; limit?: number; attempts?: number; excludeId?: string; beforeId?: string }>;
|
|
29
30
|
const DEFAULT_RECOVERY_ATTEMPTS = 3;
|
|
30
31
|
type RecoveryResult = Readonly<{
|
|
31
32
|
status: 'applied' | 'no-op' | 'blocked' | 'failed';
|
|
@@ -33,6 +34,8 @@ type RecoveryResult = Readonly<{
|
|
|
33
34
|
recovered: readonly string[];
|
|
34
35
|
pending: readonly string[];
|
|
35
36
|
error: { code: string; message: string } | null;
|
|
37
|
+
degraded?: readonly { id: string; reasonCode: string }[];
|
|
38
|
+
warnings?: readonly OperationWarning[];
|
|
36
39
|
}>;
|
|
37
40
|
type RecoveryError = { code: string; message: string; retryable?: boolean };
|
|
38
41
|
|
|
@@ -48,8 +51,8 @@ function fieldsMatchExpected(
|
|
|
48
51
|
return Object.entries(expected).every(([name, value]) => actual[name] === value);
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
function result(status: RecoveryResult['status'], recovered: string[], pending: string[], error: RecoveryError | null = null): RecoveryResult {
|
|
52
|
-
return { status, changed: recovered.length > 0, recovered, pending, error: error ? { code: error.code, message: error.message } : null };
|
|
54
|
+
function result(status: RecoveryResult['status'], recovered: string[], pending: string[], error: RecoveryError | null = null, warnings: readonly OperationWarning[] = []): RecoveryResult {
|
|
55
|
+
return { status, changed: recovered.length > 0, recovered, pending, error: error ? { code: error.code, message: error.message } : null, ...(warnings.length ? { warnings } : {}) };
|
|
53
56
|
}
|
|
54
57
|
|
|
55
58
|
function summaryPayload(taskDir: string, taskId: string): { body: string; sha256: string } | null {
|
|
@@ -164,7 +167,8 @@ async function replayIssueMetadata(taskId: string, operation: ReturnType<typeof
|
|
|
164
167
|
}
|
|
165
168
|
const issue = after.issue;
|
|
166
169
|
const metadata = operation.issueMetadata;
|
|
167
|
-
const failedOperation = sync.operations.find((item) => item.status === 'failed' || item.status === 'skipped'
|
|
170
|
+
const failedOperation = sync.operations.find((item) => item.status === 'failed' || (item.status === 'skipped'
|
|
171
|
+
&& !(sync.capabilities.push === false && ['fields', 'issue-type'].includes(item.name) && item.reasonCode === 'PUSH_REQUIRED')));
|
|
168
172
|
if (failedOperation) return platformResult('blocked', { error: {
|
|
169
173
|
code: failedOperation.reasonCode || 'PLATFORM_OPERATION_METADATA_UNCONFIRMED',
|
|
170
174
|
message: `Issue metadata recovery did not confirm operation '${failedOperation.name}'`, retryable: true
|
|
@@ -189,7 +193,7 @@ async function replayIssueMetadata(taskId: string, operation: ReturnType<typeof
|
|
|
189
193
|
code: 'PLATFORM_OPERATION_METADATA_UNCONFIRMED',
|
|
190
194
|
message: 'Issue metadata differs from the requested recovery target after reread', retryable: true
|
|
191
195
|
} });
|
|
192
|
-
return platformResult('no-op', { changed: sync.changed || after.changed });
|
|
196
|
+
return platformResult('no-op', { changed: sync.changed || after.changed, operations: sync.operations });
|
|
193
197
|
}
|
|
194
198
|
|
|
195
199
|
async function verifyIssueCreate(taskId: string, operation: ReturnType<typeof readPlatformOperationJournal>['operations'][number], cwd: string, client?: PlatformClient): Promise<PlatformResult> {
|
|
@@ -265,6 +269,12 @@ function isRecoveryEligible(operation: ReturnType<typeof readPlatformOperationJo
|
|
|
265
269
|
;
|
|
266
270
|
}
|
|
267
271
|
|
|
272
|
+
function recoveryCandidates(journal: ReturnType<typeof readPlatformOperationJournal>, options: RecoveryOptions) {
|
|
273
|
+
const boundary = options.beforeId ? journal.operations.findIndex((item) => item.id === options.beforeId) : journal.operations.length;
|
|
274
|
+
if (boundary < 0) throw Object.assign(new Error('Current platform operation boundary is unavailable'), { code: 'PLATFORM_OPERATION_BOUNDARY_MISSING' });
|
|
275
|
+
return journal.operations.slice(0, boundary).filter((item) => isRecoveryEligible(item, options.excludeId));
|
|
276
|
+
}
|
|
277
|
+
|
|
268
278
|
async function recoverPlatformOperationsOnce(
|
|
269
279
|
taskRef: string,
|
|
270
280
|
selection: 'required' | 'deferred' | 'all',
|
|
@@ -278,7 +288,9 @@ async function recoverPlatformOperationsOnce(
|
|
|
278
288
|
const value = error as { code?: string; message?: string };
|
|
279
289
|
return result('failed', [], [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
|
|
280
290
|
}
|
|
281
|
-
|
|
291
|
+
let candidates;
|
|
292
|
+
try { candidates = recoveryCandidates(journal, options); }
|
|
293
|
+
catch (error) { const value = error as { code: string; message: string }; return result('blocked', [], [], value); }
|
|
282
294
|
if (!candidates.length) return result('no-op', [], []);
|
|
283
295
|
const head = candidates[0]!;
|
|
284
296
|
if (selection !== 'all' && head.dependency !== selection) {
|
|
@@ -291,8 +303,9 @@ async function recoverPlatformOperationsOnce(
|
|
|
291
303
|
const limit = options.limit === undefined ? candidates.length : Math.max(1, options.limit);
|
|
292
304
|
const recovered: string[] = [];
|
|
293
305
|
const pending: string[] = [];
|
|
306
|
+
let warnings: readonly OperationWarning[] = [];
|
|
294
307
|
const selected = candidates.slice(0, limit);
|
|
295
|
-
for (const
|
|
308
|
+
for (const operation of selected) {
|
|
296
309
|
{
|
|
297
310
|
try {
|
|
298
311
|
recordPlatformOperation({
|
|
@@ -368,7 +381,7 @@ async function recoverPlatformOperationsOnce(
|
|
|
368
381
|
}
|
|
369
382
|
}
|
|
370
383
|
}
|
|
371
|
-
let remote: PlatformResult;
|
|
384
|
+
let remote: PlatformResult & { warnings?: readonly OperationWarning[] };
|
|
372
385
|
try {
|
|
373
386
|
remote = operation.kind === 'issue-create'
|
|
374
387
|
? await verifyIssueCreate(resolved.taskId, operation, resolved.repoRoot)
|
|
@@ -402,7 +415,7 @@ async function recoverPlatformOperationsOnce(
|
|
|
402
415
|
...(operation.pullRequestReview ? { pullRequestReview: operation.pullRequestReview } : {}),
|
|
403
416
|
dependency: operation.dependency,
|
|
404
417
|
state: succeeded ? 'succeeded' : 'unknown',
|
|
405
|
-
lastCode: remote.error?.code ?? latestOperation?.lastCode ?? null
|
|
418
|
+
lastCode: remote.error?.code ?? remote.operations.find((item) => item.status === 'skipped' && item.reasonCode === 'PUSH_REQUIRED')?.reasonCode ?? (succeeded ? null : latestOperation?.lastCode) ?? null
|
|
406
419
|
});
|
|
407
420
|
} catch (error) {
|
|
408
421
|
const value = error as { code?: string; message?: string };
|
|
@@ -412,11 +425,12 @@ async function recoverPlatformOperationsOnce(
|
|
|
412
425
|
pending.push(targetOperation.id);
|
|
413
426
|
return result(remote.status === 'failed' ? 'failed' : 'blocked', recovered, pending, remote.error ?? { code: 'PLATFORM_OPERATION_RECOVERY_UNKNOWN', message: 'Platform operation outcome remains unknown', retryable: true });
|
|
414
427
|
}
|
|
428
|
+
warnings = mergeOperationWarnings(warnings, remote.warnings ?? []);
|
|
415
429
|
recovered.push(targetOperation.id);
|
|
416
430
|
}
|
|
417
431
|
const remaining = candidates.slice(limit).map((operation) => operation.id);
|
|
418
432
|
pending.push(...remaining);
|
|
419
|
-
return result(pending.length ? 'blocked' : recovered.length ? 'applied' : 'no-op', recovered, pending, pending.length ? { code: 'PLATFORM_OPERATION_RECOVERY_PENDING', message: 'Platform operations remain pending after the recovery budget', retryable: true } : null);
|
|
433
|
+
return result(pending.length ? 'blocked' : recovered.length ? 'applied' : 'no-op', recovered, pending, pending.length ? { code: 'PLATFORM_OPERATION_RECOVERY_PENDING', message: 'Platform operations remain pending after the recovery budget', retryable: true } : null, warnings);
|
|
420
434
|
}
|
|
421
435
|
|
|
422
436
|
async function recoverPlatformOperations(
|
|
@@ -432,8 +446,9 @@ async function recoverPlatformOperations(
|
|
|
432
446
|
const value = error as { code?: string; message?: string };
|
|
433
447
|
return result('failed', [], [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
|
|
434
448
|
}
|
|
435
|
-
|
|
436
|
-
|
|
449
|
+
let first;
|
|
450
|
+
try { first = recoveryCandidates(initial, options); }
|
|
451
|
+
catch (error) { const value = error as { code: string; message: string }; return result('blocked', [], [], value); }
|
|
437
452
|
if (!first.length) return result('no-op', [], []);
|
|
438
453
|
const operationLimit = options.limit === undefined ? first.length : Math.max(1, options.limit);
|
|
439
454
|
const attemptLimit = options.attempts ?? DEFAULT_RECOVERY_ATTEMPTS;
|
|
@@ -441,47 +456,56 @@ async function recoverPlatformOperations(
|
|
|
441
456
|
return result('failed', [], [], { code: 'PLATFORM_OPERATION_RECOVERY_PAYLOAD_INVALID', message: 'Recovery attempts must be a positive safe integer', retryable: false });
|
|
442
457
|
}
|
|
443
458
|
const recovered: string[] = [];
|
|
459
|
+
let warnings: readonly OperationWarning[] = [];
|
|
444
460
|
|
|
445
461
|
for (let operationIndex = 0; operationIndex < operationLimit; operationIndex += 1) {
|
|
446
462
|
let latest;
|
|
447
463
|
try { latest = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot); }
|
|
448
464
|
catch (error) {
|
|
449
465
|
const value = error as { code?: string; message?: string };
|
|
450
|
-
return result('failed', recovered, [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false });
|
|
466
|
+
return result('failed', recovered, [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || String(error), retryable: false }, warnings);
|
|
451
467
|
}
|
|
452
|
-
|
|
468
|
+
let candidates;
|
|
469
|
+
try { candidates = recoveryCandidates(latest, options); }
|
|
470
|
+
catch (error) { const value = error as { code: string; message: string }; return result('blocked', recovered, [], value, warnings); }
|
|
471
|
+
const head = candidates[0];
|
|
453
472
|
if (!head) break;
|
|
454
473
|
if (selection !== 'all' && head.dependency !== selection) {
|
|
455
|
-
const pending =
|
|
474
|
+
const pending = candidates.map((operation) => operation.id);
|
|
456
475
|
return result('blocked', recovered, pending, {
|
|
457
476
|
code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
|
|
458
477
|
message: 'The first unresolved platform operation belongs to another recovery selection',
|
|
459
478
|
retryable: true
|
|
460
|
-
});
|
|
479
|
+
}, warnings);
|
|
461
480
|
}
|
|
462
481
|
let last: RecoveryResult | null = null;
|
|
463
482
|
for (let attempt = 0; attempt < attemptLimit; attempt += 1) {
|
|
464
483
|
last = await recoverPlatformOperationsOnce(taskRef, selection, { ...options, limit: 1 });
|
|
465
484
|
recovered.push(...last.recovered.filter((id) => !recovered.includes(id)));
|
|
485
|
+
warnings = mergeOperationWarnings(warnings, last.warnings ?? []);
|
|
466
486
|
if (last.recovered.length > 0 || last.status === 'applied' || last.status === 'no-op') break;
|
|
467
487
|
}
|
|
468
488
|
if (!last || (last.recovered.length === 0 && last.status !== 'applied' && last.status !== 'no-op')) {
|
|
469
489
|
let current;
|
|
470
490
|
try { current = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot); }
|
|
471
|
-
catch { return result('blocked', recovered, [head.id], last?.error ?? null); }
|
|
472
|
-
|
|
491
|
+
catch { return result('blocked', recovered, [head.id], last?.error ?? null, warnings); }
|
|
492
|
+
let pending: string[];
|
|
493
|
+
try { pending = recoveryCandidates(current, options).map((operation) => operation.id); }
|
|
494
|
+
catch (error) { const value = error as { code: string; message: string }; return result('blocked', recovered, [], value, warnings); }
|
|
473
495
|
return result(last?.status === 'failed' ? 'failed' : 'blocked', recovered, pending, last?.error ?? {
|
|
474
496
|
code: 'PLATFORM_OPERATION_RECOVERY_PENDING',
|
|
475
497
|
message: 'The first queued platform operation remains unresolved after this recovery run',
|
|
476
498
|
retryable: true
|
|
477
|
-
});
|
|
499
|
+
}, warnings);
|
|
478
500
|
}
|
|
479
501
|
}
|
|
480
502
|
let remaining: string[];
|
|
481
|
-
try { remaining = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot)
|
|
482
|
-
catch {
|
|
483
|
-
|
|
484
|
-
remaining.length ? { code: 'PLATFORM_OPERATION_RECOVERY_PENDING', message: 'Platform operations remain queued after this recovery run', retryable: true } : null);
|
|
503
|
+
try { remaining = recoveryCandidates(readPlatformOperationJournal(resolved.taskId, resolved.repoRoot), options).map((operation) => operation.id); }
|
|
504
|
+
catch (error) { const value = error as { code?: string; message?: string }; return result('blocked', recovered, [], { code: value.code || 'PLATFORM_OPERATION_JOURNAL_INVALID', message: value.message || 'Unable to read platform operations' }, warnings); }
|
|
505
|
+
const output = result(remaining.length ? 'blocked' : recovered.length ? 'applied' : 'no-op', recovered, remaining,
|
|
506
|
+
remaining.length ? { code: 'PLATFORM_OPERATION_RECOVERY_PENDING', message: 'Platform operations remain queued after this recovery run', retryable: true } : null, warnings);
|
|
507
|
+
const degraded = readPlatformOperationJournal(resolved.taskId, resolved.repoRoot).operations.filter((item) => recovered.includes(item.id) && item.lastCode === 'PUSH_REQUIRED').map((item) => ({ id: item.id, reasonCode: item.lastCode! }));
|
|
508
|
+
return degraded.length ? { ...output, degraded } : output;
|
|
485
509
|
}
|
|
486
510
|
|
|
487
511
|
export { fieldsMatchExpected, labelsMatchOwnedPrefix, recoverPlatformOperations };
|
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import type { ResourceIdentity } from '../platform/resource-identity.ts';
|
|
2
|
-
import {
|
|
3
|
-
isResourceIdentity,
|
|
4
|
-
parseResourceIdentity,
|
|
5
|
-
resourceIdentityNumber,
|
|
6
|
-
serializeResourceIdentity
|
|
7
|
-
} from '../platform/resource-identity.ts';
|
|
2
|
+
import { parseResourceIdentity, serializeResourceIdentity } from '../platform/resource-identity.ts';
|
|
8
3
|
|
|
9
4
|
const PR_DELIVERY_FACT_KEY = 'pr_delivery_fact';
|
|
10
5
|
|
|
@@ -182,9 +177,5 @@ function buildBoundFact(input: {
|
|
|
182
177
|
}, provenance: { establishedBy: provenanceForSource(input.source) } });
|
|
183
178
|
}
|
|
184
179
|
function factFrontmatterMutation(fact: PrDeliveryFact): { set: { [PR_DELIVERY_FACT_KEY]: string } } { return { set: { [PR_DELIVERY_FACT_KEY]: encodePrDeliveryFact(fact) } }; }
|
|
185
|
-
|
|
186
|
-
return canonicalIdentity(pullRequest);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export { PR_DELIVERY_FACT_KEY, buildBoundFact, buildSkippedFact, buildUnboundFact, decodePrDeliveryFact, encodePrDeliveryFact, factFrontmatterMutation, identityFromPullRequest, readPrDeliveryFact, serializeResourceIdentity };
|
|
180
|
+
export { PR_DELIVERY_FACT_KEY, buildBoundFact, buildSkippedFact, buildUnboundFact, decodePrDeliveryFact, encodePrDeliveryFact, factFrontmatterMutation, readPrDeliveryFact, serializeResourceIdentity };
|
|
190
181
|
export type { CreationOutcome, PrDeliveryBindingSource, PrDeliveryFact, PrDeliveryFactError, PrDeliveryFactReadResult, PrDeliveryIdentity, PrDeliveryIdentityInput, PrDeliveryProvenance };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
|
|
4
|
-
import { appendActivityEntry, locateActivityLog } from './activity-log.ts';
|
|
4
|
+
import { activityWarnings, appendActivityEntry, locateActivityLog } from './activity-log.ts';
|
|
5
5
|
import { CONFIRMATION_COLUMNS, CONFIRMATION_HEADINGS, constraintDigest, parseQualificationConfirmations, parseTaskQualification } from './qualification-audit.ts';
|
|
6
6
|
import type { QualificationConfirmation } from './qualification-audit.ts';
|
|
7
7
|
import { findSectionHeading } from './sections.ts';
|
|
@@ -25,6 +25,7 @@ type QualificationConfirmationResult = {
|
|
|
25
25
|
requestId: string | null;
|
|
26
26
|
operations: readonly TaskOperationSummary[];
|
|
27
27
|
error: { code: string; message: string } | null;
|
|
28
|
+
warnings?: readonly { code: string; message: string }[];
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
function failed(code: string, message: string, taskId: string | null = null): QualificationConfirmationResult {
|
|
@@ -73,13 +74,13 @@ function applyQualificationConfirmation(request: QualificationConfirmationReques
|
|
|
73
74
|
const previous = existing.confirmations.find((row) => row.qcrId === constraint.approvalEvidence);
|
|
74
75
|
if (operation === 'confirm' && constraint.status === 'confirmed') {
|
|
75
76
|
if (previous?.constraintId === constraint.constraintId && previous.approvedDigest === constraint.digest && previous.rationale === request.rationale.trim()) {
|
|
76
|
-
return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: previous.qcrId, requestId: previous.requestId, operations: [], error: null };
|
|
77
|
+
return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: previous.qcrId, requestId: previous.requestId, operations: [], error: null, warnings: activityWarnings(content) };
|
|
77
78
|
}
|
|
78
79
|
return failed('QUALIFICATION_CONFIRMATION_CONFLICT', `constraint '${request.constraintId}' is already confirmed with different evidence`, resolved.taskId);
|
|
79
80
|
}
|
|
80
81
|
const transitionStatus = operation === 'supersede' ? 'superseded' : 'open';
|
|
81
82
|
if (operation !== 'confirm' && constraint.status === transitionStatus && !constraint.approvalEvidence) {
|
|
82
|
-
return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: null, requestId: null, operations: [], error: null };
|
|
83
|
+
return { status: 'no-op', changed: false, taskId: resolved.taskId, qcrId: null, requestId: null, operations: [], error: null, warnings: activityWarnings(content) };
|
|
83
84
|
}
|
|
84
85
|
if (operation !== 'confirm' && constraint.status !== 'confirmed') return failed('QUALIFICATION_CONFIRMATION_REQUIRED', `constraint '${request.constraintId}' is not confirmed`, resolved.taskId);
|
|
85
86
|
if (parsed.qualification.constraintDigest !== request.expectedDigest) return failed('QUALIFICATION_DIGEST_CONFLICT', 'constraint digest changed before human confirmation', resolved.taskId);
|
|
@@ -99,18 +100,17 @@ function applyQualificationConfirmation(request: QualificationConfirmationReques
|
|
|
99
100
|
} : null;
|
|
100
101
|
const confirmations = confirmation ? [...existing.confirmations, confirmation] : existing.confirmations;
|
|
101
102
|
const activity = locateActivityLog(content);
|
|
102
|
-
if (!activity) return failed('QUALIFICATION_ACTIVITY_MISSING', 'task has no unique Activity Log section', resolved.taskId);
|
|
103
103
|
const mutations: Parameters<typeof writeTask>[0]['mutations'][number][] = [
|
|
104
104
|
{
|
|
105
105
|
kind: 'table-row', action: 'upsert', sectionAliases: ['约束', 'Constraints'], columns: ['constraint_id', 'statement', 'status', 'authority', 'source', 'evidence', 'derived_from', 'approval_evidence'], keyColumn: 'constraint_id', key: constraint.constraintId,
|
|
106
106
|
values: { statement: constraint.statement, status: operation === 'confirm' ? 'confirmed' : transitionStatus, authority: operation === 'confirm' ? 'human-declared' : constraint.authority, source: constraint.source, evidence: constraint.evidence, derived_from: constraint.derivedFrom.join(','), approval_evidence: qcrId ?? '' }
|
|
107
107
|
},
|
|
108
108
|
...(operation === 'confirm' ? [{ kind: 'section' as const, aliases: CONFIRMATION_HEADINGS, heading: findSectionHeading(content, [...CONFIRMATION_HEADINGS]), body: renderConfirmations(confirmations) }] : []),
|
|
109
|
-
{ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: activity.heading, body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Qualification Confirmation', agent: 'human', note: operation === 'confirm' ? `${request.constraintId} confirmed → ${qcrId}` : `${request.constraintId} ${operation}d` }) }
|
|
109
|
+
...(activity ? [{ kind: 'section' as const, aliases: ['活动日志', 'Activity Log'], heading: activity.heading, body: appendActivityEntry(activity, { time: metadata.timestamp, step: 'Qualification Confirmation', agent: 'human', note: operation === 'confirm' ? `${request.constraintId} confirmed → ${qcrId}` : `${request.constraintId} ${operation}d` }) }] : [])
|
|
110
110
|
];
|
|
111
111
|
const result = writeTask({ taskRef: resolved.taskId, expectedState: 'active', mutations, dryRun: request.dryRun }, { ...options, taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state }, metadataProvider: () => metadata });
|
|
112
112
|
if (result.status === 'failed') return failed(result.error.code, result.error.message, result.taskId);
|
|
113
|
-
return { status: result.status, changed: result.changed, taskId: result.taskId, qcrId, requestId, operations: result.operations, error: null };
|
|
113
|
+
return { status: result.status, changed: result.changed, taskId: result.taskId, qcrId, requestId, operations: result.operations, error: null, warnings: activityWarnings(content) };
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
export { applyQualificationConfirmation };
|
package/lib/task/resolve-ref.ts
CHANGED
|
@@ -114,28 +114,6 @@ function findTaskMd(
|
|
|
114
114
|
return archived ? { taskMdPath: archived, state: 'archive' } : null;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
/**
|
|
118
|
-
* Enumerate every task directory under the flat workspace states
|
|
119
|
-
* (active / blocked / completed) — archive is intentionally excluded so a
|
|
120
|
-
* full-tree scan never pulls in cold data. Ordered by state, then task id
|
|
121
|
-
* ascending, giving callers a deterministic traversal.
|
|
122
|
-
*/
|
|
123
|
-
function enumerateTaskDirs(repoRoot: string): { taskId: string; taskDir: string }[] {
|
|
124
|
-
const out: { taskId: string; taskDir: string }[] = [];
|
|
125
|
-
const workspaceRoot = taskRoot(repoRoot);
|
|
126
|
-
for (const sub of FLAT_WORKSPACE_DIRS) {
|
|
127
|
-
const base = path.join(workspaceRoot, sub);
|
|
128
|
-
if (!fs.existsSync(base)) continue;
|
|
129
|
-
for (const entry of fs.readdirSync(base).sort()) {
|
|
130
|
-
if (!TASK_ID_RE.test(entry)) continue;
|
|
131
|
-
const taskDir = path.join(base, entry);
|
|
132
|
-
if (!fs.existsSync(path.join(taskDir, 'task.md'))) continue;
|
|
133
|
-
out.push({ taskId: entry, taskDir });
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return out;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
117
|
/**
|
|
140
118
|
* Enumerate every task directory, including the cold YYYY/MM/DD archive.
|
|
141
119
|
* Invalid directory names, symlinks and entries without task.md are ignored;
|
|
@@ -364,7 +342,6 @@ export {
|
|
|
364
342
|
resolveTaskRef,
|
|
365
343
|
resolveTaskContext,
|
|
366
344
|
detectRepoRoot,
|
|
367
|
-
enumerateTaskDirs,
|
|
368
345
|
enumerateAllTaskDirs,
|
|
369
346
|
locateHotTaskDirs,
|
|
370
347
|
TASK_ID_RE
|
|
@@ -4,14 +4,14 @@ export function parseReviewCommand(args: readonly string[]): ReviewFinalizationR
|
|
|
4
4
|
if (!args[0] || !['preflight', 'finalize-summary'].includes(args[1] ?? '')) throw new Error('task ref and a supported review operation are required');
|
|
5
5
|
const values: Record<string, string | boolean> = {};
|
|
6
6
|
const flags = {
|
|
7
|
-
'--stage': 'stage', '--artifact': 'artifact', '--
|
|
7
|
+
'--stage': 'stage', '--artifact': 'artifact', '--dry-run': 'dryRun'
|
|
8
8
|
} as const;
|
|
9
9
|
for (let index = 2; index < args.length; index += 1) {
|
|
10
10
|
const flag = args[index]!;
|
|
11
11
|
if (!Object.hasOwn(flags, flag)) throw new Error(`unknown option '${flag}'`);
|
|
12
12
|
const key = flags[flag as keyof typeof flags];
|
|
13
13
|
if (Object.hasOwn(values, key)) throw new Error(`duplicate option '${flag}'`);
|
|
14
|
-
const value = flag === '--
|
|
14
|
+
const value = flag === '--dry-run' ? true : args[++index];
|
|
15
15
|
if (!value || typeof value === 'string' && value.startsWith('--')) throw new Error(`option '${flag}' requires a value`);
|
|
16
16
|
values[key] = value;
|
|
17
17
|
}
|
|
@@ -9,7 +9,6 @@ import type { LedgerStageStatus, ReviewStage } from './ledger.ts';
|
|
|
9
9
|
import { finalizeReviewSummaryContent } from './review-artifacts.ts';
|
|
10
10
|
import { inspectDecisionDetailDuplicates } from './decision-details.ts';
|
|
11
11
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
12
|
-
import { validateLifecycleExecution } from './lifecycle-execution.ts';
|
|
13
12
|
import type { ResolveTaskRefErrorCode } from './resolve-ref.ts';
|
|
14
13
|
import { getArtifactSchema } from './artifact-schema.ts';
|
|
15
14
|
import { validateQualificationAudit } from './qualification-audit.ts';
|
|
@@ -36,7 +35,6 @@ type ReviewFinalizationRequest = {
|
|
|
36
35
|
taskRef: string;
|
|
37
36
|
stage: string;
|
|
38
37
|
artifact: string;
|
|
39
|
-
orchestrated?: boolean;
|
|
40
38
|
dryRun?: boolean;
|
|
41
39
|
};
|
|
42
40
|
type ReviewFinalizationResult = {
|
|
@@ -144,13 +142,6 @@ function preflightReviewSummary(
|
|
|
144
142
|
content = fs.readFileSync(validated.artifact.path, 'utf8');
|
|
145
143
|
} catch (error) { return preflightFailed(request, 'REVIEW_ARTIFACT_NOT_REGULAR', String(error), resolved.taskId); }
|
|
146
144
|
|
|
147
|
-
const execution = validateLifecycleExecution(request.taskRef, {
|
|
148
|
-
mode: request.orchestrated ? 'orchestrated' : 'standalone',
|
|
149
|
-
identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
|
|
150
|
-
dryRun: request.dryRun
|
|
151
|
-
}, { repoRoot: options.repoRoot });
|
|
152
|
-
if (!execution.ok) return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`, resolved.taskId);
|
|
153
|
-
|
|
154
145
|
const artifactSha256 = sha256Content(content);
|
|
155
146
|
const semanticDigest = canonicalSemanticDigest(content);
|
|
156
147
|
const detail = inspectDecisionDetailDuplicates(content);
|
|
@@ -211,15 +202,6 @@ function prepareReviewSummaryCandidate(
|
|
|
211
202
|
catch (error) { return reject('REVIEW_ARTIFACT_NOT_REGULAR', String(error)); }
|
|
212
203
|
const artifactSha256 = sha256Content(artifactContent);
|
|
213
204
|
const semanticDigest = canonicalSemanticDigest(artifactContent);
|
|
214
|
-
const digests = { artifactSha256, semanticDigest };
|
|
215
|
-
const execution = validateLifecycleExecution(request.taskRef, {
|
|
216
|
-
mode: request.orchestrated ? 'orchestrated' : 'standalone',
|
|
217
|
-
identity: { stage: spec.family, round: parsed.round, artifact: request.artifact, role: 'reviewer' },
|
|
218
|
-
dryRun: request.dryRun
|
|
219
|
-
}, { repoRoot: options.repoRoot });
|
|
220
|
-
if (!execution.ok) {
|
|
221
|
-
return reject('REVIEW_PROVENANCE_INVALID', `${execution.error?.code ?? 'ORCHESTRATION_PROVENANCE_MISMATCH'}: ${execution.error?.message ?? 'orchestration provenance validation failed'}`);
|
|
222
|
-
}
|
|
223
205
|
try {
|
|
224
206
|
const ledger = parseLedgerDocument(taskContent);
|
|
225
207
|
if (!ledger.present) return reject('REVIEW_LEDGER_INVALID', `${LEDGER_SECTION_MISSING_CODE}: ${LEDGER_SECTION_MISSING_MESSAGE}`);
|
|
@@ -259,14 +241,6 @@ function prepareReviewSummaryCandidate(
|
|
|
259
241
|
};
|
|
260
242
|
}
|
|
261
243
|
|
|
262
|
-
function commitReviewSummaryProvenance(
|
|
263
|
-
prepared: ReviewSummaryCandidatePreparation,
|
|
264
|
-
repoRoot: string,
|
|
265
|
-
options: Readonly<{ afterPublish?: () => void }> = {}
|
|
266
|
-
): ReviewFinalizationResult {
|
|
267
|
-
return prepared.result;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
244
|
function replaceCurrentArtifactAtomically(file: string, content: string): void {
|
|
271
245
|
const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${randomUUID()}.tmp`);
|
|
272
246
|
try {
|
|
@@ -316,7 +290,7 @@ function finalizeReviewSummary(
|
|
|
316
290
|
}
|
|
317
291
|
}
|
|
318
292
|
|
|
319
|
-
export { finalizeReviewSummary, preflightReviewSummary, prepareReviewSummaryCandidate
|
|
293
|
+
export { finalizeReviewSummary, preflightReviewSummary, prepareReviewSummaryCandidate };
|
|
320
294
|
export type {
|
|
321
295
|
ReviewFinalizationError,
|
|
322
296
|
ReviewFinalizationErrorCode,
|
|
@@ -3,13 +3,6 @@ import path from "node:path";
|
|
|
3
3
|
|
|
4
4
|
import { inspectArtifactDirectory } from "./artifact-lifecycle.ts";
|
|
5
5
|
|
|
6
|
-
type ReviewedGitTree = string & { readonly __reviewedGitTree: unique symbol };
|
|
7
|
-
const REVIEWED_TREE_RE = /^[a-f0-9]{40,64}$/;
|
|
8
|
-
|
|
9
|
-
export function parseReviewedGitTree(value: string): ReviewedGitTree | null {
|
|
10
|
-
return REVIEWED_TREE_RE.test(value) ? value as ReviewedGitTree : null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
6
|
// Paths excluded from the post-review coverage gate. Fail-closed: the gate
|
|
14
7
|
// covers ALL tracked changes by default. This default denylist is EMPTY —
|
|
15
8
|
// projects opt into exclusions explicitly via review.post_review_exclude_globs.
|
|
@@ -87,7 +80,6 @@ export function extractReviewedSnapshotTree(content: string): string {
|
|
|
87
80
|
return match ? match[1]!.trim().replace(/`/g, "") : "";
|
|
88
81
|
}
|
|
89
82
|
|
|
90
|
-
export type { ReviewedGitTree };
|
|
91
83
|
|
|
92
84
|
export function parseReviewVerdict(content: string): string {
|
|
93
85
|
const match = String(content).match(/^[-*]?\s*\*\*(?:总体结论|Overall Verdict)\*\*[::]\s*(.*?)\s*$/m);
|
package/lib/task/secure-fs.ts
CHANGED
|
@@ -3,7 +3,6 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
|
|
5
5
|
const NO_FOLLOW = fs.constants.O_NOFOLLOW ?? 0;
|
|
6
|
-
const DIRECTORY = fs.constants.O_DIRECTORY ?? 0;
|
|
7
6
|
|
|
8
7
|
export class SecureFileError extends Error {
|
|
9
8
|
readonly code: string;
|
|
@@ -40,10 +39,6 @@ function assertNoSymlinkAncestors(candidate: string): void {
|
|
|
40
39
|
}
|
|
41
40
|
}
|
|
42
41
|
|
|
43
|
-
export async function readStableFile(candidate: string, options: Readonly<{ maxBytes: number; expectedSha256?: string }>): Promise<StableFile> {
|
|
44
|
-
return readStableFileSync(candidate, options);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
42
|
export function readStableFileSync(candidate: string, options: Readonly<{ maxBytes: number; expectedSha256?: string }>): StableFile {
|
|
48
43
|
if (!path.isAbsolute(candidate)) conflict('candidate path must be absolute and terminal');
|
|
49
44
|
assertNoSymlinkAncestors(candidate);
|
|
@@ -74,40 +69,3 @@ export function readStableFileSync(candidate: string, options: Readonly<{ maxByt
|
|
|
74
69
|
if (descriptor !== undefined) fs.closeSync(descriptor);
|
|
75
70
|
}
|
|
76
71
|
}
|
|
77
|
-
|
|
78
|
-
export async function writeAtomicFile(target: string, bytes: Buffer, mode = 0o600, expectedSha256?: string): Promise<void> {
|
|
79
|
-
const parent = path.dirname(target);
|
|
80
|
-
let current = path.resolve(parent);
|
|
81
|
-
while (true) {
|
|
82
|
-
let stat: fs.Stats;
|
|
83
|
-
try { stat = await fs.promises.lstat(current); }
|
|
84
|
-
catch { throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', 'authoritative parent directory is missing'); }
|
|
85
|
-
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', 'authoritative parent directory is not a real directory');
|
|
86
|
-
const next = path.dirname(current);
|
|
87
|
-
if (next === current) break;
|
|
88
|
-
current = next;
|
|
89
|
-
}
|
|
90
|
-
try {
|
|
91
|
-
const existing = await fs.promises.lstat(target);
|
|
92
|
-
if (existing.isSymbolicLink() || !existing.isFile()) throw new SecureFileError('TASK_ARTIFACT_WRITE_CONFLICT', 'authoritative target is not a regular file');
|
|
93
|
-
} catch (error) {
|
|
94
|
-
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
|
|
95
|
-
}
|
|
96
|
-
const temporary = path.join(parent, `.${path.basename(target)}.${process.pid}.${Date.now()}.tmp`);
|
|
97
|
-
let handle: fs.promises.FileHandle | undefined;
|
|
98
|
-
try {
|
|
99
|
-
handle = await fs.promises.open(temporary, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL, mode);
|
|
100
|
-
await handle.writeFile(bytes);
|
|
101
|
-
await handle.sync();
|
|
102
|
-
await handle.chmod(mode);
|
|
103
|
-
await handle.close();
|
|
104
|
-
handle = undefined;
|
|
105
|
-
if (expectedSha256 !== undefined) readStableFileSync(target, { maxBytes: 1024 * 1024, expectedSha256 });
|
|
106
|
-
fs.renameSync(temporary, target);
|
|
107
|
-
const directory = await fs.promises.open(parent, fs.constants.O_RDONLY | DIRECTORY);
|
|
108
|
-
try { await directory.sync(); } finally { await directory.close(); }
|
|
109
|
-
} finally {
|
|
110
|
-
await handle?.close().catch(() => undefined);
|
|
111
|
-
await fs.promises.rm(temporary, { force: true }).catch(() => undefined);
|
|
112
|
-
}
|
|
113
|
-
}
|
package/lib/task/short-id.ts
CHANGED
|
@@ -447,15 +447,6 @@ function readRegistry(repoRoot: string): RegistrySchema | null {
|
|
|
447
447
|
}
|
|
448
448
|
}
|
|
449
449
|
|
|
450
|
-
function readBranchFromTaskMd(repoRoot: string, taskId: string): string | null {
|
|
451
|
-
const taskMdPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId, 'task.md');
|
|
452
|
-
if (!fs.existsSync(taskMdPath)) return null;
|
|
453
|
-
const content = fs.readFileSync(taskMdPath, 'utf8');
|
|
454
|
-
const m = content.match(/^branch:\s*(.+)$/m);
|
|
455
|
-
if (!m || !m[1]) return null;
|
|
456
|
-
return m[1].trim().replace(/^(["'])(.*)\1$/, '$2');
|
|
457
|
-
}
|
|
458
|
-
|
|
459
450
|
function loadShortIdByTaskId(repoRoot: string): Map<string, string> {
|
|
460
451
|
const registry = readRegistry(repoRoot);
|
|
461
452
|
const map = new Map<string, string>();
|
|
@@ -527,44 +518,10 @@ function inspectShortIdRegistry(
|
|
|
527
518
|
}
|
|
528
519
|
}
|
|
529
520
|
|
|
530
|
-
/**
|
|
531
|
-
* Resolve a branch to its active-task short id (`NN`), or `null` when no
|
|
532
|
-
* active task is bound to that branch.
|
|
533
|
-
*
|
|
534
|
-
* Two-state semantics: this only consults the active registry
|
|
535
|
-
* (`active/.short-ids.json`) plus each `active/{taskId}/task.md`. Tasks moved
|
|
536
|
-
* to completed/blocked/cancelled/archive have already released their short id,
|
|
537
|
-
* so their branches return `null` — in `ai sandbox ls` that surfaces as `-`,
|
|
538
|
-
* meaning the sandbox is free to remove.
|
|
539
|
-
*/
|
|
540
|
-
function lookupShortIdByBranch(
|
|
541
|
-
branch: string,
|
|
542
|
-
repoRoot: string,
|
|
543
|
-
_opts?: { shortIdLength?: number }
|
|
544
|
-
): string | null {
|
|
545
|
-
const registry = readRegistry(repoRoot);
|
|
546
|
-
if (!registry) return null;
|
|
547
|
-
const matches: string[] = [];
|
|
548
|
-
for (const [key, taskId] of Object.entries(registry.ids)) {
|
|
549
|
-
const taskBranch = readBranchFromTaskMd(repoRoot, taskId);
|
|
550
|
-
if (taskBranch && taskBranch === branch) {
|
|
551
|
-
matches.push(key);
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
if (matches.length === 0) return null;
|
|
555
|
-
if (matches.length > 1) {
|
|
556
|
-
process.stderr.write(
|
|
557
|
-
`Warning: branch '${branch}' is bound to multiple active tasks: ${matches.join(', ')}; using ${matches[0]}\n`
|
|
558
|
-
);
|
|
559
|
-
}
|
|
560
|
-
return matches[0]!;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
521
|
export {
|
|
564
522
|
normalizeShortIdInput,
|
|
565
523
|
isRemovedHashShortIdInput,
|
|
566
524
|
resolveShortIdReadOnly,
|
|
567
|
-
lookupShortIdByBranch,
|
|
568
525
|
loadShortIdByTaskId,
|
|
569
526
|
inspectShortIdRegistry,
|
|
570
527
|
configuredShortIdLength,
|