@fitlab-ai/agent-infra 0.11.15 → 0.12.0
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/bin/cli.ts +1 -1
- package/dist/bin/cli.js +1 -1
- package/dist/bin/internal-cli.js +6 -1
- package/dist/lib/agent-clients/adapters/codex-lifecycle/recovery.js +6 -6
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +8 -18
- package/dist/lib/agent-clients/adapters/codex-orchestration.js +1 -1
- package/dist/lib/decide.js +2 -7
- package/dist/lib/internal/cli-route-inventory.js +3 -0
- package/dist/lib/internal/manual-validation.js +8 -16
- package/dist/lib/internal/platform-comment.js +3 -2
- package/dist/lib/internal/task-activity.js +1 -12
- package/dist/lib/internal/task-event.js +1 -12
- package/dist/lib/internal/task-ledger.js +1 -12
- package/dist/lib/internal/task-operation-registry.js +2 -1
- package/dist/lib/internal/task-qualification.js +6 -15
- package/dist/lib/internal/task-review.js +3 -16
- package/dist/lib/internal/task-short-id.js +2 -1
- package/dist/lib/internal/task-verify.js +1 -11
- package/dist/lib/internal/task-warning.js +1 -12
- package/dist/lib/internal/task-workspace.js +43 -0
- package/dist/lib/merge.js +4 -3
- package/dist/lib/platform/pr-summary.js +6 -15
- package/dist/lib/platform/pull-requests.js +3 -15
- package/dist/lib/pr-review/evidence-grading.js +3 -2
- package/dist/lib/process-data/index.js +5 -4
- package/dist/lib/process-data/sources.js +6 -0
- package/dist/lib/run/index.js +3 -2
- package/dist/lib/sandbox/commands/create.js +5 -1
- package/dist/lib/sandbox/commands/enter.js +1 -1
- package/dist/lib/sandbox/commands/list-running.js +2 -1
- package/dist/lib/sandbox/control/client.js +8 -13
- package/dist/lib/sandbox/control/completed-reentry.js +2 -1
- package/dist/lib/sandbox/control/task-view.js +4 -2
- package/dist/lib/sandbox/mounts.js +4 -4
- package/dist/lib/sandbox/recovery.js +26 -16
- package/dist/lib/sandbox/removal.js +3 -10
- package/dist/lib/sandbox/task-resolver.js +9 -6
- package/dist/lib/sandbox/workspace-identity.js +6 -5
- package/dist/lib/sandbox/workspace-view.js +29 -22
- package/dist/lib/task/activity-intent.js +2 -12
- package/dist/lib/task/artifact-lifecycle.js +17 -11
- package/dist/lib/task/artifact-operations.js +41 -49
- package/dist/lib/task/capabilities.js +2 -2
- package/dist/lib/task/commands/ls.js +4 -3
- package/dist/lib/task/commands/status.js +1 -1
- package/dist/lib/task/commit-operation.js +6 -6
- package/dist/lib/task/control-authority.js +32 -32
- package/dist/lib/task/create-service.js +2 -4
- package/dist/lib/task/create.js +12 -65
- package/dist/lib/task/delegation-receipts.js +1 -1
- package/dist/lib/task/delivery.js +2 -8
- package/dist/lib/task/events.js +5 -31
- package/dist/lib/task/finalization.js +17 -22
- package/dist/lib/task/lifecycle.js +9 -48
- package/dist/lib/task/local-artifact-finalization.js +1 -2
- package/dist/lib/task/orchestration.js +24 -57
- package/dist/lib/task/platform-operation-recovery.js +1 -2
- package/dist/lib/task/resolve-ref.js +17 -12
- package/dist/lib/task/review-finalization.js +2 -35
- package/dist/lib/task/review-identity.js +3 -1
- package/dist/lib/task/short-id.js +10 -8
- package/dist/lib/task/verification-engine.js +19 -58
- package/dist/lib/task/workspace-root.js +59 -0
- package/dist/lib/task/workspace-snapshot.js +4 -3
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/adapters/codex-lifecycle/recovery.ts +6 -6
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +8 -34
- package/lib/agent-clients/adapters/codex-orchestration.ts +1 -1
- package/lib/decide.ts +2 -7
- package/lib/internal/cli-route-inventory.ts +2 -0
- package/lib/internal/manual-validation.ts +8 -19
- package/lib/internal/platform-comment.ts +3 -2
- package/lib/internal/task-activity.ts +1 -10
- package/lib/internal/task-event.ts +1 -10
- package/lib/internal/task-ledger.ts +1 -10
- package/lib/internal/task-operation-registry.ts +2 -1
- package/lib/internal/task-qualification.ts +5 -11
- package/lib/internal/task-review.ts +3 -14
- package/lib/internal/task-short-id.ts +2 -1
- package/lib/internal/task-verify.ts +1 -9
- package/lib/internal/task-warning.ts +1 -10
- package/lib/internal/task-workspace.ts +39 -0
- package/lib/merge.ts +4 -3
- package/lib/platform/pr-summary.ts +7 -16
- package/lib/platform/pull-requests.ts +3 -24
- package/lib/pr-review/evidence-grading.ts +3 -2
- package/lib/process-data/index.ts +4 -3
- package/lib/process-data/sources.ts +6 -0
- package/lib/run/index.ts +3 -2
- package/lib/sandbox/commands/create.ts +5 -1
- package/lib/sandbox/commands/enter.ts +1 -1
- package/lib/sandbox/commands/list-running.ts +2 -1
- package/lib/sandbox/control/client.ts +7 -12
- package/lib/sandbox/control/completed-reentry.ts +2 -1
- package/lib/sandbox/control/task-view.ts +4 -2
- package/lib/sandbox/mounts.ts +4 -4
- package/lib/sandbox/recovery.ts +26 -18
- package/lib/sandbox/removal.ts +3 -26
- package/lib/sandbox/task-resolver.ts +9 -6
- package/lib/sandbox/workspace-identity.ts +6 -5
- package/lib/sandbox/workspace-view.ts +28 -20
- package/lib/task/activity-intent.ts +3 -17
- package/lib/task/artifact-lifecycle.ts +39 -11
- package/lib/task/artifact-operations.ts +28 -35
- package/lib/task/capabilities.ts +2 -2
- package/lib/task/commands/ls.ts +4 -3
- package/lib/task/commands/status.ts +1 -1
- package/lib/task/commit-operation.ts +5 -6
- package/lib/task/control-authority.ts +29 -35
- package/lib/task/create-service.ts +2 -4
- package/lib/task/create.ts +10 -51
- package/lib/task/delegation-receipts.ts +1 -1
- package/lib/task/delivery.ts +2 -10
- package/lib/task/events.ts +5 -32
- package/lib/task/finalization.ts +16 -24
- package/lib/task/lifecycle.ts +9 -50
- package/lib/task/local-artifact-finalization.ts +1 -4
- package/lib/task/orchestration.ts +31 -80
- package/lib/task/platform-operation-recovery.ts +1 -2
- package/lib/task/resolve-ref.ts +18 -12
- package/lib/task/review-finalization.ts +2 -52
- package/lib/task/review-identity.ts +3 -1
- package/lib/task/short-id.ts +10 -8
- package/lib/task/verification-engine.ts +20 -62
- package/lib/task/workspace-root.ts +78 -0
- package/lib/task/workspace-snapshot.ts +4 -3
- package/package.json +1 -1
- package/templates/.agents/QUICKSTART.en.md +19 -8
- package/templates/.agents/QUICKSTART.zh-CN.md +19 -8
- package/templates/.agents/README.en.md +10 -15
- package/templates/.agents/README.zh-CN.md +9 -13
- package/templates/.agents/rules/issue-sync.en.md +1 -1
- package/templates/.agents/rules/issue-sync.zh-CN.md +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.en.md +1 -1
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +1 -1
- package/templates/.agents/rules/next-step-output.en.md +2 -2
- package/templates/.agents/rules/next-step-output.zh-CN.md +2 -2
- 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/release-commands.en.md +3 -0
- package/templates/.agents/rules/release-commands.zh-CN.md +3 -0
- package/templates/.agents/rules/task-management.en.md +0 -2
- package/templates/.agents/rules/task-management.zh-CN.md +0 -2
- package/templates/.agents/rules/task-short-id.en.md +2 -2
- package/templates/.agents/rules/task-short-id.zh-CN.md +2 -2
- package/templates/.agents/scripts/task-short-id.js +2 -8
- package/templates/.agents/skills/analyze-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/analyze-task/config/verify.en.json +0 -1
- package/templates/.agents/skills/analyze-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/archive-tasks/SKILL.en.md +5 -5
- package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +10 -2
- package/templates/.agents/skills/block-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/block-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/block-task/config/verify.json +0 -1
- package/templates/.agents/skills/cancel-task/SKILL.en.md +6 -6
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/cancel-task/config/verify.json +0 -1
- package/templates/.agents/skills/code-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/code-task/config/verify.en.json +0 -1
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/code-task/reference/output-template.en.md +1 -1
- package/templates/.agents/skills/code-task/reference/output-template.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/SKILL.en.md +1 -1
- package/templates/.agents/skills/commit/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/commit/config/verify.json +0 -1
- package/templates/.agents/skills/commit/reference/commit-orchestration.en.md +2 -2
- package/templates/.agents/skills/commit/reference/commit-orchestration.zh-CN.md +2 -2
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +1 -1
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +0 -1
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +1 -1
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +1 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +7 -7
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +8 -8
- package/templates/.agents/skills/complete-task/config/verify.en.json +0 -1
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/create-pr/SKILL.en.md +3 -3
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/create-pr/config/verify.json +0 -1
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +1 -1
- package/templates/.agents/skills/create-release-note/SKILL.en.md +2 -2
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/create-task/SKILL.en.md +4 -4
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/create-task/config/verify.json +0 -1
- package/templates/.agents/skills/import-codescan/SKILL.en.md +1 -1
- package/templates/.agents/skills/import-codescan/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/import-codescan/config/verify.json +0 -1
- package/templates/.agents/skills/import-dependabot/SKILL.en.md +3 -3
- package/templates/.agents/skills/import-dependabot/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/import-dependabot/config/verify.json +0 -1
- package/templates/.agents/skills/import-issue/SKILL.en.md +5 -5
- package/templates/.agents/skills/import-issue/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/import-issue/config/verify.json +0 -1
- package/templates/.agents/skills/plan-task/SKILL.en.md +5 -5
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +5 -5
- package/templates/.agents/skills/plan-task/config/verify.en.json +0 -1
- package/templates/.agents/skills/plan-task/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/restore-task/SKILL.en.md +2 -2
- package/templates/.agents/skills/restore-task/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/restore-task/config/verify.json +0 -1
- package/templates/.agents/skills/review-analysis/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-analysis/config/verify.en.json +0 -1
- package/templates/.agents/skills/review-analysis/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/review-analysis/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-analysis/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/review-code/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-code/reference/output-templates.en.md +4 -4
- package/templates/.agents/skills/review-code/reference/output-templates.zh-CN.md +4 -4
- package/templates/.agents/skills/review-plan/SKILL.en.md +1 -1
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-plan/config/verify.en.json +0 -1
- package/templates/.agents/skills/review-plan/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/review-plan/reference/output-templates.en.md +5 -5
- package/templates/.agents/skills/review-plan/reference/output-templates.zh-CN.md +5 -5
- package/templates/.agents/skills/review-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/review-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/review-pr/config/verify.en.json +0 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/review-pr/reference/output-guidance.en.md +1 -1
- package/templates/.agents/skills/review-pr/reference/output-guidance.zh-CN.md +1 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +0 -1
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +0 -1
- package/templates/.agents/skills/run-task/SKILL.en.md +1 -1
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +1 -1
- package/templates/.agents/skills/test/SKILL.en.md +0 -3
- package/templates/.agents/skills/test/SKILL.zh-CN.md +0 -3
- package/templates/.agents/skills/test-integration/SKILL.en.md +0 -3
- package/templates/.agents/skills/test-integration/SKILL.zh-CN.md +0 -3
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +13 -6
- package/templates/.agents/skills/upgrade-dependency/SKILL.en.md +0 -3
- package/templates/.agents/skills/upgrade-dependency/SKILL.zh-CN.md +0 -3
- package/templates/.agents/skills/watch-pr/SKILL.en.md +3 -3
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/watch-pr/config/verify.json +0 -1
- package/templates/.agents/templates/handoff.en.md +1 -1
- package/templates/.agents/templates/handoff.zh-CN.md +1 -1
- package/templates/.agents/workflows/bug-fix.en.yaml +1 -1
- package/templates/.agents/workflows/feature-development.en.yaml +1 -1
- package/templates/.agents/workflows/refactoring.en.yaml +1 -1
- package/dist/lib/task/task-execution-lock.js +0 -250
- package/lib/task/task-execution-lock.ts +0 -320
|
@@ -2,6 +2,7 @@ import { spawnSync } from 'node:child_process';
|
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { isRemovedHashShortIdInput } from '../task/short-id.ts';
|
|
5
|
+
import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.ts';
|
|
5
6
|
|
|
6
7
|
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
7
8
|
const SHORT_ID_RE = /^\d+$/;
|
|
@@ -35,10 +36,11 @@ function stripQuotes(value: string): string {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
function readTaskContent(repoRoot: string, taskId: string): { content: string; state: TaskWorkspaceState; taskMd: string } {
|
|
39
|
+
const taskRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
|
|
38
40
|
for (const state of TASK_WORKSPACE_STATES) {
|
|
39
41
|
const taskPath = state === 'archive'
|
|
40
|
-
? path.join(
|
|
41
|
-
: path.join(
|
|
42
|
+
? path.join(taskRoot, state, ...findArchiveTaskParts(repoRoot, taskId), 'local', 'task.md')
|
|
43
|
+
: path.join(taskRoot, state, taskId, 'task.md');
|
|
42
44
|
if (fs.existsSync(taskPath)) {
|
|
43
45
|
return { content: fs.readFileSync(taskPath, 'utf8'), state, taskMd: taskPath };
|
|
44
46
|
}
|
|
@@ -47,8 +49,9 @@ function readTaskContent(repoRoot: string, taskId: string): { content: string; s
|
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
function taskWorkspaceCandidates(repoRoot: string, taskId: string): TaskWorkspace[] {
|
|
52
|
+
const taskRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
|
|
50
53
|
const hot = TASK_WORKSPACE_STATES.filter((state) => state !== 'archive').flatMap((state) => {
|
|
51
|
-
const taskMd = path.join(
|
|
54
|
+
const taskMd = path.join(taskRoot, state, taskId, 'task.md');
|
|
52
55
|
if (!fs.existsSync(taskMd)) return [];
|
|
53
56
|
const content = fs.readFileSync(taskMd, 'utf8');
|
|
54
57
|
return [{
|
|
@@ -60,7 +63,7 @@ function taskWorkspaceCandidates(repoRoot: string, taskId: string): TaskWorkspac
|
|
|
60
63
|
});
|
|
61
64
|
const parts = findArchiveTaskParts(repoRoot, taskId);
|
|
62
65
|
if (parts.length === 0) return hot;
|
|
63
|
-
const taskMd = path.join(
|
|
66
|
+
const taskMd = path.join(taskRoot, 'archive', ...parts, 'local', 'task.md');
|
|
64
67
|
if (!fs.existsSync(taskMd)) return hot;
|
|
65
68
|
const content = fs.readFileSync(taskMd, 'utf8');
|
|
66
69
|
return [...hot, {
|
|
@@ -72,7 +75,7 @@ function taskWorkspaceCandidates(repoRoot: string, taskId: string): TaskWorkspac
|
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
function findArchiveTaskParts(repoRoot: string, taskId: string): string[] {
|
|
75
|
-
const archiveRoot = path.join(repoRoot, '
|
|
78
|
+
const archiveRoot = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'archive');
|
|
76
79
|
if (!fs.existsSync(archiveRoot)) return [];
|
|
77
80
|
for (const year of fs.readdirSync(archiveRoot).sort().reverse()) {
|
|
78
81
|
if (!/^\d{4}$/.test(year)) continue;
|
|
@@ -129,7 +132,7 @@ export function resolveTaskWorkspace(taskId: string, repoRoot: string): TaskWork
|
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
export function listTaskWorkspaces(repoRoot: string, state: TaskWorkspaceState): TaskWorkspace[] {
|
|
132
|
-
const stateRoot = path.join(repoRoot,
|
|
135
|
+
const stateRoot = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), state);
|
|
133
136
|
if (state === 'archive') {
|
|
134
137
|
if (!fs.existsSync(stateRoot)) return [];
|
|
135
138
|
const rows: TaskWorkspace[] = [];
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
isRemovedHashShortIdInput,
|
|
7
7
|
normalizeShortIdInput
|
|
8
8
|
} from '../task/short-id.ts';
|
|
9
|
+
import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.ts';
|
|
9
10
|
import {
|
|
10
11
|
TASK_WORKSPACE_STATES,
|
|
11
12
|
listTaskWorkspaces,
|
|
@@ -57,7 +58,7 @@ function taskBranch(taskMd: string, taskId: string): string {
|
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
function activeRegistry(repoRoot: string): Record<string, string> {
|
|
60
|
-
const registryPath = path.join(repoRoot, '
|
|
61
|
+
const registryPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', '.short-ids.json');
|
|
61
62
|
if (!fs.existsSync(registryPath)) return {};
|
|
62
63
|
const value = JSON.parse(fs.readFileSync(registryPath, 'utf8')) as unknown;
|
|
63
64
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
@@ -74,7 +75,7 @@ function activeRegistry(repoRoot: string): Record<string, string> {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
function taskIdentity(repoRoot: string, taskId: string, registry: Record<string, string>): SandboxWorkspaceIdentity {
|
|
77
|
-
const taskMd = path.join(repoRoot, '
|
|
78
|
+
const taskMd = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId, 'task.md');
|
|
78
79
|
if (!fs.existsSync(taskMd)) throw new Error(`Active task not found: ${taskId}`);
|
|
79
80
|
const shortIds = Object.entries(registry).filter(([, id]) => id === taskId).map(([shortId]) => shortId);
|
|
80
81
|
if (shortIds.length !== 1) {
|
|
@@ -126,14 +127,14 @@ export function resolveSandboxTarget(requestedRef: string, repoRoot: string): Sa
|
|
|
126
127
|
const taskId = canonicalShortId ? registry[canonicalShortId] : undefined;
|
|
127
128
|
if (!taskId) throw new Error(`Short id '${requestedRef}' not found in active task registry`);
|
|
128
129
|
const workspace = taskIdentity(repoRoot, taskId, registry);
|
|
129
|
-
return { requestedRef, branch: taskBranch(path.join(repoRoot, '
|
|
130
|
+
return { requestedRef, branch: taskBranch(path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId, 'task.md'), taskId), workspace };
|
|
130
131
|
}
|
|
131
132
|
if (TASK_ID_RE.test(requestedRef)) {
|
|
132
133
|
const workspace = taskIdentity(repoRoot, requestedRef, registry);
|
|
133
|
-
return { requestedRef, branch: taskBranch(path.join(repoRoot, '
|
|
134
|
+
return { requestedRef, branch: taskBranch(path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', requestedRef, 'task.md'), requestedRef), workspace };
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
const activeRoot = path.join(repoRoot, '
|
|
137
|
+
const activeRoot = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active');
|
|
137
138
|
const matches = fs.existsSync(activeRoot)
|
|
138
139
|
? fs.readdirSync(activeRoot, { withFileTypes: true }).flatMap((entry) => {
|
|
139
140
|
if (!entry.isDirectory() || !TASK_ID_RE.test(entry.name)) return [];
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
3
4
|
import path from 'node:path';
|
|
5
|
+
import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.ts';
|
|
4
6
|
import type { SandboxWorkspaceIdentity, SandboxWorkspaceKey } from './workspace-identity.ts';
|
|
5
7
|
import { resolveTaskWorkspace } from './task-resolver.ts';
|
|
6
8
|
import type { SandboxControlManifest } from './control/protocol.ts';
|
|
@@ -147,7 +149,8 @@ function assertSafeMountTargetPath(
|
|
|
147
149
|
|
|
148
150
|
export function assertSandboxTaskSource(repoRoot: string, taskId: string): string {
|
|
149
151
|
if (!/^TASK-\d{8}-\d{6}$/.test(taskId)) throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
150
|
-
const
|
|
152
|
+
const workspaceRootPath = resolveProjectTaskWorkspaceRoot(repoRoot);
|
|
153
|
+
const activeSource = path.join(workspaceRootPath, 'active', taskId);
|
|
151
154
|
try {
|
|
152
155
|
const stat = fs.lstatSync(activeSource);
|
|
153
156
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
@@ -155,7 +158,7 @@ export function assertSandboxTaskSource(repoRoot: string, taskId: string): strin
|
|
|
155
158
|
}
|
|
156
159
|
const canonical = fs.realpathSync.native(activeSource);
|
|
157
160
|
const activeRoot = fs.realpathSync.native(path.dirname(activeSource));
|
|
158
|
-
const workspaceRoot = fs.realpathSync.native(
|
|
161
|
+
const workspaceRoot = fs.realpathSync.native(workspaceRootPath);
|
|
159
162
|
const relative = path.relative(workspaceRoot, canonical);
|
|
160
163
|
if (path.dirname(canonical) !== activeRoot || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
161
164
|
throw new Error(`SANDBOX_TASK_SOURCE_INVALID: ${activeSource} escapes the task workspace`);
|
|
@@ -173,7 +176,7 @@ export function assertSandboxTaskSource(repoRoot: string, taskId: string): strin
|
|
|
173
176
|
throw new Error(`SANDBOX_TASK_SOURCE_INVALID: ${source} must be a real directory`);
|
|
174
177
|
}
|
|
175
178
|
const canonical = fs.realpathSync.native(source);
|
|
176
|
-
const workspaceRoot = fs.realpathSync.native(
|
|
179
|
+
const workspaceRoot = fs.realpathSync.native(workspaceRootPath);
|
|
177
180
|
const relative = path.relative(workspaceRoot, canonical);
|
|
178
181
|
if (path.basename(canonical) !== taskId || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
179
182
|
throw new Error(`SANDBOX_TASK_SOURCE_INVALID: ${source} escapes the task workspace`);
|
|
@@ -181,30 +184,38 @@ export function assertSandboxTaskSource(repoRoot: string, taskId: string): strin
|
|
|
181
184
|
return canonical;
|
|
182
185
|
}
|
|
183
186
|
|
|
184
|
-
export function prepareSandboxWorkspaceMountTargets(
|
|
185
|
-
const
|
|
186
|
-
const
|
|
187
|
-
const workspaceRoot = path.resolve(
|
|
187
|
+
export function prepareSandboxWorkspaceMountTargets(repoRoot: string): void {
|
|
188
|
+
const resolvedHome = path.resolve(process.env.HOME || os.homedir());
|
|
189
|
+
const canonicalHome = fs.realpathSync.native(resolvedHome);
|
|
190
|
+
const workspaceRoot = path.resolve(resolveProjectTaskWorkspaceRoot(repoRoot));
|
|
191
|
+
const relativeRoot = path.relative(resolvedHome, workspaceRoot);
|
|
192
|
+
if (relativeRoot.startsWith('..') || path.isAbsolute(relativeRoot)) {
|
|
193
|
+
throw new Error(`Sandbox workspace mount target escapes the host home: ${workspaceRoot}`);
|
|
194
|
+
}
|
|
188
195
|
const statePaths = sandboxWorkspaceViewStatePaths(workspaceRoot);
|
|
189
196
|
const registryTarget = path.join(workspaceRoot, 'active', '.short-ids.json');
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
for (const target of [workspaceRoot, ...statePaths.map(({ hostPath }) => hostPath), registryTarget, ...(completedTaskTarget ? [completedTaskTarget] : [])]) {
|
|
193
|
-
assertSafeMountTargetPath(target, resolvedWorktreeRoot, canonicalWorktreeRoot);
|
|
197
|
+
for (const target of [workspaceRoot, ...statePaths.map(({ hostPath }) => hostPath), registryTarget]) {
|
|
198
|
+
assertSafeMountTargetPath(target, resolvedHome, canonicalHome);
|
|
194
199
|
}
|
|
195
200
|
fs.mkdirSync(workspaceRoot, { recursive: true, mode: 0o700 });
|
|
196
201
|
for (const { hostPath } of statePaths) {
|
|
197
202
|
fs.mkdirSync(hostPath, { recursive: true, mode: 0o700 });
|
|
198
203
|
fs.chmodSync(hostPath, 0o700);
|
|
199
204
|
}
|
|
200
|
-
if (completedTaskTarget) fs.mkdirSync(completedTaskTarget, { recursive: true, mode: 0o700 });
|
|
201
205
|
fs.chmodSync(workspaceRoot, 0o700);
|
|
202
|
-
fs.
|
|
206
|
+
const registryFd = fs.openSync(registryTarget, 'a', 0o600);
|
|
207
|
+
try {
|
|
208
|
+
if (fs.fstatSync(registryFd).size === 0) {
|
|
209
|
+
fs.writeFileSync(registryFd, `${JSON.stringify({ version: 1, ids: {} })}\n`);
|
|
210
|
+
}
|
|
211
|
+
} finally {
|
|
212
|
+
fs.closeSync(registryFd);
|
|
213
|
+
}
|
|
203
214
|
}
|
|
204
215
|
|
|
205
216
|
export function prepareSandboxCompletedTaskMountSource(repoRoot: string, taskId: string): string {
|
|
206
217
|
if (!/^TASK-\d{8}-\d{6}$/.test(taskId)) throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
207
|
-
const workspaceRoot =
|
|
218
|
+
const workspaceRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
|
|
208
219
|
fs.mkdirSync(path.join(workspaceRoot, 'completed'), { recursive: true, mode: 0o700 });
|
|
209
220
|
const completedRoot = path.join(workspaceRoot, 'completed');
|
|
210
221
|
const completedStat = fs.lstatSync(completedRoot);
|
|
@@ -224,16 +235,13 @@ export function prepareSandboxCompletedTaskMountSource(repoRoot: string, taskId:
|
|
|
224
235
|
|
|
225
236
|
export function sandboxCompletedTaskMountSource(repoRoot: string, taskId: string): string {
|
|
226
237
|
if (!/^TASK-\d{8}-\d{6}$/.test(taskId)) throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
227
|
-
const source = path.resolve(repoRoot, '
|
|
238
|
+
const source = path.resolve(resolveProjectTaskWorkspaceRoot(repoRoot), 'completed', taskId);
|
|
228
239
|
const stat = fs.lstatSync(source);
|
|
229
240
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
230
|
-
const canonicalWorkspace = fs.realpathSync.native(
|
|
241
|
+
const canonicalWorkspace = fs.realpathSync.native(resolveProjectTaskWorkspaceRoot(repoRoot));
|
|
231
242
|
const canonicalSource = fs.realpathSync.native(source);
|
|
232
|
-
const canonicalRepoRoot = fs.realpathSync.native(repoRoot);
|
|
233
|
-
const workspaceRelative = path.relative(canonicalRepoRoot, canonicalWorkspace);
|
|
234
243
|
const relative = path.relative(canonicalWorkspace, canonicalSource);
|
|
235
|
-
if (
|
|
236
|
-
|| path.dirname(canonicalSource) !== fs.realpathSync.native(path.dirname(source))
|
|
244
|
+
if (path.dirname(canonicalSource) !== fs.realpathSync.native(path.dirname(source))
|
|
237
245
|
|| relative.startsWith('..') || path.isAbsolute(relative)) throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
238
246
|
return canonicalSource;
|
|
239
247
|
}
|
|
@@ -10,8 +10,6 @@ import {
|
|
|
10
10
|
} from './artifact-lifecycle.ts';
|
|
11
11
|
import { parseArtifactName } from './artifact-name.ts';
|
|
12
12
|
import type { ArtifactIdentity } from './artifact-lifecycle.ts';
|
|
13
|
-
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
14
|
-
import type { TaskExecutionLockOptions } from './task-execution-lock.ts';
|
|
15
13
|
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
16
14
|
import type { TaskMutation, TaskOperationSummary, TaskWriteOptions } from './write.ts';
|
|
17
15
|
|
|
@@ -95,7 +93,7 @@ export type PrReviewInspectionResult = {
|
|
|
95
93
|
error: { code: string; message: string } | null;
|
|
96
94
|
};
|
|
97
95
|
|
|
98
|
-
type ActivityIntentOptions = TaskWriteOptions
|
|
96
|
+
type ActivityIntentOptions = TaskWriteOptions;
|
|
99
97
|
type ArtifactRecord = { identity: ArtifactIdentity; head: string; status: PrReviewArtifactStatus };
|
|
100
98
|
type RoundState = ArtifactRecord & {
|
|
101
99
|
startedAgent: string | null;
|
|
@@ -299,7 +297,7 @@ function terminalNote(intent: PrReviewCompleteIntent | PrReviewTerminateIntent):
|
|
|
299
297
|
return `Outcome: ${outcomeLabel(intent.outcome)}, reason: ${intent.reason} → ${intent.artifact}`;
|
|
300
298
|
}
|
|
301
299
|
|
|
302
|
-
function
|
|
300
|
+
function applyActivityIntent(intent: PrReviewActivityIntent, normalizedAgent: string, options: ActivityIntentOptions): PrReviewActivityResult {
|
|
303
301
|
const state = readSnapshot(intent.taskRef, options.repoRoot);
|
|
304
302
|
if (!state.ok) return failed(intent, state.code, state.message, state.taskId);
|
|
305
303
|
const parsed = parseArtifactName(intent.artifact)!;
|
|
@@ -401,19 +399,7 @@ export function applyPrReviewActivityIntent(intent: PrReviewActivityIntent, opti
|
|
|
401
399
|
if (resolved.state !== 'active') {
|
|
402
400
|
return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
403
401
|
}
|
|
404
|
-
|
|
405
|
-
try {
|
|
406
|
-
return withTaskExecutionLock(
|
|
407
|
-
resolved.repoRoot,
|
|
408
|
-
resolved.taskId,
|
|
409
|
-
`task-activity:${intent.kind}`,
|
|
410
|
-
() => applyLocked(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot }),
|
|
411
|
-
options.lockOptions
|
|
412
|
-
);
|
|
413
|
-
} catch (error) {
|
|
414
|
-
if (error instanceof TaskExecutionLockError) return failed(intent, error.code, error.message, resolved.taskId);
|
|
415
|
-
return failed(intent, 'ACTIVITY_LOCK_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
416
|
-
}
|
|
402
|
+
return applyActivityIntent(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot });
|
|
417
403
|
}
|
|
418
404
|
|
|
419
405
|
export type { ActivityIntentOptions };
|
|
@@ -134,6 +134,8 @@ type InspectOptions = {
|
|
|
134
134
|
sourceSha256?: string;
|
|
135
135
|
};
|
|
136
136
|
|
|
137
|
+
type ResolvedTaskRef = Extract<ReturnType<typeof resolveTaskRef>, { ok: true }>;
|
|
138
|
+
|
|
137
139
|
const BLOCKING_DIAGNOSTICS = new Set<ArtifactDiagnosticCode>([
|
|
138
140
|
'NONCANONICAL_NAME', 'ROUND_OUT_OF_RANGE', 'MISSING_BASE', 'ROUND_GAP',
|
|
139
141
|
'DUPLICATE_LOGICAL_ROUND', 'NON_REGULAR_FILE', 'SYMBOLIC_LINK',
|
|
@@ -160,10 +162,15 @@ function diagnostic(code: ArtifactDiagnosticCode, family: ArtifactFamily, name:
|
|
|
160
162
|
return { code, family, name, message };
|
|
161
163
|
}
|
|
162
164
|
|
|
163
|
-
function inspectTaskArtifacts(
|
|
165
|
+
function inspectTaskArtifacts(
|
|
166
|
+
taskRef: string,
|
|
167
|
+
family: string,
|
|
168
|
+
options: InspectOptions = {},
|
|
169
|
+
resolvedTask?: ResolvedTaskRef
|
|
170
|
+
): ArtifactInventoryResult {
|
|
164
171
|
const spec = familySpec(family);
|
|
165
172
|
if (!spec) return failure(taskRef, family, { code: 'ARTIFACT_FAMILY_UNKNOWN', message: `unknown artifact family '${family}'` });
|
|
166
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
173
|
+
const resolved = resolvedTask ?? resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
167
174
|
if (!resolved.ok) return failure(taskRef, family, { code: resolved.code, message: resolved.message }, { taskId: resolved.taskId });
|
|
168
175
|
return inspectArtifactDirectory(resolved.taskDir, spec.family, {
|
|
169
176
|
requestRef: taskRef, taskId: resolved.taskId, taskState: resolved.state
|
|
@@ -452,8 +459,13 @@ function attachArtifactSelection(
|
|
|
452
459
|
}
|
|
453
460
|
}
|
|
454
461
|
|
|
455
|
-
function
|
|
456
|
-
|
|
462
|
+
function resolveArtifactContextFromTask(
|
|
463
|
+
taskRef: string,
|
|
464
|
+
family: string,
|
|
465
|
+
options: InspectOptions,
|
|
466
|
+
resolvedTask?: ResolvedTaskRef
|
|
467
|
+
): ArtifactContextResult {
|
|
468
|
+
const inventory = inspectTaskArtifacts(taskRef, family, options, resolvedTask);
|
|
457
469
|
if (inventory.status === 'failed') return { ...inventory, inputs: [], codeMode: null };
|
|
458
470
|
const writableError = assertWritableInventory(inventory);
|
|
459
471
|
if (writableError) return { ...inventory, status: 'failed', inputs: [], codeMode: null, error: writableError };
|
|
@@ -462,7 +474,7 @@ function resolveArtifactContext(taskRef: string, family: string, options: Inspec
|
|
|
462
474
|
return contextFailure(inventory, 'LIFECYCLE_PATH_INVALID', pathState.message);
|
|
463
475
|
}
|
|
464
476
|
if (inventory.family === 'code') {
|
|
465
|
-
const code = resolveCodeContext(inventory, options);
|
|
477
|
+
const code = resolveCodeContext(inventory, options, resolvedTask);
|
|
466
478
|
const selected = attachArtifactSelection(code, pathState, options);
|
|
467
479
|
if (selected.status !== 'ready' || !selected.selection) return selected;
|
|
468
480
|
if (selected.selection.disposition === 'reuse') return selected;
|
|
@@ -496,7 +508,7 @@ function resolveArtifactContext(taskRef: string, family: string, options: Inspec
|
|
|
496
508
|
const required = REQUIRED_INPUT[inventory.family as ArtifactFamily];
|
|
497
509
|
const inputs: ArtifactIdentity[] = [];
|
|
498
510
|
if (required) {
|
|
499
|
-
const input = inspectTaskArtifacts(taskRef, required, options);
|
|
511
|
+
const input = inspectTaskArtifacts(taskRef, required, options, resolvedTask);
|
|
500
512
|
if (input.status === 'failed' || !input.latest) {
|
|
501
513
|
return { ...inventory, status: 'failed', inputs, codeMode: null, error: { code: 'ARTIFACT_INPUT_MISSING', message: `latest ${required} artifact is required` } };
|
|
502
514
|
}
|
|
@@ -505,7 +517,7 @@ function resolveArtifactContext(taskRef: string, family: string, options: Inspec
|
|
|
505
517
|
const optional = OPTIONAL_CONTEXT[inventory.family as ArtifactFamily];
|
|
506
518
|
if (optional) {
|
|
507
519
|
for (const family of optional) {
|
|
508
|
-
const context = inspectTaskArtifacts(taskRef, family, options);
|
|
520
|
+
const context = inspectTaskArtifacts(taskRef, family, options, resolvedTask);
|
|
509
521
|
if (context.status === 'ready' && context.latest) inputs.push(context.latest);
|
|
510
522
|
}
|
|
511
523
|
}
|
|
@@ -515,14 +527,30 @@ function resolveArtifactContext(taskRef: string, family: string, options: Inspec
|
|
|
515
527
|
return attachArtifactSelection({ ...inventory, inputs, codeMode: null }, pathState, options);
|
|
516
528
|
}
|
|
517
529
|
|
|
518
|
-
function
|
|
530
|
+
function resolveArtifactContext(taskRef: string, family: string, options: InspectOptions = {}): ArtifactContextResult {
|
|
531
|
+
return resolveArtifactContextFromTask(taskRef, family, options);
|
|
532
|
+
}
|
|
533
|
+
|
|
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
|
+
function resolveCodeContext(
|
|
543
|
+
inventory: ArtifactInventoryResult,
|
|
544
|
+
options: InspectOptions,
|
|
545
|
+
resolvedTask?: ResolvedTaskRef
|
|
546
|
+
): ArtifactContextResult {
|
|
519
547
|
const taskRef = inventory.requestRef;
|
|
520
548
|
const pathState = lifecyclePathState(inventory.taskDir!);
|
|
521
549
|
if (pathState.status !== 'valid') return contextFailure(inventory, 'LIFECYCLE_PATH_INVALID', pathState.message);
|
|
522
550
|
const inputFamily = pathState.decision.path === 'streamlined' ? 'analysis' : 'plan';
|
|
523
|
-
const source = inspectTaskArtifacts(taskRef, inputFamily, options);
|
|
551
|
+
const source = inspectTaskArtifacts(taskRef, inputFamily, options, resolvedTask);
|
|
524
552
|
if (source.status === 'failed' || !source.latest) return contextFailure(inventory, 'ARTIFACT_INPUT_MISSING', `latest ${inputFamily} artifact is required`);
|
|
525
|
-
const reviewCode = inspectTaskArtifacts(taskRef, 'review-code', options);
|
|
553
|
+
const reviewCode = inspectTaskArtifacts(taskRef, 'review-code', options, resolvedTask);
|
|
526
554
|
try {
|
|
527
555
|
const taskContent = fs.readFileSync(path.join(inventory.taskDir!, 'task.md'), 'utf8');
|
|
528
556
|
const hasPendingImplementation = parseImplementationInputs(taskContent).rows.some((input) => input.needsImplementation && input.status === 'pending');
|
|
@@ -538,7 +566,7 @@ function resolveCodeContext(inventory: ArtifactInventoryResult, options: Inspect
|
|
|
538
566
|
const reviewMax = reviewCode.latest?.round ?? 0;
|
|
539
567
|
const inputs = [source.latest];
|
|
540
568
|
const sourceReviewFamily = inputFamily === 'analysis' ? 'review-analysis' : 'review-plan';
|
|
541
|
-
const sourceReview = inspectTaskArtifacts(taskRef, sourceReviewFamily, options);
|
|
569
|
+
const sourceReview = inspectTaskArtifacts(taskRef, sourceReviewFamily, options, resolvedTask);
|
|
542
570
|
if (sourceReview.status === 'ready' && sourceReview.latest && sourceReview.reviewedInput?.name === source.latest.name) inputs.push(sourceReview.latest);
|
|
543
571
|
if (inventory.openRounds.length > 0) {
|
|
544
572
|
try {
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
getArtifactSchema,
|
|
10
10
|
renderArtifactSkeleton
|
|
11
11
|
} from './artifact-schema.ts';
|
|
12
|
-
import { withTaskExecutionLock } from './task-execution-lock.ts';
|
|
13
12
|
import type {
|
|
14
13
|
ArtifactSchema,
|
|
15
14
|
ArtifactSchemaFamily,
|
|
@@ -352,42 +351,36 @@ function initializeArtifactSkeleton(request: ArtifactInitRequest): ArtifactFileR
|
|
|
352
351
|
const target = path.join(request.taskDir, request.artifact);
|
|
353
352
|
if (path.dirname(path.resolve(target)) !== path.resolve(request.taskDir)) return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'artifact must be inside the current task directory');
|
|
354
353
|
try {
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
|
|
375
|
-
fs.renameSync(tempPath, target);
|
|
376
|
-
} catch (error) {
|
|
377
|
-
try { fs.unlinkSync(tempPath); } catch { /* preserve primary error */ }
|
|
378
|
-
return resultFailure('ARTIFACT_INIT_WRITE_FAILED', String(error));
|
|
379
|
-
}
|
|
380
|
-
return {
|
|
381
|
-
status: 'applied',
|
|
382
|
-
changed: true,
|
|
383
|
-
artifactSha256: sha256Content(content),
|
|
384
|
-
semanticDigest: canonicalSemanticDigest(content),
|
|
385
|
-
error: null
|
|
386
|
-
};
|
|
387
|
-
});
|
|
354
|
+
const existing = fs.lstatSync(target);
|
|
355
|
+
if (existing.isSymbolicLink() || !existing.isFile()) return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'artifact target is not a regular file');
|
|
356
|
+
return resultNoOp(readStableFileSync(target, { maxBytes: 1024 * 1024 }).bytes.toString('utf8'));
|
|
357
|
+
} catch (error) {
|
|
358
|
+
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') return resultFailure('ARTIFACT_INIT_TARGET_INVALID', String(error));
|
|
359
|
+
}
|
|
360
|
+
const skeleton = renderArtifactSkeleton({
|
|
361
|
+
taskId: request.taskId,
|
|
362
|
+
family: request.family,
|
|
363
|
+
artifact: request.artifact,
|
|
364
|
+
...(request.locale ? { locale: request.locale } : {})
|
|
365
|
+
});
|
|
366
|
+
const content = skeleton.trimEnd();
|
|
367
|
+
const tempPath = path.join(request.taskDir, `.${request.artifact}.init-${process.pid}-${Date.now()}.tmp`);
|
|
368
|
+
try {
|
|
369
|
+
fs.writeFileSync(tempPath, content, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
370
|
+
try { fs.lstatSync(target); fs.unlinkSync(tempPath); return resultFailure('ARTIFACT_INIT_TARGET_EXISTS', 'artifact was created concurrently'); }
|
|
371
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
|
|
372
|
+
fs.renameSync(tempPath, target);
|
|
388
373
|
} catch (error) {
|
|
389
|
-
|
|
374
|
+
try { fs.unlinkSync(tempPath); } catch { /* preserve primary error */ }
|
|
375
|
+
return resultFailure('ARTIFACT_INIT_WRITE_FAILED', String(error));
|
|
390
376
|
}
|
|
377
|
+
return {
|
|
378
|
+
status: 'applied',
|
|
379
|
+
changed: true,
|
|
380
|
+
artifactSha256: sha256Content(content),
|
|
381
|
+
semanticDigest: canonicalSemanticDigest(content),
|
|
382
|
+
error: null
|
|
383
|
+
};
|
|
391
384
|
}
|
|
392
385
|
|
|
393
386
|
export {
|
package/lib/task/capabilities.ts
CHANGED
|
@@ -302,7 +302,7 @@ function recommendNext(facts: LifecycleFacts): LifecycleRecommendation {
|
|
|
302
302
|
return { action: null, reasonCode: 'LIFECYCLE_REVIEWED', evidence: ['latest code review is approved'] };
|
|
303
303
|
}
|
|
304
304
|
|
|
305
|
-
function buildLifecycleFacts(taskDir: string, content: string, taskState = 'active', executionBusy = false): LifecycleFactsResult {
|
|
305
|
+
function buildLifecycleFacts(taskDir: string, content: string, taskState = 'active', executionBusy = false, repositoryRoot?: string): LifecycleFactsResult {
|
|
306
306
|
try {
|
|
307
307
|
const metadata = parseTypedTaskFrontmatter(content);
|
|
308
308
|
const rework = parseReworkIntentDocument(content);
|
|
@@ -393,7 +393,7 @@ function buildLifecycleFacts(taskDir: string, content: string, taskState = 'acti
|
|
|
393
393
|
&& artifactHashes[input] === receipt.inputSha256
|
|
394
394
|
) reviewedInputs[family] = input;
|
|
395
395
|
if (family === 'review-code') {
|
|
396
|
-
const identity = inspectReviewIdentity(taskDir, reviewContent);
|
|
396
|
+
const identity = inspectReviewIdentity(taskDir, reviewContent, repositoryRoot);
|
|
397
397
|
reviewCodeIdentityValid = identity.status === 'matched'
|
|
398
398
|
&& hasArtifactCompletionFact(content, path.join(taskDir, latest), 'review-code.completed')
|
|
399
399
|
&& hasArtifactCompletionLog(content, latest, 'review-code.completed');
|
package/lib/task/commands/ls.ts
CHANGED
|
@@ -4,10 +4,11 @@ import { execFileSync } from 'node:child_process';
|
|
|
4
4
|
import { formatTable } from '../../table.ts';
|
|
5
5
|
import { parseTaskFrontmatter, extractTitle } from '../frontmatter.ts';
|
|
6
6
|
import { loadShortIdByTaskId } from '../short-id.ts';
|
|
7
|
+
import { resolveProjectTaskWorkspaceRoot } from '../workspace-root.ts';
|
|
7
8
|
|
|
8
9
|
const USAGE = `Usage: ai task ls [--all | --blocked | --completed]
|
|
9
10
|
|
|
10
|
-
Lists tasks under .
|
|
11
|
+
Lists tasks under the project task workspace. Defaults to active tasks only.
|
|
11
12
|
--all Include active + blocked + completed (excludes archive)
|
|
12
13
|
--blocked Only blocked tasks
|
|
13
14
|
--completed Only completed tasks
|
|
@@ -73,7 +74,7 @@ type TaskRow = {
|
|
|
73
74
|
};
|
|
74
75
|
|
|
75
76
|
function collectTasks(repoRoot: string, state: 'active' | 'blocked' | 'completed'): TaskRow[] {
|
|
76
|
-
const dir = path.join(repoRoot,
|
|
77
|
+
const dir = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), state);
|
|
77
78
|
if (!fs.existsSync(dir)) return [];
|
|
78
79
|
const shortIdByTaskId = state === 'active' || state === 'completed'
|
|
79
80
|
? loadShortIdByTaskId(repoRoot)
|
|
@@ -117,7 +118,7 @@ function ls(args: string[] = []): void {
|
|
|
117
118
|
rows.push(...collectTasks(repoRoot, state));
|
|
118
119
|
}
|
|
119
120
|
if (rows.length === 0) {
|
|
120
|
-
process.stdout.write(`No tasks under
|
|
121
|
+
process.stdout.write(`No tasks under project task workspace state ${selection.join('|')}\n`);
|
|
121
122
|
return;
|
|
122
123
|
}
|
|
123
124
|
const tableRows = rows.map((r, i) => [
|
|
@@ -521,7 +521,7 @@ function buildFromResolved(input: BuildStatusModelInput): StatusModel {
|
|
|
521
521
|
const run = input.run ?? makeRunner(input.repoRoot);
|
|
522
522
|
const artifacts = enumerateArtifacts(input.taskDir);
|
|
523
523
|
const workflow = collectWorkflow(content, input.now);
|
|
524
|
-
const facts = buildLifecycleFacts(input.taskDir, content, fm.status ?? 'active');
|
|
524
|
+
const facts = buildLifecycleFacts(input.taskDir, content, fm.status ?? 'active', false, input.repoRoot);
|
|
525
525
|
const next = facts.ok ? recommendNext(facts.facts) : null;
|
|
526
526
|
const recommendation = next?.action ?? DASH;
|
|
527
527
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
import { execFileSync } from 'node:child_process';
|
|
3
4
|
|
|
4
5
|
import { commitExplicitPaths, inspectGitWorkflow, pushGitRefs } from '../git/workflow.ts';
|
|
@@ -7,7 +8,6 @@ import { checkpointCommitMatches } from './commit-identity.ts';
|
|
|
7
8
|
import { resolveTaskContext, resolveTaskRef } from './resolve-ref.ts';
|
|
8
9
|
import { commitPushDecision } from './commit-policy.ts';
|
|
9
10
|
import { mergeOperationWarnings, type OperationWarning } from './operation-outcome.ts';
|
|
10
|
-
import { TaskExecutionLockError, withRepositoryMutationLock, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
11
11
|
import { appendActivityEntry, locateActivityLog } from './activity-log.ts';
|
|
12
12
|
import { captureTaskWriteMetadata, writeTask } from './write.ts';
|
|
13
13
|
import type { TaskMutation } from './write.ts';
|
|
@@ -135,6 +135,7 @@ function resolveBoundTask(input: CommitOperationInput):
|
|
|
135
135
|
};
|
|
136
136
|
return boundTask(input, resolved.taskId, resolved.taskDir, resolved.taskMdPath, resolved.repoRoot, resolved.state);
|
|
137
137
|
}
|
|
138
|
+
if (!fs.existsSync(path.join(input.cwd, '.agents', '.airc.json'))) return { kind: 'none' };
|
|
138
139
|
const context = resolveTaskContext(undefined, { repoRoot: input.cwd });
|
|
139
140
|
if (context.ok) return boundTask(input, context.taskId, context.taskDir, context.taskMdPath, context.repoRoot, context.state);
|
|
140
141
|
if (context.code === 'TASK_CONTEXT_NOT_FOUND') return { kind: 'none' };
|
|
@@ -280,7 +281,7 @@ function checkpointTimestamp(): string {
|
|
|
280
281
|
return new Date().toISOString();
|
|
281
282
|
}
|
|
282
283
|
|
|
283
|
-
function
|
|
284
|
+
function executeCommitOperationCore(input: CommitOperationInput, task: BoundTask | null, mode: CommitExecutionMode): CommitOperationResult {
|
|
284
285
|
const inspected = inspectGitWorkflow(input.cwd);
|
|
285
286
|
if (!inspected.snapshot) return failure(input, mode, task?.taskId ?? null, 'GIT_INSPECT_FAILED', 'Unable to inspect Git repository');
|
|
286
287
|
if (mode === 'orchestrated') return failure(
|
|
@@ -516,11 +517,9 @@ function executeCommitOperation(input: CommitOperationInput): CommitOperationRes
|
|
|
516
517
|
const task = resolved.kind === 'bound' ? resolved.task : null;
|
|
517
518
|
if (mode === 'orchestrated' && !task) return failure(normalizedInput, mode, null, 'ORCHESTRATION_TASK_REQUIRED', 'orchestrated commit requires a task context', 'blocked');
|
|
518
519
|
try {
|
|
519
|
-
return task
|
|
520
|
-
? withRepositoryMutationLock(normalizedInput.cwd, () => withTaskExecutionLock(normalizedInput.cwd, task.taskId, 'commit-operation', () => executeUnlocked(normalizedInput, task, mode)))
|
|
521
|
-
: withRepositoryMutationLock(normalizedInput.cwd, () => executeUnlocked(normalizedInput, null, mode));
|
|
520
|
+
return executeCommitOperationCore(normalizedInput, task, mode);
|
|
522
521
|
} catch (error) {
|
|
523
|
-
const code =
|
|
522
|
+
const code = 'COMMIT_OPERATION_FAILED';
|
|
524
523
|
return failure(normalizedInput, mode, task?.taskId ?? null, code, error instanceof Error ? error.message : String(error), 'blocked');
|
|
525
524
|
}
|
|
526
525
|
}
|