@fitlab-ai/agent-infra 0.11.14 → 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-finalization.js +2 -2
- 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-change-report.js +10 -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 +18 -6
- 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/lifecycle.js +2 -21
- 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 +36 -20
- 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 +38 -49
- 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 +12 -10
- 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-finalization.ts +2 -2
- 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-change-report.ts +9 -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 +17 -5
- 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/lifecycle.ts +4 -21
- 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 +42 -18
- 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 +40 -51
- 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 +15 -10
- 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 +17 -9
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +18 -10
- 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 +13 -3
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +13 -3
- 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/SKILL.en.md +5 -5
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +5 -5
- 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-manual-validation/reference/discovery-and-execution.en.md +13 -2
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +13 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -0
- 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 +21 -4
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +22 -5
- package/templates/.agents/skills/watch-pr/config/verify.json +0 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +14 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +15 -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/runtime/agent-runtime.js +0 -33
- package/dist/lib/task/runtime-paths.js +0 -10
- package/dist/lib/task/task-execution-lock.js +0 -250
- package/lib/runtime/agent-runtime.ts +0 -41
- package/lib/task/runtime-paths.ts +0 -12
- package/lib/task/task-execution-lock.ts +0 -320
|
@@ -17,7 +17,6 @@ import { acquireSandboxResourceLock } from './control/native-file-lock.js';
|
|
|
17
17
|
import { toolConfigDirCandidates, toolProjectDirCandidates } from './tools.js';
|
|
18
18
|
import { getProcessStartTime } from '../server/process-state.js';
|
|
19
19
|
import { releaseStaleShortIdRegistry } from '../task/short-id.js';
|
|
20
|
-
import { withRepositoryMutationLock } from '../task/task-execution-lock.js';
|
|
21
20
|
import { fetchSandboxRows } from './commands/list-running.js';
|
|
22
21
|
import { createSandboxControlBindingEvidence } from './task-cleanup.js';
|
|
23
22
|
import { createCleanPermit, createDiscardPermit, formatWorktreeSnapshot, inspectRecoveredWorktree, inspectWorktrees, verifyWorktreePermit } from './worktree-safety.js';
|
|
@@ -665,12 +664,12 @@ async function authorizeWorktrees(worktrees, { allowDirtyDiscard, assumeYes }, {
|
|
|
665
664
|
}
|
|
666
665
|
return permits;
|
|
667
666
|
}
|
|
668
|
-
async function
|
|
667
|
+
async function runRmOne(config, tools, branch, options = {}) {
|
|
669
668
|
const target = options.target ?? resolveRmTarget(config, tools, options.cleanupTarget ?? resolveSandboxCleanupTarget(branch, config.repoRoot));
|
|
670
669
|
return rmOneCore(config, tools, branch, { ...options, target });
|
|
671
670
|
}
|
|
672
671
|
async function rmOne(config, tools, branch, options = {}) {
|
|
673
|
-
await
|
|
672
|
+
await runRmOne(config, tools, branch, options);
|
|
674
673
|
}
|
|
675
674
|
async function removeUncheckedSandbox(config, target, options) {
|
|
676
675
|
const confirm = options.prompt?.confirm ?? p.confirm;
|
|
@@ -751,9 +750,6 @@ async function rmOneCore(config, tools, branch, options = {}) {
|
|
|
751
750
|
return removeUncheckedSandbox(config, target, options);
|
|
752
751
|
}
|
|
753
752
|
async function rmPurge(config, tools, prompt = {}) {
|
|
754
|
-
return withRepositoryMutationLock(config.repoRoot, () => rmPurgeCore(config, tools, prompt));
|
|
755
|
-
}
|
|
756
|
-
async function rmPurgeCore(config, tools, prompt = {}) {
|
|
757
753
|
const engine = detectEngine(config);
|
|
758
754
|
const confirm = prompt.confirm ?? p.confirm;
|
|
759
755
|
const isCancel = prompt.isCancel ?? p.isCancel;
|
|
@@ -829,9 +825,6 @@ async function rmPurgeCore(config, tools, prompt = {}) {
|
|
|
829
825
|
p.outro(pc.green('All project sandboxes removed'));
|
|
830
826
|
}
|
|
831
827
|
async function rmUnbound(config, tools, options) {
|
|
832
|
-
return withRepositoryMutationLock(config.repoRoot, () => rmUnboundCore(config, tools, options));
|
|
833
|
-
}
|
|
834
|
-
async function rmUnboundCore(config, tools, options) {
|
|
835
828
|
const engine = detectEngine(config);
|
|
836
829
|
const listed = fetchSandboxRows(engine, sandboxLabel(config), sandboxBranchLabel(config), { mode: sandboxWorkspaceModeLabel(config), taskId: sandboxTaskIdLabel(config) });
|
|
837
830
|
const discovered = [...listed.running, ...listed.nonRunning];
|
|
@@ -892,7 +885,7 @@ async function rmUnboundCore(config, tools, options) {
|
|
|
892
885
|
return;
|
|
893
886
|
}
|
|
894
887
|
for (const { row, cleanupTarget, target } of targets) {
|
|
895
|
-
await
|
|
888
|
+
await runRmOne(config, tools, cleanupTarget.branch, {
|
|
896
889
|
assumeYes: options.assumeYes,
|
|
897
890
|
target,
|
|
898
891
|
cleanupTarget
|
|
@@ -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.js';
|
|
5
|
+
import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.js';
|
|
5
6
|
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
6
7
|
const SHORT_ID_RE = /^\d+$/;
|
|
7
8
|
export const TASK_WORKSPACE_STATES = ['active', 'completed', 'blocked', 'archive'];
|
|
@@ -20,10 +21,11 @@ function stripQuotes(value) {
|
|
|
20
21
|
return value.replace(/^(["'])(.*)\1$/, '$2');
|
|
21
22
|
}
|
|
22
23
|
function readTaskContent(repoRoot, taskId) {
|
|
24
|
+
const taskRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
|
|
23
25
|
for (const state of TASK_WORKSPACE_STATES) {
|
|
24
26
|
const taskPath = state === 'archive'
|
|
25
|
-
? path.join(
|
|
26
|
-
: path.join(
|
|
27
|
+
? path.join(taskRoot, state, ...findArchiveTaskParts(repoRoot, taskId), 'local', 'task.md')
|
|
28
|
+
: path.join(taskRoot, state, taskId, 'task.md');
|
|
27
29
|
if (fs.existsSync(taskPath)) {
|
|
28
30
|
return { content: fs.readFileSync(taskPath, 'utf8'), state, taskMd: taskPath };
|
|
29
31
|
}
|
|
@@ -31,8 +33,9 @@ function readTaskContent(repoRoot, taskId) {
|
|
|
31
33
|
throw new Error(`Task not found: ${taskId}`);
|
|
32
34
|
}
|
|
33
35
|
function taskWorkspaceCandidates(repoRoot, taskId) {
|
|
36
|
+
const taskRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
|
|
34
37
|
const hot = TASK_WORKSPACE_STATES.filter((state) => state !== 'archive').flatMap((state) => {
|
|
35
|
-
const taskMd = path.join(
|
|
38
|
+
const taskMd = path.join(taskRoot, state, taskId, 'task.md');
|
|
36
39
|
if (!fs.existsSync(taskMd))
|
|
37
40
|
return [];
|
|
38
41
|
const content = fs.readFileSync(taskMd, 'utf8');
|
|
@@ -46,7 +49,7 @@ function taskWorkspaceCandidates(repoRoot, taskId) {
|
|
|
46
49
|
const parts = findArchiveTaskParts(repoRoot, taskId);
|
|
47
50
|
if (parts.length === 0)
|
|
48
51
|
return hot;
|
|
49
|
-
const taskMd = path.join(
|
|
52
|
+
const taskMd = path.join(taskRoot, 'archive', ...parts, 'local', 'task.md');
|
|
50
53
|
if (!fs.existsSync(taskMd))
|
|
51
54
|
return hot;
|
|
52
55
|
const content = fs.readFileSync(taskMd, 'utf8');
|
|
@@ -58,7 +61,7 @@ function taskWorkspaceCandidates(repoRoot, taskId) {
|
|
|
58
61
|
}];
|
|
59
62
|
}
|
|
60
63
|
function findArchiveTaskParts(repoRoot, taskId) {
|
|
61
|
-
const archiveRoot = path.join(repoRoot, '
|
|
64
|
+
const archiveRoot = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'archive');
|
|
62
65
|
if (!fs.existsSync(archiveRoot))
|
|
63
66
|
return [];
|
|
64
67
|
for (const year of fs.readdirSync(archiveRoot).sort().reverse()) {
|
|
@@ -113,7 +116,7 @@ export function resolveTaskWorkspace(taskId, repoRoot) {
|
|
|
113
116
|
return matches[0];
|
|
114
117
|
}
|
|
115
118
|
export function listTaskWorkspaces(repoRoot, state) {
|
|
116
|
-
const stateRoot = path.join(repoRoot,
|
|
119
|
+
const stateRoot = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), state);
|
|
117
120
|
if (state === 'archive') {
|
|
118
121
|
if (!fs.existsSync(stateRoot))
|
|
119
122
|
return [];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { isRemovedHashShortIdInput } from '../task/short-id.js';
|
|
4
|
-
import {
|
|
5
|
-
import { resolveTaskWorkspace } from './task-resolver.js';
|
|
3
|
+
import { configuredShortIdLength, inspectShortIdRegistry, isRemovedHashShortIdInput, normalizeShortIdInput } from '../task/short-id.js';
|
|
4
|
+
import { resolveProjectTaskWorkspaceRoot } from '../task/workspace-root.js';
|
|
5
|
+
import { TASK_WORKSPACE_STATES, listTaskWorkspaces, resolveTaskWorkspace } from './task-resolver.js';
|
|
6
6
|
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
7
7
|
const SHORT_ID_RE = /^\d+$/;
|
|
8
8
|
function stripQuotes(value) {
|
|
@@ -17,7 +17,7 @@ function taskBranch(taskMd, taskId) {
|
|
|
17
17
|
return stripQuotes(value);
|
|
18
18
|
}
|
|
19
19
|
function activeRegistry(repoRoot) {
|
|
20
|
-
const registryPath = path.join(repoRoot, '
|
|
20
|
+
const registryPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', '.short-ids.json');
|
|
21
21
|
if (!fs.existsSync(registryPath))
|
|
22
22
|
return {};
|
|
23
23
|
const value = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
@@ -30,7 +30,7 @@ function activeRegistry(repoRoot) {
|
|
|
30
30
|
return Object.fromEntries(Object.entries(ids).filter((entry) => SHORT_ID_RE.test(entry[0]) && typeof entry[1] === 'string' && TASK_ID_RE.test(entry[1])));
|
|
31
31
|
}
|
|
32
32
|
function taskIdentity(repoRoot, taskId, registry) {
|
|
33
|
-
const taskMd = path.join(repoRoot, '
|
|
33
|
+
const taskMd = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId, 'task.md');
|
|
34
34
|
if (!fs.existsSync(taskMd))
|
|
35
35
|
throw new Error(`Active task not found: ${taskId}`);
|
|
36
36
|
const shortIds = Object.entries(registry).filter(([, id]) => id === taskId).map(([shortId]) => shortId);
|
|
@@ -42,12 +42,30 @@ function taskIdentity(repoRoot, taskId, registry) {
|
|
|
42
42
|
function cleanupTaskId(requestedRef, repoRoot) {
|
|
43
43
|
if (TASK_ID_RE.test(requestedRef))
|
|
44
44
|
return requestedRef;
|
|
45
|
-
|
|
45
|
+
const shortIdLength = configuredShortIdLength(repoRoot);
|
|
46
|
+
const normalized = normalizeShortIdInput(requestedRef, { shortIdLength });
|
|
47
|
+
if (normalized.kind === 'error')
|
|
48
|
+
throw new Error(normalized.message);
|
|
49
|
+
if (normalized.kind !== 'shortId')
|
|
46
50
|
return null;
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
49
|
-
throw new Error(
|
|
50
|
-
|
|
51
|
+
const registry = inspectShortIdRegistry(repoRoot, shortIdLength);
|
|
52
|
+
if (registry.status !== 'valid')
|
|
53
|
+
throw new Error(registry.error.message);
|
|
54
|
+
const match = [...registry.shortIds].find(([, shortId]) => shortId === normalized.value);
|
|
55
|
+
if (!match) {
|
|
56
|
+
throw new Error(`Short id '${normalized.value}' not found in active task registry`);
|
|
57
|
+
}
|
|
58
|
+
return match[0];
|
|
59
|
+
}
|
|
60
|
+
function cleanupTaskIdByBranch(requestedRef, repoRoot) {
|
|
61
|
+
const taskIds = new Set(TASK_WORKSPACE_STATES
|
|
62
|
+
.flatMap((state) => listTaskWorkspaces(repoRoot, state))
|
|
63
|
+
.filter((task) => task.branch === requestedRef)
|
|
64
|
+
.map((task) => task.taskId));
|
|
65
|
+
if (taskIds.size > 1) {
|
|
66
|
+
throw new Error(`SANDBOX_TASK_IDENTITY_AMBIGUOUS: branch '${requestedRef}' is bound to multiple tasks`);
|
|
67
|
+
}
|
|
68
|
+
return taskIds.values().next().value ?? null;
|
|
51
69
|
}
|
|
52
70
|
export function resolveSandboxTarget(requestedRef, repoRoot) {
|
|
53
71
|
if (isRemovedHashShortIdInput(requestedRef)) {
|
|
@@ -64,13 +82,13 @@ export function resolveSandboxTarget(requestedRef, repoRoot) {
|
|
|
64
82
|
if (!taskId)
|
|
65
83
|
throw new Error(`Short id '${requestedRef}' not found in active task registry`);
|
|
66
84
|
const workspace = taskIdentity(repoRoot, taskId, registry);
|
|
67
|
-
return { requestedRef, branch: taskBranch(path.join(repoRoot, '
|
|
85
|
+
return { requestedRef, branch: taskBranch(path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId, 'task.md'), taskId), workspace };
|
|
68
86
|
}
|
|
69
87
|
if (TASK_ID_RE.test(requestedRef)) {
|
|
70
88
|
const workspace = taskIdentity(repoRoot, requestedRef, registry);
|
|
71
|
-
return { requestedRef, branch: taskBranch(path.join(repoRoot, '
|
|
89
|
+
return { requestedRef, branch: taskBranch(path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', requestedRef, 'task.md'), requestedRef), workspace };
|
|
72
90
|
}
|
|
73
|
-
const activeRoot = path.join(repoRoot, '
|
|
91
|
+
const activeRoot = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active');
|
|
74
92
|
const matches = fs.existsSync(activeRoot)
|
|
75
93
|
? fs.readdirSync(activeRoot, { withFileTypes: true }).flatMap((entry) => {
|
|
76
94
|
if (!entry.isDirectory() || !TASK_ID_RE.test(entry.name))
|
|
@@ -94,7 +112,8 @@ export function resolveSandboxCleanupTarget(requestedRef, repoRoot, options = {}
|
|
|
94
112
|
if (isRemovedHashShortIdInput(requestedRef)) {
|
|
95
113
|
throw new Error(`Invalid task short id '${requestedRef}': task short ids must use bare digits`);
|
|
96
114
|
}
|
|
97
|
-
const taskId = cleanupTaskId(requestedRef, repoRoot)
|
|
115
|
+
const taskId = cleanupTaskId(requestedRef, repoRoot)
|
|
116
|
+
?? cleanupTaskIdByBranch(requestedRef, repoRoot);
|
|
98
117
|
if (taskId) {
|
|
99
118
|
let task;
|
|
100
119
|
try {
|
|
@@ -123,14 +142,11 @@ export function resolveSandboxCleanupTarget(requestedRef, repoRoot, options = {}
|
|
|
123
142
|
taskState: task.state
|
|
124
143
|
};
|
|
125
144
|
}
|
|
126
|
-
const target = resolveSandboxTarget(requestedRef, repoRoot);
|
|
127
145
|
return {
|
|
128
146
|
requestedRef,
|
|
129
|
-
branch:
|
|
130
|
-
workspace:
|
|
131
|
-
|
|
132
|
-
: { mode: 'branch-only' },
|
|
133
|
-
taskState: target.workspace.mode === 'task-bound' ? 'active' : 'branch-only'
|
|
147
|
+
branch: requestedRef,
|
|
148
|
+
workspace: { mode: 'branch-only' },
|
|
149
|
+
taskState: 'branch-only'
|
|
134
150
|
};
|
|
135
151
|
}
|
|
136
152
|
export function parseSandboxWorkspaceIdentity(labels, keys) {
|
|
@@ -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.js';
|
|
4
6
|
import { resolveTaskWorkspace } from './task-resolver.js';
|
|
5
7
|
import { createSandboxControlIdentitySentinel, writeSandboxControlIdentitySentinel } from './control/identity-sentinel.js';
|
|
6
8
|
export const SANDBOX_WORKSPACE_VIEW_STATES = Object.freeze([
|
|
@@ -101,7 +103,8 @@ function assertSafeMountTargetPath(target, worktreeRoot, canonicalWorktreeRoot)
|
|
|
101
103
|
export function assertSandboxTaskSource(repoRoot, taskId) {
|
|
102
104
|
if (!/^TASK-\d{8}-\d{6}$/.test(taskId))
|
|
103
105
|
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
104
|
-
const
|
|
106
|
+
const workspaceRootPath = resolveProjectTaskWorkspaceRoot(repoRoot);
|
|
107
|
+
const activeSource = path.join(workspaceRootPath, 'active', taskId);
|
|
105
108
|
try {
|
|
106
109
|
const stat = fs.lstatSync(activeSource);
|
|
107
110
|
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
@@ -109,7 +112,7 @@ export function assertSandboxTaskSource(repoRoot, taskId) {
|
|
|
109
112
|
}
|
|
110
113
|
const canonical = fs.realpathSync.native(activeSource);
|
|
111
114
|
const activeRoot = fs.realpathSync.native(path.dirname(activeSource));
|
|
112
|
-
const workspaceRoot = fs.realpathSync.native(
|
|
115
|
+
const workspaceRoot = fs.realpathSync.native(workspaceRootPath);
|
|
113
116
|
const relative = path.relative(workspaceRoot, canonical);
|
|
114
117
|
if (path.dirname(canonical) !== activeRoot || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
115
118
|
throw new Error(`SANDBOX_TASK_SOURCE_INVALID: ${activeSource} escapes the task workspace`);
|
|
@@ -129,39 +132,46 @@ export function assertSandboxTaskSource(repoRoot, taskId) {
|
|
|
129
132
|
throw new Error(`SANDBOX_TASK_SOURCE_INVALID: ${source} must be a real directory`);
|
|
130
133
|
}
|
|
131
134
|
const canonical = fs.realpathSync.native(source);
|
|
132
|
-
const workspaceRoot = fs.realpathSync.native(
|
|
135
|
+
const workspaceRoot = fs.realpathSync.native(workspaceRootPath);
|
|
133
136
|
const relative = path.relative(workspaceRoot, canonical);
|
|
134
137
|
if (path.basename(canonical) !== taskId || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
135
138
|
throw new Error(`SANDBOX_TASK_SOURCE_INVALID: ${source} escapes the task workspace`);
|
|
136
139
|
}
|
|
137
140
|
return canonical;
|
|
138
141
|
}
|
|
139
|
-
export function prepareSandboxWorkspaceMountTargets(
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
const workspaceRoot = path.resolve(
|
|
142
|
+
export function prepareSandboxWorkspaceMountTargets(repoRoot) {
|
|
143
|
+
const resolvedHome = path.resolve(process.env.HOME || os.homedir());
|
|
144
|
+
const canonicalHome = fs.realpathSync.native(resolvedHome);
|
|
145
|
+
const workspaceRoot = path.resolve(resolveProjectTaskWorkspaceRoot(repoRoot));
|
|
146
|
+
const relativeRoot = path.relative(resolvedHome, workspaceRoot);
|
|
147
|
+
if (relativeRoot.startsWith('..') || path.isAbsolute(relativeRoot)) {
|
|
148
|
+
throw new Error(`Sandbox workspace mount target escapes the host home: ${workspaceRoot}`);
|
|
149
|
+
}
|
|
143
150
|
const statePaths = sandboxWorkspaceViewStatePaths(workspaceRoot);
|
|
144
151
|
const registryTarget = path.join(workspaceRoot, 'active', '.short-ids.json');
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
148
|
-
for (const target of [workspaceRoot, ...statePaths.map(({ hostPath }) => hostPath), registryTarget, ...(completedTaskTarget ? [completedTaskTarget] : [])]) {
|
|
149
|
-
assertSafeMountTargetPath(target, resolvedWorktreeRoot, canonicalWorktreeRoot);
|
|
152
|
+
for (const target of [workspaceRoot, ...statePaths.map(({ hostPath }) => hostPath), registryTarget]) {
|
|
153
|
+
assertSafeMountTargetPath(target, resolvedHome, canonicalHome);
|
|
150
154
|
}
|
|
151
155
|
fs.mkdirSync(workspaceRoot, { recursive: true, mode: 0o700 });
|
|
152
156
|
for (const { hostPath } of statePaths) {
|
|
153
157
|
fs.mkdirSync(hostPath, { recursive: true, mode: 0o700 });
|
|
154
158
|
fs.chmodSync(hostPath, 0o700);
|
|
155
159
|
}
|
|
156
|
-
if (completedTaskTarget)
|
|
157
|
-
fs.mkdirSync(completedTaskTarget, { recursive: true, mode: 0o700 });
|
|
158
160
|
fs.chmodSync(workspaceRoot, 0o700);
|
|
159
|
-
fs.
|
|
161
|
+
const registryFd = fs.openSync(registryTarget, 'a', 0o600);
|
|
162
|
+
try {
|
|
163
|
+
if (fs.fstatSync(registryFd).size === 0) {
|
|
164
|
+
fs.writeFileSync(registryFd, `${JSON.stringify({ version: 1, ids: {} })}\n`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
fs.closeSync(registryFd);
|
|
169
|
+
}
|
|
160
170
|
}
|
|
161
171
|
export function prepareSandboxCompletedTaskMountSource(repoRoot, taskId) {
|
|
162
172
|
if (!/^TASK-\d{8}-\d{6}$/.test(taskId))
|
|
163
173
|
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
164
|
-
const workspaceRoot =
|
|
174
|
+
const workspaceRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
|
|
165
175
|
fs.mkdirSync(path.join(workspaceRoot, 'completed'), { recursive: true, mode: 0o700 });
|
|
166
176
|
const completedRoot = path.join(workspaceRoot, 'completed');
|
|
167
177
|
const completedStat = fs.lstatSync(completedRoot);
|
|
@@ -184,17 +194,14 @@ export function prepareSandboxCompletedTaskMountSource(repoRoot, taskId) {
|
|
|
184
194
|
export function sandboxCompletedTaskMountSource(repoRoot, taskId) {
|
|
185
195
|
if (!/^TASK-\d{8}-\d{6}$/.test(taskId))
|
|
186
196
|
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
187
|
-
const source = path.resolve(repoRoot, '
|
|
197
|
+
const source = path.resolve(resolveProjectTaskWorkspaceRoot(repoRoot), 'completed', taskId);
|
|
188
198
|
const stat = fs.lstatSync(source);
|
|
189
199
|
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
190
200
|
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
191
|
-
const canonicalWorkspace = fs.realpathSync.native(
|
|
201
|
+
const canonicalWorkspace = fs.realpathSync.native(resolveProjectTaskWorkspaceRoot(repoRoot));
|
|
192
202
|
const canonicalSource = fs.realpathSync.native(source);
|
|
193
|
-
const canonicalRepoRoot = fs.realpathSync.native(repoRoot);
|
|
194
|
-
const workspaceRelative = path.relative(canonicalRepoRoot, canonicalWorkspace);
|
|
195
203
|
const relative = path.relative(canonicalWorkspace, canonicalSource);
|
|
196
|
-
if (
|
|
197
|
-
|| path.dirname(canonicalSource) !== fs.realpathSync.native(path.dirname(source))
|
|
204
|
+
if (path.dirname(canonicalSource) !== fs.realpathSync.native(path.dirname(source))
|
|
198
205
|
|| relative.startsWith('..') || path.isAbsolute(relative))
|
|
199
206
|
throw new Error('SANDBOX_TASK_SOURCE_INVALID');
|
|
200
207
|
return canonicalSource;
|
|
@@ -4,7 +4,6 @@ import { resolveTaskRef } from './resolve-ref.js';
|
|
|
4
4
|
import { locateActivityLog, appendActivityEntry } from './activity-log.js';
|
|
5
5
|
import { assertWritableInventory, buildArtifactLinkSection, inspectTaskArtifacts } from './artifact-lifecycle.js';
|
|
6
6
|
import { parseArtifactName } from './artifact-name.js';
|
|
7
|
-
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
8
7
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
9
8
|
const HEAD_RE = /^[0-9a-f]{40}$/;
|
|
10
9
|
const REVIEW_ACTION_RE = /^Review PR \(Round ([1-9]\d*)\)( \[started\])?$/;
|
|
@@ -192,7 +191,7 @@ function terminalNote(intent) {
|
|
|
192
191
|
}
|
|
193
192
|
return `Outcome: ${outcomeLabel(intent.outcome)}, reason: ${intent.reason} → ${intent.artifact}`;
|
|
194
193
|
}
|
|
195
|
-
function
|
|
194
|
+
function applyActivityIntent(intent, normalizedAgent, options) {
|
|
196
195
|
const state = readSnapshot(intent.taskRef, options.repoRoot);
|
|
197
196
|
if (!state.ok)
|
|
198
197
|
return failed(intent, state.code, state.message, state.taskId);
|
|
@@ -297,15 +296,6 @@ export function applyPrReviewActivityIntent(intent, options = {}) {
|
|
|
297
296
|
if (resolved.state !== 'active') {
|
|
298
297
|
return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
299
298
|
}
|
|
300
|
-
|
|
301
|
-
return applyLocked(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot });
|
|
302
|
-
try {
|
|
303
|
-
return withTaskExecutionLock(resolved.repoRoot, resolved.taskId, `task-activity:${intent.kind}`, () => applyLocked(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot }), options.lockOptions);
|
|
304
|
-
}
|
|
305
|
-
catch (error) {
|
|
306
|
-
if (error instanceof TaskExecutionLockError)
|
|
307
|
-
return failed(intent, error.code, error.message, resolved.taskId);
|
|
308
|
-
return failed(intent, 'ACTIVITY_LOCK_FAILED', error instanceof Error ? error.message : String(error), resolved.taskId);
|
|
309
|
-
}
|
|
299
|
+
return applyActivityIntent(intent, validated.agent, { ...options, repoRoot: resolved.repoRoot });
|
|
310
300
|
}
|
|
311
301
|
//# sourceMappingURL=activity-intent.js.map
|
|
@@ -79,11 +79,11 @@ function failure(requestRef, family, error, extra = {}) {
|
|
|
79
79
|
function diagnostic(code, family, name, message) {
|
|
80
80
|
return { code, family, name, message };
|
|
81
81
|
}
|
|
82
|
-
function inspectTaskArtifacts(taskRef, family, options = {}) {
|
|
82
|
+
function inspectTaskArtifacts(taskRef, family, options = {}, resolvedTask) {
|
|
83
83
|
const spec = familySpec(family);
|
|
84
84
|
if (!spec)
|
|
85
85
|
return failure(taskRef, family, { code: 'ARTIFACT_FAMILY_UNKNOWN', message: `unknown artifact family '${family}'` });
|
|
86
|
-
const resolved = resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
86
|
+
const resolved = resolvedTask ?? resolveTaskRef(taskRef, { repoRoot: options.repoRoot });
|
|
87
87
|
if (!resolved.ok)
|
|
88
88
|
return failure(taskRef, family, { code: resolved.code, message: resolved.message }, { taskId: resolved.taskId });
|
|
89
89
|
return inspectArtifactDirectory(resolved.taskDir, spec.family, {
|
|
@@ -399,8 +399,8 @@ function attachArtifactSelection(context, pathState, options) {
|
|
|
399
399
|
return fail('ARTIFACT_SELECTION_INDETERMINATE', error instanceof Error ? error.message : String(error));
|
|
400
400
|
}
|
|
401
401
|
}
|
|
402
|
-
function
|
|
403
|
-
const inventory = inspectTaskArtifacts(taskRef, family, options);
|
|
402
|
+
function resolveArtifactContextFromTask(taskRef, family, options, resolvedTask) {
|
|
403
|
+
const inventory = inspectTaskArtifacts(taskRef, family, options, resolvedTask);
|
|
404
404
|
if (inventory.status === 'failed')
|
|
405
405
|
return { ...inventory, inputs: [], codeMode: null };
|
|
406
406
|
const writableError = assertWritableInventory(inventory);
|
|
@@ -411,7 +411,7 @@ function resolveArtifactContext(taskRef, family, options = {}) {
|
|
|
411
411
|
return contextFailure(inventory, 'LIFECYCLE_PATH_INVALID', pathState.message);
|
|
412
412
|
}
|
|
413
413
|
if (inventory.family === 'code') {
|
|
414
|
-
const code = resolveCodeContext(inventory, options);
|
|
414
|
+
const code = resolveCodeContext(inventory, options, resolvedTask);
|
|
415
415
|
const selected = attachArtifactSelection(code, pathState, options);
|
|
416
416
|
if (selected.status !== 'ready' || !selected.selection)
|
|
417
417
|
return selected;
|
|
@@ -447,7 +447,7 @@ function resolveArtifactContext(taskRef, family, options = {}) {
|
|
|
447
447
|
const required = REQUIRED_INPUT[inventory.family];
|
|
448
448
|
const inputs = [];
|
|
449
449
|
if (required) {
|
|
450
|
-
const input = inspectTaskArtifacts(taskRef, required, options);
|
|
450
|
+
const input = inspectTaskArtifacts(taskRef, required, options, resolvedTask);
|
|
451
451
|
if (input.status === 'failed' || !input.latest) {
|
|
452
452
|
return { ...inventory, status: 'failed', inputs, codeMode: null, error: { code: 'ARTIFACT_INPUT_MISSING', message: `latest ${required} artifact is required` } };
|
|
453
453
|
}
|
|
@@ -456,7 +456,7 @@ function resolveArtifactContext(taskRef, family, options = {}) {
|
|
|
456
456
|
const optional = OPTIONAL_CONTEXT[inventory.family];
|
|
457
457
|
if (optional) {
|
|
458
458
|
for (const family of optional) {
|
|
459
|
-
const context = inspectTaskArtifacts(taskRef, family, options);
|
|
459
|
+
const context = inspectTaskArtifacts(taskRef, family, options, resolvedTask);
|
|
460
460
|
if (context.status === 'ready' && context.latest)
|
|
461
461
|
inputs.push(context.latest);
|
|
462
462
|
}
|
|
@@ -466,16 +466,22 @@ function resolveArtifactContext(taskRef, family, options = {}) {
|
|
|
466
466
|
}
|
|
467
467
|
return attachArtifactSelection({ ...inventory, inputs, codeMode: null }, pathState, options);
|
|
468
468
|
}
|
|
469
|
-
function
|
|
469
|
+
function resolveArtifactContext(taskRef, family, options = {}) {
|
|
470
|
+
return resolveArtifactContextFromTask(taskRef, family, options);
|
|
471
|
+
}
|
|
472
|
+
export function resolveArtifactContextForResolvedTask(resolvedTask, family, options = {}) {
|
|
473
|
+
return resolveArtifactContextFromTask(resolvedTask.taskId, family, options, resolvedTask);
|
|
474
|
+
}
|
|
475
|
+
function resolveCodeContext(inventory, options, resolvedTask) {
|
|
470
476
|
const taskRef = inventory.requestRef;
|
|
471
477
|
const pathState = lifecyclePathState(inventory.taskDir);
|
|
472
478
|
if (pathState.status !== 'valid')
|
|
473
479
|
return contextFailure(inventory, 'LIFECYCLE_PATH_INVALID', pathState.message);
|
|
474
480
|
const inputFamily = pathState.decision.path === 'streamlined' ? 'analysis' : 'plan';
|
|
475
|
-
const source = inspectTaskArtifacts(taskRef, inputFamily, options);
|
|
481
|
+
const source = inspectTaskArtifacts(taskRef, inputFamily, options, resolvedTask);
|
|
476
482
|
if (source.status === 'failed' || !source.latest)
|
|
477
483
|
return contextFailure(inventory, 'ARTIFACT_INPUT_MISSING', `latest ${inputFamily} artifact is required`);
|
|
478
|
-
const reviewCode = inspectTaskArtifacts(taskRef, 'review-code', options);
|
|
484
|
+
const reviewCode = inspectTaskArtifacts(taskRef, 'review-code', options, resolvedTask);
|
|
479
485
|
try {
|
|
480
486
|
const taskContent = fs.readFileSync(path.join(inventory.taskDir, 'task.md'), 'utf8');
|
|
481
487
|
const hasPendingImplementation = parseImplementationInputs(taskContent).rows.some((input) => input.needsImplementation && input.status === 'pending');
|
|
@@ -492,7 +498,7 @@ function resolveCodeContext(inventory, options) {
|
|
|
492
498
|
const reviewMax = reviewCode.latest?.round ?? 0;
|
|
493
499
|
const inputs = [source.latest];
|
|
494
500
|
const sourceReviewFamily = inputFamily === 'analysis' ? 'review-analysis' : 'review-plan';
|
|
495
|
-
const sourceReview = inspectTaskArtifacts(taskRef, sourceReviewFamily, options);
|
|
501
|
+
const sourceReview = inspectTaskArtifacts(taskRef, sourceReviewFamily, options, resolvedTask);
|
|
496
502
|
if (sourceReview.status === 'ready' && sourceReview.latest && sourceReview.reviewedInput?.name === source.latest.name)
|
|
497
503
|
inputs.push(sourceReview.latest);
|
|
498
504
|
if (inventory.openRounds.length > 0) {
|
|
@@ -5,7 +5,6 @@ import { readStableFileSync } from './secure-fs.js';
|
|
|
5
5
|
import { parseArtifactName } from './artifact-name.js';
|
|
6
6
|
import { scanVisibleMarkdown } from './markdown.js';
|
|
7
7
|
import { getArtifactSchema, renderArtifactSkeleton } from './artifact-schema.js';
|
|
8
|
-
import { withTaskExecutionLock } from './task-execution-lock.js';
|
|
9
8
|
function sha256Content(content) {
|
|
10
9
|
return createHash('sha256').update(content, 'utf8').digest('hex');
|
|
11
10
|
}
|
|
@@ -239,57 +238,50 @@ function initializeArtifactSkeleton(request) {
|
|
|
239
238
|
if (path.dirname(path.resolve(target)) !== path.resolve(request.taskDir))
|
|
240
239
|
return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'artifact must be inside the current task directory');
|
|
241
240
|
try {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
fs.renameSync(tempPath, target);
|
|
273
|
-
}
|
|
274
|
-
catch (error) {
|
|
275
|
-
try {
|
|
276
|
-
fs.unlinkSync(tempPath);
|
|
277
|
-
}
|
|
278
|
-
catch { /* preserve primary error */ }
|
|
279
|
-
return resultFailure('ARTIFACT_INIT_WRITE_FAILED', String(error));
|
|
280
|
-
}
|
|
281
|
-
return {
|
|
282
|
-
status: 'applied',
|
|
283
|
-
changed: true,
|
|
284
|
-
artifactSha256: sha256Content(content),
|
|
285
|
-
semanticDigest: canonicalSemanticDigest(content),
|
|
286
|
-
error: null
|
|
287
|
-
};
|
|
288
|
-
});
|
|
241
|
+
const existing = fs.lstatSync(target);
|
|
242
|
+
if (existing.isSymbolicLink() || !existing.isFile())
|
|
243
|
+
return resultFailure('ARTIFACT_INIT_TARGET_INVALID', 'artifact target is not a regular file');
|
|
244
|
+
return resultNoOp(readStableFileSync(target, { maxBytes: 1024 * 1024 }).bytes.toString('utf8'));
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
if (error.code !== 'ENOENT')
|
|
248
|
+
return resultFailure('ARTIFACT_INIT_TARGET_INVALID', String(error));
|
|
249
|
+
}
|
|
250
|
+
const skeleton = renderArtifactSkeleton({
|
|
251
|
+
taskId: request.taskId,
|
|
252
|
+
family: request.family,
|
|
253
|
+
artifact: request.artifact,
|
|
254
|
+
...(request.locale ? { locale: request.locale } : {})
|
|
255
|
+
});
|
|
256
|
+
const content = skeleton.trimEnd();
|
|
257
|
+
const tempPath = path.join(request.taskDir, `.${request.artifact}.init-${process.pid}-${Date.now()}.tmp`);
|
|
258
|
+
try {
|
|
259
|
+
fs.writeFileSync(tempPath, content, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
260
|
+
try {
|
|
261
|
+
fs.lstatSync(target);
|
|
262
|
+
fs.unlinkSync(tempPath);
|
|
263
|
+
return resultFailure('ARTIFACT_INIT_TARGET_EXISTS', 'artifact was created concurrently');
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
if (error.code !== 'ENOENT')
|
|
267
|
+
throw error;
|
|
268
|
+
}
|
|
269
|
+
fs.renameSync(tempPath, target);
|
|
289
270
|
}
|
|
290
271
|
catch (error) {
|
|
291
|
-
|
|
272
|
+
try {
|
|
273
|
+
fs.unlinkSync(tempPath);
|
|
274
|
+
}
|
|
275
|
+
catch { /* preserve primary error */ }
|
|
276
|
+
return resultFailure('ARTIFACT_INIT_WRITE_FAILED', String(error));
|
|
292
277
|
}
|
|
278
|
+
return {
|
|
279
|
+
status: 'applied',
|
|
280
|
+
changed: true,
|
|
281
|
+
artifactSha256: sha256Content(content),
|
|
282
|
+
semanticDigest: canonicalSemanticDigest(content),
|
|
283
|
+
error: null
|
|
284
|
+
};
|
|
293
285
|
}
|
|
294
286
|
export { canonicalSemanticDigest, initializeArtifactSkeleton, inspectArtifactContract, inspectArtifactPatterns, inspectArtifactStructure, sha256Content };
|
|
295
287
|
//# sourceMappingURL=artifact-operations.js.map
|
|
@@ -275,7 +275,7 @@ function recommendNext(facts) {
|
|
|
275
275
|
}
|
|
276
276
|
return { action: null, reasonCode: 'LIFECYCLE_REVIEWED', evidence: ['latest code review is approved'] };
|
|
277
277
|
}
|
|
278
|
-
function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBusy = false) {
|
|
278
|
+
function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBusy = false, repositoryRoot) {
|
|
279
279
|
try {
|
|
280
280
|
const metadata = parseTypedTaskFrontmatter(content);
|
|
281
281
|
const rework = parseReworkIntentDocument(content);
|
|
@@ -387,7 +387,7 @@ function buildLifecycleFacts(taskDir, content, taskState = 'active', executionBu
|
|
|
387
387
|
&& artifactHashes[input] === receipt.inputSha256)
|
|
388
388
|
reviewedInputs[family] = input;
|
|
389
389
|
if (family === 'review-code') {
|
|
390
|
-
const identity = inspectReviewIdentity(taskDir, reviewContent);
|
|
390
|
+
const identity = inspectReviewIdentity(taskDir, reviewContent, repositoryRoot);
|
|
391
391
|
reviewCodeIdentityValid = identity.status === 'matched'
|
|
392
392
|
&& hasArtifactCompletionFact(content, path.join(taskDir, latest), 'review-code.completed')
|
|
393
393
|
&& hasArtifactCompletionLog(content, latest, 'review-code.completed');
|
|
@@ -4,9 +4,10 @@ import { execFileSync } from 'node:child_process';
|
|
|
4
4
|
import { formatTable } from '../../table.js';
|
|
5
5
|
import { parseTaskFrontmatter, extractTitle } from '../frontmatter.js';
|
|
6
6
|
import { loadShortIdByTaskId } from '../short-id.js';
|
|
7
|
+
import { resolveProjectTaskWorkspaceRoot } from '../workspace-root.js';
|
|
7
8
|
const USAGE = `Usage: ai task ls [--all | --blocked | --completed]
|
|
8
9
|
|
|
9
|
-
Lists tasks under .
|
|
10
|
+
Lists tasks under the project task workspace. Defaults to active tasks only.
|
|
10
11
|
--all Include active + blocked + completed (excludes archive)
|
|
11
12
|
--blocked Only blocked tasks
|
|
12
13
|
--completed Only completed tasks
|
|
@@ -55,7 +56,7 @@ function parseSelection(args) {
|
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
function collectTasks(repoRoot, state) {
|
|
58
|
-
const dir = path.join(repoRoot,
|
|
59
|
+
const dir = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), state);
|
|
59
60
|
if (!fs.existsSync(dir))
|
|
60
61
|
return [];
|
|
61
62
|
const shortIdByTaskId = state === 'active' || state === 'completed'
|
|
@@ -101,7 +102,7 @@ function ls(args = []) {
|
|
|
101
102
|
rows.push(...collectTasks(repoRoot, state));
|
|
102
103
|
}
|
|
103
104
|
if (rows.length === 0) {
|
|
104
|
-
process.stdout.write(`No tasks under
|
|
105
|
+
process.stdout.write(`No tasks under project task workspace state ${selection.join('|')}\n`);
|
|
105
106
|
return;
|
|
106
107
|
}
|
|
107
108
|
const tableRows = rows.map((r, i) => [
|
|
@@ -388,7 +388,7 @@ function buildFromResolved(input) {
|
|
|
388
388
|
const run = input.run ?? makeRunner(input.repoRoot);
|
|
389
389
|
const artifacts = enumerateArtifacts(input.taskDir);
|
|
390
390
|
const workflow = collectWorkflow(content, input.now);
|
|
391
|
-
const facts = buildLifecycleFacts(input.taskDir, content, fm.status ?? 'active');
|
|
391
|
+
const facts = buildLifecycleFacts(input.taskDir, content, fm.status ?? 'active', false, input.repoRoot);
|
|
392
392
|
const next = facts.ok ? recommendNext(facts.facts) : null;
|
|
393
393
|
const recommendation = next?.action ?? DASH;
|
|
394
394
|
return {
|