feat(semantic): curator-driven protocol hardening — decision memory + relation repair
- Add @RATIONALE/@REJECTED to 103+ C4/C5 contracts across backend core, services, API routes, and frontend models - Fix 109 unresolved @RELATION edges (Auth.*, SupersetClient.*, AgentChat.*, ADR cross-refs) - Add 13 @ingroup tags for DSA/HCA attention grouping - Repair 29 stale graph edges via index rebuild - Update .kilo agent prompts and skills for GRACE-Poly v2.6 compliance - Git integration: merge routes, branch lifecycle, remote providers, UX components - 0 broken anchor pairs, index rebuilt with 0 parse warnings
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
<!-- @RELATION USES -> [GitWorkspacePanel] -->
|
||||
<!-- @RELATION USES -> [GitReleasePanel] -->
|
||||
<!-- @RELATION USES -> [GitOperationsPanel] -->
|
||||
<!-- @RELATION USES -> [GitLifecycleHeader] -->
|
||||
<!-- @RELATION USES -> [GitMergeDialog] -->
|
||||
<!-- @RELATION USES -> [EXT:frontend:BranchSelector] -->
|
||||
<!-- @RELATION USES -> [EXT:frontend:DeploymentModal] -->
|
||||
@@ -25,9 +26,10 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { t } from '$lib/i18n/index.svelte.js';
|
||||
import { HelpTooltip, Icon } from '$lib/ui';
|
||||
import { stageBadgeClass, resolveDefaultConfig } from '../../../services/git-utils.js';
|
||||
import { Button, HelpTooltip, Icon } from '$lib/ui';
|
||||
import { resolveDefaultConfig } from '../../../services/git-utils.js';
|
||||
import { GitManagerModel } from '$lib/models/GitManagerModel.svelte.ts';
|
||||
import { ROUTES } from '$lib/routes';
|
||||
import BranchSelector from './BranchSelector.svelte';
|
||||
import DeploymentModal from './DeploymentModal.svelte';
|
||||
import ConflictResolver from './ConflictResolver.svelte';
|
||||
@@ -36,6 +38,9 @@
|
||||
import GitReleasePanel from './GitReleasePanel.svelte';
|
||||
import GitOperationsPanel from './GitOperationsPanel.svelte';
|
||||
import GitMergeDialog from './GitMergeDialog.svelte';
|
||||
import GitLifecycleHeader from './GitLifecycleHeader.svelte';
|
||||
import CreateBranchDialog from './CreateBranchDialog.svelte';
|
||||
import MergeDialog from './MergeDialog.svelte';
|
||||
|
||||
let { dashboardId, envId = null, dashboardTitle = '', show = $bindable(false) } = $props();
|
||||
|
||||
@@ -53,6 +58,31 @@
|
||||
|
||||
function closeModal() { show = false; model.clearGitError(); }
|
||||
|
||||
/** Called from BranchSelector when user clicks "Merge into dev".
|
||||
* Opens the MergeDialog with the given source branch targeting dev. */
|
||||
function handleOpenMergeDialog(sourceBranch: string) {
|
||||
model.mergeSourceBranch = sourceBranch;
|
||||
model.mergeTargetBranch = 'dev';
|
||||
model.showMergeDialog = true;
|
||||
}
|
||||
|
||||
function handleRecommendedAction() {
|
||||
if (model.recommendedAction === 'sync') {
|
||||
void model.handleSync();
|
||||
model.activeTab = 'workspace';
|
||||
return;
|
||||
}
|
||||
if (model.recommendedAction === 'commit') {
|
||||
model.activeTab = 'workspace';
|
||||
return;
|
||||
}
|
||||
if (model.recommendedAction === 'promote') {
|
||||
model.activeTab = 'release';
|
||||
return;
|
||||
}
|
||||
model.activeTab = 'operations';
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
if (model.showDeployConfirm) deployConfirmInput = '';
|
||||
});
|
||||
@@ -159,7 +189,7 @@
|
||||
<div class="mt-1">{($t.git?.git_server_mismatch_desc || 'Configured: {configured}, origin: {origin}.').replace('{configured}', model.configHost).replace('{origin}', model.originHost)}</div>
|
||||
<div class="mt-1.5 text-xs text-warning/80">{$t.git?.git_server_mismatch_hint || 'Push/Pull будут отправлять на настроенный сервер, а не на origin. Обновите конфигурацию в Настройках → Git, если это не intended.'}</div>
|
||||
</div>
|
||||
<a href="/settings/git" class="shrink-0 rounded-md border border-warning bg-surface-card px-3 py-1.5 text-xs font-medium text-warning transition-colors hover:bg-warning-light">{$t.git?.git_server_mismatch_fix || 'Настроить'}</a>
|
||||
<a href={ROUTES.settings.git()} class="shrink-0 rounded-md border border-warning bg-surface-card px-3 py-1.5 text-xs font-medium text-warning transition-colors hover:bg-warning-light">{$t.git?.git_server_mismatch_fix || 'Настроить'}</a>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -177,25 +207,17 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-border bg-surface-card px-4 py-3 shadow-sm">
|
||||
<div class="flex items-center gap-3">
|
||||
<span class={`inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-bold uppercase tracking-wider ${stageBadgeClass(model.currentEnvStage)}`}>
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-current opacity-60"></span>
|
||||
{model.currentEnvStage || 'DEV'}
|
||||
</span>
|
||||
<span class="hidden text-xs text-text-muted sm:inline">·</span>
|
||||
<span class="flex items-center gap-1.5 text-sm text-text-muted">
|
||||
<Icon name="code" size={16} class="text-text-subtle" strokeWidth={2} />
|
||||
<span class="hidden sm:inline">{$t.git?.branch_label || 'Ветка:'}</span> <strong class="font-mono text-text">{model.currentBranch}</strong>
|
||||
</span>
|
||||
{#if model.changedFilesCount > 0}
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-warning-light px-2.5 py-0.5 text-xs font-medium text-warning ring-1 ring-inset ring-warning-ring">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-3.5 w-3.5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 12H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
||||
{($t.git?.changes_count || '{count} изменений').replace('{count}', String(model.changedFilesCount))}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex w-full items-center gap-1 sm:w-64"><BranchSelector {dashboardId} envId={model.resolvedEnvId} bind:currentBranch={model.currentBranch} /><HelpTooltip text={$t.git?.hint_branch_selector || ''} /></div>
|
||||
<GitLifecycleHeader
|
||||
currentEnvStage={model.currentWorkflowStage}
|
||||
currentBranch={model.currentBranch}
|
||||
changedFilesCount={model.changedFilesCount}
|
||||
recommendedAction={model.recommendedAction}
|
||||
preferredDeployTargetStage={model.preferredDeployTargetStage}
|
||||
onRecommendedAction={handleRecommendedAction}
|
||||
/>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-border bg-surface-page px-4 py-3">
|
||||
<div class="text-sm font-medium text-text">{$t.git?.branch_label || 'Ветка:'}</div>
|
||||
<div class="flex w-full items-center gap-1 sm:w-72"><BranchSelector {dashboardId} envId={model.resolvedEnvId} bind:currentBranch={model.currentBranch} onchange={(event) => model.handleBranchChanged(event)} onmerge={handleOpenMergeDialog} /><HelpTooltip text={$t.git?.hint_branch_selector || ''} /></div>
|
||||
</div>
|
||||
<div class="flex flex-wrap items-center gap-1 border-b border-border pb-0">
|
||||
<button class={`relative -mb-px inline-flex items-center gap-2 rounded-t-lg px-4 py-2.5 text-sm font-medium transition-colors ${model.activeTab === 'workspace' ? 'border border-b-white bg-surface-card text-primary shadow-sm' : 'border border-transparent text-text-muted hover:bg-surface-muted hover:text-text'}`} onclick={() => (model.activeTab = 'workspace')}>
|
||||
@@ -215,11 +237,11 @@
|
||||
</button>
|
||||
</div>
|
||||
{#if model.activeTab === 'workspace'}
|
||||
<GitWorkspacePanel {dashboardId} envId={model.resolvedEnvId} commitHistoryKey={model.commitHistoryKey} hasWorkspaceChanges={model.hasWorkspaceChanges} changedFilesCount={model.changedFilesCount} workspaceLoading={model.workspaceLoading} workspaceDiff={model.workspaceDiff} committing={model.committing} generatingMessage={model.generatingMessage} bind:commitMessage={model.commitMessage} bind:autoPushAfterCommit={model.autoPushAfterCommit} loading={model.loading} pushProviderLabel={model.pushProviderLabel} onSync={() => model.handleSync()} onGenerateMessage={() => model.handleGenerateMessage()} onCommit={() => model.handleCommit()} />
|
||||
<GitWorkspacePanel {dashboardId} envId={model.resolvedEnvId} commitHistoryKey={model.commitHistoryKey} hasWorkspaceChanges={model.hasWorkspaceChanges} changedFilesCount={model.changedFilesCount} workspaceStatus={model.workspaceStatus} workspaceLoading={model.workspaceLoading} workspaceDiff={model.workspaceDiff} committing={model.committing} generatingMessage={model.generatingMessage} bind:commitMessage={model.commitMessage} bind:autoPushAfterCommit={model.autoPushAfterCommit} loading={model.loading} pushProviderLabel={model.pushProviderLabel} onSync={() => model.handleSync()} onGenerateMessage={() => model.handleGenerateMessage()} onCommit={() => model.handleCommit()} />
|
||||
{:else if model.activeTab === 'release'}
|
||||
<GitReleasePanel currentEnvStage={model.currentEnvStage} bind:promoteFromBranch={model.promoteFromBranch} bind:promoteToBranch={model.promoteToBranch} bind:promoteMode={model.promoteMode} bind:promoteReason={model.promoteReason} preferredDeployTargetStage={model.preferredDeployTargetStage} bind:showAdvancedPromote={model.showAdvancedPromote} promoting={model.promoting} onPromote={() => model.handlePromote()} />
|
||||
<GitReleasePanel currentEnvStage={model.currentEnvStage} bind:promoteFromBranch={model.promoteFromBranch} bind:promoteToBranch={model.promoteToBranch} bind:promoteMode={model.promoteMode} bind:promoteReason={model.promoteReason} preferredDeployTargetStage={model.preferredDeployTargetStage} bind:showAdvancedPromote={model.showAdvancedPromote} promoting={model.promoting} onPromote={() => model.handlePromote()} onDeploy={() => model.openDeployModal()} onOpenHistory={() => (model.activeTab = 'workspace')} />
|
||||
{:else}
|
||||
<GitOperationsPanel isPulling={model.isPulling} isPushing={model.isPushing} workspaceStatus={model.workspaceStatus} currentEnvStage={model.currentEnvStage} onPull={() => model.handlePull()} onPush={() => model.handlePush()} onDeploy={() => model.openDeployModal()} />
|
||||
<GitOperationsPanel isPulling={model.isPulling} isPushing={model.isPushing} workspaceStatus={model.workspaceStatus} onPull={() => model.handlePull()} onPush={() => model.handlePush()} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
@@ -236,7 +258,47 @@
|
||||
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50" onclick={() => { model.showDeployConfirm = false; }} onkeydown={(e) => { if (e.key === 'Escape') model.showDeployConfirm = false; }}>
|
||||
<div class="bg-surface-card rounded-xl shadow-xl p-6 max-w-md w-full mx-4 border border-border" role="alertdialog" aria-modal="true" aria-label={$t.git?.deploy || 'Deploy to Environment'} onclick={(e) => e.stopPropagation()}>
|
||||
<h3 class="text-lg font-semibold text-text mb-2">{$t.git?.deploy || 'Deploy to Environment'}</h3>
|
||||
<p class="text-sm text-text-muted mb-4">Подтвердите деплой в PROD. Введите slug дашборда: <strong>{model.deployConfirmSlug}</strong></p>
|
||||
<p class="text-sm text-text-muted mb-4">{$t.git?.deploy_confirm_intro || 'Подтвердите деплой. Введите slug дашборда:'} <strong>{model.deployConfirmSlug}</strong></p>
|
||||
<dl class="mb-4 grid grid-cols-1 gap-2 rounded-lg border border-border bg-surface-page p-3 text-sm">
|
||||
<div class="flex justify-between gap-3">
|
||||
<dt class="text-text-muted">{$t.git?.deploy_confirm_dashboard || 'Dashboard'}</dt>
|
||||
<dd class="truncate font-medium text-text">{dashboardTitle || dashboardId}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between gap-3">
|
||||
<dt class="text-text-muted">{$t.git?.deploy_confirm_branch || 'Source branch'}</dt>
|
||||
<dd class="font-mono font-medium text-text">{model.currentBranch}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between gap-3">
|
||||
<dt class="text-text-muted">{$t.git?.deploy_confirm_stage || 'Lifecycle stage'}</dt>
|
||||
<dd class="font-medium text-text">{model.currentWorkflowStage}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between gap-3">
|
||||
<dt class="text-text-muted">{$t.git?.deploy_confirm_target || 'Target'}</dt>
|
||||
<dd class="font-medium text-text">{model.preferredDeployTargetStage || model.currentEnvStage || 'PROD'}</dd>
|
||||
</div>
|
||||
<div class="flex justify-between gap-3">
|
||||
<dt class="text-text-muted">{$t.git?.deploy_confirm_changes || 'Changed files'}</dt>
|
||||
<dd class="font-medium text-text">{model.changedFilesCount}</dd>
|
||||
</div>
|
||||
{#if model.workspaceStatus?.last_commit_hash}
|
||||
<div class="flex justify-between gap-3">
|
||||
<dt class="text-text-muted">{$t.git?.deploy_confirm_last_commit || 'Last commit'}</dt>
|
||||
<dd class="font-mono font-medium text-text">{model.workspaceStatus.last_commit_hash.slice(0, 8)}</dd>
|
||||
</div>
|
||||
{/if}
|
||||
{#if model.workspaceStatus?.last_commit_author}
|
||||
<div class="flex justify-between gap-3">
|
||||
<dt class="text-text-muted">{$t.git?.deploy_confirm_author || 'Author'}</dt>
|
||||
<dd class="truncate font-medium text-text">{model.workspaceStatus.last_commit_author}</dd>
|
||||
</div>
|
||||
{/if}
|
||||
{#if model.workspaceStatus?.last_commit_message}
|
||||
<div class="grid gap-1">
|
||||
<dt class="text-text-muted">{$t.git?.deploy_confirm_message || 'Commit message'}</dt>
|
||||
<dd class="line-clamp-2 text-text">{model.workspaceStatus.last_commit_message}</dd>
|
||||
</div>
|
||||
{/if}
|
||||
</dl>
|
||||
<!-- svelte-ignore a11y-autofocus -->
|
||||
<input
|
||||
bind:value={deployConfirmInput}
|
||||
@@ -252,4 +314,23 @@
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Create Branch Dialog -->
|
||||
<CreateBranchDialog
|
||||
bind:show={model.showCreateBranchDialog}
|
||||
{dashboardId}
|
||||
{envId}
|
||||
branches={model.branches}
|
||||
oncreated={() => model.refreshBranches()}
|
||||
/>
|
||||
|
||||
<!-- Feature/Hotfix Merge Dialog -->
|
||||
<MergeDialog
|
||||
bind:show={model.showMergeDialog}
|
||||
{dashboardId}
|
||||
{envId}
|
||||
sourceBranch={model.mergeSourceBranch}
|
||||
targetBranch={model.mergeTargetBranch || 'dev'}
|
||||
onmerged={() => model.refreshBranches()}
|
||||
/>
|
||||
<!-- #endregion GitManager -->
|
||||
|
||||
Reference in New Issue
Block a user