@nuvlore/extension-bds 0.1.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/LICENSE +202 -0
- package/README.md +3 -0
- package/agents/bds-airflow-job-status-readonly.yaml +123 -0
- package/agents/bds-disk-investigator.yaml +112 -0
- package/agents/bds-inventory-readonly.yaml +60 -0
- package/agents/bds-oncall-reporter.yaml +113 -0
- package/agents/bds-prism-service-proxy-timeout-readonly.yaml +105 -0
- package/agents/bds-scylla-tenant-bucket-readonly.yaml +104 -0
- package/agents/bds-tenant-data-operation-log-readonly.yaml +98 -0
- package/agents/bds-yarn-app-health-readonly.yaml +114 -0
- package/agents/pharos-alert-investigator.yaml +81 -0
- package/package.json +60 -0
- package/skills/bds-airflow-job-status-investigation/SKILL.md +104 -0
- package/skills/bds-airflow-job-status-investigation/references/airflow-access-runbook.md +35 -0
- package/skills/bds-airflow-job-status-investigation/references/airflow-bas-dr-runbook.md +42 -0
- package/skills/bds-airflow-job-status-investigation/references/airflow-copy-and-migration-runbook.md +63 -0
- package/skills/bds-airflow-job-status-investigation/references/airflow-job-status-runbook.md +15 -0
- package/skills/bds-airflow-job-status-investigation/references/airflow-output-runbook.md +28 -0
- package/skills/bds-airflow-job-status-investigation/references/airflow-snapshot-runbook.md +38 -0
- package/skills/bds-disk-io-alert-investigation/SKILL.md +116 -0
- package/skills/bds-hadoop-review/SKILL.md +66 -0
- package/skills/bds-machine-inventory/SKILL.md +48 -0
- package/skills/bds-oncall-followup/SKILL.md +71 -0
- package/skills/bds-oncall-followup/references/oncall-followup-runbook.md +58 -0
- package/skills/bds-prism-service-proxy-timeout-investigation/SKILL.md +73 -0
- package/skills/bds-prism-service-proxy-timeout-investigation/references/service-proxy-timeout-runbook.md +48 -0
- package/skills/bds-scylla-tenant-bucket-investigation/SKILL.md +73 -0
- package/skills/bds-scylla-tenant-bucket-investigation/references/tenant-bucket-runbook.md +91 -0
- package/skills/bds-tenant-data-operation-log-investigation/SKILL.md +89 -0
- package/skills/bds-yarn-app-health-investigation/SKILL.md +77 -0
- package/skills/bds-yarn-app-health-investigation/references/yarn-app-health-runbook.md +53 -0
- package/skills/pharos-alert-investigation/SKILL.md +80 -0
- package/src/bds-atomic-log-tools.mjs +300 -0
- package/src/bds-team-membership-claude-output.mjs +36 -0
- package/src/bds-team-membership-quality.mjs +90 -0
- package/tools/bds_airflow_dag_log_read.mjs +24 -0
- package/tools/bds_airflow_job_status_command_pack.mjs +587 -0
- package/tools/bds_bas_log_read.mjs +23 -0
- package/tools/bds_bdm_httpd_log_read.mjs +24 -0
- package/tools/bds_chef_client_log_read.mjs +23 -0
- package/tools/bds_dashboard_links.mjs +178 -0
- package/tools/bds_dashboard_query_knowledge.mjs +258 -0
- package/tools/bds_dashboard_query_read.mjs +120 -0
- package/tools/bds_disk_io_alert_command_pack.mjs +287 -0
- package/tools/bds_environment_host_lookup.mjs +741 -0
- package/tools/bds_hdfs_datanode_log_read.mjs +23 -0
- package/tools/bds_hdfs_journalnode_log_read.mjs +23 -0
- package/tools/bds_hdfs_namenode_log_read.mjs +23 -0
- package/tools/bds_hdfs_path_knowledge.mjs +324 -0
- package/tools/bds_information_source_knowledge.mjs +356 -0
- package/tools/bds_kms_log_read.mjs +23 -0
- package/tools/bds_log_path_knowledge.mjs +267 -0
- package/tools/bds_machine_inventory.mjs +434 -0
- package/tools/bds_mapreduce_historyserver_log_read.mjs +23 -0
- package/tools/bds_migration_service_operation_command_pack.mjs +233 -0
- package/tools/bds_oncall_followup_payload_draft.mjs +285 -0
- package/tools/bds_pharos_query_pack.mjs +218 -0
- package/tools/bds_portal_operation_knowledge.mjs +212 -0
- package/tools/bds_prism_clone_status_links.mjs +129 -0
- package/tools/bds_prism_service_proxy_timeout_query_pack.mjs +224 -0
- package/tools/bds_public_cloud_pod_log_read.mjs +18 -0
- package/tools/bds_scylla_tenant_bucket_command_pack.mjs +323 -0
- package/tools/bds_service_proxy_log_read.mjs +23 -0
- package/tools/bds_spark_historyserver_log_read.mjs +32 -0
- package/tools/bds_spas_mt_log_read.mjs +23 -0
- package/tools/bds_tenant_data_operation_log_command_pack.mjs +321 -0
- package/tools/bds_yarn_app_health_command_pack.mjs +529 -0
- package/tools/bds_yarn_nodemanager_log_read.mjs +23 -0
- package/tools/bds_yarn_resourcemanager_log_read.mjs +23 -0
- package/tools/bds_zookeeper_log_read.mjs +23 -0
- package/tools/confluence_handoff_page_draft.mjs +173 -0
- package/vendor/dashboard/src/grafana-dashboard-query.mjs +61 -0
- package/vendor/extension-read-only-toolkit/LICENSE +202 -0
- package/vendor/extension-read-only-toolkit/README.md +29 -0
- package/vendor/extension-read-only-toolkit/package.json +46 -0
- package/vendor/extension-read-only-toolkit/src/devops-data-paths.mjs +222 -0
- package/vendor/extension-read-only-toolkit/src/devops-diagnostics.mjs +440 -0
- package/vendor/extension-read-only-toolkit/src/enterprise-api-read.mjs +180 -0
- package/vendor/extension-read-only-toolkit/src/read-only-command-pack.mjs +1 -0
- package/vendor/extension-read-only-toolkit/src/read-only-plan.mjs +45 -0
- package/vendor/extension-read-only-toolkit/src/read-only-shell-policy.d.mts +26 -0
- package/vendor/extension-read-only-toolkit/src/read-only-shell-policy.mjs +170 -0
- package/vendor/extension-workday-infra-access/LICENSE +202 -0
- package/vendor/extension-workday-infra-access/README.md +3 -0
- package/vendor/extension-workday-infra-access/agents/workday-infra-access-readonly.yaml +183 -0
- package/vendor/extension-workday-infra-access/package.json +49 -0
- package/vendor/extension-workday-infra-access/skills/workday-infra-access/SKILL.md +102 -0
- package/vendor/extension-workday-infra-access/skills/workday-infra-access/references/access-boundary-runbook.md +43 -0
- package/vendor/extension-workday-infra-access/skills/workday-infra-access/references/access-runbook.md +15 -0
- package/vendor/extension-workday-infra-access/skills/workday-infra-access/references/access-scylla-cloud-runbook.md +39 -0
- package/vendor/extension-workday-infra-access/skills/workday-infra-access/references/access-spc-argo-runbook.md +99 -0
- package/vendor/extension-workday-infra-access/src/workday-access-profiles.mjs +194 -0
- package/vendor/extension-workday-infra-access/tools/aws_cust_federated_readonly_command_pack.mjs +234 -0
- package/vendor/extension-workday-infra-access/tools/gcp_readonly_command_pack.mjs +167 -0
- package/vendor/extension-workday-infra-access/tools/public_cloud_access_status.mjs +48 -0
- package/vendor/extension-workday-infra-access/tools/workday_boundary_login.mjs +460 -0
- package/vendor/extension-workday-infra-access/tools/workday_private_cloud_sudo_readonly_command.mjs +386 -0
- package/vendor/extension-workday-infra-access/tools/workday_public_cloud_environment_map.mjs +189 -0
- package/vendor/extension-workday-infra-access/tools/workday_public_cloud_kubernetes_read.mjs +297 -0
- package/vendor/extension-workday-infra-access/tools/workday_public_cloud_spc_login.mjs +505 -0
- package/vendor/extension-workday-infra-access/tools/workday_spc_argo_readonly_command_pack.mjs +215 -0
- package/vendor/extension-workday-infra-access/workflows/public-cloud-readonly-investigation.js +99 -0
- package/workflows/bds-airflow-job-status-investigation.js +168 -0
- package/workflows/bds-disk-io-alert-investigation.js +173 -0
- package/workflows/bds-oncall-followup.js +112 -0
- package/workflows/bds-prism-service-proxy-timeout-investigation.js +69 -0
- package/workflows/bds-scylla-tenant-bucket-investigation.js +139 -0
- package/workflows/bds-tenant-data-operation-log-investigation.js +189 -0
- package/workflows/bds-yarn-app-health-investigation.js +217 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
export const description = "Create a atomic-helper BDS on-call report row and BDS Jira follow-up draft for BDS-owned on-call issues.";
|
|
2
|
+
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { defineReadOnlyTool, fail, ok } from "@nuvlore/extension-read-only-toolkit/devops-diagnostics";
|
|
5
|
+
|
|
6
|
+
const DEFAULT_ANCHOR_START = "2026/05/26";
|
|
7
|
+
const DEFAULT_PERIOD_DAYS = 14;
|
|
8
|
+
const DEFAULT_SPACE_KEY = "BD";
|
|
9
|
+
const DEFAULT_PARENT_PAGE_ID = "4259305001";
|
|
10
|
+
const DEFAULT_REPORT_PAGE_ID = "4432574596";
|
|
11
|
+
|
|
12
|
+
const issueSchema = z.object({
|
|
13
|
+
issue: z.string().trim().min(1).max(512),
|
|
14
|
+
issueDescription: z.string().trim().min(1).max(5000),
|
|
15
|
+
slackOrJira: z.array(z.string().trim().min(1).max(2048)).max(12).default([]),
|
|
16
|
+
rca: z.string().trim().max(8000).default(""),
|
|
17
|
+
impact: z.string().trim().max(5000).default(""),
|
|
18
|
+
followUpNeeded: z.boolean().default(false),
|
|
19
|
+
followUpJira: z.string().trim().max(128).default(""),
|
|
20
|
+
codePointer: z.string().trim().max(8000).default("")
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const inputSchema = {
|
|
24
|
+
currentDate: z.string().trim().regex(/^\d{4}-\d{2}-\d{2}$/u).optional(),
|
|
25
|
+
anchorStartDate: z.string().trim().regex(/^\d{4}\/\d{2}\/\d{2}$/u).default(DEFAULT_ANCHOR_START),
|
|
26
|
+
periodDays: z.number().int().positive().max(31).default(DEFAULT_PERIOD_DAYS),
|
|
27
|
+
spaceKey: z.string().trim().min(1).max(128).default(DEFAULT_SPACE_KEY),
|
|
28
|
+
parentPageId: z.string().trim().min(1).max(128).default(DEFAULT_PARENT_PAGE_ID),
|
|
29
|
+
reportPageId: z.string().trim().min(1).max(128).default(DEFAULT_REPORT_PAGE_ID),
|
|
30
|
+
issue: issueSchema,
|
|
31
|
+
format: z.enum(["json", "markdown"]).default("json")
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function parseSlashDate(value) {
|
|
35
|
+
const [year, month, day] = String(value).split("/").map(Number);
|
|
36
|
+
return new Date(Date.UTC(year, month - 1, day));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parseDashDate(value) {
|
|
40
|
+
const [year, month, day] = String(value).split("-").map(Number);
|
|
41
|
+
return new Date(Date.UTC(year, month - 1, day));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function addDays(date, days) {
|
|
45
|
+
const next = new Date(date.getTime());
|
|
46
|
+
next.setUTCDate(next.getUTCDate() + days);
|
|
47
|
+
return next;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function slashDate(date) {
|
|
51
|
+
const year = date.getUTCFullYear();
|
|
52
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
53
|
+
const day = String(date.getUTCDate()).padStart(2, "0");
|
|
54
|
+
return `${year}/${month}/${day}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function daysBetween(left, right) {
|
|
58
|
+
return Math.floor((right.getTime() - left.getTime()) / 86_400_000);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolveOncallRange({ currentDate, anchorStartDate, periodDays } = {}) {
|
|
62
|
+
const normalizedAnchorStartDate = anchorStartDate || DEFAULT_ANCHOR_START;
|
|
63
|
+
const normalizedPeriodDays = periodDays || DEFAULT_PERIOD_DAYS;
|
|
64
|
+
const anchor = parseSlashDate(normalizedAnchorStartDate);
|
|
65
|
+
const current = currentDate ? parseDashDate(currentDate) : new Date();
|
|
66
|
+
const periodsSinceAnchor = Math.floor(daysBetween(anchor, current) / normalizedPeriodDays);
|
|
67
|
+
const start = addDays(anchor, periodsSinceAnchor * normalizedPeriodDays);
|
|
68
|
+
const end = addDays(start, normalizedPeriodDays);
|
|
69
|
+
return {
|
|
70
|
+
startDate: slashDate(start),
|
|
71
|
+
endDate: slashDate(end),
|
|
72
|
+
title: `BDS On Call Handoff ${slashDate(start)}-${slashDate(end)}`
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function normalizeText(value) {
|
|
77
|
+
return typeof value === "string" ? value.trim() : "";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function normalizeIssue(input = {}) {
|
|
81
|
+
return {
|
|
82
|
+
issue: normalizeText(input.issue),
|
|
83
|
+
issueDescription: normalizeText(input.issueDescription),
|
|
84
|
+
slackOrJira: Array.isArray(input.slackOrJira) ? input.slackOrJira.filter((item) => normalizeText(item)) : [],
|
|
85
|
+
rca: normalizeText(input.rca),
|
|
86
|
+
impact: normalizeText(input.impact),
|
|
87
|
+
followUpNeeded: Boolean(input.followUpNeeded),
|
|
88
|
+
followUpJira: normalizeText(input.followUpJira),
|
|
89
|
+
codePointer: normalizeText(input.codePointer)
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function paragraphDraft(value) {
|
|
94
|
+
return normalizeText(value)
|
|
95
|
+
.split(/\n+/u)
|
|
96
|
+
.map((line) => line.trim())
|
|
97
|
+
.filter(Boolean);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function jiraUrl(keyOrUrl) {
|
|
101
|
+
const text = normalizeText(keyOrUrl);
|
|
102
|
+
if (!text) return "";
|
|
103
|
+
if (/^https?:\/\//iu.test(text)) return text;
|
|
104
|
+
if (/^[A-Z][A-Z0-9]+-\d+$/u.test(text)) return `https://jira2.workday.com/browse/${text}`;
|
|
105
|
+
return text;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function jiraKey(keyOrUrl) {
|
|
109
|
+
const text = normalizeText(keyOrUrl);
|
|
110
|
+
const match = text.match(/[A-Z][A-Z0-9]+-\d+/u);
|
|
111
|
+
return match?.[0] || text;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function buildJiraDraft(issue) {
|
|
115
|
+
if (!issue.followUpNeeded) return null;
|
|
116
|
+
const summary = `Follow up: ${issue.issue}`;
|
|
117
|
+
const codePointer = normalizeText(issue.codePointer);
|
|
118
|
+
return {
|
|
119
|
+
project: "BDS",
|
|
120
|
+
issueType: "Story",
|
|
121
|
+
component: "Devops",
|
|
122
|
+
labels: ["oncall-followup"],
|
|
123
|
+
summary,
|
|
124
|
+
description: [
|
|
125
|
+
"Context:",
|
|
126
|
+
issue.issueDescription,
|
|
127
|
+
"",
|
|
128
|
+
"Current code/problem to investigate:",
|
|
129
|
+
normalizeText(issue.rca) || "TBD from BDS-owned evidence.",
|
|
130
|
+
"",
|
|
131
|
+
...(codePointer ? [
|
|
132
|
+
"Code pointer:",
|
|
133
|
+
"{code}",
|
|
134
|
+
codePointer,
|
|
135
|
+
"{code}",
|
|
136
|
+
""
|
|
137
|
+
] : []),
|
|
138
|
+
"What needs correction:",
|
|
139
|
+
"- Identify the BDS-owned code or workflow gap that allowed this on-call issue.",
|
|
140
|
+
"- Keep the fix focused on the BDS-owned failure mode. Do not broaden to unrelated TAR/MTP/DB issues.",
|
|
141
|
+
"- Leave implementation details to the assignee after code inspection.",
|
|
142
|
+
"",
|
|
143
|
+
"Acceptance criteria:",
|
|
144
|
+
"- Add or update a regression test for the BDS-owned failure mode.",
|
|
145
|
+
"- Error handling or diagnostics are clear enough for on-call to distinguish BDS state from upstream/non-BDS issues.",
|
|
146
|
+
"",
|
|
147
|
+
"References:",
|
|
148
|
+
...issue.slackOrJira.map((item) => `- ${item}`)
|
|
149
|
+
].join("\n")
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function buildOncallRowDraft(issue) {
|
|
154
|
+
const followUp = normalizeText(issue.followUpJira);
|
|
155
|
+
const row = {
|
|
156
|
+
issue: issue.issue,
|
|
157
|
+
issueDescription: issue.issueDescription,
|
|
158
|
+
slackChannelOrJira: issue.slackOrJira,
|
|
159
|
+
rca: normalizeText(issue.rca),
|
|
160
|
+
impact: normalizeText(issue.impact),
|
|
161
|
+
followUpJira: followUp ? {
|
|
162
|
+
key: jiraKey(followUp),
|
|
163
|
+
url: jiraUrl(followUp)
|
|
164
|
+
} : null
|
|
165
|
+
};
|
|
166
|
+
return {
|
|
167
|
+
...row,
|
|
168
|
+
paragraphDraft: {
|
|
169
|
+
issue: paragraphDraft(row.issue),
|
|
170
|
+
issueDescription: paragraphDraft(row.issueDescription),
|
|
171
|
+
slackChannelOrJira: row.slackChannelOrJira,
|
|
172
|
+
rca: paragraphDraft(row.rca),
|
|
173
|
+
impact: paragraphDraft(row.impact),
|
|
174
|
+
followUpJira: row.followUpJira
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export class BdsOncallFollowupPayloadDraftService {
|
|
180
|
+
invoke(input = {}) {
|
|
181
|
+
const range = resolveOncallRange(input);
|
|
182
|
+
const issue = normalizeIssue(input.issue);
|
|
183
|
+
const jiraDraft = buildJiraDraft(issue);
|
|
184
|
+
const followUpJira = normalizeText(issue.followUpJira);
|
|
185
|
+
const payloadDraft = {
|
|
186
|
+
mode: "atomic-helper",
|
|
187
|
+
remoteWrite: "not_executed",
|
|
188
|
+
range,
|
|
189
|
+
confluence: {
|
|
190
|
+
spaceKey: input.spaceKey || DEFAULT_SPACE_KEY,
|
|
191
|
+
parentPageId: input.parentPageId || DEFAULT_PARENT_PAGE_ID,
|
|
192
|
+
reportPageId: input.reportPageId || DEFAULT_REPORT_PAGE_ID,
|
|
193
|
+
reportTitle: range.title,
|
|
194
|
+
reportUrl: `https://confluence.workday.com/pages/viewpage.action?pageId=${input.reportPageId || DEFAULT_REPORT_PAGE_ID}`,
|
|
195
|
+
rowDraft: buildOncallRowDraft(issue)
|
|
196
|
+
},
|
|
197
|
+
jira: {
|
|
198
|
+
shouldCreateFollowUp: Boolean(issue.followUpNeeded && !followUpJira),
|
|
199
|
+
existingFollowUp: followUpJira ? {
|
|
200
|
+
key: jiraKey(followUpJira),
|
|
201
|
+
url: jiraUrl(followUpJira)
|
|
202
|
+
} : null,
|
|
203
|
+
draft: jiraDraft
|
|
204
|
+
},
|
|
205
|
+
reviewNotes: [
|
|
206
|
+
"Verify the issue is BDS-owned before creating a BDS Jira.",
|
|
207
|
+
"If the issue source is a Slack incident thread, use that Slack thread as the report Issue source and keep any main Jira as tracking context.",
|
|
208
|
+
"When a main Jira exists, read its issuelinks and use a linked monitoring/product follow-up ticket as the Follow-up Jira when present.",
|
|
209
|
+
"Search Jira for existing follow-ups using tenant, cluster, alert, and issue keywords before creating a new ticket.",
|
|
210
|
+
"Create a BDS Jira only when followUpNeeded=true and no followUpJira is already present.",
|
|
211
|
+
"Keep the Jira focused on concrete BDS code/workflow gaps; avoid over-specifying implementation.",
|
|
212
|
+
"Add or update the current on-call Confluence row, including the follow-up Jira link when present.",
|
|
213
|
+
"When updating an existing Confluence table, preserve unrelated issue rows; if a row contains multiple issues, split or append the new issue instead of replacing the whole row.",
|
|
214
|
+
"Render every issue row in paragraph format: split issue description, RCA/evidence, impact, and links into separate paragraphs instead of one dense cell.",
|
|
215
|
+
"Do not mention Codex or internal assistant/thread provenance in requester-facing Confluence or Jira content.",
|
|
216
|
+
"Read back Jira and Confluence after writes to confirm both systems were updated, the Confluence version advanced, and the target row contains the Slack thread, main Jira, and follow-up Jira when relevant."
|
|
217
|
+
]
|
|
218
|
+
};
|
|
219
|
+
return input.format === "markdown" ? renderMarkdown(payloadDraft) : JSON.stringify(payloadDraft, null, 2);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function bdsOncallFollowupPayloadDraft(input = {}) {
|
|
224
|
+
return new BdsOncallFollowupPayloadDraftService().invoke(input);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const defaultService = new BdsOncallFollowupPayloadDraftService();
|
|
228
|
+
|
|
229
|
+
function renderMarkdown(payloadDraft) {
|
|
230
|
+
const lines = [
|
|
231
|
+
"# BDS On-call Follow-up Payload Draft",
|
|
232
|
+
"",
|
|
233
|
+
`Mode: ${payloadDraft.mode}`,
|
|
234
|
+
`Remote write: ${payloadDraft.remoteWrite}`,
|
|
235
|
+
`Report: ${payloadDraft.confluence.reportTitle}`,
|
|
236
|
+
`Report URL: ${payloadDraft.confluence.reportUrl}`,
|
|
237
|
+
"",
|
|
238
|
+
"## On-call Row",
|
|
239
|
+
`- Issue: ${payloadDraft.confluence.rowDraft.issue}`,
|
|
240
|
+
`- Issue Description: ${payloadDraft.confluence.rowDraft.issueDescription}`,
|
|
241
|
+
`- Slack/Jira: ${payloadDraft.confluence.rowDraft.slackChannelOrJira.join(", ") || "TBD"}`,
|
|
242
|
+
`- RCA: ${payloadDraft.confluence.rowDraft.rca || "TBD"}`,
|
|
243
|
+
`- Impact: ${payloadDraft.confluence.rowDraft.impact || "TBD"}`,
|
|
244
|
+
`- Follow up Jira: ${payloadDraft.confluence.rowDraft.followUpJira?.url || "TBD"}`,
|
|
245
|
+
"",
|
|
246
|
+
"## Paragraph Draft",
|
|
247
|
+
`- Issue paragraphs: ${payloadDraft.confluence.rowDraft.paragraphDraft.issue.join(" | ") || "TBD"}`,
|
|
248
|
+
`- Description paragraphs: ${payloadDraft.confluence.rowDraft.paragraphDraft.issueDescription.join(" | ") || "TBD"}`,
|
|
249
|
+
`- RCA paragraphs: ${payloadDraft.confluence.rowDraft.paragraphDraft.rca.join(" | ") || "TBD"}`,
|
|
250
|
+
`- Impact paragraphs: ${payloadDraft.confluence.rowDraft.paragraphDraft.impact.join(" | ") || "TBD"}`,
|
|
251
|
+
"",
|
|
252
|
+
"## Jira",
|
|
253
|
+
payloadDraft.jira.shouldCreateFollowUp ? `Create draft: ${payloadDraft.jira.draft.summary}` : `Existing follow-up: ${payloadDraft.jira.existingFollowUp?.url || "none"}`,
|
|
254
|
+
"",
|
|
255
|
+
"## Review Notes",
|
|
256
|
+
...payloadDraft.reviewNotes.map((item) => `- ${item}`)
|
|
257
|
+
];
|
|
258
|
+
return lines.join("\n");
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export default defineReadOnlyTool({
|
|
262
|
+
name: "bds_oncall_followup_payload_draft",
|
|
263
|
+
description: "Create a atomic-helper BDS on-call report row and BDS Jira follow-up draft for BDS-owned on-call issues.",
|
|
264
|
+
inputSchema,
|
|
265
|
+
annotations: { openWorldHint: false },
|
|
266
|
+
async handler(input) {
|
|
267
|
+
try {
|
|
268
|
+
return ok(defaultService.invoke(input));
|
|
269
|
+
} catch (error) {
|
|
270
|
+
return fail(error);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
export const __test = {
|
|
276
|
+
DEFAULT_ANCHOR_START,
|
|
277
|
+
DEFAULT_PERIOD_DAYS,
|
|
278
|
+
buildJiraDraft,
|
|
279
|
+
buildOncallRowDraft,
|
|
280
|
+
bdsOncallFollowupPayloadDraft,
|
|
281
|
+
BdsOncallFollowupPayloadDraftService,
|
|
282
|
+
normalizeIssue,
|
|
283
|
+
paragraphDraft,
|
|
284
|
+
resolveOncallRange
|
|
285
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
export const description = "Create a atomic-helper translation from BDS Pharos investigation fields to read-only Pharos alert, silence, Prometheus, and dashboard tool calls; does not call Pharos.";
|
|
2
|
+
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { defineReadOnlyTool, fail, ok } from "@nuvlore/extension-read-only-toolkit/devops-diagnostics";
|
|
5
|
+
|
|
6
|
+
const domainQueries = [
|
|
7
|
+
{
|
|
8
|
+
domain: "disk",
|
|
9
|
+
dashboards: ["single_server", "datanode", "hdfs", "cluster"],
|
|
10
|
+
queries: [
|
|
11
|
+
"topk(10, bds_disk_kernel_daily_io_error_total{wd_owner=\"bds\", bds_cluster=\"$cluster\"})",
|
|
12
|
+
"topk(10, rate(datanode_failed_volumes{bds_cluster=\"$cluster\"}[5m]))"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
domain: "yarn",
|
|
17
|
+
dashboards: ["yarn", "resourcemanager", "nodemanager", "cluster"],
|
|
18
|
+
queries: [
|
|
19
|
+
"bds_yarn_overall_status{bds_cluster=\"$cluster\"}",
|
|
20
|
+
"topk(10, yarn_queue_apps_running{bds_cluster=\"$cluster\"})"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
domain: "namenode",
|
|
25
|
+
dashboards: ["namenode", "hdfs", "cluster"],
|
|
26
|
+
queries: [
|
|
27
|
+
"bds_namenode_ha_state{bds_cluster=\"$cluster\"}",
|
|
28
|
+
"topk(10, namenode_rpc_queue_time_avg_time{bds_cluster=\"$cluster\"})"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
domain: "kubernetes",
|
|
33
|
+
dashboards: ["k8s", "cluster"],
|
|
34
|
+
queries: [
|
|
35
|
+
"up{wd_owner=\"bds\", bds_cluster=\"$cluster\", job=~\".*kube.*\"}",
|
|
36
|
+
"sum by (pod) (kube_pod_container_status_restarts_total{bds_cluster=\"$cluster\"})"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
domain: "airflow",
|
|
41
|
+
dashboards: ["airflow", "cluster"],
|
|
42
|
+
queries: [
|
|
43
|
+
"airflow_dag_status{bds_cluster=\"$cluster\"}",
|
|
44
|
+
"topk(10, airflow_task_status{bds_cluster=\"$cluster\"})"
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
domain: "kms",
|
|
49
|
+
dashboards: ["kms", "cluster"],
|
|
50
|
+
queries: [
|
|
51
|
+
"up{wd_owner=\"bds\", bds_cluster=\"$cluster\", job=~\".*kms.*\"}",
|
|
52
|
+
"topk(10, rate(kms_requests_total{bds_cluster=\"$cluster\"}[5m]))"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const domainQueryByName = new Map(domainQueries.map((entry) => [entry.domain, entry]));
|
|
58
|
+
|
|
59
|
+
export class BdsTimeWindowResolver {
|
|
60
|
+
resolve(input) {
|
|
61
|
+
if (input.start || input.end) {
|
|
62
|
+
return { start: input.start, end: input.end ?? "now" };
|
|
63
|
+
}
|
|
64
|
+
return { start: "now-6h", end: "now" };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const timeWindowResolver = new BdsTimeWindowResolver();
|
|
69
|
+
|
|
70
|
+
function renderQuery(query, cluster, source) {
|
|
71
|
+
let rendered = query;
|
|
72
|
+
if (cluster) rendered = rendered.replaceAll("$cluster", cluster);
|
|
73
|
+
else rendered = rendered.replaceAll('bds_cluster="$cluster"', 'wd_owner="bds"');
|
|
74
|
+
if (source && !rendered.includes("source=")) {
|
|
75
|
+
rendered = rendered.replace(/\{([^}]*)\}/, (_match, labels) => `{${labels}, source="${source}"}`);
|
|
76
|
+
}
|
|
77
|
+
return rendered;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function bdsPharosQueryPlan(input, dependencies = {}) {
|
|
81
|
+
const {
|
|
82
|
+
timeWindowResolver: activeTimeWindowResolver = timeWindowResolver
|
|
83
|
+
} = dependencies;
|
|
84
|
+
const text = input.request.trim();
|
|
85
|
+
const cluster = input.cluster || "";
|
|
86
|
+
const source = input.source || "";
|
|
87
|
+
const alertName = input.alertName || "";
|
|
88
|
+
const intent = input.intent || "metric_query";
|
|
89
|
+
const domain = input.domain ? domainQueryByName.get(input.domain) : null;
|
|
90
|
+
const timeWindow = activeTimeWindowResolver.resolve(input);
|
|
91
|
+
const filters = [
|
|
92
|
+
alertName ? { key: "alertname", value: alertName } : null,
|
|
93
|
+
source ? { key: "source", value: source } : null,
|
|
94
|
+
cluster ? { key: "bds_cluster", value: cluster } : null
|
|
95
|
+
].filter(Boolean);
|
|
96
|
+
|
|
97
|
+
const steps = [];
|
|
98
|
+
if (intent === "firing_alerts" || intent === "health_check" || alertName) {
|
|
99
|
+
steps.push({
|
|
100
|
+
tool: "mcp__devops-tool__pharos_alert_read",
|
|
101
|
+
action: "firing_alerts",
|
|
102
|
+
args: { action: "firing_alerts", environment: input.environment, namespace: "bds", alertName: alertName || undefined, filters }
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
if (intent === "silence_status" || intent === "health_check" || alertName || source || cluster) {
|
|
106
|
+
steps.push({
|
|
107
|
+
tool: "mcp__devops-tool__pharos_alert_read",
|
|
108
|
+
action: "active_silences",
|
|
109
|
+
args: { action: "active_silences", environment: input.environment, namespace: "bds", filters }
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
if (intent === "rule_details" && alertName) {
|
|
113
|
+
steps.push({
|
|
114
|
+
tool: "mcp__devops-tool__pharos_alert_read",
|
|
115
|
+
action: "alert_rules",
|
|
116
|
+
args: { action: "alert_rules", environment: input.environment, namespace: "bds", alertName, filters: [] }
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (intent === "discover_metrics") {
|
|
120
|
+
steps.push({
|
|
121
|
+
tool: "mcp__devops-tool__pharos_prometheus_read",
|
|
122
|
+
action: "label_values",
|
|
123
|
+
args: { action: "label_values", environment: input.environment, labelName: "bds_cluster", matchers: ['{wd_owner="bds"}'] }
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
for (const query of (domain?.queries ?? ['up{wd_owner="bds"}']).slice(0, 3)) {
|
|
127
|
+
steps.push({
|
|
128
|
+
tool: "mcp__devops-tool__pharos_prometheus_read",
|
|
129
|
+
action: intent === "metric_range" || intent === "health_check" ? "query_range" : "query",
|
|
130
|
+
args: {
|
|
131
|
+
action: intent === "metric_range" || intent === "health_check" ? "query_range" : "query",
|
|
132
|
+
environment: input.environment,
|
|
133
|
+
query: renderQuery(query, cluster, source),
|
|
134
|
+
start: intent === "metric_range" || intent === "health_check" ? timeWindow.start : undefined,
|
|
135
|
+
end: intent === "metric_range" || intent === "health_check" ? timeWindow.end : undefined,
|
|
136
|
+
step: input.step ?? "5m"
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
steps.push({
|
|
141
|
+
tool: "mcp__devops-tool__bds_dashboard_links",
|
|
142
|
+
action: "links",
|
|
143
|
+
args: {
|
|
144
|
+
environment: input.environment,
|
|
145
|
+
alertName: alertName || undefined,
|
|
146
|
+
source: source || undefined,
|
|
147
|
+
cluster: cluster || undefined,
|
|
148
|
+
from: timeWindow.start,
|
|
149
|
+
to: timeWindow.end,
|
|
150
|
+
domain: domain?.domain,
|
|
151
|
+
dashboards: domain?.dashboards
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return JSON.stringify({
|
|
156
|
+
mode: "natural-language-query-pack",
|
|
157
|
+
provider: "bds-pharos",
|
|
158
|
+
request: text,
|
|
159
|
+
parsed: {
|
|
160
|
+
environment: input.environment,
|
|
161
|
+
intent,
|
|
162
|
+
domain: domain?.domain ?? "general",
|
|
163
|
+
cluster,
|
|
164
|
+
source,
|
|
165
|
+
alertName,
|
|
166
|
+
timeWindow
|
|
167
|
+
},
|
|
168
|
+
steps,
|
|
169
|
+
guidance: [
|
|
170
|
+
"Execute steps in order until the answer has enough direct evidence.",
|
|
171
|
+
"Use pharos_prometheus_read for arbitrary BDS PromQL when the planner cannot infer a domain metric.",
|
|
172
|
+
"Never mutate Pharos from this workflow."
|
|
173
|
+
]
|
|
174
|
+
}, null, 2);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export class BdsPharosQueryPackService {
|
|
178
|
+
constructor(dependencies = {}) {
|
|
179
|
+
this.dependencies = dependencies;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
invoke(input) {
|
|
183
|
+
return bdsPharosQueryPlan(input, this.dependencies);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const defaultService = new BdsPharosQueryPackService();
|
|
188
|
+
|
|
189
|
+
export default defineReadOnlyTool({
|
|
190
|
+
name: "bds_pharos_query_pack",
|
|
191
|
+
description: "Create a atomic-helper translation from BDS Pharos investigation fields to read-only Pharos alert, silence, Prometheus, and dashboard tool calls; does not call Pharos.",
|
|
192
|
+
inputSchema: {
|
|
193
|
+
request: z.string().trim().min(1).max(2_000),
|
|
194
|
+
intent: z.enum(["metric_query", "metric_range", "health_check", "firing_alerts", "silence_status", "rule_details", "discover_metrics"]).default("metric_query"),
|
|
195
|
+
domain: z.enum(domainQueries.map((entry) => entry.domain)).optional(),
|
|
196
|
+
environment: z.enum(["dev", "prod", "il4"]).default("prod"),
|
|
197
|
+
cluster: z.string().trim().min(1).max(128).regex(/^[A-Za-z0-9_.:-]+$/).optional(),
|
|
198
|
+
source: z.string().trim().min(1).max(255).regex(/^[A-Za-z0-9_.:@/-]+$/).optional(),
|
|
199
|
+
alertName: z.string().trim().min(1).max(200).regex(/^[A-Za-z0-9_.:-]+$/).optional(),
|
|
200
|
+
start: z.string().trim().min(1).max(80).optional(),
|
|
201
|
+
end: z.string().trim().min(1).max(80).optional(),
|
|
202
|
+
step: z.string().trim().min(1).max(40).default("5m")
|
|
203
|
+
},
|
|
204
|
+
async handler(input) {
|
|
205
|
+
try {
|
|
206
|
+
return ok(defaultService.invoke(input));
|
|
207
|
+
} catch (error) {
|
|
208
|
+
return fail(error);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
export const __test = {
|
|
214
|
+
BdsPharosQueryPackService,
|
|
215
|
+
BdsTimeWindowResolver,
|
|
216
|
+
bdsPharosQueryPlan,
|
|
217
|
+
domainQueries
|
|
218
|
+
};
|