@fitlab-ai/agent-infra 0.9.12 → 0.9.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -11
- package/README.zh-CN.md +11 -11
- package/dist/bin/internal-cli.js +24 -19
- package/dist/lib/agent-clients/next-steps.js +6 -1
- package/dist/lib/decide.js +3 -3
- package/dist/lib/internal/platform-checks.js +3 -3
- package/dist/lib/internal/platform-comment.js +9 -9
- package/dist/lib/internal/platform-context.js +2 -2
- package/dist/lib/internal/platform-issue.js +8 -9
- package/dist/lib/internal/platform-pr-review.js +10 -10
- package/dist/lib/internal/platform-pr.js +63 -24
- package/dist/lib/internal/platform-release-notes.js +3 -3
- package/dist/lib/internal/pr-review-grade.js +11 -9
- package/dist/lib/internal/release-workflow.js +4 -4
- package/dist/lib/internal/sandbox-control.js +30 -4
- package/dist/lib/internal/task-activity.js +3 -3
- package/dist/lib/internal/task-artifact.js +76 -24
- package/dist/lib/internal/task-create.js +2 -2
- package/dist/lib/internal/task-event.js +6 -3
- package/dist/lib/internal/task-finalization.js +2 -2
- package/dist/lib/internal/task-invalidation.js +60 -0
- package/dist/lib/internal/task-ledger.js +9 -6
- package/dist/lib/internal/task-lifecycle.js +2 -2
- package/dist/lib/internal/task-override.js +4 -4
- package/dist/lib/internal/task-review.js +3 -3
- package/dist/lib/internal/task-verify.js +6 -6
- package/dist/lib/internal/task-warning.js +3 -3
- package/dist/lib/platform/adapters.js +1 -3
- package/dist/lib/platform/change-request-git-evidence.js +27 -7
- package/dist/lib/platform/completion-backfill.js +6 -5
- package/dist/lib/platform/context.js +70 -130
- package/dist/lib/platform/github-client.js +1 -1
- package/dist/lib/platform/github-provider.js +903 -0
- package/dist/lib/platform/in-label-sync.js +143 -0
- package/dist/lib/platform/issue-comments.js +94 -41
- package/dist/lib/platform/issues.js +335 -189
- package/dist/lib/platform/metadata-labels.js +6 -2
- package/dist/lib/platform/none-provider.js +28 -0
- package/dist/lib/platform/pr-change-report.js +551 -0
- package/dist/lib/platform/pr-checks.js +119 -104
- package/dist/lib/platform/pr-review.js +88 -72
- package/dist/lib/platform/pr-summary.js +520 -50
- package/dist/lib/platform/provider-bridge.js +45 -0
- package/dist/lib/platform/provider-contract.d.ts +487 -0
- package/dist/lib/platform/provider-contract.js +95 -0
- package/dist/lib/platform/provider-loader.js +251 -0
- package/dist/lib/platform/provider-validation.js +504 -0
- package/dist/lib/platform/pull-request-metadata.js +8 -6
- package/dist/lib/platform/pull-requests.js +783 -537
- package/dist/lib/platform/release-notes.js +78 -55
- package/dist/lib/platform/releases.js +79 -50
- package/dist/lib/platform/resource-identity.d.ts +26 -0
- package/dist/lib/platform/resource-identity.js +92 -0
- package/dist/lib/platform/task-identities.js +31 -0
- package/dist/lib/platform/verification-required.js +11 -12
- package/dist/lib/platform/verification-sync.js +112 -238
- package/dist/lib/pr-review/evidence-grading.js +19 -6
- package/dist/lib/run/index.js +63 -23
- package/dist/lib/sandbox/control/client.js +96 -15
- package/dist/lib/sandbox/control/executor.js +3 -3
- package/dist/lib/sandbox/control/protocol.js +77 -0
- package/dist/lib/sandbox/control/server.js +398 -25
- package/dist/lib/sandbox/control/state.js +204 -1
- package/dist/lib/server/protocol.js +38 -1
- package/dist/lib/task/activity-log.js +8 -1
- package/dist/lib/task/artifact-lifecycle.js +33 -5
- package/dist/lib/task/capabilities.js +239 -0
- package/dist/lib/task/commands/status.js +32 -0
- package/dist/lib/task/commit-operation.js +12 -1
- package/dist/lib/task/control-authority.js +8 -5
- package/dist/lib/task/create-service.js +8 -8
- package/dist/lib/task/current-contract.js +5 -1
- package/dist/lib/task/decision-details.js +1 -1
- package/dist/lib/task/events.js +297 -17
- package/dist/lib/task/finalization.js +37 -13
- package/dist/lib/task/human-override.js +58 -36
- package/dist/lib/task/invalidation-command.js +86 -0
- package/dist/lib/task/invalidation.js +260 -0
- package/dist/lib/task/ledger-intents.js +37 -0
- package/dist/lib/task/local-artifact-finalization.js +374 -0
- package/dist/lib/task/orchestration.js +49 -93
- package/dist/lib/task/pr-delivery-fact.js +102 -101
- package/dist/lib/task/review-fingerprint.js +24 -2
- package/dist/lib/task/rework-intent.js +138 -0
- package/dist/lib/task/skill-scope.js +23 -0
- package/dist/lib/task/task-execution-lock.js +27 -17
- package/dist/lib/task/verification-engine.js +43 -16
- package/dist/lib/task/verification.js +3 -3
- package/dist/lib/task/workflow-warning-intents.js +1 -1
- package/dist/lib/task/write.js +11 -0
- package/dist/lib/version.js +11 -1
- package/dist/lifecycle-build-manifest.json +3 -3
- package/lib/agent-clients/next-steps.ts +6 -1
- package/lib/decide.ts +3 -3
- package/lib/init.ts +1 -1
- package/lib/internal/platform-checks.ts +3 -3
- package/lib/internal/platform-comment.ts +8 -8
- package/lib/internal/platform-context.ts +2 -2
- package/lib/internal/platform-issue.ts +7 -8
- package/lib/internal/platform-pr-review.ts +9 -9
- package/lib/internal/platform-pr.ts +47 -22
- package/lib/internal/platform-release-notes.ts +3 -3
- package/lib/internal/pr-review-grade.ts +10 -8
- package/lib/internal/release-workflow.ts +4 -4
- package/lib/internal/sandbox-control.ts +27 -3
- package/lib/internal/task-activity.ts +3 -3
- package/lib/internal/task-artifact.ts +65 -22
- package/lib/internal/task-create.ts +2 -2
- package/lib/internal/task-event.ts +6 -3
- package/lib/internal/task-finalization.ts +3 -3
- package/lib/internal/task-invalidation.ts +39 -0
- package/lib/internal/task-ledger.ts +11 -8
- package/lib/internal/task-lifecycle.ts +2 -2
- package/lib/internal/task-override.ts +6 -6
- package/lib/internal/task-review.ts +3 -3
- package/lib/internal/task-verify.ts +6 -6
- package/lib/internal/task-warning.ts +3 -3
- package/lib/platform/adapters.ts +3 -2
- package/lib/platform/change-request-git-evidence.ts +36 -9
- package/lib/platform/completion-backfill.ts +9 -8
- package/lib/platform/context.ts +97 -134
- package/lib/platform/github-client.ts +2 -2
- package/lib/platform/github-provider.ts +910 -0
- package/lib/platform/in-label-sync.ts +208 -0
- package/lib/platform/issue-comments.ts +97 -47
- package/lib/platform/issues.ts +344 -174
- package/lib/platform/metadata-labels.ts +5 -2
- package/lib/platform/none-provider.ts +35 -0
- package/lib/platform/pr-change-report.ts +645 -0
- package/lib/platform/pr-checks.ts +126 -106
- package/lib/platform/pr-review.ts +88 -85
- package/lib/platform/pr-summary.ts +560 -53
- package/lib/platform/provider-bridge.ts +82 -0
- package/lib/platform/provider-contract.ts +536 -0
- package/lib/platform/provider-loader.ts +307 -0
- package/lib/platform/provider-validation.ts +529 -0
- package/lib/platform/pull-request-metadata.ts +10 -7
- package/lib/platform/pull-requests.ts +826 -543
- package/lib/platform/release-notes.ts +76 -59
- package/lib/platform/releases.ts +77 -43
- package/lib/platform/resource-identity.ts +121 -0
- package/lib/platform/task-identities.ts +40 -0
- package/lib/platform/types.ts +4 -2
- package/lib/platform/verification-required.ts +11 -13
- package/lib/platform/verification-sync.ts +112 -292
- package/lib/pr-review/evidence-grading.ts +25 -8
- package/lib/run/index.ts +57 -22
- package/lib/sandbox/control/client.ts +105 -15
- package/lib/sandbox/control/executor.ts +4 -4
- package/lib/sandbox/control/protocol.ts +113 -0
- package/lib/sandbox/control/server.ts +431 -23
- package/lib/sandbox/control/state.ts +251 -1
- package/lib/server/protocol.ts +36 -1
- package/lib/task/activity-log.ts +8 -0
- package/lib/task/artifact-lifecycle.ts +32 -6
- package/lib/task/capabilities.ts +262 -0
- package/lib/task/commands/status.ts +38 -0
- package/lib/task/commit-operation.ts +12 -1
- package/lib/task/control-authority.ts +11 -8
- package/lib/task/create-service.ts +8 -8
- package/lib/task/current-contract.ts +6 -1
- package/lib/task/decision-details.ts +6 -2
- package/lib/task/events.ts +296 -16
- package/lib/task/finalization.ts +50 -14
- package/lib/task/human-override.ts +70 -46
- package/lib/task/invalidation-command.ts +86 -0
- package/lib/task/invalidation.ts +332 -0
- package/lib/task/ledger-intents.ts +35 -1
- package/lib/task/local-artifact-finalization.ts +563 -0
- package/lib/task/orchestration.ts +52 -85
- package/lib/task/pr-delivery-fact.ts +131 -148
- package/lib/task/review-fingerprint.ts +23 -2
- package/lib/task/rework-intent.ts +165 -0
- package/lib/task/skill-scope.ts +24 -0
- package/lib/task/task-execution-lock.ts +23 -14
- package/lib/task/verification-engine.ts +52 -17
- package/lib/task/verification.ts +4 -4
- package/lib/task/workflow-warning-intents.ts +1 -1
- package/lib/task/write.ts +19 -0
- package/lib/update.ts +1 -1
- package/lib/version.ts +15 -1
- package/package.json +7 -1
- package/templates/.agents/rules/README.en.md +1 -1
- package/templates/.agents/rules/README.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.en.md +1 -1
- package/templates/.agents/rules/issue-pr-commands.zh-CN.md +1 -1
- package/templates/.agents/rules/issue-sync.en.md +2 -0
- package/templates/.agents/rules/issue-sync.zh-CN.md +2 -0
- package/templates/.agents/rules/local-artifact-repair.en.md +9 -2
- package/templates/.agents/rules/local-artifact-repair.zh-CN.md +9 -2
- package/templates/.agents/rules/pr-sync.en.md +10 -4
- package/templates/.agents/rules/pr-sync.zh-CN.md +17 -4
- package/templates/.agents/rules/task-short-id.en.md +1 -4
- package/templates/.agents/rules/task-short-id.zh-CN.md +1 -3
- package/templates/.agents/skills/analyze-task/SKILL.en.md +13 -3
- package/templates/.agents/skills/analyze-task/SKILL.zh-CN.md +10 -3
- package/templates/.agents/skills/check-task/SKILL.en.md +16 -37
- package/templates/.agents/skills/check-task/SKILL.zh-CN.md +16 -37
- package/templates/.agents/skills/code-task/SKILL.en.md +27 -8
- package/templates/.agents/skills/code-task/SKILL.zh-CN.md +29 -10
- package/templates/.agents/skills/code-task/config/verify.en.json +1 -0
- package/templates/.agents/skills/code-task/config/verify.zh-CN.json +1 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.en.md +2 -0
- package/templates/.agents/skills/commit/reference/issue-metadata-sync.zh-CN.md +2 -0
- package/templates/.agents/skills/commit/reference/pr-summary-sync.en.md +3 -3
- package/templates/.agents/skills/commit/reference/pr-summary-sync.zh-CN.md +3 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.en.md +5 -3
- package/templates/.agents/skills/complete-manual-validation/SKILL.zh-CN.md +5 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.en.md +4 -3
- package/templates/.agents/skills/complete-manual-validation/reference/summary-update.zh-CN.md +4 -3
- package/templates/.agents/skills/complete-task/SKILL.en.md +21 -3
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +20 -3
- package/templates/.agents/skills/create-pr/SKILL.en.md +7 -6
- package/templates/.agents/skills/create-pr/SKILL.zh-CN.md +7 -7
- package/templates/.agents/skills/create-pr/config/verify.json +1 -0
- package/templates/.agents/skills/create-pr/reference/change-report.en.md +16 -4
- package/templates/.agents/skills/create-pr/reference/change-report.zh-CN.md +16 -4
- package/templates/.agents/skills/create-pr/reference/comment-publish.en.md +5 -3
- package/templates/.agents/skills/create-pr/reference/comment-publish.zh-CN.md +5 -3
- package/templates/.agents/skills/create-pr/reference/pr-body-template.en.md +1 -1
- package/templates/.agents/skills/create-pr/reference/pr-body-template.zh-CN.md +1 -1
- package/templates/.agents/skills/create-pr/scripts/change-report.mjs +3 -0
- package/templates/.agents/skills/plan-task/SKILL.en.md +13 -3
- package/templates/.agents/skills/plan-task/SKILL.zh-CN.md +10 -3
- package/templates/.agents/skills/review-analysis/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-analysis/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/review-code/SKILL.en.md +4 -4
- package/templates/.agents/skills/review-code/SKILL.zh-CN.md +4 -4
- package/templates/.agents/skills/review-plan/SKILL.en.md +3 -3
- package/templates/.agents/skills/review-plan/SKILL.zh-CN.md +3 -3
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +4 -2
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +4 -2
- package/templates/.agents/skills/watch-pr/SKILL.en.md +2 -2
- package/templates/.agents/skills/watch-pr/SKILL.zh-CN.md +2 -2
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.en.md +6 -0
- package/templates/.agents/skills/watch-pr/reference/monitor-and-heal.zh-CN.md +6 -0
- package/templates/.agents/templates/task.en.md +18 -1
- package/templates/.agents/templates/task.zh-CN.md +18 -1
- package/templates/.github/workflows/pr-label.yml +7 -26
|
@@ -7,10 +7,13 @@ import { requirementFieldLabels, renderTemplateBody } from '../task/issue-form.j
|
|
|
7
7
|
import { resolveTaskRef } from '../task/resolve-ref.js';
|
|
8
8
|
import { extractSection, findSectionHeading } from '../task/sections.js';
|
|
9
9
|
import { writeTask } from '../task/write.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { DEFAULT_REQUIREMENT_SECTION_ANCHORS, chooseMilestone, computeInLabels, desiredIssueType, normalizeOption, planIssueMetadata } from './issue-metadata.js';
|
|
10
|
+
import { resolvePlatformProviderContext } from './context.js';
|
|
11
|
+
import { DEFAULT_REQUIREMENT_SECTION_ANCHORS, chooseMilestone, desiredIssueType, normalizeOption, planIssueMetadata } from './issue-metadata.js';
|
|
13
12
|
import { platformResult } from './types.js';
|
|
13
|
+
import { labelDelta, planInLabelUpdate, syncLabelDelta, validateInLabelMapping, validateRepositoryLabelPayload } from './in-label-sync.js';
|
|
14
|
+
import { providerError, providerOperationContext, providerStatus, providerResourceIdentity, providerResourceToken, unsupportedProviderOperation } from './provider-bridge.js';
|
|
15
|
+
import { resourceIdentityEquals, resourceIdentityNumber, resourceIdentityString, serializeResourceIdentity } from './resource-identity.js';
|
|
16
|
+
import { taskIssueIdentity, taskIssueIdentityError } from './task-identities.js';
|
|
14
17
|
const ISSUE_TYPES_QUERY = `query($owner:String!){organization(login:$owner){issueTypes(first:20){nodes{id name pinnedFields{__typename ... on IssueFieldSingleSelect{id name options{id name}} ... on IssueFieldDate{id name} ... on IssueFieldText{id name} ... on IssueFieldNumber{id name}}}}}}`;
|
|
15
18
|
const ISSUE_FIELDS_QUERY = `query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){issue(number:$number){id issueType{id name pinnedFields{__typename ... on IssueFieldSingleSelect{id name options{id name}} ... on IssueFieldDate{id name} ... on IssueFieldText{id name} ... on IssueFieldNumber{id name}}} issueFieldValues(first:50){nodes{__typename ... on IssueFieldSingleSelectValue{name optionId field{... on IssueFieldSingleSelect{id name}}} ... on IssueFieldDateValue{value field{... on IssueFieldDate{id name}}} ... on IssueFieldTextValue{value field{... on IssueFieldText{id name}}} ... on IssueFieldNumberValue{value field{... on IssueFieldNumber{id name}}}}}}}}`;
|
|
16
19
|
function result(status, taskId, issueNumber, overrides = {}) {
|
|
@@ -21,7 +24,7 @@ function result(status, taskId, issueNumber, overrides = {}) {
|
|
|
21
24
|
...overrides
|
|
22
25
|
};
|
|
23
26
|
}
|
|
24
|
-
function resolvedContext(taskRef, options) {
|
|
27
|
+
async function resolvedContext(taskRef, options) {
|
|
25
28
|
const resolved = resolveTaskRef(taskRef, options.cwd ? { repoRoot: options.cwd } : {});
|
|
26
29
|
if (!resolved.ok)
|
|
27
30
|
return { ok: false, output: result('failed', resolved.taskId, null, {
|
|
@@ -29,33 +32,126 @@ function resolvedContext(taskRef, options) {
|
|
|
29
32
|
}) };
|
|
30
33
|
const content = fs.readFileSync(resolved.taskMdPath, 'utf8');
|
|
31
34
|
const frontmatter = parseTaskFrontmatter(content);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
let issueIdentity;
|
|
36
|
+
try {
|
|
37
|
+
issueIdentity = taskIssueIdentity(frontmatter, undefined, options.runtimeVersion);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
return { ok: false, output: result('failed', resolved.taskId, null, { error: { ...taskIssueIdentityError(error), retryable: false } }) };
|
|
41
|
+
}
|
|
42
|
+
const issueNumber = resourceIdentityNumber(issueIdentity);
|
|
43
|
+
const loaded = await resolvePlatformProviderContext({ cwd: resolved.repoRoot, client: options.client });
|
|
44
|
+
const context = loaded.ok ? loaded.value.context : loaded.context;
|
|
36
45
|
const usable = (context.status === 'no-op' || context.status === 'degraded') && context.platform.repository;
|
|
37
|
-
if (!usable)
|
|
46
|
+
if (!usable || !loaded.ok)
|
|
38
47
|
return { ok: false, output: result(context.status, resolved.taskId, issueNumber, {
|
|
39
48
|
platform: context.platform, capabilities: context.capabilities, operations: context.operations, error: context.error
|
|
40
49
|
}) };
|
|
41
|
-
return { ok: true, resolved, content, frontmatter, issueNumber, client, context };
|
|
50
|
+
return { ok: true, resolved, content, frontmatter, issueIdentity, issueNumber, client: options.client, context, provider: loaded.value.provider, providerType: loaded.value.providerType, loadedContext: loaded.value };
|
|
51
|
+
}
|
|
52
|
+
function normalizeProviderIssue(remote, repository, fallbackNumber) {
|
|
53
|
+
const identity = remote.identity || (remote.number ? { kind: 'number', value: remote.number } : { kind: 'id', value: remote.id });
|
|
54
|
+
const snapshot = {
|
|
55
|
+
repository,
|
|
56
|
+
number: remote.number ?? resourceIdentityNumber(identity) ?? fallbackNumber ?? 0,
|
|
57
|
+
identity,
|
|
58
|
+
databaseId: null,
|
|
59
|
+
nodeId: remote.id,
|
|
60
|
+
url: remote.displayUrl || '',
|
|
61
|
+
state: remote.state,
|
|
62
|
+
title: remote.title,
|
|
63
|
+
body: remote.body,
|
|
64
|
+
labels: [...remote.labels].sort(),
|
|
65
|
+
assignees: [...remote.assignees].sort(),
|
|
66
|
+
milestone: remote.milestone,
|
|
67
|
+
issueType: remote.issueType?.name || null,
|
|
68
|
+
fields: { ...remote.fields }
|
|
69
|
+
};
|
|
70
|
+
Object.defineProperty(snapshot, 'identity', { value: identity, enumerable: false, configurable: true });
|
|
71
|
+
if (remote.issueType)
|
|
72
|
+
Object.defineProperty(snapshot, 'issueTypeSnapshot', { value: remote.issueType, enumerable: false, configurable: true });
|
|
73
|
+
return snapshot;
|
|
74
|
+
}
|
|
75
|
+
function planProviderMetadata(remote, metadata, frontmatter, options, capabilities) {
|
|
76
|
+
if (!options.issueType && !options.fields)
|
|
77
|
+
return [];
|
|
78
|
+
if (!capabilities.push)
|
|
79
|
+
return [
|
|
80
|
+
...(options.issueType ? [{ name: 'issue-type', status: 'skipped', reasonCode: 'PUSH_REQUIRED' }] : []),
|
|
81
|
+
...(options.fields ? [{ name: 'fields', status: 'skipped', reasonCode: 'PUSH_REQUIRED' }] : [])
|
|
82
|
+
];
|
|
83
|
+
const target = metadata.issueTypes.find((candidate) => candidate.name === desiredIssueType(frontmatter.type || ''));
|
|
84
|
+
if (!target)
|
|
85
|
+
return [
|
|
86
|
+
...(options.issueType ? [{ name: 'issue-type', status: 'skipped', reasonCode: 'ISSUE_TYPES_UNSUPPORTED' }] : []),
|
|
87
|
+
...(options.fields ? [{ name: 'fields', status: 'skipped', reasonCode: 'ISSUE_TYPES_UNSUPPORTED' }] : [])
|
|
88
|
+
];
|
|
89
|
+
const operations = [];
|
|
90
|
+
if (options.issueType) {
|
|
91
|
+
operations.push(remote.issueType && resourceIdentityEquals(remote.issueType.identity, target.identity)
|
|
92
|
+
? { name: 'issue-type', status: 'no-op', reasonCode: null }
|
|
93
|
+
: { name: 'issue-type', status: 'planned', reasonCode: null, value: target.name });
|
|
94
|
+
}
|
|
95
|
+
if (options.fields) {
|
|
96
|
+
const fields = target.fields.map((field) => ({
|
|
97
|
+
id: resourceIdentityString(field.identity) || '',
|
|
98
|
+
name: field.name,
|
|
99
|
+
kind: field.kind,
|
|
100
|
+
options: field.options.map((option) => ({ id: resourceIdentityString(option.identity) || '', name: option.name }))
|
|
101
|
+
}));
|
|
102
|
+
const desired = desiredFieldValues(frontmatter, fields);
|
|
103
|
+
const values = Object.fromEntries(desired.map((field) => [field.name, field.value]));
|
|
104
|
+
const changed = Object.entries(values).some(([name, value]) => remote.fields[name] !== value);
|
|
105
|
+
operations.push(changed
|
|
106
|
+
? { name: 'fields', status: 'planned', reasonCode: null, value: values }
|
|
107
|
+
: { name: 'fields', status: 'no-op', reasonCode: null });
|
|
108
|
+
}
|
|
109
|
+
return operations;
|
|
110
|
+
}
|
|
111
|
+
function providerIssueError(base, error, issueIdentity) {
|
|
112
|
+
const issueNumber = resourceIdentityNumber(issueIdentity);
|
|
113
|
+
return result(error.code.startsWith('IN_LABEL_SYNC') ? 'blocked' : providerStatus(error), base.resolved.taskId, issueNumber, {
|
|
114
|
+
changed: error.code === 'IN_LABEL_SYNC_PARTIAL',
|
|
115
|
+
platform: base.context.platform,
|
|
116
|
+
capabilities: base.context.capabilities,
|
|
117
|
+
resource: { kind: 'issue', number: issueNumber, ...(issueIdentity ? { identity: issueIdentity } : {}) },
|
|
118
|
+
error: providerError(error, 'PLATFORM_PROVIDER_OPERATION_FAILED')
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
async function inspectExternalIssue(base, issueIdentity) {
|
|
122
|
+
const operation = base.provider.issues?.inspect
|
|
123
|
+
? await base.provider.issues.inspect({ context: providerOperationContext(base.loadedContext), target: issueIdentity })
|
|
124
|
+
: unsupportedProviderOperation(base.provider, 'issues.inspect');
|
|
125
|
+
if (!operation.ok)
|
|
126
|
+
return providerIssueError(base, operation.error, issueIdentity);
|
|
127
|
+
const issue = normalizeProviderIssue(operation.value, base.context.platform.repository, resourceIdentityNumber(issueIdentity));
|
|
128
|
+
return result('no-op', base.resolved.taskId, resourceIdentityNumber(issueIdentity), {
|
|
129
|
+
platform: base.context.platform,
|
|
130
|
+
capabilities: base.context.capabilities,
|
|
131
|
+
resource: { kind: 'issue', number: resourceIdentityNumber(issueIdentity), identity: issueIdentity },
|
|
132
|
+
issue,
|
|
133
|
+
error: null
|
|
134
|
+
});
|
|
42
135
|
}
|
|
43
|
-
function normalizeIssue(remote, repository) {
|
|
44
|
-
|
|
136
|
+
function normalizeIssue(remote, repository, fallbackNumber) {
|
|
137
|
+
const number = Number.isInteger(remote.number) && Number(remote.number) > 0
|
|
138
|
+
? Number(remote.number)
|
|
139
|
+
: fallbackNumber;
|
|
140
|
+
if (!number || !Number.isSafeInteger(number) || remote.pull_request)
|
|
45
141
|
return null;
|
|
46
142
|
return {
|
|
47
143
|
repository,
|
|
48
|
-
number
|
|
144
|
+
number,
|
|
49
145
|
databaseId: Number.isInteger(remote.id) ? Number(remote.id) : null,
|
|
50
|
-
nodeId: remote.node_id
|
|
51
|
-
url: remote.html_url || `https://github.com/${repository}/issues/${
|
|
52
|
-
state: remote.state === 'closed' ? 'closed' : 'open',
|
|
146
|
+
nodeId: remote.node_id || `issue-${number}`,
|
|
147
|
+
url: remote.html_url || `https://github.com/${repository}/issues/${number}`,
|
|
148
|
+
state: String(remote.state || '').toLowerCase() === 'closed' ? 'closed' : 'open',
|
|
53
149
|
title: remote.title || '',
|
|
54
150
|
body: remote.body || '',
|
|
55
151
|
labels: (remote.labels || []).map((label) => typeof label === 'string' ? label : label.name || '').filter(Boolean).sort(),
|
|
56
152
|
assignees: (remote.assignees || []).map((assignee) => assignee.login || '').filter(Boolean).sort(),
|
|
57
153
|
milestone: remote.milestone?.title || null,
|
|
58
|
-
issueType: null,
|
|
154
|
+
issueType: remote.type?.name || null,
|
|
59
155
|
fields: {}
|
|
60
156
|
};
|
|
61
157
|
}
|
|
@@ -93,15 +189,17 @@ function normalizeIssueTypes(value) {
|
|
|
93
189
|
}
|
|
94
190
|
function normalizeCurrentFields(value) {
|
|
95
191
|
const issue = value?.data?.repository?.issue;
|
|
96
|
-
const type = issue?.issueType?.
|
|
97
|
-
id: issue.issueType.id
|
|
192
|
+
const type = issue?.issueType?.name ? {
|
|
193
|
+
id: issue.issueType.id || issue.issueType.name,
|
|
194
|
+
name: issue.issueType.name,
|
|
195
|
+
fields: normalizeFieldSchemas(issue.issueType.pinnedFields)
|
|
98
196
|
} : null;
|
|
99
197
|
const values = (issue?.issueFieldValues?.nodes || []).flatMap((raw) => {
|
|
100
198
|
const item = raw;
|
|
101
199
|
const kind = fieldKind(item);
|
|
102
200
|
const value = kind === 'single-select' ? item.name : item.value;
|
|
103
|
-
return item.field?.
|
|
104
|
-
? [{ id: item.field.id, name: item.field.name, kind, value: value ?? null }]
|
|
201
|
+
return item.field?.name && kind && value !== undefined
|
|
202
|
+
? [{ id: item.field.id || item.field.name, name: item.field.name, kind, value: value ?? null }]
|
|
105
203
|
: [];
|
|
106
204
|
});
|
|
107
205
|
return { issueId: issue?.id || null, type, values };
|
|
@@ -124,7 +222,7 @@ function inspectGitHubIssue(client, repository, issue, cwd) {
|
|
|
124
222
|
const fetched = fetchIssue(client, repository, issue, cwd);
|
|
125
223
|
if (!fetched.ok)
|
|
126
224
|
return fetched;
|
|
127
|
-
const snapshot = normalizeIssue(fetched.value, repository);
|
|
225
|
+
const snapshot = normalizeIssue(fetched.value, repository, issue);
|
|
128
226
|
return snapshot
|
|
129
227
|
? { ok: true, value: snapshot }
|
|
130
228
|
: { ok: false, error: { code: 'ISSUE_IDENTITY_INVALID', message: 'Remote resource is not a valid Issue', retryable: false } };
|
|
@@ -144,33 +242,16 @@ function inspectGitHubIssueMetadata(client, repository, issue, cwd) {
|
|
|
144
242
|
}
|
|
145
243
|
};
|
|
146
244
|
}
|
|
147
|
-
function inspectPlatformIssue(taskRef, options = {}) {
|
|
148
|
-
const base = resolvedContext(taskRef, options);
|
|
245
|
+
async function inspectPlatformIssue(taskRef, options = {}) {
|
|
246
|
+
const base = await resolvedContext(taskRef, options);
|
|
149
247
|
if (!base.ok)
|
|
150
248
|
return base.output;
|
|
151
|
-
if (!base.
|
|
249
|
+
if (!base.issueIdentity)
|
|
152
250
|
return result('no-op', base.resolved.taskId, null, {
|
|
153
251
|
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
154
|
-
error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid
|
|
155
|
-
});
|
|
156
|
-
const fetched = inspectGitHubIssue(base.client, base.context.platform.repository, base.issueNumber, base.resolved.repoRoot);
|
|
157
|
-
if (!fetched.ok)
|
|
158
|
-
return result(fetched.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, {
|
|
159
|
-
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
160
|
-
resource: { kind: 'issue', number: base.issueNumber }, error: fetched.error
|
|
252
|
+
error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid platform issue identity', retryable: false }
|
|
161
253
|
});
|
|
162
|
-
|
|
163
|
-
if (base.context.capabilities.push) {
|
|
164
|
-
const graph = graphState(base.client, base.context.platform.repository, base.issueNumber, base.resolved.repoRoot);
|
|
165
|
-
if (graph) {
|
|
166
|
-
issue.issueType = graph.type?.name || null;
|
|
167
|
-
issue.fields = Object.fromEntries(graph.values.map((field) => [field.name, field.value]));
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
return result('no-op', base.resolved.taskId, base.issueNumber, {
|
|
171
|
-
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
172
|
-
resource: { kind: 'issue', number: base.issueNumber }, issue, error: null
|
|
173
|
-
});
|
|
254
|
+
return inspectExternalIssue(base, base.issueIdentity);
|
|
174
255
|
}
|
|
175
256
|
function taskTitle(content) {
|
|
176
257
|
return extractTitle(content).replace(/^(?:任务:|Task:\s*)/i, '');
|
|
@@ -239,114 +320,128 @@ function deterministicIssueBody(repoRoot, content, taskType) {
|
|
|
239
320
|
}
|
|
240
321
|
return buildDefaultBody(content);
|
|
241
322
|
}
|
|
242
|
-
function createPlatformIssue(taskRef, options) {
|
|
243
|
-
const base = resolvedContext(taskRef, options);
|
|
323
|
+
async function createPlatformIssue(taskRef, options) {
|
|
324
|
+
const base = await resolvedContext(taskRef, options);
|
|
244
325
|
if (!base.ok)
|
|
245
326
|
return base.output;
|
|
246
|
-
if (base.
|
|
327
|
+
if (base.issueIdentity)
|
|
247
328
|
return inspectPlatformIssue(taskRef, options);
|
|
248
329
|
const repository = base.context.platform.repository;
|
|
249
330
|
let repositoryLabels = [];
|
|
250
331
|
let milestones = [];
|
|
251
332
|
if (base.context.capabilities.triage) {
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
})).filter((item) => item.title && Number.isInteger(item.number));
|
|
333
|
+
const metadata = base.provider.issues?.describeRepository
|
|
334
|
+
? await base.provider.issues.describeRepository({ context: providerOperationContext(base.loadedContext) })
|
|
335
|
+
: unsupportedProviderOperation(base.provider, 'issues.describeRepository');
|
|
336
|
+
if (!metadata.ok)
|
|
337
|
+
return providerIssueError(base, metadata.error, null);
|
|
338
|
+
repositoryLabels = metadata.value.labels.map((label) => label.name);
|
|
339
|
+
milestones = metadata.value.milestones.map((milestone) => milestone.title);
|
|
260
340
|
}
|
|
261
341
|
const typeLabel = {
|
|
262
342
|
bug: 'type: bug', bugfix: 'type: bug', feature: 'type: feature', enhancement: 'type: enhancement',
|
|
263
343
|
refactor: 'type: enhancement', refactoring: 'type: enhancement', documentation: 'type: documentation',
|
|
264
344
|
docs: 'type: documentation', 'dependency-upgrade': 'type: dependency-upgrade', task: 'type: task', chore: 'type: task'
|
|
265
345
|
}[base.frontmatter.type || ''];
|
|
266
|
-
const explicitMilestone = base.frontmatter.milestone && milestones.some((item) => item
|
|
346
|
+
const explicitMilestone = base.frontmatter.milestone && milestones.some((item) => item === base.frontmatter.milestone)
|
|
267
347
|
? base.frontmatter.milestone
|
|
268
|
-
: chooseMilestone('initial', milestones
|
|
348
|
+
: chooseMilestone('initial', milestones);
|
|
269
349
|
const payload = {
|
|
270
350
|
title: conventionalTitle(base.content, base.frontmatter.type || 'task', base.resolved.repoRoot),
|
|
271
351
|
body: deterministicIssueBody(base.resolved.repoRoot, base.content, base.frontmatter.type || 'task'),
|
|
272
352
|
assignees: base.context.platform.currentUser ? [base.context.platform.currentUser] : [],
|
|
273
353
|
...(typeLabel && repositoryLabels.includes(typeLabel) ? { labels: [typeLabel] } : {}),
|
|
274
|
-
...(explicitMilestone ? { milestone:
|
|
354
|
+
...(explicitMilestone ? { milestone: explicitMilestone } : {})
|
|
275
355
|
};
|
|
276
356
|
if (options.dryRun)
|
|
277
357
|
return result('planned', base.resolved.taskId, null, {
|
|
278
358
|
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
279
359
|
operations: [{ name: 'issue:create', status: 'planned', reasonCode: null }], error: null
|
|
280
360
|
});
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
361
|
+
{
|
|
362
|
+
const created = base.provider.issues?.create
|
|
363
|
+
? await base.provider.issues.create({
|
|
364
|
+
context: providerOperationContext(base.loadedContext),
|
|
365
|
+
desired: {
|
|
366
|
+
title: payload.title,
|
|
367
|
+
body: payload.body,
|
|
368
|
+
labels: payload.labels || [],
|
|
369
|
+
assignees: payload.assignees,
|
|
370
|
+
milestone: typeof payload.milestone === 'string' ? payload.milestone : null,
|
|
371
|
+
fields: {}
|
|
372
|
+
},
|
|
373
|
+
mutation: { idempotencyKey: `issue:create:${base.resolved.taskId}` }
|
|
374
|
+
})
|
|
375
|
+
: unsupportedProviderOperation(base.provider, 'issues.create');
|
|
376
|
+
if (!created.ok)
|
|
377
|
+
return providerIssueError(base, created.error, null);
|
|
378
|
+
let createdIdentity;
|
|
379
|
+
try {
|
|
380
|
+
createdIdentity = providerResourceIdentity(base.provider, 'issue', created.value.remoteId);
|
|
381
|
+
}
|
|
382
|
+
catch (error) {
|
|
383
|
+
return result('failed', base.resolved.taskId, null, {
|
|
384
|
+
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
385
|
+
error: { code: 'ISSUE_CREATE_RESPONSE_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false }
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
const inspected = base.provider.issues?.inspect
|
|
389
|
+
? await base.provider.issues.inspect({
|
|
390
|
+
context: providerOperationContext(base.loadedContext),
|
|
391
|
+
target: createdIdentity
|
|
392
|
+
})
|
|
393
|
+
: unsupportedProviderOperation(base.provider, 'issues.inspect');
|
|
394
|
+
if (!inspected.ok)
|
|
395
|
+
return providerIssueError(base, inspected.error, createdIdentity);
|
|
396
|
+
const issueNumber = resourceIdentityNumber(inspected.value.identity || createdIdentity);
|
|
397
|
+
const written = writeTask({
|
|
398
|
+
taskRef: base.resolved.taskId, expectedState: 'active', mutations: [{ kind: 'frontmatter', set: { platform_issue_identity: serializeResourceIdentity(inspected.value.identity || createdIdentity) } }]
|
|
399
|
+
}, { repoRoot: base.resolved.repoRoot });
|
|
400
|
+
if (written.status === 'failed')
|
|
401
|
+
return result('failed', base.resolved.taskId, null, {
|
|
402
|
+
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
403
|
+
resource: { kind: 'issue', number: issueNumber, identity: inspected.value.identity || createdIdentity },
|
|
404
|
+
error: { code: 'ISSUE_CREATED_BIND_FAILED', message: written.error.message, retryable: false }
|
|
405
|
+
});
|
|
406
|
+
return result('applied', base.resolved.taskId, issueNumber, {
|
|
407
|
+
changed: true,
|
|
408
|
+
platform: base.context.platform,
|
|
409
|
+
capabilities: base.context.capabilities,
|
|
410
|
+
resource: { kind: 'issue', number: issueNumber, identity: inspected.value.identity || createdIdentity },
|
|
411
|
+
operations: [{ name: 'issue:create', status: 'applied', reasonCode: null }, { name: 'task:bind', status: 'applied', reasonCode: null }],
|
|
412
|
+
issue: normalizeProviderIssue(inspected.value, repository, issueNumber),
|
|
413
|
+
error: null
|
|
297
414
|
});
|
|
298
415
|
}
|
|
299
|
-
const written = writeTask({
|
|
300
|
-
taskRef: base.resolved.taskId, expectedState: 'active', mutations: [{ kind: 'frontmatter', set: { issue_number: issueNumber } }]
|
|
301
|
-
}, { repoRoot: base.resolved.repoRoot });
|
|
302
|
-
if (written.status === 'failed')
|
|
303
|
-
return result('failed', base.resolved.taskId, null, {
|
|
304
|
-
platform: base.context.platform, capabilities: base.context.capabilities,
|
|
305
|
-
resource: { kind: 'issue', number: issueNumber },
|
|
306
|
-
operations: [{ name: 'issue:create', status: 'applied', reasonCode: null }, { name: 'task:bind', status: 'failed', reasonCode: written.error.code }],
|
|
307
|
-
error: { code: 'ISSUE_CREATED_BIND_FAILED', message: `${created.value.html_url}: ${written.error.message}`, retryable: false }
|
|
308
|
-
});
|
|
309
|
-
return result('applied', base.resolved.taskId, issueNumber, {
|
|
310
|
-
changed: true, platform: base.context.platform, capabilities: base.context.capabilities,
|
|
311
|
-
resource: { kind: 'issue', number: issueNumber },
|
|
312
|
-
operations: [{ name: 'issue:create', status: 'applied', reasonCode: null }, { name: 'task:bind', status: 'applied', reasonCode: null }],
|
|
313
|
-
issue: normalizeIssue({
|
|
314
|
-
...created.value,
|
|
315
|
-
state: 'open',
|
|
316
|
-
title: payload.title,
|
|
317
|
-
body: payload.body,
|
|
318
|
-
labels: [],
|
|
319
|
-
assignees: payload.assignees.map((login) => ({ login })),
|
|
320
|
-
milestone: null
|
|
321
|
-
}, repository),
|
|
322
|
-
error: null
|
|
323
|
-
});
|
|
324
416
|
}
|
|
325
|
-
function bindPlatformIssue(taskRef, options) {
|
|
326
|
-
const base = resolvedContext(taskRef, options);
|
|
417
|
+
async function bindPlatformIssue(taskRef, options) {
|
|
418
|
+
const base = await resolvedContext(taskRef, options);
|
|
327
419
|
if (!base.ok)
|
|
328
420
|
return base.output;
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
421
|
+
let identity;
|
|
422
|
+
try {
|
|
423
|
+
identity = providerResourceToken(base.provider, 'issue', String(options.issue));
|
|
424
|
+
}
|
|
425
|
+
catch (error) {
|
|
426
|
+
return result('failed', base.resolved.taskId, base.issueNumber, { error: { code: 'PLATFORM_IDENTITY_TOKEN_INVALID', message: error instanceof Error ? error.message : String(error), retryable: false } });
|
|
427
|
+
}
|
|
428
|
+
if (base.issueIdentity && !resourceIdentityEquals(base.issueIdentity, identity))
|
|
334
429
|
return result('failed', base.resolved.taskId, base.issueNumber, {
|
|
335
|
-
error: { code: 'ISSUE_BIND_CONFLICT', message:
|
|
430
|
+
error: { code: 'ISSUE_BIND_CONFLICT', message: 'Task is already bound to a different Issue identity', retryable: false }
|
|
336
431
|
});
|
|
337
|
-
const fetched =
|
|
432
|
+
const fetched = base.provider.issues?.inspect
|
|
433
|
+
? await base.provider.issues.inspect({ context: providerOperationContext(base.loadedContext), target: identity })
|
|
434
|
+
: unsupportedProviderOperation(base.provider, 'issues.inspect');
|
|
338
435
|
if (!fetched.ok)
|
|
339
|
-
return
|
|
340
|
-
const issue =
|
|
341
|
-
if (
|
|
342
|
-
return result('
|
|
343
|
-
|
|
344
|
-
return result('no-op', base.resolved.taskId, options.issue, { issue, error: null });
|
|
345
|
-
const written = writeTask({ taskRef: base.resolved.taskId, expectedState: 'active', dryRun: options.dryRun, mutations: [{ kind: 'frontmatter', set: { issue_number: options.issue } }] }, { repoRoot: base.resolved.repoRoot });
|
|
436
|
+
return providerIssueError(base, fetched.error, identity);
|
|
437
|
+
const issue = normalizeProviderIssue(fetched.value, base.context.platform.repository, resourceIdentityNumber(identity));
|
|
438
|
+
if (base.issueIdentity && resourceIdentityEquals(base.issueIdentity, identity))
|
|
439
|
+
return result('no-op', base.resolved.taskId, resourceIdentityNumber(identity), { issue, error: null });
|
|
440
|
+
const written = writeTask({ taskRef: base.resolved.taskId, expectedState: 'active', dryRun: options.dryRun, mutations: [{ kind: 'frontmatter', set: { platform_issue_identity: serializeResourceIdentity(identity) } }] }, { repoRoot: base.resolved.repoRoot });
|
|
346
441
|
if (written.status === 'failed')
|
|
347
442
|
return result('failed', base.resolved.taskId, null, { issue, error: { code: written.error.code, message: written.error.message, retryable: false } });
|
|
348
|
-
return result(options.dryRun ? 'planned' : 'applied', base.resolved.taskId, options.dryRun ? null :
|
|
349
|
-
changed: !options.dryRun, issue, operations: [{ name: 'task:bind', status: options.dryRun ? 'planned' : 'applied', reasonCode: null }], error: null
|
|
443
|
+
return result(options.dryRun ? 'planned' : 'applied', base.resolved.taskId, options.dryRun ? null : resourceIdentityNumber(identity), {
|
|
444
|
+
changed: !options.dryRun, issue, resource: { kind: 'issue', number: resourceIdentityNumber(identity), identity }, operations: [{ name: 'task:bind', status: options.dryRun ? 'planned' : 'applied', reasonCode: null }], error: null
|
|
350
445
|
});
|
|
351
446
|
}
|
|
352
447
|
function requirementsFromTask(content) {
|
|
@@ -398,20 +493,6 @@ function applyRestOperations(snapshot, operations) {
|
|
|
398
493
|
for (const operation of operations) {
|
|
399
494
|
if (operation.status !== 'planned')
|
|
400
495
|
continue;
|
|
401
|
-
if (operation.name === 'labels:status') {
|
|
402
|
-
labels = [
|
|
403
|
-
...labels.filter((label) => !label.startsWith('status:')),
|
|
404
|
-
...operation.value.filter((label) => label.startsWith('status:'))
|
|
405
|
-
];
|
|
406
|
-
labelsChanged = true;
|
|
407
|
-
}
|
|
408
|
-
if (operation.name === 'labels:in') {
|
|
409
|
-
labels = [
|
|
410
|
-
...labels.filter((label) => !label.startsWith('in:')),
|
|
411
|
-
...operation.value.filter((label) => label.startsWith('in:'))
|
|
412
|
-
];
|
|
413
|
-
labelsChanged = true;
|
|
414
|
-
}
|
|
415
496
|
if (operation.name === 'assignees')
|
|
416
497
|
payload.assignees = operation.value;
|
|
417
498
|
if (operation.name === 'requirements')
|
|
@@ -420,11 +501,31 @@ function applyRestOperations(snapshot, operations) {
|
|
|
420
501
|
payload.state = operation.value;
|
|
421
502
|
if (operation.name === 'milestone')
|
|
422
503
|
payload.milestone = operation.value;
|
|
504
|
+
if (operation.name === 'labels:status') {
|
|
505
|
+
labels = applyLabelDeltaToSnapshot(labels, operation.value, 'status:');
|
|
506
|
+
labelsChanged = true;
|
|
507
|
+
}
|
|
508
|
+
if (operation.name === 'labels:in') {
|
|
509
|
+
labels = applyLabelDeltaToSnapshot(labels, operation.value, 'in:');
|
|
510
|
+
labelsChanged = true;
|
|
511
|
+
}
|
|
512
|
+
if (operation.name === 'issue-type')
|
|
513
|
+
payload.issueType = operation.value;
|
|
514
|
+
if (operation.name === 'fields')
|
|
515
|
+
payload.fields = operation.value;
|
|
423
516
|
}
|
|
424
517
|
if (labelsChanged)
|
|
425
|
-
payload.labels =
|
|
518
|
+
payload.labels = labels;
|
|
426
519
|
return payload;
|
|
427
520
|
}
|
|
521
|
+
function applyLabelDeltaToSnapshot(current, target, prefix) {
|
|
522
|
+
const delta = labelDelta(current, target, prefix);
|
|
523
|
+
const targetLabels = [...new Set(target.filter((label) => label.startsWith(prefix)))];
|
|
524
|
+
return [...new Set([
|
|
525
|
+
...current.filter((label) => !delta.remove.includes(label)),
|
|
526
|
+
...targetLabels
|
|
527
|
+
])].sort();
|
|
528
|
+
}
|
|
428
529
|
function desiredFieldValues(frontmatter, fields) {
|
|
429
530
|
const mapping = [
|
|
430
531
|
['priority', 'Priority'], ['effort', 'Effort'], ['start_date', 'Start date'], ['target_date', 'Target date']
|
|
@@ -512,32 +613,31 @@ function executeGraphOperation(client, cwd, issueId, operation) {
|
|
|
512
613
|
cwd, method: 'POST', input: JSON.stringify({ query, variables: { issueId, issueFields: operation.value } })
|
|
513
614
|
});
|
|
514
615
|
}
|
|
515
|
-
function syncPlatformIssue(taskRef, options) {
|
|
516
|
-
const base = resolvedContext(taskRef, options);
|
|
616
|
+
async function syncPlatformIssue(taskRef, options) {
|
|
617
|
+
const base = await resolvedContext(taskRef, options);
|
|
517
618
|
if (!base.ok)
|
|
518
619
|
return base.output;
|
|
519
|
-
if (!base.
|
|
520
|
-
return result('failed', base.resolved.taskId, null, { error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid
|
|
620
|
+
if (!base.issueIdentity)
|
|
621
|
+
return result('failed', base.resolved.taskId, null, { error: { code: 'ISSUE_NOT_LINKED', message: 'Task has no valid platform issue identity', retryable: false } });
|
|
521
622
|
const repository = base.context.platform.repository;
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
623
|
+
const inspected = base.provider.issues?.inspect
|
|
624
|
+
? await base.provider.issues.inspect({ context: providerOperationContext(base.loadedContext), target: base.issueIdentity })
|
|
625
|
+
: unsupportedProviderOperation(base.provider, 'issues.inspect');
|
|
626
|
+
if (!inspected.ok)
|
|
627
|
+
return providerIssueError(base, inspected.error, base.issueIdentity);
|
|
628
|
+
const snapshot = normalizeProviderIssue(inspected.value, repository, resourceIdentityNumber(base.issueIdentity));
|
|
629
|
+
let providerMetadata = null;
|
|
528
630
|
let repositoryLabels = [];
|
|
529
|
-
if (options.status !== undefined || options.inLabels !== undefined) {
|
|
530
|
-
const labels = base.client.json(['api', '--paginate', '--slurp', `repos/${repository}/labels?per_page=100`], { cwd: base.resolved.repoRoot });
|
|
531
|
-
if (!labels.ok)
|
|
532
|
-
return result(labels.error.retryable ? 'blocked' : 'failed', base.resolved.taskId, base.issueNumber, { issue: snapshot, error: labels.error });
|
|
533
|
-
repositoryLabels = listNames(labels.value).flatMap((name) => name);
|
|
534
|
-
}
|
|
535
631
|
let milestones = [];
|
|
536
|
-
if (options.milestone !== undefined) {
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
632
|
+
if (base.context.capabilities.triage && (options.status !== undefined || options.inLabels !== undefined || options.milestone !== undefined || options.issueType || options.fields)) {
|
|
633
|
+
const metadata = base.provider.issues?.describeRepository
|
|
634
|
+
? await base.provider.issues.describeRepository({ context: providerOperationContext(base.loadedContext) })
|
|
635
|
+
: unsupportedProviderOperation(base.provider, 'issues.describeRepository');
|
|
636
|
+
if (!metadata.ok)
|
|
637
|
+
return providerIssueError(base, metadata.error, base.issueIdentity);
|
|
638
|
+
providerMetadata = metadata.value;
|
|
639
|
+
repositoryLabels = metadata.value.labels.map((label) => label.name);
|
|
640
|
+
milestones = metadata.value.milestones.map((milestone) => milestone.title);
|
|
541
641
|
}
|
|
542
642
|
const desired = {
|
|
543
643
|
status: options.status,
|
|
@@ -550,7 +650,7 @@ function syncPlatformIssue(taskRef, options) {
|
|
|
550
650
|
snapshot: options.milestone === 'specific'
|
|
551
651
|
? { ...snapshot, milestone: milestoneBasis(base.resolved.repoRoot, snapshot.milestone) }
|
|
552
652
|
: snapshot,
|
|
553
|
-
desired, repositoryLabels: new Set(repositoryLabels), milestones
|
|
653
|
+
desired, repositoryLabels: new Set(repositoryLabels), milestones,
|
|
554
654
|
currentUser: base.context.platform.currentUser,
|
|
555
655
|
requirementAnchors: requirementSectionAnchors(base.resolved.repoRoot, base.frontmatter.type || 'task'),
|
|
556
656
|
capabilities: base.context.capabilities
|
|
@@ -564,21 +664,54 @@ function syncPlatformIssue(taskRef, options) {
|
|
|
564
664
|
: { name: 'labels:in', status: 'skipped', reasonCode: 'TRIAGE_REQUIRED' });
|
|
565
665
|
}
|
|
566
666
|
if (options.inLabels === 'from-diff') {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
plan.operations.push(labels.join('\0') === snapshot.labels.join('\0')
|
|
573
|
-
? { name: 'labels:in', status: 'no-op', reasonCode: null }
|
|
574
|
-
: { name: 'labels:in', status: 'planned', reasonCode: null, value: labels });
|
|
667
|
+
const taskBase = typeof base.frontmatter.delivery_base_ref === 'string'
|
|
668
|
+
? base.frontmatter.delivery_base_ref.trim()
|
|
669
|
+
: '';
|
|
670
|
+
if (!taskBase) {
|
|
671
|
+
plan.operations.push({ name: 'labels:in', status: 'failed', reasonCode: 'IN_LABEL_SYNC_BASE_MISSING' });
|
|
575
672
|
}
|
|
576
|
-
|
|
577
|
-
plan.operations.push({ name: 'labels:in', status: '
|
|
673
|
+
else if (options.base && options.base !== taskBase) {
|
|
674
|
+
plan.operations.push({ name: 'labels:in', status: 'failed', reasonCode: 'IN_LABEL_SYNC_BASE_MISMATCH' });
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
try {
|
|
678
|
+
const changed = execFileSync('git', ['diff', `${taskBase}...HEAD`, '--name-only'], {
|
|
679
|
+
cwd: base.resolved.repoRoot, encoding: 'utf8'
|
|
680
|
+
}).trim().split(/\r?\n/).filter(Boolean);
|
|
681
|
+
const config = JSON.parse(fs.readFileSync(`${base.resolved.repoRoot}/.agents/.airc.json`, 'utf8'));
|
|
682
|
+
const mapping = validateInLabelMapping(config.labels?.in);
|
|
683
|
+
if (!mapping.ok) {
|
|
684
|
+
plan.operations.push({ name: 'labels:in', status: 'failed', reasonCode: mapping.error.code });
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
const planned = planInLabelUpdate({
|
|
688
|
+
changedFiles: changed,
|
|
689
|
+
currentLabels: snapshot.labels,
|
|
690
|
+
mapping: mapping.value,
|
|
691
|
+
repositoryLabels: new Set(repositoryLabels)
|
|
692
|
+
});
|
|
693
|
+
if (planned.error) {
|
|
694
|
+
plan.operations.push({ name: 'labels:in', status: 'failed', reasonCode: planned.error.code });
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
plan.operations.push(planned.changed
|
|
698
|
+
? { name: 'labels:in', status: 'planned', reasonCode: null, value: planned.labels }
|
|
699
|
+
: { name: 'labels:in', status: 'no-op', reasonCode: null });
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
catch {
|
|
704
|
+
if (!plan.operations.some((operation) => operation.name === 'labels:in' && operation.status === 'failed')) {
|
|
705
|
+
plan.operations.push({ name: 'labels:in', status: 'failed', reasonCode: 'IN_LABEL_SYNC_EVIDENCE_UNAVAILABLE' });
|
|
706
|
+
}
|
|
707
|
+
}
|
|
578
708
|
}
|
|
579
709
|
}
|
|
580
|
-
|
|
581
|
-
|
|
710
|
+
if (options.issueType || options.fields) {
|
|
711
|
+
plan.operations.push(...(providerMetadata
|
|
712
|
+
? planProviderMetadata(inspected.value, providerMetadata, base.frontmatter, options, base.context.capabilities)
|
|
713
|
+
: [{ name: 'issue-type', status: 'skipped', reasonCode: 'ISSUE_TYPES_UNSUPPORTED' }]));
|
|
714
|
+
}
|
|
582
715
|
const failure = plan.operations.find((operation) => operation.status === 'failed');
|
|
583
716
|
if (failure)
|
|
584
717
|
return result('failed', base.resolved.taskId, base.issueNumber, { issue: snapshot, operations: plan.operations, error: { code: failure.reasonCode || 'ISSUE_SYNC_FAILED', message: `Operation ${failure.name} failed`, retryable: false } });
|
|
@@ -588,32 +721,45 @@ function syncPlatformIssue(taskRef, options) {
|
|
|
588
721
|
return result(planned.length ? 'planned' : skipped ? 'degraded' : 'no-op', base.resolved.taskId, base.issueNumber, { issue: snapshot, operations: plan.operations, error: null });
|
|
589
722
|
if (planned.length === 0)
|
|
590
723
|
return result(skipped ? 'degraded' : 'no-op', base.resolved.taskId, base.issueNumber, { issue: snapshot, operations: plan.operations, error: null });
|
|
591
|
-
const
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
724
|
+
const payload = applyRestOperations(snapshot, planned);
|
|
725
|
+
const updated = base.provider.issues?.update
|
|
726
|
+
? await base.provider.issues.update({ context: providerOperationContext(base.loadedContext), target: base.issueIdentity, currentLabels: snapshot.labels, patch: payload, mutation: { idempotencyKey: `issue:update:${base.resolved.taskId}` } })
|
|
727
|
+
: unsupportedProviderOperation(base.provider, 'issues.update');
|
|
728
|
+
if (!updated.ok)
|
|
729
|
+
return providerIssueError(base, updated.error, base.issueIdentity);
|
|
730
|
+
const labelOperations = planned.filter((operation) => operation.name === 'labels:status' || operation.name === 'labels:in');
|
|
731
|
+
let finalIssue = snapshot;
|
|
732
|
+
if (labelOperations.length > 0) {
|
|
733
|
+
const reread = base.provider.issues?.inspect
|
|
734
|
+
? await base.provider.issues.inspect({ context: providerOperationContext(base.loadedContext), target: base.issueIdentity })
|
|
735
|
+
: unsupportedProviderOperation(base.provider, 'issues.inspect');
|
|
736
|
+
if (!reread.ok)
|
|
737
|
+
return result('blocked', base.resolved.taskId, base.issueNumber, {
|
|
738
|
+
changed: updated.value.changed,
|
|
739
|
+
issue: snapshot,
|
|
740
|
+
operations: plan.operations,
|
|
741
|
+
error: { ...reread.error, code: 'IN_LABEL_SYNC_PARTIAL', message: `In-label synchronization is partial or unknown: ${reread.error.message}` }
|
|
742
|
+
});
|
|
743
|
+
finalIssue = normalizeProviderIssue(reread.value, repository, resourceIdentityNumber(base.issueIdentity));
|
|
744
|
+
let expectedLabels = [...snapshot.labels];
|
|
745
|
+
for (const operation of labelOperations) {
|
|
746
|
+
expectedLabels = applyLabelDeltaToSnapshot(expectedLabels, operation.value, operation.name === 'labels:status' ? 'status:' : 'in:');
|
|
747
|
+
}
|
|
748
|
+
const expected = expectedLabels.filter((label) => label.startsWith('status:') || label.startsWith('in:')).sort();
|
|
749
|
+
const actual = finalIssue.labels.filter((label) => label.startsWith('status:') || label.startsWith('in:')).sort();
|
|
750
|
+
if (expected.join('\0') !== actual.join('\0'))
|
|
751
|
+
return result('blocked', base.resolved.taskId, base.issueNumber, {
|
|
752
|
+
changed: updated.value.changed,
|
|
753
|
+
issue: finalIssue,
|
|
754
|
+
operations: plan.operations,
|
|
755
|
+
error: { code: 'IN_LABEL_SYNC_PARTIAL', message: 'Issue labels did not converge after update', retryable: true }
|
|
756
|
+
});
|
|
611
757
|
}
|
|
612
758
|
return result(skipped ? 'degraded' : 'applied', base.resolved.taskId, base.issueNumber, {
|
|
613
|
-
changed:
|
|
614
|
-
resource: { kind: 'issue', number: base.issueNumber }, issue:
|
|
759
|
+
changed: updated.value.changed, platform: base.context.platform, capabilities: base.context.capabilities,
|
|
760
|
+
resource: { kind: 'issue', number: base.issueNumber, identity: base.issueIdentity }, issue: finalIssue,
|
|
615
761
|
operations: plan.operations.map((operation) => operation.status === 'planned' ? { ...operation, status: 'applied' } : operation), error: null
|
|
616
762
|
});
|
|
617
763
|
}
|
|
618
|
-
export { bindPlatformIssue, createPlatformIssue, inspectGitHubIssue, inspectGitHubIssueMetadata, inspectPlatformIssue, normalizeIssue, requirementSectionAnchors, syncPlatformIssue };
|
|
764
|
+
export { bindPlatformIssue, createPlatformIssue, graphState, inspectGitHubIssue, inspectGitHubIssueMetadata, inspectPlatformIssue, normalizeIssue, requirementSectionAnchors, syncPlatformIssue };
|
|
619
765
|
//# sourceMappingURL=issues.js.map
|