taskin 1.0.13 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -51,7 +51,7 @@ var init_esm_shims = __esm({
|
|
|
51
51
|
}
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
// ../
|
|
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
|
-
"../
|
|
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
|
-
// ../
|
|
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
|
|
125
|
-
const inlineTypePattern = /^(Type|Tipo):\s
|
|
126
|
-
const inlineAssigneePattern = /^(Assignee|Responsável):\s
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
const
|
|
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*(.+?)(\
|
|
183
|
+
/^(Status|Tipo):\s*(.+?)([ \t]*)$/im,
|
|
180
184
|
(_, key, value) => `${key}: ${value.trim()} `
|
|
181
185
|
);
|
|
182
186
|
newContent = newContent.replace(
|
|
183
|
-
/^(Type|Tipo):\s*(.+?)(\
|
|
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*(.+?)(\
|
|
191
|
+
/^(Assignee|Responsável):\s*(.+?)([ \t]*)$/im,
|
|
188
192
|
(_, key, value) => `${key}: ${value.trim()} `
|
|
189
193
|
);
|
|
190
194
|
wasModified = true;
|
|
191
195
|
}
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
"../
|
|
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(
|
|
3553
|
-
let input =
|
|
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 [
|
|
3754
|
-
wsComponent.path =
|
|
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,
|
|
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,
|
|
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
|
-
// ../
|
|
7189
|
+
// ../file-system-task-provider/dist/index.js
|
|
7174
7190
|
init_esm_shims();
|
|
7175
7191
|
|
|
7176
|
-
// ../
|
|
7192
|
+
// ../file-system-task-provider/dist/file-system-metrics-adapter.js
|
|
7177
7193
|
init_esm_shims();
|
|
7178
|
-
|
|
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
|
|
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 =
|
|
7200
|
-
const content = await
|
|
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
|
|
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
|
|
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
|
|
7649
|
+
await fs2.writeFile(task.filePath, updatedContent, "utf-8");
|
|
7265
7650
|
}
|
|
7266
7651
|
async getAllTasks() {
|
|
7267
|
-
const files = await
|
|
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 =
|
|
7274
|
-
const content = await
|
|
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 =
|
|
7333
|
-
const fileExists = await
|
|
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
|
|
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
|
|
7385
|
-
const taskFiles = files.filter((file) => file.startsWith("task-") && file.endsWith(".md")).map((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
|
-
// ../
|
|
7792
|
+
// ../file-system-task-provider/dist/index.js
|
|
7408
7793
|
init_i18n();
|
|
7409
7794
|
|
|
7410
|
-
// ../
|
|
7795
|
+
// ../file-system-task-provider/dist/user-registry.js
|
|
7411
7796
|
init_esm_shims();
|
|
7412
|
-
import { promises as
|
|
7413
|
-
import
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
7912
|
-
|
|
7913
|
-
|
|
7914
|
-
|
|
7915
|
-
|
|
7916
|
-
|
|
7917
|
-
|
|
7918
|
-
|
|
7919
|
-
|
|
7920
|
-
|
|
7921
|
-
|
|
7922
|
-
|
|
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
|
-
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
7939
|
-
|
|
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
|
-
|
|
7954
|
-
|
|
7955
|
-
|
|
7956
|
-
|
|
7957
|
-
|
|
7958
|
-
|
|
7959
|
-
|
|
7960
|
-
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
7967
|
-
|
|
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
|
|
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
|
|
8438
|
+
import path5 from "path";
|
|
8059
8439
|
function isTaskinProject(cwd = process.cwd()) {
|
|
8060
|
-
return existsSync(
|
|
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 =
|
|
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 =
|
|
8551
|
+
let tasksDir = path6.join(currentDir, "TASKS");
|
|
8172
8552
|
try {
|
|
8173
|
-
await import("fs").then((
|
|
8553
|
+
await import("fs").then((fs5) => fs5.promises.access(tasksDir));
|
|
8174
8554
|
} catch {
|
|
8175
|
-
while (currentDir !==
|
|
8176
|
-
const workspaceFile =
|
|
8555
|
+
while (currentDir !== path6.dirname(currentDir)) {
|
|
8556
|
+
const workspaceFile = path6.join(currentDir, "pnpm-workspace.yaml");
|
|
8177
8557
|
try {
|
|
8178
|
-
await import("fs").then((
|
|
8179
|
-
tasksDir =
|
|
8558
|
+
await import("fs").then((fs5) => fs5.promises.access(workspaceFile));
|
|
8559
|
+
tasksDir = path6.join(currentDir, "TASKS");
|
|
8180
8560
|
break;
|
|
8181
8561
|
} catch {
|
|
8182
|
-
currentDir =
|
|
8562
|
+
currentDir = path6.dirname(currentDir);
|
|
8183
8563
|
}
|
|
8184
8564
|
}
|
|
8185
8565
|
}
|
|
8186
8566
|
info(`Using tasks directory: ${tasksDir}`);
|
|
8187
|
-
const monorepoRoot =
|
|
8188
|
-
const taskinDir =
|
|
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 ?
|
|
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
|
-
(
|
|
8223
|
-
|
|
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/
|
|
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
|
|
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
|
-
|
|
9092
|
+
path8.join(process.cwd(), "packages", "cli", "sounds", `${soundName}.mp3`),
|
|
8310
9093
|
// In production (relative to dist)
|
|
8311
|
-
|
|
9094
|
+
path8.join(__dirname, "..", "sounds", `${soundName}.mp3`),
|
|
8312
9095
|
// Custom sound in project root
|
|
8313
|
-
|
|
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 =
|
|
8352
|
-
const monorepoRoot =
|
|
8353
|
-
const taskinDir =
|
|
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-
|
|
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-
|
|
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
|
|
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 =
|
|
8878
|
-
const monorepoRoot =
|
|
8879
|
-
const taskinDir =
|
|
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
|
|
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 =
|
|
12728
|
-
const monorepoRoot =
|
|
12729
|
-
const taskinDir =
|
|
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
|
|
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 =
|
|
13667
|
+
const tasksDir = path12.join(process.cwd(), "TASKS");
|
|
12885
13668
|
if (!existsSync5(tasksDir)) {
|
|
12886
13669
|
mkdirSync2(tasksDir, { recursive: true });
|
|
12887
13670
|
}
|
|
12888
|
-
const monorepoRoot =
|
|
12889
|
-
const taskinDir =
|
|
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 =
|
|
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
|
|
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 =
|
|
12983
|
-
const monorepoRoot =
|
|
12984
|
-
const taskinDir =
|
|
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
|
|
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 =
|
|
13065
|
-
const monorepoRoot =
|
|
13066
|
-
const taskinDir =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
14470
|
+
console.log(chalk7.cyan(`
|
|
13462
14471
|
\u{1F4C4} ${file}`));
|
|
13463
14472
|
for (const error2 of fileErrors) {
|
|
13464
|
-
const icon = error2.severity === "error" ?
|
|
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
|
-
|
|
14480
|
+
chalk7.bold(
|
|
13472
14481
|
`
|
|
13473
|
-
\u{1F4CA} Summary: ${
|
|
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);
|