emdash-cms--emdash
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
122 行
4.3 KiB
TypeScript
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>;
|