项目文件夹

文件
wehub-resource-sync b3a7f98e5a
CI / E2E Cloudflare (4/8) (push) Failing after 0s
CI / E2E Cloudflare (8/8) (push) Failing after 1s
CI / Lint (push) Failing after 1s
Auto Extract / Extract (push) Failing after 4s
CI / Version Check (push) Failing after 9s
CI / Integration Tests (push) Failing after 1s
CI / E2E tests (1/8) (push) Failing after 1s
CI / E2E tests (2/8) (push) Failing after 2s
CI / E2E tests (3/8) (push) Failing after 2s
CI / Browser Tests (push) Failing after 1s
CI / E2E tests (5/8) (push) Failing after 1s
CI / E2E Cloudflare (2/8) (push) Failing after 2s
CI / Typecheck (push) Failing after 1s
CI / Changeset Validation (push) Failing after 2s
CI / E2E Cloudflare (5/8) (push) Failing after 1s
CI / E2E Cloudflare (6/8) (push) Failing after 1s
CI / E2E Cloudflare (7/8) (push) Failing after 1s
CodeQL / Analyze (javascript-typescript) (push) Failing after 1s
Format / Format (push) Failing after 0s
CodeQL / Analyze (actions) (push) Failing after 4s
CI / E2E tests (4/8) (push) Failing after 1s
CI / E2E tests (6/8) (push) Failing after 1s
CI / E2E tests (7/8) (push) Failing after 2s
CI / E2E tests (8/8) (push) Failing after 1s
CI / E2E Cloudflare (1/8) (push) Failing after 1s
CI / E2E Cloudflare (3/8) (push) Failing after 2s
Preview Releases / Publish Preview (push) Failing after 0s
zizmor / Run zizmor (push) Failing after 1s
Release / Release (push) Failing after 2s
CI / Smoke Tests (push) Failing after 5m36s
CI / Tests (push) Failing after 6m36s
Release / Sync Templates (push) Has been skipped
CI / E2E Tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:23:53 +08:00

122 行
4.3 KiB
TypeScript

// Lightweight classifier shared between investigate and classify-reply
// workflows. Uses kimi-k2.7-code via our Cloudflare AI Gateway -- cheap and
// fast for structured classification tasks.
import { writeFileSync } from "node:fs";
import { createAgent } from "@flue/runtime";
import * as v from "valibot";
/**
* Shared classifier agent. Default sandbox (in-memory, no host access).
* Used for cheap structured-output prompts that don't need a shell.
*/
export const classifier = createAgent(() => ({
model: "cloudflare-ai-gateway/workers-ai/@cf/moonshotai/kimi-k2.7-code",
}));
/**
* Persist a classifier result to `CLASSIFY_RESULT_PATH` (set by the calling
* workflow) so the GitHub Actions orchestrator reads it from a file instead of
* scraping it out of `flue run`'s stdout. flue interleaves build-log lines and
* pretty-prints the returned value, which defeats both line- and slurp-based
* stdout parsing -- the parse then silently falls back to `unclear`, stranding
* every reply. Mirrors investigate.ts's `INVESTIGATE_RESULT_PATH` handoff. When
* the env var is unset (local prototyping) the write is skipped.
*/
export function persistClassifierResult<T>(result: T): T {
const path = process.env.CLASSIFY_RESULT_PATH;
if (path) {
try {
writeFileSync(path, JSON.stringify(result));
} catch (error) {
console.error("[classify] failed to write result file:", error);
}
}
return result;
}
/**
* Schema for the issue-classification step that runs at the top of the
* investigate pipeline. The orchestrator uses the classification to
* pick which `repro-*` sub-skill to invoke and to decide whether to
* skip non-bug issues entirely.
*/
export const issueClassificationSchema = v.object({
kind: v.pipe(
v.picklist(["bug", "enhancement", "documentation", "question"]),
v.description("What kind of issue this is. Only `bug` triggers the full pipeline."),
),
area: v.pipe(
v.picklist(["api", "admin", "public", "migration", "build", "other"]),
v.description("Which part of EmDash the issue lives in. Drives sub-skill choice."),
),
requiresBrowser: v.pipe(
v.boolean(),
v.description(
"True for admin or public bugs; selects between agent-browser and pure CLI repro.",
),
),
summary: v.pipe(
v.string(),
v.minLength(10),
v.maxLength(200),
v.description("One-sentence factual summary of the reported behaviour."),
),
});
export type IssueClassification = v.InferOutput<typeof issueClassificationSchema>;
/**
* Schema for the reporter-reply classifier. Decides whether the issue
* author's reply confirms the fix worked, says it didn't, or is
* ambiguous and needs a clarifying ask.
*/
export const replyClassificationSchema = v.object({
classification: v.pipe(
v.picklist(["positive", "negative", "unclear"]),
v.description(
"positive: the reporter confirms the fix works. negative: it doesn't, or the fix is wrong. unclear: neither clearly stated.",
),
),
reasoning: v.pipe(
v.string(),
v.minLength(5),
v.maxLength(400),
v.description("Short justification quoting the relevant phrase from the reply."),
),
});
export type ReplyClassification = v.InferOutput<typeof replyClassificationSchema>;
/**
* Schema for the maintainer-reply classifier. A maintainer addresses the
* bot (`@emdashbot ...`) on a triage issue; this maps their freeform
* instruction to one of a fixed set of intents the orchestrator can act
* on. The `directive` is the implementation guidance passed through to a
* directed investigate run -- never used to build identifiers or shell.
*/
export const maintainerIntentSchema = v.object({
intent: v.pipe(
v.picklist(["implement", "close", "takeover", "unclear"]),
v.description(
"implement: the maintainer wants the fix built (approving the proposal or naming a changed approach). close: not a bug / wontfix / by design. takeover: a human is taking over, the bot should disengage. unclear: no actionable instruction.",
),
),
directive: v.pipe(
v.string(),
v.maxLength(2000),
v.description(
"For implement: the concrete instruction to hand the fix agent (which option to take, what to change). Empty for close/takeover/unclear.",
),
),
reasoning: v.pipe(
v.string(),
v.minLength(5),
v.maxLength(400),
v.description("Short justification quoting the relevant phrase from the maintainer's comment."),
),
});
export type MaintainerIntent = v.InferOutput<typeof maintainerIntentSchema>;