Files
fusion/plugins/fusion-plugin-quality/src/runner/command-presets.ts
gsxdsm e3f98253cc feat: Quality plugin — Task QA tab, preview servers, tests, and suggested cases (#2127)
## Summary

Adds a bundled **Quality** plugin (`fusion-plugin-quality`) that makes
task QA easier and more visual:

- **Task QA tab** (action-first): preview/test server for the task
worktree, allowlisted test runs, report viewer, screenshots CTA,
suggested test cases, CI handoff
- **Quality hub** (left sidebar): project-wide run history and preset
launches
- Host **task-detail slot context** (`taskId`, worktree, `projectId`) so
plugin tabs can scope correctly
- `superviseSpawn` re-exported on the plugin packaging shim for
published plugins
- Plan: `docs/plans/2026-07-14-001-feat-quality-plugin-plan.md`

## Design constraints

- Does **not** replace the merge gate — advisory orchestration only
- Composes Dev Server process patterns and artifact registry (no second
browser stack)
- Never free-form shell; never port 4040
- Full-suite requires explicit confirm

## Test plan

- [x] `pnpm --filter @fusion-plugin-examples/quality test` (15 tests)
- [x] PluginSlot unit tests still pass
- [ ] Enable Quality plugin in dashboard Settings → Built-in Plugins
- [ ] Open Task Detail → **QA** tab with a worktree; start preview, run
verify:fast, generate suggestions
- [ ] Open left sidebar **Quality** hub and list runs
- [ ] Confirm merge gate / PR checks unchanged

## Residual / follow-up (same plan, later units)

- Deeper hub CI (host route)
- Full browser-verification toggle UX + agent QA sessions (U7/U9/U10)
- Richer screenshots gallery wiring to live artifacts API
- Test plans CRUD polish

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added the Quality plugin with a project Quality hub and task-focused
QA tab.
* Added test runs, reports, preview server controls, suggested test
cases, and run history.
* Added configurable test presets, cancellation, status tracking, and
safe command execution.
* Added experimental-feature controls for enabling Quality
functionality.
* Bundled Quality with the CLI and made it available through the plugin
manager.

* **Documentation**
* Added Quality plugin guidance, terminology, configuration details, and
implementation planning documentation.

* **Bug Fixes**
* Improved process supervision so command failures and shutdown timers
are handled safely.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-15 20:28:11 -07:00

106 lines
3.7 KiB
TypeScript

import type { QualityPresetId } from "../store/quality-types.js";
/*
FNXC:Quality 2026-07-14-21:45:
Allowlisted preset id → command mapping. Server resolves only; clients never supply command/argv/cwd.
full-suite requires explicit confirmFullSuite. file-scoped builds from server-side path list.
*/
export const QUALITY_PRESET_IDS: readonly QualityPresetId[] = [
"project-test",
"test-gate",
"verify-fast",
"file-scoped",
"full-suite",
] as const;
export function isQualityPresetId(value: unknown): value is QualityPresetId {
return typeof value === "string" && (QUALITY_PRESET_IDS as readonly string[]).includes(value);
}
export interface ResolvePresetInput {
preset: QualityPresetId;
/** Project settings.testCommand when set */
testCommand?: string | null;
/** Absolute project root */
projectRoot: string;
/** File paths relative to project/worktree for file-scoped preset */
filePaths?: string[];
confirmFullSuite?: boolean;
}
export type ResolvePresetResult =
| { ok: true; command: string; label: string }
| { ok: false; reason: string; code: "unknown_preset" | "disabled" | "confirm_required" | "empty_files" };
/**
* Reject path tokens that could escape the worktree or inject shell metacharacters.
*/
export function isSafeFilePathToken(path: string): boolean {
if (!path || typeof path !== "string") return false;
if (path.includes("\0") || path.includes("\n") || path.includes("\r")) return false;
if (path.startsWith("/") || path.includes("..")) return false;
// Disallow shell metacharacters when paths are joined into a shell command string.
if (/[;&|`$<>\\]/.test(path)) return false;
return true;
}
export function resolvePresetCommand(input: ResolvePresetInput): ResolvePresetResult {
switch (input.preset) {
case "project-test": {
const cmd = (input.testCommand ?? "").trim();
if (!cmd) {
return {
ok: false,
reason: "Project testCommand is not configured",
code: "disabled",
};
}
return { ok: true, command: cmd, label: "Project test" };
}
case "test-gate":
return { ok: true, command: "pnpm test:gate", label: "Merge gate tests" };
case "verify-fast":
return { ok: true, command: "pnpm verify:fast", label: "Verify fast (test-free)" };
case "file-scoped": {
const paths = (input.filePaths ?? []).map((p) => p.trim()).filter(Boolean);
if (paths.length === 0) {
return { ok: false, reason: "No changed files for file-scoped run", code: "empty_files" };
}
const safe = paths.filter(isSafeFilePathToken);
if (safe.length === 0) {
return { ok: false, reason: "No safe file paths for file-scoped run", code: "empty_files" };
}
// Prefer vitest path list; keep command server-built.
const joined = safe.map((p) => JSON.stringify(p)).join(" ");
return {
ok: true,
command: `pnpm exec vitest run ${joined}`,
label: "File-scoped tests",
};
}
case "full-suite": {
if (!input.confirmFullSuite) {
return {
ok: false,
reason: "full-suite requires confirmFullSuite: true",
code: "confirm_required",
};
}
return { ok: true, command: "pnpm test:full", label: "Full suite (opt-in)" };
}
default:
return { ok: false, reason: "Unknown preset", code: "unknown_preset" };
}
}
export function listPresetCatalog(): Array<{ id: QualityPresetId; label: string; needsConfirm?: boolean }> {
return [
{ id: "project-test", label: "Project test" },
{ id: "test-gate", label: "Merge gate (test:gate)" },
{ id: "verify-fast", label: "Verify fast" },
{ id: "file-scoped", label: "File-scoped" },
{ id: "full-suite", label: "Full suite", needsConfirm: true },
];
}