@wardby/cli 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +22 -3
- package/README.md +11 -9
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +1 -0
- package/dist/cli.js +68 -10
- package/dist/coding/base-commit.d.ts +6 -0
- package/dist/coding/base-commit.js +12 -0
- package/dist/coding/protocol.d.ts +17 -1
- package/dist/coding/protocol.js +17 -6
- package/dist/coding/provider.d.ts +6 -1
- package/dist/coding/provider.js +16 -9
- package/dist/config/providers.d.ts +25 -0
- package/dist/config/providers.js +71 -0
- package/dist/core/attribution.d.ts +101 -0
- package/dist/core/attribution.js +208 -0
- package/dist/core/budget-groups.d.ts +17 -10
- package/dist/core/budget-groups.js +15 -12
- package/dist/core/coding-queue.d.ts +3 -0
- package/dist/core/coding-queue.js +6 -2
- package/dist/core/coding-service-status.d.ts +11 -0
- package/dist/core/coding-service-status.js +17 -0
- package/dist/core/cost-report.d.ts +88 -0
- package/dist/core/cost-report.js +248 -0
- package/dist/core/dispatch.d.ts +42 -2
- package/dist/core/dispatch.js +144 -26
- package/dist/core/engine-native.js +17 -4
- package/dist/core/glob.d.ts +10 -0
- package/dist/core/glob.js +33 -0
- package/dist/core/host-events.d.ts +24 -1
- package/dist/core/host-events.js +341 -1
- package/dist/core/host-status.d.ts +19 -2
- package/dist/core/host-status.js +47 -30
- package/dist/core/issue-bridge.d.ts +60 -0
- package/dist/core/issue-bridge.js +189 -0
- package/dist/core/issue-dedupe.d.ts +70 -0
- package/dist/core/issue-dedupe.js +255 -0
- package/dist/core/issue-events.d.ts +42 -0
- package/dist/core/issue-events.js +155 -0
- package/dist/core/issue-status.d.ts +29 -0
- package/dist/core/issue-status.js +241 -0
- package/dist/core/issue-tracker-tools.d.ts +64 -0
- package/dist/core/issue-tracker-tools.js +850 -0
- package/dist/core/model-usage.d.ts +10 -0
- package/dist/core/model-usage.js +24 -0
- package/dist/core/reconciler.d.ts +8 -4
- package/dist/core/reconciler.js +15 -4
- package/dist/core/review-host-tools.js +10 -3
- package/dist/core/run-pricing.d.ts +61 -0
- package/dist/core/run-pricing.js +56 -0
- package/dist/core/runner.d.ts +5 -2
- package/dist/core/runner.js +178 -27
- package/dist/core/scheduler.d.ts +4 -1
- package/dist/core/scheduler.js +3 -2
- package/dist/core/self-defects.d.ts +80 -0
- package/dist/core/self-defects.js +180 -0
- package/dist/core/tool-names.js +3 -0
- package/dist/core/webhooks.d.ts +9 -1
- package/dist/core/webhooks.js +19 -1
- package/dist/env.js +6 -1
- package/dist/generated/prisma/browser.d.ts +66 -0
- package/dist/generated/prisma/client.d.ts +66 -0
- package/dist/generated/prisma/commonInputTypes.d.ts +122 -52
- package/dist/generated/prisma/enums.d.ts +7 -0
- package/dist/generated/prisma/enums.js +6 -0
- package/dist/generated/prisma/internal/class.d.ts +99 -0
- package/dist/generated/prisma/internal/class.js +4 -4
- package/dist/generated/prisma/internal/prismaNamespace.d.ts +826 -1
- package/dist/generated/prisma/internal/prismaNamespace.js +135 -2
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.d.ts +142 -0
- package/dist/generated/prisma/internal/prismaNamespaceBrowser.js +135 -2
- package/dist/generated/prisma/models/Agent.d.ts +389 -1
- package/dist/generated/prisma/models/AgentIssueProject.d.ts +1838 -0
- package/dist/generated/prisma/models/AgentIssueProject.js +1 -0
- package/dist/generated/prisma/models/AgentRepository.d.ts +1 -1
- package/dist/generated/prisma/models/AuthUser.d.ts +1 -1
- package/dist/generated/prisma/models/CodingProxySession.d.ts +73 -1
- package/dist/generated/prisma/models/CodingRun.d.ts +130 -1
- package/dist/generated/prisma/models/CodingRunServiceStatus.d.ts +1404 -0
- package/dist/generated/prisma/models/CodingRunServiceStatus.js +1 -0
- package/dist/generated/prisma/models/IssueFingerprint.d.ts +1183 -0
- package/dist/generated/prisma/models/IssueFingerprint.js +1 -0
- package/dist/generated/prisma/models/IssuePullRequest.d.ts +1255 -0
- package/dist/generated/prisma/models/IssuePullRequest.js +1 -0
- package/dist/generated/prisma/models/ModelCatalogEntry.d.ts +1322 -0
- package/dist/generated/prisma/models/ModelCatalogEntry.js +1 -0
- package/dist/generated/prisma/models/Run.d.ts +933 -1
- package/dist/generated/prisma/models/RunAttribution.d.ts +1259 -0
- package/dist/generated/prisma/models/RunAttribution.js +1 -0
- package/dist/generated/prisma/models/RunIssueStatus.d.ts +1199 -0
- package/dist/generated/prisma/models/RunIssueStatus.js +1 -0
- package/dist/generated/prisma/models/RunModelUsage.d.ts +1316 -0
- package/dist/generated/prisma/models/RunModelUsage.js +1 -0
- package/dist/generated/prisma/models/WorkItem.d.ts +1408 -0
- package/dist/generated/prisma/models/WorkItem.js +1 -0
- package/dist/generated/prisma/models.d.ts +9 -0
- package/dist/help-index.json +355 -16
- package/dist/import/neutral-schema.d.ts +16 -16
- package/dist/knowledge/check.d.ts +13 -0
- package/dist/knowledge/check.js +69 -0
- package/dist/knowledge/cli.d.ts +14 -0
- package/dist/knowledge/cli.js +67 -0
- package/dist/knowledge/concept.d.ts +54 -0
- package/dist/knowledge/concept.js +78 -0
- package/dist/knowledge/note.d.ts +11 -0
- package/dist/knowledge/note.js +39 -0
- package/dist/knowledge/relevance.d.ts +11 -0
- package/dist/knowledge/relevance.js +14 -0
- package/dist/knowledge/span-hash.d.ts +3 -0
- package/dist/knowledge/span-hash.js +16 -0
- package/dist/mcp/auth/access.d.ts +4 -2
- package/dist/mcp/auth/ownership.d.ts +9 -9
- package/dist/mcp/auth/resource-server.d.ts +3 -1
- package/dist/mcp/auth/resource-server.js +18 -3
- package/dist/mcp/auth/self-hosted/credentials.d.ts +3 -3
- package/dist/mcp/auth/self-hosted/session.d.ts +5 -5
- package/dist/mcp/context.d.ts +3 -0
- package/dist/mcp/host-events/deliveries.d.ts +9 -0
- package/dist/mcp/host-events/deliveries.js +17 -0
- package/dist/mcp/host-events/github-ingress.d.ts +4 -2
- package/dist/mcp/host-events/github-ingress.js +4 -13
- package/dist/mcp/host-events/jira-ingress.d.ts +29 -0
- package/dist/mcp/host-events/jira-ingress.js +92 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +87 -9
- package/dist/mcp/server.js +5 -2
- package/dist/mcp/tools/agents.js +74 -3
- package/dist/mcp/tools/cost-report.d.ts +8 -0
- package/dist/mcp/tools/cost-report.js +60 -0
- package/dist/mcp/tools/issue-projects.d.ts +2 -0
- package/dist/mcp/tools/issue-projects.js +238 -0
- package/dist/mcp/tools/model-catalog.d.ts +22 -0
- package/dist/mcp/tools/model-catalog.js +423 -0
- package/dist/mcp/tools/repositories.js +2 -1
- package/dist/mcp/tools/tools.d.ts +2 -2
- package/dist/mcp/tools/trigger.js +33 -5
- package/dist/mcp/transport/streamable-http.d.ts +5 -0
- package/dist/mcp/transport/streamable-http.js +23 -1
- package/dist/mcp/webhooks/ingress.d.ts +2 -1
- package/dist/mcp/webhooks/ingress.js +9 -2
- package/dist/providers/auth/self-hosted.d.ts +8 -1
- package/dist/providers/auth/self-hosted.js +39 -2
- package/dist/providers/coding-proxy/memory-ledger.d.ts +1 -1
- package/dist/providers/coding-proxy/memory-ledger.js +10 -1
- package/dist/providers/coding-proxy/metering.d.ts +2 -1
- package/dist/providers/coding-proxy/metering.js +13 -2
- package/dist/providers/coding-proxy/prisma-ledger.js +59 -6
- package/dist/providers/coding-proxy/proxy.d.ts +12 -2
- package/dist/providers/coding-proxy/proxy.js +92 -30
- package/dist/providers/coding-proxy/types.d.ts +18 -1
- package/dist/providers/coding-proxy/types.js +12 -1
- package/dist/providers/engine/types.d.ts +19 -0
- package/dist/providers/executor/composition.js +9 -1
- package/dist/providers/executor/container.d.ts +30 -2
- package/dist/providers/executor/container.js +98 -17
- package/dist/providers/executor/dbos.d.ts +2 -0
- package/dist/providers/executor/dbos.js +7 -5
- package/dist/providers/executor/routing.d.ts +6 -0
- package/dist/providers/executor/routing.js +5 -0
- package/dist/providers/executor/types.d.ts +12 -0
- package/dist/providers/issue-tracker/adf.d.ts +31 -0
- package/dist/providers/issue-tracker/adf.js +181 -0
- package/dist/providers/issue-tracker/index.d.ts +5 -0
- package/dist/providers/issue-tracker/index.js +12 -0
- package/dist/providers/issue-tracker/jira-client.d.ts +41 -0
- package/dist/providers/issue-tracker/jira-client.js +151 -0
- package/dist/providers/issue-tracker/jira-events.d.ts +3 -0
- package/dist/providers/issue-tracker/jira-events.js +98 -0
- package/dist/providers/issue-tracker/jira.d.ts +116 -0
- package/dist/providers/issue-tracker/jira.js +502 -0
- package/dist/providers/issue-tracker/types.d.ts +269 -0
- package/dist/providers/issue-tracker/types.js +16 -0
- package/dist/providers/jobs/docker.d.ts +5 -1
- package/dist/providers/jobs/docker.js +61 -33
- package/dist/providers/jobs/kubernetes.d.ts +3 -0
- package/dist/providers/jobs/kubernetes.js +44 -4
- package/dist/providers/jobs/service-state.d.ts +22 -0
- package/dist/providers/jobs/service-state.js +17 -0
- package/dist/providers/llm/anthropic.d.ts +3 -3
- package/dist/providers/llm/anthropic.js +3 -9
- package/dist/providers/llm/bedrock.d.ts +3 -3
- package/dist/providers/llm/bedrock.js +3 -9
- package/dist/providers/llm/catalog-lookup.d.ts +10 -0
- package/dist/providers/llm/catalog-lookup.js +15 -0
- package/dist/providers/llm/catalog-shipped.d.ts +18 -0
- package/dist/providers/llm/catalog-shipped.js +197 -0
- package/dist/providers/llm/catalog-store.d.ts +58 -0
- package/dist/providers/llm/catalog-store.js +138 -0
- package/dist/providers/llm/catalog-types.d.ts +66 -0
- package/dist/providers/llm/catalog-types.js +64 -0
- package/dist/providers/llm/catalog.d.ts +61 -0
- package/dist/providers/llm/catalog.js +147 -0
- package/dist/providers/llm/claude-provider.d.ts +10 -14
- package/dist/providers/llm/claude-provider.js +11 -6
- package/dist/providers/llm/index.d.ts +9 -6
- package/dist/providers/llm/index.js +8 -5
- package/dist/providers/llm/openai.d.ts +14 -5
- package/dist/providers/llm/openai.js +24 -14
- package/dist/providers/llm/pricing-core.d.ts +5 -3
- package/dist/providers/llm/registration.js +8 -12
- package/dist/providers/llm/routing.d.ts +18 -17
- package/dist/providers/llm/routing.js +40 -24
- package/dist/providers/review-host/github-events.js +47 -1
- package/dist/providers/review-host/github.js +7 -6
- package/dist/providers/review-host/types.d.ts +25 -0
- package/dist/providers/vcs/git.js +2 -22
- package/dist/providers/vcs/github.d.ts +20 -0
- package/dist/providers/vcs/github.js +28 -2
- package/dist/providers/vcs/types.d.ts +6 -0
- package/dist/quickstart/index.d.ts +8 -0
- package/dist/quickstart/index.js +34 -34
- package/dist/serve.js +8 -2
- package/dist/viewer/api-schema.d.ts +2757 -0
- package/dist/viewer/api-schema.js +165 -0
- package/dist/viewer/build-schemas.d.ts +2 -0
- package/dist/viewer/build-schemas.js +18 -0
- package/dist/viewer/event-bus.d.ts +38 -0
- package/dist/viewer/event-bus.js +232 -0
- package/dist/viewer/graph.d.ts +40 -0
- package/dist/viewer/graph.js +243 -0
- package/dist/viewer/http.d.ts +30 -0
- package/dist/viewer/http.js +133 -0
- package/dist/viewer/run-detail.d.ts +4 -0
- package/dist/viewer/run-detail.js +61 -0
- package/dist/wardby-bin.js +5 -0
- package/docs/README.md +10 -0
- package/docs/agent-recipes.md +383 -0
- package/docs/code-review-agents.md +29 -2
- package/docs/coding-agent-setup.md +3 -0
- package/docs/coding-worker-isolation.md +39 -5
- package/docs/getting-started-gke.md +28 -11
- package/docs/getting-started-identity-provider.md +49 -38
- package/docs/getting-started.md +14 -0
- package/docs/jira-agents.md +649 -0
- package/docs/knowledge.md +387 -0
- package/docs/models.md +221 -0
- package/docs/security-deployment.md +19 -9
- package/docs/viewer-api.md +142 -0
- package/help/admin-viewer.md +39 -0
- package/help/agent-recipes.md +173 -0
- package/help/architecture-agent.md +189 -0
- package/help/builder-agent.md +80 -0
- package/help/code-review-agents.md +6 -0
- package/help/cost-attribution.md +67 -0
- package/help/creating-agents.md +22 -0
- package/help/deploy-gke.md +6 -0
- package/help/errors/model-unavailable.md +63 -0
- package/help/getting-started.md +1 -0
- package/help/github.md +18 -0
- package/help/identity-and-access.md +8 -3
- package/help/jira.md +135 -0
- package/help/knowledge.md +47 -0
- package/help/models.md +90 -0
- package/help/operating-agents.md +7 -1
- package/help/troubleshooting/budgets.md +6 -0
- package/package.json +5 -2
- package/prisma/migrations/20260930000000_jira_issue_projects/migration.sql +34 -0
- package/prisma/migrations/20261001000000_jira_phase2_allowlists/migration.sql +3 -0
- package/prisma/migrations/20261001010000_jira_link_types_allowlist/migration.sql +2 -0
- package/prisma/migrations/20261002000000_jira_coding_bridge/migration.sql +28 -0
- package/prisma/migrations/20261002010000_jira_issue_creation/migration.sql +25 -0
- package/prisma/migrations/20261003000000_issue_cost_attribution/migration.sql +56 -0
- package/prisma/migrations/20261003010000_coding_run_service_status/migration.sql +23 -0
- package/prisma/migrations/20261003020000_viewer_notify/migration.sql +54 -0
- package/prisma/migrations/20261003030000_viewer_notify_fixes/migration.sql +47 -0
- package/prisma/migrations/20261003040000_viewer_indexes/migration.sql +12 -0
- package/prisma/migrations/20261004000000_model_catalog/migration.sql +26 -0
- package/prisma/schema.prisma +258 -2
- package/dist/mcp/tools/models.d.ts +0 -8
- package/dist/mcp/tools/models.js +0 -15
- package/dist/providers/llm/pricing-anthropic.d.ts +0 -14
- package/dist/providers/llm/pricing-anthropic.js +0 -48
- package/dist/providers/llm/pricing-bedrock-claude.d.ts +0 -20
- package/dist/providers/llm/pricing-bedrock-claude.js +0 -46
- package/dist/providers/llm/pricing.d.ts +0 -30
- package/dist/providers/llm/pricing.js +0 -74
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// `_x_` is italic only when the underscores aren't inside a word, so
|
|
2
|
+
// snake_case identifiers (WARDBY_JIRA_API_TOKEN, my_var_name) stay literal.
|
|
3
|
+
// A bare https:// URL becomes a link too; it ends before whitespace or a
|
|
4
|
+
// bracket and drops trailing sentence punctuation (`see https://x/y.`).
|
|
5
|
+
const INLINE = /\*\*([^*]+)\*\*|`([^`]+)`|(?<![A-Za-z0-9_])_([^_\s][^_]*?)_(?![A-Za-z0-9_])|\[([^\]]+)\]\(([^)\s]+)\)|(https:\/\/[^\s<>()[\]"']*[^\s<>()[\]"'.,;:!?])/g;
|
|
6
|
+
const ISSUE_KEY = /^[A-Z][A-Z0-9_]+-[1-9][0-9]*$/;
|
|
7
|
+
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8
|
+
function inline(text, smart) {
|
|
9
|
+
const out = [];
|
|
10
|
+
const site = smart?.siteUrl.replace(/\/+$/, "");
|
|
11
|
+
const card = (key) => ({ type: "inlineCard", attrs: { url: `${site}/browse/${key}` } });
|
|
12
|
+
const keys = (smart?.projectKeys ?? []).filter((k) => /^[A-Z][A-Z0-9_]+$/.test(k));
|
|
13
|
+
const keyRe = keys.length
|
|
14
|
+
? new RegExp(`(?<![A-Za-z0-9_/.-])(?:${keys.map(escapeRe).join("|")})-[1-9][0-9]*(?![A-Za-z0-9_-])`, "g")
|
|
15
|
+
: null;
|
|
16
|
+
const plain = (t) => {
|
|
17
|
+
if (!t)
|
|
18
|
+
return;
|
|
19
|
+
if (!keyRe)
|
|
20
|
+
return void out.push({ type: "text", text: t });
|
|
21
|
+
let at = 0;
|
|
22
|
+
for (const k of t.matchAll(keyRe)) {
|
|
23
|
+
if (k.index > at)
|
|
24
|
+
out.push({ type: "text", text: t.slice(at, k.index) });
|
|
25
|
+
out.push(card(k[0]));
|
|
26
|
+
at = k.index + k[0].length;
|
|
27
|
+
}
|
|
28
|
+
if (at < t.length)
|
|
29
|
+
out.push({ type: "text", text: t.slice(at) });
|
|
30
|
+
};
|
|
31
|
+
const push = (t, marks) => {
|
|
32
|
+
if (!t)
|
|
33
|
+
return;
|
|
34
|
+
if (marks)
|
|
35
|
+
out.push({ type: "text", text: t, marks });
|
|
36
|
+
else
|
|
37
|
+
plain(t);
|
|
38
|
+
};
|
|
39
|
+
const siteIssueKey = (url) => {
|
|
40
|
+
if (!site || !url.startsWith(`${site}/browse/`))
|
|
41
|
+
return null;
|
|
42
|
+
const key = url.slice(site.length + "/browse/".length);
|
|
43
|
+
return ISSUE_KEY.test(key) ? key : null;
|
|
44
|
+
};
|
|
45
|
+
let last = 0;
|
|
46
|
+
for (const m of text.matchAll(INLINE)) {
|
|
47
|
+
push(text.slice(last, m.index));
|
|
48
|
+
if (m[1] !== undefined)
|
|
49
|
+
push(m[1], [{ type: "strong" }]);
|
|
50
|
+
else if (m[2] !== undefined)
|
|
51
|
+
push(m[2], [{ type: "code" }]);
|
|
52
|
+
else if (m[3] !== undefined)
|
|
53
|
+
push(m[3], [{ type: "em" }]);
|
|
54
|
+
else if (m[5]?.startsWith("https://"))
|
|
55
|
+
push(m[4], [{ type: "link", attrs: { href: m[5] } }]);
|
|
56
|
+
else if (m[6] !== undefined) {
|
|
57
|
+
const key = siteIssueKey(m[6]);
|
|
58
|
+
if (key)
|
|
59
|
+
out.push(card(key));
|
|
60
|
+
else
|
|
61
|
+
push(m[6], [{ type: "link", attrs: { href: m[6] } }]);
|
|
62
|
+
}
|
|
63
|
+
else
|
|
64
|
+
push(m[0]);
|
|
65
|
+
last = m.index + m[0].length;
|
|
66
|
+
}
|
|
67
|
+
push(text.slice(last));
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
export function markdownToAdf(markdown, smart) {
|
|
71
|
+
const paragraph = (text) => ({ type: "paragraph", content: inline(text, smart) });
|
|
72
|
+
const lines = markdown.replace(/\r\n/g, "\n").split("\n");
|
|
73
|
+
const content = [];
|
|
74
|
+
let i = 0;
|
|
75
|
+
while (i < lines.length) {
|
|
76
|
+
const line = lines[i];
|
|
77
|
+
if (line.trim() === "") {
|
|
78
|
+
i++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const fence = /^```(\w*)\s*$/.exec(line);
|
|
82
|
+
if (fence) {
|
|
83
|
+
const body = [];
|
|
84
|
+
i++;
|
|
85
|
+
while (i < lines.length && !/^```\s*$/.test(lines[i]))
|
|
86
|
+
body.push(lines[i++]);
|
|
87
|
+
i++;
|
|
88
|
+
content.push({
|
|
89
|
+
type: "codeBlock",
|
|
90
|
+
...(fence[1] ? { attrs: { language: fence[1] } } : {}),
|
|
91
|
+
content: body.length ? [{ type: "text", text: body.join("\n") }] : [],
|
|
92
|
+
});
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const heading = /^(#{1,3})\s+(.*)$/.exec(line);
|
|
96
|
+
if (heading) {
|
|
97
|
+
content.push({ type: "heading", attrs: { level: heading[1].length }, content: inline(heading[2], smart) });
|
|
98
|
+
i++;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const listOf = (re, type) => {
|
|
102
|
+
const items = [];
|
|
103
|
+
while (i < lines.length && re.test(lines[i])) {
|
|
104
|
+
items.push({ type: "listItem", content: [paragraph(lines[i].replace(re, ""))] });
|
|
105
|
+
i++;
|
|
106
|
+
}
|
|
107
|
+
content.push({ type, content: items });
|
|
108
|
+
};
|
|
109
|
+
if (/^\s*[-*]\s+/.test(line)) {
|
|
110
|
+
listOf(/^\s*[-*]\s+/, "bulletList");
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (/^\s*\d+\.\s+/.test(line)) {
|
|
114
|
+
listOf(/^\s*\d+\.\s+/, "orderedList");
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (/^>\s?/.test(line)) {
|
|
118
|
+
const quoted = [];
|
|
119
|
+
while (i < lines.length && /^>\s?/.test(lines[i]))
|
|
120
|
+
quoted.push(lines[i++].replace(/^>\s?/, ""));
|
|
121
|
+
content.push({ type: "blockquote", content: [paragraph(quoted.join(" "))] });
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const para = [];
|
|
125
|
+
while (i < lines.length && lines[i].trim() !== "" && !/^(```|#{1,3}\s|\s*[-*]\s|\s*\d+\.\s|>)/.test(lines[i])) {
|
|
126
|
+
para.push(lines[i++]);
|
|
127
|
+
}
|
|
128
|
+
content.push(paragraph(para.join(" ")));
|
|
129
|
+
}
|
|
130
|
+
return { type: "doc", version: 1, content };
|
|
131
|
+
}
|
|
132
|
+
const BLOCKS = new Set(["paragraph", "heading", "codeBlock", "blockquote", "listItem", "rule", "panel", "tableRow"]);
|
|
133
|
+
/**
|
|
134
|
+
* Webhook payloads carry rich text in the REST v2 shape: a wiki-markup
|
|
135
|
+
* string, where a mention is `[~accountid:<accountId>]`.
|
|
136
|
+
*/
|
|
137
|
+
const WIKI_MENTION = /\[~accountid:([^\]\s]+)\]/gi;
|
|
138
|
+
export function adfToText(node, maxChars = 20_000) {
|
|
139
|
+
if (typeof node === "string")
|
|
140
|
+
return cap(node.replace(WIKI_MENTION, "@user"), maxChars);
|
|
141
|
+
const parts = [];
|
|
142
|
+
const walk = (n) => {
|
|
143
|
+
if (!n || typeof n !== "object")
|
|
144
|
+
return;
|
|
145
|
+
const a = n;
|
|
146
|
+
if (a.type === "text" && typeof a.text === "string")
|
|
147
|
+
parts.push(a.text);
|
|
148
|
+
else if (a.type === "mention")
|
|
149
|
+
parts.push(typeof a.attrs?.text === "string" ? a.attrs.text : "@user");
|
|
150
|
+
else if (a.type === "hardBreak")
|
|
151
|
+
parts.push("\n");
|
|
152
|
+
for (const c of a.content ?? [])
|
|
153
|
+
walk(c);
|
|
154
|
+
if (BLOCKS.has(a.type))
|
|
155
|
+
parts.push("\n");
|
|
156
|
+
};
|
|
157
|
+
walk(node);
|
|
158
|
+
return cap(parts
|
|
159
|
+
.join("")
|
|
160
|
+
.replace(/\n{2,}/g, "\n")
|
|
161
|
+
.trim(), maxChars);
|
|
162
|
+
}
|
|
163
|
+
function cap(text, max) {
|
|
164
|
+
return text.length > max ? `${text.slice(0, max)}…` : text;
|
|
165
|
+
}
|
|
166
|
+
export function adfMentionIds(node) {
|
|
167
|
+
if (typeof node === "string")
|
|
168
|
+
return [...node.matchAll(WIKI_MENTION)].map((m) => m[1]);
|
|
169
|
+
const ids = [];
|
|
170
|
+
const walk = (n) => {
|
|
171
|
+
if (!n || typeof n !== "object")
|
|
172
|
+
return;
|
|
173
|
+
const a = n;
|
|
174
|
+
if (a.type === "mention" && typeof a.attrs?.id === "string")
|
|
175
|
+
ids.push(a.attrs.id);
|
|
176
|
+
for (const c of a.content ?? [])
|
|
177
|
+
walk(c);
|
|
178
|
+
};
|
|
179
|
+
walk(node);
|
|
180
|
+
return ids;
|
|
181
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IssueTrackerRegistry } from "./types.js";
|
|
2
|
+
export * from "./types.js";
|
|
3
|
+
export { JiraIssueTracker, agentFooter } from "./jira.js";
|
|
4
|
+
/** One tracker per configured provider; empty when Jira isn't configured (the jira_* tools are then never offered). */
|
|
5
|
+
export declare function buildIssueTrackers(env?: NodeJS.ProcessEnv): IssueTrackerRegistry;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { loadJiraConfig } from "../../config/providers.js";
|
|
2
|
+
import { JiraClient } from "./jira-client.js";
|
|
3
|
+
import { JiraIssueTracker } from "./jira.js";
|
|
4
|
+
export * from "./types.js";
|
|
5
|
+
export { JiraIssueTracker, agentFooter } from "./jira.js";
|
|
6
|
+
/** One tracker per configured provider; empty when Jira isn't configured (the jira_* tools are then never offered). */
|
|
7
|
+
export function buildIssueTrackers(env = process.env) {
|
|
8
|
+
const jira = loadJiraConfig(env);
|
|
9
|
+
return jira
|
|
10
|
+
? { jira: new JiraIssueTracker(new JiraClient(jira), jira.siteUrl, { epicLinkField: jira.epicLinkField }) }
|
|
11
|
+
: {};
|
|
12
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal Jira Cloud REST client. Holds the credential; callers pass paths.
|
|
3
|
+
* One retry on 429 honouring Retry-After (capped), per Atlassian's
|
|
4
|
+
* rate-limiting guidance. Response bodies are never logged.
|
|
5
|
+
*/
|
|
6
|
+
import type { JiraConfig } from "../../config/providers.js";
|
|
7
|
+
export interface RequestOptions {
|
|
8
|
+
/** Accept an empty 2xx body as `undefined` (void writes). */
|
|
9
|
+
allowEmpty?: boolean;
|
|
10
|
+
/** Per-attempt timeout; default 30 s. */
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
/** Retry once on 429 honouring Retry-After; default true. */
|
|
13
|
+
retryOn429?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare class JiraClient {
|
|
16
|
+
private readonly cfg;
|
|
17
|
+
private readonly authorization;
|
|
18
|
+
private readonly fetchImpl;
|
|
19
|
+
private readonly sleep;
|
|
20
|
+
/**
|
|
21
|
+
* Always sent explicitly: undici's default `Accept-Language: *` makes Jira answer in the site's default
|
|
22
|
+
* language rather than the account's own, which breaks matching of status and link type names.
|
|
23
|
+
*/
|
|
24
|
+
private language;
|
|
25
|
+
constructor(cfg: Pick<JiraConfig, "apiBaseUrl" | "auth">, opts?: {
|
|
26
|
+
fetch?: typeof fetch;
|
|
27
|
+
sleep?: (ms: number) => Promise<void>;
|
|
28
|
+
});
|
|
29
|
+
setLanguage(tag: string): void;
|
|
30
|
+
request<T>(method: "GET" | "POST" | "PUT", path: string, body?: unknown, opts?: RequestOptions): Promise<T>;
|
|
31
|
+
/**
|
|
32
|
+
* GET returning at most `maxBytes` of the raw body (attachment content). Sends `Range` (unless
|
|
33
|
+
* `range: false`) and caps client-side too, in case the server ignores it. Redirects are never followed with the credential:
|
|
34
|
+
* a 3xx is followed only to an https Atlassian host and without Authorization.
|
|
35
|
+
*/
|
|
36
|
+
requestBytes(path: string, opts: {
|
|
37
|
+
maxBytes: number;
|
|
38
|
+
timeoutMs?: number;
|
|
39
|
+
range?: boolean;
|
|
40
|
+
}): Promise<Uint8Array>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { IssueTrackerError } from "./types.js";
|
|
2
|
+
const MAX_RETRY_AFTER_MS = 10_000;
|
|
3
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
4
|
+
export class JiraClient {
|
|
5
|
+
cfg;
|
|
6
|
+
authorization;
|
|
7
|
+
fetchImpl;
|
|
8
|
+
sleep;
|
|
9
|
+
/**
|
|
10
|
+
* Always sent explicitly: undici's default `Accept-Language: *` makes Jira answer in the site's default
|
|
11
|
+
* language rather than the account's own, which breaks matching of status and link type names.
|
|
12
|
+
*/
|
|
13
|
+
language = "en-US";
|
|
14
|
+
constructor(cfg, opts = {}) {
|
|
15
|
+
this.cfg = cfg;
|
|
16
|
+
this.authorization = `Bearer ${cfg.auth.token}`;
|
|
17
|
+
this.fetchImpl = opts.fetch ?? fetch;
|
|
18
|
+
this.sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
19
|
+
}
|
|
20
|
+
setLanguage(tag) {
|
|
21
|
+
this.language = tag;
|
|
22
|
+
}
|
|
23
|
+
async request(method, path, body, opts = {}) {
|
|
24
|
+
for (let attempt = 0;; attempt++) {
|
|
25
|
+
let res;
|
|
26
|
+
try {
|
|
27
|
+
res = await this.fetchImpl(`${this.cfg.apiBaseUrl}${path}`, {
|
|
28
|
+
method,
|
|
29
|
+
headers: {
|
|
30
|
+
authorization: this.authorization,
|
|
31
|
+
accept: "application/json",
|
|
32
|
+
"accept-language": this.language,
|
|
33
|
+
...(body !== undefined ? { "content-type": "application/json" } : {}),
|
|
34
|
+
},
|
|
35
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
36
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
throw new IssueTrackerError("tracker_api_error", "Jira could not be reached.");
|
|
41
|
+
}
|
|
42
|
+
if (res.status === 429 && attempt === 0 && opts.retryOn429 !== false) {
|
|
43
|
+
const seconds = Number(res.headers.get("retry-after"));
|
|
44
|
+
await this.sleep(Math.min(Number.isFinite(seconds) && seconds > 0 ? seconds * 1000 : 2000, MAX_RETRY_AFTER_MS));
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (res.status === 204)
|
|
48
|
+
return undefined;
|
|
49
|
+
if (!res.ok)
|
|
50
|
+
throw statusError(res.status);
|
|
51
|
+
try {
|
|
52
|
+
// Void writes (issue links, property writes) may succeed with an empty body; JSON callers may not.
|
|
53
|
+
const text = await res.text();
|
|
54
|
+
if (text === "" && opts.allowEmpty)
|
|
55
|
+
return undefined;
|
|
56
|
+
return JSON.parse(text);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
throw new IssueTrackerError("tracker_invalid_response", "Jira returned a response that was not JSON.");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* GET returning at most `maxBytes` of the raw body (attachment content). Sends `Range` (unless
|
|
65
|
+
* `range: false`) and caps client-side too, in case the server ignores it. Redirects are never followed with the credential:
|
|
66
|
+
* a 3xx is followed only to an https Atlassian host and without Authorization.
|
|
67
|
+
*/
|
|
68
|
+
async requestBytes(path, opts) {
|
|
69
|
+
const headers = {
|
|
70
|
+
authorization: this.authorization,
|
|
71
|
+
accept: "*/*",
|
|
72
|
+
"accept-language": this.language,
|
|
73
|
+
...(opts.range === false ? {} : { range: `bytes=0-${Math.max(opts.maxBytes, 1) - 1}` }),
|
|
74
|
+
};
|
|
75
|
+
let url = `${this.cfg.apiBaseUrl}${path}`;
|
|
76
|
+
let res;
|
|
77
|
+
let retried = false;
|
|
78
|
+
try {
|
|
79
|
+
for (let hops = 0; hops <= 3;) {
|
|
80
|
+
res = await this.fetchImpl(url, {
|
|
81
|
+
method: "GET",
|
|
82
|
+
headers,
|
|
83
|
+
redirect: "manual",
|
|
84
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? DEFAULT_TIMEOUT_MS),
|
|
85
|
+
});
|
|
86
|
+
if (res.status === 429 && !retried) {
|
|
87
|
+
retried = true;
|
|
88
|
+
const seconds = Number(res.headers.get("retry-after"));
|
|
89
|
+
await this.sleep(Math.min(Number.isFinite(seconds) && seconds > 0 ? seconds * 1000 : 2000, MAX_RETRY_AFTER_MS));
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (res.status < 300 || res.status >= 400)
|
|
93
|
+
break;
|
|
94
|
+
hops++;
|
|
95
|
+
let next;
|
|
96
|
+
try {
|
|
97
|
+
next = new URL(res.headers.get("location") ?? "", url);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
throw new IssueTrackerError("tracker_invalid_response", "Jira returned an unusable download redirect.");
|
|
101
|
+
}
|
|
102
|
+
if (next.protocol !== "https:" || !isAtlassianHost(next.hostname))
|
|
103
|
+
throw new IssueTrackerError("tracker_invalid_response", "Jira redirected the download somewhere unexpected.");
|
|
104
|
+
url = next.toString();
|
|
105
|
+
// A pre-signed media URL: the Jira credential never goes to it.
|
|
106
|
+
delete headers.authorization;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
if (err instanceof IssueTrackerError)
|
|
111
|
+
throw err;
|
|
112
|
+
throw new IssueTrackerError("tracker_api_error", "Jira could not be reached.");
|
|
113
|
+
}
|
|
114
|
+
if (!res || (res.status >= 300 && res.status < 400))
|
|
115
|
+
throw new IssueTrackerError("tracker_api_error", "Jira's download redirected too many times.");
|
|
116
|
+
if (!res.ok)
|
|
117
|
+
throw statusError(res.status);
|
|
118
|
+
const chunks = [];
|
|
119
|
+
let total = 0;
|
|
120
|
+
try {
|
|
121
|
+
const reader = res.body?.getReader();
|
|
122
|
+
while (reader && total < opts.maxBytes) {
|
|
123
|
+
const { done, value } = await reader.read();
|
|
124
|
+
if (done)
|
|
125
|
+
break;
|
|
126
|
+
const piece = value.subarray(0, opts.maxBytes - total);
|
|
127
|
+
chunks.push(piece);
|
|
128
|
+
total += piece.length;
|
|
129
|
+
}
|
|
130
|
+
if (reader && total >= opts.maxBytes)
|
|
131
|
+
await reader.cancel().catch(() => undefined);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
throw new IssueTrackerError("tracker_api_error", "Jira's download was interrupted.");
|
|
135
|
+
}
|
|
136
|
+
return Buffer.concat(chunks);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Hosts a download redirect may point at (Atlassian media). */
|
|
140
|
+
const isAtlassianHost = (h) => /(^|\.)(atlassian\.com|atlassian\.net|atl-paas\.net)$/.test(h);
|
|
141
|
+
function statusError(status) {
|
|
142
|
+
if (status === 404)
|
|
143
|
+
return new IssueTrackerError("tracker_not_found", "Not found in Jira (or not visible to wardby's Jira account).");
|
|
144
|
+
if (status === 401 || status === 403)
|
|
145
|
+
return new IssueTrackerError("tracker_permission_denied", "wardby's Jira account is not allowed to do that.");
|
|
146
|
+
if (status === 429)
|
|
147
|
+
return new IssueTrackerError("tracker_rate_limited", "Jira is rate-limiting wardby; try again shortly.");
|
|
148
|
+
if (status === 400)
|
|
149
|
+
return new IssueTrackerError("tracker_invalid_request", "Jira rejected the request as invalid.");
|
|
150
|
+
return new IssueTrackerError("tracker_api_error", `Jira returned HTTP ${status}.`);
|
|
151
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jira Cloud webhook → host-neutral IssueEvent. Pure: no I/O. The signature
|
|
3
|
+
* is WebSub-style (X-Hub-Signature: <method>=<hex HMAC of the raw body>);
|
|
4
|
+
* only sha256 is accepted. Actors that aren't licensed humans (JSM
|
|
5
|
+
* customers, apps) and the bot itself never produce an event.
|
|
6
|
+
*/
|
|
7
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
8
|
+
import { adfMentionIds, adfToText } from "./adf.js";
|
|
9
|
+
import { ISSUE_KEY, projectOf } from "./types.js";
|
|
10
|
+
const MAX_TEXT = 20_000;
|
|
11
|
+
export function verifyJiraSignature(rawBody, header, secret) {
|
|
12
|
+
if (!header || !/^sha256=[0-9a-f]{64}$/i.test(header))
|
|
13
|
+
return false;
|
|
14
|
+
const expected = createHmac("sha256", secret).update(rawBody, "utf8").digest();
|
|
15
|
+
const presented = Buffer.from(header.slice("sha256=".length), "hex");
|
|
16
|
+
return presented.length === expected.length && timingSafeEqual(presented, expected);
|
|
17
|
+
}
|
|
18
|
+
const obj = (v) => (v && typeof v === "object" && !Array.isArray(v) ? v : null);
|
|
19
|
+
const str = (v) => (typeof v === "string" ? v : "");
|
|
20
|
+
function humanActor(user, bot) {
|
|
21
|
+
const u = obj(user);
|
|
22
|
+
if (!u || typeof u.accountId !== "string" || u.accountType !== "atlassian" || u.accountId === bot)
|
|
23
|
+
return null;
|
|
24
|
+
return { accountId: u.accountId, displayName: str(u.displayName) };
|
|
25
|
+
}
|
|
26
|
+
const words = (s) => str(s).split(/\s+/).filter(Boolean);
|
|
27
|
+
function getChangelogField(item, field) {
|
|
28
|
+
return item[field];
|
|
29
|
+
}
|
|
30
|
+
export function normalizeJiraEvent(payload, botAccountId) {
|
|
31
|
+
const p = obj(payload);
|
|
32
|
+
if (!p)
|
|
33
|
+
return null;
|
|
34
|
+
const event = str(p.webhookEvent);
|
|
35
|
+
const issue = obj(p.issue);
|
|
36
|
+
const issueKey = str(issue?.key);
|
|
37
|
+
if (!ISSUE_KEY.test(issueKey))
|
|
38
|
+
return null;
|
|
39
|
+
const fields = obj(issue?.fields);
|
|
40
|
+
const subject = fields
|
|
41
|
+
? { summary: str(fields.summary).slice(0, 256), description: adfToText(fields.description, MAX_TEXT) }
|
|
42
|
+
: undefined;
|
|
43
|
+
const base = {
|
|
44
|
+
provider: "jira",
|
|
45
|
+
projectKey: projectOf(issueKey),
|
|
46
|
+
issueKey,
|
|
47
|
+
addedLabels: [],
|
|
48
|
+
...(subject ? { subject } : {}),
|
|
49
|
+
};
|
|
50
|
+
if (event === "jira:issue_created") {
|
|
51
|
+
const actor = humanActor(p.user, botAccountId);
|
|
52
|
+
return actor ? { ...base, kinds: ["created"], actor } : null;
|
|
53
|
+
}
|
|
54
|
+
if (event === "jira:issue_updated") {
|
|
55
|
+
const actor = humanActor(p.user, botAccountId);
|
|
56
|
+
if (!actor)
|
|
57
|
+
return null;
|
|
58
|
+
const items = Array.isArray(obj(p.changelog)?.items) ? obj(p.changelog).items.map(obj) : [];
|
|
59
|
+
const kinds = [];
|
|
60
|
+
const out = { ...base, kinds, actor };
|
|
61
|
+
for (const item of items) {
|
|
62
|
+
if (!item)
|
|
63
|
+
continue;
|
|
64
|
+
if (item.field === "status" && str(getChangelogField(item, "toString"))) {
|
|
65
|
+
kinds.push("transitioned");
|
|
66
|
+
out.toStatus = str(getChangelogField(item, "toString"));
|
|
67
|
+
}
|
|
68
|
+
else if (item.field === "labels") {
|
|
69
|
+
const before = new Set(words(getChangelogField(item, "fromString")));
|
|
70
|
+
const added = words(getChangelogField(item, "toString")).filter((l) => !before.has(l));
|
|
71
|
+
if (added.length) {
|
|
72
|
+
kinds.push("labeled");
|
|
73
|
+
out.addedLabels = added;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else if (item.field === "assignee" && typeof item.to === "string" && item.to) {
|
|
77
|
+
kinds.push("assigned");
|
|
78
|
+
out.assigneeAccountId = item.to;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return kinds.length ? out : null;
|
|
82
|
+
}
|
|
83
|
+
if (event === "comment_created" || event === "comment_updated") {
|
|
84
|
+
const comment = obj(p.comment);
|
|
85
|
+
// An edit is attributed to the editor, never the original author:
|
|
86
|
+
// otherwise anyone who can edit others' comments could reuse a trusted
|
|
87
|
+
// user's mention to pass the allowlist.
|
|
88
|
+
const actor = event === "comment_updated"
|
|
89
|
+
? humanActor(comment?.updateAuthor, botAccountId)
|
|
90
|
+
: humanActor(comment?.author ?? p.user, botAccountId);
|
|
91
|
+
if (!actor || !comment || typeof comment.id !== "string")
|
|
92
|
+
return null;
|
|
93
|
+
if (!adfMentionIds(comment.body).includes(botAccountId))
|
|
94
|
+
return null;
|
|
95
|
+
return { ...base, kinds: ["mention"], actor, comment: { id: comment.id, body: adfToText(comment.body, MAX_TEXT) } };
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { JiraClient, RequestOptions } from "./jira-client.js";
|
|
2
|
+
import { type CreateIssueInput, type CreateMetaField, type CreateMetaIssueType, type IssueSearchResult, type IssueSnapshot, type IssueTracker, type IssueTrackerIdentity, type IssueView, type TrackerCallOptions } from "./types.js";
|
|
3
|
+
export declare const agentFooter: (agentId: string) => string;
|
|
4
|
+
/** Whether rendered comment text ends with this agent's footer (as adfToText renders it: no markdown underscores). */
|
|
5
|
+
export declare const hasAgentFooter: (text: string, agentId: string) => boolean;
|
|
6
|
+
/** Whether rendered comment text ends with wardby's run-status footer (`_wardby run <id>_` as adfToText renders it). */
|
|
7
|
+
export declare const isStatusComment: (text: string) => boolean;
|
|
8
|
+
/**
|
|
9
|
+
* One call's budget (TrackerCallOptions): each request gets what is left of `timeoutMs`, and awaits that
|
|
10
|
+
* are not requests (the cached identity) are abandoned at the deadline. Without `timeoutMs`, a no-op.
|
|
11
|
+
*/
|
|
12
|
+
declare function callBudget(opts?: TrackerCallOptions): {
|
|
13
|
+
request(extra?: RequestOptions): RequestOptions;
|
|
14
|
+
within<T>(p: Promise<T>): Promise<T>;
|
|
15
|
+
};
|
|
16
|
+
type CallBudget = ReturnType<typeof callBudget>;
|
|
17
|
+
export declare class JiraIssueTracker implements IssueTracker {
|
|
18
|
+
private readonly client;
|
|
19
|
+
private readonly siteUrl;
|
|
20
|
+
private readonly opts;
|
|
21
|
+
readonly provider: "jira";
|
|
22
|
+
private me;
|
|
23
|
+
constructor(client: JiraClient, siteUrl: string, opts?: {
|
|
24
|
+
epicLinkField?: string;
|
|
25
|
+
});
|
|
26
|
+
identity(): Promise<IssueTrackerIdentity>;
|
|
27
|
+
/** Refuses to act when the token belongs to a person; a failed identity() propagates and is not cached. */
|
|
28
|
+
private ready;
|
|
29
|
+
botAccountId(): Promise<string>;
|
|
30
|
+
/** Markdown to ADF, with this site's issue links (and the given projects' bare keys) as smart links. */
|
|
31
|
+
private toAdf;
|
|
32
|
+
issueUrl(key: string): string;
|
|
33
|
+
getIssue(key: string, opts: {
|
|
34
|
+
maxComments: number;
|
|
35
|
+
agentMarker: string;
|
|
36
|
+
} & TrackerCallOptions): Promise<IssueView>;
|
|
37
|
+
issueProject(key: string): Promise<string>;
|
|
38
|
+
snapshotIssue(key: string, opts?: {
|
|
39
|
+
timeoutMs?: number;
|
|
40
|
+
retryOn429?: boolean;
|
|
41
|
+
}): Promise<IssueSnapshot>;
|
|
42
|
+
search(jql: string, opts: {
|
|
43
|
+
maxResults: number;
|
|
44
|
+
timeoutMs?: number;
|
|
45
|
+
retryOn429?: boolean;
|
|
46
|
+
}): Promise<IssueSearchResult>;
|
|
47
|
+
matchesJql(key: string, jql: string, opts?: {
|
|
48
|
+
timeoutMs?: number;
|
|
49
|
+
retryOn429?: boolean;
|
|
50
|
+
}): Promise<boolean>;
|
|
51
|
+
comment(key: string, input: {
|
|
52
|
+
markdown: string;
|
|
53
|
+
visibilityRole?: string;
|
|
54
|
+
issueKeyProjects?: readonly string[];
|
|
55
|
+
}, opts?: TrackerCallOptions): Promise<{
|
|
56
|
+
id: string;
|
|
57
|
+
url: string;
|
|
58
|
+
}>;
|
|
59
|
+
editComment(key: string, commentId: string, input: {
|
|
60
|
+
markdown: string;
|
|
61
|
+
issueKeyProjects?: readonly string[];
|
|
62
|
+
}): Promise<void>;
|
|
63
|
+
readComment(key: string, commentId: string): Promise<{
|
|
64
|
+
authorId: string | null;
|
|
65
|
+
body: string;
|
|
66
|
+
} | null>;
|
|
67
|
+
transitions(key: string): Promise<Array<{
|
|
68
|
+
id: string;
|
|
69
|
+
name: string;
|
|
70
|
+
toStatus: string;
|
|
71
|
+
toCategory: string;
|
|
72
|
+
}>>;
|
|
73
|
+
transitionTo(key: string, toStatus: string): Promise<{
|
|
74
|
+
transitionId: string;
|
|
75
|
+
toStatus: string;
|
|
76
|
+
}>;
|
|
77
|
+
editableFields(key: string): Promise<string[]>;
|
|
78
|
+
editFields(key: string, fields: Record<string, unknown>): Promise<void>;
|
|
79
|
+
linkTypes(opts?: TrackerCallOptions): Promise<Array<{
|
|
80
|
+
name: string;
|
|
81
|
+
inward: string;
|
|
82
|
+
outward: string;
|
|
83
|
+
}>>;
|
|
84
|
+
linkIssues(input: {
|
|
85
|
+
type: string;
|
|
86
|
+
inwardKey: string;
|
|
87
|
+
outwardKey: string;
|
|
88
|
+
}, opts?: TrackerCallOptions): Promise<void>;
|
|
89
|
+
addRemoteLink(key: string, input: {
|
|
90
|
+
globalId: string;
|
|
91
|
+
url: string;
|
|
92
|
+
title: string;
|
|
93
|
+
status?: {
|
|
94
|
+
resolved: boolean;
|
|
95
|
+
};
|
|
96
|
+
}): Promise<void>;
|
|
97
|
+
getProperty(key: string, property: string): Promise<unknown>;
|
|
98
|
+
setProperty(key: string, property: string, value: unknown): Promise<void>;
|
|
99
|
+
/** Pages a createmeta endpoint (startAt/maxResults/total), capped. */
|
|
100
|
+
private pagedMeta;
|
|
101
|
+
createMeta(projectKey: string, budget?: CallBudget): Promise<{
|
|
102
|
+
issueTypes: CreateMetaIssueType[];
|
|
103
|
+
}>;
|
|
104
|
+
fieldMeta(projectKey: string, issueTypeId: string, budget?: CallBudget): Promise<CreateMetaField[]>;
|
|
105
|
+
createIssue(input: CreateIssueInput, opts?: TrackerCallOptions): Promise<{
|
|
106
|
+
key: string;
|
|
107
|
+
url: string;
|
|
108
|
+
}>;
|
|
109
|
+
readAttachmentText(id: string, maxBytes: number): Promise<{
|
|
110
|
+
filename: string;
|
|
111
|
+
mimeType: string;
|
|
112
|
+
text: string;
|
|
113
|
+
truncated: boolean;
|
|
114
|
+
}>;
|
|
115
|
+
}
|
|
116
|
+
export {};
|