crewx 0.8.9-rc.2 → 0.8.9-rc.21

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 (82) hide show
  1. package/dist/assets/{MarketPage-BpajB_Ex.js → MarketPage-Cl9KvLAo.js} +1 -1
  2. package/dist/assets/PromptTab-gj_BvClc.js +27 -0
  3. package/dist/assets/{arc-B0fFLEOc.js → arc-DDzVSXZh.js} +1 -1
  4. package/dist/assets/{architectureDiagram-UYN6MBPD-CrC7MKTM.js → architectureDiagram-UYN6MBPD-p8OOXe5w.js} +1 -1
  5. package/dist/assets/{blockDiagram-ZHA2E4KO-DJsG-9x2.js → blockDiagram-ZHA2E4KO-BTlfXGnf.js} +7 -7
  6. package/dist/assets/{c4Diagram-6F5ED5ID-DlkyjvkS.js → c4Diagram-6F5ED5ID-CKbrDPWE.js} +1 -1
  7. package/dist/assets/channel-Hzz-cXd0.js +1 -0
  8. package/dist/assets/{chunk-5HRBRIJM-B_Csqjvw.js → chunk-5HRBRIJM-otR_A_1G.js} +1 -1
  9. package/dist/assets/{chunk-7U56Z5CX-BlNdzxbh.js → chunk-7U56Z5CX-BliJIH4-.js} +1 -1
  10. package/dist/assets/{chunk-ASOPGD6M-BiFWfa3w.js → chunk-ASOPGD6M-CmzypD4Z.js} +1 -1
  11. package/dist/assets/{chunk-KFBOBJHC-ocugjY-9.js → chunk-KFBOBJHC-BW4u3lPL.js} +1 -1
  12. package/dist/assets/{chunk-T2TOU4HS-kUreJ-bv.js → chunk-T2TOU4HS-BAp6Xscz.js} +1 -1
  13. package/dist/assets/{chunk-TMUBEWPD-Du19Q8X-.js → chunk-TMUBEWPD-Bv_RKIlz.js} +1 -1
  14. package/dist/assets/classDiagram-LNE6IOMH-CXR-DJKP.js +1 -0
  15. package/dist/assets/classDiagram-v2-MQ7JQ4JX-CXR-DJKP.js +1 -0
  16. package/dist/assets/{dagre-4EVJKHTY-8N5qxubq.js → dagre-4EVJKHTY-sYyP6lkA.js} +1 -1
  17. package/dist/assets/{diagram-QW4FP2JN-CjLH9uYk.js → diagram-QW4FP2JN-XWldv7Gz.js} +1 -1
  18. package/dist/assets/{erDiagram-6RL3IURR-CXgt8NtN.js → erDiagram-6RL3IURR-Cd8OVAQ0.js} +1 -1
  19. package/dist/assets/{flowDiagram-7ASYPVHJ-CdFlmdmT.js → flowDiagram-7ASYPVHJ-Cc3onSB6.js} +1 -1
  20. package/dist/assets/{ganttDiagram-NTVNEXSI-DDjK4yxy.js → ganttDiagram-NTVNEXSI-Q6HxMUkA.js} +1 -1
  21. package/dist/assets/{gitGraph-YCYPL57B-Bd9jYZ-C.js → gitGraph-YCYPL57B-BUh0zUco.js} +1 -1
  22. package/dist/assets/{gitGraphDiagram-NRZ2UAAF-pmwetPkN.js → gitGraphDiagram-NRZ2UAAF-CvPqw-ya.js} +1 -1
  23. package/dist/assets/{infoDiagram-A4XQUW5V-BoDEaOyd.js → infoDiagram-A4XQUW5V-CbKGdGqb.js} +1 -1
  24. package/dist/assets/{journeyDiagram-G5WM74LC-D1LRuhB3.js → journeyDiagram-G5WM74LC-BXyAvuxS.js} +1 -1
  25. package/dist/assets/{kanban-definition-QRCXZQQD-B66SGjA2.js → kanban-definition-QRCXZQQD-CxBFX9if.js} +1 -1
  26. package/dist/assets/{linear-VMCGabuP.js → linear-Bu0P9vdO.js} +1 -1
  27. package/dist/assets/main-AYmbxFu4.css +10 -0
  28. package/dist/assets/main-CcrxU2Tf.js +1454 -0
  29. package/dist/assets/{mindmap-definition-GWI6TPTV--NljwHD9.js → mindmap-definition-GWI6TPTV-BbXI83yG.js} +1 -1
  30. package/dist/assets/{pieDiagram-YF2LJOPJ-CP4IPp3Q.js → pieDiagram-YF2LJOPJ-DuXspLPW.js} +1 -1
  31. package/dist/assets/{quadrantDiagram-OS5C2QUG-baFc-DtQ.js → quadrantDiagram-OS5C2QUG-DnngSA8p.js} +1 -1
  32. package/dist/assets/{requirementDiagram-MIRIMTAZ-BTWwNcnt.js → requirementDiagram-MIRIMTAZ-ChJE8XYc.js} +1 -1
  33. package/dist/assets/{sankeyDiagram-Y46BX6SQ-CnrACC1i.js → sankeyDiagram-Y46BX6SQ-BlkQKMk0.js} +1 -1
  34. package/dist/assets/{sequenceDiagram-G6AWOVSC-Ct5WkHJl.js → sequenceDiagram-G6AWOVSC-BvYCRnWL.js} +1 -1
  35. package/dist/assets/{stateDiagram-MAYHULR4-DeZHZmIy.js → stateDiagram-MAYHULR4-Cb6BiEq_.js} +1 -1
  36. package/dist/assets/stateDiagram-v2-4JROLMXI-31Hvt1M0.js +1 -0
  37. package/dist/assets/{timeline-definition-U7ZMHBDA-BZfUE50X.js → timeline-definition-U7ZMHBDA-w1-dGiEP.js} +1 -1
  38. package/dist/assets/{xychartDiagram-6QU3TZC5-DV6BFyHM.js → xychartDiagram-6QU3TZC5-B85iIKzT.js} +1 -1
  39. package/dist/index.html +2 -2
  40. package/dist-server/__stubs__/ccusage-codex.js +8 -0
  41. package/dist-server/app.module.js +2 -0
  42. package/dist-server/common/limits/defaults.js +3 -3
  43. package/dist-server/domain/agent/agent.service.js +11 -0
  44. package/dist-server/domain/cli/cli.service.js +3 -3
  45. package/dist-server/domain/cron/cron.service.js +5 -1
  46. package/dist-server/domain/doc/doc.controller.js +22 -0
  47. package/dist-server/domain/doc/doc.service.js +42 -10
  48. package/dist-server/domain/git/git.controller.js +180 -0
  49. package/dist-server/domain/git/git.module.js +2 -0
  50. package/dist-server/domain/git/git.service.js +327 -34
  51. package/dist-server/domain/message/message.service.js +18 -8
  52. package/dist-server/domain/onboarding/onboarding.service.js +1 -1
  53. package/dist-server/domain/onboarding/presets/custom.js +2 -2
  54. package/dist-server/domain/onboarding/presets/dev-team.js +7 -7
  55. package/dist-server/domain/onboarding/presets/marketing-team.js +5 -5
  56. package/dist-server/domain/onboarding/presets/planning-team.js +5 -5
  57. package/dist-server/domain/onboarding/presets/solo.js +3 -3
  58. package/dist-server/domain/settings/settings.module.js +2 -0
  59. package/dist-server/domain/settings/settings.service.js +75 -8
  60. package/dist-server/domain/task/task.service.js +7 -7
  61. package/dist-server/domain/thread/thread.service.js +2 -1
  62. package/dist-server/domain/usage/usage.controller.js +30 -0
  63. package/dist-server/domain/usage/usage.service.js +212 -0
  64. package/dist-server/domain/wi/wi.controller.js +43 -0
  65. package/dist-server/domain/wi/wi.module.js +22 -0
  66. package/dist-server/domain/wi/wi.service.js +110 -0
  67. package/dist-server/domain/wi/wi.types.js +2 -0
  68. package/dist-server/domain/workspace/workspace.service.js +4 -1
  69. package/dist-server/modules/crewx-pool.service.js +194 -53
  70. package/dist-server/repository/task.repository.js +5 -1
  71. package/package.json +16 -14
  72. package/packages/cli/dist/commands/agent.js +1 -1
  73. package/packages/cli/dist/commands/init.d.ts +2 -0
  74. package/packages/cli/dist/commands/init.js +198 -6
  75. package/packages/cli/package.json +3 -2
  76. package/dist/assets/PromptTab-HhD4MQcW.js +0 -37
  77. package/dist/assets/channel-ClmdPiZd.js +0 -1
  78. package/dist/assets/classDiagram-LNE6IOMH-Q0hRWLPF.js +0 -1
  79. package/dist/assets/classDiagram-v2-MQ7JQ4JX-Q0hRWLPF.js +0 -1
  80. package/dist/assets/main-AqROOc4i.css +0 -10
  81. package/dist/assets/main-DWCTap27.js +0 -1289
  82. package/dist/assets/stateDiagram-v2-4JROLMXI-DVsMMyDB.js +0 -1
@@ -45,18 +45,26 @@ var UsageService_1;
45
45
  Object.defineProperty(exports, "__esModule", { value: true });
46
46
  exports.UsageService = void 0;
47
47
  const common_1 = require("@nestjs/common");
48
+ const node_child_process_1 = require("node:child_process");
49
+ const fs = __importStar(require("node:fs"));
50
+ const os = __importStar(require("node:os"));
51
+ const path = __importStar(require("node:path"));
52
+ const node_util_1 = require("node:util");
53
+ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
48
54
  const data_loader_1 = require("ccusage/data-loader");
49
55
  const task_repository_js_1 = require("../../repository/task.repository.js");
50
56
  const project_repository_js_1 = require("../../repository/project.repository.js");
51
57
  const CACHE_TTL = 5 * 60 * 1000; // 5분
52
58
  const CACHE_MAX_SIZE = 10;
53
59
  const CACHE_CLEANUP_INTERVAL = 5 * 60 * 1000; // 5분
60
+ const CLAUDE_OAUTH_CACHE_TTL = 5 * 60 * 1000; // 5분
54
61
  let UsageService = UsageService_1 = class UsageService {
55
62
  taskRepository;
56
63
  projectRepository;
57
64
  logger = new common_1.Logger(UsageService_1.name);
58
65
  cache = new Map();
59
66
  cleanupInterval;
67
+ claudeOAuthCache = null;
60
68
  constructor(taskRepository, projectRepository) {
61
69
  this.taskRepository = taskRepository;
62
70
  this.projectRepository = projectRepository;
@@ -189,6 +197,32 @@ let UsageService = UsageService_1 = class UsageService {
189
197
  }));
190
198
  return { from, to, timezone: tz, daily };
191
199
  }
200
+ // --- USG.PROVIDER: Provider usage aggregation ---
201
+ getProviderUsage(from, to, workspace) {
202
+ const rows = this.taskRepository.getProviderUsage(from, to, workspace);
203
+ const grandTotalTokens = rows.reduce((sum, r) => sum + r.totalTokens, 0);
204
+ const providers = rows.map((r) => ({
205
+ provider: r.provider,
206
+ totalTokens: r.totalTokens,
207
+ inputTokens: r.inputTokens,
208
+ outputTokens: r.outputTokens,
209
+ cachedInputTokens: r.cachedInputTokens,
210
+ totalCost: r.costUsd,
211
+ taskCount: r.totalTasks,
212
+ activeDurationMs: r.activeDurationMs,
213
+ sharePercent: grandTotalTokens > 0
214
+ ? Math.round((r.totalTokens / grandTotalTokens) * 10000) / 100
215
+ : 0,
216
+ lastActiveAt: r.lastActiveAt,
217
+ }));
218
+ const totals = {
219
+ totalTokens: grandTotalTokens,
220
+ totalCost: rows.reduce((sum, r) => sum + r.costUsd, 0),
221
+ totalTasks: rows.reduce((sum, r) => sum + r.totalTasks, 0),
222
+ totalActiveDurationMs: rows.reduce((sum, r) => sum + r.activeDurationMs, 0),
223
+ };
224
+ return { from, to, providers, totals };
225
+ }
192
226
  /** Convert UTC ISO string to local date string (YYYY-MM-DD) using timezone. */
193
227
  utcToLocalDate(utcIso, tz) {
194
228
  try {
@@ -349,6 +383,184 @@ let UsageService = UsageService_1 = class UsageService {
349
383
  }
350
384
  }
351
385
  }
386
+ // --- USG.LIMITS: Subscription limit windows ---
387
+ async getSubscriptionLimits() {
388
+ const [codex, claude] = await Promise.all([
389
+ this.parseCodexLimits(),
390
+ this.parseClaudeLimits(),
391
+ ]);
392
+ return { codex, claude };
393
+ }
394
+ async parseCodexLimits() {
395
+ try {
396
+ const sessionsDir = path.join(os.homedir(), '.codex', 'sessions');
397
+ const jsonlFile = this.findLatestCodexJsonl(sessionsDir);
398
+ if (!jsonlFile)
399
+ return null;
400
+ const content = fs.readFileSync(jsonlFile, 'utf-8');
401
+ const lines = content.split('\n');
402
+ // Read from the end; skip partial writes (parse failures)
403
+ for (let i = lines.length - 1; i >= 0; i--) {
404
+ const line = lines[i].trim();
405
+ if (!line)
406
+ continue;
407
+ try {
408
+ const obj = JSON.parse(line);
409
+ const payload = obj['payload'];
410
+ const rateLimits = payload?.['rate_limits'];
411
+ if (!rateLimits)
412
+ continue;
413
+ return {
414
+ fiveHour: this.extractCodexWindow(rateLimits['primary']),
415
+ weekly: this.extractCodexWindow(rateLimits['secondary']),
416
+ };
417
+ }
418
+ catch {
419
+ // partial write — try next line
420
+ }
421
+ }
422
+ return null;
423
+ }
424
+ catch {
425
+ return null;
426
+ }
427
+ }
428
+ findLatestCodexJsonl(sessionsDir) {
429
+ try {
430
+ let latest = null;
431
+ const walkDir = (dir) => {
432
+ let entries;
433
+ try {
434
+ entries = fs.readdirSync(dir, { withFileTypes: true });
435
+ }
436
+ catch {
437
+ return;
438
+ }
439
+ for (const entry of entries) {
440
+ const fullPath = path.join(dir, entry.name);
441
+ if (entry.isDirectory()) {
442
+ walkDir(fullPath);
443
+ }
444
+ else if (entry.isFile() && entry.name.startsWith('rollout-') && entry.name.endsWith('.jsonl')) {
445
+ try {
446
+ const stat = fs.statSync(fullPath);
447
+ if (!latest || stat.mtimeMs > latest.mtime) {
448
+ latest = { file: fullPath, mtime: stat.mtimeMs };
449
+ }
450
+ }
451
+ catch {
452
+ // skip unreadable files
453
+ }
454
+ }
455
+ }
456
+ };
457
+ walkDir(sessionsDir);
458
+ return latest?.file ?? null;
459
+ }
460
+ catch {
461
+ return null;
462
+ }
463
+ }
464
+ extractCodexWindow(raw) {
465
+ if (!raw || typeof raw !== 'object')
466
+ return null;
467
+ const w = raw;
468
+ const usedPercent = w['used_percent'];
469
+ if (typeof usedPercent !== 'number')
470
+ return null;
471
+ let resetsAt = null;
472
+ if (typeof w['resets_at'] === 'number') {
473
+ resetsAt = new Date(w['resets_at'] * 1000).toISOString();
474
+ }
475
+ return { usedPercent: Math.round(usedPercent), resetsAt };
476
+ }
477
+ async parseClaudeLimits() {
478
+ const now = Date.now();
479
+ if (this.claudeOAuthCache && now - this.claudeOAuthCache.ts < CLAUDE_OAUTH_CACHE_TTL) {
480
+ return this.claudeOAuthCache.data;
481
+ }
482
+ try {
483
+ const token = await this.getClaudeOAuthToken();
484
+ if (!token) {
485
+ this.claudeOAuthCache = { data: null, ts: now };
486
+ return null;
487
+ }
488
+ const limits = await this.fetchClaudeOAuthLimits(token);
489
+ this.claudeOAuthCache = { data: limits, ts: now };
490
+ return limits;
491
+ }
492
+ catch {
493
+ this.claudeOAuthCache = { data: null, ts: now };
494
+ return null;
495
+ }
496
+ }
497
+ async getClaudeOAuthToken() {
498
+ const configDir = process.env['CLAUDE_CONFIG_DIR']?.trim() || path.join(os.homedir(), '.claude');
499
+ for (const filename of ['.credentials.json', 'credentials.json']) {
500
+ try {
501
+ const raw = fs.readFileSync(path.join(configDir, filename), 'utf-8');
502
+ const parsed = JSON.parse(raw);
503
+ const oauth = parsed['claudeAiOauth'];
504
+ if (typeof oauth === 'object' && oauth !== null) {
505
+ const token = oauth['accessToken'];
506
+ if (typeof token === 'string' && token.length > 0)
507
+ return token;
508
+ }
509
+ }
510
+ catch {
511
+ // file absent or unreadable — try next
512
+ }
513
+ }
514
+ if (process.platform === 'darwin') {
515
+ try {
516
+ const { stdout } = await execFileAsync('security', ['find-generic-password', '-s', 'Claude Code-credentials', '-w'], { timeout: 5000 });
517
+ const parsed = JSON.parse(stdout.trim());
518
+ const oauth = parsed['claudeAiOauth'];
519
+ if (typeof oauth === 'object' && oauth !== null) {
520
+ const token = oauth['accessToken'];
521
+ if (typeof token === 'string' && token.length > 0)
522
+ return token;
523
+ }
524
+ }
525
+ catch {
526
+ // keychain unavailable or denied
527
+ }
528
+ }
529
+ return null;
530
+ }
531
+ async fetchClaudeOAuthLimits(token) {
532
+ const resp = await this.fetchWithTimeout('https://api.anthropic.com/api/oauth/usage', {
533
+ headers: {
534
+ Authorization: `Bearer ${token}`,
535
+ 'anthropic-beta': 'oauth-2025-04-20',
536
+ },
537
+ }, 8000);
538
+ if (!resp.ok)
539
+ return null;
540
+ const body = (await resp.json());
541
+ const toPercent = (utilization) => {
542
+ if (utilization == null)
543
+ return null;
544
+ return Math.min(100, Math.round(utilization < 1 ? utilization * 100 : utilization));
545
+ };
546
+ const fiveHour = body.five_hour != null
547
+ ? { usedPercent: toPercent(body.five_hour.utilization) ?? 0, resetsAt: body.five_hour.resets_at ?? null }
548
+ : null;
549
+ const sevenDay = body.seven_day != null
550
+ ? { usedPercent: toPercent(body.seven_day.utilization) ?? 0, resetsAt: body.seven_day.resets_at ?? null }
551
+ : null;
552
+ return { fiveHour, sevenDay };
553
+ }
554
+ async fetchWithTimeout(url, init, ms) {
555
+ const controller = new AbortController();
556
+ const timer = setTimeout(() => controller.abort(), ms);
557
+ try {
558
+ return await fetch(url, { ...init, signal: controller.signal });
559
+ }
560
+ finally {
561
+ clearTimeout(timer);
562
+ }
563
+ }
352
564
  };
353
565
  exports.UsageService = UsageService;
354
566
  exports.UsageService = UsageService = UsageService_1 = __decorate([
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.WiController = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const swagger_1 = require("@nestjs/swagger");
18
+ const wi_service_js_1 = require("./wi.service.js");
19
+ const workspace_decorator_js_1 = require("../../common/decorators/workspace.decorator.js");
20
+ let WiController = class WiController {
21
+ wiService;
22
+ constructor(wiService) {
23
+ this.wiService = wiService;
24
+ }
25
+ async list(ws) {
26
+ const data = await this.wiService.list(ws.path);
27
+ return { success: true, data };
28
+ }
29
+ };
30
+ exports.WiController = WiController;
31
+ __decorate([
32
+ (0, swagger_1.ApiOperation)({ operationId: 'WI.LIST', summary: 'List work instructions from docs/wi/*.md' }),
33
+ (0, common_1.Get)(),
34
+ __param(0, (0, workspace_decorator_js_1.Workspace)()),
35
+ __metadata("design:type", Function),
36
+ __metadata("design:paramtypes", [Object]),
37
+ __metadata("design:returntype", Promise)
38
+ ], WiController.prototype, "list", null);
39
+ exports.WiController = WiController = __decorate([
40
+ (0, swagger_1.ApiTags)('wi'),
41
+ (0, common_1.Controller)('ws/:slug/wi'),
42
+ __metadata("design:paramtypes", [wi_service_js_1.WiService])
43
+ ], WiController);
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.WiModule = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const wi_controller_js_1 = require("./wi.controller.js");
12
+ const wi_service_js_1 = require("./wi.service.js");
13
+ let WiModule = class WiModule {
14
+ };
15
+ exports.WiModule = WiModule;
16
+ exports.WiModule = WiModule = __decorate([
17
+ (0, common_1.Module)({
18
+ controllers: [wi_controller_js_1.WiController],
19
+ providers: [wi_service_js_1.WiService],
20
+ exports: [wi_service_js_1.WiService],
21
+ })
22
+ ], WiModule);
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.WiService = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const fs_1 = require("fs");
12
+ const path_1 = require("path");
13
+ const js_yaml_1 = require("js-yaml");
14
+ const WI_ID_RE = /^WI-\d{8}-\d+$/;
15
+ const VALID_STATUSES = new Set(['draft', 'approved', 'in-progress', 'done', 'cancelled']);
16
+ function toArray(v) {
17
+ if (v === undefined || v === null)
18
+ return [];
19
+ if (Array.isArray(v))
20
+ return v.map(String);
21
+ return [String(v)];
22
+ }
23
+ function normalizeCost(v) {
24
+ if (v === undefined || v === null)
25
+ return undefined;
26
+ const n = parseFloat(String(v).replace(/[^0-9.]/g, ''));
27
+ return isNaN(n) ? undefined : n;
28
+ }
29
+ function normalizeStatus(v) {
30
+ const s = String(v ?? '');
31
+ return VALID_STATUSES.has(s) ? s : 'draft';
32
+ }
33
+ let WiService = class WiService {
34
+ validatePathBoundary(dir, fullPath) {
35
+ const dirCmp = dir.replace(/\\/g, '/').toLowerCase();
36
+ const pathCmp = fullPath.replace(/\\/g, '/').toLowerCase();
37
+ if (pathCmp !== dirCmp && !pathCmp.startsWith(dirCmp + '/')) {
38
+ throw new common_1.BadRequestException('Path is outside wi directory');
39
+ }
40
+ }
41
+ parseFrontmatter(raw) {
42
+ if (!raw.startsWith('---'))
43
+ return null;
44
+ const end = raw.indexOf('\n---', 3);
45
+ if (end === -1)
46
+ return null;
47
+ const yaml = raw.slice(3, end).trim();
48
+ try {
49
+ const parsed = (0, js_yaml_1.load)(yaml);
50
+ if (typeof parsed !== 'object' || parsed === null)
51
+ return null;
52
+ return parsed;
53
+ }
54
+ catch {
55
+ return null;
56
+ }
57
+ }
58
+ toSummary(data, filename, mtimeMs) {
59
+ const relPath = `docs/wi/${filename}`;
60
+ return {
61
+ wi_id: String(data['wi_id'] ?? ''),
62
+ title: String(data['title'] ?? ''),
63
+ status: normalizeStatus(data['status']),
64
+ priority: data['priority'] !== undefined ? String(data['priority']) : undefined,
65
+ assignee: data['assignee'] !== undefined ? String(data['assignee']) : undefined,
66
+ reviewer: data['reviewer'] !== undefined ? String(data['reviewer']) : undefined,
67
+ risk_level: data['risk_level'] !== undefined ? String(data['risk_level']) : undefined,
68
+ estimated_files: data['estimated_files'] !== undefined ? Number(data['estimated_files']) : undefined,
69
+ estimated_cost: normalizeCost(data['estimated_cost']),
70
+ depends_on: toArray(data['depends_on']),
71
+ blocks: toArray(data['blocks']),
72
+ date: data['date'] !== undefined ? String(data['date']) : undefined,
73
+ spec_id: data['spec_id'] !== undefined ? String(data['spec_id']) : undefined,
74
+ screens: toArray(data['screens']),
75
+ path: relPath,
76
+ mtime: mtimeMs,
77
+ };
78
+ }
79
+ async list(workspacePath) {
80
+ const dir = (0, path_1.join)(workspacePath, 'docs', 'wi');
81
+ if (!(0, fs_1.existsSync)(dir))
82
+ return [];
83
+ const entries = await fs_1.promises.readdir(dir);
84
+ const files = entries.filter((f) => f.endsWith('.md'));
85
+ const out = [];
86
+ for (const f of files) {
87
+ try {
88
+ const full = (0, path_1.resolve)(dir, f);
89
+ this.validatePathBoundary(dir, full);
90
+ const raw = await fs_1.promises.readFile(full, 'utf8');
91
+ const data = this.parseFrontmatter(raw);
92
+ if (!data)
93
+ continue;
94
+ const wiId = String(data['wi_id'] ?? '');
95
+ if (!WI_ID_RE.test(wiId))
96
+ continue;
97
+ const stat = await fs_1.promises.stat(full);
98
+ out.push(this.toSummary(data, f, stat.mtimeMs));
99
+ }
100
+ catch {
101
+ // skip malformed files
102
+ }
103
+ }
104
+ return out.sort((a, b) => b.mtime - a.mtime);
105
+ }
106
+ };
107
+ exports.WiService = WiService;
108
+ exports.WiService = WiService = __decorate([
109
+ (0, common_1.Injectable)()
110
+ ], WiService);
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -179,12 +179,15 @@ let WorkspaceService = WorkspaceService_1 = class WorkspaceService {
179
179
  }
180
180
  /** Map a DB row to the API response shape */
181
181
  toWorkspaceInfo(row) {
182
+ const workspacePath = row.workspace_path ? (0, sdk_1.normalizeWorkspacePath)(row.workspace_path) : '';
183
+ const isGitRepo = workspacePath ? (0, fs_1.existsSync)(path.join(workspacePath, '.git')) : false;
182
184
  return {
183
185
  id: row.id,
184
186
  slug: row.slug,
185
187
  name: row.name,
186
- workspace_path: row.workspace_path ? (0, sdk_1.normalizeWorkspacePath)(row.workspace_path) : '',
188
+ workspace_path: workspacePath,
187
189
  is_current: row.id === this.currentWorkspaceId,
190
+ is_git_repo: isGitRepo,
188
191
  created_at: row.created_at,
189
192
  updated_at: row.updated_at,
190
193
  };