@fitlab-ai/agent-infra 0.11.4 → 0.11.6
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/dist/bin/internal-cli.js +0 -5
- package/dist/lib/decide.js +4 -41
- package/dist/lib/git/branch-worktree.js +45 -0
- package/dist/lib/internal/cli-route-inventory.js +0 -3
- package/dist/lib/internal/task-activity.js +7 -40
- package/dist/lib/internal/task-event.js +4 -37
- package/dist/lib/internal/task-finalization.js +9 -2
- package/dist/lib/internal/task-ledger.js +5 -43
- package/dist/lib/internal/task-lifecycle.js +1 -1
- package/dist/lib/internal/task-operation-registry.js +1 -4
- package/dist/lib/internal/task-review.js +5 -37
- package/dist/lib/internal/task-verify.js +3 -47
- package/dist/lib/internal/task-warning.js +4 -40
- package/dist/lib/merge.js +62 -10
- package/dist/lib/platform/github-provider.js +9 -14
- package/dist/lib/platform/github-release-notes.js +80 -11
- package/dist/lib/platform/none-provider.js +1 -1
- package/dist/lib/platform/pr-summary.js +4 -23
- package/dist/lib/platform/provider-contract.d.ts +39 -16
- package/dist/lib/platform/provider-contract.js +1 -1
- package/dist/lib/platform/provider-validation.js +83 -13
- package/dist/lib/platform/release-notes.js +7 -8
- package/dist/lib/platform/verification-sync.js +12 -49
- package/dist/lib/sandbox/capture.js +1 -1
- package/dist/lib/sandbox/commands/create.js +46 -1
- package/dist/lib/sandbox/commands/enter.js +1 -1
- package/dist/lib/sandbox/commands/start.js +1 -1
- package/dist/lib/sandbox/config.js +10 -0
- package/dist/lib/sandbox/control/client.js +4 -1
- package/dist/lib/sandbox/control/executor.js +12 -4
- package/dist/lib/sandbox/control/lifecycle.js +18 -13
- package/dist/lib/sandbox/control/protocol.js +2 -1
- package/dist/lib/sandbox/removal.js +15 -8
- package/dist/lib/sandbox/task-resolver.js +62 -2
- package/dist/lib/task/activity-intent.js +1 -2
- package/dist/lib/task/artifact-operations.js +36 -2
- package/dist/lib/task/artifact-schema.js +7 -7
- package/dist/lib/task/control-authority.js +9 -166
- package/dist/lib/task/decision-intents.js +2 -4
- package/dist/lib/task/events.js +7 -28
- package/dist/lib/task/finalization-handoff.js +92 -0
- package/dist/lib/task/finalization.js +123 -12
- package/dist/lib/task/intermediate-cleanup.js +9 -148
- package/dist/lib/task/ledger-intents.js +3 -6
- package/dist/lib/task/lifecycle.js +50 -11
- package/dist/lib/task/local-artifact-finalization.js +13 -28
- package/dist/lib/task/resolve-ref.js +3 -3
- package/dist/lib/task/review-command.js +1 -2
- package/dist/lib/task/review-finalization.js +1 -2
- package/dist/lib/task/snapshot.js +6 -6
- package/dist/lib/task/verification-engine.js +16 -11
- package/dist/lib/task/verification.js +1 -13
- package/dist/lib/task/workflow-warning-intents.js +2 -4
- package/dist/lib/task/write.js +122 -3
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/decide.ts +5 -35
- package/lib/git/branch-worktree.ts +41 -0
- package/lib/internal/cli-route-inventory.ts +0 -2
- package/lib/internal/task-activity.ts +6 -34
- package/lib/internal/task-event.ts +4 -37
- package/lib/internal/task-finalization.ts +9 -2
- package/lib/internal/task-ledger.ts +5 -38
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-operation-registry.ts +1 -4
- package/lib/internal/task-review.ts +5 -31
- package/lib/internal/task-verify.ts +3 -39
- package/lib/internal/task-warning.ts +3 -32
- package/lib/merge.ts +59 -10
- package/lib/platform/github-provider.ts +11 -18
- package/lib/platform/github-release-notes.ts +89 -14
- package/lib/platform/none-provider.ts +1 -1
- package/lib/platform/pr-summary.ts +4 -25
- package/lib/platform/provider-contract.ts +38 -12
- package/lib/platform/provider-validation.ts +83 -12
- package/lib/platform/release-notes.ts +11 -12
- package/lib/platform/verification-sync.ts +13 -53
- package/lib/sandbox/capture.ts +1 -1
- package/lib/sandbox/commands/create.ts +52 -1
- package/lib/sandbox/commands/enter.ts +1 -1
- package/lib/sandbox/commands/start.ts +1 -1
- package/lib/sandbox/config.ts +12 -0
- package/lib/sandbox/control/client.ts +5 -1
- package/lib/sandbox/control/executor.ts +13 -4
- package/lib/sandbox/control/lifecycle.ts +22 -13
- package/lib/sandbox/control/protocol.ts +3 -2
- package/lib/sandbox/removal.ts +23 -7
- package/lib/sandbox/task-resolver.ts +51 -2
- package/lib/task/activity-intent.ts +1 -2
- package/lib/task/artifact-operations.ts +58 -2
- package/lib/task/artifact-schema.ts +9 -7
- package/lib/task/control-authority.ts +14 -188
- package/lib/task/decision-intents.ts +2 -4
- package/lib/task/events.ts +8 -27
- package/lib/task/finalization-handoff.ts +104 -0
- package/lib/task/finalization.ts +120 -12
- package/lib/task/intermediate-cleanup.ts +9 -125
- package/lib/task/ledger-intents.ts +3 -6
- package/lib/task/lifecycle.ts +45 -13
- package/lib/task/local-artifact-finalization.ts +16 -35
- package/lib/task/resolve-ref.ts +3 -3
- package/lib/task/review-command.ts +2 -5
- package/lib/task/review-finalization.ts +1 -4
- package/lib/task/snapshot.ts +6 -6
- package/lib/task/verification-engine.ts +20 -10
- package/lib/task/verification.ts +1 -15
- package/lib/task/workflow-warning-intents.ts +2 -4
- package/lib/task/write.ts +116 -5
- package/package.json +1 -1
- package/templates/.agents/rules/evidence-reporting.en.md +1 -2
- package/templates/.agents/rules/evidence-reporting.zh-CN.md +1 -2
- package/templates/.agents/skills/analyze-task/SKILL.en.md +11 -2
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +11 -2
- package/templates/.agents/skills/archive-tasks/SKILL.en.md +2 -2
- package/templates/.agents/skills/archive-tasks/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/archive-tasks/scripts/archive-tasks.sh +109 -6
- package/templates/.agents/skills/code-task/reference/report-template.en.md +6 -6
- package/templates/.agents/skills/code-task/reference/report-template.zh-CN.md +6 -6
- package/templates/.agents/skills/complete-manual-validation/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-manual-validation/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.en.md +1 -3
- package/templates/.agents/skills/complete-manual-validation/reference/report-template.zh-CN.md +1 -3
- package/templates/.agents/skills/complete-task/config/verify.en.json +2 -2
- package/templates/.agents/skills/complete-task/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/create-release-note/SKILL.en.md +17 -11
- package/templates/.agents/skills/create-release-note/SKILL.zh-CN.md +17 -11
- package/templates/.agents/skills/review-analysis/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-analysis/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-code/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-plan/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-plan/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/review-pr/config/verify.en.json +3 -1
- package/templates/.agents/skills/review-pr/config/verify.zh-CN.json +3 -1
- package/templates/.agents/skills/review-pr/reference/report-template.en.md +3 -3
- package/templates/.agents/skills/review-pr/reference/report-template.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +2 -2
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +2 -2
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +2 -2
- package/dist/lib/internal/task-override.js +0 -138
- package/dist/lib/task/artifact-recovery.js +0 -476
- package/dist/lib/task/artifact-repair-intent.js +0 -181
- package/dist/lib/task/guard-override.js +0 -31
- package/dist/lib/task/human-override.js +0 -1259
- package/lib/internal/task-override.ts +0 -144
- package/lib/task/artifact-recovery.ts +0 -572
- package/lib/task/artifact-repair-intent.ts +0 -227
- package/lib/task/guard-override.ts +0 -50
- package/lib/task/human-override.ts +0 -1522
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { inspectDecisionDetailDuplicates } from './decision-details.js';
|
|
4
|
-
import { scanVisibleMarkdown } from './markdown.js';
|
|
5
4
|
import { parseArtifactName } from './artifact-name.js';
|
|
5
|
+
import { parseLifecyclePathDecision } from './lifecycle-path.js';
|
|
6
6
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
7
|
-
import { canonicalSemanticDigest,
|
|
7
|
+
import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
|
|
8
8
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
9
9
|
const LOCAL_ARTIFACT_REQUIRED_SECTIONS = {
|
|
10
10
|
analysis: getArtifactSchema('analysis').sections.map((section) => section.headings.zh),
|
|
@@ -16,45 +16,30 @@ function localDiagnosticCode(code) {
|
|
|
16
16
|
ARTIFACT_EMPTY: 'LOCAL_ARTIFACT_EMPTY',
|
|
17
17
|
ARTIFACT_MISSING_SECTION: 'LOCAL_ARTIFACT_MISSING_SECTION',
|
|
18
18
|
ARTIFACT_DUPLICATE_SECTION: 'LOCAL_ARTIFACT_DUPLICATE_SECTION',
|
|
19
|
-
ARTIFACT_HEADING_TRAILING_PUNCTUATION: 'LOCAL_SECTION_HEADING_TRAILING_PUNCTUATION'
|
|
19
|
+
ARTIFACT_HEADING_TRAILING_PUNCTUATION: 'LOCAL_SECTION_HEADING_TRAILING_PUNCTUATION',
|
|
20
|
+
ARTIFACT_STATE_CHECK_PATTERN_MISSING: 'LOCAL_STATUS_COMMAND_MISSING',
|
|
21
|
+
ARTIFACT_REQUIRED_PATTERN_MISSING: 'LOCAL_REQUIRED_PATTERN_MISSING'
|
|
20
22
|
};
|
|
21
23
|
return map[code] ?? 'LOCAL_STRUCTURAL_INVALID';
|
|
22
24
|
}
|
|
23
25
|
function localDiagnostic(item) {
|
|
24
26
|
return { code: localDiagnosticCode(item.code), message: item.message, line: item.line };
|
|
25
27
|
}
|
|
26
|
-
function isStatusPattern(pattern) {
|
|
27
|
-
return pattern === '^\\$ ';
|
|
28
|
-
}
|
|
29
28
|
function validateLocalArtifact(content, options) {
|
|
30
29
|
const schema = getArtifactSchema(options.family);
|
|
31
30
|
const diagnostics = [];
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
for (const item of structure.diagnostics)
|
|
31
|
+
const inspection = inspectArtifactContract(content, schema, { scopeStateCheckSection: true });
|
|
32
|
+
for (const item of inspection.diagnostics)
|
|
35
33
|
diagnostics.push(localDiagnostic(item));
|
|
36
|
-
const statusSection = schema.sections.find((section) => section.id === 'state-check');
|
|
37
|
-
if (statusSection) {
|
|
38
|
-
const statusHeading = scanned.headings.find((heading) => (heading.level === 2 && [statusSection.headings.zh, statusSection.headings.en].some((name) => (heading.text === name || heading.text === `${name}:` || heading.text === `${name}:`))));
|
|
39
|
-
if (statusHeading) {
|
|
40
|
-
const next = scanned.headings.find((candidate) => candidate.start > statusHeading.start && candidate.level <= 2);
|
|
41
|
-
const body = content.slice(statusHeading.end, next?.start ?? content.length);
|
|
42
|
-
for (const pattern of schema.requiredPatterns.filter(isStatusPattern)) {
|
|
43
|
-
if (!new RegExp(pattern, 'm').test(body)) {
|
|
44
|
-
diagnostics.push({ code: 'LOCAL_STATUS_COMMAND_MISSING', message: `status section '${statusHeading.text}' is missing required command output`, line: content.slice(0, statusHeading.start).split('\n').length });
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
const patternInspection = inspectArtifactPatterns(content, {
|
|
50
|
-
...schema,
|
|
51
|
-
requiredPatterns: schema.requiredPatterns.filter((item) => !isStatusPattern(item))
|
|
52
|
-
});
|
|
53
|
-
for (const item of patternInspection.diagnostics)
|
|
54
|
-
diagnostics.push({ code: 'LOCAL_REQUIRED_PATTERN_MISSING', message: item.message, line: null });
|
|
55
34
|
const decisionDetails = inspectDecisionDetailDuplicates(content);
|
|
56
35
|
if (!decisionDetails.ok)
|
|
57
36
|
diagnostics.push({ code: 'LOCAL_DECISION_DETAIL_DUPLICATE', message: decisionDetails.message, line: null });
|
|
37
|
+
if (options.family === 'analysis') {
|
|
38
|
+
const flowDecision = parseLifecyclePathDecision(content);
|
|
39
|
+
if (flowDecision.status !== 'valid') {
|
|
40
|
+
diagnostics.push({ code: 'LOCAL_FLOW_DECISION_INVALID', message: flowDecision.message, line: null });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
58
43
|
return {
|
|
59
44
|
ok: diagnostics.length === 0,
|
|
60
45
|
family: options.family,
|
|
@@ -41,7 +41,7 @@ function listSortedNumeric(dir, width) {
|
|
|
41
41
|
.reverse();
|
|
42
42
|
}
|
|
43
43
|
function findInArchive(repoRoot, taskId) {
|
|
44
|
-
// archive-tasks
|
|
44
|
+
// archive-tasks stores local material at .agents/workspace/archive/YYYY/MM/DD/{taskId}/local/task.md
|
|
45
45
|
// where YYYY/MM/DD comes from completed_at (or updated_at fallback) — NOT from
|
|
46
46
|
// the task id's creation date. So we cannot derive the path from taskId alone;
|
|
47
47
|
// walk the bounded YYYY/MM/DD tree instead. Newest-first to favor recent archives.
|
|
@@ -51,7 +51,7 @@ function findInArchive(repoRoot, taskId) {
|
|
|
51
51
|
for (const month of listSortedNumeric(yearDir, 2)) {
|
|
52
52
|
const monthDir = path.join(yearDir, month);
|
|
53
53
|
for (const day of listSortedNumeric(monthDir, 2)) {
|
|
54
|
-
const candidate = path.join(monthDir, day, taskId, 'task.md');
|
|
54
|
+
const candidate = path.join(monthDir, day, taskId, 'local', 'task.md');
|
|
55
55
|
if (fs.existsSync(candidate))
|
|
56
56
|
return candidate;
|
|
57
57
|
}
|
|
@@ -125,7 +125,7 @@ function enumerateAllTaskDirs(repoRoot) {
|
|
|
125
125
|
for (const day of listSortedNumeric(monthDir, 2).reverse()) {
|
|
126
126
|
const dayDir = path.join(monthDir, day);
|
|
127
127
|
for (const taskId of fs.readdirSync(dayDir).sort())
|
|
128
|
-
add(taskId, path.join(dayDir, taskId), 'archive');
|
|
128
|
+
add(taskId, path.join(dayDir, taskId, 'local'), 'archive');
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
}
|
|
@@ -3,8 +3,7 @@ export function parseReviewCommand(args) {
|
|
|
3
3
|
throw new Error('task ref and a supported review operation are required');
|
|
4
4
|
const values = {};
|
|
5
5
|
const flags = {
|
|
6
|
-
'--stage': 'stage', '--artifact': 'artifact', '--orchestrated': 'orchestrated', '--dry-run': 'dryRun'
|
|
7
|
-
'--override-ticket': 'overrideTicket', '--override-target': 'overrideTarget', '--override-scope': 'overrideScope'
|
|
6
|
+
'--stage': 'stage', '--artifact': 'artifact', '--orchestrated': 'orchestrated', '--dry-run': 'dryRun'
|
|
8
7
|
};
|
|
9
8
|
for (let index = 2; index < args.length; index += 1) {
|
|
10
9
|
const flag = args[index];
|
|
@@ -9,7 +9,6 @@ import { inspectDecisionDetailDuplicates } from './decision-details.js';
|
|
|
9
9
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
10
10
|
import { validateLifecycleExecution } from './lifecycle-execution.js';
|
|
11
11
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task-execution-lock.js';
|
|
12
|
-
import { allowsManualOverride } from './guard-override.js';
|
|
13
12
|
import { getArtifactSchema } from './artifact-schema.js';
|
|
14
13
|
import { canonicalSemanticDigest, inspectArtifactContract, sha256Content } from './artifact-operations.js';
|
|
15
14
|
const STAGES = {
|
|
@@ -138,7 +137,7 @@ function prepareReviewSummaryCandidate(request, artifactContent, options = {}) {
|
|
|
138
137
|
taskId = resolved.taskId ?? null;
|
|
139
138
|
if (!resolved.ok)
|
|
140
139
|
return reject(resolved.code, resolved.message);
|
|
141
|
-
if (resolved.state !== 'active'
|
|
140
|
+
if (resolved.state !== 'active') {
|
|
142
141
|
return reject('TASK_STATE_MISMATCH', `task ${taskId} is ${resolved.state}, expected active`);
|
|
143
142
|
}
|
|
144
143
|
const parsed = parseArtifactName(request.artifact);
|
|
@@ -56,25 +56,25 @@ function collectTaskSnapshot(taskRef, options = {}) {
|
|
|
56
56
|
const observations = [];
|
|
57
57
|
try {
|
|
58
58
|
const output = stripOneTrailingNewline((options.gitStatus ?? defaultGitStatus)(resolved.repoRoot));
|
|
59
|
-
observations.push({ name: 'git', status: 'ready', command: '
|
|
59
|
+
observations.push({ name: 'git', status: 'ready', command: 'git status --short --untracked-files=all', output, error: null });
|
|
60
60
|
}
|
|
61
61
|
catch (error) {
|
|
62
|
-
return failedSnapshot(resolved, taskRef, observations, 'git', '
|
|
62
|
+
return failedSnapshot(resolved, taskRef, observations, 'git', 'git status --short --untracked-files=all', 'SNAPSHOT_GIT_FAILED', error);
|
|
63
63
|
}
|
|
64
64
|
try {
|
|
65
65
|
const entries = (options.readDirectory ?? ((dir) => fs.readdirSync(dir, { withFileTypes: true })))(resolved.taskDir);
|
|
66
66
|
const output = renderDirectory(entries, resolved.taskDir, options.lstat ?? fs.lstatSync);
|
|
67
|
-
observations.push({ name: 'task-directory', status: 'ready', command:
|
|
67
|
+
observations.push({ name: 'task-directory', status: 'ready', command: `fs.readdirSync ${taskDirPath}/`, output, error: null });
|
|
68
68
|
}
|
|
69
69
|
catch (error) {
|
|
70
|
-
return failedSnapshot(resolved, taskRef, observations, 'task-directory',
|
|
70
|
+
return failedSnapshot(resolved, taskRef, observations, 'task-directory', `fs.readdirSync ${taskDirPath}/`, 'SNAPSHOT_DIRECTORY_READ_FAILED', error);
|
|
71
71
|
}
|
|
72
72
|
try {
|
|
73
73
|
const content = (options.readTask ?? ((file) => fs.readFileSync(file, 'utf8')))(resolved.taskMdPath);
|
|
74
|
-
observations.push({ name: 'task-tail', status: 'ready', command:
|
|
74
|
+
observations.push({ name: 'task-tail', status: 'ready', command: `fs.readFileSync ${taskPath} (last 10 lines)`, output: renderTail(content), error: null });
|
|
75
75
|
}
|
|
76
76
|
catch (error) {
|
|
77
|
-
return failedSnapshot(resolved, taskRef, observations, 'task-tail',
|
|
77
|
+
return failedSnapshot(resolved, taskRef, observations, 'task-tail', `fs.readFileSync ${taskPath} (last 10 lines)`, 'SNAPSHOT_TASK_READ_FAILED', error);
|
|
78
78
|
}
|
|
79
79
|
return {
|
|
80
80
|
status: 'ready', changed: false, requestRef: taskRef, taskId: resolved.taskId,
|
|
@@ -11,6 +11,7 @@ import { inspectPlatformPullRequest } from "../platform/pull-requests.js";
|
|
|
11
11
|
import { resolveMaterializedReviewedHeadRelation } from "../platform/change-request-git-evidence.js";
|
|
12
12
|
import { resolveReviewedHeadRelation } from "../platform/merged-pr-equivalence.js";
|
|
13
13
|
import { resolveLocalReviewedCommitRelation } from "../git/reviewed-commit-equivalence.js";
|
|
14
|
+
import { resolveBranchWorktree } from "../git/branch-worktree.js";
|
|
14
15
|
import { parseTypedTaskFrontmatter } from "./frontmatter.js";
|
|
15
16
|
import { LEDGER_TERMINAL, LEDGER_SECTION_MISSING_CODE, LEDGER_SECTION_MISSING_MESSAGE, parseLedgerDocument, summarizeLedgerStage, validateLedgerRows } from "./ledger.js";
|
|
16
17
|
import { isValidAgentInfraVersion } from "../version.js";
|
|
@@ -483,6 +484,18 @@ function checkArtifact({ taskDir, config, artifactFile, skillName }) {
|
|
|
483
484
|
return failResult("artifact", `${path.basename(artifactPath)} is missing required pattern: ${pattern}`);
|
|
484
485
|
}
|
|
485
486
|
}
|
|
487
|
+
const stateCheckPatterns = config.state_check_patterns || [];
|
|
488
|
+
if (stateCheckPatterns.length > 0) {
|
|
489
|
+
const scanned = scanVisibleMarkdown(content);
|
|
490
|
+
const stateCheckHeading = scanned.headings.find((heading) => (heading.level === 2 && (heading.text === "状态核对" || heading.text === "State Check")));
|
|
491
|
+
const nextHeading = stateCheckHeading && scanned.headings.find((heading) => (heading.start > stateCheckHeading.start && heading.level <= 2));
|
|
492
|
+
const stateCheckBody = stateCheckHeading ? content.slice(stateCheckHeading.end, nextHeading?.start ?? content.length) : "";
|
|
493
|
+
for (const pattern of stateCheckPatterns) {
|
|
494
|
+
if (!new RegExp(pattern, "m").test(stateCheckBody)) {
|
|
495
|
+
return failResult("artifact", `${path.basename(artifactPath)} state-check section is missing required command: ${pattern}`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
486
499
|
return passResult("artifact", `${path.basename(artifactPath)} passed (${requiredSections.length} sections)`);
|
|
487
500
|
}
|
|
488
501
|
function checkDecisionDetails({ taskDir, artifactFile }) {
|
|
@@ -1078,17 +1091,9 @@ function checkReviewFact({ taskDir, artifactFile, repositoryRoot }) {
|
|
|
1078
1091
|
function reviewWorktreeForTask(repositoryRoot, branch) {
|
|
1079
1092
|
if (!branch)
|
|
1080
1093
|
return null;
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
1085
|
-
}).trim().split(/\r?\n\r?\n/);
|
|
1086
|
-
for (const record of records) {
|
|
1087
|
-
const worktree = /^worktree (.+)$/m.exec(record)?.[1];
|
|
1088
|
-
const branchRef = /^branch (.+)$/m.exec(record)?.[1];
|
|
1089
|
-
if (worktree && branchRef === expectedRef)
|
|
1090
|
-
return worktree;
|
|
1091
|
-
}
|
|
1094
|
+
const worktree = resolveBranchWorktree(repositoryRoot, branch);
|
|
1095
|
+
if (worktree)
|
|
1096
|
+
return worktree;
|
|
1092
1097
|
throw new Error(`Task branch '${branch}' is not checked out in a registered worktree`);
|
|
1093
1098
|
}
|
|
1094
1099
|
function resolvePostReviewBaseline({ gitRoot, lastReviewedCommit, reviewArtifact }) {
|
|
@@ -4,7 +4,6 @@ import path from 'node:path';
|
|
|
4
4
|
import { verifyInProcess } from './verification-engine.js';
|
|
5
5
|
import { parseArtifactName } from './artifact-name.js';
|
|
6
6
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
7
|
-
import { allowsManualOverride } from './guard-override.js';
|
|
8
7
|
const gate = (skill, expectedState, artifactFamily) => ({
|
|
9
8
|
skill, expectedState, mode: 'gate', ...(artifactFamily ? { artifactFamily } : {})
|
|
10
9
|
});
|
|
@@ -50,7 +49,7 @@ async function verifyTaskEvent(request, options = {}) {
|
|
|
50
49
|
if (!resolved.ok)
|
|
51
50
|
return failure(request, resolved.code, resolved.message, { taskId: resolved.taskId, skill: spec.skill, mode: spec.mode });
|
|
52
51
|
const identity = { taskId: resolved.taskId, taskDir: resolved.taskDir, taskState: resolved.state, skill: spec.skill, mode: spec.mode };
|
|
53
|
-
if (resolved.state !== spec.expectedState
|
|
52
|
+
if (resolved.state !== spec.expectedState) {
|
|
54
53
|
return failure(request, 'VERIFY_TASK_STATE_MISMATCH', `event '${request.event}' requires workspace '${spec.expectedState}', received '${resolved.state}'`, identity);
|
|
55
54
|
}
|
|
56
55
|
if (spec.artifactFamily) {
|
|
@@ -69,23 +68,12 @@ async function verifyTaskEvent(request, options = {}) {
|
|
|
69
68
|
if (spec.mode === 'gate') {
|
|
70
69
|
const payload = await engine({ mode: 'gate', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [], repositoryRoot: resolved.repoRoot });
|
|
71
70
|
const status = payload.gate;
|
|
72
|
-
if (status !== 'pass' && allowsManualOverride(options.manualOverride, 'verification-engine', status === 'blocked' ? 'CHECK_BLOCKED' : 'CHECK_FAILED')) {
|
|
73
|
-
invocations.push({ status: 'pass', exitCode: 0, payload: { ...payload, gate: 'pass', humanOverride: 'human-approved', originalGate: status } });
|
|
74
|
-
return {
|
|
75
|
-
status: 'pass', changed: false, event: request.event, requestRef: request.taskRef,
|
|
76
|
-
...identity, artifact: request.artifact ?? null, invocations, error: null
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
71
|
invocations.push({ status, exitCode: { pass: 0, fail: 1, blocked: 2 }[status], payload });
|
|
80
72
|
}
|
|
81
73
|
else {
|
|
82
74
|
for (const check of spec.checks ?? []) {
|
|
83
75
|
const payload = await engine({ mode: 'checks', skillName: spec.skill, taskDir: resolved.taskDir, artifactFile: request.artifact, checks: [check], repositoryRoot: resolved.repoRoot });
|
|
84
76
|
const status = (payload.effectiveStatus ?? payload.status);
|
|
85
|
-
if (status !== 'pass' && allowsManualOverride(options.manualOverride, 'verification-engine', status === 'blocked' ? 'CHECK_BLOCKED' : 'CHECK_FAILED')) {
|
|
86
|
-
invocations.push({ status: 'pass', exitCode: 0, payload: { ...payload, status: 'pass', humanOverride: 'human-approved', originalStatus: status } });
|
|
87
|
-
break;
|
|
88
|
-
}
|
|
89
77
|
invocations.push({ status, exitCode: { pass: 0, fail: 1, blocked: 2 }[status], payload });
|
|
90
78
|
if (status !== 'pass')
|
|
91
79
|
break;
|
|
@@ -3,7 +3,6 @@ import { resolveTaskRef } from './resolve-ref.js';
|
|
|
3
3
|
import { parseTaskFrontmatter } from './frontmatter.js';
|
|
4
4
|
import { WORKFLOW_WARNING_COLUMNS, WORKFLOW_WARNING_HEADINGS, WORKFLOW_WARNING_SEVERITIES, WORKFLOW_WARNING_STATUSES, parseWorkflowWarnings } from './workflow-warnings.js';
|
|
5
5
|
import { captureTaskWriteMetadata, writeTask } from './write.js';
|
|
6
|
-
import { allowsManualOverride } from './guard-override.js';
|
|
7
6
|
import { retryHintForWarning } from './operation-outcome.js';
|
|
8
7
|
function failed(intent, code, message, taskId = null, entityId = null) {
|
|
9
8
|
return { status: 'failed', changed: false, intent: intent.kind, taskId, entityId, before: null, after: null, warnings: [], operations: [], error: { code, message } };
|
|
@@ -44,8 +43,7 @@ function applyWorkflowWarningIntent(intent, options = {}) {
|
|
|
44
43
|
const resolved = resolveTaskRef(intent.taskRef, { repoRoot: options.repoRoot });
|
|
45
44
|
if (!resolved.ok)
|
|
46
45
|
return failed(intent, resolved.code, resolved.message, resolved.taskId);
|
|
47
|
-
|
|
48
|
-
if (resolved.state !== 'active' && !stateOverride)
|
|
46
|
+
if (resolved.state !== 'active')
|
|
49
47
|
return failed(intent, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected active`, resolved.taskId);
|
|
50
48
|
let content;
|
|
51
49
|
let rows;
|
|
@@ -102,7 +100,7 @@ function applyWorkflowWarningIntent(intent, options = {}) {
|
|
|
102
100
|
after = { ...before, status: intent.status, resolvedAt: metadata.timestamp, resolution };
|
|
103
101
|
}
|
|
104
102
|
const writeResult = writeTask({
|
|
105
|
-
taskRef: intent.taskRef, expectedState:
|
|
103
|
+
taskRef: intent.taskRef, expectedState: 'active', mutations: [...sectionMutation(content), rowMutation(after)], dryRun: intent.dryRun
|
|
106
104
|
}, {
|
|
107
105
|
...options,
|
|
108
106
|
taskLocation: { repoRoot: resolved.repoRoot, taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, state: resolved.state },
|
package/dist/lib/task/write.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
3
4
|
import { isValidAgentInfraVersion, VERSION } from '../version.js';
|
|
4
5
|
import { parseTypedTaskFrontmatter, updateTaskFrontmatter } from './frontmatter.js';
|
|
5
6
|
import { resolveTaskRef } from './resolve-ref.js';
|
|
6
|
-
import { allowsManualOverride } from './guard-override.js';
|
|
7
7
|
import { mutateTableRow, upsertSection } from './sections.js';
|
|
8
8
|
import { validateCurrentTaskContract } from './current-contract.js';
|
|
9
9
|
import { invalidationBlocks, parseInvalidationDocument } from './invalidation.js';
|
|
@@ -54,7 +54,7 @@ function errorDetails(error, fallback) {
|
|
|
54
54
|
message: error instanceof Error ? error.message : String(error)
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
|
-
function
|
|
57
|
+
function writeTaskCore(request, options = {}) {
|
|
58
58
|
const resolved = options.taskLocation
|
|
59
59
|
? {
|
|
60
60
|
ok: true,
|
|
@@ -73,7 +73,7 @@ function writeTask(request, options = {}) {
|
|
|
73
73
|
taskMdPath: resolved.taskMdPath,
|
|
74
74
|
actualState: resolved.state
|
|
75
75
|
};
|
|
76
|
-
if (resolved.state !== request.expectedState
|
|
76
|
+
if (resolved.state !== request.expectedState) {
|
|
77
77
|
return failure(request, identity, 'TASK_STATE_MISMATCH', `task ${resolved.taskId} is ${resolved.state}, expected ${request.expectedState}`);
|
|
78
78
|
}
|
|
79
79
|
const io = { ...DEFAULT_FILE_SYSTEM, ...options.fileSystem };
|
|
@@ -259,5 +259,124 @@ function writeTask(request, options = {}) {
|
|
|
259
259
|
}
|
|
260
260
|
return { ...successBase, status: 'applied', changed: true };
|
|
261
261
|
}
|
|
262
|
+
function archiveContentsHash(localDir) {
|
|
263
|
+
const files = [];
|
|
264
|
+
const walk = (dir) => {
|
|
265
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
266
|
+
if (entry.name.includes('\n') || entry.name.includes('\r') || entry.name === 'manifest.md') {
|
|
267
|
+
throw new Error(`Archive source path is not valid: ${path.join(dir, entry.name)}`);
|
|
268
|
+
}
|
|
269
|
+
const target = path.join(dir, entry.name);
|
|
270
|
+
const stat = fs.lstatSync(target);
|
|
271
|
+
if (stat.isSymbolicLink())
|
|
272
|
+
throw new Error(`Archive source cannot contain symbolic links: ${target}`);
|
|
273
|
+
if (stat.isDirectory())
|
|
274
|
+
walk(target);
|
|
275
|
+
else if (stat.isFile()) {
|
|
276
|
+
if (entry.name !== 'contents.sha256')
|
|
277
|
+
files.push(target);
|
|
278
|
+
}
|
|
279
|
+
else
|
|
280
|
+
throw new Error(`Archive source cannot contain special files: ${target}`);
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
walk(localDir);
|
|
284
|
+
const rows = files
|
|
285
|
+
.map((file) => ({ file, relative: path.relative(localDir, file).split(path.sep).join('/') }))
|
|
286
|
+
.sort((left, right) => Buffer.compare(Buffer.from(left.relative), Buffer.from(right.relative)))
|
|
287
|
+
.map(({ file, relative }) => `${crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex')} ${relative}`);
|
|
288
|
+
return `${rows.join('\n')}${rows.length ? '\n' : ''}`;
|
|
289
|
+
}
|
|
290
|
+
function writeArchiveContentsHash(localDir) {
|
|
291
|
+
const contents = archiveContentsHash(localDir);
|
|
292
|
+
const hashPath = path.join(localDir, 'contents.sha256');
|
|
293
|
+
const tempPath = path.join(localDir, `.contents.sha256.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`);
|
|
294
|
+
try {
|
|
295
|
+
fs.writeFileSync(tempPath, contents, { flag: 'wx' });
|
|
296
|
+
fs.renameSync(tempPath, hashPath);
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
try {
|
|
300
|
+
fs.unlinkSync(tempPath);
|
|
301
|
+
}
|
|
302
|
+
catch (cleanupError) {
|
|
303
|
+
if (cleanupError.code !== 'ENOENT') {
|
|
304
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)}; failed to remove ${tempPath}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
function restoreArchivedTask(taskMdPath, contents, mode) {
|
|
311
|
+
const tempPath = `${taskMdPath}.${process.pid}.${Math.random().toString(36).slice(2)}.rollback`;
|
|
312
|
+
fs.writeFileSync(tempPath, contents, { flag: 'wx', mode });
|
|
313
|
+
try {
|
|
314
|
+
fs.renameSync(tempPath, taskMdPath);
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
try {
|
|
318
|
+
fs.unlinkSync(tempPath);
|
|
319
|
+
}
|
|
320
|
+
catch (cleanupError) {
|
|
321
|
+
if (cleanupError.code !== 'ENOENT') {
|
|
322
|
+
throw new Error(`${error instanceof Error ? error.message : String(error)}; failed to remove ${tempPath}: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
function writeTask(request, options = {}) {
|
|
329
|
+
const resolved = options.taskLocation
|
|
330
|
+
? {
|
|
331
|
+
ok: true,
|
|
332
|
+
repoRoot: options.taskLocation.repoRoot,
|
|
333
|
+
taskId: options.taskLocation.taskId,
|
|
334
|
+
taskDir: path.dirname(options.taskLocation.taskMdPath),
|
|
335
|
+
taskMdPath: options.taskLocation.taskMdPath,
|
|
336
|
+
state: options.taskLocation.state
|
|
337
|
+
}
|
|
338
|
+
: resolveTaskRef(request.taskRef, { repoRoot: options.repoRoot });
|
|
339
|
+
if (!resolved.ok) {
|
|
340
|
+
return failure(request, { taskId: resolved.taskId, taskMdPath: null, actualState: null }, resolved.code, resolved.message);
|
|
341
|
+
}
|
|
342
|
+
if (resolved.state !== 'archive' || request.expectedState !== 'archive' || request.dryRun) {
|
|
343
|
+
return writeTaskCore(request, { ...options, taskLocation: {
|
|
344
|
+
repoRoot: resolved.repoRoot,
|
|
345
|
+
taskId: resolved.taskId,
|
|
346
|
+
taskMdPath: resolved.taskMdPath,
|
|
347
|
+
state: resolved.state
|
|
348
|
+
} });
|
|
349
|
+
}
|
|
350
|
+
const identity = { taskId: resolved.taskId, taskMdPath: resolved.taskMdPath, actualState: resolved.state };
|
|
351
|
+
try {
|
|
352
|
+
const originalTask = fs.readFileSync(resolved.taskMdPath);
|
|
353
|
+
const originalMode = fs.statSync(resolved.taskMdPath).mode;
|
|
354
|
+
archiveContentsHash(resolved.taskDir);
|
|
355
|
+
const result = writeTaskCore(request, { ...options, taskLocation: {
|
|
356
|
+
repoRoot: resolved.repoRoot,
|
|
357
|
+
taskId: resolved.taskId,
|
|
358
|
+
taskMdPath: resolved.taskMdPath,
|
|
359
|
+
state: resolved.state
|
|
360
|
+
} });
|
|
361
|
+
if (result.status === 'applied') {
|
|
362
|
+
try {
|
|
363
|
+
writeArchiveContentsHash(resolved.taskDir);
|
|
364
|
+
}
|
|
365
|
+
catch (error) {
|
|
366
|
+
try {
|
|
367
|
+
restoreArchivedTask(resolved.taskMdPath, originalTask, originalMode);
|
|
368
|
+
}
|
|
369
|
+
catch (rollbackError) {
|
|
370
|
+
throw new Error(`Archive checksum update failed: ${error instanceof Error ? error.message : String(error)}; task rollback failed: ${rollbackError instanceof Error ? rollbackError.message : String(rollbackError)}`);
|
|
371
|
+
}
|
|
372
|
+
throw error;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
377
|
+
catch (error) {
|
|
378
|
+
return failure(request, identity, 'TASK_READ_FAILED', error instanceof Error ? error.message : String(error));
|
|
379
|
+
}
|
|
380
|
+
}
|
|
262
381
|
export { writeTask, captureTaskWriteMetadata, canonicalTimestamp };
|
|
263
382
|
//# sourceMappingURL=write.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"protocolVersion": 3,
|
|
3
|
-
"packageVersion": "0.11.
|
|
4
|
-
"sourceInputHash": "
|
|
5
|
-
"internalExecutableBuildHash": "
|
|
3
|
+
"packageVersion": "0.11.6",
|
|
4
|
+
"sourceInputHash": "402048e73650c5ac4edfb1bc4b48ca3fceb7b9068790b1321281c4994121e80c",
|
|
5
|
+
"internalExecutableBuildHash": "47ecd04ac30183443b2b86f83d1668b8bdf9e59162cc9e2cedd333d3011843e9"
|
|
6
6
|
}
|
package/lib/decide.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { VERSION } from './version.ts';
|
|
|
2
2
|
import { parseTaskScope } from './task/command-options.ts';
|
|
3
3
|
import { applyHumanDecision } from './task/decision-intents.ts';
|
|
4
4
|
import { canonicalTimestamp } from './task/write.ts';
|
|
5
|
-
import { consumeHumanOverride, failureId } from './task/human-override.ts';
|
|
6
5
|
import { resolveTaskContext } from './task/resolve-ref.ts';
|
|
7
6
|
import { TaskExecutionLockError, withTaskExecutionLock } from './task/task-execution-lock.ts';
|
|
8
7
|
|
|
@@ -12,12 +11,9 @@ type DecideOptions = {
|
|
|
12
11
|
version?: string;
|
|
13
12
|
};
|
|
14
13
|
|
|
15
|
-
function parseDecisionParts(parts: string[]): { decision: string; needsImplementation: boolean | undefined
|
|
14
|
+
function parseDecisionParts(parts: string[]): { decision: string; needsImplementation: boolean | undefined } {
|
|
16
15
|
const decision: string[] = [];
|
|
17
16
|
let needsImplementation: boolean | undefined;
|
|
18
|
-
let overrideTicket: string | undefined;
|
|
19
|
-
let overrideTarget: string | undefined;
|
|
20
|
-
let overrideScope: string | undefined;
|
|
21
17
|
for (let index = 0; index < parts.length; index += 1) {
|
|
22
18
|
const part = parts[index];
|
|
23
19
|
if (part === '--needs-implementation') {
|
|
@@ -27,18 +23,14 @@ function parseDecisionParts(parts: string[]): { decision: string; needsImplement
|
|
|
27
23
|
throw new Error("--needs-implementation must be 'true' or 'false'");
|
|
28
24
|
}
|
|
29
25
|
needsImplementation = value === 'true';
|
|
30
|
-
} else if (part
|
|
31
|
-
|
|
32
|
-
if (!value) throw new Error(`${part} requires a value`);
|
|
33
|
-
if (part === '--override-ticket') overrideTicket = value;
|
|
34
|
-
else if (part === '--override-target') overrideTarget = value;
|
|
35
|
-
else overrideScope = value;
|
|
26
|
+
} else if (part?.startsWith('--')) {
|
|
27
|
+
throw new Error(`unknown option '${part}'`);
|
|
36
28
|
} else {
|
|
37
29
|
decision.push(parts[index]!);
|
|
38
30
|
}
|
|
39
31
|
}
|
|
40
32
|
if (decision.length === 0) throw new Error('decision content is required');
|
|
41
|
-
return { decision: decision.join(' '), needsImplementation
|
|
33
|
+
return { decision: decision.join(' '), needsImplementation };
|
|
42
34
|
}
|
|
43
35
|
|
|
44
36
|
export async function decide(args: string[], options: DecideOptions = {}): Promise<number> {
|
|
@@ -79,29 +71,7 @@ export async function decide(args: string[], options: DecideOptions = {}): Promi
|
|
|
79
71
|
metadataProvider: () => ({ timestamp: now, agentInfraVersion: options.version ?? VERSION })
|
|
80
72
|
};
|
|
81
73
|
let result;
|
|
82
|
-
const execute = async () =>
|
|
83
|
-
let current = applyHumanDecision(request, writeOptions);
|
|
84
|
-
if (current.status !== 'failed' || !parsedDecision.overrideTicket) return current;
|
|
85
|
-
if (!parsedDecision.overrideTarget || !parsedDecision.overrideScope) throw new Error('override ticket requires --override-target and --override-scope');
|
|
86
|
-
const consumed = await consumeHumanOverride({
|
|
87
|
-
taskRef: resolved.taskId,
|
|
88
|
-
ticketId: parsedDecision.overrideTicket,
|
|
89
|
-
failureId: failureId('decision-intent', current.error?.code ?? 'TASK_STATE_MISMATCH'),
|
|
90
|
-
target: parsedDecision.overrideTarget,
|
|
91
|
-
scope: parsedDecision.overrideScope
|
|
92
|
-
}, {
|
|
93
|
-
...writeOptions,
|
|
94
|
-
effectExecutor: (capability) => {
|
|
95
|
-
const retried = applyHumanDecision(request, { ...writeOptions, manualOverride: capability });
|
|
96
|
-
current = retried;
|
|
97
|
-
return retried.status === 'failed' || retried.status === 'planned'
|
|
98
|
-
? { code: 'OVERRIDE_EFFECT_FAILED', message: retried.status === 'planned' ? 'producer returned planned; no decision effect was committed' : `${retried.error?.code ?? 'DECISION_FAILED'}: ${retried.error?.message ?? 'manual decision effect failed'}` }
|
|
99
|
-
: null;
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
if (consumed.status === 'failed') throw new Error(consumed.error.message);
|
|
103
|
-
return current;
|
|
104
|
-
};
|
|
74
|
+
const execute = async () => applyHumanDecision(request, writeOptions);
|
|
105
75
|
result = await withTaskExecutionLock(resolved.repoRoot, resolved.taskId, 'task-decision', execute);
|
|
106
76
|
if (result.error) throw new Error(result.error.message);
|
|
107
77
|
return 0;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
function gitText(repositoryRoot: string, args: string[]): string | null {
|
|
4
|
+
try {
|
|
5
|
+
return execFileSync('git', ['-C', repositoryRoot, ...args], {
|
|
6
|
+
encoding: 'utf8',
|
|
7
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
8
|
+
}).trim();
|
|
9
|
+
} catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function verifiedWorktreeRoot(candidate: string, expectedBranch: string, expectedCommonDirectory: string): string | null {
|
|
15
|
+
const commonDirectory = gitText(candidate, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
|
|
16
|
+
if (commonDirectory !== expectedCommonDirectory) return null;
|
|
17
|
+
const root = gitText(candidate, ['rev-parse', '--show-toplevel']);
|
|
18
|
+
if (!root) return null;
|
|
19
|
+
const branch = gitText(root, ['symbolic-ref', '--short', 'HEAD']);
|
|
20
|
+
return branch === expectedBranch ? root : null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function resolveBranchWorktree(repositoryRoot: string, expectedBranch: string): string | null {
|
|
24
|
+
const commonDirectory = gitText(repositoryRoot, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
|
|
25
|
+
if (!commonDirectory) return null;
|
|
26
|
+
const currentRoot = verifiedWorktreeRoot(repositoryRoot, expectedBranch, commonDirectory);
|
|
27
|
+
if (currentRoot) return currentRoot;
|
|
28
|
+
|
|
29
|
+
const records = gitText(repositoryRoot, ['worktree', 'list', '--porcelain']);
|
|
30
|
+
if (!records) return null;
|
|
31
|
+
const expectedRef = `refs/heads/${expectedBranch}`;
|
|
32
|
+
for (const record of records.split(/\r?\n\r?\n/)) {
|
|
33
|
+
const candidate = /^worktree (.+)$/m.exec(record)?.[1];
|
|
34
|
+
const branch = /^branch (.+)$/m.exec(record)?.[1];
|
|
35
|
+
if (candidate && branch === expectedRef) {
|
|
36
|
+
const verifiedRoot = verifiedWorktreeRoot(candidate, expectedBranch, commonDirectory);
|
|
37
|
+
if (verifiedRoot) return verifiedRoot;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
@@ -61,7 +61,6 @@ export const INTERNAL_HANDLER_ROUTE_SELECTORS = Object.freeze({
|
|
|
61
61
|
'task-review': ['preflight', 'finalize-summary'],
|
|
62
62
|
'task-event': ['event'],
|
|
63
63
|
'task-invalidation': ['reconcile'],
|
|
64
|
-
'task-override': ['diagnose', 'issue', 'consume'],
|
|
65
64
|
'task-lifecycle': ['intent'],
|
|
66
65
|
'task-finalization': ['complete'],
|
|
67
66
|
'task-short-id': ['list', 'list-verify', 'alloc', 'release', 'resolve'],
|
|
@@ -98,7 +97,6 @@ export function internalRouteSelector(command: string, args: readonly string[]):
|
|
|
98
97
|
if (command === 'task-orchestration') return args[1] === 'status' ? 'status' : args[1] ? 'progress' : '';
|
|
99
98
|
if (command === 'task-review') return args[1] ?? '';
|
|
100
99
|
if (command === 'task-invalidation') return args[1] ?? '';
|
|
101
|
-
if (command === 'task-override') return args[1] ?? '';
|
|
102
100
|
if (command === 'task-artifact') return args[1] ?? '';
|
|
103
101
|
if (command === 'platform-pr-review' && first(args) === 'publish') {
|
|
104
102
|
const scope = optionValue(args, '--scope') ?? '';
|