taskin 1.0.11 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +560 -495
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -51,6 +51,48 @@ var init_esm_shims = __esm({
51
51
  }
52
52
  });
53
53
 
54
+ // ../fs-task-provider/dist/i18n.js
55
+ function getI18n(locale = "en-US") {
56
+ return i18nConfig[locale];
57
+ }
58
+ function detectLocale(content) {
59
+ if (content.includes("## Descri\xE7\xE3o") || content.includes("## Tipo") || content.includes("## Respons\xE1vel") || content.includes("## Tarefas")) {
60
+ return "pt-BR";
61
+ }
62
+ return "en-US";
63
+ }
64
+ var i18nConfig;
65
+ var init_i18n = __esm({
66
+ "../fs-task-provider/dist/i18n.js"() {
67
+ "use strict";
68
+ init_esm_shims();
69
+ i18nConfig = {
70
+ "en-US": {
71
+ status: "Status",
72
+ type: "Type",
73
+ assignee: "Assignee",
74
+ description: "Description",
75
+ tasks: "Tasks",
76
+ notes: "Notes",
77
+ defaultAssignee: "To be defined",
78
+ descriptionPlaceholder: "Add task description here...",
79
+ notesPlaceholder: "Add any relevant notes or links here."
80
+ },
81
+ "pt-BR": {
82
+ status: "Status",
83
+ type: "Tipo",
84
+ assignee: "Respons\xE1vel",
85
+ description: "Descri\xE7\xE3o",
86
+ tasks: "Tarefas",
87
+ notes: "Notas",
88
+ defaultAssignee: "A definir",
89
+ descriptionPlaceholder: "Adicione a descri\xE7\xE3o da tarefa aqui...",
90
+ notesPlaceholder: "Adicione notas ou links relevantes aqui."
91
+ }
92
+ };
93
+ }
94
+ });
95
+
54
96
  // ../fs-task-provider/dist/task-validator.js
55
97
  var task_validator_exports = {};
56
98
  __export(task_validator_exports, {
@@ -62,47 +104,79 @@ import { readFile, writeFile } from "fs/promises";
62
104
  async function fixTaskFile(filePath) {
63
105
  try {
64
106
  const content = await readFile(filePath, "utf-8");
65
- const hasInlineStatus = /^Status:\s*.+$/im.test(content);
66
- const hasInlineType = /^Type:\s*.+$/im.test(content);
67
- const hasInlineAssignee = /^Assignee:\s*.+$/im.test(content);
68
- if (!hasInlineStatus && !hasInlineType && !hasInlineAssignee) {
107
+ const locale = detectLocale(content);
108
+ const i18n = getI18n(locale);
109
+ const statusPattern = new RegExp(`##\\s*(?:Status|${i18n.status})\\s*\\n\\s*([^\\n\\r]+)`, "i");
110
+ const typePattern = new RegExp(`##\\s*(?:Type|${i18n.type})\\s*\\n\\s*([^\\n\\r]+)`, "i");
111
+ const assigneePattern = new RegExp(`##\\s*(?:Assignee|${i18n.assignee})\\s*\\n\\s*([^\\n\\r]+)`, "i");
112
+ const hasSectionStatus = statusPattern.test(content);
113
+ const hasSectionType = typePattern.test(content);
114
+ const hasSectionAssignee = assigneePattern.test(content);
115
+ const inlineStatusPattern = /^(Status|Tipo):\s*(.+?)(\s*)$/im;
116
+ const inlineTypePattern = /^(Type|Tipo):\s*(.+?)(\s*)$/im;
117
+ const inlineAssigneePattern = /^(Assignee|Responsável):\s*(.+?)(\s*)$/im;
118
+ const hasInlineStatus = inlineStatusPattern.test(content);
119
+ const hasInlineType = inlineTypePattern.test(content);
120
+ const hasInlineAssignee = inlineAssigneePattern.test(content);
121
+ const needsSpaceFix = hasInlineStatus && !/^(?:Status|Tipo):.+ $/im.test(content) || hasInlineType && !/^(?:Type|Tipo):.+ $/im.test(content) || hasInlineAssignee && !/^(?:Assignee|Responsável):.+ $/im.test(content);
122
+ if (!hasSectionStatus && !hasSectionType && !hasSectionAssignee && !needsSpaceFix) {
69
123
  return false;
70
124
  }
71
- const statusMatch = content.match(/^Status:\s*(.+)$/im);
72
- const typeMatch = content.match(/^Type:\s*(.+)$/im);
73
- const assigneeMatch = content.match(/^Assignee:\s*(.+)$/im);
74
125
  let newContent = content;
75
- if (statusMatch) {
76
- newContent = newContent.replace(/^Status:\s*.+$/im, "");
77
- }
78
- if (typeMatch) {
79
- newContent = newContent.replace(/^Type:\s*.+$/im, "");
80
- }
81
- if (assigneeMatch) {
82
- newContent = newContent.replace(/^Assignee:\s*.+$/im, "");
83
- }
84
- newContent = newContent.replace(/\n{3,}/g, "\n\n");
85
- const titleLineIdx = newContent.split("\n").findIndex((line) => line.trim().startsWith("# "));
86
- if (titleLineIdx === -1) {
87
- return false;
88
- }
89
- const contentLines = newContent.split("\n");
90
- const beforeTitle = contentLines.slice(0, titleLineIdx + 1);
91
- const afterTitle = contentLines.slice(titleLineIdx + 1);
92
- const sections = [];
93
- if (statusMatch) {
94
- sections.push("", "## Status", "", statusMatch[1].trim());
95
- }
96
- if (typeMatch) {
97
- sections.push("", "## Type", "", typeMatch[1].trim());
98
- }
99
- if (assigneeMatch) {
100
- sections.push("", "## Assignee", "", assigneeMatch[1].trim());
126
+ let wasModified = false;
127
+ if (hasSectionStatus || hasSectionType || hasSectionAssignee) {
128
+ const statusMatch = content.match(statusPattern);
129
+ const typeMatch = content.match(typePattern);
130
+ const assigneeMatch = content.match(assigneePattern);
131
+ if (statusMatch) {
132
+ newContent = newContent.replace(statusPattern, "");
133
+ }
134
+ if (typeMatch) {
135
+ newContent = newContent.replace(typePattern, "");
136
+ }
137
+ if (assigneeMatch) {
138
+ newContent = newContent.replace(assigneePattern, "");
139
+ }
140
+ newContent = newContent.replace(/\n{3,}/g, "\n\n");
141
+ const titleLineIdx = newContent.split("\n").findIndex((line) => line.trim().startsWith("# "));
142
+ if (titleLineIdx === -1) {
143
+ return false;
144
+ }
145
+ const contentLines = newContent.split("\n");
146
+ const beforeTitle = contentLines.slice(0, titleLineIdx + 1);
147
+ const afterTitle = contentLines.slice(titleLineIdx + 1);
148
+ const inlineMetadata = [];
149
+ if (statusMatch) {
150
+ inlineMetadata.push(`Status: ${statusMatch[1].trim()} `);
151
+ }
152
+ if (typeMatch) {
153
+ inlineMetadata.push(`Type: ${typeMatch[1].trim()} `);
154
+ }
155
+ if (assigneeMatch) {
156
+ inlineMetadata.push(`Assignee: ${assigneeMatch[1].trim()} `);
157
+ }
158
+ newContent = [
159
+ ...beforeTitle,
160
+ "",
161
+ ...inlineMetadata,
162
+ "",
163
+ ...afterTitle
164
+ ].join("\n");
165
+ wasModified = true;
166
+ }
167
+ if (needsSpaceFix) {
168
+ newContent = newContent.replace(/^(# .+)\n([^#\n])/m, "$1\n\n$2");
169
+ newContent = newContent.replace(/^(Status|Tipo):\s*(.+?)(\s*)$/im, (_, key, value) => `${key}: ${value.trim()} `);
170
+ newContent = newContent.replace(/^(Type|Tipo):\s*(.+?)(\s*)$/im, (_, key, value) => `${key}: ${value.trim()} `);
171
+ newContent = newContent.replace(/^(Assignee|Responsável):\s*(.+?)(\s*)$/im, (_, key, value) => `${key}: ${value.trim()} `);
172
+ wasModified = true;
173
+ }
174
+ if (wasModified) {
175
+ const finalContent = newContent.replace(/\n{3,}/g, "\n\n").trim() + "\n";
176
+ await writeFile(filePath, finalContent, "utf-8");
177
+ return true;
101
178
  }
102
- const fixed = [...beforeTitle, ...sections, "", ...afterTitle].join("\n");
103
- const finalContent = fixed.replace(/\n{3,}/g, "\n\n").trim() + "\n";
104
- await writeFile(filePath, finalContent, "utf-8");
105
- return true;
179
+ return false;
106
180
  } catch (error2) {
107
181
  console.error(`Failed to fix ${filePath}:`, error2);
108
182
  return false;
@@ -113,9 +187,13 @@ async function validateTaskFile(filePath) {
113
187
  try {
114
188
  const content = await readFile(filePath, "utf-8");
115
189
  const lines = content.split("\n");
190
+ const locale = detectLocale(content);
191
+ const i18n = getI18n(locale);
116
192
  const hasTitleSection = lines.some((line) => line.trim().startsWith("# "));
117
- const hasStatusSection = content.includes("## Status");
118
- const hasInlineStatus = /^Status:\s*.+$/im.test(content);
193
+ const inlineStatusPattern = new RegExp(`^(?:Status|${i18n.status}):\\s*.+$`, "im");
194
+ const sectionStatusPattern = new RegExp(`##\\s*(?:Status|${i18n.status})`, "i");
195
+ const hasInlineStatus = inlineStatusPattern.test(content);
196
+ const hasSectionStatus = sectionStatusPattern.test(content);
119
197
  const hasDescriptionSection = content.includes("## Description") || content.includes("## Descri\xE7\xE3o");
120
198
  if (!hasTitleSection) {
121
199
  issues.push({
@@ -126,38 +204,43 @@ async function validateTaskFile(filePath) {
126
204
  suggestion: "Add a level-1 heading at the start: # Your Task Title"
127
205
  });
128
206
  }
129
- if (hasInlineStatus) {
130
- const statusLineIdx = lines.findIndex(
131
- (line) => /^Status:/i.test(line.trim())
132
- );
207
+ if (hasSectionStatus) {
208
+ const statusLineIdx = lines.findIndex((line) => sectionStatusPattern.test(line.trim()));
133
209
  issues.push({
134
210
  file: filePath,
135
211
  line: statusLineIdx >= 0 ? statusLineIdx + 1 : void 0,
136
- message: 'Inline metadata ("Status: ...") is not allowed. Use a "## Status" section instead.',
212
+ message: 'Section-based metadata ("## Status") is not allowed. Use inline format instead.',
137
213
  severity: "error",
138
- suggestion: "Replace inline metadata with a section:\n## Status\n<todo|in-progress|done>"
214
+ suggestion: `Replace section with inline metadata:
215
+ ${i18n.status}: <todo|in-progress|done>`
139
216
  });
140
217
  }
141
- if (!hasStatusSection) {
218
+ if (!hasInlineStatus) {
142
219
  issues.push({
143
220
  file: filePath,
144
- message: "Task file must have a ## Status section",
221
+ message: "Task file must have a Status field",
145
222
  severity: "error",
146
- suggestion: "Add a section:\n## Status\n<todo|in-progress|done>"
223
+ suggestion: `Add inline metadata after title:
224
+ ${i18n.status}: <todo|in-progress|done>`
147
225
  });
148
226
  } else {
149
- const statusMatch = content.match(/## Status\s*\n\s*([^\n\r]+)/i);
150
- const statusValue = statusMatch ? statusMatch[1].trim().toLowerCase() : "";
151
- if (!["todo", "in-progress", "done", "pending"].includes(statusValue)) {
152
- const statusLineIdx = lines.findIndex(
153
- (line) => line.trim() === "## Status"
154
- );
227
+ const statusMatch = content.match(inlineStatusPattern);
228
+ const statusValue = statusMatch ? statusMatch[0].split(":")[1]?.trim().toLowerCase() || "" : "";
229
+ if (![
230
+ "todo",
231
+ "in-progress",
232
+ "done",
233
+ "pending",
234
+ "blocked",
235
+ "canceled"
236
+ ].includes(statusValue)) {
237
+ const statusLineIdx = lines.findIndex((line) => inlineStatusPattern.test(line.trim()));
155
238
  issues.push({
156
239
  file: filePath,
157
- line: statusLineIdx >= 0 ? statusLineIdx + 2 : void 0,
158
- message: "Status must be one of: todo, in-progress, done, pending",
240
+ line: statusLineIdx >= 0 ? statusLineIdx + 1 : void 0,
241
+ message: "Status must be one of: todo, in-progress, done, pending, blocked, canceled",
159
242
  severity: "error",
160
- suggestion: "Set status to: todo, in-progress, done, or pending"
243
+ suggestion: "Set status to: todo, in-progress, done, pending, blocked, or canceled"
161
244
  });
162
245
  }
163
246
  }
@@ -198,15 +281,9 @@ async function validateTaskFile(filePath) {
198
281
  return issues;
199
282
  }
200
283
  function createLintResult(allIssues) {
201
- const errorCount = allIssues.filter(
202
- (issue) => issue.severity === "error"
203
- ).length;
204
- const warningCount = allIssues.filter(
205
- (issue) => issue.severity === "warning"
206
- ).length;
207
- const infoCount = allIssues.filter(
208
- (issue) => issue.severity === "info"
209
- ).length;
284
+ const errorCount = allIssues.filter((issue) => issue.severity === "error").length;
285
+ const warningCount = allIssues.filter((issue) => issue.severity === "warning").length;
286
+ const infoCount = allIssues.filter((issue) => issue.severity === "info").length;
210
287
  return {
211
288
  valid: errorCount === 0,
212
289
  issues: allIssues,
@@ -219,6 +296,7 @@ var init_task_validator = __esm({
219
296
  "../fs-task-provider/dist/task-validator.js"() {
220
297
  "use strict";
221
298
  init_esm_shims();
299
+ init_i18n();
222
300
  }
223
301
  });
224
302
 
@@ -7063,47 +7141,10 @@ init_esm_shims();
7063
7141
 
7064
7142
  // ../fs-task-provider/dist/fs-task-provider.js
7065
7143
  init_esm_shims();
7144
+ init_i18n();
7145
+ init_task_validator();
7066
7146
  import { promises as fs } from "fs";
7067
7147
  import path2 from "path";
7068
-
7069
- // ../fs-task-provider/dist/i18n.js
7070
- init_esm_shims();
7071
- var i18nConfig = {
7072
- "en-US": {
7073
- status: "Status",
7074
- type: "Type",
7075
- assignee: "Assignee",
7076
- description: "Description",
7077
- tasks: "Tasks",
7078
- notes: "Notes",
7079
- defaultAssignee: "To be defined",
7080
- descriptionPlaceholder: "Add task description here...",
7081
- notesPlaceholder: "Add any relevant notes or links here."
7082
- },
7083
- "pt-BR": {
7084
- status: "Status",
7085
- type: "Tipo",
7086
- assignee: "Respons\xE1vel",
7087
- description: "Descri\xE7\xE3o",
7088
- tasks: "Tarefas",
7089
- notes: "Notas",
7090
- defaultAssignee: "A definir",
7091
- descriptionPlaceholder: "Adicione a descri\xE7\xE3o da tarefa aqui...",
7092
- notesPlaceholder: "Adicione notas ou links relevantes aqui."
7093
- }
7094
- };
7095
- function getI18n(locale = "en-US") {
7096
- return i18nConfig[locale];
7097
- }
7098
- function detectLocale(content) {
7099
- if (content.includes("## Descri\xE7\xE3o") || content.includes("## Tipo") || content.includes("## Respons\xE1vel") || content.includes("## Tarefas")) {
7100
- return "pt-BR";
7101
- }
7102
- return "en-US";
7103
- }
7104
-
7105
- // ../fs-task-provider/dist/fs-task-provider.js
7106
- init_task_validator();
7107
7148
  var FileSystemTaskProvider = class {
7108
7149
  tasksDirectory;
7109
7150
  userRegistry;
@@ -7115,9 +7156,7 @@ var FileSystemTaskProvider = class {
7115
7156
  }
7116
7157
  async findTask(taskId) {
7117
7158
  const files = await fs.readdir(this.tasksDirectory);
7118
- const taskFile = files.find(
7119
- (file) => file.startsWith(`task-${taskId}-`) && file.endsWith(".md")
7120
- );
7159
+ const taskFile = files.find((file) => file.startsWith(`task-${taskId}-`) && file.endsWith(".md"));
7121
7160
  if (!taskFile) {
7122
7161
  return void 0;
7123
7162
  }
@@ -7127,26 +7166,26 @@ var FileSystemTaskProvider = class {
7127
7166
  const title = titleMatch ? titleMatch[1].trim() : "Untitled";
7128
7167
  const contentLocale = detectLocale(content);
7129
7168
  const i18n = getI18n(contentLocale);
7130
- const extractSection = (name, localizedName) => {
7169
+ const extractInline = (name, localizedName) => {
7131
7170
  const names = localizedName && localizedName !== name ? [localizedName, name] : [name];
7132
7171
  for (const n of names) {
7133
- const rx = new RegExp(`##\\s*${n}\\s*\\n\\s*([^\\n\\r]+)`, "i");
7172
+ const escapedName = n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7173
+ const rx = new RegExp(`^${escapedName}:\\s*(.+)$`, "im");
7134
7174
  const m = content.match(rx);
7135
- if (m) return m[1].trim();
7175
+ if (m)
7176
+ return m[1].trim();
7136
7177
  }
7137
7178
  return null;
7138
7179
  };
7139
- const statusMatch = extractSection("Status", i18n.status);
7140
- const typeMatch = extractSection("Type", i18n.type);
7141
- const assigneeMatch = extractSection("Assignee", i18n.assignee);
7180
+ const statusMatch = extractInline("Status", i18n.status);
7181
+ const typeMatch = extractInline("Type", i18n.type);
7182
+ const assigneeMatch = extractInline("Assignee", i18n.assignee);
7142
7183
  let assignee;
7143
7184
  if (assigneeMatch) {
7144
7185
  const assigneeValue = assigneeMatch.trim();
7145
7186
  assignee = this.userRegistry.resolveUser(assigneeValue);
7146
7187
  if (!assignee) {
7147
- console.warn(
7148
- `[FS Provider] User "${assigneeValue}" not found in registry, creating temporary user`
7149
- );
7188
+ console.warn(`[FS Provider] User "${assigneeValue}" not found in registry, creating temporary user`);
7150
7189
  assignee = this.userRegistry.createTemporaryUser(assigneeValue);
7151
7190
  }
7152
7191
  }
@@ -7164,36 +7203,24 @@ var FileSystemTaskProvider = class {
7164
7203
  }
7165
7204
  async updateTask(task) {
7166
7205
  const currentContent = await fs.readFile(task.filePath, "utf-8");
7167
- const hasInlineMetadata = /^(Status|Type|Assignee):\s*.+$/im.test(
7168
- currentContent
7169
- );
7170
- if (hasInlineMetadata) {
7206
+ const hasSectionMetadata = /##\s*(Status|Type|Assignee)/i.test(currentContent);
7207
+ if (hasSectionMetadata) {
7171
7208
  const { fixTaskFile: fixTaskFile2 } = await Promise.resolve().then(() => (init_task_validator(), task_validator_exports));
7172
7209
  await fixTaskFile2(task.filePath);
7173
7210
  }
7174
7211
  const content = await fs.readFile(task.filePath, "utf-8");
7175
7212
  let updatedContent;
7176
- if (/##\s*Status/i.test(content)) {
7177
- updatedContent = content.replace(
7178
- /(##\s*Status\s*\n\s*)([^\n\r]*)/i,
7179
- `$1${task.status}`
7180
- );
7213
+ if (/^Status:\s*.+$/im.test(content)) {
7214
+ updatedContent = content.replace(/^Status:\s*.+$/im, `Status: ${task.status} `);
7181
7215
  } else {
7182
- updatedContent = content.replace(
7183
- /(^#.*\n)/,
7184
- `$1
7185
- ## Status
7186
- ${task.status}
7187
- `
7188
- );
7216
+ updatedContent = content.replace(/(^#.*\n)/, `$1Status: ${task.status}
7217
+ `);
7189
7218
  }
7190
7219
  await fs.writeFile(task.filePath, updatedContent, "utf-8");
7191
7220
  }
7192
7221
  async getAllTasks() {
7193
7222
  const files = await fs.readdir(this.tasksDirectory);
7194
- const taskFiles = files.filter(
7195
- (file) => file.startsWith("task-") && file.endsWith(".md")
7196
- );
7223
+ const taskFiles = files.filter((file) => file.startsWith("task-") && file.endsWith(".md"));
7197
7224
  const tasks = [];
7198
7225
  for (const file of taskFiles) {
7199
7226
  const filePath = path2.join(this.tasksDirectory, file);
@@ -7204,18 +7231,20 @@ ${task.status}
7204
7231
  const title = titleMatch ? titleMatch[1].trim() : "Untitled";
7205
7232
  const contentLocale = detectLocale(content);
7206
7233
  const i18n = getI18n(contentLocale);
7207
- const extractSection = (name, localizedName) => {
7234
+ const extractInline = (name, localizedName) => {
7208
7235
  const names = localizedName && localizedName !== name ? [localizedName, name] : [name];
7209
7236
  for (const n of names) {
7210
- const rx = new RegExp(`##\\s*${n}\\s*\\n\\s*([^\\n\\r]+)`, "i");
7237
+ const escapedName = n.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7238
+ const rx = new RegExp(`^${escapedName}:\\s*(.+)$`, "im");
7211
7239
  const m = content.match(rx);
7212
- if (m) return m[1].trim();
7240
+ if (m)
7241
+ return m[1].trim();
7213
7242
  }
7214
7243
  return null;
7215
7244
  };
7216
- const statusMatch = extractSection("Status", i18n.status);
7217
- const typeMatch = extractSection("Type", i18n.type);
7218
- const assigneeMatch = extractSection("Assignee", i18n.assignee);
7245
+ const statusMatch = extractInline("Status", i18n.status);
7246
+ const typeMatch = extractInline("Type", i18n.type);
7247
+ const assigneeMatch = extractInline("Assignee", i18n.assignee);
7219
7248
  let assignee;
7220
7249
  if (assigneeMatch) {
7221
7250
  const assigneeValue = assigneeMatch.trim();
@@ -7239,8 +7268,13 @@ ${task.status}
7239
7268
  return tasks;
7240
7269
  }
7241
7270
  async createTask(options) {
7242
- const i18n = getI18n(this.locale);
7243
7271
  const allTasks = await this.getAllTasks();
7272
+ let detectedLocale = this.locale;
7273
+ if (allTasks.length > 0) {
7274
+ const lastTask = allTasks[allTasks.length - 1];
7275
+ detectedLocale = detectLocale(lastTask.content);
7276
+ }
7277
+ const i18n = getI18n(detectedLocale);
7244
7278
  const taskNumbers = allTasks.map((task2) => {
7245
7279
  const match = task2.id.match(/^(\d+)$/);
7246
7280
  return match ? parseInt(match[1], 10) : 0;
@@ -7284,30 +7318,19 @@ ${task.status}
7284
7318
  const { id, title, type, description, assignee, i18n } = data;
7285
7319
  return `# \u{1F9E9} Task ${id} \u2014 ${title}
7286
7320
 
7287
- ## ${i18n.status}
7288
-
7289
- pending
7290
-
7291
- ## ${i18n.type}
7292
-
7293
- ${type}
7294
-
7295
- ## ${i18n.assignee}
7296
-
7297
- ${assignee}
7321
+ ${i18n.status}: pending
7322
+ ${i18n.type}: ${type}
7323
+ ${i18n.assignee}: ${assignee}
7298
7324
 
7299
7325
  ## ${i18n.description}
7300
-
7301
7326
  ${description || i18n.descriptionPlaceholder}
7302
7327
 
7303
7328
  ## ${i18n.tasks}
7304
-
7305
7329
  - [ ] Task 1
7306
7330
  - [ ] Task 2
7307
7331
  - [ ] Task 3
7308
7332
 
7309
7333
  ## ${i18n.notes}
7310
-
7311
7334
  ${i18n.notesPlaceholder}
7312
7335
  `;
7313
7336
  }
@@ -7335,6 +7358,9 @@ ${i18n.notesPlaceholder}
7335
7358
  }
7336
7359
  };
7337
7360
 
7361
+ // ../fs-task-provider/dist/index.js
7362
+ init_i18n();
7363
+
7338
7364
  // ../fs-task-provider/dist/user-registry.js
7339
7365
  init_esm_shims();
7340
7366
  import { promises as fs2 } from "fs";
@@ -7834,38 +7860,41 @@ init_esm_shims();
7834
7860
  // ../utils/dist/security.js
7835
7861
  init_esm_shims();
7836
7862
  import { z } from "zod";
7837
- var HostSchema = z.string().refine((host) => {
7838
- if (!host || host.length === 0)
7839
- return false;
7840
- if (host === "localhost")
7841
- return true;
7842
- const parts = host.split(".");
7843
- const allNumeric = parts.every((p) => /^\d+$/.test(p));
7844
- if (allNumeric) {
7845
- if (parts.length !== 4)
7846
- return false;
7847
- return parts.every((part) => {
7848
- const num = parseInt(part, 10);
7849
- return !isNaN(num) && num >= 0 && num <= 255 && part === num.toString();
7850
- });
7863
+ var HostSchema = z.string().refine(
7864
+ (host) => {
7865
+ if (!host || host.length === 0) return false;
7866
+ if (host === "localhost") return true;
7867
+ const parts = host.split(".");
7868
+ const allNumeric = parts.every((p) => /^\d+$/.test(p));
7869
+ if (allNumeric) {
7870
+ if (parts.length !== 4) return false;
7871
+ return parts.every((part) => {
7872
+ const num = parseInt(part, 10);
7873
+ return !isNaN(num) && num >= 0 && num <= 255 && part === num.toString();
7874
+ });
7875
+ }
7876
+ 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])?)*$/;
7877
+ return hostnameRegex.test(host);
7878
+ },
7879
+ {
7880
+ message: "Invalid host. Must be localhost, a valid IPv4 address, or hostname."
7851
7881
  }
7852
- 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])?)*$/;
7853
- return hostnameRegex.test(host);
7854
- }, {
7855
- message: "Invalid host. Must be localhost, a valid IPv4 address, or hostname."
7856
- });
7882
+ );
7857
7883
  var PortSchema = z.union([
7858
7884
  z.number().int().min(1).max(65535),
7859
7885
  z.string().regex(/^\d+$/).transform((val) => parseInt(val, 10)).pipe(z.number().int().min(1).max(65535))
7860
7886
  ]);
7861
- var WebSocketUrlSchema = z.string().refine((url) => {
7862
- try {
7863
- const parsed = new URL(url);
7864
- return parsed.protocol === "ws:" || parsed.protocol === "wss:";
7865
- } catch {
7866
- return false;
7867
- }
7868
- }, { message: "Invalid WebSocket URL. Must use ws:// or wss:// protocol." });
7887
+ var WebSocketUrlSchema = z.string().refine(
7888
+ (url) => {
7889
+ try {
7890
+ const parsed = new URL(url);
7891
+ return parsed.protocol === "ws:" || parsed.protocol === "wss:";
7892
+ } catch {
7893
+ return false;
7894
+ }
7895
+ },
7896
+ { message: "Invalid WebSocket URL. Must use ws:// or wss:// protocol." }
7897
+ );
7869
7898
  var TaskIdSchema = z.string().min(1).max(100).regex(/^[a-zA-Z0-9_-]+$/, {
7870
7899
  message: "Task ID must contain only alphanumeric characters, hyphens, and underscores."
7871
7900
  });
@@ -7873,23 +7902,25 @@ var UserIdSchema = z.string().min(1).max(100).regex(/^[a-zA-Z0-9._-]+$/, {
7873
7902
  message: "User ID must contain only alphanumeric characters, dots, hyphens, and underscores."
7874
7903
  });
7875
7904
  var EmailSchema = z.string().email().max(254);
7876
- var SafePathSchema = z.string().refine((filePath) => {
7877
- if (!filePath || filePath.length === 0)
7878
- return false;
7879
- const dangerousPatterns = [
7880
- /\.\./,
7881
- // Parent directory (..)
7882
- /~\//,
7883
- // Home directory
7884
- /^\//,
7885
- // Absolute path
7886
- /^[A-Za-z]:\\/
7887
- // Windows absolute path
7888
- ];
7889
- return !dangerousPatterns.some((pattern) => pattern.test(filePath));
7890
- }, {
7891
- message: "Invalid path. Must be a relative path without traversal patterns."
7892
- });
7905
+ var SafePathSchema = z.string().refine(
7906
+ (filePath) => {
7907
+ if (!filePath || filePath.length === 0) return false;
7908
+ const dangerousPatterns = [
7909
+ /\.\./,
7910
+ // Parent directory (..)
7911
+ /~\//,
7912
+ // Home directory
7913
+ /^\//,
7914
+ // Absolute path
7915
+ /^[A-Za-z]:\\/
7916
+ // Windows absolute path
7917
+ ];
7918
+ return !dangerousPatterns.some((pattern) => pattern.test(filePath));
7919
+ },
7920
+ {
7921
+ message: "Invalid path. Must be a relative path without traversal patterns."
7922
+ }
7923
+ );
7893
7924
  var DashboardOptionsSchema = z.object({
7894
7925
  host: HostSchema.optional(),
7895
7926
  port: PortSchema.optional(),
@@ -8666,8 +8697,15 @@ async function setupFileSystemProvider(cwd) {
8666
8697
  info(`${colors2.highlight("TASKS/")} directory already exists`);
8667
8698
  success("\u2713 Directory is ready to use");
8668
8699
  }
8669
- const sampleTaskFile = join(tasksDir, "task-001-setup-project.md");
8670
- if (!existsSync4(sampleTaskFile)) {
8700
+ const fs3 = __require("fs");
8701
+ const existingTask001 = fs3.readdirSync(tasksDir).find(
8702
+ (file) => file.startsWith("task-001-") && file.endsWith(".md")
8703
+ );
8704
+ if (existingTask001) {
8705
+ info(`Sample task already exists: ${colors2.highlight(existingTask001)}`);
8706
+ info("Skipping sample task creation (users already know the pattern)");
8707
+ } else {
8708
+ const sampleTaskFile = join(tasksDir, "task-001-setup-project.md");
8671
8709
  info("Creating sample task...");
8672
8710
  const sampleTask = `# Task 001 \u2014 Setup Project
8673
8711
 
@@ -8701,303 +8739,59 @@ You can edit or delete this file. Use \`taskin list\` to see all tasks.
8701
8739
 
8702
8740
  // src/commands/lint.ts
8703
8741
  init_esm_shims();
8704
- import { join as join3 } from "path";
8705
-
8706
- // src/lib/file-system-task-linter/index.ts
8707
- init_esm_shims();
8708
-
8709
- // src/lib/file-system-task-linter/file-system-task-linter.ts
8710
- init_esm_shims();
8711
8742
  import chalk4 from "chalk";
8712
- import { readdir, readFile as readFile2 } from "fs/promises";
8713
8743
  import { join as join2 } from "path";
8714
- var VALID_STATUSES = ["pending", "in-progress", "done", "blocked"];
8715
- var VALID_TYPES = ["feat", "fix", "chore", "docs", "refactor", "test"];
8716
- var FileSystemTaskLinter = class {
8717
- errors = [];
8718
- addError(file, message, severity = "error", line) {
8719
- this.errors.push({ file, message, severity, line });
8720
- }
8721
- /**
8722
- * Validate task file name format (FileSystem-specific)
8723
- */
8724
- validateFileName(fileName) {
8725
- const pattern = /^task-\d{2,3}-[a-z0-9-]+\.md$/;
8726
- if (!pattern.test(fileName)) {
8727
- return {
8728
- file: fileName,
8729
- message: "Invalid filename. Expected: task-NNN-kebab-case-title.md",
8730
- severity: "error"
8731
- };
8744
+ var lintCommand = defineCommand({
8745
+ name: "lint",
8746
+ description: "\u{1F50D} Validate task markdown files",
8747
+ options: [
8748
+ {
8749
+ flags: "-p, --path <directory>",
8750
+ description: "Path to TASKS directory",
8751
+ defaultValue: "TASKS"
8752
+ },
8753
+ {
8754
+ flags: "-f, --fix",
8755
+ description: "Automatically fix task file format issues"
8732
8756
  }
8733
- return null;
8757
+ ],
8758
+ handler: async (options) => {
8759
+ await executeLint(options);
8734
8760
  }
8735
- extractMetadata(content) {
8736
- const headerSection = content.split(/^##/m)[0];
8737
- const statusMatch = headerSection.match(/^Status:\s*(.+)$/im);
8738
- const typeMatch = headerSection.match(/^Type:\s*(.+)$/im);
8739
- const assigneeMatch = headerSection.match(/^Assignee:\s*(.+)$/im);
8740
- return {
8741
- status: statusMatch?.[1]?.trim().toLowerCase(),
8742
- type: typeMatch?.[1]?.trim().toLowerCase(),
8743
- assignee: assigneeMatch?.[1]?.trim()
8744
- };
8761
+ });
8762
+ async function executeLint(options) {
8763
+ const tasksDir = options.path || join2(process.cwd(), "TASKS");
8764
+ if (options.fix) {
8765
+ console.log(`\u{1F527} Fixing task files in: ${tasksDir}
8766
+ `);
8767
+ } else {
8768
+ console.log(`\u{1F4CB} Linting task files in: ${tasksDir}
8769
+ `);
8745
8770
  }
8746
- /**
8747
- * Validate task metadata (FileSystem-specific)
8748
- */
8749
- validateMetadata(metadata, filePath) {
8750
- const errors = [];
8751
- if (!metadata.status) {
8752
- errors.push({
8753
- file: filePath,
8754
- message: "Missing required metadata: Status",
8755
- severity: "error"
8756
- });
8757
- } else if (!VALID_STATUSES.includes(metadata.status)) {
8758
- errors.push({
8759
- file: filePath,
8760
- message: `Invalid status "${metadata.status}". Valid: ${VALID_STATUSES.join(", ")}`,
8761
- severity: "error"
8762
- });
8763
- }
8764
- if (!metadata.type) {
8765
- errors.push({
8766
- file: filePath,
8767
- message: "Missing required metadata: Type",
8768
- severity: "error"
8769
- });
8770
- } else if (!VALID_TYPES.includes(metadata.type)) {
8771
- errors.push({
8772
- file: filePath,
8773
- message: `Invalid type "${metadata.type}". Valid: ${VALID_TYPES.join(", ")}`,
8774
- severity: "error"
8775
- });
8776
- }
8777
- if (!metadata.assignee) {
8778
- errors.push({
8779
- file: filePath,
8780
- message: "Missing recommended metadata: Assignee",
8781
- severity: "warning"
8782
- });
8771
+ const userRegistry = new UserRegistry({
8772
+ taskinDir: join2(process.cwd(), ".taskin")
8773
+ });
8774
+ await userRegistry.load();
8775
+ const provider = new FileSystemTaskProvider(tasksDir, userRegistry);
8776
+ const result = await provider.lint(options.fix);
8777
+ if (result.valid) {
8778
+ console.log(chalk4.green(`\u2705 All task files are valid!
8779
+ `));
8780
+ } else {
8781
+ console.log(chalk4.red(`
8782
+ \u274C Found ${result.issues.length} issue(s):
8783
+ `));
8784
+ for (const issue of result.issues) {
8785
+ console.log(chalk4.yellow(` ${issue.file}: ${issue.message}`));
8783
8786
  }
8784
- return errors;
8787
+ console.log();
8785
8788
  }
8786
- validateContent(filename, content) {
8787
- const lines = content.split("\n");
8788
- if (!lines[0]?.trim().startsWith("# ")) {
8789
- this.addError(
8790
- filename,
8791
- "Task file must start with a level 1 heading (# Task NNN \u2014 Title)",
8792
- "error",
8793
- 1
8794
- );
8795
- }
8796
- const h1Pattern = /^#\s+(?:🧩\s+)?Task\s+\d{2,3}\s+[—-]\s+.+$/i;
8797
- if (lines[0] && !h1Pattern.test(lines[0])) {
8798
- this.addError(
8799
- filename,
8800
- 'H1 heading must follow format: "# Task NNN \u2014 Title"',
8801
- "error",
8802
- 1
8803
- );
8804
- }
8805
- const metadata = this.extractMetadata(content);
8806
- const metadataErrors = this.validateMetadata(metadata, filename);
8807
- metadataErrors.forEach((error2) => {
8808
- this.addError(error2.file, error2.message, error2.severity, error2.line);
8809
- });
8810
- const h2Sections = content.match(/^##\s+.+$/gm);
8811
- if (!h2Sections || h2Sections.length === 0) {
8812
- this.addError(
8813
- filename,
8814
- "Task should have at least one section (## heading)",
8815
- "warning"
8816
- );
8817
- }
8818
- const firstH2Index = content.indexOf("\n##");
8819
- if (firstH2Index > -1) {
8820
- const afterH2 = content.substring(firstH2Index);
8821
- if (afterH2.match(/^Status:/im) || afterH2.match(/^Type:/im) || afterH2.match(/^Assignee:/im)) {
8822
- this.addError(
8823
- filename,
8824
- "Metadata must be placed BEFORE the first ## section",
8825
- "error"
8826
- );
8827
- }
8828
- }
8829
- }
8830
- /**
8831
- * Lint a single task file (FileSystem-specific)
8832
- */
8833
- async lintFile(filePath) {
8834
- const errors = [];
8835
- const fileName = filePath.split("/").pop() || filePath;
8836
- const fileNameError = this.validateFileName(fileName);
8837
- if (fileNameError) {
8838
- errors.push(fileNameError);
8839
- }
8840
- try {
8841
- const content = await readFile2(filePath, "utf-8");
8842
- this.errors = [];
8843
- this.validateContent(fileName, content);
8844
- errors.push(...this.errors);
8845
- } catch (error2) {
8846
- errors.push({
8847
- file: fileName,
8848
- message: `Failed to read file: ${error2}`,
8849
- severity: "error"
8850
- });
8851
- }
8852
- return errors;
8853
- }
8854
- /**
8855
- * Lint all task files in the specified directory (FileSystem-specific)
8856
- */
8857
- async lintDirectory(tasksDir) {
8858
- this.errors = [];
8859
- try {
8860
- const files = await readdir(tasksDir);
8861
- const taskFiles = files.filter((f) => f.endsWith(".md"));
8862
- for (const file of taskFiles) {
8863
- const fileNameError = this.validateFileName(file);
8864
- if (fileNameError) {
8865
- this.addError(file, fileNameError.message, fileNameError.severity);
8866
- }
8867
- const content = await readFile2(join2(tasksDir, file), "utf-8");
8868
- this.validateContent(file, content);
8869
- }
8870
- const errors = this.errors.filter((e) => e.severity === "error");
8871
- const warnings = this.errors.filter((e) => e.severity === "warning");
8872
- return {
8873
- errors,
8874
- warnings,
8875
- filesChecked: taskFiles.length,
8876
- valid: errors.length === 0
8877
- };
8878
- } catch (error2) {
8879
- throw new Error(`Failed to lint tasks: ${error2}`);
8880
- }
8881
- }
8882
- static printResults(result) {
8883
- if (result.errors.length === 0 && result.warnings.length === 0) {
8884
- console.log(
8885
- chalk4.green(`\u2705 All ${result.filesChecked} task files are valid!
8886
- `)
8887
- );
8888
- return;
8889
- }
8890
- console.log(
8891
- chalk4.bold(
8892
- `
8893
- \u{1F4CA} Validation Results (${result.filesChecked} files checked):
8894
- `
8895
- )
8896
- );
8897
- const errorsByFile = /* @__PURE__ */ new Map();
8898
- [...result.errors, ...result.warnings].forEach((error2) => {
8899
- if (!errorsByFile.has(error2.file)) {
8900
- errorsByFile.set(error2.file, []);
8901
- }
8902
- errorsByFile.get(error2.file).push(error2);
8903
- });
8904
- for (const [file, fileErrors] of errorsByFile) {
8905
- console.log(chalk4.cyan(`
8906
- \u{1F4C4} ${file}`));
8907
- for (const error2 of fileErrors) {
8908
- const icon = error2.severity === "error" ? chalk4.red("\u274C") : chalk4.yellow("\u26A0\uFE0F");
8909
- const location = error2.line ? `:${error2.line}` : "";
8910
- console.log(` ${icon} ${error2.message}${location}`);
8911
- }
8912
- }
8913
- console.log("\n" + "\u2500".repeat(60));
8914
- console.log(
8915
- chalk4.bold(
8916
- `
8917
- \u{1F4CA} Summary: ${chalk4.red(result.errors.length + " error(s)")}, ${chalk4.yellow(result.warnings.length + " warning(s)")}
8918
- `
8919
- )
8920
- );
8921
- }
8922
- };
8923
-
8924
- // src/lib/file-system-task-linter/file-system-task-linter.mock.ts
8925
- init_esm_shims();
8926
- function createMockFileValidationError(overrides) {
8927
- return {
8928
- file: "task-001.md",
8929
- message: "Mock validation error",
8930
- severity: "error",
8931
- ...overrides
8932
- };
8933
- }
8934
- var MOCK_INVALID_LINT_RESULT = {
8935
- errors: [
8936
- createMockFileValidationError({
8937
- file: "task-001.md",
8938
- message: 'Invalid status: "invalid"'
8939
- }),
8940
- createMockFileValidationError({
8941
- file: "task-002.md",
8942
- message: 'Invalid type: "unknown"'
8943
- })
8944
- ],
8945
- filesChecked: 2,
8946
- valid: false,
8947
- warnings: []
8948
- };
8949
- var MOCK_WARNINGS_LINT_RESULT = {
8950
- errors: [],
8951
- filesChecked: 3,
8952
- valid: true,
8953
- warnings: [
8954
- createMockFileValidationError({
8955
- file: "task-003.md",
8956
- message: "Consider adding assignee",
8957
- severity: "warning"
8958
- })
8959
- ]
8960
- };
8961
- var MOCK_VALIDATION_ERRORS = [
8962
- createMockFileValidationError({
8963
- file: "task-001.md",
8964
- message: "Invalid status: must be one of [pending, in-progress, done, blocked]",
8965
- line: 5
8966
- }),
8967
- createMockFileValidationError({
8968
- file: "task-001.md",
8969
- message: "Invalid type: must be one of [feat, fix, chore, docs, refactor, test]",
8970
- line: 6
8971
- })
8972
- ];
8973
-
8974
- // src/lib/file-system-task-linter/file-system-task-linter.types.ts
8975
- init_esm_shims();
8976
-
8977
- // src/commands/lint.ts
8978
- var lintCommand = defineCommand({
8979
- name: "lint",
8980
- description: "\u{1F50D} Validate task markdown files",
8981
- options: [
8982
- {
8983
- flags: "-p, --path <directory>",
8984
- description: "Path to TASKS directory",
8985
- defaultValue: "TASKS"
8986
- }
8987
- ],
8988
- handler: async (options) => {
8989
- await executeLint(options);
8990
- }
8991
- });
8992
- async function executeLint(options) {
8993
- const tasksDir = options.path || join3(process.cwd(), "TASKS");
8994
- console.log(`\u{1F4CB} Linting task files in: ${tasksDir}
8995
- `);
8996
- const linter = new FileSystemTaskLinter();
8997
- const result = await linter.lintDirectory(tasksDir);
8998
- FileSystemTaskLinter.printResults(result);
8999
- if (!result.valid) {
9000
- process.exit(1);
8789
+ if (!result.valid && !options.fix) {
8790
+ console.log(
8791
+ chalk4.blue(`\u{1F4A1} Run with --fix to automatically fix format issues
8792
+ `)
8793
+ );
8794
+ process.exit(1);
9001
8795
  }
9002
8796
  }
9003
8797
 
@@ -11800,13 +11594,13 @@ function showCustomHelp() {
11800
11594
  // src/version.ts
11801
11595
  init_esm_shims();
11802
11596
  import { readFileSync } from "fs";
11803
- import { dirname, join as join4 } from "path";
11597
+ import { dirname, join as join3 } from "path";
11804
11598
  import { fileURLToPath as fileURLToPath3 } from "url";
11805
11599
  var __filename3 = fileURLToPath3(import.meta.url);
11806
11600
  var __dirname3 = dirname(__filename3);
11807
11601
  function getVersion() {
11808
11602
  try {
11809
- const packageJsonPath = join4(__dirname3, "../package.json");
11603
+ const packageJsonPath = join3(__dirname3, "../package.json");
11810
11604
  const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
11811
11605
  return packageJson.version;
11812
11606
  } catch {
@@ -11818,6 +11612,277 @@ function getVersion() {
11818
11612
  init_esm_shims();
11819
11613
  import { dirname as dirname2, join as join5 } from "path";
11820
11614
 
11615
+ // src/lib/file-system-task-linter/index.ts
11616
+ init_esm_shims();
11617
+
11618
+ // src/lib/file-system-task-linter/file-system-task-linter.ts
11619
+ init_esm_shims();
11620
+ import chalk6 from "chalk";
11621
+ import { readdir, readFile as readFile2 } from "fs/promises";
11622
+ import { join as join4 } from "path";
11623
+ var VALID_STATUSES = ["pending", "in-progress", "done", "blocked"];
11624
+ var VALID_TYPES = ["feat", "fix", "chore", "docs", "refactor", "test"];
11625
+ var FileSystemTaskLinter = class {
11626
+ errors = [];
11627
+ addError(file, message, severity = "error", line) {
11628
+ this.errors.push({ file, message, severity, line });
11629
+ }
11630
+ /**
11631
+ * Validate task file name format (FileSystem-specific)
11632
+ */
11633
+ validateFileName(fileName) {
11634
+ const pattern = /^task-\d{2,3}-[a-z0-9-]+\.md$/;
11635
+ if (!pattern.test(fileName)) {
11636
+ return {
11637
+ file: fileName,
11638
+ message: "Invalid filename. Expected: task-NNN-kebab-case-title.md",
11639
+ severity: "error"
11640
+ };
11641
+ }
11642
+ return null;
11643
+ }
11644
+ extractMetadata(content) {
11645
+ const headerSection = content.split(/^##/m)[0];
11646
+ const statusMatch = headerSection.match(/^Status:\s*(.+)$/im);
11647
+ const typeMatch = headerSection.match(/^Type:\s*(.+)$/im);
11648
+ const assigneeMatch = headerSection.match(/^Assignee:\s*(.+)$/im);
11649
+ return {
11650
+ status: statusMatch?.[1]?.trim().toLowerCase(),
11651
+ type: typeMatch?.[1]?.trim().toLowerCase(),
11652
+ assignee: assigneeMatch?.[1]?.trim()
11653
+ };
11654
+ }
11655
+ /**
11656
+ * Validate task metadata (FileSystem-specific)
11657
+ */
11658
+ validateMetadata(metadata, filePath) {
11659
+ const errors = [];
11660
+ if (!metadata.status) {
11661
+ errors.push({
11662
+ file: filePath,
11663
+ message: "Missing required metadata: Status",
11664
+ severity: "error"
11665
+ });
11666
+ } else if (!VALID_STATUSES.includes(metadata.status)) {
11667
+ errors.push({
11668
+ file: filePath,
11669
+ message: `Invalid status "${metadata.status}". Valid: ${VALID_STATUSES.join(", ")}`,
11670
+ severity: "error"
11671
+ });
11672
+ }
11673
+ if (!metadata.type) {
11674
+ errors.push({
11675
+ file: filePath,
11676
+ message: "Missing required metadata: Type",
11677
+ severity: "error"
11678
+ });
11679
+ } else if (!VALID_TYPES.includes(metadata.type)) {
11680
+ errors.push({
11681
+ file: filePath,
11682
+ message: `Invalid type "${metadata.type}". Valid: ${VALID_TYPES.join(", ")}`,
11683
+ severity: "error"
11684
+ });
11685
+ }
11686
+ if (!metadata.assignee) {
11687
+ errors.push({
11688
+ file: filePath,
11689
+ message: "Missing recommended metadata: Assignee",
11690
+ severity: "warning"
11691
+ });
11692
+ }
11693
+ return errors;
11694
+ }
11695
+ validateContent(filename, content) {
11696
+ const lines = content.split("\n");
11697
+ if (!lines[0]?.trim().startsWith("# ")) {
11698
+ this.addError(
11699
+ filename,
11700
+ "Task file must start with a level 1 heading (# Task NNN \u2014 Title)",
11701
+ "error",
11702
+ 1
11703
+ );
11704
+ }
11705
+ const h1Pattern = /^#\s+(?:🧩\s+)?Task\s+\d{2,3}\s+[—-]\s+.+$/i;
11706
+ if (lines[0] && !h1Pattern.test(lines[0])) {
11707
+ this.addError(
11708
+ filename,
11709
+ 'H1 heading must follow format: "# Task NNN \u2014 Title"',
11710
+ "error",
11711
+ 1
11712
+ );
11713
+ }
11714
+ const metadata = this.extractMetadata(content);
11715
+ const metadataErrors = this.validateMetadata(metadata, filename);
11716
+ metadataErrors.forEach((error2) => {
11717
+ this.addError(error2.file, error2.message, error2.severity, error2.line);
11718
+ });
11719
+ const h2Sections = content.match(/^##\s+.+$/gm);
11720
+ if (!h2Sections || h2Sections.length === 0) {
11721
+ this.addError(
11722
+ filename,
11723
+ "Task should have at least one section (## heading)",
11724
+ "warning"
11725
+ );
11726
+ }
11727
+ const firstH2Index = content.indexOf("\n##");
11728
+ if (firstH2Index > -1) {
11729
+ const afterH2 = content.substring(firstH2Index);
11730
+ if (afterH2.match(/^Status:/im) || afterH2.match(/^Type:/im) || afterH2.match(/^Assignee:/im)) {
11731
+ this.addError(
11732
+ filename,
11733
+ "Metadata must be placed BEFORE the first ## section",
11734
+ "error"
11735
+ );
11736
+ }
11737
+ }
11738
+ }
11739
+ /**
11740
+ * Lint a single task file (FileSystem-specific)
11741
+ */
11742
+ async lintFile(filePath) {
11743
+ const errors = [];
11744
+ const fileName = filePath.split("/").pop() || filePath;
11745
+ const fileNameError = this.validateFileName(fileName);
11746
+ if (fileNameError) {
11747
+ errors.push(fileNameError);
11748
+ }
11749
+ try {
11750
+ const content = await readFile2(filePath, "utf-8");
11751
+ this.errors = [];
11752
+ this.validateContent(fileName, content);
11753
+ errors.push(...this.errors);
11754
+ } catch (error2) {
11755
+ errors.push({
11756
+ file: fileName,
11757
+ message: `Failed to read file: ${error2}`,
11758
+ severity: "error"
11759
+ });
11760
+ }
11761
+ return errors;
11762
+ }
11763
+ /**
11764
+ * Lint all task files in the specified directory (FileSystem-specific)
11765
+ */
11766
+ async lintDirectory(tasksDir) {
11767
+ this.errors = [];
11768
+ try {
11769
+ const files = await readdir(tasksDir);
11770
+ const taskFiles = files.filter((f) => f.endsWith(".md"));
11771
+ for (const file of taskFiles) {
11772
+ const fileNameError = this.validateFileName(file);
11773
+ if (fileNameError) {
11774
+ this.addError(file, fileNameError.message, fileNameError.severity);
11775
+ }
11776
+ const content = await readFile2(join4(tasksDir, file), "utf-8");
11777
+ this.validateContent(file, content);
11778
+ }
11779
+ const errors = this.errors.filter((e) => e.severity === "error");
11780
+ const warnings = this.errors.filter((e) => e.severity === "warning");
11781
+ return {
11782
+ errors,
11783
+ warnings,
11784
+ filesChecked: taskFiles.length,
11785
+ valid: errors.length === 0
11786
+ };
11787
+ } catch (error2) {
11788
+ throw new Error(`Failed to lint tasks: ${error2}`);
11789
+ }
11790
+ }
11791
+ static printResults(result) {
11792
+ if (result.errors.length === 0 && result.warnings.length === 0) {
11793
+ console.log(
11794
+ chalk6.green(`\u2705 All ${result.filesChecked} task files are valid!
11795
+ `)
11796
+ );
11797
+ return;
11798
+ }
11799
+ console.log(
11800
+ chalk6.bold(
11801
+ `
11802
+ \u{1F4CA} Validation Results (${result.filesChecked} files checked):
11803
+ `
11804
+ )
11805
+ );
11806
+ const errorsByFile = /* @__PURE__ */ new Map();
11807
+ [...result.errors, ...result.warnings].forEach((error2) => {
11808
+ if (!errorsByFile.has(error2.file)) {
11809
+ errorsByFile.set(error2.file, []);
11810
+ }
11811
+ errorsByFile.get(error2.file).push(error2);
11812
+ });
11813
+ for (const [file, fileErrors] of errorsByFile) {
11814
+ console.log(chalk6.cyan(`
11815
+ \u{1F4C4} ${file}`));
11816
+ for (const error2 of fileErrors) {
11817
+ const icon = error2.severity === "error" ? chalk6.red("\u274C") : chalk6.yellow("\u26A0\uFE0F");
11818
+ const location = error2.line ? `:${error2.line}` : "";
11819
+ console.log(` ${icon} ${error2.message}${location}`);
11820
+ }
11821
+ }
11822
+ console.log("\n" + "\u2500".repeat(60));
11823
+ console.log(
11824
+ chalk6.bold(
11825
+ `
11826
+ \u{1F4CA} Summary: ${chalk6.red(result.errors.length + " error(s)")}, ${chalk6.yellow(result.warnings.length + " warning(s)")}
11827
+ `
11828
+ )
11829
+ );
11830
+ }
11831
+ };
11832
+
11833
+ // src/lib/file-system-task-linter/file-system-task-linter.mock.ts
11834
+ init_esm_shims();
11835
+ function createMockFileValidationError(overrides) {
11836
+ return {
11837
+ file: "task-001.md",
11838
+ message: "Mock validation error",
11839
+ severity: "error",
11840
+ ...overrides
11841
+ };
11842
+ }
11843
+ var MOCK_INVALID_LINT_RESULT = {
11844
+ errors: [
11845
+ createMockFileValidationError({
11846
+ file: "task-001.md",
11847
+ message: 'Invalid status: "invalid"'
11848
+ }),
11849
+ createMockFileValidationError({
11850
+ file: "task-002.md",
11851
+ message: 'Invalid type: "unknown"'
11852
+ })
11853
+ ],
11854
+ filesChecked: 2,
11855
+ valid: false,
11856
+ warnings: []
11857
+ };
11858
+ var MOCK_WARNINGS_LINT_RESULT = {
11859
+ errors: [],
11860
+ filesChecked: 3,
11861
+ valid: true,
11862
+ warnings: [
11863
+ createMockFileValidationError({
11864
+ file: "task-003.md",
11865
+ message: "Consider adding assignee",
11866
+ severity: "warning"
11867
+ })
11868
+ ]
11869
+ };
11870
+ var MOCK_VALIDATION_ERRORS = [
11871
+ createMockFileValidationError({
11872
+ file: "task-001.md",
11873
+ message: "Invalid status: must be one of [pending, in-progress, done, blocked]",
11874
+ line: 5
11875
+ }),
11876
+ createMockFileValidationError({
11877
+ file: "task-001.md",
11878
+ message: "Invalid type: must be one of [feat, fix, chore, docs, refactor, test]",
11879
+ line: 6
11880
+ })
11881
+ ];
11882
+
11883
+ // src/lib/file-system-task-linter/file-system-task-linter.types.ts
11884
+ init_esm_shims();
11885
+
11821
11886
  // src/taskin.ts
11822
11887
  init_esm_shims();
11823
11888
  var Taskin = class {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskin",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "Task management system integrated with Git workflows",
5
5
  "motivation": "Provide a CLI tool for task management integrated with Git workflows",
6
6
  "solve": "Simplifies task tracking and management directly from the command line",
@@ -86,11 +86,11 @@
86
86
  "typescript": "^5.9.3",
87
87
  "vitest": "^1.6.1",
88
88
  "@opentask/taskin-fs-provider": "1.0.5",
89
+ "@opentask/taskin-task-server-mcp": "0.1.0",
89
90
  "@opentask/taskin-task-manager": "1.0.5",
91
+ "@opentask/taskin-types": "1.0.5",
90
92
  "@opentask/taskin-task-server-ws": "0.1.0",
91
- "@opentask/taskin-utils": "1.0.5",
92
- "@opentask/taskin-task-server-mcp": "0.1.0",
93
- "@opentask/taskin-types": "1.0.5"
93
+ "@opentask/taskin-utils": "1.0.5"
94
94
  },
95
95
  "scripts": {
96
96
  "build": "pnpm run build:dashboard && tsup",