Merge current main (fast-tests quality-backfill projects) into plugin install fixes
This commit is contained in:
@@ -16,9 +16,10 @@ These tools are **not** part of the user-invokable extension surface. They are i
|
||||
| `fn_task_document_write` | triage, executor, heartbeat | Save/update a named task document revision | `key` (string), `content` (string), `author?` (string) |
|
||||
| `fn_task_document_read` | triage, executor, heartbeat | Read one task document or list all | `key?` (string) |
|
||||
| `fn_workflow_list` | executor | List the project's custom workflows (read-only built-ins plus user definitions) | none |
|
||||
| `fn_workflow_get` | executor | Fetch one workflow definition by id — name, description, builtin flag, and the full IR (nodes/edges/columns/artifacts/fields) as JSON | `workflow_id` (string) |
|
||||
| `fn_workflow_select` | executor | Assign a custom workflow to a task (defaults to the current task) | `workflow_id` (string), `task_id?` (string) |
|
||||
| `fn_workflow_create` | executor | Create a custom workflow definition from a graph IR (validated server-side) | `name` (string), `description?` (string), `ir` (object), `layout?` (object) |
|
||||
| `fn_workflow_update` | executor | Update a custom workflow definition's name/description/ir/layout (built-ins cannot be edited) | `workflow_id` (string), `name?` (string), `description?` (string), `ir?` (object), `layout?` (object), `rehome_to?` (string) |
|
||||
| `fn_workflow_create` | executor | Create a custom workflow definition from a graph IR (validated server-side). v2 IR supports step-inversion constructs: `parse-steps`, `foreach` (mode/isolation/concurrency/maxReworkCycles), `step-execute`, `step-review`, `code` nodes, `rework` edges, plus `artifacts` and custom `fields` declarations | `name` (string), `description?` (string), `ir` (object), `layout?` (object) |
|
||||
| `fn_workflow_update` | executor | Update a custom workflow definition's name/description/ir/layout (built-ins cannot be edited; same step-inversion IR constructs as create; editing `fields` orphans rather than destroys existing task values) | `workflow_id` (string), `name?` (string), `description?` (string), `ir?` (object), `layout?` (object), `rehome_to?` (string) |
|
||||
| `fn_workflow_delete` | executor | Delete a custom workflow definition (built-ins cannot be deleted); selecting tasks are re-homed to the default workflow's entry column | `workflow_id` (string) |
|
||||
| `fn_task_promote` | executor | Promote a held task out of a manual-release hold column (defaults to the current task) | `task_id?` (string) |
|
||||
| `fn_trait_list` | executor | List the registered column trait catalog (built-in and plugin traits) | none |
|
||||
@@ -58,7 +59,7 @@ Note: step-session execution (`step-session-executor.ts`) reuses executor coordi
|
||||
|
||||
| Tool | Purpose | Parameters |
|
||||
|---|---|---|
|
||||
| `fn_task_update` | Update a spec step status (`pending`/`in-progress`/`done`/`skipped`) | `step` (number), `status` (enum) |
|
||||
| `fn_task_update` | Update a spec step status (`pending`/`in-progress`/`done`/`skipped`), task dependencies, and/or workflow-defined custom field values | `step?` (number, 1-indexed), `status?` (enum), `dependencies?` (string[]), `custom_fields?` (object keyed by field id; validated against the workflow field schema, `null` clears a field) |
|
||||
| `fn_task_add_dep` | Add a dependency to current task (confirmation-gated) | `task_id` (string), `confirm?` (boolean) |
|
||||
| `fn_task_done` | Mark task complete and optionally store summary | `summary?` (string) |
|
||||
| `fn_review_step` | Spawn step plan/code reviewer | `step` (number), `type` (`plan` \| `code`), `step_name` (string), `baseline?` (string) |
|
||||
|
||||
@@ -276,12 +276,18 @@ describe("Workspace bootstrap script contract", () => {
|
||||
const defaultTest = dashboardPkg.scripts?.test;
|
||||
const defaultAppQuality = dashboardPkg.scripts?.["test:quality:app"];
|
||||
const defaultApiQuality = dashboardPkg.scripts?.["test:quality:api"];
|
||||
const apiCurated = dashboardPkg.scripts?.["test:quality:api:curated"];
|
||||
const deepTest = dashboardPkg.scripts?.["test:deep"];
|
||||
|
||||
expect(defaultTest).toBe("pnpm run test:quality:app && pnpm run test:quality:api");
|
||||
expect(defaultAppQuality).toContain("test:quality:app:foundation-api");
|
||||
expect(defaultAppQuality).toContain("test:quality:app:settings");
|
||||
expect(hasProjectArg(defaultApiQuality, "dashboard-api-quality")).toBe(true);
|
||||
// The api lane chains curated + backfill sub-lanes; the curated sub-lane
|
||||
// carries the explicit quality project, and the backfill lane is the
|
||||
// curated-gate completeness net (broad glob minus curated minus skip-list).
|
||||
expect(defaultApiQuality).toContain("test:quality:api:curated");
|
||||
expect(defaultApiQuality).toContain("test:quality:api:backfill");
|
||||
expect(hasProjectArg(apiCurated, "dashboard-api-quality")).toBe(true);
|
||||
expect(hasProjectArg(defaultTest, "dashboard-app")).toBe(false);
|
||||
expect(hasProjectArg(defaultTest, "dashboard-api")).toBe(false);
|
||||
|
||||
|
||||
@@ -166,7 +166,10 @@ afterEach(() => {
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
async function waitForFrameContains(lastFrame: () => string | undefined, text: string, timeoutMs = 3000) {
|
||||
// 10s bound: ink schedules frames on timer ticks and has flaked past 3s under
|
||||
// loaded CI shards while passing instantly in isolation. vi.waitFor polls, so
|
||||
// a generous bound adds zero time to passing runs.
|
||||
async function waitForFrameContains(lastFrame: () => string | undefined, text: string, timeoutMs = 10_000) {
|
||||
await vi.waitFor(() => {
|
||||
expect(lastFrame() ?? "").toContain(text);
|
||||
}, { timeout: timeoutMs });
|
||||
|
||||
@@ -1613,6 +1613,15 @@ function TaskDetailScreen({
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Card-placed custom fields (U13/KTD-14): read-only bracketed labels. */}
|
||||
{detail.customFields && detail.customFields.length > 0 && (
|
||||
<Box flexDirection="row" gap={1} flexWrap="wrap" flexShrink={0}>
|
||||
{detail.customFields.map((f) => (
|
||||
<Text key={f.label} color="magenta">[{f.label}: {f.value}]</Text>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box height={1} flexShrink={0} />
|
||||
|
||||
{/* Steps section */}
|
||||
|
||||
@@ -230,6 +230,10 @@ export interface TaskDetailData {
|
||||
currentStepIndex?: number;
|
||||
steps: TaskStep[];
|
||||
recentLogs: TaskLogEntry[]; // last ~200 entries on initial load
|
||||
/** Card-placed custom field values, pre-rendered as read-only bracketed
|
||||
* labels for the task detail view (U13/KTD-14). Absent/empty when the
|
||||
* workflow declares no card fields or none have values. */
|
||||
customFields?: Array<{ label: string; value: string }>;
|
||||
}
|
||||
|
||||
export type TaskEvent =
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
isWorkflowColumnsEnabled,
|
||||
resolveColumnFlags,
|
||||
BUILTIN_CODING_WORKFLOW_IR,
|
||||
parseWorkflowIr,
|
||||
type WorkflowIrColumn,
|
||||
type TraitFlags,
|
||||
} from "@fusion/core";
|
||||
@@ -2742,6 +2743,48 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
text: entry.outcome ? `${entry.action} → ${entry.outcome}` : entry.action,
|
||||
source: entry.runContext?.agentId ? "agent" : "executor",
|
||||
}));
|
||||
// Card-placed custom fields → read-only bracketed labels
|
||||
// (U13/KTD-14). Resolve the task's workflow IR, filter
|
||||
// card-placed field defs, and render any present values.
|
||||
// Best-effort: any resolution failure simply omits the chips.
|
||||
let customFields: Array<{ label: string; value: string }> | undefined;
|
||||
try {
|
||||
const values = (t as { customFields?: Record<string, unknown> }).customFields;
|
||||
if (values && Object.keys(values).length > 0) {
|
||||
const selection = projectStore.getTaskWorkflowSelection(t.id);
|
||||
const def = selection?.workflowId
|
||||
? await projectStore.getWorkflowDefinition(selection.workflowId)
|
||||
: undefined;
|
||||
const ir = def
|
||||
? (typeof def.ir === "string" ? parseWorkflowIr(def.ir) : def.ir)
|
||||
: BUILTIN_CODING_WORKFLOW_IR;
|
||||
const fields = ir.version === "v2" ? (ir.fields ?? []) : [];
|
||||
const chips: Array<{ label: string; value: string }> = [];
|
||||
for (const field of fields) {
|
||||
if (field.render?.placement !== "card") continue;
|
||||
const raw = values[field.id];
|
||||
if (raw === undefined || raw === null || raw === "") continue;
|
||||
const optLabel = (v: string): string =>
|
||||
field.options?.find((o) => o.value === v)?.label ?? v;
|
||||
let display: string;
|
||||
if (field.type === "boolean") {
|
||||
if (raw !== true) continue;
|
||||
display = field.name;
|
||||
} else if (field.type === "multi-enum" && Array.isArray(raw)) {
|
||||
if (raw.length === 0) continue;
|
||||
display = raw.map((v) => optLabel(String(v))).join(", ");
|
||||
} else if (field.type === "enum") {
|
||||
display = optLabel(String(raw));
|
||||
} else {
|
||||
display = String(raw);
|
||||
}
|
||||
chips.push({ label: field.name, value: display });
|
||||
}
|
||||
if (chips.length > 0) customFields = chips;
|
||||
}
|
||||
} catch {
|
||||
customFields = undefined;
|
||||
}
|
||||
return {
|
||||
id: t.id,
|
||||
title: t.title,
|
||||
@@ -2753,6 +2796,7 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
|
||||
currentStepIndex: t.currentStep,
|
||||
steps,
|
||||
recentLogs,
|
||||
...(customFields ? { customFields } : {}),
|
||||
};
|
||||
} catch {
|
||||
// Task not found (deleted/archived between selection and fetch).
|
||||
|
||||
Reference in New Issue
Block a user