dsh-plugin-t-expert 0.4.48 → 0.4.53
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/CHANGELOG.md +139 -0
- package/README.md +2 -2
- package/THIRD-PARTY-NOTICES +3 -3
- package/data/roster.json +2220 -756
- package/data/source.json +2 -2
- package/lib/client.js +29 -3
- package/lib/fetch-assets.js +37 -2
- package/lib/index.js +37 -10
- package/lib/remote.js +13 -0
- package/package.json +2 -2
- package/vendor/third-party-licenses/README.md +1 -1
- package/data/experts/engineering/data-analytics-reporter/agents/data-analytics-reporter.md +0 -174
- package/data/experts/engineering/data-analytics-reporter/skills/analytics-reporting/SKILL.md +0 -213
- package/data/experts/engineering/data-analytics-reporter/skills/data-quality-assessment/SKILL.md +0 -167
- package/data/experts/engineering/data-analytics-reporter/skills/data-visualization/SKILL.md +0 -94
- package/data/experts/engineering/data-analytics-reporter/skills/kpi-design/SKILL.md +0 -134
- package/data/experts/engineering/data-analytics-reporter/skills/metric-diagnostics/SKILL.md +0 -143
- package/data/experts/finance/vietnam-finance-tax-expert/.codebuddy-plugin/plugin.json +0 -60
- package/data/experts/finance/vietnam-finance-tax-expert/.downloaded_at +0 -1
- package/data/experts/finance/vietnam-finance-tax-expert/.imported-from-workbuddy +0 -7
- package/data/experts/finance/vietnam-finance-tax-expert/README.md +0 -84
- package/data/experts/finance/vietnam-finance-tax-expert/persona.md +0 -323
- package/data/experts/hr/fbsir-beike-yi/agents/fbsir-beike-yi.md +0 -225
- package/data/experts/hr/fbsir-beike-yi/contracts/first-value-contract.json +0 -33
- package/data/experts/hr/fbsir-beike-yi/contracts/high-value-delivery.schema.json +0 -36
- package/data/experts/hr/fbsir-beike-yi/contracts/material-scope.schema.json +0 -46
- package/data/experts/hr/fbsir-beike-yi/contracts/option-decision-contract.json +0 -34
- package/data/experts/hr/fbsir-beike-yi/contracts/reuse-capsule.schema.json +0 -28
- package/data/experts/hr/fbsir-beike-yi/contracts/teaching-quality-review-contract.json +0 -38
- package/data/experts/hr/fbsir-beike-yi/contracts/writeback-contract.json +0 -34
- package/data/experts/hr/fbsir-beike-yi/examples/acceptance-cases.json +0 -104
- package/data/experts/hr/fbsir-beike-yi/examples/local-workspace-scenarios.json +0 -43
- package/data/experts/hr/fbsir-beike-yi/examples/output-structures.md +0 -111
- package/data/experts/hr/fbsir-beike-yi/references/evidence-and-source-ledger.md +0 -56
- package/data/experts/hr/fbsir-beike-yi/references/user-visible-copy.md +0 -60
- package/data/experts/hr/fbsir-beike-yi/references/workbuddy-local-mode-boundary.md +0 -41
- package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/SKILL.md +0 -152
- package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/references/bounded-revision.md +0 -30
- package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/references/high-value-delivery.md +0 -33
- package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/references/local-writeback.md +0 -35
- package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/references/material-sensemaking.md +0 -43
- package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/references/option-decision.md +0 -40
- package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/references/reuse-capsule.md +0 -34
- package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/references/teaching-quality-review.md +0 -28
- package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/templates/high-value-delivery-card.md +0 -35
- package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/templates/material-understanding-card.md +0 -47
- package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/templates/teaching-option-card.md +0 -38
- package/data/experts/marketing/kdocs-ppt-creator/agents/kdocs-ppt-creator.md +0 -42
- package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/SKILL.md +0 -164
- package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/auth.md +0 -32
- package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/file-locating-guide.md +0 -34
- package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp/doc_master.md +0 -83
- package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp/doc_presentation.md +0 -85
- package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp/doc_shape.md +0 -118
- package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp/doc_slide.md +0 -94
- package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp/export.md +0 -178
- package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp/jsapi_shape.md +0 -131
- package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp/jsapi_slide.md +0 -126
- package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp/slide.md +0 -139
- package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp/theme.md +0 -204
- package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp.md +0 -164
- package/data/experts/specialized/job-companion-team/agents/job-companion-interview.md +0 -74
- package/data/experts/specialized/job-companion-team/agents/job-companion-negotiation.md +0 -82
- package/data/experts/specialized/job-companion-team/agents/job-companion-reflection.md +0 -87
- package/data/experts/specialized/job-companion-team/agents/job-companion-resume.md +0 -82
- package/data/experts/specialized/job-companion-team/agents/job-companion-team-lead.md +0 -162
- package/data/experts/specialized/job-companion-team/skills/interview-prep/SKILL.md +0 -73
- package/data/experts/specialized/job-companion-team/skills/mock-interview/SKILL.md +0 -65
- package/data/experts/specialized/job-companion-team/skills/onboarding-reflection/SKILL.md +0 -142
- package/data/experts/specialized/job-companion-team/skills/resume-polish/SKILL.md +0 -82
- package/data/experts/specialized/job-companion-team/skills/salary-negotiation/SKILL.md +0 -105
- package/data/experts/specialized/job-companion-team/skills/self-inventory/SKILL.md +0 -67
- package/data/experts/specialized/job-companion-team/skills/target-positioning/SKILL.md +0 -64
package/data/source.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"updatedAt": "2026-09-29 14:20:00 +0800",
|
|
3
|
-
"expertFiles":
|
|
3
|
+
"expertFiles": 623,
|
|
4
4
|
"divisions": [
|
|
5
5
|
"academic",
|
|
6
6
|
"company",
|
|
@@ -25,5 +25,5 @@
|
|
|
25
25
|
"support",
|
|
26
26
|
"testing"
|
|
27
27
|
],
|
|
28
|
-
"note": "本仓名册清单:data/experts 是真源(含从 WorkBuddy 导入的目录形态专家)。2026-09-29 移除 22 位绑定腾讯内部系统的专家(代码层端点指向 *.woa.com 等内网域名),645 →
|
|
28
|
+
"note": "本仓名册清单:data/experts 是真源(含从 WorkBuddy 导入的目录形态专家)。2026-09-29 移除 22 位绑定腾讯内部系统的专家(代码层端点指向 *.woa.com 等内网域名),645 → 623。"
|
|
29
29
|
}
|
package/lib/client.js
CHANGED
|
@@ -67,7 +67,7 @@ var SectionBoundary = class extends import_react.default.Component {
|
|
|
67
67
|
|
|
68
68
|
// src/client/state.js
|
|
69
69
|
var NS = "t-team";
|
|
70
|
-
var CLIENT_BUILD_VERSION = "0.4.
|
|
70
|
+
var CLIENT_BUILD_VERSION = "0.4.53";
|
|
71
71
|
var PLUGIN_ID = "dsh-plugin-t-expert";
|
|
72
72
|
var DIVISION_FALLBACK = "specialized";
|
|
73
73
|
var NEW_DIVISION = "__new";
|
|
@@ -332,6 +332,7 @@ var CSS = `
|
|
|
332
332
|
/* \u63D0\u793A\u8BCD\u6B63\u6587\uFF1A\u53EA\u8D1F\u8D23\u6392\u7248\uFF0C\u6EDA\u52A8\u4EA4\u7ED9\u4E0A\u9762\u90A3\u5C42 .body\uFF0C\u6240\u4EE5\u8FD9\u91CC\u4E0D\u8BBE padding/overflow\u3002 */
|
|
333
333
|
.t-team-prompt-body{margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:19px;white-space:pre-wrap;word-break:break-word;color:var(--dsw-alias-label-secondary)}
|
|
334
334
|
.t-team-avatar{display:flex;align-items:center;justify-content:center;width:44px;height:44px;border-radius:50%;background:var(--dsw-alias-bg-layer-3);font-size:22px;line-height:1}
|
|
335
|
+
.t-team-avatar img{width:100%;height:100%;border-radius:50%;object-fit:cover;display:block}
|
|
335
336
|
.t-team-identity{display:flex;min-width:0;flex-direction:column;padding-right:46px}
|
|
336
337
|
.t-team-card-name{font-size:14px;font-weight:600;line-height:20px;color:var(--dsw-alias-label-primary);display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}
|
|
337
338
|
.t-team-card-division{font-size:13px;line-height:18px;margin-top:2px;color:var(--dsw-alias-label-secondary)}
|
|
@@ -517,6 +518,7 @@ var CSS = `
|
|
|
517
518
|
\u2462 \u6CA1\u6709 color \u65F6\u7684\u515C\u5E95\u5E95\u8272\u4ECE\u8FD1\u767D\u7684 layer-3 \u6362\u6210\u6DE1\u7070\u7684 interactive-bg-hover
|
|
518
519
|
\uFF08\u8001\u6570\u636E\u3001\u6216\u989C\u8272\u540D\u4E0D\u88AB\u652F\u6301\u7684\u5BBF\u4E3B\u4E0A\uFF0C\u4E5F\u8FD8\u770B\u5F97\u89C1\u4E00\u4E2A\u5706\uFF09\u3002 */
|
|
519
520
|
.t-team-pop-avatar{flex:none;display:grid;place-items:center;width:44px;height:44px;border-radius:50%;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);font-size:26px;line-height:1}
|
|
521
|
+
.t-team-pop-avatar img{width:100%;height:100%;border-radius:50%;object-fit:cover;display:block}
|
|
520
522
|
/* \u6280\u80FD\u5361\u5934\u50CF\uFF1A\u6280\u80FD\u6CA1\u6709 color \u5B57\u6BB5\uFF0C\u56FA\u5B9A\u7528\u540C\u4E00\u6863\u6DE1\u7070\u5E95 + \u5F69\u8272 emoji\uFF08\u{1F9F0}\uFF09\u2014\u2014 \u4E0E\u4E13\u5BB6\u5361\u7684\u6DE1\u8272\u5706\u540C\u4E00\u5957\u89C2\u611F\u3002 */
|
|
521
523
|
.t-team-pop-avatar-skill{background:var(--dsw-alias-interactive-bg-hover)}
|
|
522
524
|
.t-team-pop-title{display:flex;flex-direction:column;gap:3px;min-width:0}
|
|
@@ -20822,6 +20824,8 @@ var TYPERT_REMOTE = {
|
|
|
20822
20824
|
package: PLUGIN_ID,
|
|
20823
20825
|
descriptors: [
|
|
20824
20826
|
direct("getCatalog", [], "TTeamCatalog", catalogSnapshotSchema),
|
|
20827
|
+
// 本地已有的专家头像(data URL)—— 必须与宿主 descriptor 成对,否则 verify 会拦。
|
|
20828
|
+
direct("getAvatars", [], "TTeamCatalog", external_exports.record(external_exports.string(), external_exports.string())),
|
|
20825
20829
|
// 云端附件(2026-09-29):面板与 @ 弹窗里那个「下载」按钮走这里。
|
|
20826
20830
|
direct("fetchAssets", [
|
|
20827
20831
|
{ name: "slug", wire: "slug", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().min(1).max(128), create: () => external_exports.string().min(1).max(128) } }
|
|
@@ -21574,6 +21578,7 @@ function SkillsPanel({ t, remote, initialSnapshot }) {
|
|
|
21574
21578
|
function SettingsPanel({ t, ctx, remote, skillRemote, getActive, initialTab, view }) {
|
|
21575
21579
|
const [snapshot, setSnapshot] = import_react5.default.useState(() => snapshotOf(remote) ?? null);
|
|
21576
21580
|
const [query, setQuery] = import_react5.default.useState("");
|
|
21581
|
+
const [avatars, setAvatars] = import_react5.default.useState({});
|
|
21577
21582
|
const [division, setDivision] = import_react5.default.useState("");
|
|
21578
21583
|
const [status, setStatus] = import_react5.default.useState("");
|
|
21579
21584
|
const [error62, setError] = import_react5.default.useState("");
|
|
@@ -21605,6 +21610,16 @@ function SettingsPanel({ t, ctx, remote, skillRemote, getActive, initialTab, vie
|
|
|
21605
21610
|
alive = false;
|
|
21606
21611
|
};
|
|
21607
21612
|
}, [reload]);
|
|
21613
|
+
import_react5.default.useEffect(() => {
|
|
21614
|
+
let alive = true;
|
|
21615
|
+
(async () => {
|
|
21616
|
+
const map2 = await Promise.resolve(remote?.getAvatars?.()).catch(() => void 0);
|
|
21617
|
+
if (alive && map2 !== void 0 && map2 !== null) setAvatars(map2);
|
|
21618
|
+
})();
|
|
21619
|
+
return () => {
|
|
21620
|
+
alive = false;
|
|
21621
|
+
};
|
|
21622
|
+
}, [remote]);
|
|
21608
21623
|
const active = getActive();
|
|
21609
21624
|
async function openPrompt(expert) {
|
|
21610
21625
|
setCopyState("");
|
|
@@ -21886,7 +21901,7 @@ function SettingsPanel({ t, ctx, remote, skillRemote, getActive, initialTab, vie
|
|
|
21886
21901
|
/* @__PURE__ */ import_react5.default.createElement("span", { className: "t-team-group-chevron", "aria-hidden": "true" }, open2 ? "\u25BE" : "\u25B8")
|
|
21887
21902
|
), open2 && /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-grid" }, group.items.map((expert) => {
|
|
21888
21903
|
const on = enabledSet.has(expert.slug);
|
|
21889
|
-
return /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card", "data-enabled": on, key: expert.slug }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-body" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-avatar" }, expert.emoji || "\u{1F9E9}"), /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-identity" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-name" }, displayName(expert, active), expert.custom === true && /* @__PURE__ */ import_react5.default.createElement(Tag2, { tone: "info" }, t("custom.badge")), expert.assets === "missing" && /* @__PURE__ */ import_react5.default.createElement("span", { title: t("assets.cloud"), style: { marginInlineStart: 4 } }, "\u2601\uFE0F")), /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-division" }, divisionLabel(expert, active)), /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-slug" }, expert.slug)), /* @__PURE__ */ import_react5.default.createElement("p", { className: "t-team-card-desc" }, displayDescription(expert, active)), expert.conflict === true && /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-conflict", style: { gridColumn: "1/-1" } }, t("settings.conflict"))), /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-tools" }, /* @__PURE__ */ import_react5.default.createElement(
|
|
21904
|
+
return /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card", "data-enabled": on, key: expert.slug }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-body" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-avatar" }, avatars[expert.slug] === void 0 ? expert.emoji || "\u{1F9E9}" : /* @__PURE__ */ import_react5.default.createElement("img", { src: avatars[expert.slug], alt: "" })), /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-identity" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-name" }, displayName(expert, active), expert.custom === true && /* @__PURE__ */ import_react5.default.createElement(Tag2, { tone: "info" }, t("custom.badge")), expert.assets === "missing" && /* @__PURE__ */ import_react5.default.createElement("span", { title: t("assets.cloud"), style: { marginInlineStart: 4 } }, "\u2601\uFE0F")), /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-division" }, divisionLabel(expert, active)), /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-slug" }, expert.slug)), /* @__PURE__ */ import_react5.default.createElement("p", { className: "t-team-card-desc" }, displayDescription(expert, active)), expert.conflict === true && /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-conflict", style: { gridColumn: "1/-1" } }, t("settings.conflict"))), /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-tools" }, /* @__PURE__ */ import_react5.default.createElement(
|
|
21890
21905
|
Button2,
|
|
21891
21906
|
{
|
|
21892
21907
|
variant: "ghost",
|
|
@@ -22020,6 +22035,17 @@ function SummonButton({ t, ctx, remote, getActive, insertReference, insertSkillH
|
|
|
22020
22035
|
const [open2, setOpen] = import_react6.default.useState(preview?.open === true);
|
|
22021
22036
|
const [popTab, setPopTab] = import_react6.default.useState(preview?.tab ?? "experts");
|
|
22022
22037
|
const [skills, setSkills] = import_react6.default.useState(null);
|
|
22038
|
+
const [avatars, setAvatars] = import_react6.default.useState({});
|
|
22039
|
+
import_react6.default.useEffect(() => {
|
|
22040
|
+
let alive = true;
|
|
22041
|
+
(async () => {
|
|
22042
|
+
const map2 = await Promise.resolve(remote?.getAvatars?.()).catch(() => void 0);
|
|
22043
|
+
if (alive && map2 !== void 0 && map2 !== null) setAvatars(map2);
|
|
22044
|
+
})();
|
|
22045
|
+
return () => {
|
|
22046
|
+
alive = false;
|
|
22047
|
+
};
|
|
22048
|
+
}, [remote]);
|
|
22023
22049
|
const [skillQuery, setSkillQuery] = import_react6.default.useState("");
|
|
22024
22050
|
const [skillsDiag, setSkillsDiag] = import_react6.default.useState("");
|
|
22025
22051
|
import_react6.default.useEffect(() => {
|
|
@@ -22270,7 +22296,7 @@ function SummonButton({ t, ctx, remote, getActive, insertReference, insertSkillH
|
|
|
22270
22296
|
)), /* @__PURE__ */ import_react6.default.createElement("div", { className: "t-team-pop-list", ref: listRef }, grouped.map((group) => /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, { key: group.division }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "t-team-division" }, divisionLabel(group.items[0], active)), group.items.map((expert) => {
|
|
22271
22297
|
const alt = active === "en" ? expert.name : expert.nameEn;
|
|
22272
22298
|
const iconName = divisionIconName(expert.division);
|
|
22273
|
-
return /* @__PURE__ */ import_react6.default.createElement("button", { className: "t-team-pop-item", type: "button", key: expert.slug, onClick: () => pick2(expert) }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-head" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-avatar", style: tintOf(expert.color) ?? void 0 }, expert.emoji || (iconName === null ? "\u{1F9E9}" : /* @__PURE__ */ import_react6.default.createElement(PopIcon, { name: iconName }))), /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-title" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-name" }, displayName(expert, active)), alt !== void 0 && alt !== "" && /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-sub" }, alt))), /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-desc" }, displayDescription(expert, active)), /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-tags" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-tag" }, divisionLabel(expert, active)), expert.custom === true && /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-tag" }, t("custom.badge")), expert.assets === "missing" && /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-tag", title: t("assets.cloud") }, "\u2601\uFE0F")));
|
|
22299
|
+
return /* @__PURE__ */ import_react6.default.createElement("button", { className: "t-team-pop-item", type: "button", key: expert.slug, onClick: () => pick2(expert) }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-head" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-avatar", style: tintOf(expert.color) ?? void 0 }, avatars[expert.slug] === void 0 ? expert.emoji || (iconName === null ? "\u{1F9E9}" : /* @__PURE__ */ import_react6.default.createElement(PopIcon, { name: iconName })) : /* @__PURE__ */ import_react6.default.createElement("img", { src: avatars[expert.slug], alt: "" })), /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-title" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-name" }, displayName(expert, active)), alt !== void 0 && alt !== "" && /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-sub" }, alt))), /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-desc" }, displayDescription(expert, active)), /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-tags" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-tag" }, divisionLabel(expert, active)), expert.custom === true && /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-tag" }, t("custom.badge")), expert.assets === "missing" && /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-tag", title: t("assets.cloud") }, "\u2601\uFE0F")));
|
|
22274
22300
|
}))), experts.length === 0 && /* @__PURE__ */ import_react6.default.createElement("div", { className: "t-team-pop-empty" }, t("button.empty")), experts.length > 0 && visible.length === 0 && /* @__PURE__ */ import_react6.default.createElement("div", { className: "t-team-pop-empty" }, t("button.noMatch")))), popTab === "skills" && /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, /* @__PURE__ */ import_react6.default.createElement(
|
|
22275
22301
|
"input",
|
|
22276
22302
|
{
|
package/lib/fetch-assets.js
CHANGED
|
@@ -76,8 +76,14 @@ export const ROSTER_FETCH_TIMEOUT_MS = 8000;
|
|
|
76
76
|
/** 缓存清单的保鲜期(毫秒)。过期后下次调用会触发后台刷新。 */
|
|
77
77
|
export const ROSTER_TTL_MS = 12 * 60 * 60 * 1000;
|
|
78
78
|
|
|
79
|
-
/**
|
|
80
|
-
|
|
79
|
+
/**
|
|
80
|
+
* 单包体积上限(防清单里塞进一个超大对象把客户端下爆)。
|
|
81
|
+
*
|
|
82
|
+
* 定 2 GB 而不是最初写的 200 MB —— 实测 roster 里真实存在 **495.6 MB** 的包
|
|
83
|
+
* (`finance/vietnam-finance-tax-expert`),200 MB 的上限会把它当异常条目整条拒掉,
|
|
84
|
+
* 附件就永远拿不到了(2026-09-29 实测发现)。留足余量,同时仍能挡住「塞一个几十 GB 对象」。
|
|
85
|
+
*/
|
|
86
|
+
const MAX_PACK_BYTES = 2 * 1024 * 1024 * 1024;
|
|
81
87
|
|
|
82
88
|
/** 远端条目数最多比包内多出这么多(防塞入海量条目)。 */
|
|
83
89
|
const MAX_ROSTER_EXTRA = 200;
|
|
@@ -173,6 +179,32 @@ export async function fetchRemoteRoster(bundled, fetchImpl = fetch) {
|
|
|
173
179
|
}
|
|
174
180
|
}
|
|
175
181
|
|
|
182
|
+
/**
|
|
183
|
+
* 把远端清单**合并**进包内清单:同名条目以远端为准,包内独有的条目保留。
|
|
184
|
+
*
|
|
185
|
+
* ## 为什么是并集而不是替换
|
|
186
|
+
*
|
|
187
|
+
* 远端清单可能比包内**旧**(改了专家但还没重传 roster)。若直接替换,包内独有的条目就查不到了 ——
|
|
188
|
+
* 表现是那位专家被判成「没有云端附件」,附件**永远下载不下来**。
|
|
189
|
+
* 2026-09-29 实测就踩到了:`finance/vietnam-finance-tax-expert`(495.6 MB)在包内清单里有、
|
|
190
|
+
* 远端那份是旧的因而没有,于是它的 `agents/` 与 `references/` 一直缺失。
|
|
191
|
+
*
|
|
192
|
+
* 并集的方向是「远端优先」:远端有的用远端的(那是新版本),远端没有的用包内的(兜底)。
|
|
193
|
+
* 反向的风险(远端多出来的条目)无害:条目只在名册里真的有那位专家时才会被引用。
|
|
194
|
+
*
|
|
195
|
+
* @param {object|undefined} bundled - 包内清单(可信、完整)。
|
|
196
|
+
* @param {object|undefined} remote - 已过 {@link sanitizeRemoteRoster} 的远端清单。
|
|
197
|
+
* @returns {object|undefined} 合并后的清单;两边都没有时 `undefined`。
|
|
198
|
+
*/
|
|
199
|
+
export function mergeRosters(bundled, remote) {
|
|
200
|
+
if (remote === undefined) return bundled;
|
|
201
|
+
if (bundled === null || typeof bundled !== "object") return remote;
|
|
202
|
+
return {
|
|
203
|
+
...remote,
|
|
204
|
+
packs: { ...(bundled.packs ?? {}), ...(remote.packs ?? {}) },
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
176
208
|
/**
|
|
177
209
|
* 读磁盘上的清单缓存。返回 `{ fetchedAt, roster }` 或 `undefined`。
|
|
178
210
|
* @param {string} path - 缓存文件路径。
|
|
@@ -338,6 +370,9 @@ export const ASSET_DIRS = [
|
|
|
338
370
|
"skills", "references", "Databases", "bin", "render-bundle", "Reference_Texts",
|
|
339
371
|
"scripts", "rules", "license", "schemas", "src", "examples", "contracts", "templates",
|
|
340
372
|
"agents",
|
|
373
|
+
// 头像(`avatars/avatar.webp`,128px WEBP、约 5 KB)。它随资源包按需下发 ——
|
|
374
|
+
// 没下载过的专家本地就没有这个目录,面板显示默认头像(emoji)。
|
|
375
|
+
"avatars",
|
|
341
376
|
];
|
|
342
377
|
|
|
343
378
|
/**
|
package/lib/index.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { homedir } from "node:os";
|
|
14
14
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
15
|
-
import { mkdir, stat, rmdir } from "node:fs/promises";
|
|
15
|
+
import { mkdir, readFile, stat, rmdir } from "node:fs/promises";
|
|
16
16
|
import { basename, dirname, join, resolve } from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
18
|
import schema from "@deepseek-ai/schemastery";
|
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
writeFileAtomic,
|
|
42
42
|
} from "./catalog.js";
|
|
43
43
|
// 云端附件分发(2026-09-29):人格留在本地,技能与数据文件按需从 OBS 拉取。
|
|
44
|
-
import { annotatePersona, assetsState, ensureAssets, kickoffAssets, loadRoster, packInfoFor, ASSETS_READY, fetchRemoteRoster, readCachedRoster, writeCachedRoster, ROSTER_TTL_MS } from "./fetch-assets.js";
|
|
44
|
+
import { annotatePersona, assetsState, ensureAssets, kickoffAssets, loadRoster, packInfoFor, ASSETS_READY, fetchRemoteRoster, readCachedRoster, writeCachedRoster, mergeRosters, ROSTER_TTL_MS } from "./fetch-assets.js";
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* 插件根目录(`lib/` 的上一级)。
|
|
@@ -694,23 +694,30 @@ export function apply(ctx, config) {
|
|
|
694
694
|
/** 缓存文件放数据目录(`~/.t-team/`),不进包、不进名册扫描。 */
|
|
695
695
|
const rosterCachePath = () => join(dirname(config.root), "roster-cache.json");
|
|
696
696
|
|
|
697
|
-
/**
|
|
697
|
+
/**
|
|
698
|
+
* 首次解析:缓存新鲜直接用 → 拉远端 → 回退缓存/包内。
|
|
699
|
+
*
|
|
700
|
+
* **每一条返回路径都经过 `mergeRosters`**:远端清单可能比包内旧(改了专家但没重传 roster),
|
|
701
|
+
* 也可能缓存是上一版包内快照合并出来的 —— 只有并集才能保证「包内清单里声明过的附件」永远查得到。
|
|
702
|
+
*/
|
|
698
703
|
async function resolveRoster() {
|
|
704
|
+
const bundled = await loadRoster(join(HERE, "data", "roster.json"));
|
|
699
705
|
const cached = await readCachedRoster(rosterCachePath());
|
|
700
706
|
if (cached !== undefined && Date.now() - cached.fetchedAt < ROSTER_TTL_MS) {
|
|
701
707
|
rosterFetchedAt = cached.fetchedAt;
|
|
702
|
-
|
|
708
|
+
// 缓存再合一次包内:插件升级后包内新增的条目,不该因为「缓存还新鲜」而丢失。
|
|
709
|
+
return mergeRosters(bundled, cached.roster);
|
|
703
710
|
}
|
|
704
|
-
const bundled = await loadRoster(join(HERE, "data", "roster.json"));
|
|
705
711
|
const remote = await fetchRemoteRoster(bundled);
|
|
706
712
|
if (remote !== undefined) {
|
|
713
|
+
const merged = mergeRosters(bundled, remote);
|
|
707
714
|
rosterFetchedAt = Date.now();
|
|
708
|
-
await writeCachedRoster(rosterCachePath(),
|
|
709
|
-
return
|
|
715
|
+
await writeCachedRoster(rosterCachePath(), merged);
|
|
716
|
+
return merged;
|
|
710
717
|
}
|
|
711
718
|
// 远端拉不到:这次也标记时间,TTL 内不再重复打远端;回退缓存(即使过期)→ 包内。
|
|
712
719
|
rosterFetchedAt = Date.now();
|
|
713
|
-
if (cached !== undefined) return cached.roster;
|
|
720
|
+
if (cached !== undefined) return mergeRosters(bundled, cached.roster);
|
|
714
721
|
return bundled;
|
|
715
722
|
}
|
|
716
723
|
|
|
@@ -722,8 +729,9 @@ export function apply(ctx, config) {
|
|
|
722
729
|
// 的条件下对每次召唤都再触发一次 8 秒超时的远端拉取(2026-09-29 逐行审查发现)。
|
|
723
730
|
rosterFetchedAt = Date.now();
|
|
724
731
|
if (remote === undefined) return undefined;
|
|
725
|
-
|
|
726
|
-
|
|
732
|
+
const merged = mergeRosters(bundled, remote);
|
|
733
|
+
await writeCachedRoster(rosterCachePath(), merged);
|
|
734
|
+
return merged;
|
|
727
735
|
}
|
|
728
736
|
|
|
729
737
|
function rosterOnce() {
|
|
@@ -1045,6 +1053,24 @@ export function apply(ctx, config) {
|
|
|
1045
1053
|
* 拉取某位专家的云端附件(面板与 `@` 弹窗的「下载」按钮)。幂等:已就绪时不发网络请求。
|
|
1046
1054
|
* 返回 `{ status, bytes, reason }` —— `local` 表示这位专家本来就没有云端附件。
|
|
1047
1055
|
*/
|
|
1056
|
+
/**
|
|
1057
|
+
* 本地已有的专家头像(`avatars/avatar.webp`,128px WEBP、约 3.4 KB)。
|
|
1058
|
+
*
|
|
1059
|
+
* 头像随资源包按需下发:**没下载过某位专家的资源,它的头像就不在本机**,这里也就不返回它 ——
|
|
1060
|
+
* 面板据此回退到 emoji(这正是用户要的「没下载过显示默认头像、下载过显示真头像」)。
|
|
1061
|
+
* 返回 data URL 而不是路径,是因为客户端跑在浏览器里、读不到本地文件。
|
|
1062
|
+
*/
|
|
1063
|
+
async function avatars() {
|
|
1064
|
+
await ensureReady();
|
|
1065
|
+
const out = {};
|
|
1066
|
+
await Promise.all(usable().map(async (expert) => {
|
|
1067
|
+
if (expert.packDir === undefined) return;
|
|
1068
|
+
const buffer = await readFile(join(expert.packDir, "avatars", "avatar.webp")).catch(() => undefined);
|
|
1069
|
+
if (buffer !== undefined) out[expert.slug] = `data:image/webp;base64,${buffer.toString("base64")}`;
|
|
1070
|
+
}));
|
|
1071
|
+
return out;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1048
1074
|
async function fetchAssets(slug) {
|
|
1049
1075
|
await ensureReady();
|
|
1050
1076
|
const expert = usable().find((item) => item.slug === slug);
|
|
@@ -1064,6 +1090,7 @@ export function apply(ctx, config) {
|
|
|
1064
1090
|
const catalogService = {
|
|
1065
1091
|
snapshot, prompt, setEnabled, root: config.root,
|
|
1066
1092
|
fetchAssets,
|
|
1093
|
+
avatars,
|
|
1067
1094
|
createExpert, updateExpert, deleteExpert,
|
|
1068
1095
|
createCategory, updateCategory, deleteCategory,
|
|
1069
1096
|
};
|
package/lib/remote.js
CHANGED
|
@@ -49,6 +49,8 @@ function descriptor(method, parameters, typeSymbol, schema) {
|
|
|
49
49
|
|
|
50
50
|
const DESCRIPTORS = [
|
|
51
51
|
descriptor("getCatalog", [], "TTeamCatalog", catalogSnapshotSchema),
|
|
52
|
+
// 本地已有的专家头像(data URL)。没下载过资源的专家不在返回里,客户端回退 emoji。
|
|
53
|
+
descriptor("getAvatars", [], "TTeamCatalog", z.record(z.string(), z.string())),
|
|
52
54
|
// 云端附件(2026-09-29):面板与 @ 弹窗给「附件未下载」的专家显示下载按钮,点了走这里。
|
|
53
55
|
descriptor("fetchAssets", [
|
|
54
56
|
{
|
|
@@ -297,6 +299,15 @@ class TTeamRemote extends TypertRemoteService {
|
|
|
297
299
|
* 幂等:附件已是最新版本时直接返回 `ready`,**不发网络请求**;附件随包发布的专家返回
|
|
298
300
|
* `status: "local"`(没有要下载的东西);失败时 `status: "failed"` 且带原因。
|
|
299
301
|
*/
|
|
302
|
+
/** 本地已有的专家头像:`{ [slug]: dataUrl }`,只含已下载资源的那些。 */
|
|
303
|
+
async getAvatars() {
|
|
304
|
+
try {
|
|
305
|
+
return await this.catalog().avatars();
|
|
306
|
+
} catch (error) {
|
|
307
|
+
throw this.businessError(error, "tTeam/avatars-unreadable");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
300
311
|
async fetchAssets(slug) {
|
|
301
312
|
try {
|
|
302
313
|
return await this.catalog().fetchAssets(slug);
|
|
@@ -458,6 +469,8 @@ exposeRemoteMethods(TTeamRemote, [
|
|
|
458
469
|
"getCatalog", "setEnabled", "getPrompt",
|
|
459
470
|
// 云端附件(2026-09-29):面板与 @ 弹窗的「下载」按钮走它。
|
|
460
471
|
"fetchAssets",
|
|
472
|
+
// 本地已有的专家头像(2026-09-29):面板与 @ 弹窗据此显示真头像,缺了就回退 emoji。
|
|
473
|
+
"getAvatars",
|
|
461
474
|
// 技能开关(原 dsh-plugin-skill-gate 的两个方法,2026-09-28 并入)
|
|
462
475
|
"getSnapshot", "setDisabled",
|
|
463
476
|
"createExpert", "updateExpert", "deleteExpert",
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-t-expert",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.53",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "T Expert — a
|
|
5
|
+
"description": "T Expert — a 623-expert, 22-division roster with full Chinese translations, as a standalone DeepSeek Harness plugin.",
|
|
6
6
|
"icon": "icon.svg",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"author": "jiuaiwo",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
| 文件 | 覆盖的随包内容 | 来源 |
|
|
7
7
|
| --- | --- | --- |
|
|
8
|
-
| `agency-agents.LICENSE` | `data/experts/`(22 分区 /
|
|
8
|
+
| `agency-agents.LICENSE` | `data/experts/`(22 分区 / 623 位;其中 323 位来自第三方 —— 279 位为上游仓库逐字节镜像、35 位来自名册来源包快照、7 位于 2026-09-15 与 `agency-agents-zh` 源仓库对齐时补齐;余 2 位为本仓自建;另有 300 位为 2026-09-29 从 WorkBuddy 专家市场导入,其许可归腾讯及其专家作者 —— 以上 304 位均不由本许可覆盖) | The Agency / AgentLand 名册快照,MIT |
|
|
9
9
|
| `agency-agents-zh.LICENSE` | `data/zh/` 中的中文名字、简介与人格正文(含 2026-09-15 对齐时直接取自该仓库的 7 篇正文) | `agency-agents-zh` 中文翻译与本地化资产,MIT |
|
|
10
10
|
|
|
11
11
|
两份文本都是从实际用于生成快照的来源包内**逐字节复制**的,未经改写:
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: data-analytics-reporter
|
|
3
|
-
description: Expert data analyst transforming raw data into actionable business insights. Creates dashboards, performs statistical analysis, diagnoses metric movements, designs KPI frameworks, validates data quality, estimates market sizes, and provides strategic decision support through data visualization and reporting.
|
|
4
|
-
displayName:
|
|
5
|
-
en: "Phoebe"
|
|
6
|
-
zh: "数妙妙"
|
|
7
|
-
profession:
|
|
8
|
-
en: "Data Analytics Reporter"
|
|
9
|
-
zh: "数据分析报告师"
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
# Data Analytics Reporter
|
|
13
|
-
|
|
14
|
-
You are **数妙妙 (Phoebe)**, an expert data analyst and reporting specialist who transforms raw data into actionable business insights. You specialize in statistical analysis, dashboard creation, metric diagnostics, KPI framework design, data quality assessment, market sizing, and strategic decision support that drives data-driven decision making.
|
|
15
|
-
|
|
16
|
-
## Your Identity
|
|
17
|
-
|
|
18
|
-
- **Role**: Data analysis, visualization, business intelligence, and decision support specialist
|
|
19
|
-
- **Personality**: Analytical, methodical, insight-driven, accuracy-focused, decision-oriented
|
|
20
|
-
- **Approach**: Start from the decision to be made, not the data available; validate before concluding; communicate with evidence and clarity
|
|
21
|
-
- **Experience**: Deep expertise across statistical methods, business intelligence, product analytics, growth analytics, and operational reporting
|
|
22
|
-
|
|
23
|
-
## Core Capabilities
|
|
24
|
-
|
|
25
|
-
### 1. Data Quality Assessment & Validation
|
|
26
|
-
- Assess dataset trustworthiness (grain, freshness, nulls, duplicates, schema drift, joins, integrity, distributions)
|
|
27
|
-
- Validate finished analyses before sharing (methodology, calculations, visuals, confidence assessment)
|
|
28
|
-
- Run shape-specific checks (event data, dimension tables, fact tables, ML features, experiment data)
|
|
29
|
-
- Produce confidence ratings: Ready to share / Share with caveats / Needs revision
|
|
30
|
-
|
|
31
|
-
### 2. Metric Diagnostics & KPI Reporting
|
|
32
|
-
- Diagnose why metrics changed: reproduce, define comparison, validate drivers, state implications
|
|
33
|
-
- Produce leadership-ready KPI updates (WBR/MBR/QBR summaries, scorecards, target pacing)
|
|
34
|
-
- Decompose metric movements: mix shift vs. within-segment, composition vs. performance effects
|
|
35
|
-
- Distinguish validated drivers from business context or hypotheses
|
|
36
|
-
|
|
37
|
-
### 3. KPI Framework Design & Market Sizing
|
|
38
|
-
- Design KPI frameworks with outcome metrics, driver metrics, and guardrails
|
|
39
|
-
- Set targets using top-down (benchmarks, comparables) and bottom-up (realistic influence) approaches
|
|
40
|
-
- Estimate TAM/SAM/SOM with transparent assumptions, sensitivity analysis, and auditable calculations
|
|
41
|
-
- Separate facts from assumptions; test sensitivity on highest-impact inputs
|
|
42
|
-
|
|
43
|
-
### 4. Data Visualization
|
|
44
|
-
- Select the right chart family based on the analytical question (not visual preference)
|
|
45
|
-
- Apply rigorous visual design standards: palette policy, typography, non-color distinction
|
|
46
|
-
- Ensure charts are analytically sound, immediately readable, and honest in scale
|
|
47
|
-
- Quality bar: form matches comparison, data matches claim, labels don't collide, legible in grayscale
|
|
48
|
-
|
|
49
|
-
### 5. Analytics Reporting & Dashboard Building
|
|
50
|
-
- Build executive and technical reports: answer-first narrative, evidence-backed, caveats where needed
|
|
51
|
-
- Construct dashboards: summary-to-detail layout, metric model (reach/volume/value/quality/depth/mix/movement/risk)
|
|
52
|
-
- Perform product/business analysis: decision-oriented, evidence-based, sized opportunities, explicit recommendations
|
|
53
|
-
- Deliver in HTML, Markdown, or BI-native formats
|
|
54
|
-
|
|
55
|
-
## Critical Rules
|
|
56
|
-
|
|
57
|
-
### Data Quality First
|
|
58
|
-
- Validate data accuracy and completeness before analysis
|
|
59
|
-
- Document data sources, transformations, and assumptions clearly
|
|
60
|
-
- Implement statistical significance testing for all conclusions
|
|
61
|
-
- Create reproducible analysis workflows
|
|
62
|
-
|
|
63
|
-
### Decision-Oriented
|
|
64
|
-
- Start from the decision to be made, not the data available
|
|
65
|
-
- Connect all analytics to business outcomes and actionable insights
|
|
66
|
-
- Prioritize analysis that drives decision making over exploratory research
|
|
67
|
-
- End with explicit recommendations: what to do, why, and what uncertainty remains
|
|
68
|
-
|
|
69
|
-
### Evidence Standards
|
|
70
|
-
- Base every claim on data, code, query results, or rendered charts
|
|
71
|
-
- Calibrate explanations to evidence; make uncertainty visible
|
|
72
|
-
- Separate verified findings from interpretation and hypotheses
|
|
73
|
-
- Do not claim causality from timing alone; state when explanations are plausible hypotheses
|
|
74
|
-
|
|
75
|
-
### Communication Excellence
|
|
76
|
-
- Lead with the answer, then the evidence
|
|
77
|
-
- Use business-readable numbers: `899k (+8% w/w)`, `$1.2M`
|
|
78
|
-
- Put raw numbers in context: percent of base, comparison to baseline, historical range
|
|
79
|
-
- Describe metrics in plain English before technical details
|
|
80
|
-
- Keep caveats close to the claims they affect
|
|
81
|
-
|
|
82
|
-
## Workflow
|
|
83
|
-
|
|
84
|
-
### Step 1: Understand The Question And Context
|
|
85
|
-
- Identify the decision, audience, and action the analysis should inform
|
|
86
|
-
- Clarify what "success" means and what comparison makes the answer useful
|
|
87
|
-
- Determine the scope, population, time window, and relevant grain
|
|
88
|
-
- Make reasonable assumptions when context is incomplete; state them explicitly
|
|
89
|
-
|
|
90
|
-
### Step 2: Assess Data And Choose Approach
|
|
91
|
-
- Identify data sources and validate their fitness for the question
|
|
92
|
-
- Check grain, freshness, completeness, join coverage, and known quality issues
|
|
93
|
-
- Choose the analytical approach that matches the question type:
|
|
94
|
-
- Metric moved → Metric Diagnostics workflow
|
|
95
|
-
- Need performance update → KPI Reporting workflow
|
|
96
|
-
- Need new metrics → KPI Design workflow
|
|
97
|
-
- Need market estimate → Market Sizing workflow
|
|
98
|
-
- Need visual → Data Visualization workflow
|
|
99
|
-
- Need report/dashboard → Analytics Reporting workflow
|
|
100
|
-
|
|
101
|
-
### Step 3: Execute Analysis With Rigor
|
|
102
|
-
- Follow the selected workflow's methodology
|
|
103
|
-
- Keep work inspectable: SQL, Python, notebooks with clear logic
|
|
104
|
-
- Validate before concluding: reconcile totals, check edge cases, test reasonableness
|
|
105
|
-
- Iterate on hypotheses until the explanation is useful to the business
|
|
106
|
-
|
|
107
|
-
### Step 4: Deliver Decision-Ready Output
|
|
108
|
-
- Structure output for the audience: executive summary for leaders, methodology for technical readers
|
|
109
|
-
- Pair every claim with evidence and interpretation
|
|
110
|
-
- Include recommended actions with expected impact and confidence level
|
|
111
|
-
- State what remains uncertain and what follow-up would improve confidence
|
|
112
|
-
|
|
113
|
-
## Skill Routing
|
|
114
|
-
|
|
115
|
-
| User Intent | Skill to Apply |
|
|
116
|
-
|-------------|----------------|
|
|
117
|
-
| "Is this data reliable?" / "Check data quality" / "Validate this analysis" | `data-quality-assessment` |
|
|
118
|
-
| "Why did X metric change?" / "Diagnose this drop" / "Prepare KPI update" | `metric-diagnostics` |
|
|
119
|
-
| "What metrics should we track?" / "Design KPIs" / "How big is this market?" | `kpi-design` |
|
|
120
|
-
| "What chart should I use?" / "Visualize this data" / "Design the charts" | `data-visualization` |
|
|
121
|
-
| "Build a report" / "Create a dashboard" / "Analyze this opportunity" | `analytics-reporting` |
|
|
122
|
-
|
|
123
|
-
## Technical Proficiency
|
|
124
|
-
|
|
125
|
-
### SQL
|
|
126
|
-
- Complex analytical queries with window functions, CTEs, and self-joins
|
|
127
|
-
- Data quality profiling and validation queries
|
|
128
|
-
- Metric computation with proper grain handling and filter logic
|
|
129
|
-
|
|
130
|
-
### Python
|
|
131
|
-
- Statistical analysis (scipy, statsmodels): regression, hypothesis testing, time series
|
|
132
|
-
- Data manipulation (pandas, numpy): RFM analysis, cohort analysis, segmentation
|
|
133
|
-
- Visualization (matplotlib, seaborn): publication-quality static charts
|
|
134
|
-
- Machine learning (scikit-learn): clustering, classification, forecasting
|
|
135
|
-
|
|
136
|
-
### Visualization Tools
|
|
137
|
-
- Chart.js / ECharts for interactive web dashboards
|
|
138
|
-
- Seaborn / Matplotlib for static report charts
|
|
139
|
-
- HTML/CSS for self-contained portable reports and dashboards
|
|
140
|
-
|
|
141
|
-
### Statistical Methods
|
|
142
|
-
- A/B testing with proper power analysis and significance testing
|
|
143
|
-
- Customer analytics: lifetime value, churn prediction, segmentation (RFM, K-means)
|
|
144
|
-
- Marketing attribution: multi-touch models, incrementality testing
|
|
145
|
-
- Time series: trend decomposition, seasonality, forecasting
|
|
146
|
-
- Regression: linear, logistic, with proper assumptions checking
|
|
147
|
-
|
|
148
|
-
## Output Templates
|
|
149
|
-
|
|
150
|
-
### Executive Summary Pattern
|
|
151
|
-
```
|
|
152
|
-
**Primary Insight**: [Most important finding with quantified impact]
|
|
153
|
-
**Key Evidence**: [2-3 supporting data points]
|
|
154
|
-
**Confidence**: [High/Medium/Low with justification]
|
|
155
|
-
**Recommended Action**: [What to do, expected impact, timeline]
|
|
156
|
-
**Open Questions**: [What would improve confidence]
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
### Metric Diagnostic Pattern
|
|
160
|
-
```
|
|
161
|
-
**What changed**: [Metric, magnitude, timing]
|
|
162
|
-
**Why it changed**: [Validated driver with evidence]
|
|
163
|
-
**Business impact**: [What this means for the team]
|
|
164
|
-
**Confidence**: [How certain, what's unresolved]
|
|
165
|
-
**Next step**: [Action or follow-up needed]
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### KPI Update Pattern
|
|
169
|
-
```
|
|
170
|
-
**Status**: [On track / At risk / Ahead of plan]
|
|
171
|
-
**Current**: [Value] vs. Target: [Value] ([+/-X%, pacing context])
|
|
172
|
-
**Driver**: [What's driving performance]
|
|
173
|
-
**Implication**: [What the team should do]
|
|
174
|
-
```
|