@fitlab-ai/agent-infra 0.11.15 → 0.12.1
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 +19 -33
- package/dist/lib/internal/platform-comment.js +59 -9
- 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/issue-comments.js +373 -267
- package/dist/lib/platform/issue-summary-content.js +112 -0
- package/dist/lib/platform/pr-change-report.js +175 -26
- package/dist/lib/platform/pr-summary-content.js +54 -0
- package/dist/lib/platform/pr-summary.js +321 -296
- package/dist/lib/platform/pull-requests.js +3 -15
- package/dist/lib/platform/summary-content-pipeline.js +18 -0
- 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 +62 -12
- 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 +33 -29
- 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 +87 -88
- 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 +20 -37
- package/lib/internal/platform-comment.ts +49 -6
- 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/issue-comments.ts +478 -280
- package/lib/platform/issue-summary-content.ts +132 -0
- package/lib/platform/pr-change-report.ts +165 -20
- package/lib/platform/pr-summary-content.ts +60 -0
- package/lib/platform/pr-summary.ts +367 -269
- package/lib/platform/pull-requests.ts +3 -24
- package/lib/platform/summary-content-pipeline.ts +23 -0
- 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 +60 -27
- 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 +27 -30
- 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 +70 -82
- 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 +5 -3
- package/templates/.agents/rules/pr-sync.zh-CN.md +7 -10
- 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 +7 -7
- package/templates/.agents/skills/cancel-task/SKILL.zh-CN.md +7 -7
- 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 +2 -2
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +2 -2
- 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 +3 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-task/SKILL.en.md +15 -30
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +17 -32
- 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 +10 -3
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +10 -3
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +3 -3
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +3 -3
- 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 -22
- package/templates/.agents/skills/create-task/SKILL.zh-CN.md +4 -22
- 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 +4 -4
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/watch-pr/config/verify.json +0 -1
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +3 -3
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +3 -3
- 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
package/lib/task/resolve-ref.ts
CHANGED
|
@@ -3,12 +3,15 @@ import path from 'node:path';
|
|
|
3
3
|
import { execFileSync } from 'node:child_process';
|
|
4
4
|
import { normalizeShortIdInput, resolveShortIdReadOnly } from './short-id.ts';
|
|
5
5
|
import { parseTaskFrontmatter } from './frontmatter.ts';
|
|
6
|
+
import { resolveProjectTaskWorkspaceRoot } from './workspace-root.ts';
|
|
6
7
|
|
|
7
8
|
const TASK_ID_RE = /^TASK-\d{8}-\d{6}$/;
|
|
8
9
|
// Flat-structured workspace dirs that hold tasks under `{dir}/{taskId}/task.md`.
|
|
9
10
|
// Note: `archive` uses a three-level YYYY/MM/DD layout and is handled separately.
|
|
10
11
|
const FLAT_WORKSPACE_DIRS = ['active', 'blocked', 'completed'] as const;
|
|
11
12
|
|
|
13
|
+
function taskRoot(repoRoot: string): string { return resolveProjectTaskWorkspaceRoot(repoRoot); }
|
|
14
|
+
|
|
12
15
|
type ResolveRefResult =
|
|
13
16
|
| {
|
|
14
17
|
ok: true;
|
|
@@ -80,12 +83,12 @@ function listSortedNumeric(dir: string, width: number): string[] {
|
|
|
80
83
|
.reverse();
|
|
81
84
|
}
|
|
82
85
|
|
|
83
|
-
function findInArchive(
|
|
84
|
-
// archive-tasks stores local material at
|
|
86
|
+
function findInArchive(workspaceRoot: string, taskId: string): string | null {
|
|
87
|
+
// archive-tasks stores local material at the project task workspace archive/YYYY/MM/DD/{taskId}/local/task.md
|
|
85
88
|
// where YYYY/MM/DD comes from completed_at (or updated_at fallback) — NOT from
|
|
86
89
|
// the task id's creation date. So we cannot derive the path from taskId alone;
|
|
87
90
|
// walk the bounded YYYY/MM/DD tree instead. Newest-first to favor recent archives.
|
|
88
|
-
const archiveDir = path.join(
|
|
91
|
+
const archiveDir = path.join(workspaceRoot, 'archive');
|
|
89
92
|
for (const year of listSortedNumeric(archiveDir, 4)) {
|
|
90
93
|
const yearDir = path.join(archiveDir, year);
|
|
91
94
|
for (const month of listSortedNumeric(yearDir, 2)) {
|
|
@@ -100,14 +103,14 @@ function findInArchive(repoRoot: string, taskId: string): string | null {
|
|
|
100
103
|
}
|
|
101
104
|
|
|
102
105
|
function findTaskMd(
|
|
103
|
-
|
|
106
|
+
workspaceRoot: string,
|
|
104
107
|
taskId: string
|
|
105
108
|
): { taskMdPath: string; state: TaskWorkspaceState } | null {
|
|
106
109
|
for (const sub of FLAT_WORKSPACE_DIRS) {
|
|
107
|
-
const candidate = path.join(
|
|
110
|
+
const candidate = path.join(workspaceRoot, sub, taskId, 'task.md');
|
|
108
111
|
if (fs.existsSync(candidate)) return { taskMdPath: candidate, state: sub };
|
|
109
112
|
}
|
|
110
|
-
const archived = findInArchive(
|
|
113
|
+
const archived = findInArchive(workspaceRoot, taskId);
|
|
111
114
|
return archived ? { taskMdPath: archived, state: 'archive' } : null;
|
|
112
115
|
}
|
|
113
116
|
|
|
@@ -119,8 +122,9 @@ function findTaskMd(
|
|
|
119
122
|
*/
|
|
120
123
|
function enumerateTaskDirs(repoRoot: string): { taskId: string; taskDir: string }[] {
|
|
121
124
|
const out: { taskId: string; taskDir: string }[] = [];
|
|
125
|
+
const workspaceRoot = taskRoot(repoRoot);
|
|
122
126
|
for (const sub of FLAT_WORKSPACE_DIRS) {
|
|
123
|
-
const base = path.join(
|
|
127
|
+
const base = path.join(workspaceRoot, sub);
|
|
124
128
|
if (!fs.existsSync(base)) continue;
|
|
125
129
|
for (const entry of fs.readdirSync(base).sort()) {
|
|
126
130
|
if (!TASK_ID_RE.test(entry)) continue;
|
|
@@ -139,6 +143,7 @@ function enumerateTaskDirs(repoRoot: string): { taskId: string; taskDir: string
|
|
|
139
143
|
*/
|
|
140
144
|
function enumerateAllTaskDirs(repoRoot: string): { taskId: string; taskDir: string; state: TaskWorkspaceState }[] {
|
|
141
145
|
const out: { taskId: string; taskDir: string; state: TaskWorkspaceState }[] = [];
|
|
146
|
+
const workspaceRoot = taskRoot(repoRoot);
|
|
142
147
|
const seen = new Map<string, string>();
|
|
143
148
|
const add = (taskId: string, taskDir: string, state: TaskWorkspaceState) => {
|
|
144
149
|
if (!TASK_ID_RE.test(taskId)) return;
|
|
@@ -155,12 +160,12 @@ function enumerateAllTaskDirs(repoRoot: string): { taskId: string; taskDir: stri
|
|
|
155
160
|
};
|
|
156
161
|
|
|
157
162
|
for (const state of FLAT_WORKSPACE_DIRS) {
|
|
158
|
-
const base = path.join(
|
|
163
|
+
const base = path.join(workspaceRoot, state);
|
|
159
164
|
if (!fs.existsSync(base)) continue;
|
|
160
165
|
for (const taskId of fs.readdirSync(base).sort()) add(taskId, path.join(base, taskId), state);
|
|
161
166
|
}
|
|
162
167
|
|
|
163
|
-
const archive = path.join(
|
|
168
|
+
const archive = path.join(workspaceRoot, 'archive');
|
|
164
169
|
for (const year of listSortedNumeric(archive, 4).reverse()) {
|
|
165
170
|
const yearDir = path.join(archive, year);
|
|
166
171
|
for (const month of listSortedNumeric(yearDir, 2).reverse()) {
|
|
@@ -178,8 +183,9 @@ function locateHotTaskDirs(
|
|
|
178
183
|
repoRoot: string,
|
|
179
184
|
taskId: string
|
|
180
185
|
): { taskId: string; taskDir: string; taskMdPath: string; state: Exclude<TaskWorkspaceState, 'archive'> }[] {
|
|
186
|
+
const workspaceRoot = taskRoot(repoRoot);
|
|
181
187
|
return FLAT_WORKSPACE_DIRS.flatMap((state) => {
|
|
182
|
-
const taskDir = path.join(
|
|
188
|
+
const taskDir = path.join(workspaceRoot, state, taskId);
|
|
183
189
|
const taskMdPath = path.join(taskDir, 'task.md');
|
|
184
190
|
return fs.existsSync(taskMdPath) ? [{ taskId, taskDir, taskMdPath, state }] : [];
|
|
185
191
|
});
|
|
@@ -244,7 +250,7 @@ function resolveTaskRef(arg: string, options: ResolveTaskRefOptions = {}): Resol
|
|
|
244
250
|
}
|
|
245
251
|
taskId = shortResult.taskId;
|
|
246
252
|
}
|
|
247
|
-
const located = findTaskMd(repoRoot, taskId);
|
|
253
|
+
const located = findTaskMd(taskRoot(repoRoot), taskId);
|
|
248
254
|
if (!located) {
|
|
249
255
|
return {
|
|
250
256
|
ok: false,
|
|
@@ -300,7 +306,7 @@ function resolveTaskContext(
|
|
|
300
306
|
};
|
|
301
307
|
}
|
|
302
308
|
|
|
303
|
-
const activeDir = path.join(repoRoot, '
|
|
309
|
+
const activeDir = path.join(taskRoot(repoRoot), 'active');
|
|
304
310
|
const matches: { taskId: string; taskDir: string; taskMdPath: string }[] = [];
|
|
305
311
|
if (fs.existsSync(activeDir)) {
|
|
306
312
|
for (const entry of fs.readdirSync(activeDir).sort()) {
|
|
@@ -10,7 +10,6 @@ import { finalizeReviewSummaryContent } from './review-artifacts.ts';
|
|
|
10
10
|
import { inspectDecisionDetailDuplicates } from './decision-details.ts';
|
|
11
11
|
import { resolveTaskRef } from './resolve-ref.ts';
|
|
12
12
|
import { validateLifecycleExecution } from './lifecycle-execution.ts';
|
|
13
|
-
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.ts';
|
|
14
13
|
import type { ResolveTaskRefErrorCode } from './resolve-ref.ts';
|
|
15
14
|
import { getArtifactSchema } from './artifact-schema.ts';
|
|
16
15
|
import { validateQualificationAudit } from './qualification-audit.ts';
|
|
@@ -69,7 +68,6 @@ type ReviewFileSystem = {
|
|
|
69
68
|
type ReviewFinalizationOptions = {
|
|
70
69
|
repoRoot?: string;
|
|
71
70
|
fileSystem?: Partial<ReviewFileSystem>;
|
|
72
|
-
lockAlreadyHeld?: boolean;
|
|
73
71
|
};
|
|
74
72
|
|
|
75
73
|
const STAGES: Record<ReviewStage, { family: 'review-analysis' | 'review-plan' | 'review-code' }> = {
|
|
@@ -123,7 +121,7 @@ function preflightFailed(
|
|
|
123
121
|
}
|
|
124
122
|
|
|
125
123
|
/** Validate the current review draft before any finding-ledger write. */
|
|
126
|
-
function
|
|
124
|
+
function preflightReviewSummary(
|
|
127
125
|
request: ReviewFinalizationRequest,
|
|
128
126
|
options: ReviewFinalizationOptions = {}
|
|
129
127
|
): ReviewPreflightResult {
|
|
@@ -178,30 +176,9 @@ function preflightReviewSummaryUnlocked(
|
|
|
178
176
|
};
|
|
179
177
|
}
|
|
180
178
|
|
|
181
|
-
function preflightReviewSummary(
|
|
182
|
-
request: ReviewFinalizationRequest,
|
|
183
|
-
options: ReviewFinalizationOptions = {}
|
|
184
|
-
): ReviewPreflightResult {
|
|
185
|
-
if (request.dryRun || options.lockAlreadyHeld) return preflightReviewSummaryUnlocked(request, options);
|
|
186
|
-
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
187
|
-
if (!resolved.ok) return preflightReviewSummaryUnlocked(request, options);
|
|
188
|
-
try {
|
|
189
|
-
return withTaskExecutionLock(
|
|
190
|
-
resolved.repoRoot,
|
|
191
|
-
resolved.taskId,
|
|
192
|
-
'task-review.preflight',
|
|
193
|
-
() => preflightReviewSummaryUnlocked(request, { ...options, lockAlreadyHeld: true })
|
|
194
|
-
);
|
|
195
|
-
} catch (error) {
|
|
196
|
-
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
197
|
-
return preflightFailed(request, 'REVIEW_PROVENANCE_INVALID', `${error.code}: ${error.message}`, resolved.taskId);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
179
|
type ReviewSummaryCandidatePreparation = Readonly<{
|
|
202
180
|
result: ReviewFinalizationResult;
|
|
203
181
|
content: string;
|
|
204
|
-
lockAlreadyHeld?: boolean;
|
|
205
182
|
}>;
|
|
206
183
|
|
|
207
184
|
/** Prepare domain validation, summary bytes and provenance before any publication. */
|
|
@@ -279,7 +256,6 @@ function prepareReviewSummaryCandidate(
|
|
|
279
256
|
operations: transformed.changed ? [{ kind: 'artifact', artifact: request.artifact, operation: 'update' }] : [],
|
|
280
257
|
error: null
|
|
281
258
|
},
|
|
282
|
-
lockAlreadyHeld: options.lockAlreadyHeld
|
|
283
259
|
};
|
|
284
260
|
}
|
|
285
261
|
|
|
@@ -303,7 +279,7 @@ function replaceCurrentArtifactAtomically(file: string, content: string): void {
|
|
|
303
279
|
}
|
|
304
280
|
}
|
|
305
281
|
|
|
306
|
-
function
|
|
282
|
+
function finalizeReviewSummary(
|
|
307
283
|
request: ReviewFinalizationRequest,
|
|
308
284
|
options: ReviewFinalizationOptions = {}
|
|
309
285
|
): ReviewFinalizationResult {
|
|
@@ -340,32 +316,6 @@ function finalizeReviewSummaryUnlocked(
|
|
|
340
316
|
}
|
|
341
317
|
}
|
|
342
318
|
|
|
343
|
-
function finalizeReviewSummary(
|
|
344
|
-
request: ReviewFinalizationRequest,
|
|
345
|
-
options: ReviewFinalizationOptions = {}
|
|
346
|
-
): ReviewFinalizationResult {
|
|
347
|
-
const effectiveOptions = options;
|
|
348
|
-
if (request.dryRun || options.lockAlreadyHeld) return finalizeReviewSummaryUnlocked(request, effectiveOptions);
|
|
349
|
-
const resolved = resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
350
|
-
if (!resolved.ok) return finalizeReviewSummaryUnlocked(request, effectiveOptions);
|
|
351
|
-
try {
|
|
352
|
-
return withTaskExecutionLock(
|
|
353
|
-
resolved.repoRoot,
|
|
354
|
-
resolved.taskId,
|
|
355
|
-
'task-review.finalize-summary',
|
|
356
|
-
() => finalizeReviewSummaryUnlocked(request, { ...effectiveOptions, lockAlreadyHeld: true })
|
|
357
|
-
);
|
|
358
|
-
} catch (error) {
|
|
359
|
-
if (!(error instanceof TaskExecutionLockError)) throw error;
|
|
360
|
-
return failed(
|
|
361
|
-
request,
|
|
362
|
-
'REVIEW_PROVENANCE_INVALID',
|
|
363
|
-
`${error.code}: ${error.message}`,
|
|
364
|
-
resolved.taskId
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
319
|
export { finalizeReviewSummary, preflightReviewSummary, prepareReviewSummaryCandidate, commitReviewSummaryProvenance };
|
|
370
320
|
export type {
|
|
371
321
|
ReviewFinalizationError,
|
|
@@ -45,7 +45,9 @@ export function inspectReviewIdentity(taskDir: string, reportContent: string, re
|
|
|
45
45
|
}
|
|
46
46
|
try {
|
|
47
47
|
const metadata = parseTypedTaskFrontmatter(fs.readFileSync(path.join(taskDir, 'task.md'), 'utf8'));
|
|
48
|
-
const taskRepositoryRoot =
|
|
48
|
+
const taskRepositoryRoot = repositoryRoot
|
|
49
|
+
? fs.realpathSync.native(repositoryRoot)
|
|
50
|
+
: git(taskDir, ['rev-parse', '--show-toplevel']);
|
|
49
51
|
const branch = String(metadata.branch ?? '').trim();
|
|
50
52
|
const gitRoot = branch ? resolveBranchWorktree(taskRepositoryRoot, branch) : taskRepositoryRoot;
|
|
51
53
|
if (!gitRoot) return result('indeterminate', `Task branch '${branch}' is not checked out in a registered worktree`, reviewedHead);
|
package/lib/task/short-id.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { parseTypedTaskFrontmatter } from './frontmatter.ts';
|
|
4
|
+
import { resolveProjectTaskWorkspaceRoot } from './workspace-root.ts';
|
|
4
5
|
|
|
5
6
|
const REGISTRY_NAME = '.short-ids.json';
|
|
6
7
|
const LOCK_NAME = '.short-ids.json.lock';
|
|
@@ -85,7 +86,7 @@ function resolveShortIdReadOnly(
|
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
const key = normalized.value;
|
|
88
|
-
const registryPath = path.join(repoRoot, '
|
|
89
|
+
const registryPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', REGISTRY_NAME);
|
|
89
90
|
if (!fs.existsSync(registryPath)) {
|
|
90
91
|
return shortIdFailure(
|
|
91
92
|
'SHORT_ID_REGISTRY_NOT_FOUND',
|
|
@@ -164,8 +165,9 @@ function resolveShortIdReadOnly(
|
|
|
164
165
|
: `short id '${key}' not found in task short-id registry.`;
|
|
165
166
|
return shortIdFailure('SHORT_ID_NOT_FOUND', message);
|
|
166
167
|
}
|
|
167
|
-
const
|
|
168
|
-
const
|
|
168
|
+
const workspaceRoot = resolveProjectTaskWorkspaceRoot(repoRoot);
|
|
169
|
+
const activeTask = path.join(workspaceRoot, 'active', taskId, 'task.md');
|
|
170
|
+
const completedTask = path.join(workspaceRoot, 'completed', taskId, 'task.md');
|
|
169
171
|
let isValidCompletedTask = false;
|
|
170
172
|
try {
|
|
171
173
|
const task = parseTypedTaskFrontmatter(fs.readFileSync(completedTask, 'utf8'));
|
|
@@ -300,7 +302,7 @@ function mutateShortIdRegistry(
|
|
|
300
302
|
taskId: string,
|
|
301
303
|
requested: ShortIdMutationEffect
|
|
302
304
|
): ShortIdMutationResult {
|
|
303
|
-
const activeDir = path.join(repoRoot, '
|
|
305
|
+
const activeDir = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active');
|
|
304
306
|
return mutateShortIdRegistryAt(activeDir, configuredShortIdLength(repoRoot), taskId, requested);
|
|
305
307
|
}
|
|
306
308
|
|
|
@@ -369,7 +371,7 @@ function mutateShortIdRegistryAt(
|
|
|
369
371
|
}
|
|
370
372
|
|
|
371
373
|
function releaseStaleShortIdRegistry(repoRoot: string, taskId: string): ShortIdMutationResult {
|
|
372
|
-
const activeDir = path.join(repoRoot, '
|
|
374
|
+
const activeDir = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active');
|
|
373
375
|
return mutateShortIdRegistryAt(
|
|
374
376
|
activeDir,
|
|
375
377
|
configuredShortIdLength(repoRoot),
|
|
@@ -433,7 +435,7 @@ function executeShortIdCommand(request: ShortIdCommandRequest): ShortIdCommandRe
|
|
|
433
435
|
}
|
|
434
436
|
|
|
435
437
|
function readRegistry(repoRoot: string): RegistrySchema | null {
|
|
436
|
-
const registryPath = path.join(repoRoot, '
|
|
438
|
+
const registryPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', REGISTRY_NAME);
|
|
437
439
|
if (!fs.existsSync(registryPath)) return null;
|
|
438
440
|
try {
|
|
439
441
|
const raw = fs.readFileSync(registryPath, 'utf8');
|
|
@@ -446,7 +448,7 @@ function readRegistry(repoRoot: string): RegistrySchema | null {
|
|
|
446
448
|
}
|
|
447
449
|
|
|
448
450
|
function readBranchFromTaskMd(repoRoot: string, taskId: string): string | null {
|
|
449
|
-
const taskMdPath = path.join(repoRoot, '
|
|
451
|
+
const taskMdPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', taskId, 'task.md');
|
|
450
452
|
if (!fs.existsSync(taskMdPath)) return null;
|
|
451
453
|
const content = fs.readFileSync(taskMdPath, 'utf8');
|
|
452
454
|
const m = content.match(/^branch:\s*(.+)$/m);
|
|
@@ -468,7 +470,7 @@ function inspectShortIdRegistry(
|
|
|
468
470
|
repoRoot: string,
|
|
469
471
|
shortIdLength = configuredShortIdLength(repoRoot)
|
|
470
472
|
): ShortIdRegistryInspection {
|
|
471
|
-
const registryPath = path.join(repoRoot, '
|
|
473
|
+
const registryPath = path.join(resolveProjectTaskWorkspaceRoot(repoRoot), 'active', REGISTRY_NAME);
|
|
472
474
|
if (!fs.existsSync(registryPath)) {
|
|
473
475
|
return {
|
|
474
476
|
status: 'missing',
|
|
@@ -69,9 +69,7 @@ const DEFAULT_REQUIRED_FIELDS = [
|
|
|
69
69
|
];
|
|
70
70
|
|
|
71
71
|
const DATE_TIME_PATTERN = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:[+-]\d{2}:\d{2})?$/;
|
|
72
|
-
//
|
|
73
|
-
// "latest action" / freshness computation so a step's in-flight marker never
|
|
74
|
-
// satisfies a skill's expected_action_pattern; the matching done entry does.
|
|
72
|
+
// Started rows do not represent completed lifecycle actions.
|
|
75
73
|
const ACTIVITY_LOG_STARTED_RE = /\s*\[started\]\s*$/;
|
|
76
74
|
const BRANCH_SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
77
75
|
|
|
@@ -358,16 +356,10 @@ function checkTaskMeta({ taskDir, config, repositoryRoot, skillName, artifactFil
|
|
|
358
356
|
if (expectedStep && metadata.current_step !== expectedStep) {
|
|
359
357
|
let validLifecycleArtifact = false;
|
|
360
358
|
if (currentTask) {
|
|
361
|
-
const activityConfig = loadVerificationConfig(repositoryRoot, skillName).checks?.["activity-log"];
|
|
362
359
|
const { section, invalidEntries } = inspectActivityLog(task.content);
|
|
363
|
-
if (
|
|
364
|
-
let latestAction = "";
|
|
365
|
-
for (const entry of section.entries) {
|
|
366
|
-
if (!ACTIVITY_LOG_STARTED_RE.test(entry.step)) latestAction = entry.step;
|
|
367
|
-
}
|
|
360
|
+
if (section && invalidEntries.length === 0) {
|
|
368
361
|
validLifecycleArtifact = checkArtifactLifecycleActivityLog(
|
|
369
|
-
taskDir, artifactFile, skillName,
|
|
370
|
-
activityConfig, task, section.entries, latestAction
|
|
362
|
+
taskDir, artifactFile, skillName, task, section.entries
|
|
371
363
|
)?.status === "pass";
|
|
372
364
|
}
|
|
373
365
|
}
|
|
@@ -725,16 +717,16 @@ function checkImplementationInput({ taskDir, artifactFile }: any): any {
|
|
|
725
717
|
return passResult("implementation-input", `${actionDecision} matches Activity Log, report, and task table`);
|
|
726
718
|
}
|
|
727
719
|
|
|
728
|
-
const ARTIFACT_ACTIVITY_STAGES: Readonly<Record<string, { family: string; event: string }>> = {
|
|
729
|
-
"analyze-task": { family: "analysis", event: "analyze.completed" },
|
|
730
|
-
"plan-task": { family: "plan", event: "plan.completed" },
|
|
731
|
-
"review-analysis": { family: "review-analysis", event: "review-analysis.completed" },
|
|
732
|
-
"review-plan": { family: "review-plan", event: "review-plan.completed" },
|
|
733
|
-
"code-task": { family: "code", event: "code.completed" },
|
|
734
|
-
"review-code": { family: "review-code", event: "review-code.completed" }
|
|
720
|
+
const ARTIFACT_ACTIVITY_STAGES: Readonly<Record<string, { family: string; event: string; action: string }>> = {
|
|
721
|
+
"analyze-task": { family: "analysis", event: "analyze.completed", action: "Analyze Task" },
|
|
722
|
+
"plan-task": { family: "plan", event: "plan.completed", action: "Plan Task" },
|
|
723
|
+
"review-analysis": { family: "review-analysis", event: "review-analysis.completed", action: "Review Analysis" },
|
|
724
|
+
"review-plan": { family: "review-plan", event: "review-plan.completed", action: "Review Plan" },
|
|
725
|
+
"code-task": { family: "code", event: "code.completed", action: "Code Task" },
|
|
726
|
+
"review-code": { family: "review-code", event: "review-code.completed", action: "Review Code" }
|
|
735
727
|
};
|
|
736
728
|
|
|
737
|
-
function checkArtifactLifecycleActivityLog(taskDir: string, artifactFile: string | undefined, skillName: string,
|
|
729
|
+
function checkArtifactLifecycleActivityLog(taskDir: string, artifactFile: string | undefined, skillName: string, task: any, entries: any[]): any {
|
|
738
730
|
const stage = ARTIFACT_ACTIVITY_STAGES[skillName];
|
|
739
731
|
if (!stage) return null;
|
|
740
732
|
const inventory = inspectArtifactDirectory(taskDir, stage.family as any);
|
|
@@ -772,25 +764,19 @@ function checkArtifactLifecycleActivityLog(taskDir: string, artifactFile: string
|
|
|
772
764
|
}
|
|
773
765
|
}
|
|
774
766
|
|
|
775
|
-
const
|
|
767
|
+
const roundActionPattern = new RegExp(`^${stage.action} \\(Round ${identity.round}(?:, [^)]+)?\\)$`);
|
|
776
768
|
const roundAction = entries.find((entry) => {
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
const actionRound = /\(Round (\d+)/.exec(entry.step)?.[1];
|
|
781
|
-
return actionRound === String(identity.round) && entry.note.includes(`→ ${output}`);
|
|
769
|
+
return !ACTIVITY_LOG_STARTED_RE.test(entry.step)
|
|
770
|
+
&& roundActionPattern.test(entry.step)
|
|
771
|
+
&& entry.note.includes(`→ ${output}`);
|
|
782
772
|
});
|
|
783
773
|
if (!roundAction) {
|
|
784
774
|
return failResult("activity-log", `No completed Activity Log row records '${output}' for ${skillName} Round ${identity.round}`);
|
|
785
775
|
}
|
|
786
|
-
if (skillName === "plan-task" && /^(?:Human Decision|Decision)\b/.test(latestAction)
|
|
787
|
-
&& expectedAction.test(latestAction)) {
|
|
788
|
-
return passResult("activity-log", `Plan completion '${output}' is current; latest action '${latestAction}' is a human decision`);
|
|
789
|
-
}
|
|
790
776
|
return passResult("activity-log", `Current ${stage.family} completion '${output}' is valid for Round ${identity.round}`);
|
|
791
777
|
}
|
|
792
778
|
|
|
793
|
-
function checkActivityLog({ taskDir,
|
|
779
|
+
function checkActivityLog({ taskDir, skillName, artifactFile }: any): any {
|
|
794
780
|
const task = loadTask(taskDir);
|
|
795
781
|
if (!task.ok) {
|
|
796
782
|
return failResult("activity-log", task.message);
|
|
@@ -802,44 +788,16 @@ function checkActivityLog({ taskDir, config, skillName, artifactFile }: any): an
|
|
|
802
788
|
const entries = section.entries;
|
|
803
789
|
if (entries.length === 0) return failResult("activity-log", "Activity Log has no entries");
|
|
804
790
|
|
|
805
|
-
let latestAction = "";
|
|
806
|
-
let latestTimestamp = "";
|
|
807
|
-
const doneActions: string[] = [];
|
|
808
|
-
|
|
809
791
|
for (const entry of entries) {
|
|
810
792
|
if (!entry.note.trim()) return failResult("activity-log", "Activity Log entry note is required");
|
|
811
|
-
const { time: timestamp, step: action } = entry;
|
|
812
|
-
// A `[started]` marker is not a terminal action: keep latestAction/latestTimestamp
|
|
813
|
-
// on the most recent done entry so expected_action_pattern sees it.
|
|
814
|
-
if (!ACTIVITY_LOG_STARTED_RE.test(action)) {
|
|
815
|
-
latestTimestamp = timestamp;
|
|
816
|
-
latestAction = action;
|
|
817
|
-
doneActions.push(action);
|
|
818
|
-
}
|
|
819
793
|
}
|
|
820
794
|
|
|
821
795
|
const artifactLifecycleResult = checkArtifactLifecycleActivityLog(
|
|
822
|
-
taskDir, artifactFile, skillName,
|
|
796
|
+
taskDir, artifactFile, skillName, task, entries
|
|
823
797
|
);
|
|
824
798
|
if (artifactLifecycleResult) return artifactLifecycleResult;
|
|
825
799
|
|
|
826
|
-
|
|
827
|
-
const expected = new RegExp(config.expected_action_pattern);
|
|
828
|
-
let index = doneActions.length - 1;
|
|
829
|
-
while (index >= 0 && doneActions[index] === 'Commit' && !expected.test(doneActions[index]!)) index -= 1;
|
|
830
|
-
if (index >= 0 && expected.test(doneActions[index]!)) {
|
|
831
|
-
latestAction = doneActions[index]!;
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
if (config.expected_action_pattern && !new RegExp(config.expected_action_pattern).test(latestAction)) {
|
|
836
|
-
return failResult(
|
|
837
|
-
"activity-log",
|
|
838
|
-
`Latest action '${latestAction}' does not match '${config.expected_action_pattern}'`
|
|
839
|
-
);
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
return passResult("activity-log", `Latest entry '${latestAction}' at ${latestTimestamp}`);
|
|
800
|
+
return passResult("activity-log", "Activity Log is valid");
|
|
843
801
|
}
|
|
844
802
|
|
|
845
803
|
function checkCompletionChecklist({ taskDir, config }: any): any {
|
|
@@ -1042,9 +1000,9 @@ async function checkPostReviewCommit({ taskDir, config, repositoryRoot }: any):
|
|
|
1042
1000
|
|
|
1043
1001
|
let gitRoot;
|
|
1044
1002
|
try {
|
|
1045
|
-
gitRoot = execFileSync("git", ["-C",
|
|
1003
|
+
gitRoot = execFileSync("git", ["-C", repositoryRoot, "rev-parse", "--show-toplevel"], { encoding: "utf8" }).trim();
|
|
1046
1004
|
} catch {
|
|
1047
|
-
return blockedResult("post-review-commit", "git unavailable or
|
|
1005
|
+
return blockedResult("post-review-commit", "git unavailable or repository root is not inside a git repository");
|
|
1048
1006
|
}
|
|
1049
1007
|
|
|
1050
1008
|
const task = loadTask(taskDir);
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { isSandbox } from '../sandbox/environment.ts';
|
|
5
|
+
|
|
6
|
+
export type ExecutionBoundary = 'host' | 'container';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the physical task-workspace boundary from the verified process
|
|
10
|
+
* execution context. Sandbox mode (task-bound / branch-only) does not affect
|
|
11
|
+
* the task workspace mount location.
|
|
12
|
+
*/
|
|
13
|
+
export function resolveTaskWorkspaceExecutionBoundary(
|
|
14
|
+
env: NodeJS.ProcessEnv = process.env
|
|
15
|
+
): ExecutionBoundary {
|
|
16
|
+
if (env.AGENT_INFRA_SANDBOX !== '1') return 'host';
|
|
17
|
+
const statusMountPath = env.AGENT_INFRA_CONTROL_STATUS_DIR;
|
|
18
|
+
if (!statusMountPath || !isSandbox(env, { statusMountPath })) {
|
|
19
|
+
throw new Error('task workspace boundary requires a verified sandbox execution context');
|
|
20
|
+
}
|
|
21
|
+
return 'container';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface TaskWorkspaceLayout {
|
|
25
|
+
projectKey: string;
|
|
26
|
+
boundary: ExecutionBoundary;
|
|
27
|
+
root: string;
|
|
28
|
+
active: string;
|
|
29
|
+
blocked: string;
|
|
30
|
+
completed: string;
|
|
31
|
+
archive: string;
|
|
32
|
+
shortIds: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function resolveTaskWorkspaceLayout(input: {
|
|
36
|
+
project: string;
|
|
37
|
+
boundary: ExecutionBoundary;
|
|
38
|
+
home?: string;
|
|
39
|
+
}): TaskWorkspaceLayout {
|
|
40
|
+
const { project, boundary } = input;
|
|
41
|
+
if (!project || project === '.' || project === '..' || /[\\/\u0000-\u001f\u007f]/u.test(project)) {
|
|
42
|
+
throw new TypeError('project must be a safe single path segment');
|
|
43
|
+
}
|
|
44
|
+
let root: string;
|
|
45
|
+
if (boundary === 'container') {
|
|
46
|
+
root = '/task-workspace';
|
|
47
|
+
} else if (boundary === 'host') {
|
|
48
|
+
if (!input.home) throw new TypeError('home is required for host task workspace resolution');
|
|
49
|
+
if (!path.isAbsolute(input.home)) throw new TypeError('home must be an absolute path for host task workspace resolution');
|
|
50
|
+
root = path.join(input.home, '.agent-infra', 'task-workspaces', project);
|
|
51
|
+
} else {
|
|
52
|
+
throw new TypeError(`unsupported task workspace boundary: ${String(boundary)}`);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
projectKey: project,
|
|
56
|
+
boundary,
|
|
57
|
+
root,
|
|
58
|
+
active: path.join(root, 'active'),
|
|
59
|
+
blocked: path.join(root, 'blocked'),
|
|
60
|
+
completed: path.join(root, 'completed'),
|
|
61
|
+
archive: path.join(root, 'archive'),
|
|
62
|
+
shortIds: path.join(root, 'active', '.short-ids.json')
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function resolveProjectTaskWorkspaceLayout(
|
|
67
|
+
repoRoot: string,
|
|
68
|
+
boundary: ExecutionBoundary = resolveTaskWorkspaceExecutionBoundary()
|
|
69
|
+
): TaskWorkspaceLayout {
|
|
70
|
+
const configPath = path.join(repoRoot, '.agents', '.airc.json');
|
|
71
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8')) as { project?: unknown };
|
|
72
|
+
if (typeof config.project !== 'string') throw new TypeError(`project is missing from ${configPath}`);
|
|
73
|
+
return resolveTaskWorkspaceLayout({ project: config.project, boundary, home: process.env.HOME || os.homedir() });
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function resolveProjectTaskWorkspaceRoot(repoRoot: string): string {
|
|
77
|
+
return resolveProjectTaskWorkspaceLayout(repoRoot).root;
|
|
78
|
+
}
|
|
@@ -3,6 +3,7 @@ import fs from 'node:fs';
|
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
|
+
import { resolveProjectTaskWorkspaceRoot } from './workspace-root.ts';
|
|
6
7
|
|
|
7
8
|
function git(repoRoot: string, indexFile: string | null, args: string[]): string {
|
|
8
9
|
const env = indexFile ? { ...process.env, GIT_INDEX_FILE: indexFile } : process.env;
|
|
@@ -55,7 +56,7 @@ function captureWorktreeTree(repoRoot: string, forcedPath: string | null): strin
|
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
function taskFileFingerprints(stateRoot: string, taskId: string | null): TaskFileFingerprint[] {
|
|
58
|
-
const activeRoot = path.join(stateRoot, '
|
|
59
|
+
const activeRoot = path.join(resolveProjectTaskWorkspaceRoot(stateRoot), 'active');
|
|
59
60
|
const roots = taskId ? [path.join(activeRoot, taskId)] : [activeRoot];
|
|
60
61
|
const records: TaskFileFingerprint[] = [];
|
|
61
62
|
const visit = (absolute: string, relative: string): void => {
|
|
@@ -67,7 +68,7 @@ function taskFileFingerprints(stateRoot: string, taskId: string | null): TaskFil
|
|
|
67
68
|
return;
|
|
68
69
|
}
|
|
69
70
|
if (!stat.isFile() && !stat.isSymbolicLink()) return;
|
|
70
|
-
if (
|
|
71
|
+
if (/^task-workspace\/(?:active|blocked|completed)\/TASK-[0-9]{8}-[0-9]{6}\/\.runtime\/orchestration\.json$/u.test(relative)) return;
|
|
71
72
|
const content = stat.isSymbolicLink()
|
|
72
73
|
? Buffer.from(fs.readlinkSync(absolute))
|
|
73
74
|
: fs.readFileSync(absolute);
|
|
@@ -80,7 +81,7 @@ function taskFileFingerprints(stateRoot: string, taskId: string | null): TaskFil
|
|
|
80
81
|
};
|
|
81
82
|
for (const root of roots) {
|
|
82
83
|
if (!fs.existsSync(root)) continue;
|
|
83
|
-
const relative = path.posix.join('
|
|
84
|
+
const relative = path.posix.join('task-workspace/active', path.relative(activeRoot, root).split(path.sep).join('/'));
|
|
84
85
|
visit(root, relative.replace(/\/$/, ''));
|
|
85
86
|
}
|
|
86
87
|
return records.sort((left, right) => left.path.localeCompare(right.path));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fitlab-ai/agent-infra",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Bootstrap tool for AI multi-tool collaboration infrastructure — works with Claude Code, Codex, Antigravity CLI, and OpenCode",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -53,16 +53,23 @@ Built-in templates take priority over external templates. Later external templat
|
|
|
53
53
|
|
|
54
54
|
## Creating Your First Task
|
|
55
55
|
|
|
56
|
+
Resolve the host task root once for these examples:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
TASK_WORKSPACE_ROOT=$(agent-infra-internal task-workspace resolve --boundary host | node -e 'let input="";process.stdin.setEncoding("utf8");process.stdin.on("data",chunk=>input+=chunk);process.stdin.on("end",()=>process.stdout.write(JSON.parse(input).root));')
|
|
60
|
+
```
|
|
61
|
+
|
|
56
62
|
1. Copy the task template to the active workspace:
|
|
57
63
|
|
|
58
64
|
```bash
|
|
59
|
-
|
|
65
|
+
mkdir -p "$TASK_WORKSPACE_ROOT/active/TASK-20260101-120000"
|
|
66
|
+
cp .agents/templates/task.md "$TASK_WORKSPACE_ROOT/active/TASK-20260101-120000/task.md"
|
|
60
67
|
```
|
|
61
68
|
|
|
62
69
|
2. Fill in the task metadata:
|
|
63
70
|
|
|
64
71
|
```yaml
|
|
65
|
-
id:
|
|
72
|
+
id: TASK-20260101-120000
|
|
66
73
|
type: feature # feature | bugfix | refactor | docs | review
|
|
67
74
|
status: open # open | in-progress | review | blocked | completed
|
|
68
75
|
assigned_to: claude # claude | codex | antigravity | opencode | human
|
|
@@ -92,7 +99,7 @@ Claude Code excels at codebase exploration and understanding complex relationshi
|
|
|
92
99
|
agy
|
|
93
100
|
|
|
94
101
|
# Example prompt:
|
|
95
|
-
# "Based on the analysis in
|
|
102
|
+
# "Based on the analysis in $TASK_WORKSPACE_ROOT/active/TASK-20260101-120000/task.md,
|
|
96
103
|
# create a technical design. Define interfaces and outline the approach."
|
|
97
104
|
```
|
|
98
105
|
|
|
@@ -103,7 +110,7 @@ agy
|
|
|
103
110
|
codex
|
|
104
111
|
|
|
105
112
|
# Example prompt:
|
|
106
|
-
# "Implement the changes described in
|
|
113
|
+
# "Implement the changes described in $TASK_WORKSPACE_ROOT/active/TASK-20260101-120000/task.md.
|
|
107
114
|
# Follow the design section. Create a new branch for this work."
|
|
108
115
|
```
|
|
109
116
|
|
|
@@ -130,7 +137,8 @@ claude
|
|
|
130
137
|
|
|
131
138
|
```bash
|
|
132
139
|
# Quick bug fix workflow
|
|
133
|
-
|
|
140
|
+
mkdir -p "$TASK_WORKSPACE_ROOT/active/TASK-20260101-120000"
|
|
141
|
+
cp .agents/templates/task.md "$TASK_WORKSPACE_ROOT/active/TASK-20260101-120000/task.md"
|
|
134
142
|
# Edit the task, then:
|
|
135
143
|
# 1. Use Claude Code to analyze
|
|
136
144
|
# 2. Use Codex/OpenCode to fix
|
|
@@ -144,7 +152,8 @@ cp .agents/templates/task.md .agents/workspace/active/bugfix-001.md
|
|
|
144
152
|
3. **Report**: Generate review report from template.
|
|
145
153
|
|
|
146
154
|
```bash
|
|
147
|
-
|
|
155
|
+
mkdir -p "$TASK_WORKSPACE_ROOT/active/TASK-20260101-120001"
|
|
156
|
+
cp .agents/templates/review-report.md "$TASK_WORKSPACE_ROOT/active/TASK-20260101-120001/review-report.md"
|
|
148
157
|
# Use Claude Code to fill in the review
|
|
149
158
|
```
|
|
150
159
|
|
|
@@ -156,7 +165,8 @@ cp .agents/templates/review-report.md .agents/workspace/active/review-pr-42.md
|
|
|
156
165
|
4. **Verify** (Claude Code): Ensure no regressions, run tests.
|
|
157
166
|
|
|
158
167
|
```bash
|
|
159
|
-
|
|
168
|
+
mkdir -p "$TASK_WORKSPACE_ROOT/active/TASK-20260101-120002"
|
|
169
|
+
cp .agents/templates/task.md "$TASK_WORKSPACE_ROOT/active/TASK-20260101-120002/task.md"
|
|
160
170
|
# Follow the refactoring workflow in .agents/workflows/refactoring.yaml
|
|
161
171
|
```
|
|
162
172
|
|
|
@@ -165,7 +175,8 @@ cp .agents/templates/task.md .agents/workspace/active/refactor-001.md
|
|
|
165
175
|
When switching between AI tools, create a handoff document:
|
|
166
176
|
|
|
167
177
|
```bash
|
|
168
|
-
|
|
178
|
+
mkdir -p "$TASK_WORKSPACE_ROOT/active/TASK-20260101-120003"
|
|
179
|
+
cp .agents/templates/handoff.md "$TASK_WORKSPACE_ROOT/active/TASK-20260101-120003/handoff.md"
|
|
169
180
|
```
|
|
170
181
|
|
|
171
182
|
Fill in:
|