codetrap 0.1.5 → 0.1.7

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.
@@ -0,0 +1,335 @@
1
+ export const WEB_TEXT = {
2
+ en: {
3
+ "app.subtitle": "review console",
4
+ "nav.review": "Review",
5
+ "nav.library": "Library",
6
+ "nav.insights": "Insights",
7
+ "action.refresh": "Refresh",
8
+ "action.add": "Add",
9
+ "action.deleteSession": "Delete",
10
+ "action.cleanDeletedCandidates": "Clear deleted candidates",
11
+ "section.sessions": "sessions",
12
+ "placeholder.projectPath": "/path/to/project",
13
+ "title.candidateInbox": "candidate inbox",
14
+ "title.candidateDetail": "candidate detail",
15
+ "title.trapLibrary": "trap library",
16
+ "title.trapDetail": "trap detail",
17
+ "title.growthInsights": "growth insights",
18
+ "title.insightDetail": "insight detail",
19
+ "title.recentTraps": "recent traps",
20
+ "title.mostViewed": "most viewed",
21
+ "title.recentHighSeverity": "recent high severity",
22
+ "title.evidence": "evidence",
23
+ "title.possibleConflicts": "possible conflicts",
24
+ "title.before": "Before",
25
+ "title.after": "After",
26
+ "tab.inbox": "Inbox {count}",
27
+ "tab.reviewed": "Reviewed {count}",
28
+ "meta.noProject": "no project selected",
29
+ "meta.noSession": "no session selected",
30
+ "meta.sessionCounts": "{goal} / {pending} pending, {reviewed} reviewed",
31
+ "meta.libraryCounts": "{shown} shown / {loaded} loaded / {sort}",
32
+ "meta.insightCounts": "{count} traps / {status} status",
33
+ "meta.selectCandidate": "select a candidate",
34
+ "meta.selectTrap": "select a trap",
35
+ "meta.selectProject": "select a project",
36
+ "empty.noProjects": "No projects",
37
+ "empty.noSessions": "No sessions",
38
+ "empty.noPending": "No pending candidates",
39
+ "empty.noReviewed": "No reviewed candidates",
40
+ "empty.noTrapMatches": "No traps match this view",
41
+ "empty.noTrapSelected": "No trap selected",
42
+ "empty.loadingTrapDetails": "Loading trap details",
43
+ "empty.noCandidateSelected": "No candidate selected",
44
+ "empty.noEvidence": "No evidence",
45
+ "empty.noData": "No data",
46
+ "empty.noTraps": "No traps",
47
+ "action.viewTrap": "View trap",
48
+ "action.clearFilters": "Clear filters",
49
+ "action.save": "Save",
50
+ "action.accept": "Accept",
51
+ "action.reject": "Reject",
52
+ "action.acceptAnyway": "Accept anyway",
53
+ "action.supersede": "Supersede",
54
+ "placeholder.searchTraps": "Search title, context, mistake, fix, tags",
55
+ "placeholder.anyModule": "any module",
56
+ "placeholder.anyOwner": "any owner",
57
+ "placeholder.supersedesId": "supersedes id",
58
+ "label.scope": "Scope",
59
+ "label.status": "Status",
60
+ "label.category": "Category",
61
+ "label.sort": "Sort",
62
+ "label.module": "Module",
63
+ "label.owner": "Owner",
64
+ "label.title": "Title",
65
+ "label.severity": "Severity",
66
+ "label.tags": "Tags",
67
+ "label.pathGlobs": "Path globs",
68
+ "label.context": "Context",
69
+ "label.mistake": "Mistake",
70
+ "label.fix": "Fix",
71
+ "label.created": "Created",
72
+ "label.updated": "Updated",
73
+ "label.stateKey": "State key",
74
+ "label.supersedes": "Supersedes",
75
+ "label.validFrom": "Valid from",
76
+ "label.validUntil": "Valid until",
77
+ "metric.loadedTraps": "Loaded traps",
78
+ "metric.confirmedTraps": "Confirmed traps",
79
+ "metric.highSeverity": "High severity",
80
+ "metric.topCategory": "Top category",
81
+ "metric.focusArea": "Focus area",
82
+ "metric.mostViewed": "Most viewed",
83
+ "metric.currentFilters": "current filters",
84
+ "metric.selectedScope": "selected scope",
85
+ "metric.errorCritical": "error + critical",
86
+ "metric.repeatedPattern": "repeated pattern",
87
+ "metric.largestPattern": "largest pattern",
88
+ "metric.module": "module",
89
+ "metric.tag": "tag",
90
+ "metric.noHits": "no hits yet",
91
+ "insight.categories": "categories",
92
+ "insight.modules": "modules",
93
+ "insight.tags": "tags",
94
+ "insight.severityMix": "severity mix",
95
+ "option.projectGlobal": "project + global",
96
+ "option.allCategories": "all categories",
97
+ "sort.updated": "recently updated",
98
+ "sort.severity": "severity",
99
+ "sort.hits": "hit count",
100
+ "sort.category": "category",
101
+ "sort.title": "title",
102
+ "sortLabel.updated": "recent first",
103
+ "sortLabel.severity": "severity first",
104
+ "sortLabel.hits": "hits first",
105
+ "sortLabel.category": "category sort",
106
+ "sortLabel.title": "title sort",
107
+ "pill.hits": "{count} hits",
108
+ "pill.candidates": "{count} candidates",
109
+ "pill.accepted": "{count} accepted",
110
+ "pill.warnings": "{count} warnings",
111
+ "pill.quality": "quality {score}",
112
+ "pill.conflict": "conflict {status}",
113
+ "pill.action": "action {action}",
114
+ "review.pending": "pending review",
115
+ "review.rejected": "rejected",
116
+ "review.accepted": "accepted -> trap #{id}",
117
+ "review.acceptedDeleted": "accepted -> trap #{id} deleted",
118
+ "review.acceptedLinkMissing": "accepted -> trap link missing",
119
+ "status.refreshed": "Refreshed",
120
+ "status.candidateSaved": "Candidate saved",
121
+ "status.candidateRejected": "Candidate rejected",
122
+ "status.candidateAccepted": "Candidate accepted",
123
+ "status.possibleConflict": "Possible conflict found",
124
+ "status.supersedesRequired": "Supersedes id is required",
125
+ "status.openedTrap": "Opened trap #{id}",
126
+ "status.trapNotInLibrary": "Trap #{id} is not in the current library",
127
+ "status.sessionDeleted": "Session deleted",
128
+ "status.deletedCandidatesCleaned": "Deleted candidate links cleared",
129
+ "prompt.rejectReason": "Reject reason",
130
+ "prompt.deleteSession": "Delete session {id}?",
131
+ "value.project": "project",
132
+ "value.global": "global",
133
+ "value.active": "active",
134
+ "value.all": "all",
135
+ "value.archived": "archived",
136
+ "value.superseded": "superseded",
137
+ "value.proposed": "proposed",
138
+ "value.accepted": "accepted",
139
+ "value.rejected": "rejected",
140
+ "value.accepted_missing": "accepted missing",
141
+ "value.warning": "warning",
142
+ "value.error": "error",
143
+ "value.critical": "critical",
144
+ "value.api": "api",
145
+ "value.database": "database",
146
+ "value.auth": "auth",
147
+ "value.convention": "convention",
148
+ "value.security": "security",
149
+ "value.performance": "performance",
150
+ "value.bug": "bug",
151
+ "value.other": "other",
152
+ "value.none": "none",
153
+ "value.possible": "possible",
154
+ "value.confirmed": "confirmed",
155
+ "value.accept": "accept",
156
+ "value.edit": "edit",
157
+ "value.supersede": "supersede",
158
+ "value.archive_old": "archive old",
159
+ "value.manual": "manual",
160
+ "value.conversation": "conversation",
161
+ "value.commit": "commit",
162
+ "value.issue": "issue",
163
+ "value.test_failure": "test failure",
164
+ "value.article": "article",
165
+ },
166
+ zh: {
167
+ "app.subtitle": "复盘控制台",
168
+ "nav.review": "审核",
169
+ "nav.library": "库",
170
+ "nav.insights": "洞察",
171
+ "action.refresh": "刷新",
172
+ "action.add": "添加",
173
+ "action.deleteSession": "删除",
174
+ "action.cleanDeletedCandidates": "清除已删除候选",
175
+ "section.sessions": "会话",
176
+ "placeholder.projectPath": "/项目/路径",
177
+ "title.candidateInbox": "候选收件箱",
178
+ "title.candidateDetail": "候选详情",
179
+ "title.trapLibrary": "陷阱库",
180
+ "title.trapDetail": "陷阱详情",
181
+ "title.growthInsights": "成长洞察",
182
+ "title.insightDetail": "洞察详情",
183
+ "title.recentTraps": "最近陷阱",
184
+ "title.mostViewed": "查看最多",
185
+ "title.recentHighSeverity": "最近高严重度",
186
+ "title.evidence": "证据",
187
+ "title.possibleConflicts": "可能冲突",
188
+ "title.before": "修改前",
189
+ "title.after": "修改后",
190
+ "tab.inbox": "待审 {count}",
191
+ "tab.reviewed": "已审 {count}",
192
+ "meta.noProject": "未选择项目",
193
+ "meta.noSession": "未选择会话",
194
+ "meta.sessionCounts": "{goal} / {pending} 个待审,{reviewed} 个已审",
195
+ "meta.libraryCounts": "显示 {shown} / 已加载 {loaded} / {sort}",
196
+ "meta.insightCounts": "{count} 条陷阱 / 状态 {status}",
197
+ "meta.selectCandidate": "选择一个候选",
198
+ "meta.selectTrap": "选择一个陷阱",
199
+ "meta.selectProject": "选择一个项目",
200
+ "empty.noProjects": "没有项目",
201
+ "empty.noSessions": "没有会话",
202
+ "empty.noPending": "没有待审候选",
203
+ "empty.noReviewed": "没有已审候选",
204
+ "empty.noTrapMatches": "没有匹配的陷阱",
205
+ "empty.noTrapSelected": "未选择陷阱",
206
+ "empty.loadingTrapDetails": "正在加载陷阱详情",
207
+ "empty.noCandidateSelected": "未选择候选",
208
+ "empty.noEvidence": "没有证据",
209
+ "empty.noData": "没有数据",
210
+ "empty.noTraps": "没有陷阱",
211
+ "action.viewTrap": "查看陷阱",
212
+ "action.clearFilters": "清除筛选",
213
+ "action.save": "保存",
214
+ "action.accept": "接受",
215
+ "action.reject": "拒绝",
216
+ "action.acceptAnyway": "仍然接受",
217
+ "action.supersede": "标记取代",
218
+ "placeholder.searchTraps": "搜索标题、上下文、错误、修复、标签",
219
+ "placeholder.anyModule": "任意模块",
220
+ "placeholder.anyOwner": "任意负责人",
221
+ "placeholder.supersedesId": "被取代的 id",
222
+ "label.scope": "范围",
223
+ "label.status": "状态",
224
+ "label.category": "分类",
225
+ "label.sort": "排序",
226
+ "label.module": "模块",
227
+ "label.owner": "负责人",
228
+ "label.title": "标题",
229
+ "label.severity": "严重度",
230
+ "label.tags": "标签",
231
+ "label.pathGlobs": "路径规则",
232
+ "label.context": "上下文",
233
+ "label.mistake": "错误",
234
+ "label.fix": "修复",
235
+ "label.created": "创建时间",
236
+ "label.updated": "更新时间",
237
+ "label.stateKey": "状态键",
238
+ "label.supersedes": "取代",
239
+ "label.validFrom": "生效开始",
240
+ "label.validUntil": "生效结束",
241
+ "metric.loadedTraps": "已加载陷阱",
242
+ "metric.confirmedTraps": "确认陷阱",
243
+ "metric.highSeverity": "高严重度",
244
+ "metric.topCategory": "最高分类",
245
+ "metric.focusArea": "关注区域",
246
+ "metric.mostViewed": "查看最多",
247
+ "metric.currentFilters": "当前筛选",
248
+ "metric.selectedScope": "选中范围",
249
+ "metric.errorCritical": "error + critical",
250
+ "metric.repeatedPattern": "重复模式",
251
+ "metric.largestPattern": "最大模式",
252
+ "metric.module": "模块",
253
+ "metric.tag": "标签",
254
+ "metric.noHits": "还没有查看记录",
255
+ "insight.categories": "分类",
256
+ "insight.modules": "模块",
257
+ "insight.tags": "标签",
258
+ "insight.severityMix": "严重度分布",
259
+ "option.projectGlobal": "项目 + 全局",
260
+ "option.allCategories": "全部分类",
261
+ "sort.updated": "最近更新",
262
+ "sort.severity": "严重度",
263
+ "sort.hits": "查看次数",
264
+ "sort.category": "分类",
265
+ "sort.title": "标题",
266
+ "sortLabel.updated": "最近优先",
267
+ "sortLabel.severity": "严重度优先",
268
+ "sortLabel.hits": "查看次数优先",
269
+ "sortLabel.category": "按分类排序",
270
+ "sortLabel.title": "按标题排序",
271
+ "pill.hits": "{count} 次查看",
272
+ "pill.candidates": "{count} 个候选",
273
+ "pill.accepted": "{count} 个已接受",
274
+ "pill.warnings": "{count} 个警告",
275
+ "pill.quality": "质量 {score}",
276
+ "pill.conflict": "冲突 {status}",
277
+ "pill.action": "建议 {action}",
278
+ "review.pending": "待审核",
279
+ "review.rejected": "已拒绝",
280
+ "review.accepted": "已接受 -> 陷阱 #{id}",
281
+ "review.acceptedDeleted": "已接受 -> 陷阱 #{id} 已删除",
282
+ "review.acceptedLinkMissing": "已接受 -> 缺少陷阱链接",
283
+ "status.refreshed": "已刷新",
284
+ "status.candidateSaved": "候选已保存",
285
+ "status.candidateRejected": "候选已拒绝",
286
+ "status.candidateAccepted": "候选已接受",
287
+ "status.possibleConflict": "发现可能冲突",
288
+ "status.supersedesRequired": "需要填写被取代的 id",
289
+ "status.openedTrap": "已打开陷阱 #{id}",
290
+ "status.trapNotInLibrary": "当前陷阱库里没有陷阱 #{id}",
291
+ "status.sessionDeleted": "会话已删除",
292
+ "status.deletedCandidatesCleaned": "已清除删除候选链接",
293
+ "prompt.rejectReason": "拒绝原因",
294
+ "prompt.deleteSession": "删除会话 {id}?",
295
+ "value.project": "项目",
296
+ "value.global": "全局",
297
+ "value.active": "有效",
298
+ "value.all": "全部",
299
+ "value.archived": "已归档",
300
+ "value.superseded": "已取代",
301
+ "value.proposed": "待提议",
302
+ "value.accepted": "已接受",
303
+ "value.rejected": "已拒绝",
304
+ "value.accepted_missing": "接受记录缺失",
305
+ "value.warning": "警告",
306
+ "value.error": "错误",
307
+ "value.critical": "严重",
308
+ "value.api": "API",
309
+ "value.database": "数据库",
310
+ "value.auth": "认证",
311
+ "value.convention": "约定",
312
+ "value.security": "安全",
313
+ "value.performance": "性能",
314
+ "value.bug": "缺陷",
315
+ "value.other": "其他",
316
+ "value.none": "无",
317
+ "value.possible": "可能",
318
+ "value.confirmed": "确认",
319
+ "value.accept": "接受",
320
+ "value.edit": "编辑",
321
+ "value.supersede": "取代",
322
+ "value.archive_old": "归档旧项",
323
+ "value.manual": "手动",
324
+ "value.conversation": "对话",
325
+ "value.commit": "提交",
326
+ "value.issue": "Issue",
327
+ "value.test_failure": "测试失败",
328
+ "value.article": "文章",
329
+ }
330
+ } as const;
331
+
332
+ export type WebLocale = keyof typeof WEB_TEXT;
333
+ export type WebTextKey = keyof typeof WEB_TEXT["en"];
334
+
335
+ export const WEB_TEXT_JSON = JSON.stringify(WEB_TEXT);
@@ -0,0 +1,106 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { basename, join } from "node:path";
4
+ import { CODETRAP_DIR } from "../lib/constants";
5
+ import { findProjectRoot, resolveScopePath } from "../lib/scope";
6
+
7
+ export const WEB_PROJECTS_FILE = "web-projects.json";
8
+ export const WEB_PROJECTS_VERSION = 1;
9
+
10
+ export interface WebProject {
11
+ root: string;
12
+ name: string;
13
+ last_opened_at: string;
14
+ }
15
+
16
+ export interface WebProjectRegistry {
17
+ version: typeof WEB_PROJECTS_VERSION;
18
+ projects: WebProject[];
19
+ }
20
+
21
+ export function webProjectsPath(home = homedir()): string {
22
+ return join(home, CODETRAP_DIR, WEB_PROJECTS_FILE);
23
+ }
24
+
25
+ export function loadWebProjectRegistry(home = homedir()): WebProjectRegistry {
26
+ const path = webProjectsPath(home);
27
+ if (!existsSync(path)) return emptyRegistry();
28
+ try {
29
+ const parsed = JSON.parse(readFileSync(path, "utf-8"));
30
+ return normalizeRegistry(parsed);
31
+ } catch (error) {
32
+ const message = error instanceof Error ? error.message : String(error);
33
+ throw new Error(`Invalid codetrap web project registry at ${path}: ${message}`);
34
+ }
35
+ }
36
+
37
+ export function saveWebProjectRegistry(registry: WebProjectRegistry, home = homedir()): void {
38
+ const dir = join(home, CODETRAP_DIR);
39
+ mkdirSync(dir, { recursive: true });
40
+ writeFileSync(webProjectsPath(home), `${JSON.stringify(normalizeRegistry(registry), null, 2)}\n`);
41
+ }
42
+
43
+ export function resolveWebProjectRoot(path: string, home = homedir()): string {
44
+ const resolved = resolveScopePath(path);
45
+ const root = findProjectRoot(resolved, home);
46
+ if (!root) {
47
+ throw new Error(`No initialized codetrap project found at or above ${resolved}. Run 'codetrap init' first.`);
48
+ }
49
+ return root;
50
+ }
51
+
52
+ export function addWebProject(path: string, home = homedir(), now = new Date()): WebProject {
53
+ const root = resolveWebProjectRoot(path, home);
54
+ const registry = loadWebProjectRegistry(home);
55
+ const project: WebProject = {
56
+ root,
57
+ name: basename(root) || root,
58
+ last_opened_at: now.toISOString(),
59
+ };
60
+ const projects = [
61
+ project,
62
+ ...registry.projects.filter((item) => item.root !== root),
63
+ ].sort((a, b) => b.last_opened_at.localeCompare(a.last_opened_at));
64
+ saveWebProjectRegistry({ version: WEB_PROJECTS_VERSION, projects }, home);
65
+ return project;
66
+ }
67
+
68
+ function emptyRegistry(): WebProjectRegistry {
69
+ return { version: WEB_PROJECTS_VERSION, projects: [] };
70
+ }
71
+
72
+ function normalizeRegistry(value: unknown): WebProjectRegistry {
73
+ if (!isRecord(value) || !Array.isArray(value.projects)) return emptyRegistry();
74
+ const projects = value.projects
75
+ .map(normalizeProject)
76
+ .filter((project): project is WebProject => project !== null);
77
+ return {
78
+ version: WEB_PROJECTS_VERSION,
79
+ projects: uniqueProjects(projects).sort((a, b) => b.last_opened_at.localeCompare(a.last_opened_at)),
80
+ };
81
+ }
82
+
83
+ function normalizeProject(value: unknown): WebProject | null {
84
+ if (!isRecord(value) || typeof value.root !== "string") return null;
85
+ const root = resolveScopePath(value.root);
86
+ return {
87
+ root,
88
+ name: typeof value.name === "string" && value.name.trim() ? value.name.trim() : basename(root) || root,
89
+ last_opened_at: typeof value.last_opened_at === "string" ? value.last_opened_at : new Date(0).toISOString(),
90
+ };
91
+ }
92
+
93
+ function uniqueProjects(projects: WebProject[]): WebProject[] {
94
+ const seen = new Set<string>();
95
+ const out: WebProject[] = [];
96
+ for (const project of projects) {
97
+ if (seen.has(project.root)) continue;
98
+ seen.add(project.root);
99
+ out.push(project);
100
+ }
101
+ return out;
102
+ }
103
+
104
+ function isRecord(value: unknown): value is Record<string, unknown> {
105
+ return typeof value === "object" && value !== null && !Array.isArray(value);
106
+ }