CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / integration (push) Successful in 50s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / Python tests (push) Successful in 1m44s
CI & Build / Build & push image (push) Successful in 36s
#4769 "Rulings are counted where someone will read them": milestone 444 step 4 wrote system_usage_events and nothing read it. - retrieval_telemetry gains a `system_usage` block: surfacings and opens by source, distinct counts, and `by_system` naming the areas most shown. There is deliberately no pull-through ratio, because rulings travel in full in the line and opens are the exception. - usage_for_systems (one GROUP BY) adds `usage` to the REST Systems list and detail, and to MCP get_system. MCP list_systems is unchanged. - The Systems UI shows a "rulings shown N×" chip. - rulings_pre_tool, rulings_write_path and mcp_get_system are now declared registry points; the registry guard covers their recorders. - The Systems store merges a PATCH reply instead of replacing the row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
105 lines
3.1 KiB
TypeScript
105 lines
3.1 KiB
TypeScript
import { apiGet, apiPost, apiPatch, apiDelete } from "@/api/client";
|
|
import type { CanonicalMatch } from "@/api/canonicalSystems";
|
|
import type { RecordUsage } from "@/types/usage";
|
|
|
|
export interface System {
|
|
id: number;
|
|
project_id: number;
|
|
name: string;
|
|
/**
|
|
* The global area this System is an instance of, or null. Null is a valid
|
|
* resting state — a project-specific area should stay unmapped.
|
|
*/
|
|
canonical_id: number | null;
|
|
description: string;
|
|
color: string | null;
|
|
status: "active" | "archived";
|
|
order_index: number;
|
|
/**
|
|
* The files that are this area, as globs relative to the repo root: `*`
|
|
* within one directory, `**` across any depth, a plain directory covering
|
|
* everything under it. Empty means the area has not named its files.
|
|
*/
|
|
path_patterns: string[];
|
|
open_issue_count: number;
|
|
/**
|
|
* How often this area's rulings were shown to a session because its files
|
|
* were touched (`surfaced_count`), and how often it was opened (#4769).
|
|
* Present on the list and detail reads; optional because a System returned
|
|
* by a create or update carries none.
|
|
*/
|
|
usage?: RecordUsage;
|
|
created_at: string | null;
|
|
updated_at: string | null;
|
|
}
|
|
|
|
export async function listSystems(projectId: number): Promise<System[]> {
|
|
const data = await apiGet<{ systems: System[] }>(`/api/projects/${projectId}/systems`);
|
|
return data.systems;
|
|
}
|
|
|
|
/**
|
|
* A created System, plus the catalog's answer about its name. An `exact`
|
|
* catalog hit is applied by the server and arrives as a populated
|
|
* `canonical_id`; an `overlap` is only OFFERED, and comes back here for the
|
|
* caller to accept or ignore.
|
|
*
|
|
* A same-named System in this project is a 409 ApiError carrying
|
|
* `{duplicate, existing_id}` — the same gate the MCP door enforces (#2482).
|
|
*/
|
|
export interface CreatedSystem extends System {
|
|
canonical_suggestion?: CanonicalMatch;
|
|
}
|
|
|
|
export async function createSystem(
|
|
projectId: number,
|
|
data: {
|
|
name: string;
|
|
description?: string;
|
|
color?: string;
|
|
canonical_id?: number;
|
|
path_patterns?: string[];
|
|
},
|
|
): Promise<CreatedSystem> {
|
|
return apiPost(`/api/projects/${projectId}/systems`, data);
|
|
}
|
|
|
|
export async function updateSystem(
|
|
projectId: number,
|
|
systemId: number,
|
|
data: Partial<{
|
|
name: string;
|
|
description: string;
|
|
color: string | null;
|
|
status: "active" | "archived";
|
|
order_index: number;
|
|
path_patterns: string[];
|
|
}>,
|
|
): Promise<System> {
|
|
return apiPatch(`/api/projects/${projectId}/systems/${systemId}`, data);
|
|
}
|
|
|
|
export async function deleteSystem(projectId: number, systemId: number): Promise<void> {
|
|
return apiDelete(`/api/projects/${projectId}/systems/${systemId}`);
|
|
}
|
|
|
|
// Lightweight issue shape returned by the project-issues list endpoint.
|
|
export interface TaskLike {
|
|
id: number;
|
|
title: string;
|
|
status: string;
|
|
priority: string;
|
|
systems?: System[];
|
|
updated_at?: string | null;
|
|
}
|
|
|
|
export async function getProjectIssues(
|
|
projectId: number,
|
|
openOnly = true,
|
|
): Promise<TaskLike[]> {
|
|
const data = await apiGet<{ issues: TaskLike[] }>(
|
|
`/api/projects/${projectId}/issues?open_only=${openOnly}`,
|
|
);
|
|
return data.issues;
|
|
}
|