taskin 1.0.13 → 2.0.1

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.
package/dist/index.js CHANGED
@@ -51,7 +51,7 @@ var init_esm_shims = __esm({
51
51
  }
52
52
  });
53
53
 
54
- // ../fs-task-provider/dist/i18n.js
54
+ // ../file-system-task-provider/dist/i18n.js
55
55
  function getI18n(locale = "en-US") {
56
56
  return i18nConfig[locale];
57
57
  }
@@ -63,7 +63,7 @@ function detectLocale(content) {
63
63
  }
64
64
  var i18nConfig;
65
65
  var init_i18n = __esm({
66
- "../fs-task-provider/dist/i18n.js"() {
66
+ "../file-system-task-provider/dist/i18n.js"() {
67
67
  "use strict";
68
68
  init_esm_shims();
69
69
  i18nConfig = {
@@ -93,7 +93,7 @@ var init_i18n = __esm({
93
93
  }
94
94
  });
95
95
 
96
- // ../fs-task-provider/dist/task-validator.js
96
+ // ../file-system-task-provider/dist/task-validator.js
97
97
  var task_validator_exports = {};
98
98
  __export(task_validator_exports, {
99
99
  createLintResult: () => createLintResult,
@@ -121,13 +121,18 @@ async function fixTaskFile(filePath) {
121
121
  const hasSectionStatus = statusPattern.test(content);
122
122
  const hasSectionType = typePattern.test(content);
123
123
  const hasSectionAssignee = assigneePattern.test(content);
124
- const inlineStatusPattern = /^(Status|Tipo):\s*(.+?)(\s*)$/im;
125
- const inlineTypePattern = /^(Type|Tipo):\s*(.+?)(\s*)$/im;
126
- const inlineAssigneePattern = /^(Assignee|Responsável):\s*(.+?)(\s*)$/im;
127
- const hasInlineStatus = inlineStatusPattern.test(content);
128
- const hasInlineType = inlineTypePattern.test(content);
129
- const hasInlineAssignee = inlineAssigneePattern.test(content);
130
- const needsSpaceFix = hasInlineStatus && !/^(?:Status|Tipo):.+ $/im.test(content) || hasInlineType && !/^(?:Type|Tipo):.+ $/im.test(content) || hasInlineAssignee && !/^(?:Assignee|Responsável):.+ $/im.test(content);
124
+ const inlineStatusPattern = /^(Status|Tipo):\s*/i;
125
+ const inlineTypePattern = /^(Type|Tipo):\s*/i;
126
+ const inlineAssigneePattern = /^(Assignee|Responsável):\s*/i;
127
+ const lines = content.split(/\r?\n/);
128
+ const inlineStatusLine = lines.find((l) => inlineStatusPattern.test(l));
129
+ const inlineTypeLine = lines.find((l) => inlineTypePattern.test(l));
130
+ const inlineAssigneeLine = lines.find((l) => inlineAssigneePattern.test(l));
131
+ const hasInlineStatus = !!inlineStatusLine;
132
+ const hasInlineType = !!inlineTypeLine;
133
+ const hasInlineAssignee = !!inlineAssigneeLine;
134
+ const endsWithTwoSpaces = (line) => !!line && /\s{2}$/.test(line);
135
+ const needsSpaceFix = hasInlineStatus && !endsWithTwoSpaces(inlineStatusLine) || hasInlineType && !endsWithTwoSpaces(inlineTypeLine) || hasInlineAssignee && !endsWithTwoSpaces(inlineAssigneeLine);
131
136
  if (!hasSectionStatus && !hasSectionType && !hasSectionAssignee && !needsSpaceFix) {
132
137
  return false;
133
138
  }
@@ -174,23 +179,34 @@ async function fixTaskFile(filePath) {
174
179
  wasModified = true;
175
180
  }
176
181
  if (needsSpaceFix) {
177
- newContent = newContent.replace(/^(# .+)\n([^#\n])/m, "$1\n\n$2");
178
182
  newContent = newContent.replace(
179
- /^(Status|Tipo):\s*(.+?)(\s*)$/im,
183
+ /^(Status|Tipo):\s*(.+?)([ \t]*)$/im,
180
184
  (_, key, value) => `${key}: ${value.trim()} `
181
185
  );
182
186
  newContent = newContent.replace(
183
- /^(Type|Tipo):\s*(.+?)(\s*)$/im,
187
+ /^(Type|Tipo):\s*(.+?)([ \t]*)$/im,
184
188
  (_, key, value) => `${key}: ${value.trim()} `
185
189
  );
186
190
  newContent = newContent.replace(
187
- /^(Assignee|Responsável):\s*(.+?)(\s*)$/im,
191
+ /^(Assignee|Responsável):\s*(.+?)([ \t]*)$/im,
188
192
  (_, key, value) => `${key}: ${value.trim()} `
189
193
  );
190
194
  wasModified = true;
191
195
  }
192
- if (wasModified) {
193
- const finalContent = newContent.replace(/\n{3,}/g, "\n\n").trim() + "\n";
196
+ const finalContentRaw = newContent.replace(/\n{3,}/g, "\n\n").trim() + "\n";
197
+ const originalContentRaw = content.replace(/\n{3,}/g, "\n\n").trim() + "\n";
198
+ const normalizeForCompare = (s) => s.replace(/(^# .*?)\n+/m, "$1\n\n").trim() + "\n";
199
+ const finalContent = normalizeForCompare(finalContentRaw);
200
+ const normalizedOriginal = normalizeForCompare(originalContentRaw);
201
+ if (filePath.endsWith("/tasks/task-001.md")) {
202
+ console.debug("DEBUG-NORM COMPARE", {
203
+ finalContentRaw: finalContentRaw.replace(/\n/g, "\\n"),
204
+ originalContentRaw: originalContentRaw.replace(/\n/g, "\\n"),
205
+ finalContent: finalContent.replace(/\n/g, "\\n"),
206
+ normalizedOriginal: normalizedOriginal.replace(/\n/g, "\\n")
207
+ });
208
+ }
209
+ if (finalContent !== normalizedOriginal) {
194
210
  await writeFile(filePath, finalContent, "utf-8");
195
211
  return true;
196
212
  }
@@ -327,7 +343,7 @@ function createLintResult(allIssues) {
327
343
  };
328
344
  }
329
345
  var init_task_validator = __esm({
330
- "../fs-task-provider/dist/task-validator.js"() {
346
+ "../file-system-task-provider/dist/task-validator.js"() {
331
347
  "use strict";
332
348
  init_esm_shims();
333
349
  init_i18n();
@@ -3549,8 +3565,8 @@ var require_utils = __commonJS({
3549
3565
  }
3550
3566
  return ind;
3551
3567
  }
3552
- function removeDotSegments(path13) {
3553
- let input = path13;
3568
+ function removeDotSegments(path16) {
3569
+ let input = path16;
3554
3570
  const output = [];
3555
3571
  let nextSlash = -1;
3556
3572
  let len = 0;
@@ -3750,8 +3766,8 @@ var require_schemes = __commonJS({
3750
3766
  wsComponent.secure = void 0;
3751
3767
  }
3752
3768
  if (wsComponent.resourceName) {
3753
- const [path13, query] = wsComponent.resourceName.split("?");
3754
- wsComponent.path = path13 && path13 !== "/" ? path13 : void 0;
3769
+ const [path16, query] = wsComponent.resourceName.split("?");
3770
+ wsComponent.path = path16 && path16 !== "/" ? path16 : void 0;
3755
3771
  wsComponent.query = query;
3756
3772
  wsComponent.resourceName = void 0;
3757
3773
  }
@@ -7150,12 +7166,12 @@ var require_dist = __commonJS({
7150
7166
  throw new Error(`Unknown format "${name}"`);
7151
7167
  return f;
7152
7168
  };
7153
- function addFormats(ajv, list, fs3, exportName) {
7169
+ function addFormats(ajv, list, fs5, exportName) {
7154
7170
  var _a;
7155
7171
  var _b;
7156
7172
  (_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
7157
7173
  for (const f of list)
7158
- ajv.addFormat(f, fs3[f]);
7174
+ ajv.addFormat(f, fs5[f]);
7159
7175
  }
7160
7176
  module.exports = exports = formatsPlugin;
7161
7177
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -7170,15 +7186,384 @@ import { Command } from "commander";
7170
7186
  // src/commands/dashboard.ts
7171
7187
  init_esm_shims();
7172
7188
 
7173
- // ../fs-task-provider/dist/index.js
7189
+ // ../file-system-task-provider/dist/index.js
7174
7190
  init_esm_shims();
7175
7191
 
7176
- // ../fs-task-provider/dist/fs-task-provider.js
7192
+ // ../file-system-task-provider/dist/file-system-metrics-adapter.js
7177
7193
  init_esm_shims();
7178
- init_i18n();
7179
- init_task_validator();
7194
+ import { UserStatsSchema } from "@opentask/taskin-types";
7180
7195
  import { promises as fs } from "fs";
7181
7196
  import path2 from "path";
7197
+ var MILLISECONDS_PER_SECOND = 1e3;
7198
+ var SECONDS_PER_MINUTE = 60;
7199
+ var MINUTES_PER_HOUR = 60;
7200
+ var HOURS_PER_DAY = 24;
7201
+ var DAYS_PER_WEEK = 7;
7202
+ var MILLISECONDS_PER_DAY = HOURS_PER_DAY * MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MILLISECONDS_PER_SECOND;
7203
+ var TASK_FILENAME_PATTERN = /^task-(\d+)-/;
7204
+ var TASK_TITLE_PATTERNS = {
7205
+ withDash: /^# .*?[—-]\s*(.+)$/im,
7206
+ withNumber: /^# .*?\s+(\d+)\s*-\s*(.+)$/im
7207
+ };
7208
+ function toISOString(date) {
7209
+ return date.toISOString();
7210
+ }
7211
+ function emptyCodeMetrics() {
7212
+ return {
7213
+ linesAdded: 0,
7214
+ linesRemoved: 0,
7215
+ netChange: 0,
7216
+ characters: 0,
7217
+ filesChanged: 0,
7218
+ commits: 0
7219
+ };
7220
+ }
7221
+ function emptyTemporalMetrics() {
7222
+ return {
7223
+ byDayOfWeek: {
7224
+ "0": 0,
7225
+ "1": 0,
7226
+ "2": 0,
7227
+ "3": 0,
7228
+ "4": 0,
7229
+ "5": 0,
7230
+ "6": 0
7231
+ },
7232
+ byTimeOfDay: { morning: 0, afternoon: 0, evening: 0, night: 0 },
7233
+ streak: 0,
7234
+ trend: "stable"
7235
+ };
7236
+ }
7237
+ function removeCodeBlocks(content) {
7238
+ return content.replace(/```[\s\S]*?```/g, "");
7239
+ }
7240
+ async function calculateCodeMetrics(gitAnalyzer, username, since, until) {
7241
+ if (!gitAnalyzer) {
7242
+ return emptyCodeMetrics();
7243
+ }
7244
+ try {
7245
+ const commits = await gitAnalyzer.getCommits({
7246
+ author: username,
7247
+ since: since.toISOString(),
7248
+ until: until.toISOString()
7249
+ });
7250
+ const metrics = commits.reduce((acc, commit) => ({
7251
+ linesAdded: acc.linesAdded + commit.linesAdded,
7252
+ linesRemoved: acc.linesRemoved + commit.linesRemoved,
7253
+ filesChanged: acc.filesChanged + commit.filesChanged,
7254
+ commits: acc.commits + 1
7255
+ }), {
7256
+ linesAdded: 0,
7257
+ linesRemoved: 0,
7258
+ filesChanged: 0,
7259
+ commits: 0
7260
+ });
7261
+ return {
7262
+ ...metrics,
7263
+ netChange: metrics.linesAdded - metrics.linesRemoved,
7264
+ characters: (metrics.linesAdded + metrics.linesRemoved) * 40
7265
+ // estimate ~40 chars/line
7266
+ };
7267
+ } catch (error2) {
7268
+ console.warn("Failed to calculate code metrics:", error2);
7269
+ return emptyCodeMetrics();
7270
+ }
7271
+ }
7272
+ async function calculateTemporalMetrics(gitAnalyzer, username, since, until) {
7273
+ if (!gitAnalyzer) {
7274
+ return emptyTemporalMetrics();
7275
+ }
7276
+ try {
7277
+ const commits = await gitAnalyzer.getCommits({
7278
+ author: username,
7279
+ since: since.toISOString(),
7280
+ until: until.toISOString()
7281
+ });
7282
+ const byDayOfWeek = {
7283
+ "0": 0,
7284
+ "1": 0,
7285
+ "2": 0,
7286
+ "3": 0,
7287
+ "4": 0,
7288
+ "5": 0,
7289
+ "6": 0
7290
+ };
7291
+ const byTimeOfDay = { morning: 0, afternoon: 0, evening: 0, night: 0 };
7292
+ const commitsByDate = /* @__PURE__ */ new Map();
7293
+ for (const commit of commits) {
7294
+ const date = new Date(commit.date);
7295
+ const day = date.getDay();
7296
+ const hour = date.getHours();
7297
+ byDayOfWeek[day.toString()]++;
7298
+ if (hour >= 6 && hour < 12)
7299
+ byTimeOfDay.morning++;
7300
+ else if (hour >= 12 && hour < 18)
7301
+ byTimeOfDay.afternoon++;
7302
+ else if (hour >= 18 && hour < 24)
7303
+ byTimeOfDay.evening++;
7304
+ else
7305
+ byTimeOfDay.night++;
7306
+ const dateKey = date.toISOString().split("T")[0];
7307
+ commitsByDate.set(dateKey, (commitsByDate.get(dateKey) || 0) + 1);
7308
+ }
7309
+ const sortedDates = Array.from(commitsByDate.keys()).sort();
7310
+ let currentStreak = 0;
7311
+ let maxStreak = 0;
7312
+ for (let i = 0; i < sortedDates.length; i++) {
7313
+ if (i === 0) {
7314
+ currentStreak = 1;
7315
+ } else {
7316
+ const prevDate = new Date(sortedDates[i - 1]);
7317
+ const currDate = new Date(sortedDates[i]);
7318
+ const daysDiff = (currDate.getTime() - prevDate.getTime()) / (1e3 * 60 * 60 * 24);
7319
+ if (daysDiff === 1) {
7320
+ currentStreak++;
7321
+ } else {
7322
+ currentStreak = 1;
7323
+ }
7324
+ }
7325
+ maxStreak = Math.max(maxStreak, currentStreak);
7326
+ }
7327
+ const midpoint = Math.floor(commits.length / 2);
7328
+ const firstHalf = commits.slice(0, midpoint);
7329
+ const secondHalf = commits.slice(midpoint);
7330
+ const firstHalfAvg = firstHalf.length ? firstHalf.length / Math.max(1, sortedDates.length / 2) : 0;
7331
+ const secondHalfAvg = secondHalf.length ? secondHalf.length / Math.max(1, sortedDates.length / 2) : 0;
7332
+ let trend = "stable";
7333
+ if (secondHalfAvg > firstHalfAvg * 1.2)
7334
+ trend = "increasing";
7335
+ else if (secondHalfAvg < firstHalfAvg * 0.8)
7336
+ trend = "decreasing";
7337
+ return {
7338
+ byDayOfWeek,
7339
+ byTimeOfDay,
7340
+ streak: maxStreak,
7341
+ trend
7342
+ };
7343
+ } catch (error2) {
7344
+ console.warn("Failed to calculate temporal metrics:", error2);
7345
+ return emptyTemporalMetrics();
7346
+ }
7347
+ }
7348
+ var FileSystemMetricsAdapter = class {
7349
+ tasksDirectory;
7350
+ userRegistry;
7351
+ gitAnalyzer;
7352
+ constructor(tasksDirectory, userRegistry, gitAnalyzer) {
7353
+ this.tasksDirectory = tasksDirectory;
7354
+ this.userRegistry = userRegistry;
7355
+ this.gitAnalyzer = gitAnalyzer;
7356
+ }
7357
+ /**
7358
+ * Reads and parses task files from the filesystem
7359
+ * @returns Array of parsed task file data
7360
+ * @private
7361
+ */
7362
+ async readTaskFiles() {
7363
+ let files;
7364
+ try {
7365
+ files = await fs.readdir(this.tasksDirectory);
7366
+ } catch (error2) {
7367
+ if (error2?.code === "ENOENT") {
7368
+ console.warn(`Tasks directory not found: ${this.tasksDirectory}`);
7369
+ return [];
7370
+ }
7371
+ throw new Error(`Failed to read tasks directory: ${error2?.message || error2}`);
7372
+ }
7373
+ const taskFiles = files.filter((f) => f.startsWith("task-") && f.endsWith(".md"));
7374
+ const tasks = [];
7375
+ for (const file of taskFiles) {
7376
+ const filePath = path2.join(this.tasksDirectory, file);
7377
+ let content;
7378
+ try {
7379
+ content = await fs.readFile(filePath, "utf-8");
7380
+ } catch (error2) {
7381
+ console.warn(`Failed to read task file ${filePath}: ${error2?.message}`);
7382
+ continue;
7383
+ }
7384
+ const idMatch = file.match(TASK_FILENAME_PATTERN);
7385
+ const id = idMatch ? idMatch[1] : file;
7386
+ const titleMatch = content.match(TASK_TITLE_PATTERNS.withDash) || content.match(TASK_TITLE_PATTERNS.withNumber);
7387
+ const title = titleMatch ? titleMatch[1] : file.replace(/\.md$/, "");
7388
+ const contentWithoutCodeBlocks = removeCodeBlocks(content);
7389
+ const extract = (name) => {
7390
+ const rx = new RegExp(`^${name}:\\s*(.+)$`, "im");
7391
+ const m = contentWithoutCodeBlocks.match(rx);
7392
+ return m ? m[1].trim() : void 0;
7393
+ };
7394
+ const statusValue = extract("Status");
7395
+ const assigneeValue = extract("Assignee");
7396
+ const typeValue = extract("Type");
7397
+ const validStatuses = [
7398
+ "pending",
7399
+ "in-progress",
7400
+ "done",
7401
+ "blocked",
7402
+ "canceled"
7403
+ ];
7404
+ const validTypes = [
7405
+ "feat",
7406
+ "fix",
7407
+ "refactor",
7408
+ "docs",
7409
+ "test",
7410
+ "chore"
7411
+ ];
7412
+ const status = validStatuses.includes(statusValue) ? statusValue : void 0;
7413
+ const type = validTypes.includes(typeValue) ? typeValue : void 0;
7414
+ tasks.push({
7415
+ id,
7416
+ title,
7417
+ status,
7418
+ assignee: assigneeValue,
7419
+ type,
7420
+ filePath
7421
+ });
7422
+ }
7423
+ return tasks;
7424
+ }
7425
+ async getUserMetrics(userId, _query) {
7426
+ const user = this.userRegistry.getUser(userId);
7427
+ const username = user ? user.name : userId;
7428
+ const now = /* @__PURE__ */ new Date();
7429
+ const weekAgo = new Date(Date.now() - DAYS_PER_WEEK * MILLISECONDS_PER_DAY);
7430
+ const tasks = await this.readTaskFiles();
7431
+ const assigned = tasks.filter((t) => {
7432
+ if (!t.assignee)
7433
+ return false;
7434
+ return t.assignee === user?.id || t.assignee === user?.name || t.assignee === username;
7435
+ });
7436
+ const completed = assigned.filter((t) => t.status === "done").length;
7437
+ const active = assigned.filter((t) => t.status !== "done").length;
7438
+ const codeMetrics = await calculateCodeMetrics(this.gitAnalyzer, username, weekAgo, now);
7439
+ const temporalMetrics = await calculateTemporalMetrics(this.gitAnalyzer, username, weekAgo, now);
7440
+ const rawMetrics = {
7441
+ username,
7442
+ period: "week",
7443
+ periodStart: toISOString(weekAgo),
7444
+ periodEnd: toISOString(now),
7445
+ codeMetrics,
7446
+ temporalMetrics,
7447
+ contributionMetrics: {
7448
+ totalCommits: codeMetrics.commits,
7449
+ tasksCompleted: completed,
7450
+ averageCompletionTime: 0,
7451
+ // TODO: calculate from task timestamps
7452
+ taskTypeDistribution: {},
7453
+ // TODO: calculate from task types
7454
+ activityFrequency: codeMetrics.commits / DAYS_PER_WEEK
7455
+ // commits per day
7456
+ },
7457
+ engagementMetrics: {
7458
+ commitsPerDay: codeMetrics.commits / DAYS_PER_WEEK,
7459
+ consistency: 0,
7460
+ // TODO: calculate standard deviation
7461
+ activeTasksCount: active,
7462
+ completionRate: assigned.length ? completed / assigned.length : 0
7463
+ },
7464
+ topTasks: []
7465
+ };
7466
+ return UserStatsSchema.parse(rawMetrics);
7467
+ }
7468
+ async getTeamMetrics(_teamId, _query) {
7469
+ const now = /* @__PURE__ */ new Date();
7470
+ const weekAgo = new Date(Date.now() - DAYS_PER_WEEK * MILLISECONDS_PER_DAY);
7471
+ const tasks = await this.readTaskFiles();
7472
+ const contributors = /* @__PURE__ */ new Map();
7473
+ for (const t of tasks) {
7474
+ const assignee = t.assignee || "unknown";
7475
+ const prev = contributors.get(assignee) || {
7476
+ username: assignee,
7477
+ commits: 0,
7478
+ tasksCompleted: 0,
7479
+ codeMetrics: emptyCodeMetrics()
7480
+ };
7481
+ if (t.status === "done")
7482
+ prev.tasksCompleted += 1;
7483
+ contributors.set(assignee, prev);
7484
+ }
7485
+ for (const [username, data] of contributors.entries()) {
7486
+ try {
7487
+ const codeMetrics = await calculateCodeMetrics(this.gitAnalyzer, username, weekAgo, now);
7488
+ data.commits = codeMetrics.commits;
7489
+ data.codeMetrics = codeMetrics;
7490
+ } catch (error2) {
7491
+ console.error(`Failed to calculate metrics for ${username}:`, error2);
7492
+ }
7493
+ }
7494
+ const totalCommits = Array.from(contributors.values()).reduce((sum, c) => sum + c.commits, 0);
7495
+ const totalTasksCompleted = Array.from(contributors.values()).reduce((sum, c) => sum + c.tasksCompleted, 0);
7496
+ const aggregatedCodeMetrics = Array.from(contributors.values()).reduce((acc, c) => ({
7497
+ linesAdded: acc.linesAdded + c.codeMetrics.linesAdded,
7498
+ linesRemoved: acc.linesRemoved + c.codeMetrics.linesRemoved,
7499
+ netChange: acc.netChange + c.codeMetrics.netChange,
7500
+ characters: acc.characters + c.codeMetrics.characters,
7501
+ filesChanged: acc.filesChanged + c.codeMetrics.filesChanged,
7502
+ commits: acc.commits + c.codeMetrics.commits
7503
+ }), emptyCodeMetrics());
7504
+ const team = {
7505
+ period: "week",
7506
+ periodStart: toISOString(weekAgo),
7507
+ periodEnd: toISOString(now),
7508
+ totalContributors: contributors.size,
7509
+ totalCommits,
7510
+ totalTasksCompleted,
7511
+ codeMetrics: aggregatedCodeMetrics,
7512
+ contributors: Array.from(contributors.values()).map((c) => ({
7513
+ username: c.username,
7514
+ commits: c.commits,
7515
+ tasksCompleted: c.tasksCompleted,
7516
+ codeMetrics: c.codeMetrics
7517
+ })),
7518
+ taskTypeDistribution: {}
7519
+ };
7520
+ return team;
7521
+ }
7522
+ async getTaskMetrics(taskId, _query) {
7523
+ const tasks = await this.readTaskFiles();
7524
+ const found = tasks.find((t) => t.id === taskId || t.filePath.includes(taskId));
7525
+ const now = /* @__PURE__ */ new Date();
7526
+ const validStatuses = [
7527
+ "pending",
7528
+ "in-progress",
7529
+ "done",
7530
+ "blocked",
7531
+ "canceled"
7532
+ ];
7533
+ const base = {
7534
+ taskId,
7535
+ title: found ? found.title : taskId,
7536
+ type: found && found.type || "feat",
7537
+ status: found?.status && validStatuses.includes(found.status) ? found.status : "pending",
7538
+ assignee: found?.assignee,
7539
+ duration: 0,
7540
+ created: toISOString(now),
7541
+ contributors: [],
7542
+ codeMetrics: emptyCodeMetrics(),
7543
+ refactoringMetrics: void 0,
7544
+ temporalMetrics: emptyTemporalMetrics(),
7545
+ workPattern: { mostActiveTimeOfDay: "morning", daysWorked: 0, gaps: 0 },
7546
+ commitHistory: {
7547
+ totalCommits: 0,
7548
+ averageCommitSize: 0,
7549
+ largestCommit: { linesAdded: 0, linesRemoved: 0 },
7550
+ smallestCommit: { linesAdded: 0, linesRemoved: 0 }
7551
+ },
7552
+ firstCommit: void 0,
7553
+ lastCommit: void 0,
7554
+ statusChangedToDone: void 0,
7555
+ createdAt: void 0
7556
+ };
7557
+ return base;
7558
+ }
7559
+ };
7560
+
7561
+ // ../file-system-task-provider/dist/fs-task-provider.js
7562
+ init_esm_shims();
7563
+ init_i18n();
7564
+ init_task_validator();
7565
+ import { promises as fs2 } from "fs";
7566
+ import path3 from "path";
7182
7567
  var FileSystemTaskProvider = class {
7183
7568
  tasksDirectory;
7184
7569
  userRegistry;
@@ -7189,15 +7574,15 @@ var FileSystemTaskProvider = class {
7189
7574
  this.locale = locale;
7190
7575
  }
7191
7576
  async findTask(taskId) {
7192
- const files = await fs.readdir(this.tasksDirectory);
7577
+ const files = await fs2.readdir(this.tasksDirectory);
7193
7578
  const taskFile = files.find(
7194
7579
  (file) => file.startsWith(`task-${taskId}-`) && file.endsWith(".md")
7195
7580
  );
7196
7581
  if (!taskFile) {
7197
7582
  return void 0;
7198
7583
  }
7199
- const filePath = path2.join(this.tasksDirectory, taskFile);
7200
- const content = await fs.readFile(filePath, "utf-8");
7584
+ const filePath = path3.join(this.tasksDirectory, taskFile);
7585
+ const content = await fs2.readFile(filePath, "utf-8");
7201
7586
  const titleMatch = content.match(/^# .*Task.*?[—-]\s*(.+)$/im);
7202
7587
  const title = titleMatch ? titleMatch[1].trim() : "Untitled";
7203
7588
  const contentLocale = detectLocale(content);
@@ -7239,7 +7624,7 @@ var FileSystemTaskProvider = class {
7239
7624
  return task;
7240
7625
  }
7241
7626
  async updateTask(task) {
7242
- const currentContent = await fs.readFile(task.filePath, "utf-8");
7627
+ const currentContent = await fs2.readFile(task.filePath, "utf-8");
7243
7628
  const hasSectionMetadata = /##\s*(Status|Type|Assignee)/i.test(
7244
7629
  currentContent
7245
7630
  );
@@ -7247,31 +7632,31 @@ var FileSystemTaskProvider = class {
7247
7632
  const { fixTaskFile: fixTaskFile2 } = await Promise.resolve().then(() => (init_task_validator(), task_validator_exports));
7248
7633
  await fixTaskFile2(task.filePath);
7249
7634
  }
7250
- const content = await fs.readFile(task.filePath, "utf-8");
7635
+ const content = await fs2.readFile(task.filePath, "utf-8");
7251
7636
  let updatedContent;
7252
7637
  if (/^Status:\s*.+$/im.test(content)) {
7253
7638
  updatedContent = content.replace(
7254
7639
  /^Status:\s*.+$/im,
7255
- `Status: ${task.status} `
7640
+ `Status: ${task.status}`
7256
7641
  );
7257
7642
  } else {
7258
7643
  updatedContent = content.replace(
7259
7644
  /(^#.*\n)/,
7260
- `$1Status: ${task.status}
7645
+ `$1Status: ${task.status}
7261
7646
  `
7262
7647
  );
7263
7648
  }
7264
- await fs.writeFile(task.filePath, updatedContent, "utf-8");
7649
+ await fs2.writeFile(task.filePath, updatedContent, "utf-8");
7265
7650
  }
7266
7651
  async getAllTasks() {
7267
- const files = await fs.readdir(this.tasksDirectory);
7652
+ const files = await fs2.readdir(this.tasksDirectory);
7268
7653
  const taskFiles = files.filter(
7269
7654
  (file) => file.startsWith("task-") && file.endsWith(".md")
7270
7655
  );
7271
7656
  const tasks = [];
7272
7657
  for (const file of taskFiles) {
7273
- const filePath = path2.join(this.tasksDirectory, file);
7274
- const content = await fs.readFile(filePath, "utf-8");
7658
+ const filePath = path3.join(this.tasksDirectory, file);
7659
+ const content = await fs2.readFile(filePath, "utf-8");
7275
7660
  const idMatch = file.match(/^task-(\d+)-/);
7276
7661
  const taskId = idMatch ? idMatch[1] : "unknown";
7277
7662
  const titleMatch = content.match(/^# .*Task.*?[—-]\s*(.+)$/im);
@@ -7329,8 +7714,8 @@ var FileSystemTaskProvider = class {
7329
7714
  const taskId = String(nextNumber).padStart(3, "0");
7330
7715
  const titleSlug = options.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
7331
7716
  const fileName = `task-${taskId}-${titleSlug}.md`;
7332
- const filePath = path2.join(this.tasksDirectory, fileName);
7333
- const fileExists = await fs.access(filePath).then(() => true).catch(() => false);
7717
+ const filePath = path3.join(this.tasksDirectory, fileName);
7718
+ const fileExists = await fs2.access(filePath).then(() => true).catch(() => false);
7334
7719
  if (fileExists) {
7335
7720
  throw new Error(`Task file already exists: ${fileName}`);
7336
7721
  }
@@ -7349,7 +7734,7 @@ var FileSystemTaskProvider = class {
7349
7734
  assignee: assignee?.name || i18n.defaultAssignee,
7350
7735
  i18n
7351
7736
  });
7352
- await fs.writeFile(filePath, taskContent, "utf-8");
7737
+ await fs2.writeFile(filePath, taskContent, "utf-8");
7353
7738
  const task = await this.findTask(taskId);
7354
7739
  if (!task) {
7355
7740
  throw new Error(`Failed to create task ${taskId}`);
@@ -7381,8 +7766,8 @@ ${i18n.notesPlaceholder}
7381
7766
  `;
7382
7767
  }
7383
7768
  async lint(fix) {
7384
- const files = await fs.readdir(this.tasksDirectory);
7385
- const taskFiles = files.filter((file) => file.startsWith("task-") && file.endsWith(".md")).map((file) => path2.join(this.tasksDirectory, file));
7769
+ const files = await fs2.readdir(this.tasksDirectory);
7770
+ const taskFiles = files.filter((file) => file.startsWith("task-") && file.endsWith(".md")).map((file) => path3.join(this.tasksDirectory, file));
7386
7771
  if (fix) {
7387
7772
  let fixedCount = 0;
7388
7773
  for (const filePath of taskFiles) {
@@ -7404,27 +7789,27 @@ ${i18n.notesPlaceholder}
7404
7789
  }
7405
7790
  };
7406
7791
 
7407
- // ../fs-task-provider/dist/index.js
7792
+ // ../file-system-task-provider/dist/index.js
7408
7793
  init_i18n();
7409
7794
 
7410
- // ../fs-task-provider/dist/user-registry.js
7795
+ // ../file-system-task-provider/dist/user-registry.js
7411
7796
  init_esm_shims();
7412
- import { promises as fs2 } from "fs";
7413
- import path3 from "path";
7797
+ import { promises as fs3 } from "fs";
7798
+ import path4 from "path";
7414
7799
  var UserRegistry = class {
7415
7800
  config;
7416
7801
  users = /* @__PURE__ */ new Map();
7417
7802
  usersFilePath;
7418
7803
  constructor(config) {
7419
7804
  this.config = config;
7420
- this.usersFilePath = path3.join(config.taskinDir, "users.json");
7805
+ this.usersFilePath = path4.join(config.taskinDir, "users.json");
7421
7806
  }
7422
7807
  /**
7423
7808
  * Load users from users.json
7424
7809
  */
7425
7810
  async load() {
7426
7811
  try {
7427
- const content = await fs2.readFile(this.usersFilePath, "utf-8");
7812
+ const content = await fs3.readFile(this.usersFilePath, "utf-8");
7428
7813
  const data = JSON.parse(content);
7429
7814
  this.users.clear();
7430
7815
  for (const [id, user] of Object.entries(data.users)) {
@@ -7497,7 +7882,7 @@ var UserRegistry = class {
7497
7882
  const data = {
7498
7883
  users: Object.fromEntries(this.users.entries())
7499
7884
  };
7500
- await fs2.writeFile(
7885
+ await fs3.writeFile(
7501
7886
  this.usersFilePath,
7502
7887
  JSON.stringify(data, null, 2),
7503
7888
  "utf-8"
@@ -7906,41 +8291,38 @@ init_esm_shims();
7906
8291
  // ../utils/dist/security.js
7907
8292
  init_esm_shims();
7908
8293
  import { z } from "zod";
7909
- var HostSchema = z.string().refine(
7910
- (host) => {
7911
- if (!host || host.length === 0) return false;
7912
- if (host === "localhost") return true;
7913
- const parts = host.split(".");
7914
- const allNumeric = parts.every((p) => /^\d+$/.test(p));
7915
- if (allNumeric) {
7916
- if (parts.length !== 4) return false;
7917
- return parts.every((part) => {
7918
- const num = parseInt(part, 10);
7919
- return !isNaN(num) && num >= 0 && num <= 255 && part === num.toString();
7920
- });
7921
- }
7922
- const hostnameRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
7923
- return hostnameRegex.test(host);
7924
- },
7925
- {
7926
- message: "Invalid host. Must be localhost, a valid IPv4 address, or hostname."
8294
+ var HostSchema = z.string().refine((host) => {
8295
+ if (!host || host.length === 0)
8296
+ return false;
8297
+ if (host === "localhost")
8298
+ return true;
8299
+ const parts = host.split(".");
8300
+ const allNumeric = parts.every((p) => /^\d+$/.test(p));
8301
+ if (allNumeric) {
8302
+ if (parts.length !== 4)
8303
+ return false;
8304
+ return parts.every((part) => {
8305
+ const num = parseInt(part, 10);
8306
+ return !isNaN(num) && num >= 0 && num <= 255 && part === num.toString();
8307
+ });
7927
8308
  }
7928
- );
8309
+ const hostnameRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
8310
+ return hostnameRegex.test(host);
8311
+ }, {
8312
+ message: "Invalid host. Must be localhost, a valid IPv4 address, or hostname."
8313
+ });
7929
8314
  var PortSchema = z.union([
7930
8315
  z.number().int().min(1).max(65535),
7931
8316
  z.string().regex(/^\d+$/).transform((val) => parseInt(val, 10)).pipe(z.number().int().min(1).max(65535))
7932
8317
  ]);
7933
- var WebSocketUrlSchema = z.string().refine(
7934
- (url) => {
7935
- try {
7936
- const parsed = new URL(url);
7937
- return parsed.protocol === "ws:" || parsed.protocol === "wss:";
7938
- } catch {
7939
- return false;
7940
- }
7941
- },
7942
- { message: "Invalid WebSocket URL. Must use ws:// or wss:// protocol." }
7943
- );
8318
+ var WebSocketUrlSchema = z.string().refine((url) => {
8319
+ try {
8320
+ const parsed = new URL(url);
8321
+ return parsed.protocol === "ws:" || parsed.protocol === "wss:";
8322
+ } catch {
8323
+ return false;
8324
+ }
8325
+ }, { message: "Invalid WebSocket URL. Must use ws:// or wss:// protocol." });
7944
8326
  var TaskIdSchema = z.string().min(1).max(100).regex(/^[a-zA-Z0-9_-]+$/, {
7945
8327
  message: "Task ID must contain only alphanumeric characters, hyphens, and underscores."
7946
8328
  });
@@ -7948,25 +8330,23 @@ var UserIdSchema = z.string().min(1).max(100).regex(/^[a-zA-Z0-9._-]+$/, {
7948
8330
  message: "User ID must contain only alphanumeric characters, dots, hyphens, and underscores."
7949
8331
  });
7950
8332
  var EmailSchema = z.string().email().max(254);
7951
- var SafePathSchema = z.string().refine(
7952
- (filePath) => {
7953
- if (!filePath || filePath.length === 0) return false;
7954
- const dangerousPatterns = [
7955
- /\.\./,
7956
- // Parent directory (..)
7957
- /~\//,
7958
- // Home directory
7959
- /^\//,
7960
- // Absolute path
7961
- /^[A-Za-z]:\\/
7962
- // Windows absolute path
7963
- ];
7964
- return !dangerousPatterns.some((pattern) => pattern.test(filePath));
7965
- },
7966
- {
7967
- message: "Invalid path. Must be a relative path without traversal patterns."
7968
- }
7969
- );
8333
+ var SafePathSchema = z.string().refine((filePath) => {
8334
+ if (!filePath || filePath.length === 0)
8335
+ return false;
8336
+ const dangerousPatterns = [
8337
+ /\.\./,
8338
+ // Parent directory (..)
8339
+ /~\//,
8340
+ // Home directory
8341
+ /^\//,
8342
+ // Absolute path
8343
+ /^[A-Za-z]:\\/
8344
+ // Windows absolute path
8345
+ ];
8346
+ return !dangerousPatterns.some((pattern) => pattern.test(filePath));
8347
+ }, {
8348
+ message: "Invalid path. Must be a relative path without traversal patterns."
8349
+ });
7970
8350
  var DashboardOptionsSchema = z.object({
7971
8351
  host: HostSchema.optional(),
7972
8352
  port: PortSchema.optional(),
@@ -8011,7 +8391,7 @@ var colors = {
8011
8391
  import chalk3 from "chalk";
8012
8392
  import express from "express";
8013
8393
  import { createServer } from "http";
8014
- import path5 from "path";
8394
+ import path6 from "path";
8015
8395
  import { fileURLToPath as fileURLToPath2 } from "url";
8016
8396
 
8017
8397
  // src/lib/colors.ts
@@ -8055,9 +8435,9 @@ function info(message) {
8055
8435
  // src/lib/project-check.ts
8056
8436
  init_esm_shims();
8057
8437
  import { existsSync } from "fs";
8058
- import path4 from "path";
8438
+ import path5 from "path";
8059
8439
  function isTaskinProject(cwd = process.cwd()) {
8060
- return existsSync(path4.join(cwd, ".taskin.json"));
8440
+ return existsSync(path5.join(cwd, ".taskin.json"));
8061
8441
  }
8062
8442
  function requireTaskinProject(cwd = process.cwd()) {
8063
8443
  if (!isTaskinProject(cwd)) {
@@ -8100,7 +8480,7 @@ init_esm_shims();
8100
8480
 
8101
8481
  // src/commands/dashboard.ts
8102
8482
  var __filename2 = fileURLToPath2(import.meta.url);
8103
- var __dirname2 = path5.dirname(__filename2);
8483
+ var __dirname2 = path6.dirname(__filename2);
8104
8484
  var dashboardCommand = defineCommand({
8105
8485
  name: "dashboard",
8106
8486
  description: "\u{1F4CA} Start the Taskin dashboard with WebSocket server",
@@ -8168,24 +8548,24 @@ async function startDashboard(options) {
8168
8548
  try {
8169
8549
  info("Initializing task provider...");
8170
8550
  let currentDir = process.cwd();
8171
- let tasksDir = path5.join(currentDir, "TASKS");
8551
+ let tasksDir = path6.join(currentDir, "TASKS");
8172
8552
  try {
8173
- await import("fs").then((fs3) => fs3.promises.access(tasksDir));
8553
+ await import("fs").then((fs5) => fs5.promises.access(tasksDir));
8174
8554
  } catch {
8175
- while (currentDir !== path5.dirname(currentDir)) {
8176
- const workspaceFile = path5.join(currentDir, "pnpm-workspace.yaml");
8555
+ while (currentDir !== path6.dirname(currentDir)) {
8556
+ const workspaceFile = path6.join(currentDir, "pnpm-workspace.yaml");
8177
8557
  try {
8178
- await import("fs").then((fs3) => fs3.promises.access(workspaceFile));
8179
- tasksDir = path5.join(currentDir, "TASKS");
8558
+ await import("fs").then((fs5) => fs5.promises.access(workspaceFile));
8559
+ tasksDir = path6.join(currentDir, "TASKS");
8180
8560
  break;
8181
8561
  } catch {
8182
- currentDir = path5.dirname(currentDir);
8562
+ currentDir = path6.dirname(currentDir);
8183
8563
  }
8184
8564
  }
8185
8565
  }
8186
8566
  info(`Using tasks directory: ${tasksDir}`);
8187
- const monorepoRoot = path5.dirname(tasksDir);
8188
- const taskinDir = path5.join(monorepoRoot, ".taskin");
8567
+ const monorepoRoot = path6.dirname(tasksDir);
8568
+ const taskinDir = path6.join(monorepoRoot, ".taskin");
8189
8569
  const userRegistry = new UserRegistry({ taskinDir });
8190
8570
  await userRegistry.load();
8191
8571
  const provider = new FileSystemTaskProvider(tasksDir, userRegistry);
@@ -8203,7 +8583,7 @@ async function startDashboard(options) {
8203
8583
  success(`\u2713 WebSocket server running on ws://${host}:${wsPort}`);
8204
8584
  info(`Starting dashboard server on http://${host}:${port}...`);
8205
8585
  const isDev = __dirname2.includes("/src/");
8206
- const dashboardDist = isDev ? path5.join(__dirname2, "..", "..", "dashboard-dist") : path5.join(__dirname2, "..", "dashboard-dist");
8586
+ const dashboardDist = isDev ? path6.join(__dirname2, "..", "..", "dashboard-dist") : path6.join(__dirname2, "..", "dashboard-dist");
8207
8587
  const app = express();
8208
8588
  app.disable("x-powered-by");
8209
8589
  app.use((req, res, next) => {
@@ -8219,8 +8599,8 @@ async function startDashboard(options) {
8219
8599
  app.use((req, res, next) => {
8220
8600
  if (req.path === "/" || req.path === "/index.html") {
8221
8601
  import("fs").then(
8222
- (fs3) => fs3.promises.readFile(
8223
- path5.join(dashboardDist, "index.html"),
8602
+ (fs5) => fs5.promises.readFile(
8603
+ path6.join(dashboardDist, "index.html"),
8224
8604
  "utf-8"
8225
8605
  )
8226
8606
  ).then((html) => {
@@ -8292,25 +8672,428 @@ async function startDashboard(options) {
8292
8672
  }
8293
8673
  }
8294
8674
 
8295
- // src/commands/finish.ts
8675
+ // src/commands/export.ts
8676
+ init_esm_shims();
8677
+
8678
+ // ../git-utils/dist/src/index.js
8296
8679
  init_esm_shims();
8680
+
8681
+ // ../git-utils/dist/src/git.js
8682
+ init_esm_shims();
8683
+
8684
+ // ../git-utils/dist/src/git-analyzer.js
8685
+ init_esm_shims();
8686
+ import { exec } from "child_process";
8687
+ import { promisify } from "util";
8688
+ var execAsync = promisify(exec);
8689
+ function isNodeError(error2) {
8690
+ return typeof error2 === "object" && error2 !== null && "code" in error2;
8691
+ }
8692
+ async function executeGit(command, cwd) {
8693
+ try {
8694
+ const { stdout } = await execAsync(`git ${command}`, {
8695
+ cwd: cwd || process.cwd(),
8696
+ encoding: "utf8",
8697
+ maxBuffer: 10 * 1024 * 1024
8698
+ // 10MB buffer for large repos
8699
+ });
8700
+ return stdout.trim();
8701
+ } catch (error2) {
8702
+ if (isNodeError(error2)) {
8703
+ if (error2.code === "ENOENT") {
8704
+ throw new Error("Git is not installed or not in PATH");
8705
+ }
8706
+ }
8707
+ return "";
8708
+ }
8709
+ }
8710
+ var GitAnalyzer = class {
8711
+ repositoryPath;
8712
+ constructor(repositoryPath) {
8713
+ this.repositoryPath = repositoryPath;
8714
+ }
8715
+ async isValidRepository() {
8716
+ try {
8717
+ const result = await executeGit("rev-parse --is-inside-work-tree", this.repositoryPath);
8718
+ return result === "true";
8719
+ } catch {
8720
+ return false;
8721
+ }
8722
+ }
8723
+ async getRepositoryRoot() {
8724
+ const result = await executeGit("rev-parse --show-toplevel", this.repositoryPath);
8725
+ if (!result) {
8726
+ throw new Error("Not a git repository");
8727
+ }
8728
+ return result;
8729
+ }
8730
+ async getCommits(options = {}) {
8731
+ const args = ["log"];
8732
+ args.push("--pretty='format:%H|%an|%aI|%s%x00%b'", "--numstat");
8733
+ if (options.since) {
8734
+ args.push(`--since="${options.since}"`);
8735
+ }
8736
+ if (options.until) {
8737
+ args.push(`--until="${options.until}"`);
8738
+ }
8739
+ if (options.author) {
8740
+ args.push(`--author="${options.author}"`);
8741
+ }
8742
+ if (options.maxCount) {
8743
+ args.push(`-n ${options.maxCount}`);
8744
+ }
8745
+ if (!options.includeMerges) {
8746
+ args.push("--no-merges");
8747
+ }
8748
+ if (options.filePath) {
8749
+ args.push("--", options.filePath);
8750
+ }
8751
+ const command = args.join(" ");
8752
+ const output = await executeGit(command, this.repositoryPath);
8753
+ if (!output) {
8754
+ return [];
8755
+ }
8756
+ return this.parseCommits(output);
8757
+ }
8758
+ parseCommits(output) {
8759
+ const commits = [];
8760
+ const lines = output.split("\n");
8761
+ let i = 0;
8762
+ while (i < lines.length) {
8763
+ const line = lines[i];
8764
+ if (!line.trim()) {
8765
+ i++;
8766
+ continue;
8767
+ }
8768
+ if (line.includes("|")) {
8769
+ const parts = line.split("|");
8770
+ if (parts.length < 4 || !/^[0-9a-f]{40}$/.test(parts[0])) {
8771
+ i++;
8772
+ continue;
8773
+ }
8774
+ const [hash, author, date, messageWithBody] = parts;
8775
+ const nullByteIndex = messageWithBody?.indexOf("\0") ?? -1;
8776
+ const subject = nullByteIndex !== -1 ? messageWithBody.substring(0, nullByteIndex) : messageWithBody || "";
8777
+ let bodyLines = [];
8778
+ if (nullByteIndex !== -1) {
8779
+ bodyLines.push(messageWithBody.substring(nullByteIndex + 1));
8780
+ }
8781
+ i++;
8782
+ while (i < lines.length && !lines[i].includes("|") && !lines[i].includes(" ")) {
8783
+ if (lines[i].trim()) {
8784
+ bodyLines.push(lines[i]);
8785
+ }
8786
+ i++;
8787
+ }
8788
+ const body = bodyLines.join("\n");
8789
+ const fullMessage = body ? `${subject}
8790
+
8791
+ ${body}` : subject;
8792
+ let filesChanged = 0;
8793
+ let linesAdded = 0;
8794
+ let linesRemoved = 0;
8795
+ while (i < lines.length && !lines[i].includes("|")) {
8796
+ const statLine = lines[i].trim();
8797
+ if (!statLine) {
8798
+ i++;
8799
+ continue;
8800
+ }
8801
+ const [added, removed] = statLine.split(" ");
8802
+ if (added !== "-" && removed !== "-") {
8803
+ linesAdded += parseInt(added || "0", 10);
8804
+ linesRemoved += parseInt(removed || "0", 10);
8805
+ filesChanged++;
8806
+ }
8807
+ i++;
8808
+ }
8809
+ commits.push({
8810
+ hash,
8811
+ author,
8812
+ date,
8813
+ message: subject,
8814
+ filesChanged,
8815
+ linesAdded,
8816
+ linesRemoved,
8817
+ coAuthors: this.extractCoAuthors(fullMessage)
8818
+ });
8819
+ } else {
8820
+ i++;
8821
+ }
8822
+ }
8823
+ return commits;
8824
+ }
8825
+ extractCoAuthors(message) {
8826
+ const coAuthorRegex = /Co-authored-by:\s*(.+?)\s*<(.+?)>/gi;
8827
+ const matches = Array.from(message.matchAll(coAuthorRegex));
8828
+ if (matches.length === 0) {
8829
+ return void 0;
8830
+ }
8831
+ return matches.map((match) => match[1].trim());
8832
+ }
8833
+ async getDiff(from = "HEAD", to = "") {
8834
+ const args = ["diff", "--numstat"];
8835
+ if (to) {
8836
+ args.push(`${from}..${to}`);
8837
+ } else {
8838
+ args.push(from);
8839
+ }
8840
+ const output = await executeGit(args.join(" "), this.repositoryPath);
8841
+ return this.parseDiff(output);
8842
+ }
8843
+ parseDiff(output) {
8844
+ const files = [];
8845
+ let totalLinesAdded = 0;
8846
+ let totalLinesRemoved = 0;
8847
+ if (!output) {
8848
+ return {
8849
+ files,
8850
+ totalLinesAdded,
8851
+ totalLinesRemoved,
8852
+ netChange: 0
8853
+ };
8854
+ }
8855
+ const lines = output.split("\n").filter((l) => l.trim());
8856
+ for (const line of lines) {
8857
+ const parts = line.split(" ");
8858
+ if (parts.length < 3)
8859
+ continue;
8860
+ const [added, removed, path16] = parts;
8861
+ if (added === "-" || removed === "-") {
8862
+ continue;
8863
+ }
8864
+ const linesAdded = parseInt(added, 10);
8865
+ const linesRemoved = parseInt(removed, 10);
8866
+ totalLinesAdded += linesAdded;
8867
+ totalLinesRemoved += linesRemoved;
8868
+ files.push({
8869
+ path: path16,
8870
+ linesAdded,
8871
+ linesRemoved,
8872
+ changeType: "modified"
8873
+ // Simplified - could be enhanced with --name-status
8874
+ });
8875
+ }
8876
+ return {
8877
+ files,
8878
+ totalLinesAdded,
8879
+ totalLinesRemoved,
8880
+ netChange: totalLinesAdded - totalLinesRemoved
8881
+ };
8882
+ }
8883
+ async getFileDiff(filePath, from = "HEAD", to = "") {
8884
+ const args = ["diff", "--numstat"];
8885
+ if (to) {
8886
+ args.push(`${from}..${to}`);
8887
+ } else {
8888
+ args.push(from);
8889
+ }
8890
+ args.push("--", filePath);
8891
+ const output = await executeGit(args.join(" "), this.repositoryPath);
8892
+ if (!output) {
8893
+ return null;
8894
+ }
8895
+ const [added, removed, path16] = output.split(" ");
8896
+ if (added === "-" || removed === "-") {
8897
+ return null;
8898
+ }
8899
+ return {
8900
+ path: path16,
8901
+ linesAdded: parseInt(added, 10),
8902
+ linesRemoved: parseInt(removed, 10),
8903
+ changeType: "modified"
8904
+ };
8905
+ }
8906
+ async getBlame(filePath) {
8907
+ const args = ["blame", "--line-porcelain", filePath];
8908
+ const output = await executeGit(args.join(" "), this.repositoryPath);
8909
+ if (!output) {
8910
+ return [];
8911
+ }
8912
+ return this.parseBlame(output);
8913
+ }
8914
+ parseBlame(output) {
8915
+ const lines = output.split("\n");
8916
+ const blameInfo = [];
8917
+ let currentHash = "";
8918
+ let currentAuthor = "";
8919
+ let currentDate = "";
8920
+ let lineNumber = 0;
8921
+ for (let i = 0; i < lines.length; i++) {
8922
+ const line = lines[i];
8923
+ if (line.match(/^[0-9a-f]{40}/)) {
8924
+ const parts = line.split(" ");
8925
+ currentHash = parts[0];
8926
+ lineNumber = parseInt(parts[2], 10);
8927
+ } else if (line.startsWith("author ")) {
8928
+ currentAuthor = line.substring(7);
8929
+ } else if (line.startsWith("author-time ")) {
8930
+ const timestamp = parseInt(line.substring(12), 10);
8931
+ currentDate = new Date(timestamp * 1e3).toISOString();
8932
+ } else if (line.startsWith(" ")) {
8933
+ const content = line.substring(1);
8934
+ blameInfo.push({
8935
+ lineNumber,
8936
+ commitHash: currentHash,
8937
+ author: currentAuthor,
8938
+ date: new Date(currentDate),
8939
+ content
8940
+ });
8941
+ }
8942
+ }
8943
+ return blameInfo;
8944
+ }
8945
+ async getAuthors(options = {}) {
8946
+ const args = ["shortlog", "-sne"];
8947
+ if (options.since) {
8948
+ args.push(`--since="${options.since}"`);
8949
+ }
8950
+ if (options.until) {
8951
+ args.push(`--until="${options.until}"`);
8952
+ }
8953
+ if (!options.includeMerges) {
8954
+ args.push("--no-merges");
8955
+ }
8956
+ if (options.filePath) {
8957
+ args.push("--", options.filePath);
8958
+ }
8959
+ const output = await executeGit(args.join(" "), this.repositoryPath);
8960
+ if (!output) {
8961
+ return [];
8962
+ }
8963
+ return this.parseAuthors(output);
8964
+ }
8965
+ parseAuthors(output) {
8966
+ const lines = output.split("\n").filter((l) => l.trim());
8967
+ const authors = [];
8968
+ for (const line of lines) {
8969
+ const match = line.match(/^\s*(\d+)\s+(.+?)\s+<(.+?)>/);
8970
+ if (match) {
8971
+ const [, commits, name, email] = match;
8972
+ authors.push({
8973
+ name,
8974
+ email,
8975
+ commits: parseInt(commits, 10)
8976
+ });
8977
+ }
8978
+ }
8979
+ return authors;
8980
+ }
8981
+ async getFileHistory(filePath, options = {}) {
8982
+ return this.getCommits({
8983
+ ...options,
8984
+ filePath
8985
+ });
8986
+ }
8987
+ };
8988
+
8989
+ // ../git-utils/dist/src/git-analyzer.types.js
8990
+ init_esm_shims();
8991
+
8992
+ // ../git-utils/dist/src/git.types.js
8993
+ init_esm_shims();
8994
+
8995
+ // src/commands/export.ts
8996
+ import fs4 from "fs/promises";
8297
8997
  import path7 from "path";
8998
+ function userStatsToCsv(stats) {
8999
+ const headers = [
9000
+ "username",
9001
+ "period",
9002
+ "period_start",
9003
+ "period_end",
9004
+ "commits",
9005
+ "lines_added",
9006
+ "lines_removed",
9007
+ "net_change",
9008
+ "files_changed",
9009
+ "tasks_completed",
9010
+ "completion_rate",
9011
+ "commits_per_day",
9012
+ "streak",
9013
+ "trend"
9014
+ ].join(",");
9015
+ const values = [
9016
+ stats.username,
9017
+ stats.period,
9018
+ stats.periodStart,
9019
+ stats.periodEnd,
9020
+ stats.codeMetrics.commits,
9021
+ stats.codeMetrics.linesAdded,
9022
+ stats.codeMetrics.linesRemoved,
9023
+ stats.codeMetrics.netChange,
9024
+ stats.codeMetrics.filesChanged,
9025
+ stats.contributionMetrics.tasksCompleted,
9026
+ stats.engagementMetrics.completionRate.toFixed(2),
9027
+ stats.engagementMetrics.commitsPerDay.toFixed(2),
9028
+ stats.temporalMetrics.streak,
9029
+ stats.temporalMetrics.trend
9030
+ ].join(",");
9031
+ return `${headers}
9032
+ ${values}`;
9033
+ }
9034
+ function registerExportCommand(program2) {
9035
+ program2.command("export").description("Export metrics to CSV or JSON").option("-u, --user <username>", "User to export metrics for").option("-f, --format <format>", "Output format (csv|json)", "json").option("-o, --output <file>", "Output file path").option("-p, --period <period>", "Time period (day|week|month)", "week").action(async (options) => {
9036
+ try {
9037
+ const cwd = process.cwd();
9038
+ const tasksDir = path7.join(cwd, "TASKS");
9039
+ try {
9040
+ await fs4.access(tasksDir);
9041
+ } catch {
9042
+ console.error("\u274C TASKS directory not found in current directory");
9043
+ process.exit(1);
9044
+ }
9045
+ const gitAnalyzer = new GitAnalyzer(cwd);
9046
+ const userRegistry = new UserRegistry({
9047
+ taskinDir: path7.join(cwd, ".taskin")
9048
+ });
9049
+ await userRegistry.load();
9050
+ const metricsAdapter = new FileSystemMetricsAdapter(
9051
+ tasksDir,
9052
+ userRegistry,
9053
+ gitAnalyzer
9054
+ );
9055
+ const username = options.user || process.env.USER || "unknown";
9056
+ const stats = await metricsAdapter.getUserMetrics(username, {
9057
+ period: options.period
9058
+ });
9059
+ let output;
9060
+ if (options.format === "csv") {
9061
+ output = userStatsToCsv(stats);
9062
+ } else {
9063
+ output = JSON.stringify(stats, null, 2);
9064
+ }
9065
+ if (options.output) {
9066
+ await fs4.writeFile(options.output, output, "utf-8");
9067
+ console.log(`\u2705 Metrics exported to ${options.output}`);
9068
+ } else {
9069
+ console.log(output);
9070
+ }
9071
+ } catch (error2) {
9072
+ console.error("\u274C Error exporting metrics:", error2);
9073
+ process.exit(1);
9074
+ }
9075
+ });
9076
+ }
9077
+
9078
+ // src/commands/finish.ts
9079
+ init_esm_shims();
9080
+ import path9 from "path";
8298
9081
 
8299
9082
  // src/lib/sound-player.ts
8300
9083
  init_esm_shims();
8301
9084
  import { existsSync as existsSync2 } from "fs";
8302
- import path6 from "path";
9085
+ import path8 from "path";
8303
9086
  import player from "play-sound";
8304
9087
  var soundPlayer = player({});
8305
9088
  function playSound(soundName) {
8306
9089
  try {
8307
9090
  const possiblePaths = [
8308
9091
  // In development (from src)
8309
- path6.join(process.cwd(), "packages", "cli", "sounds", `${soundName}.mp3`),
9092
+ path8.join(process.cwd(), "packages", "cli", "sounds", `${soundName}.mp3`),
8310
9093
  // In production (relative to dist)
8311
- path6.join(__dirname, "..", "sounds", `${soundName}.mp3`),
9094
+ path8.join(__dirname, "..", "sounds", `${soundName}.mp3`),
8312
9095
  // Custom sound in project root
8313
- path6.join(process.cwd(), ".taskin", `${soundName}.mp3`)
9096
+ path8.join(process.cwd(), ".taskin", `${soundName}.mp3`)
8314
9097
  ];
8315
9098
  const soundPath = possiblePaths.find((p) => existsSync2(p));
8316
9099
  if (!soundPath) {
@@ -8348,9 +9131,9 @@ async function finishTask(taskId, options) {
8348
9131
  requireTaskinProject();
8349
9132
  printHeader(`Finishing Task ${taskId}`, "\u2705");
8350
9133
  const normalizedId = taskId.replace(/^task-/, "").padStart(3, "0");
8351
- const tasksDir = path7.join(process.cwd(), "TASKS");
8352
- const monorepoRoot = path7.dirname(tasksDir);
8353
- const taskinDir = path7.join(monorepoRoot, ".taskin");
9134
+ const tasksDir = path9.join(process.cwd(), "TASKS");
9135
+ const monorepoRoot = path9.dirname(tasksDir);
9136
+ const taskinDir = path9.join(monorepoRoot, ".taskin");
8354
9137
  const userRegistry = new UserRegistry({ taskinDir });
8355
9138
  await userRegistry.load();
8356
9139
  const taskProvider = new FileSystemTaskProvider(tasksDir, userRegistry);
@@ -8424,7 +9207,7 @@ function detectPackageManager() {
8424
9207
  }
8425
9208
  function isBundledProvider(packageName) {
8426
9209
  const bundledProviders = [
8427
- "@opentask/taskin-fs-provider",
9210
+ "@opentask/taskin-file-system-provider",
8428
9211
  "@opentask/taskin-core",
8429
9212
  "@opentask/taskin-task-manager",
8430
9213
  "@opentask/taskin-git-utils",
@@ -8501,7 +9284,7 @@ var AVAILABLE_PROVIDERS = [
8501
9284
  id: "fs",
8502
9285
  name: "\u{1F4C1} File System",
8503
9286
  description: "Store tasks as Markdown files in a local TASKS/ directory",
8504
- packageName: "@opentask/taskin-fs-provider",
9287
+ packageName: "@opentask/taskin-file-system-provider",
8505
9288
  configSchema: {
8506
9289
  required: ["tasksDir"],
8507
9290
  properties: {
@@ -8848,7 +9631,7 @@ async function executeLint(options) {
8848
9631
 
8849
9632
  // src/commands/list.ts
8850
9633
  init_esm_shims();
8851
- import path8 from "path";
9634
+ import path10 from "path";
8852
9635
  var listCommand = defineCommand({
8853
9636
  name: "list [filter]",
8854
9637
  description: "\u{1F4CA} List all tasks in the project",
@@ -8874,9 +9657,9 @@ var listCommand = defineCommand({
8874
9657
  async function listTasks(filter, options) {
8875
9658
  requireTaskinProject();
8876
9659
  printHeader("Task List", "\u{1F4CA}");
8877
- const tasksDir = path8.join(process.cwd(), "TASKS");
8878
- const monorepoRoot = path8.dirname(tasksDir);
8879
- const taskinDir = path8.join(monorepoRoot, ".taskin");
9660
+ const tasksDir = path10.join(process.cwd(), "TASKS");
9661
+ const monorepoRoot = path10.dirname(tasksDir);
9662
+ const taskinDir = path10.join(monorepoRoot, ".taskin");
8880
9663
  const userRegistry = new UserRegistry({ taskinDir });
8881
9664
  await userRegistry.load();
8882
9665
  const taskProvider = new FileSystemTaskProvider(tasksDir, userRegistry);
@@ -12697,7 +13480,7 @@ init_esm_shims();
12697
13480
 
12698
13481
  // src/commands/mcp-server.ts
12699
13482
  import chalk5 from "chalk";
12700
- import path9 from "path";
13483
+ import path11 from "path";
12701
13484
  var mcpServerCommand = defineCommand({
12702
13485
  name: "mcp-server",
12703
13486
  description: "\u{1F916} Start Model Context Protocol server for LLM integration",
@@ -12724,9 +13507,9 @@ async function startMCPServer(options) {
12724
13507
  printHeader("Starting MCP Server", "\u{1F916}");
12725
13508
  try {
12726
13509
  info("Initializing task manager...");
12727
- const tasksDir = path9.join(process.cwd(), "TASKS");
12728
- const monorepoRoot = path9.dirname(tasksDir);
12729
- const taskinDir = path9.join(monorepoRoot, ".taskin");
13510
+ const tasksDir = path11.join(process.cwd(), "TASKS");
13511
+ const monorepoRoot = path11.dirname(tasksDir);
13512
+ const taskinDir = path11.join(monorepoRoot, ".taskin");
12730
13513
  const userRegistry = new UserRegistry({ taskinDir });
12731
13514
  await userRegistry.load();
12732
13515
  const provider = new FileSystemTaskProvider(tasksDir, userRegistry);
@@ -12789,7 +13572,7 @@ async function startMCPServer(options) {
12789
13572
  init_esm_shims();
12790
13573
  import { existsSync as existsSync5, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
12791
13574
  import inquirer2 from "inquirer";
12792
- import path10 from "path";
13575
+ import path12 from "path";
12793
13576
  var createCommand = defineCommand({
12794
13577
  name: "new",
12795
13578
  description: "\u2795 Create a new task",
@@ -12881,12 +13664,12 @@ async function createTask(options) {
12881
13664
  );
12882
13665
  return;
12883
13666
  }
12884
- const tasksDir = path10.join(process.cwd(), "TASKS");
13667
+ const tasksDir = path12.join(process.cwd(), "TASKS");
12885
13668
  if (!existsSync5(tasksDir)) {
12886
13669
  mkdirSync2(tasksDir, { recursive: true });
12887
13670
  }
12888
- const monorepoRoot = path10.dirname(tasksDir);
12889
- const taskinDir = path10.join(monorepoRoot, ".taskin");
13671
+ const monorepoRoot = path12.dirname(tasksDir);
13672
+ const taskinDir = path12.join(monorepoRoot, ".taskin");
12890
13673
  const userRegistry = new UserRegistry({ taskinDir });
12891
13674
  await userRegistry.load();
12892
13675
  const taskProvider = new FileSystemTaskProvider(tasksDir, userRegistry);
@@ -12899,7 +13682,7 @@ async function createTask(options) {
12899
13682
  const taskId = String(nextNumber).padStart(3, "0");
12900
13683
  const titleSlug = options.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
12901
13684
  const fileName = `task-${taskId}-${titleSlug}.md`;
12902
- const filePath = path10.join(tasksDir, fileName);
13685
+ const filePath = path12.join(tasksDir, fileName);
12903
13686
  if (existsSync5(filePath)) {
12904
13687
  error(`Task file already exists: ${fileName}`);
12905
13688
  return;
@@ -12952,7 +13735,7 @@ Add any relevant notes or links here.
12952
13735
  // src/commands/pause.ts
12953
13736
  init_esm_shims();
12954
13737
  import { execSync as execSync2 } from "child_process";
12955
- import path11 from "path";
13738
+ import path13 from "path";
12956
13739
  var pauseCommand = defineCommand({
12957
13740
  name: "pause <task-id>",
12958
13741
  description: "\u23F8\uFE0F Pause work on a task",
@@ -12979,9 +13762,9 @@ async function pauseTask(taskId, options) {
12979
13762
  requireTaskinProject();
12980
13763
  printHeader(`Pausing Task ${taskId}`, "\u23F8\uFE0F");
12981
13764
  const normalizedId = taskId.replace(/^task-/, "").padStart(3, "0");
12982
- const tasksDir = path11.join(process.cwd(), "TASKS");
12983
- const monorepoRoot = path11.dirname(tasksDir);
12984
- const taskinDir = path11.join(monorepoRoot, ".taskin");
13765
+ const tasksDir = path13.join(process.cwd(), "TASKS");
13766
+ const monorepoRoot = path13.dirname(tasksDir);
13767
+ const taskinDir = path13.join(monorepoRoot, ".taskin");
12985
13768
  const userRegistry = new UserRegistry({ taskinDir });
12986
13769
  await userRegistry.load();
12987
13770
  const taskProvider = new FileSystemTaskProvider(tasksDir, userRegistry);
@@ -13034,7 +13817,7 @@ async function pauseTask(taskId, options) {
13034
13817
 
13035
13818
  // src/commands/start.ts
13036
13819
  init_esm_shims();
13037
- import path12 from "path";
13820
+ import path14 from "path";
13038
13821
  var startCommand = defineCommand({
13039
13822
  name: "start <task-id>",
13040
13823
  description: "\u{1F680} Start working on a task",
@@ -13061,9 +13844,9 @@ async function startTask(taskId, _options) {
13061
13844
  requireTaskinProject();
13062
13845
  printHeader(`Starting Task ${taskId}`, "\u{1F680}");
13063
13846
  const normalizedId = taskId.replace(/^task-/, "").padStart(3, "0");
13064
- const tasksDir = path12.join(process.cwd(), "TASKS");
13065
- const monorepoRoot = path12.dirname(tasksDir);
13066
- const taskinDir = path12.join(monorepoRoot, ".taskin");
13847
+ const tasksDir = path14.join(process.cwd(), "TASKS");
13848
+ const monorepoRoot = path14.dirname(tasksDir);
13849
+ const taskinDir = path14.join(monorepoRoot, ".taskin");
13067
13850
  const userRegistry = new UserRegistry({ taskinDir });
13068
13851
  await userRegistry.load();
13069
13852
  const taskProvider = new FileSystemTaskProvider(tasksDir, userRegistry);
@@ -13102,6 +13885,232 @@ async function startTask(taskId, _options) {
13102
13885
  }
13103
13886
  }
13104
13887
 
13888
+ // src/commands/stats.ts
13889
+ init_esm_shims();
13890
+ import chalk6 from "chalk";
13891
+ import path15 from "path";
13892
+ var statsCommand = defineCommand({
13893
+ name: "stats",
13894
+ description: "\u{1F4CA} Show user/team metrics and statistics",
13895
+ options: [
13896
+ {
13897
+ flags: "-u, --user <username>",
13898
+ description: "Show stats for specific user"
13899
+ },
13900
+ {
13901
+ flags: "-p, --period <period>",
13902
+ description: "Time period (day, week, month, year)"
13903
+ },
13904
+ {
13905
+ flags: "-t, --task <taskId>",
13906
+ description: "Show stats for specific task"
13907
+ },
13908
+ {
13909
+ flags: "-d, --detailed",
13910
+ description: "Show detailed metrics"
13911
+ },
13912
+ {
13913
+ flags: "--team",
13914
+ description: "Show team metrics instead of user metrics"
13915
+ }
13916
+ ],
13917
+ handler: async (options) => {
13918
+ await showStats(options);
13919
+ }
13920
+ });
13921
+ async function showStats(options) {
13922
+ requireTaskinProject();
13923
+ const tasksDir = path15.join(process.cwd(), "TASKS");
13924
+ const userRegistry = new UserRegistry({
13925
+ taskinDir: path15.join(process.cwd(), ".taskin")
13926
+ });
13927
+ await userRegistry.load();
13928
+ const gitAnalyzer = new GitAnalyzer(process.cwd());
13929
+ const metricsAdapter = new FileSystemMetricsAdapter(
13930
+ tasksDir,
13931
+ userRegistry,
13932
+ gitAnalyzer
13933
+ );
13934
+ const query = {
13935
+ period: options.period || "week"
13936
+ };
13937
+ try {
13938
+ if (options.team) {
13939
+ printHeader("Team Statistics", "\u{1F465}");
13940
+ const stats = await metricsAdapter.getTeamMetrics("default", query);
13941
+ displayTeamStats(stats, options.detailed);
13942
+ } else if (options.task) {
13943
+ printHeader(`Task Statistics: ${options.task}`, "\u{1F4CB}");
13944
+ const stats = await metricsAdapter.getTaskMetrics(options.task, query);
13945
+ displayTaskStats(stats, options.detailed);
13946
+ } else {
13947
+ const username = options.user || process.env.USER || "unknown";
13948
+ printHeader(`User Statistics: ${username}`, "\u{1F464}");
13949
+ const stats = await metricsAdapter.getUserMetrics(username, query);
13950
+ displayUserStats(stats, options.detailed);
13951
+ }
13952
+ } catch (error2) {
13953
+ console.error(chalk6.red("\n\u274C Error fetching stats:"), error2);
13954
+ process.exit(1);
13955
+ }
13956
+ }
13957
+ function displayUserStats(stats, detailed = false) {
13958
+ console.log(
13959
+ `${chalk6.dim("Period:")} ${stats.period} (${formatDate(stats.periodStart)} to ${formatDate(stats.periodEnd)})
13960
+ `
13961
+ );
13962
+ console.log(chalk6.bold("\u{1F4DD} Code Metrics"));
13963
+ console.log(
13964
+ ` ${chalk6.green("+")}${stats.codeMetrics.linesAdded} lines added`
13965
+ );
13966
+ console.log(
13967
+ ` ${chalk6.red("-")}${stats.codeMetrics.linesRemoved} lines removed`
13968
+ );
13969
+ console.log(` ${chalk6.cyan("=")}${stats.codeMetrics.netChange} net change`);
13970
+ console.log(` \u{1F4C1} ${stats.codeMetrics.filesChanged} files changed`);
13971
+ console.log(` \u{1F4BE} ${stats.codeMetrics.commits} commits
13972
+ `);
13973
+ console.log(chalk6.bold("\u{1F3AF} Contribution"));
13974
+ console.log(
13975
+ ` \u2705 ${stats.contributionMetrics.tasksCompleted} tasks completed`
13976
+ );
13977
+ console.log(
13978
+ ` \u{1F4CA} ${stats.contributionMetrics.activityFrequency.toFixed(2)} commits/day
13979
+ `
13980
+ );
13981
+ console.log(chalk6.bold("\u26A1 Engagement"));
13982
+ console.log(
13983
+ ` \u{1F525} ${(stats.engagementMetrics.completionRate * 100).toFixed(1)}% completion rate`
13984
+ );
13985
+ console.log(
13986
+ ` \u{1F4C8} ${stats.engagementMetrics.activeTasksCount} active tasks
13987
+ `
13988
+ );
13989
+ if (detailed) {
13990
+ console.log(chalk6.bold("\u23F0 Temporal Patterns"));
13991
+ console.log(" By Day of Week:");
13992
+ const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
13993
+ Object.entries(stats.temporalMetrics.byDayOfWeek).forEach(
13994
+ ([day, count]) => {
13995
+ const dayName = days[parseInt(day)];
13996
+ const bar = createBar(
13997
+ count,
13998
+ Math.max(...Object.values(stats.temporalMetrics.byDayOfWeek))
13999
+ );
14000
+ console.log(` ${dayName}: ${bar} ${count}`);
14001
+ }
14002
+ );
14003
+ console.log("\n By Time of Day:");
14004
+ Object.entries(stats.temporalMetrics.byTimeOfDay).forEach(
14005
+ ([time, count]) => {
14006
+ const maxTime = Math.max(
14007
+ ...Object.values(stats.temporalMetrics.byTimeOfDay)
14008
+ );
14009
+ const bar = createBar(count, maxTime);
14010
+ console.log(` ${time.padEnd(10)}: ${bar} ${count}`);
14011
+ }
14012
+ );
14013
+ console.log(`
14014
+ \u{1F525} Streak: ${stats.temporalMetrics.streak} days`);
14015
+ console.log(
14016
+ ` \u{1F4C8} Trend: ${getTrendEmoji(stats.temporalMetrics.trend)} ${stats.temporalMetrics.trend}
14017
+ `
14018
+ );
14019
+ }
14020
+ }
14021
+ function displayTeamStats(stats, detailed = false) {
14022
+ console.log(
14023
+ `${chalk6.dim("Period:")} ${stats.period} (${formatDate(stats.periodStart)} to ${formatDate(stats.periodEnd)})
14024
+ `
14025
+ );
14026
+ console.log(chalk6.bold("\u{1F465} Team Overview"));
14027
+ console.log(` \u{1F464} ${stats.totalContributors} contributors`);
14028
+ console.log(` \u{1F4BE} ${stats.totalCommits} total commits`);
14029
+ console.log(` \u2705 ${stats.totalTasksCompleted} tasks completed
14030
+ `);
14031
+ console.log(chalk6.bold("\u{1F4DD} Code Metrics"));
14032
+ console.log(
14033
+ ` ${chalk6.green("+")}${stats.codeMetrics.linesAdded} lines added`
14034
+ );
14035
+ console.log(
14036
+ ` ${chalk6.red("-")}${stats.codeMetrics.linesRemoved} lines removed`
14037
+ );
14038
+ console.log(` \u{1F4C1} ${stats.codeMetrics.filesChanged} files changed
14039
+ `);
14040
+ if (detailed && stats.contributors.length > 0) {
14041
+ console.log(chalk6.bold("\u{1F3C6} Top Contributors"));
14042
+ stats.contributors.sort((a, b) => {
14043
+ if (b.commits !== a.commits) {
14044
+ return b.commits - a.commits;
14045
+ }
14046
+ if (b.tasksCompleted !== a.tasksCompleted) {
14047
+ return b.tasksCompleted - a.tasksCompleted;
14048
+ }
14049
+ return a.username.localeCompare(b.username);
14050
+ }).slice(0, 5).forEach((contrib, idx) => {
14051
+ console.log(
14052
+ ` ${idx + 1}. ${contrib.username}: ${contrib.commits} commits, ${contrib.tasksCompleted} tasks`
14053
+ );
14054
+ });
14055
+ }
14056
+ }
14057
+ function displayTaskStats(stats, detailed = false) {
14058
+ console.log(`${chalk6.dim("Task:")} ${stats.taskId} - ${stats.title}
14059
+ `);
14060
+ console.log(chalk6.bold("\u{1F4CB} Task Info"));
14061
+ console.log(` Status: ${getStatusEmoji(stats.status)} ${stats.status}`);
14062
+ console.log(` Type: ${stats.type}`);
14063
+ console.log(` Assignee: ${stats.assignee || "unassigned"}
14064
+ `);
14065
+ console.log(chalk6.bold("\u{1F4DD} Code Metrics"));
14066
+ console.log(
14067
+ ` ${chalk6.green("+")}${stats.codeMetrics.linesAdded} lines added`
14068
+ );
14069
+ console.log(
14070
+ ` ${chalk6.red("-")}${stats.codeMetrics.linesRemoved} lines removed`
14071
+ );
14072
+ console.log(` \u{1F4C1} ${stats.codeMetrics.filesChanged} files changed
14073
+ `);
14074
+ if (stats.contributors.length > 0) {
14075
+ console.log(chalk6.bold("\u{1F465} Contributors"));
14076
+ stats.contributors.forEach((c) => {
14077
+ console.log(` \u2022 ${c}`);
14078
+ });
14079
+ }
14080
+ }
14081
+ function formatDate(isoString) {
14082
+ return new Date(isoString).toLocaleDateString();
14083
+ }
14084
+ function createBar(value, max, length = 20) {
14085
+ if (max === 0) return "\u2591".repeat(length);
14086
+ const filled = Math.round(value / max * length);
14087
+ return chalk6.cyan("\u2588".repeat(filled)) + chalk6.dim("\u2591".repeat(length - filled));
14088
+ }
14089
+ function getTrendEmoji(trend) {
14090
+ switch (trend) {
14091
+ case "increasing":
14092
+ return "\u{1F4C8}";
14093
+ case "decreasing":
14094
+ return "\u{1F4C9}";
14095
+ default:
14096
+ return "\u27A1\uFE0F";
14097
+ }
14098
+ }
14099
+ function getStatusEmoji(status) {
14100
+ switch (status) {
14101
+ case "done":
14102
+ return "\u2705";
14103
+ case "in-progress":
14104
+ return "\u{1F504}";
14105
+ case "blocked":
14106
+ return "\u{1F6AB}";
14107
+ case "pending":
14108
+ return "\u23F3";
14109
+ default:
14110
+ return "\u2753";
14111
+ }
14112
+ }
14113
+
13105
14114
  // src/lib/help.ts
13106
14115
  init_esm_shims();
13107
14116
  function showCustomHelp() {
@@ -13264,7 +14273,7 @@ init_esm_shims();
13264
14273
 
13265
14274
  // src/lib/file-system-task-linter/file-system-task-linter.ts
13266
14275
  init_esm_shims();
13267
- import chalk6 from "chalk";
14276
+ import chalk7 from "chalk";
13268
14277
  import { readdir, readFile as readFile2 } from "fs/promises";
13269
14278
  import { join as join4 } from "path";
13270
14279
  var VALID_STATUSES = ["pending", "in-progress", "done", "blocked"];
@@ -13438,13 +14447,13 @@ var FileSystemTaskLinter = class {
13438
14447
  static printResults(result) {
13439
14448
  if (result.errors.length === 0 && result.warnings.length === 0) {
13440
14449
  console.log(
13441
- chalk6.green(`\u2705 All ${result.filesChecked} task files are valid!
14450
+ chalk7.green(`\u2705 All ${result.filesChecked} task files are valid!
13442
14451
  `)
13443
14452
  );
13444
14453
  return;
13445
14454
  }
13446
14455
  console.log(
13447
- chalk6.bold(
14456
+ chalk7.bold(
13448
14457
  `
13449
14458
  \u{1F4CA} Validation Results (${result.filesChecked} files checked):
13450
14459
  `
@@ -13458,19 +14467,19 @@ var FileSystemTaskLinter = class {
13458
14467
  errorsByFile.get(error2.file).push(error2);
13459
14468
  });
13460
14469
  for (const [file, fileErrors] of errorsByFile) {
13461
- console.log(chalk6.cyan(`
14470
+ console.log(chalk7.cyan(`
13462
14471
  \u{1F4C4} ${file}`));
13463
14472
  for (const error2 of fileErrors) {
13464
- const icon = error2.severity === "error" ? chalk6.red("\u274C") : chalk6.yellow("\u26A0\uFE0F");
14473
+ const icon = error2.severity === "error" ? chalk7.red("\u274C") : chalk7.yellow("\u26A0\uFE0F");
13465
14474
  const location = error2.line ? `:${error2.line}` : "";
13466
14475
  console.log(` ${icon} ${error2.message}${location}`);
13467
14476
  }
13468
14477
  }
13469
14478
  console.log("\n" + "\u2500".repeat(60));
13470
14479
  console.log(
13471
- chalk6.bold(
14480
+ chalk7.bold(
13472
14481
  `
13473
- \u{1F4CA} Summary: ${chalk6.red(result.errors.length + " error(s)")}, ${chalk6.yellow(result.warnings.length + " warning(s)")}
14482
+ \u{1F4CA} Summary: ${chalk7.red(result.errors.length + " error(s)")}, ${chalk7.yellow(result.warnings.length + " warning(s)")}
13474
14483
  `
13475
14484
  )
13476
14485
  );
@@ -13644,6 +14653,8 @@ createCommand(program);
13644
14653
  startCommand(program);
13645
14654
  pauseCommand(program);
13646
14655
  finishCommand(program);
14656
+ statsCommand(program);
14657
+ registerExportCommand(program);
13647
14658
  lintCommand(program);
13648
14659
  dashboardCommand(program);
13649
14660
  mcpServerCommand(program);