@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
package/lib/sandbox/shell.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import type { ExecFileSyncOptions, StdioOptions,
|
|
4
|
+
import type { ExecFileSyncOptions, StdioOptions, SpawnSyncReturns } from 'node:child_process';
|
|
5
5
|
import { getAdapter } from './engines/index.ts';
|
|
6
6
|
|
|
7
7
|
const DEFAULT_TIMEOUT_MS = 60 * 60 * 1000;
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
1
|
import fs from 'node:fs';
|
|
3
2
|
import path from 'node:path';
|
|
4
|
-
import { isRemovedHashShortIdInput } from '../task/short-id.ts';
|
|
5
3
|
import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.ts';
|
|
6
4
|
|
|
7
5
|
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
8
|
-
const SHORT_ID_RE = /^\d+$/;
|
|
9
6
|
export const TASK_WORKSPACE_STATES = ['active', 'completed', 'blocked', 'archive'] as const;
|
|
10
7
|
export type TaskWorkspaceState = typeof TASK_WORKSPACE_STATES[number];
|
|
11
8
|
export type TaskWorkspace = Readonly<{
|
|
@@ -15,39 +12,10 @@ export type TaskWorkspace = Readonly<{
|
|
|
15
12
|
taskMd: string;
|
|
16
13
|
}>;
|
|
17
14
|
|
|
18
|
-
function resolveShortIdStrict(arg: string, repoRoot: string): string {
|
|
19
|
-
const scriptPath = path.join(repoRoot, '.agents', 'scripts', 'task-short-id.js');
|
|
20
|
-
if (!fs.existsSync(scriptPath)) {
|
|
21
|
-
throw new Error(
|
|
22
|
-
`Short id '${arg}' provided but task-short-id.js script is missing at ${scriptPath}`
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
const result = spawnSync('node', [scriptPath, 'resolve', arg], { encoding: 'utf8', cwd: repoRoot });
|
|
26
|
-
if (result.status !== 0) {
|
|
27
|
-
throw new Error(
|
|
28
|
-
`Short id '${arg}' not found in active task registry: ${(result.stderr || '').trim()}`
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
return result.stdout.trim();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
15
|
function stripQuotes(value: string): string {
|
|
35
16
|
return value.replace(/^(["'])(.*)\1$/, '$2');
|
|
36
17
|
}
|
|
37
18
|
|
|
38
|
-
function readTaskContent(repoRoot: string, taskId: string): { content: string; state: TaskWorkspaceState; taskMd: string } {
|
|
39
|
-
const taskRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
|
|
40
|
-
for (const state of TASK_WORKSPACE_STATES) {
|
|
41
|
-
const taskPath = state === 'archive'
|
|
42
|
-
? path.join(taskRoot, state, ...findArchiveTaskParts(repoRoot, taskId), 'local', 'task.md')
|
|
43
|
-
: path.join(taskRoot, state, taskId, 'task.md');
|
|
44
|
-
if (fs.existsSync(taskPath)) {
|
|
45
|
-
return { content: fs.readFileSync(taskPath, 'utf8'), state, taskMd: taskPath };
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
throw new Error(`Task not found: ${taskId}`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
19
|
function taskWorkspaceCandidates(repoRoot: string, taskId: string): TaskWorkspace[] {
|
|
52
20
|
const taskRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
|
|
53
21
|
const hot = TASK_WORKSPACE_STATES.filter((state) => state !== 'archive').flatMap((state) => {
|
|
@@ -103,22 +71,6 @@ function resolveBranchFromTaskContent(content: string, taskId: string): string {
|
|
|
103
71
|
throw new Error(`Task ${taskId} has no branch field in task.md`);
|
|
104
72
|
}
|
|
105
73
|
|
|
106
|
-
export function resolveTaskBranch(arg: string, repoRoot: string): string {
|
|
107
|
-
if (isRemovedHashShortIdInput(arg)) {
|
|
108
|
-
throw new Error(`Invalid task short id '${arg}': task short ids must use bare digits`);
|
|
109
|
-
}
|
|
110
|
-
if (SHORT_ID_RE.test(arg)) {
|
|
111
|
-
const taskId = resolveShortIdStrict(arg, repoRoot);
|
|
112
|
-
const { content } = readTaskContent(repoRoot, taskId);
|
|
113
|
-
return resolveBranchFromTaskContent(content, taskId);
|
|
114
|
-
}
|
|
115
|
-
if (!TASK_ID_RE.test(arg)) {
|
|
116
|
-
return arg;
|
|
117
|
-
}
|
|
118
|
-
const { content } = readTaskContent(repoRoot, arg);
|
|
119
|
-
return resolveBranchFromTaskContent(content, arg);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
74
|
export function resolveTaskWorkspace(taskId: string, repoRoot: string): TaskWorkspace {
|
|
123
75
|
if (!TASK_ID_RE.test(taskId)) throw new Error(`Invalid task id: ${taskId}`);
|
|
124
76
|
const matches = taskWorkspaceCandidates(repoRoot, taskId);
|
package/lib/sandbox/tools.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { createSandboxCapabilityPlan } from './agent-client-reconciler.ts';
|
|
3
|
-
import { safeNameCandidates
|
|
3
|
+
import { safeNameCandidates } from './constants.ts';
|
|
4
4
|
import { hostJoin } from './engines/wsl2-paths.ts';
|
|
5
5
|
import type { SandboxTool, SandboxToolInstall } from './tool-types.ts';
|
|
6
6
|
|
|
@@ -287,10 +287,6 @@ export function resolveTools(config: ToolsConfig): SandboxTool[] {
|
|
|
287
287
|
return [...createSandboxCapabilityPlan(config).tools];
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
-
export function toolConfigDir(tool: SandboxTool, project: string, branch: string): string {
|
|
291
|
-
return hostJoin(tool.sandboxBase, project, sanitizeBranchName(branch));
|
|
292
|
-
}
|
|
293
|
-
|
|
294
290
|
export function toolConfigDirCandidates(tool: SandboxTool, project: string, branch: string): string[] {
|
|
295
291
|
return safeNameCandidates(branch).map((name) => hostJoin(tool.sandboxBase, project, name));
|
|
296
292
|
}
|
|
@@ -59,7 +59,7 @@ export type SandboxControlSetup = Readonly<{
|
|
|
59
59
|
controlRootId: string;
|
|
60
60
|
}>;
|
|
61
61
|
|
|
62
|
-
export type SandboxControlManifestDraft = Readonly<Omit<SandboxControlManifest, 'containerIdentity' | 'engine'
|
|
62
|
+
export type SandboxControlManifestDraft = Readonly<Omit<SandboxControlManifest, 'containerIdentity' | 'engine'> & {
|
|
63
63
|
engine: string;
|
|
64
64
|
}>;
|
|
65
65
|
|
|
@@ -355,15 +355,13 @@ export function finalizeSandboxControlManifest(
|
|
|
355
355
|
engine: string;
|
|
356
356
|
id: string;
|
|
357
357
|
labels: Readonly<Record<string, string>>;
|
|
358
|
-
authorityEvidence: SandboxControlManifest['authorityEvidence'];
|
|
359
358
|
}>
|
|
360
359
|
): SandboxControlManifest {
|
|
361
360
|
if (!identity.engine || !identity.id) throw new Error('SANDBOX_CONTROL_CONTAINER_ID_INVALID');
|
|
362
361
|
const manifest: SandboxControlManifest = {
|
|
363
362
|
...setup.manifestDraft,
|
|
364
363
|
engine: identity.engine,
|
|
365
|
-
containerIdentity: { id: identity.id, labels: { ...identity.labels } }
|
|
366
|
-
authorityEvidence: identity.authorityEvidence
|
|
364
|
+
containerIdentity: { id: identity.id, labels: { ...identity.labels } }
|
|
367
365
|
};
|
|
368
366
|
const persistedManifest = {
|
|
369
367
|
...manifest,
|
package/lib/server/display.ts
CHANGED
|
@@ -32,10 +32,6 @@ export function markdownMessage(markdown: string): DisplayMessage {
|
|
|
32
32
|
return { kind: 'markdown', markdown };
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export function tableMessage(columns: string[], rows: string[][], title?: string): DisplayMessage {
|
|
36
|
-
return { kind: 'table', title, columns, rows };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
35
|
export function statusCard(
|
|
40
36
|
title: string,
|
|
41
37
|
tone: DisplayTone,
|
|
@@ -59,16 +55,6 @@ export function streamEvent(
|
|
|
59
55
|
return message;
|
|
60
56
|
}
|
|
61
57
|
|
|
62
|
-
export function commandResult(
|
|
63
|
-
title: string,
|
|
64
|
-
exitCode: number | null,
|
|
65
|
-
signal: NodeJS.Signals | null,
|
|
66
|
-
stdout?: string,
|
|
67
|
-
stderr?: string
|
|
68
|
-
): DisplayMessage {
|
|
69
|
-
return { kind: 'command-result', title, exitCode, signal, stdout, stderr };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
58
|
export function normalizeOutbound(message: OutboundMessage): DisplayMessage {
|
|
73
59
|
return typeof message === 'string' ? textMessage(message) : message;
|
|
74
60
|
}
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
removePidFileIfMatches,
|
|
13
13
|
writePidRecord
|
|
14
14
|
} from './process-state.ts';
|
|
15
|
-
import type { PidRecord, ProcessIdentity
|
|
15
|
+
import type { PidRecord, ProcessIdentity } from './process-state.ts';
|
|
16
16
|
|
|
17
17
|
export { isProcessAlive } from './process-state.ts';
|
|
18
18
|
|
|
@@ -2,7 +2,8 @@ import fs from 'node:fs';
|
|
|
2
2
|
|
|
3
3
|
import { normalizeAgentToken } from '../agent-clients/tokens.ts';
|
|
4
4
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
5
|
-
import { locateActivityLog, appendActivityEntry } from './activity-log.ts';
|
|
5
|
+
import { locateActivityLog, appendActivityEntry, inspectActivityLog } from './activity-log.ts';
|
|
6
|
+
import { lifecycleExecutions, prReviewExecution, serializeLifecycleExecution } from './lifecycle-execution.ts';
|
|
6
7
|
import {
|
|
7
8
|
assertWritableInventory,
|
|
8
9
|
buildArtifactLinkSection,
|
|
@@ -82,6 +83,7 @@ export type PrReviewActivityResult = {
|
|
|
82
83
|
artifact: string | null;
|
|
83
84
|
operations: readonly TaskOperationSummary[];
|
|
84
85
|
error: { code: string; message: string } | null;
|
|
86
|
+
warnings?: readonly { code: string; message: string }[];
|
|
85
87
|
};
|
|
86
88
|
|
|
87
89
|
export type PrReviewInspectionResult = {
|
|
@@ -102,7 +104,6 @@ type RoundState = ArtifactRecord & {
|
|
|
102
104
|
};
|
|
103
105
|
|
|
104
106
|
const HEAD_RE = /^[0-9a-f]{40}$/;
|
|
105
|
-
const REVIEW_ACTION_RE = /^Review PR \(Round ([1-9]\d*)\)( \[started\])?$/;
|
|
106
107
|
const VERDICTS: ReadonlySet<string> = new Set(['approved', 'changes-requested', 'commented']);
|
|
107
108
|
const OUTCOMES: ReadonlySet<string> = new Set(['aborted', 'superseded']);
|
|
108
109
|
const SUCCESS_STATUSES: ReadonlySet<PrReviewArtifactStatus> = new Set(['applied', 'no-op']);
|
|
@@ -181,48 +182,28 @@ function readSnapshot(taskRef: string, repoRoot?: string):
|
|
|
181
182
|
} catch (error) {
|
|
182
183
|
return { ok: false, code: 'TASK_READ_FAILED', message: error instanceof Error ? error.message : String(error), taskId: resolved.taskId };
|
|
183
184
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const knownRounds = new Set(inventory.artifacts.map((artifact) => artifact.round));
|
|
188
|
-
for (const entry of activity.entries) {
|
|
189
|
-
const match = REVIEW_ACTION_RE.exec(entry.step);
|
|
190
|
-
if (match && !knownRounds.has(Number(match[1]))) {
|
|
191
|
-
return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: `Activity Log round ${match[1]} has no canonical pr-review artifact`, taskId: resolved.taskId };
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
185
|
+
let pr;
|
|
186
|
+
try { pr = prReviewExecution(content); }
|
|
187
|
+
catch (error) { return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: String(error), taskId: resolved.taskId }; }
|
|
195
188
|
const states: RoundState[] = [];
|
|
196
189
|
for (const identity of inventory.artifacts) {
|
|
197
190
|
const parsed = parseArtifactRecord(identity);
|
|
198
191
|
if ('code' in parsed) return { ok: false, ...parsed, taskId: resolved.taskId };
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
192
|
+
const started = pr.open?.artifact === identity.name ? pr.open : null;
|
|
193
|
+
const terminals = pr.terminals.filter((entry) => entry.artifact === identity.name);
|
|
194
|
+
if (terminals.length > 1) return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: `duplicate PR review terminal for ${identity.name}`, taskId: resolved.taskId };
|
|
195
|
+
const terminal = terminals[0];
|
|
196
|
+
if (terminal) {
|
|
197
|
+
const matches = (terminal.note.startsWith('Verdict: ') && LOCAL_COMPLETION_STATUSES.has(parsed.status))
|
|
198
|
+
|| (terminal.note.startsWith('Outcome: Aborted, ') && parsed.status === 'aborted')
|
|
199
|
+
|| (terminal.note.startsWith('Outcome: Superseded, ') && parsed.status === 'superseded')
|
|
200
|
+
|| (terminal.note.startsWith('receipt ') && SUCCESS_STATUSES.has(parsed.status));
|
|
201
|
+
if (!matches) return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: `terminal note conflicts with artifact status ${parsed.status}`, taskId: resolved.taskId };
|
|
207
202
|
}
|
|
208
|
-
if (terminal.
|
|
209
|
-
|
|
210
|
-
const terminalMatchesStatus =
|
|
211
|
-
(note.startsWith('Verdict: ') && LOCAL_COMPLETION_STATUSES.has(parsed.status))
|
|
212
|
-
|| (note.startsWith('Outcome: Aborted, ') && parsed.status === 'aborted')
|
|
213
|
-
|| (note.startsWith('Outcome: Superseded, ') && parsed.status === 'superseded')
|
|
214
|
-
|| (note.startsWith('receipt ') && SUCCESS_STATUSES.has(parsed.status));
|
|
215
|
-
if (!terminalMatchesStatus) {
|
|
216
|
-
return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: `${base} terminal note conflicts with artifact status ${parsed.status}`, taskId: resolved.taskId };
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
states.push({
|
|
220
|
-
...parsed,
|
|
221
|
-
startedAgent: started[0]?.agent ?? null,
|
|
222
|
-
terminalAgent: terminal[0]?.agent ?? null,
|
|
223
|
-
terminalNote: terminal[0]?.note ?? null
|
|
224
|
-
});
|
|
203
|
+
if ((started && started.head !== parsed.head) || (terminal && terminal.head !== parsed.head)) return { ok: false, code: 'ACTIVITY_IDENTITY_MISMATCH', message: `head does not match ${identity.name}`, taskId: resolved.taskId };
|
|
204
|
+
states.push({ ...parsed, startedAgent: started?.agent ?? null, terminalAgent: terminal?.agent ?? null, terminalNote: terminal?.note ?? null });
|
|
225
205
|
}
|
|
206
|
+
if ((pr.open && !states.some((state) => state.identity.name === pr.open!.artifact)) || pr.terminals.some((entry) => !states.some((state) => state.identity.name === entry.artifact))) return { ok: false, code: 'ACTIVITY_STATE_CONFLICT', message: 'PR review execution references a missing artifact', taskId: resolved.taskId };
|
|
226
207
|
|
|
227
208
|
const openStates = states.filter((state) => state.startedAgent !== null && state.terminalNote === null);
|
|
228
209
|
const preparedStates = states.filter((state) => state.status === 'pending' && state.startedAgent === null && state.terminalNote === null);
|
|
@@ -353,11 +334,15 @@ function applyActivityIntent(intent: PrReviewActivityIntent, normalizedAgent: st
|
|
|
353
334
|
} catch (error) {
|
|
354
335
|
return failed(intent, 'METADATA_CAPTURE_FAILED', error instanceof Error ? error.message : String(error), state.taskId);
|
|
355
336
|
}
|
|
356
|
-
const section = locateActivityLog(state.content)
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
337
|
+
const section = locateActivityLog(state.content);
|
|
338
|
+
const pr = prReviewExecution(state.content);
|
|
339
|
+
if (intent.kind === 'pr-review-start') pr.open = { artifact: intent.artifact, head: intent.head, agent: normalizedAgent, startedAt: metadata.timestamp };
|
|
340
|
+
else {
|
|
341
|
+
pr.open = null;
|
|
342
|
+
pr.terminals.push({ artifact: intent.artifact, head: intent.head, agent: normalizedAgent, note });
|
|
343
|
+
}
|
|
344
|
+
const mutations: TaskMutation[] = [{ kind: 'frontmatter', lifecycleExecutionDomain: 'pr-review', set: { lifecycle_execution: serializeLifecycleExecution(lifecycleExecutions(state.content), state.content, pr) } }];
|
|
345
|
+
if (section) mutations.push({ kind: 'section', aliases: ['活动日志', 'Activity Log'], heading: section.heading, body: appendActivityEntry(section, { time: metadata.timestamp, step, agent: normalizedAgent, note }) });
|
|
361
346
|
if (linkArtifact) {
|
|
362
347
|
const link = buildArtifactLinkSection(state.content, roundState.identity);
|
|
363
348
|
mutations.push({ kind: 'section', aliases: link.aliases, heading: link.heading, body: link.body });
|
|
@@ -399,7 +384,14 @@ export function applyPrReviewActivityIntent(intent: PrReviewActivityIntent, opti
|
|
|
399
384
|
if (resolved.state !== 'active') {
|
|
400
385
|
return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
401
386
|
}
|
|
402
|
-
|
|
387
|
+
let warnings: { code: string; message: string }[] = [];
|
|
388
|
+
try {
|
|
389
|
+
const inspected = inspectActivityLog(fs.readFileSync(resolved.taskMdPath, 'utf8'));
|
|
390
|
+
warnings = !inspected.section ? [{ code: 'ACTIVITY_LOG_UNAVAILABLE', message: 'Activity Log is missing or ambiguous' }] : inspected.invalidEntries.length ? [{ code: 'ACTIVITY_LOG_INVALID_ENTRIES', message: 'Activity Log contains malformed entries' }] : [];
|
|
391
|
+
} catch { /* The operation reports read errors. */ }
|
|
392
|
+
const result = applyActivityIntent(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot });
|
|
393
|
+
if (result.status === 'failed') return result;
|
|
394
|
+
return warnings.length ? { ...result, warnings } : result;
|
|
403
395
|
}
|
|
404
396
|
|
|
405
397
|
export type { ActivityIntentOptions };
|
package/lib/task/activity-log.ts
CHANGED
|
@@ -3,13 +3,10 @@ const ENTRY_RE = /^- (\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2}) — \*
|
|
|
3
3
|
const STARTED_SUFFIX_RE = /\s*\[started\]\s*$/;
|
|
4
4
|
const ABORTED_SUFFIX_RE = /\s*\[aborted\]\s*$/;
|
|
5
5
|
const ATTEMPT_RE = /(?:^|;\s*)attempt=([A-Za-z0-9-]{8,64})(?:;|$)/;
|
|
6
|
-
const COMMIT_STARTED_RE = /^started; attempt=([A-Za-z0-9-]{8,64}); baseline=([a-f0-9]{40,64}); agent=([a-z-]+)$/;
|
|
7
|
-
const LIFECYCLE_STEP_RE = /^(?:Complete Manual Validation|(?:Analyze Task|Review Analysis|Plan Task|Review Plan|Code Task|Review Code|Run Manual Validation) \(Round [1-9]\d*(?:(?:, fix for review-code(?:-r[2-9]|-r[1-9]\d+)?\.md)|(?:, decision II-[1-9]\d*))?\))$/;
|
|
8
6
|
|
|
9
7
|
type LogEntry = { time: string; step: string; agent: string; note: string };
|
|
10
8
|
type StepRow = { step: string; agent: string; started: string; done: string; note: string; attempt?: string };
|
|
11
9
|
type ActivityLogSection = { heading: string; body: string; entries: LogEntry[] };
|
|
12
|
-
type CommitAttempt = Readonly<{ attempt: string; baseline: string; agent: string }>;
|
|
13
10
|
|
|
14
11
|
function parseActivityLog(content: string): { sectionFound: boolean; entries: LogEntry[] } {
|
|
15
12
|
const section = locateActivityLog(content);
|
|
@@ -37,6 +34,13 @@ function inspectActivityLog(content: string): { section: ActivityLogSection | nu
|
|
|
37
34
|
return { section: { heading: heading.text, body, entries }, invalidEntries };
|
|
38
35
|
}
|
|
39
36
|
|
|
37
|
+
function activityWarnings(content: string): { code: string; message: string }[] {
|
|
38
|
+
const inspected = inspectActivityLog(content);
|
|
39
|
+
if (!inspected.section) return [{ code: 'ACTIVITY_LOG_UNAVAILABLE', message: 'Activity Log is missing or ambiguous; repair the section to resume logging' }];
|
|
40
|
+
if (inspected.invalidEntries.length) return [{ code: 'ACTIVITY_LOG_INVALID_ENTRIES', message: 'Activity Log contains malformed entries; repair the entries' }];
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
|
|
40
44
|
function locateActivityLog(content: string): ActivityLogSection | null {
|
|
41
45
|
const { section } = inspectActivityLog(content);
|
|
42
46
|
if (!section) return null;
|
|
@@ -53,15 +57,6 @@ function activityAttempt(note: string): string | null {
|
|
|
53
57
|
return ATTEMPT_RE.exec(note)?.[1] ?? null;
|
|
54
58
|
}
|
|
55
59
|
|
|
56
|
-
function parseCommitAttemptStarted(note: string): CommitAttempt | null {
|
|
57
|
-
const match = COMMIT_STARTED_RE.exec(note);
|
|
58
|
-
return match ? { attempt: match[1]!, baseline: match[2]!, agent: match[3]! } : null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function commitAttemptStartedNote(attempt: CommitAttempt): string {
|
|
62
|
-
return `started; attempt=${attempt.attempt}; baseline=${attempt.baseline}; agent=${attempt.agent}`;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
60
|
function pairEntries(entries: LogEntry[]): StepRow[] {
|
|
66
61
|
const rows: StepRow[] = [];
|
|
67
62
|
const open = new Map<string, StepRow[]>();
|
|
@@ -101,22 +96,14 @@ function startedBackedRows(rows: StepRow[]): StepRow[] {
|
|
|
101
96
|
return rows.filter((row) => row.started !== '');
|
|
102
97
|
}
|
|
103
98
|
|
|
104
|
-
function hasOpenLifecycleExecution(content: string): boolean {
|
|
105
|
-
const section = locateActivityLog(content);
|
|
106
|
-
if (!section) return false;
|
|
107
|
-
return pairEntries(section.entries).some((row) => row.done === '' && LIFECYCLE_STEP_RE.test(row.step));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
99
|
export {
|
|
111
100
|
activityAttempt,
|
|
101
|
+
activityWarnings,
|
|
112
102
|
appendActivityEntry,
|
|
113
|
-
hasOpenLifecycleExecution,
|
|
114
|
-
commitAttemptStartedNote,
|
|
115
103
|
inspectActivityLog,
|
|
116
104
|
locateActivityLog,
|
|
117
105
|
pairEntries,
|
|
118
106
|
parseActivityLog,
|
|
119
|
-
parseCommitAttemptStarted,
|
|
120
107
|
startedBackedRows
|
|
121
108
|
};
|
|
122
|
-
export type { ActivityLogSection,
|
|
109
|
+
export type { ActivityLogSection, LogEntry, StepRow };
|
|
@@ -98,7 +98,7 @@ export function executeArtifactCommand(
|
|
|
98
98
|
matchingInitializedSkeleton = existing.includes(`<!-- artifact-context:${resolved.taskId}:${family}:${parsed.round} -->`);
|
|
99
99
|
} catch { /* an absent candidate is created only for the selected identity */ }
|
|
100
100
|
if ((context.status !== 'ready' && !matchingInitializedSkeleton) || (context.next?.name !== artifact
|
|
101
|
-
&& !hasOpenArtifactRound(fs.readFileSync(resolved.taskMdPath, 'utf8'), family, parsed.round)
|
|
101
|
+
&& !hasOpenArtifactRound(taskDir, fs.readFileSync(resolved.taskMdPath, 'utf8'), family, parsed.round)
|
|
102
102
|
&& !matchingInitializedSkeleton)) {
|
|
103
103
|
return fail('ARTIFACT_INIT_CONTEXT_INVALID', context.error?.message ?? `artifact '${artifact}' is not the next ${family} artifact`);
|
|
104
104
|
}
|
|
@@ -4,13 +4,13 @@ import path from 'node:path';
|
|
|
4
4
|
import { ARTIFACT_FAMILY_CATALOG, artifactName, parseArtifactName } from './artifact-name.ts';
|
|
5
5
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
6
6
|
import type { ResolveTaskRefErrorCode, TaskWorkspaceState } from './resolve-ref.ts';
|
|
7
|
-
import {
|
|
7
|
+
import { lifecycleExecutions } from './lifecycle-execution.ts';
|
|
8
8
|
import { parseImplementationInputs, selectPendingImplementationInput } from './implementation-inputs.ts';
|
|
9
9
|
import { parseTypedTaskFrontmatter } from './frontmatter.ts';
|
|
10
10
|
import { parseVerdict } from './review-artifacts.ts';
|
|
11
11
|
import { extractSection, findSectionHeading } from './sections.ts';
|
|
12
12
|
import { receiptForOutput, receiptsForOutput, sha256File } from './artifact-receipts.ts';
|
|
13
|
-
import {
|
|
13
|
+
import { hasArtifactCompletionRecord, hasValidArtifactCompletion } from './completion-facts.ts';
|
|
14
14
|
import { inspectReviewIdentity } from './review-identity.ts';
|
|
15
15
|
import type { ArtifactFamily, ArtifactFamilySpec } from './artifact-name.ts';
|
|
16
16
|
import { parseLifecyclePathDecision } from './lifecycle-path.ts';
|
|
@@ -21,35 +21,29 @@ import type { ArtifactSelection } from './artifact-selection.ts';
|
|
|
21
21
|
import { executionInputsMatchLatest } from './execution-inputs.ts';
|
|
22
22
|
|
|
23
23
|
const artifactFamilyCatalog = ARTIFACT_FAMILY_CATALOG;
|
|
24
|
-
const ARTIFACT_STEPS: Readonly<Record<string, string>> = {
|
|
25
|
-
analysis: 'Analyze Task', 'review-analysis': 'Review Analysis',
|
|
26
|
-
plan: 'Plan Task', 'review-plan': 'Review Plan',
|
|
27
|
-
code: 'Code Task', 'review-code': 'Review Code'
|
|
28
|
-
};
|
|
29
|
-
|
|
30
24
|
/** Init, repair and finalization share the same authoritative round identity. */
|
|
31
|
-
export function hasOpenArtifactRound(content: string, family:
|
|
32
|
-
return openArtifactRounds(content, family).includes(round);
|
|
25
|
+
export function hasOpenArtifactRound(taskDir: string, content: string, family: ArtifactFamily, round: number): boolean {
|
|
26
|
+
return openArtifactRounds(taskDir, content, family).includes(round);
|
|
33
27
|
}
|
|
34
28
|
|
|
35
|
-
function openArtifactRounds(content: string, family:
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
29
|
+
function openArtifactRounds(taskDir: string, content: string, family: ArtifactFamily): number[] {
|
|
30
|
+
const executionFamily = family === 'analysis' ? 'analyze' : family;
|
|
31
|
+
const event = completionEvent(family);
|
|
32
|
+
return [...new Set(lifecycleExecutions(content)
|
|
33
|
+
.filter((item) => item.family === executionFamily && item.round !== undefined)
|
|
34
|
+
.flatMap((item) => {
|
|
35
|
+
const round = item.round!;
|
|
36
|
+
const name = artifactName(family, round);
|
|
37
|
+
if (!event || !hasArtifactCompletionRecord(content, name, event)) return [round];
|
|
38
|
+
if (!hasValidArtifactCompletion(content, taskDir, name, event)) throw new Error(`completion evidence for started ${family} Round ${round} is invalid`);
|
|
39
|
+
return [];
|
|
40
|
+
}))].sort((left, right) => left - right);
|
|
46
41
|
}
|
|
47
42
|
|
|
48
|
-
function hasCompletedArtifactRound(content: string, family: string, round: number, name: string): boolean {
|
|
43
|
+
function hasCompletedArtifactRound(taskDir: string, content: string, family: string, round: number, name: string): boolean {
|
|
49
44
|
const event = completionEvent(family);
|
|
50
45
|
return Boolean(event && parseArtifactName(name)?.round === round
|
|
51
|
-
&&
|
|
52
|
-
&& hasArtifactCompletionFact(content, name, event));
|
|
46
|
+
&& hasValidArtifactCompletion(content, taskDir, name, event));
|
|
53
47
|
}
|
|
54
48
|
|
|
55
49
|
function completionEvent(family: string): string | null {
|
|
@@ -238,7 +232,14 @@ function inspectArtifactDirectory(
|
|
|
238
232
|
}
|
|
239
233
|
artifacts.sort((left, right) => left.round - right.round);
|
|
240
234
|
knownArtifacts.sort((left, right) => left.round - right.round);
|
|
241
|
-
|
|
235
|
+
let openRounds: number[];
|
|
236
|
+
try { openRounds = openArtifactRounds(taskDir, taskContent, family); }
|
|
237
|
+
catch (error) {
|
|
238
|
+
return failure(requestRef, family, {
|
|
239
|
+
code: 'ARTIFACT_SELECTION_INDETERMINATE',
|
|
240
|
+
message: error instanceof Error ? error.message : String(error)
|
|
241
|
+
}, { taskId: identity.taskId ?? null, taskDir, taskState: identity.taskState ?? null });
|
|
242
|
+
}
|
|
242
243
|
const knownRounds = new Set(knownArtifacts.map((item) => item.round));
|
|
243
244
|
for (const round of openRounds) knownRounds.add(round);
|
|
244
245
|
const max = Math.max(...knownRounds, 0);
|
|
@@ -248,11 +249,8 @@ function inspectArtifactDirectory(
|
|
|
248
249
|
}
|
|
249
250
|
const event = completionEvent(family);
|
|
250
251
|
const completed = event
|
|
251
|
-
? artifacts.
|
|
252
|
-
|
|
253
|
-
return order !== null && hasArtifactCompletionFact(taskContent, artifact.path, event)
|
|
254
|
-
? [{ artifact, order }] : [];
|
|
255
|
-
}).sort((left, right) => left.order - right.order).map(({ artifact }) => artifact)
|
|
252
|
+
? artifacts.filter((artifact) => hasValidArtifactCompletion(taskContent, taskDir, artifact.name, event))
|
|
253
|
+
.sort((left, right) => left.round - right.round)
|
|
256
254
|
: [];
|
|
257
255
|
if (openRounds.length > 1) diagnostics.push(diagnostic('MULTIPLE_OPEN_ROUNDS', family, null, `multiple incomplete ${family} rounds are open: ${openRounds.join(', ')}`));
|
|
258
256
|
const latest = completed.at(-1) ?? null;
|
|
@@ -387,8 +385,8 @@ function hasSelectionEvidence(
|
|
|
387
385
|
const reportContent = fs.readFileSync(context.latest.path, 'utf8');
|
|
388
386
|
const identity = inspectReviewIdentity(context.taskDir!, reportContent, options.repoRoot);
|
|
389
387
|
return identity.status !== 'matched'
|
|
390
|
-
|| !
|
|
391
|
-
|| !
|
|
388
|
+
|| !context.taskDir
|
|
389
|
+
|| !hasValidArtifactCompletion(content, context.taskDir, context.latest.name, 'review-code.completed');
|
|
392
390
|
} catch {
|
|
393
391
|
return true;
|
|
394
392
|
}
|
|
@@ -428,13 +426,10 @@ function attachArtifactSelection(
|
|
|
428
426
|
...context, status: 'failed', selection: null, error: { code, message }
|
|
429
427
|
});
|
|
430
428
|
if (!context.next) return fail('ARTIFACT_SELECTION_INDETERMINATE', 'artifact inventory did not provide a writable identity');
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
return fail('ARTIFACT_SELECTION_INDETERMINATE', error instanceof Error ? error.message : String(error));
|
|
436
|
-
}
|
|
437
|
-
const openRounds = openArtifactRounds(taskContent, family);
|
|
429
|
+
const state = readArtifactSelectionState(context.taskDir, family);
|
|
430
|
+
if (!state.ok) return fail('ARTIFACT_SELECTION_INDETERMINATE', state.message);
|
|
431
|
+
const taskContent = state.content;
|
|
432
|
+
const openRounds = state.openRounds;
|
|
438
433
|
if (openRounds.length > 1) return fail('ARTIFACT_TOPOLOGY_CONFLICT', `multiple incomplete ${family} rounds are open: ${openRounds.join(', ')}`);
|
|
439
434
|
const openRound = openRounds[0] ?? null;
|
|
440
435
|
const selectedIdentity = openRound === null ? context.next : {
|
|
@@ -459,6 +454,17 @@ function attachArtifactSelection(
|
|
|
459
454
|
}
|
|
460
455
|
}
|
|
461
456
|
|
|
457
|
+
function readArtifactSelectionState(taskDir: string, family: ArtifactFamily):
|
|
458
|
+
| { ok: true; content: string; openRounds: number[] }
|
|
459
|
+
| { ok: false; message: string } {
|
|
460
|
+
try {
|
|
461
|
+
const content = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
|
|
462
|
+
return { ok: true, content, openRounds: openArtifactRounds(taskDir, content, family) };
|
|
463
|
+
} catch (error) {
|
|
464
|
+
return { ok: false, message: error instanceof Error ? error.message : String(error) };
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
462
468
|
function resolveArtifactContextFromTask(
|
|
463
469
|
taskRef: string,
|
|
464
470
|
family: string,
|
|
@@ -531,14 +537,6 @@ function resolveArtifactContext(taskRef: string, family: string, options: Inspec
|
|
|
531
537
|
return resolveArtifactContextFromTask(taskRef, family, options);
|
|
532
538
|
}
|
|
533
539
|
|
|
534
|
-
export function resolveArtifactContextForResolvedTask(
|
|
535
|
-
resolvedTask: ResolvedTaskRef,
|
|
536
|
-
family: string,
|
|
537
|
-
options: InspectOptions = {}
|
|
538
|
-
): ArtifactContextResult {
|
|
539
|
-
return resolveArtifactContextFromTask(resolvedTask.taskId, family, options, resolvedTask);
|
|
540
|
-
}
|
|
541
|
-
|
|
542
540
|
function resolveCodeContext(
|
|
543
541
|
inventory: ArtifactInventoryResult,
|
|
544
542
|
options: InspectOptions,
|
|
@@ -555,8 +553,7 @@ function resolveCodeContext(
|
|
|
555
553
|
const taskContent = fs.readFileSync(path.join(inventory.taskDir!, 'task.md'), 'utf8');
|
|
556
554
|
const hasPendingImplementation = parseImplementationInputs(taskContent).rows.some((input) => input.needsImplementation && input.status === 'pending');
|
|
557
555
|
if (hasPendingImplementation && !reviewCode.latest) {
|
|
558
|
-
|
|
559
|
-
return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', `cannot find completed Activity Log identity for ${candidate}`);
|
|
556
|
+
return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', 'no valid review-code completion fact is available for pending implementation inputs');
|
|
560
557
|
}
|
|
561
558
|
} catch (error) {
|
|
562
559
|
return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', error instanceof Error ? error.message : String(error));
|
|
@@ -575,12 +572,10 @@ function resolveCodeContext(
|
|
|
575
572
|
const review = reviewCode.latest;
|
|
576
573
|
const openInputs = review ? [...inputs, review] : inputs;
|
|
577
574
|
const openVerdict = review ? parseVerdict(review.path) : null;
|
|
578
|
-
const openRow =
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
const decisionInput = /, decision (II-[1-9]\d*)\)$/u.exec(openRow?.step ?? '')?.[1] ?? null;
|
|
583
|
-
const fixInput = /, fix for (review-code(?:-r\d+)?\.md)\)$/u.exec(openRow?.step ?? '')?.[1] ?? null;
|
|
575
|
+
const openRow = lifecycleExecutions(taskContent)
|
|
576
|
+
.find((row) => row.family === 'code' && row.round === openRound);
|
|
577
|
+
const decisionInput = openRow?.implementationInput ?? null;
|
|
578
|
+
const fixInput = openRow?.fixFor ?? null;
|
|
584
579
|
const mode = decisionInput ? 'decision'
|
|
585
580
|
: fixInput || (openVerdict?.ok && openVerdict.verdict === 'Changes Requested') ? 'fix'
|
|
586
581
|
: 'init';
|
|
@@ -617,7 +612,7 @@ function resolveCodeContext(
|
|
|
617
612
|
if (!review) return contextFailure(inventory, 'ARTIFACT_INPUT_MISSING', 'latest review-code artifact is required');
|
|
618
613
|
const verdict = parseVerdict(review.path);
|
|
619
614
|
if (!verdict.ok) return contextFailure(inventory, 'ARTIFACT_VERDICT_INVALID', `${verdict.code}: ${verdict.message}`, review.name);
|
|
620
|
-
const decision = resolveDecisionImplementationInput(
|
|
615
|
+
const decision = resolveDecisionImplementationInput(reviewCode, review);
|
|
621
616
|
if ('error' in decision) return contextFailure(inventory, 'ARTIFACT_REFERENCE_INVALID', decision.error, review.name);
|
|
622
617
|
if (decision.input) {
|
|
623
618
|
return withCodeMode(
|
|
@@ -680,12 +675,10 @@ function resolveDecisionImplementationInput(
|
|
|
680
675
|
catch (error) { return { error: error instanceof Error ? error.message : String(error) }; }
|
|
681
676
|
const pending = rows.filter((row) => row.needsImplementation && row.status === 'pending');
|
|
682
677
|
if (pending.length === 0) return { input: null };
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
);
|
|
688
|
-
if (!completed) return { error: `cannot find completed Activity Log identity for ${review.name}` };
|
|
678
|
+
if (inventory.latest?.name !== review.name
|
|
679
|
+
|| !inventory.completed.some((artifact) => artifact.name === review.name)) {
|
|
680
|
+
return { error: `Review Code completion facts do not validate ${review.name}` };
|
|
681
|
+
}
|
|
689
682
|
try { return { input: selectPendingImplementationInput(rows) }; }
|
|
690
683
|
catch (error) { return { error: error instanceof Error ? error.message : String(error) }; }
|
|
691
684
|
}
|
|
@@ -720,14 +713,12 @@ export function validateArtifactPublication(taskDir: string, family: ArtifactFam
|
|
|
720
713
|
if (existing) {
|
|
721
714
|
try {
|
|
722
715
|
const task = fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8');
|
|
723
|
-
if (hasCompletedArtifactRound(task, family, existing.round, name)) {
|
|
716
|
+
if (hasCompletedArtifactRound(taskDir, task, family, existing.round, name)) {
|
|
724
717
|
return { code: 'ARTIFACT_IDENTITY_INVALID', message: `completed artifact '${name}' is immutable` };
|
|
725
718
|
}
|
|
726
719
|
const event = completionEvent(family);
|
|
727
|
-
const
|
|
728
|
-
|
|
729
|
-
.some((fact) => fact.event === event && fact.output === name);
|
|
730
|
-
if (event && completionFactExists && artifactCompletionOrder(task, name, event) !== null) {
|
|
720
|
+
const completionFactExists = event !== null && hasArtifactCompletionRecord(task, name, event);
|
|
721
|
+
if (event && completionFactExists) {
|
|
731
722
|
return { code: 'ARTIFACT_IDENTITY_INVALID', message: `completion evidence for '${name}' does not match its current content` };
|
|
732
723
|
}
|
|
733
724
|
} catch (error) {
|
|
@@ -44,12 +44,5 @@ export function parseArtifactName(name: string): { family: ArtifactFamily; round
|
|
|
44
44
|
return { family: match[1] as ArtifactFamily, round, name };
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
export function maxArtifactRound(names: readonly string[], family: string): number {
|
|
48
|
-
return names.reduce((max, name) => {
|
|
49
|
-
const identity = parseArtifactName(name);
|
|
50
|
-
return identity?.family === family ? Math.max(max, identity.round) : max;
|
|
51
|
-
}, 0);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
47
|
export { ARTIFACT_FAMILY_CATALOG };
|
|
55
48
|
export type { ArtifactFamily, ArtifactFamilySpec };
|