11
This commit is contained in:
commit
e6f1776d4f
264 files changed
+54215
No files matched your search
@@ -0,0 +1,28 @@
|
||||
import type { Issue } from '../types'
|
||||
|
||||
/**
|
||||
* 计算一个 issue 是否处于"锁定"状态(其前置工单尚未完成)。
|
||||
*
|
||||
* 锁定条件(全部满足):
|
||||
* 1. `issue.prerequisite !== undefined`
|
||||
* 2. 在 `allIssues` 中能找到对应的前置工单
|
||||
* 3. 前置工单的 `column !== 'done'`
|
||||
*
|
||||
* @param issue 待检查的工单
|
||||
* @param allIssues 当前所有工单(用于查找前置工单状态)
|
||||
* @returns 锁定状态及前置工单号(用于 tooltip 文案)
|
||||
*/
|
||||
export function isIssueLocked(
|
||||
issue: Issue,
|
||||
allIssues: Issue[],
|
||||
): { locked: boolean, prerequisiteNumber?: number } {
|
||||
if (issue.prerequisite === undefined)
|
||||
return { locked: false }
|
||||
const prereq = allIssues.find(i => i.number === issue.prerequisite)
|
||||
if (!prereq)
|
||||
return { locked: false }
|
||||
return {
|
||||
locked: prereq.column !== 'done',
|
||||
prerequisiteNumber: issue.prerequisite,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { Issue, IssueColumn } from '../types'
|
||||
|
||||
/**
|
||||
* 看板每一列的卡片排序。
|
||||
*
|
||||
* `done` 列按 PR 合并时间(merged_at)降序,最近合并的在最前;缺合并时间的
|
||||
* 工单排在有时间的之后,按工单号降序兜底。其余列一律按工单号降序。
|
||||
*/
|
||||
export function compareIssuesInColumn(col: IssueColumn, a: Issue, b: Issue): number {
|
||||
if (col === 'done') {
|
||||
const am = a.prMergedAt
|
||||
const bm = b.prMergedAt
|
||||
if (am && bm && am !== bm)
|
||||
return bm.localeCompare(am)
|
||||
if (am && !bm)
|
||||
return -1
|
||||
if (bm && !am)
|
||||
return 1
|
||||
}
|
||||
return b.number - a.number
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* Mirror of `src/panel/messages.ts` for the webview side.
|
||||
*
|
||||
* Kept in sync manually; if the two ever diverge that's a step-3+ concern.
|
||||
*/
|
||||
|
||||
import type { Issue, IssueColumn, ManagedSessionsData } from '../types'
|
||||
|
||||
export interface ToastLink {
|
||||
label: string
|
||||
url: string
|
||||
}
|
||||
|
||||
/** PR 提交的精简视图(提交列表渲染用)。 */
|
||||
export interface PrCommit {
|
||||
sha: string
|
||||
message: string
|
||||
authorName: string
|
||||
date: string
|
||||
}
|
||||
|
||||
/** 提交内单个文件改动:status 取 added/modified/deleted/renamed/copied 等。 */
|
||||
export interface PrCommitFile {
|
||||
path: string
|
||||
status: string
|
||||
}
|
||||
|
||||
export type ToastLevel = 'info' | 'success' | 'error'
|
||||
|
||||
/**
|
||||
* Mirror of the extension-side LogEntry in src/logging/logger.ts.
|
||||
* Kept manually in sync — workspace boundaries prevent direct import.
|
||||
*/
|
||||
export type LogLevel = 'info' | 'warn' | 'error'
|
||||
|
||||
export interface LogEntry {
|
||||
ts: number
|
||||
level: LogLevel
|
||||
source: string
|
||||
message: string
|
||||
details?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirror of the extension-side ProfilesData in src/profiles/store.ts.
|
||||
* Workspace 级 profile 表:每行一个 key,跨多个 profile 列的字符串值。
|
||||
*/
|
||||
export interface ProfileRow {
|
||||
key: string
|
||||
values: Record<string, string>
|
||||
}
|
||||
|
||||
export interface ProfilesData {
|
||||
profiles: string[]
|
||||
rows: ProfileRow[]
|
||||
}
|
||||
|
||||
export type ExtensionToWebview
|
||||
= | { type: 'issues/loading' }
|
||||
| { type: 'issues/update', issues: Issue[], globalAutoReview: boolean, youtrackConfigured: boolean }
|
||||
| { type: 'issues/error', message: string }
|
||||
| { type: 'issue/patch', issueNumber: number, patch: { autoReview?: boolean, specFile?: string, planFile?: string, prDiffFile?: string, sessionId?: string, implementSessionId?: string, reviewSessionId?: string, testSessionId?: string, pr?: string, implementStatus?: 'running' | 'done' | 'failed', column?: IssueColumn, worktreePath?: string, prMerged?: boolean, prMergedAt?: string, branch?: string, color?: string, worktreeExists?: boolean, brainstormTabOpen?: boolean, implementTabOpen?: boolean, reviewTabOpen?: boolean, testTabOpen?: boolean, profilePath?: string, testProfilePath?: string } }
|
||||
| { type: 'issue/pr-diff-summary-done', issueNumber: number }
|
||||
| { type: 'issue/append', issue: Issue, select?: boolean }
|
||||
| { type: 'issue/select-by-number', issueNumber: number }
|
||||
| {
|
||||
type: 'settings/show'
|
||||
host: string
|
||||
errorMessage?: string
|
||||
canCancel?: boolean
|
||||
tokenSaved: boolean
|
||||
webhookPort: number
|
||||
brainstormPrompt: string
|
||||
implementPlanPrompt: string
|
||||
autoReview: boolean
|
||||
reviewPrompt: string
|
||||
devBranch: string
|
||||
autoBuildBranch: string
|
||||
worktreePostCreateScript: string
|
||||
worktreePreRemoveScript: string
|
||||
implTabPreCreateScript: string
|
||||
implTabPostCloseScript: string
|
||||
youtrackBaseUrl?: string
|
||||
youtrackProjectShortName?: string
|
||||
youtrackCloseCommand?: string
|
||||
youtrackTokenSaved?: boolean
|
||||
}
|
||||
| { type: 'youtrack/projects', projects: Array<{ id: string, name: string, shortName: string }>, error?: string }
|
||||
| {
|
||||
type: 'toast/show'
|
||||
id: string
|
||||
level: ToastLevel
|
||||
message: string
|
||||
spinner?: boolean
|
||||
link?: ToastLink
|
||||
dismissOnTimer?: number
|
||||
}
|
||||
| { type: 'toast/dismiss', id: string }
|
||||
| { type: 'profiles/update', profiles: Array<{ name: string, path: string }> }
|
||||
| { type: 'logs/snapshot', entries: LogEntry[] }
|
||||
| { type: 'logs/append', entry: LogEntry }
|
||||
| { type: 'logs/cleared' }
|
||||
| { type: 'commit/state', running: boolean }
|
||||
| { type: 'commit/has-changes', value: boolean }
|
||||
| {
|
||||
type: 'branch-sync/status'
|
||||
behind: number
|
||||
devBranch: string
|
||||
autoBuildBranch: string
|
||||
unavailable?: boolean
|
||||
reason?: string
|
||||
}
|
||||
| { type: 'env-lock/status', locked: boolean, fileCount: number, failedCount?: number }
|
||||
| { type: 'issue/remove', issueNumber: number }
|
||||
| { type: 'profiles/show', data: ProfilesData }
|
||||
| { type: 'managed-sessions/show', data: ManagedSessionsData }
|
||||
| { type: 'pr-commits/show', issueNumber: number, commits: PrCommit[], confirmedCommits: string[], error?: string }
|
||||
| { type: 'pr-commit-files/show', issueNumber: number, sha: string, parentSha?: string, files: PrCommitFile[], confirmed: string[], error?: string }
|
||||
|
||||
export type WebviewToExtension
|
||||
= | { type: 'issues/refresh' }
|
||||
| {
|
||||
type: 'settings/save'
|
||||
host: string
|
||||
token: string
|
||||
webhookPort: number
|
||||
brainstormPrompt: string
|
||||
implementPlanPrompt: string
|
||||
autoReview: boolean
|
||||
reviewPrompt: string
|
||||
devBranch: string
|
||||
autoBuildBranch: string
|
||||
worktreePostCreateScript: string
|
||||
worktreePreRemoveScript: string
|
||||
implTabPreCreateScript: string
|
||||
implTabPostCloseScript: string
|
||||
youtrackBaseUrl: string
|
||||
youtrackProjectShortName: string
|
||||
youtrackCloseCommand: string
|
||||
youtrackToken: string
|
||||
}
|
||||
| { type: 'settings/edit-request' }
|
||||
| { type: 'youtrack/list-projects', baseUrl: string, token: string }
|
||||
| { type: 'youtrack/import' }
|
||||
| { type: 'issue/create', userRequest: string, images?: Array<{ mediaType: string, base64: string }>, profilePath?: string }
|
||||
| { type: 'toast/open-url', url: string }
|
||||
| { type: 'session/resume', sessionId: string, profilePath?: string, cwd?: string, issueNumber?: number }
|
||||
| { type: 'session/focus', issueNumber: number }
|
||||
| { type: 'session/resume-review', sessionId: string, issueNumber: number, cwd?: string }
|
||||
| { type: 'session/resume-test', sessionId: string, issueNumber: number, cwd?: string }
|
||||
| { type: 'session/start-test', issueNumber: number }
|
||||
| { type: 'editor/open-file', path: string }
|
||||
| { type: 'profiles/list' }
|
||||
| { type: 'issue/implement', issueNumber: number, planFile: string, profilePath?: string, sessionId?: string }
|
||||
| { type: 'issue/generate-pr-diff-summary', issueNumber: number }
|
||||
| { type: 'pr/open', pr: string }
|
||||
| { type: 'worktree/open', path: string }
|
||||
| { type: 'worktree/delete', issueNumber: number, path: string }
|
||||
| { type: 'git/merge-preview', issueNumber: number, branch: string }
|
||||
| { type: 'column/change', issueNumber: number, toColumn: 'todo' | 'in-progress' | 'review' | 'done', source?: 'gitea' | 'youtrack', externalId?: string }
|
||||
| { type: 'dependency/set', issueNumber: number, prerequisiteNumber: number }
|
||||
| { type: 'dependency/clear', issueNumber: number, prerequisiteNumber: number }
|
||||
| { type: 'issue/update-auto-review', issueNumber: number, value: boolean }
|
||||
| { type: 'issue/update-profile-path', issueNumber: number, profilePath: string }
|
||||
| { type: 'issue/update-test-profile-path', issueNumber: number, testProfilePath: string }
|
||||
| { type: 'logs/fetch' }
|
||||
| { type: 'logs/clear' }
|
||||
| { type: 'commit/run' }
|
||||
| { type: 'session/close-tab', issueNumber: number, kind: 'brainstorm' | 'implement' | 'review' | 'test' }
|
||||
| { type: 'branch-sync/check' }
|
||||
| { type: 'branch-sync/run' }
|
||||
| { type: 'env-lock/check' }
|
||||
| { type: 'env-lock/toggle' }
|
||||
| { type: 'issue/delete', issueNumber: number }
|
||||
| { type: 'issue/close', issueNumber: number }
|
||||
| { type: 'brainstorm/start', issueNumber: number }
|
||||
| { type: 'profiles/get' }
|
||||
| { type: 'profiles/save', data: ProfilesData }
|
||||
| { type: 'profiles/open', value: string }
|
||||
| { type: 'managed-sessions/get' }
|
||||
| { type: 'managed-sessions/create', profilePath: string, name?: string, prompt?: string }
|
||||
| { type: 'managed-sessions/rename', sessionId: string, name: string }
|
||||
| { type: 'managed-sessions/resume', sessionId: string }
|
||||
| { type: 'managed-sessions/delete', sessionId: string }
|
||||
| { type: 'managed-sessions/close-tab', sessionId: string }
|
||||
| { type: 'pr-commits/get', issueNumber: number }
|
||||
| { type: 'pr-commit-files/get', issueNumber: number, sha: string }
|
||||
| { type: 'pr-commit-diff/open', issueNumber: number, sha: string, parentSha?: string, path: string, status: string }
|
||||
| { type: 'pr-review/set', issueNumber: number, sha: string, confirmed: string[] }
|
||||
| { type: 'pr-review/set-commits', issueNumber: number, confirmed: string[] }
|
||||
@@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from 'clsx'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export function cn(...inputs: ClassValue[]): string {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Tiny typed bridge over the `acquireVsCodeApi()` postMessage channel.
|
||||
*
|
||||
* The webview gets exactly one chance to call `acquireVsCodeApi()` per
|
||||
* lifetime, so we cache the handle module-side. When running outside the
|
||||
* VS Code host (e.g. `vite dev`) we fall back to no-op stubs.
|
||||
*/
|
||||
|
||||
import type { ExtensionToWebview, WebviewToExtension } from './messages'
|
||||
|
||||
interface VsCodeApi {
|
||||
postMessage: (msg: WebviewToExtension) => void
|
||||
getState: <T = unknown>() => T | undefined
|
||||
setState: <T = unknown>(state: T) => void
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
acquireVsCodeApi?: () => VsCodeApi
|
||||
}
|
||||
}
|
||||
|
||||
const api: VsCodeApi = window.acquireVsCodeApi?.() ?? {
|
||||
postMessage: () => {},
|
||||
getState: () => undefined,
|
||||
setState: () => {},
|
||||
}
|
||||
|
||||
export function postMessage(msg: WebviewToExtension): void {
|
||||
api.postMessage(msg)
|
||||
}
|
||||
|
||||
export function onMessage(handler: (msg: ExtensionToWebview) => void): () => void {
|
||||
const listener = (ev: MessageEvent): void => {
|
||||
handler(ev.data as ExtensionToWebview)
|
||||
}
|
||||
window.addEventListener('message', listener)
|
||||
return () => window.removeEventListener('message', listener)
|
||||
}
|
||||
Reference in new issue
Block a user