dsh-github-flow 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.en.md +112 -17
  2. package/README.md +115 -17
  3. package/docs/UPGRADE-NOTES.md +12 -11
  4. package/docs/architecture.md +81 -22
  5. package/docs/images/1.png +0 -0
  6. package/lib/api/route-helpers.d.ts +19 -0
  7. package/lib/api/route-helpers.js +54 -0
  8. package/lib/api/routes-actions.js +214 -9
  9. package/lib/api/routes-lifecycle.js +8 -1
  10. package/lib/api/routes-overview.d.ts +26 -0
  11. package/lib/api/routes-overview.js +396 -36
  12. package/lib/api/routes-proxy.js +8 -1
  13. package/lib/api/routes.d.ts +11 -1
  14. package/lib/api/routes.js +15 -2
  15. package/lib/cache.d.ts +56 -4
  16. package/lib/cache.js +77 -7
  17. package/lib/client.d.ts +8 -3
  18. package/lib/client.js +5542 -1391
  19. package/lib/destructive.d.ts +59 -0
  20. package/lib/destructive.js +285 -0
  21. package/lib/executor/args-guard.d.ts +17 -0
  22. package/lib/executor/args-guard.js +23 -0
  23. package/lib/executor/error-classifier.d.ts +55 -0
  24. package/lib/executor/error-classifier.js +199 -0
  25. package/lib/executor/failure-anchors.d.ts +52 -0
  26. package/lib/executor/failure-anchors.js +116 -0
  27. package/lib/executor/git-parser.d.ts +124 -0
  28. package/lib/executor/git-parser.js +397 -0
  29. package/lib/executor/pr-review.d.ts +95 -0
  30. package/lib/executor/pr-review.js +175 -0
  31. package/lib/executor/run-log.d.ts +36 -0
  32. package/lib/executor/run-log.js +78 -0
  33. package/lib/executor.d.ts +277 -6
  34. package/lib/executor.js +1265 -94
  35. package/lib/github-service.d.ts +1 -1
  36. package/lib/main.d.ts +92 -18
  37. package/lib/main.js +186 -95
  38. package/lib/renderers/common.d.ts +0 -3
  39. package/lib/renderers/common.js +8 -44
  40. package/lib/renderers/markdown.d.ts +1 -0
  41. package/lib/renderers/markdown.js +1 -0
  42. package/lib/renderers/pr.js +26 -1
  43. package/lib/renderers/task.d.ts +9 -0
  44. package/lib/renderers/task.js +120 -0
  45. package/lib/review-rules.d.ts +60 -0
  46. package/lib/review-rules.js +204 -0
  47. package/lib/shared/state-badges.d.ts +42 -0
  48. package/lib/shared/state-badges.js +102 -0
  49. package/lib/task-primitives.d.ts +109 -0
  50. package/lib/task-primitives.js +194 -0
  51. package/lib/tool-tiers.d.ts +57 -0
  52. package/lib/tool-tiers.js +168 -0
  53. package/lib/tools/agent-task.js +4 -4
  54. package/lib/tools/api.js +18 -6
  55. package/lib/tools/cache.js +42 -40
  56. package/lib/tools/codespace.js +4 -12
  57. package/lib/tools/discussion.js +4 -4
  58. package/lib/tools/gist.d.ts +1 -39
  59. package/lib/tools/gist.js +4 -14
  60. package/lib/tools/help.js +72 -18
  61. package/lib/tools/image-utils.d.ts +27 -0
  62. package/lib/tools/image-utils.js +73 -3
  63. package/lib/tools/issue.js +16 -28
  64. package/lib/tools/label.js +4 -15
  65. package/lib/tools/pr.d.ts +47 -0
  66. package/lib/tools/pr.js +170 -33
  67. package/lib/tools/project.js +4 -13
  68. package/lib/tools/release.js +11 -22
  69. package/lib/tools/repo.js +4 -4
  70. package/lib/tools/ruleset.js +9 -6
  71. package/lib/tools/run.js +76 -24
  72. package/lib/tools/secret.js +11 -17
  73. package/lib/tools/skill.js +18 -22
  74. package/lib/tools/status.js +4 -4
  75. package/lib/tools/task.d.ts +219 -0
  76. package/lib/tools/task.js +457 -0
  77. package/lib/types.d.ts +514 -1
  78. package/lib/ui/help-modal.d.ts +6 -0
  79. package/lib/ui/help-modal.js +124 -16
  80. package/lib/ui/issue-modal.d.ts +14 -0
  81. package/lib/ui/issue-modal.js +370 -0
  82. package/lib/ui/markdown-lite.d.ts +66 -0
  83. package/lib/ui/markdown-lite.js +312 -0
  84. package/lib/ui/network-banner.d.ts +12 -0
  85. package/lib/ui/network-banner.js +35 -0
  86. package/lib/ui/pr-modal.d.ts +17 -0
  87. package/lib/ui/pr-modal.js +555 -0
  88. package/lib/ui/repo-badges.d.ts +43 -0
  89. package/lib/ui/repo-badges.js +82 -0
  90. package/lib/ui/repo-selector.d.ts +14 -0
  91. package/lib/ui/repo-selector.js +112 -0
  92. package/lib/ui/run-modal.d.ts +26 -0
  93. package/lib/ui/run-modal.js +486 -0
  94. package/lib/ui/storage.d.ts +47 -0
  95. package/lib/ui/storage.js +139 -0
  96. package/lib/ui/styles.d.ts +1 -1
  97. package/lib/ui/styles.js +1330 -44
  98. package/lib/ui/sync-settings-modal.js +1 -1
  99. package/lib/ui/utils.js +13 -1
  100. package/lib/ui/workspace-panel.js +226 -29
  101. package/lib/version.d.ts +1 -1
  102. package/lib/version.js +1 -1
  103. package/package.json +9 -2
@@ -0,0 +1,82 @@
1
+ /**
2
+ * 仓库卡片徽章推导(纯函数,无 React 依赖,可被单测直接覆盖)。
3
+ *
4
+ * 为什么独立成模块:`client.ts` 是近 2000 行的巨石,其中的 UI 判定逻辑
5
+ * 只能靠「bundle 里有没有这个字符串」这类弱断言覆盖。把判定规则抽成纯函数后,
6
+ * 「什么情况出什么徽章」可以被真实断言,而不是靠人工目测。
7
+ */
8
+ /** 只接受 http/https 的绝对 URL;其余(javascript:、data:、相对路径)一律拒收 */
9
+ function safeHttpUrl(raw) {
10
+ if (typeof raw !== 'string' || !raw)
11
+ return null;
12
+ try {
13
+ const u = new URL(raw);
14
+ return u.protocol === 'http:' || u.protocol === 'https:' ? u.toString() : null;
15
+ }
16
+ catch {
17
+ return null;
18
+ }
19
+ }
20
+ /**
21
+ * 依据仓库元数据推导展示信息。
22
+ *
23
+ * 三条规则(与上位方案 S4 展示契约对齐,但**不含上游 star**):
24
+ * 1. `isFork` 为 true → `⑂ fork` 徽章;有 parent 时附带血缘行
25
+ * 2. `viewerPermission` 为 READ / TRIAGE → `🔒 只读` 徽章
26
+ * 3. 其余一律零徽章(保持现有外观)
27
+ *
28
+ * 为什么权限未知时不显示任何徽章:离线数据没有该字段,此时既不能说
29
+ * 「可写」也不能说「只读」——沉默优于猜测。显示一个可能错误的徽章,
30
+ * 比不显示更糟。
31
+ *
32
+ * 为什么不展示上游 star:`parent` 只返回骨架,拿不到 stargazerCount
33
+ * (`--json` 不支持嵌套子字段,实测);补一次网络往返实测 3~6 秒,
34
+ * 而 star 数不是决策信息。详见 s4 执行方案 §2.1。
35
+ *
36
+ * @param repo RepoMetadata 或 null/undefined(离线、非 Git 工作区)
37
+ */
38
+ export function resolveRepoBadges(repo) {
39
+ const badges = [];
40
+ let forkLine = null;
41
+ if (!repo || typeof repo !== 'object') {
42
+ return { badges, forkLine };
43
+ }
44
+ // ---- 规则 1:fork ----
45
+ // 用 `=== true` 严格比较:字符串 "true" / 数字 1 都不算——
46
+ // 这类来源不明的真值若被接受,会在数据形态漂移时静默产生错误徽章。
47
+ if (repo.isFork === true) {
48
+ const rawName = typeof repo.parent?.nameWithOwner === 'string' ? repo.parent.nameWithOwner.trim() : '';
49
+ if (rawName) {
50
+ // URL 只信 http/https:parent.url 来自 API 响应,直接塞进 href 会承接
51
+ // 任意协议(javascript:/data:)。校验不过就从 nameWithOwner 重建——
52
+ // 拼出来的地址一定是 https://github.com/<owner>/<repo>,天然安全。
53
+ const parentUrl = safeHttpUrl(repo.parent?.url) || `https://github.com/${rawName}`;
54
+ forkLine = { parentNameWithOwner: rawName, parentUrl };
55
+ badges.push({
56
+ text: '⑂ fork',
57
+ tone: 'purple',
58
+ title: `这是 ${rawName} 的 fork。改动默认提交到你的 fork,需要时再向上游发起 PR。`,
59
+ });
60
+ }
61
+ else {
62
+ // 拿不到上游也必须告诉用户「这是 fork」——那是确定事实
63
+ badges.push({
64
+ text: '⑂ fork',
65
+ tone: 'purple',
66
+ title: '这是一个 fork 仓库(未能解析出上游仓库)。',
67
+ });
68
+ }
69
+ }
70
+ // ---- 规则 2:只读 ----
71
+ // 只认 READ / TRIAGE:这两个档位必然无法写代码。MAINTAIN 及以上均可写。
72
+ const perm = typeof repo.viewerPermission === 'string' ? repo.viewerPermission.toUpperCase() : '';
73
+ if (perm === 'READ' || perm === 'TRIAGE') {
74
+ badges.push({
75
+ text: '🔒 只读',
76
+ tone: 'yellow',
77
+ title: '当前账号对该仓库仅有只读权限,写操作(创建 Issue / PR / 合并等)会被 GitHub 拒绝。' +
78
+ '如需提交改动,请先 fork 到自己的账号(gh repo fork --clone)。',
79
+ });
80
+ }
81
+ return { badges, forkLine };
82
+ }
@@ -0,0 +1,14 @@
1
+ export interface RepoSelectorProps {
2
+ associatedRepos?: any[];
3
+ currentRepo?: string;
4
+ cwd: string | null;
5
+ onRepoSwitched?: (repo: string) => void;
6
+ showToast?: (msg: string) => void;
7
+ }
8
+ /**
9
+ * 关联仓库(上游源仓库 ↔ 个人复刻 Fork)切换下拉组件。
10
+ *
11
+ * 为什么单仓库时返回 null:单仓库场景没有切换意义,保持现有卡片外观 100% 纯净零噪音;
12
+ * 仅在检测到当前工作区存在双 remote 或 Fork 关联拓扑时激活。
13
+ */
14
+ export declare function RepoSelectorDropdown(props: RepoSelectorProps): any;
@@ -0,0 +1,112 @@
1
+ /**
2
+ * 关联仓库(上游源仓库 ↔ 个人复刻 Fork)切换下拉组件。
3
+ *
4
+ * 为什么单仓库时返回 null:单仓库场景没有切换意义,保持现有卡片外观 100% 纯净零噪音;
5
+ * 仅在检测到当前工作区存在双 remote 或 Fork 关联拓扑时激活。
6
+ */
7
+ export function RepoSelectorDropdown(props) {
8
+ var associatedRepos = Array.isArray(props.associatedRepos) ? props.associatedRepos : [];
9
+ var currentRepo = (props.currentRepo || '').toLowerCase();
10
+ var cwd = props.cwd;
11
+ var onRepoSwitched = props.onRepoSwitched || (() => { });
12
+ var showToast = props.showToast || (() => { });
13
+ // 严格遵守 React Hooks 铁律:所有 useState / useRef / useEffect 必须无条件在顶层执行,
14
+ // 严禁在任何条件分支/early return 之后调用 Hooks,杜绝触发
15
+ // "Rendered fewer hooks than expected" 的致命运行时崩溃。
16
+ var [isOpen, setIsOpen] = React.useState(false);
17
+ var [switching, setSwitching] = React.useState(false);
18
+ var containerRef = React.useRef(null);
19
+ React.useEffect(() => {
20
+ if (!isOpen)
21
+ return;
22
+ var handleClickOutside = (e) => {
23
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
24
+ setIsOpen(false);
25
+ }
26
+ };
27
+ document.addEventListener('mousedown', handleClickOutside);
28
+ return () => document.removeEventListener('mousedown', handleClickOutside);
29
+ }, [isOpen]);
30
+ // Hooks 执行完毕后再进行单仓库零噪音短路返回
31
+ if (associatedRepos.length <= 1) {
32
+ return null;
33
+ }
34
+ var toggleOpen = (e) => {
35
+ e.stopPropagation();
36
+ setIsOpen(!isOpen);
37
+ };
38
+ var handleSwitch = async (targetRepo, targetRemote) => {
39
+ if (switching)
40
+ return;
41
+ setSwitching(true);
42
+ try {
43
+ var res = await fetch('/api/github/action', {
44
+ method: 'POST',
45
+ headers: { 'Content-Type': 'application/json' },
46
+ body: JSON.stringify({
47
+ action: 'set_default_repo',
48
+ cwd,
49
+ repo: targetRepo,
50
+ remote: targetRemote,
51
+ }),
52
+ });
53
+ var json = await res.json().catch(() => ({}));
54
+ if (!res.ok || !json.ok) {
55
+ showToast(`❌ 切换基准仓库失败:${json.error || json.message || res.statusText}`);
56
+ return;
57
+ }
58
+ setIsOpen(false);
59
+ showToast(`✅ 已将基准仓库切换为 ${targetRepo}`);
60
+ onRepoSwitched(targetRepo);
61
+ }
62
+ catch (err) {
63
+ showToast(`❌ 切换请求异常:${err?.message || err}`);
64
+ }
65
+ finally {
66
+ setSwitching(false);
67
+ }
68
+ };
69
+ var roleLabel = (role) => {
70
+ switch (role) {
71
+ case 'fork': return '我的复刻';
72
+ case 'upstream': return '上游源仓库';
73
+ case 'parent': return '上游主干';
74
+ case 'origin': return 'origin';
75
+ default: return '远程关联';
76
+ }
77
+ };
78
+ var roleBadgeTone = (role) => {
79
+ switch (role) {
80
+ case 'fork': return 'dsh-badge-purple';
81
+ case 'upstream':
82
+ case 'parent': return 'dsh-badge-readonly';
83
+ default: return 'dsh-badge-blue';
84
+ }
85
+ };
86
+ var h = React.createElement;
87
+ return h('div', {
88
+ ref: containerRef,
89
+ style: { position: 'relative', display: 'inline-flex', alignItems: 'center' },
90
+ className: 'dsh-github-repo-selector-container',
91
+ }, h('button', {
92
+ type: 'button',
93
+ className: 'dsh-github-repo-switch-trigger',
94
+ onClick: toggleOpen,
95
+ title: `当前关联 ${associatedRepos.length} 个远程仓库(点击切换基准)`,
96
+ disabled: switching,
97
+ }, h('span', null, switching ? '⏳' : '⇄'), h('span', { style: { fontSize: '11px', fontWeight: 500 } }, '切换仓库'), h('span', { style: { fontSize: '9px', opacity: 0.7 } }, '▾')), isOpen
98
+ ? h('div', { className: 'dsh-github-repo-dropdown' }, h('div', { className: 'dsh-github-repo-dropdown-header' }, h('span', null, '选择当前工作区基准仓库'), h('span', { style: { fontSize: '11px', opacity: 0.6 } }, `${associatedRepos.length} 可用`)), h('div', { className: 'dsh-github-repo-dropdown-list' }, associatedRepos.map(function (item) {
99
+ var isSelected = item.isCurrent || item.nameWithOwner.toLowerCase() === currentRepo;
100
+ return h('div', {
101
+ key: item.nameWithOwner,
102
+ className: 'dsh-github-repo-item' + (isSelected ? ' selected' : ''),
103
+ title: isSelected ? '当前正在使用的基准仓库' : `点击切换基准为 ${item.nameWithOwner}`,
104
+ onClick: function () {
105
+ if (!isSelected && !switching) {
106
+ handleSwitch(item.nameWithOwner, item.remoteName);
107
+ }
108
+ },
109
+ }, h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '8px' } }, h('div', { style: { display: 'flex', alignItems: 'center', gap: '6px', minWidth: 0 } }, h('span', { style: { color: isSelected ? 'var(--dsw-alias-link, #0969da)' : 'inherit', fontWeight: isSelected ? 600 : 400 } }, item.nameWithOwner), h('span', { className: 'dsh-github-badge ' + roleBadgeTone(item.role), style: { fontSize: '10px', padding: '0 5px' } }, roleLabel(item.role))), isSelected ? h('span', { style: { color: 'var(--dsw-alias-state-success, #1a7f37)', fontWeight: 600 } }, '✓') : null));
110
+ })), h('div', { className: 'dsh-github-repo-dropdown-footer' }, '💡 切换后系统将自动调用 gh repo set-default 对齐终端与面板'))
111
+ : null);
112
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Actions run / job / step 的耗时格式化(导出以便单测直接覆盖)。
3
+ *
4
+ * 为什么不用 formatRelativeTime:那个函数答的是「发生在多久前」,这里是「跑了多久」;
5
+ * 而且 run → job → step 三层共用同一套精度规则(<60s → `12s`、<60m → `3m20s`、
6
+ * 其余 `1h05m`),分钟/秒补零才能让三层的列宽稳定对齐。
7
+ * 任一端缺失或不可解析一律返回空串:排队中/已跳过的 job 没有 startedAt/completedAt,
8
+ * 硬算只会渲染出「0s」这种假精确值,比不显示更误导。
9
+ */
10
+ export declare function formatRunDuration(startISO?: string, endISO?: string): string;
11
+ export interface RunDetailModalProps {
12
+ runId: number;
13
+ repoNameWithOwner?: string;
14
+ cwd?: string | null;
15
+ onClose: () => void;
16
+ showToast?: (msg: string) => void;
17
+ }
18
+ /**
19
+ * Actions Run 详情弹窗(只读:run 元信息 + job/step 树 + 失败日志片段)。
20
+ *
21
+ * 单列纵向:run 的信息天然是「job 列表 + 一条日志流」的阅读顺序,没有需要并排
22
+ * 对照的第二个信息源(PR 才有 diff 双栏)。
23
+ * 日志刻意渲染成 <pre> 原文而不是 markdown-lite:原始日志里的 `#`、`|`、`---`
24
+ * 是 shell 注释、表格与 diff 分隔符,交给 Markdown 解析会把堆栈与 diff 撕碎。
25
+ */
26
+ export declare function RunDetailModal(props: RunDetailModalProps): any;