mirror of
https://github.com/Kaelio/ktx.git
synced 2026-07-07 11:02:11 +02:00
feat(cli): clean up dev command surface (#57)
* feat(cli): clean up dev command surface * test: align CI expectations with CLI cleanup * test(cli): update slow test command expectations
This commit is contained in:
parent
46b013e32a
commit
b9e0a746af
59 changed files with 420 additions and 3918 deletions
|
|
@ -1,34 +1,10 @@
|
|||
import { loadKtxProject } from '@ktx/context/project';
|
||||
import {
|
||||
type ApplyLocalScanRelationshipReviewDecisionsResult,
|
||||
adviseLocalRelationshipFeedbackThresholds,
|
||||
applyLocalScanRelationshipReviewDecisions,
|
||||
calibrateLocalRelationshipFeedbackLabels,
|
||||
type ExportLocalRelationshipFeedbackLabelsResult,
|
||||
exportLocalRelationshipFeedbackLabels,
|
||||
formatKtxRelationshipFeedbackCalibrationMarkdown,
|
||||
formatKtxRelationshipFeedbackLabelsJsonl,
|
||||
formatKtxRelationshipThresholdAdviceMarkdown,
|
||||
getLocalScanReport,
|
||||
getLocalScanStatus,
|
||||
type KtxProgressPort,
|
||||
type KtxRelationshipArtifact,
|
||||
type KtxRelationshipArtifactEdge,
|
||||
type KtxRelationshipArtifactStatus,
|
||||
type KtxRelationshipDiagnosticsArtifact,
|
||||
type KtxRelationshipFeedbackCalibrationReport,
|
||||
type KtxRelationshipFeedbackDecisionFilter,
|
||||
type KtxRelationshipFeedbackLabel,
|
||||
type KtxRelationshipReviewDecisionValue,
|
||||
type KtxRelationshipThresholdAdviceReport,
|
||||
type KtxScanMode,
|
||||
type KtxScanReport,
|
||||
type KtxScanWarning,
|
||||
type LocalScanStatusResponse,
|
||||
readLocalScanRelationshipArtifacts,
|
||||
runLocalScan,
|
||||
type WriteLocalScanRelationshipReviewDecisionResult,
|
||||
writeLocalScanRelationshipReviewDecision,
|
||||
} from '@ktx/context/scan';
|
||||
import type { KtxCliIo } from './index.js';
|
||||
import { createKtxCliLocalIngestAdapters } from './local-adapters.js';
|
||||
|
|
@ -38,88 +14,21 @@ import { profileMark } from './startup-profile.js';
|
|||
|
||||
profileMark('module:scan');
|
||||
|
||||
export type KtxScanArgs =
|
||||
| {
|
||||
command: 'run';
|
||||
projectDir: string;
|
||||
connectionId: string;
|
||||
mode: KtxScanMode;
|
||||
detectRelationships: boolean;
|
||||
dryRun: boolean;
|
||||
databaseIntrospectionUrl?: string;
|
||||
cliVersion?: string;
|
||||
runtimeInstallPolicy?: KtxManagedPythonInstallPolicy;
|
||||
}
|
||||
| { command: 'status'; projectDir: string; runId: string }
|
||||
| { command: 'report'; projectDir: string; runId: string; json: boolean }
|
||||
| {
|
||||
command: 'relationships';
|
||||
projectDir: string;
|
||||
runId: string;
|
||||
status: KtxRelationshipArtifactStatus;
|
||||
json: boolean;
|
||||
limit: number;
|
||||
}
|
||||
| {
|
||||
command: 'relationshipDecision';
|
||||
projectDir: string;
|
||||
runId: string;
|
||||
candidateId: string;
|
||||
decision: KtxRelationshipReviewDecisionValue;
|
||||
reviewer: string;
|
||||
note: string | null;
|
||||
json: boolean;
|
||||
}
|
||||
| {
|
||||
command: 'relationshipApply';
|
||||
projectDir: string;
|
||||
runId: string;
|
||||
applyAllAccepted: boolean;
|
||||
candidateIds: string[];
|
||||
dryRun: boolean;
|
||||
json: boolean;
|
||||
}
|
||||
| {
|
||||
command: 'relationshipFeedback';
|
||||
projectDir: string;
|
||||
connectionId: string | null;
|
||||
decision: KtxRelationshipFeedbackDecisionFilter;
|
||||
json: boolean;
|
||||
jsonl: boolean;
|
||||
}
|
||||
| {
|
||||
command: 'relationshipCalibration';
|
||||
projectDir: string;
|
||||
connectionId: string | null;
|
||||
decision: KtxRelationshipFeedbackDecisionFilter;
|
||||
acceptThreshold: number;
|
||||
reviewThreshold: number;
|
||||
json: boolean;
|
||||
}
|
||||
| {
|
||||
command: 'relationshipThresholds';
|
||||
projectDir: string;
|
||||
connectionId: string | null;
|
||||
minTotalLabels: number;
|
||||
minAcceptedLabels: number;
|
||||
minRejectedLabels: number;
|
||||
json: boolean;
|
||||
};
|
||||
export interface KtxScanArgs {
|
||||
command: 'run';
|
||||
projectDir: string;
|
||||
connectionId: string;
|
||||
mode: KtxScanMode;
|
||||
detectRelationships: boolean;
|
||||
dryRun: boolean;
|
||||
databaseIntrospectionUrl?: string;
|
||||
cliVersion?: string;
|
||||
runtimeInstallPolicy?: KtxManagedPythonInstallPolicy;
|
||||
}
|
||||
|
||||
interface KtxScanDeps {
|
||||
runLocalScan?: typeof runLocalScan;
|
||||
createLocalIngestAdapters?: typeof createKtxCliLocalIngestAdapters;
|
||||
getLocalScanStatus?: typeof getLocalScanStatus;
|
||||
getLocalScanReport?: typeof getLocalScanReport;
|
||||
readLocalScanRelationshipArtifacts?: typeof readLocalScanRelationshipArtifacts;
|
||||
writeLocalScanRelationshipReviewDecision?: typeof writeLocalScanRelationshipReviewDecision;
|
||||
applyLocalScanRelationshipReviewDecisions?: typeof applyLocalScanRelationshipReviewDecisions;
|
||||
exportLocalRelationshipFeedbackLabels?: typeof exportLocalRelationshipFeedbackLabels;
|
||||
formatKtxRelationshipFeedbackLabelsJsonl?: typeof formatKtxRelationshipFeedbackLabelsJsonl;
|
||||
calibrateLocalRelationshipFeedbackLabels?: typeof calibrateLocalRelationshipFeedbackLabels;
|
||||
formatKtxRelationshipFeedbackCalibrationMarkdown?: typeof formatKtxRelationshipFeedbackCalibrationMarkdown;
|
||||
adviseLocalRelationshipFeedbackThresholds?: typeof adviseLocalRelationshipFeedbackThresholds;
|
||||
formatKtxRelationshipThresholdAdviceMarkdown?: typeof formatKtxRelationshipThresholdAdviceMarkdown;
|
||||
}
|
||||
|
||||
function shouldUseStyledOutput(io: KtxCliIo): boolean {
|
||||
|
|
@ -284,208 +193,8 @@ function writeRunSummary(report: KtxScanReport, projectDir: string, io: KtxCliIo
|
|||
writeHumanReportBody(report, io);
|
||||
const projectDirArg = quoteCliArg(projectDir);
|
||||
io.stdout.write('\nNext:\n');
|
||||
const statusCommand = styled ? dim('ktx dev scan status') : 'ktx dev scan status';
|
||||
const reportCommand = styled ? dim('ktx dev scan report') : 'ktx dev scan report';
|
||||
io.stdout.write(` ${statusCommand} --project-dir ${projectDirArg} ${report.runId}\n`);
|
||||
io.stdout.write(` ${reportCommand} --project-dir ${projectDirArg} ${report.runId}\n`);
|
||||
}
|
||||
|
||||
function writeReport(report: KtxScanReport, io: KtxCliIo): void {
|
||||
io.stdout.write('KTX scan report\n');
|
||||
writeHumanReportBody(report, io);
|
||||
}
|
||||
|
||||
function formatRelationshipEndpoint(edge: KtxRelationshipArtifactEdge, side: 'from' | 'to'): string {
|
||||
const endpoint = edge[side];
|
||||
if (endpoint.columns.length === 1) {
|
||||
return `${endpoint.table.name}.${endpoint.columns[0]}`;
|
||||
}
|
||||
return `${endpoint.table.name}.(${endpoint.columns.join(',')})`;
|
||||
}
|
||||
|
||||
function formatRelationshipScore(value: number | null): string {
|
||||
return value === null ? 'n/a' : value.toFixed(2);
|
||||
}
|
||||
|
||||
function relationshipStatusTitle(status: Exclude<KtxRelationshipArtifactStatus, 'all'>): string {
|
||||
if (status === 'accepted') {
|
||||
return 'Accepted relationships';
|
||||
}
|
||||
if (status === 'review') {
|
||||
return 'Review relationships';
|
||||
}
|
||||
if (status === 'rejected') {
|
||||
return 'Rejected relationships';
|
||||
}
|
||||
return 'Skipped relationships';
|
||||
}
|
||||
|
||||
function filteredRelationshipArtifact(
|
||||
relationships: KtxRelationshipArtifact,
|
||||
status: KtxRelationshipArtifactStatus,
|
||||
): KtxRelationshipArtifact {
|
||||
if (status === 'all') {
|
||||
return relationships;
|
||||
}
|
||||
return {
|
||||
connectionId: relationships.connectionId,
|
||||
accepted: status === 'accepted' ? relationships.accepted : [],
|
||||
review: status === 'review' ? relationships.review : [],
|
||||
rejected: status === 'rejected' ? relationships.rejected : [],
|
||||
skipped: status === 'skipped' ? relationships.skipped : [],
|
||||
};
|
||||
}
|
||||
|
||||
function writeRelationshipEdge(edge: KtxRelationshipArtifactEdge, index: number, io: KtxCliIo): void {
|
||||
io.stdout.write(
|
||||
` ${index + 1}. ${formatRelationshipEndpoint(edge, 'from')} -> ${formatRelationshipEndpoint(edge, 'to')}\n`,
|
||||
);
|
||||
io.stdout.write(
|
||||
` type=${edge.relationshipType} source=${edge.source} confidence=${edge.confidence.toFixed(2)} pkScore=${formatRelationshipScore(edge.pkScore)} fkScore=${formatRelationshipScore(edge.fkScore)}\n`,
|
||||
);
|
||||
io.stdout.write(` reasons=${edge.reasons.length > 0 ? edge.reasons.join(', ') : 'none'}\n`);
|
||||
}
|
||||
|
||||
function writeRelationshipGroup(
|
||||
status: Exclude<KtxRelationshipArtifactStatus, 'all'>,
|
||||
relationships: KtxRelationshipArtifact,
|
||||
limit: number,
|
||||
io: KtxCliIo,
|
||||
): void {
|
||||
if (status === 'skipped') {
|
||||
io.stdout.write(`\n${relationshipStatusTitle(status)} (${relationships.skipped.length})\n`);
|
||||
relationships.skipped.slice(0, limit).forEach((item, index) => {
|
||||
io.stdout.write(` ${index + 1}. ${item.relationshipId}\n`);
|
||||
io.stdout.write(` reason=${item.reason}\n`);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const edges =
|
||||
status === 'accepted'
|
||||
? relationships.accepted
|
||||
: status === 'review'
|
||||
? relationships.review
|
||||
: relationships.rejected;
|
||||
io.stdout.write(`\n${relationshipStatusTitle(status)} (${edges.length})\n`);
|
||||
edges.slice(0, limit).forEach((edge, index) => {
|
||||
writeRelationshipEdge(edge, index, io);
|
||||
});
|
||||
if (edges.length > limit) {
|
||||
io.stdout.write(` ${edges.length - limit} more not shown; rerun with --limit ${edges.length}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
function writeRelationshipArtifactSummary(input: {
|
||||
runId: string;
|
||||
connectionId: string;
|
||||
syncId: string;
|
||||
status: KtxRelationshipArtifactStatus;
|
||||
limit: number;
|
||||
summary: KtxRelationshipArtifact;
|
||||
relationships: KtxRelationshipArtifact;
|
||||
diagnostics: KtxRelationshipDiagnosticsArtifact | null;
|
||||
relationshipsPath: string;
|
||||
io: KtxCliIo;
|
||||
}): void {
|
||||
input.io.stdout.write('KTX relationship artifacts\n');
|
||||
input.io.stdout.write(`Run: ${input.runId}\n`);
|
||||
input.io.stdout.write(`Connection: ${input.connectionId}\n`);
|
||||
input.io.stdout.write(`Sync: ${input.syncId}\n`);
|
||||
input.io.stdout.write(
|
||||
`Summary: accepted=${input.summary.accepted.length} review=${input.summary.review.length} rejected=${input.summary.rejected.length} skipped=${input.summary.skipped.length}\n`,
|
||||
);
|
||||
if (input.diagnostics?.noAcceptedReason) {
|
||||
input.io.stdout.write(`Reason: ${input.diagnostics.noAcceptedReason}\n`);
|
||||
}
|
||||
input.io.stdout.write(`Artifacts: ${input.relationshipsPath}\n`);
|
||||
|
||||
const statuses: Array<Exclude<KtxRelationshipArtifactStatus, 'all'>> =
|
||||
input.status === 'all' ? ['accepted', 'review', 'rejected', 'skipped'] : [input.status];
|
||||
for (const status of statuses) {
|
||||
writeRelationshipGroup(status, input.relationships, input.limit, input.io);
|
||||
}
|
||||
}
|
||||
|
||||
function writeRelationshipDecisionResult(result: WriteLocalScanRelationshipReviewDecisionResult, io: KtxCliIo): void {
|
||||
io.stdout.write('Recorded relationship decision\n');
|
||||
io.stdout.write(`Decision: ${result.decision.decision}\n`);
|
||||
io.stdout.write(`Candidate: ${result.decision.candidateId}\n`);
|
||||
io.stdout.write(`Previous status: ${result.decision.previousStatus}\n`);
|
||||
io.stdout.write(`Reviewer: ${result.decision.reviewer}\n`);
|
||||
if (result.decision.note) {
|
||||
io.stdout.write(`Note: ${result.decision.note}\n`);
|
||||
}
|
||||
io.stdout.write(`Path: ${result.path}\n`);
|
||||
}
|
||||
|
||||
function writeRelationshipApplyResult(result: ApplyLocalScanRelationshipReviewDecisionsResult, io: KtxCliIo): void {
|
||||
io.stdout.write('Relationship review apply\n');
|
||||
io.stdout.write(`Run: ${result.runId}\n`);
|
||||
io.stdout.write(`Connection: ${result.connectionId}\n`);
|
||||
io.stdout.write(`Sync: ${result.syncId}\n`);
|
||||
io.stdout.write(`Mode: ${result.dryRun ? 'dry-run' : 'write'}\n`);
|
||||
io.stdout.write(`Decisions: ${result.selectedDecisions} ${plural(result.selectedDecisions, 'accepted decision')}\n`);
|
||||
io.stdout.write(
|
||||
`Applied: ${result.appliedRelationships} manual ${plural(result.appliedRelationships, 'relationship')}\n`,
|
||||
);
|
||||
io.stdout.write(`Schema shards written: ${result.manifestShardsWritten}\n`);
|
||||
if (result.manifestShards.length > 0) {
|
||||
io.stdout.write('Schema shards:\n');
|
||||
for (const shard of result.manifestShards) {
|
||||
io.stdout.write(` - ${shard}\n`);
|
||||
}
|
||||
}
|
||||
io.stdout.write(`Decisions: ${result.decisionsPath}\n`);
|
||||
}
|
||||
|
||||
function formatFeedbackColumns(columns: readonly string[]): string {
|
||||
return columns.length === 1 ? (columns[0] ?? 'unknown') : `(${columns.join(',')})`;
|
||||
}
|
||||
|
||||
function feedbackTableShortName(value: string): string {
|
||||
return value.split('.').at(-1) ?? value;
|
||||
}
|
||||
|
||||
function feedbackEndpoint(label: KtxRelationshipFeedbackLabel, side: 'from' | 'to'): string {
|
||||
if (side === 'from') {
|
||||
return `${feedbackTableShortName(label.fromTable)}.${formatFeedbackColumns(label.fromColumns)}`;
|
||||
}
|
||||
return `${feedbackTableShortName(label.toTable)}.${formatFeedbackColumns(label.toColumns)}`;
|
||||
}
|
||||
|
||||
function writeRelationshipFeedbackSummary(result: ExportLocalRelationshipFeedbackLabelsResult, io: KtxCliIo): void {
|
||||
io.stdout.write('KTX relationship feedback labels\n');
|
||||
io.stdout.write(`Generated: ${result.generatedAt}\n`);
|
||||
io.stdout.write(`Filter connection: ${result.filters.connectionId ?? 'all'}\n`);
|
||||
io.stdout.write(`Filter decision: ${result.filters.decision}\n`);
|
||||
io.stdout.write(`Total: ${result.summary.total}\n`);
|
||||
io.stdout.write(`Accepted: ${result.summary.accepted}\n`);
|
||||
io.stdout.write(`Rejected: ${result.summary.rejected}\n`);
|
||||
io.stdout.write(`Connections: ${result.summary.connections}\n`);
|
||||
io.stdout.write(`Runs: ${result.summary.runs}\n`);
|
||||
|
||||
if (result.warnings.length > 0) {
|
||||
io.stdout.write('\nWarnings\n');
|
||||
for (const warning of result.warnings.slice(0, 5)) {
|
||||
io.stdout.write(` - ${warning.path}: ${warning.message}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
if (result.labels.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
io.stdout.write('\nLabels\n');
|
||||
for (const label of result.labels.slice(0, 25)) {
|
||||
io.stdout.write(` - ${feedbackEndpoint(label, 'from')} -> ${feedbackEndpoint(label, 'to')}\n`);
|
||||
io.stdout.write(
|
||||
` decision=${label.decision} previous=${label.previousStatus} score=${formatRelationshipScore(label.score)} reviewer=${label.reviewer}\n`,
|
||||
);
|
||||
}
|
||||
if (result.labels.length > 25) {
|
||||
io.stdout.write(` ${result.labels.length - 25} more labels not shown; rerun with --jsonl for the full dataset\n`);
|
||||
}
|
||||
const statusCommand = styled ? dim('ktx status') : 'ktx status';
|
||||
io.stdout.write(` ${statusCommand} --project-dir ${projectDirArg}\n`);
|
||||
}
|
||||
|
||||
interface KtxCliScanProgressState {
|
||||
|
|
@ -540,184 +249,9 @@ export function createCliScanProgress(
|
|||
return progress;
|
||||
}
|
||||
|
||||
function writeStatus(status: LocalScanStatusResponse, io: KtxCliIo): void {
|
||||
io.stdout.write(`Run: ${status.runId}\n`);
|
||||
io.stdout.write(`Status: ${status.status}\n`);
|
||||
io.stdout.write(`Connection: ${status.connectionId}\n`);
|
||||
io.stdout.write(`Mode: ${status.mode}\n`);
|
||||
io.stdout.write(`Sync: ${status.syncId}\n`);
|
||||
io.stdout.write(`Progress: ${status.progress}\n`);
|
||||
io.stdout.write(`Report: ${status.reportPath ?? 'none'}\n`);
|
||||
}
|
||||
|
||||
export async function runKtxScan(args: KtxScanArgs, io: KtxCliIo = process, deps: KtxScanDeps = {}): Promise<number> {
|
||||
try {
|
||||
const project = await loadKtxProject({ projectDir: args.projectDir });
|
||||
if (args.command === 'status') {
|
||||
const status = await (deps.getLocalScanStatus ?? getLocalScanStatus)(project, args.runId);
|
||||
if (!status) {
|
||||
throw new Error(`Scan run "${args.runId}" was not found`);
|
||||
}
|
||||
writeStatus(status, io);
|
||||
return 0;
|
||||
}
|
||||
if (args.command === 'report') {
|
||||
const report = await (deps.getLocalScanReport ?? getLocalScanReport)(project, args.runId);
|
||||
if (!report) {
|
||||
throw new Error(`Scan report "${args.runId}" was not found`);
|
||||
}
|
||||
if (args.json) {
|
||||
io.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
||||
} else {
|
||||
writeReport(report, io);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (args.command === 'relationships') {
|
||||
const result = await (deps.readLocalScanRelationshipArtifacts ?? readLocalScanRelationshipArtifacts)(
|
||||
project,
|
||||
args.runId,
|
||||
);
|
||||
if (!result) {
|
||||
throw new Error(`Scan run "${args.runId}" was not found`);
|
||||
}
|
||||
const filtered = filteredRelationshipArtifact(result.relationships, args.status);
|
||||
if (args.json) {
|
||||
io.stdout.write(
|
||||
`${JSON.stringify(
|
||||
{
|
||||
runId: result.runId,
|
||||
connectionId: result.connectionId,
|
||||
syncId: result.syncId,
|
||||
status: args.status,
|
||||
paths: result.paths,
|
||||
diagnostics: result.diagnostics,
|
||||
summary: {
|
||||
accepted: result.relationships.accepted.length,
|
||||
review: result.relationships.review.length,
|
||||
rejected: result.relationships.rejected.length,
|
||||
skipped: result.relationships.skipped.length,
|
||||
},
|
||||
relationships: filtered,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
);
|
||||
} else {
|
||||
writeRelationshipArtifactSummary({
|
||||
runId: result.runId,
|
||||
connectionId: result.connectionId,
|
||||
syncId: result.syncId,
|
||||
status: args.status,
|
||||
limit: args.limit,
|
||||
summary: result.relationships,
|
||||
relationships: filtered,
|
||||
diagnostics: result.diagnostics,
|
||||
relationshipsPath: result.paths.relationships,
|
||||
io,
|
||||
});
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (args.command === 'relationshipDecision') {
|
||||
const result = await (deps.writeLocalScanRelationshipReviewDecision ?? writeLocalScanRelationshipReviewDecision)(
|
||||
project,
|
||||
{
|
||||
runId: args.runId,
|
||||
candidateId: args.candidateId,
|
||||
decision: args.decision,
|
||||
reviewer: args.reviewer,
|
||||
note: args.note,
|
||||
},
|
||||
);
|
||||
if (!result) {
|
||||
throw new Error(`Scan run "${args.runId}" was not found`);
|
||||
}
|
||||
if (args.json) {
|
||||
io.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
||||
} else {
|
||||
writeRelationshipDecisionResult(result, io);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (args.command === 'relationshipApply') {
|
||||
const result = await (
|
||||
deps.applyLocalScanRelationshipReviewDecisions ?? applyLocalScanRelationshipReviewDecisions
|
||||
)(project, {
|
||||
runId: args.runId,
|
||||
applyAllAccepted: args.applyAllAccepted,
|
||||
candidateIds: args.candidateIds,
|
||||
dryRun: args.dryRun,
|
||||
});
|
||||
if (args.json) {
|
||||
io.stdout.write(
|
||||
`${JSON.stringify(result satisfies ApplyLocalScanRelationshipReviewDecisionsResult, null, 2)}\n`,
|
||||
);
|
||||
} else {
|
||||
writeRelationshipApplyResult(result, io);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (args.command === 'relationshipFeedback') {
|
||||
const result = await (deps.exportLocalRelationshipFeedbackLabels ?? exportLocalRelationshipFeedbackLabels)(
|
||||
project,
|
||||
{
|
||||
connectionId: args.connectionId,
|
||||
decision: args.decision,
|
||||
},
|
||||
);
|
||||
if (args.jsonl) {
|
||||
io.stdout.write(
|
||||
(deps.formatKtxRelationshipFeedbackLabelsJsonl ?? formatKtxRelationshipFeedbackLabelsJsonl)(result),
|
||||
);
|
||||
} else if (args.json) {
|
||||
io.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
||||
} else {
|
||||
writeRelationshipFeedbackSummary(result, io);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (args.command === 'relationshipCalibration') {
|
||||
const result = await (deps.calibrateLocalRelationshipFeedbackLabels ?? calibrateLocalRelationshipFeedbackLabels)(
|
||||
project,
|
||||
{
|
||||
connectionId: args.connectionId,
|
||||
decision: args.decision,
|
||||
acceptThreshold: args.acceptThreshold,
|
||||
reviewThreshold: args.reviewThreshold,
|
||||
},
|
||||
);
|
||||
if (args.json) {
|
||||
io.stdout.write(`${JSON.stringify(result satisfies KtxRelationshipFeedbackCalibrationReport, null, 2)}\n`);
|
||||
} else {
|
||||
io.stdout.write(
|
||||
(deps.formatKtxRelationshipFeedbackCalibrationMarkdown ?? formatKtxRelationshipFeedbackCalibrationMarkdown)(
|
||||
result,
|
||||
),
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (args.command === 'relationshipThresholds') {
|
||||
const result = await (
|
||||
deps.adviseLocalRelationshipFeedbackThresholds ?? adviseLocalRelationshipFeedbackThresholds
|
||||
)(project, {
|
||||
connectionId: args.connectionId,
|
||||
minTotalLabels: args.minTotalLabels,
|
||||
minAcceptedLabels: args.minAcceptedLabels,
|
||||
minRejectedLabels: args.minRejectedLabels,
|
||||
});
|
||||
if (args.json) {
|
||||
io.stdout.write(`${JSON.stringify(result satisfies KtxRelationshipThresholdAdviceReport, null, 2)}\n`);
|
||||
} else {
|
||||
io.stdout.write(
|
||||
(deps.formatKtxRelationshipThresholdAdviceMarkdown ?? formatKtxRelationshipThresholdAdviceMarkdown)(result),
|
||||
);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const managedDaemon = managedDaemonOptionsForScanRun(args, io);
|
||||
const connector =
|
||||
args.mode !== 'structural' || args.detectRelationships
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue