@tomkapa/tayto 0.6.0 → 0.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomkapa/tayto",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "CLI tool for task management - for solo devs and AI agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,199 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/logging/logger.ts
4
- import { appendFileSync, readdirSync, unlinkSync } from "fs";
5
- import { join } from "path";
6
- import { trace, SpanStatusCode } from "@opentelemetry/api";
7
- var tracer = trace.getTracer("task");
8
- var LOG_RETENTION_DAYS = 7;
9
- function formatTimestamp() {
10
- return (/* @__PURE__ */ new Date()).toISOString();
11
- }
12
- function formatAttrs(attrs) {
13
- if (!attrs || Object.keys(attrs).length === 0) return "";
14
- return " " + JSON.stringify(attrs);
15
- }
16
- var Logger = class {
17
- logFilePath = null;
18
- init(logDir) {
19
- const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
20
- this.logFilePath = join(logDir, `task-${date}.log`);
21
- this.pruneOldLogs(logDir);
22
- }
23
- info(message, attrs) {
24
- this.write("INFO", message, attrs);
25
- const span = trace.getActiveSpan();
26
- if (span) {
27
- span.addEvent(message, attrs);
28
- }
29
- }
30
- warn(message, attrs) {
31
- this.write("WARN", message, attrs);
32
- const span = trace.getActiveSpan();
33
- if (span) {
34
- span.addEvent(`WARN: ${message}`, attrs);
35
- }
36
- }
37
- error(message, error, attrs) {
38
- const errorDetail = error instanceof Error ? ` | ${error.stack ?? error.message}` : "";
39
- this.write("ERROR", `${message}${errorDetail}`, attrs);
40
- const span = trace.getActiveSpan();
41
- if (span) {
42
- span.addEvent(`ERROR: ${message}`, attrs);
43
- if (error instanceof Error) {
44
- span.recordException(error);
45
- }
46
- span.setStatus({ code: SpanStatusCode.ERROR, message });
47
- }
48
- }
49
- startSpan(name, fn) {
50
- return tracer.startActiveSpan(name, (span) => {
51
- try {
52
- const result = fn(span);
53
- span.end();
54
- return result;
55
- } catch (e) {
56
- if (e instanceof Error) {
57
- span.recordException(e);
58
- }
59
- span.setStatus({ code: SpanStatusCode.ERROR });
60
- span.end();
61
- throw e;
62
- }
63
- });
64
- }
65
- write(level, message, attrs) {
66
- if (!this.logFilePath) return;
67
- const line = `${formatTimestamp()} [${level}] ${message}${formatAttrs(attrs)}
68
- `;
69
- try {
70
- appendFileSync(this.logFilePath, line);
71
- } catch {
72
- }
73
- }
74
- pruneOldLogs(logDir) {
75
- try {
76
- const cutoff = Date.now() - LOG_RETENTION_DAYS * 24 * 60 * 60 * 1e3;
77
- const files = readdirSync(logDir).filter((f) => f.startsWith("task-") && f.endsWith(".log"));
78
- for (const file of files) {
79
- const dateStr = file.slice("task-".length, -".log".length);
80
- const fileDate = new Date(dateStr).getTime();
81
- if (!isNaN(fileDate) && fileDate < cutoff) {
82
- unlinkSync(join(logDir, file));
83
- }
84
- }
85
- } catch {
86
- }
87
- }
88
- };
89
- var logger = new Logger();
90
-
91
- // src/types/enums.ts
92
- var TaskStatus = {
93
- Backlog: "backlog",
94
- Todo: "todo",
95
- InProgress: "in-progress",
96
- Review: "review",
97
- Done: "done",
98
- Cancelled: "cancelled"
99
- };
100
- var TaskType = {
101
- Epic: "epic",
102
- Story: "story",
103
- TechDebt: "tech-debt",
104
- Bug: "bug"
105
- };
106
- var TaskLevel = {
107
- Epic: 1,
108
- Work: 2
109
- };
110
- var TYPE_TO_LEVEL = {
111
- [TaskType.Epic]: TaskLevel.Epic,
112
- [TaskType.Story]: TaskLevel.Work,
113
- [TaskType.TechDebt]: TaskLevel.Work,
114
- [TaskType.Bug]: TaskLevel.Work
115
- };
116
- function getTaskLevel(type) {
117
- return TYPE_TO_LEVEL[type] ?? TaskLevel.Work;
118
- }
119
- var WORK_TYPES = /* @__PURE__ */ new Set([
120
- TaskType.Story,
121
- TaskType.TechDebt,
122
- TaskType.Bug
123
- ]);
124
- var DependencyType = {
125
- Blocks: "blocks",
126
- RelatesTo: "relates-to",
127
- Duplicates: "duplicates"
128
- };
129
- var UIDependencyType = {
130
- ...DependencyType,
131
- BlockedBy: "blocked-by"
132
- };
133
- var RANK_GAP = 1e3;
134
- function midpoint(a, b) {
135
- const m = (a + b) / 2;
136
- return m > a && m < b ? m : null;
137
- }
138
- var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
139
- TaskStatus.Done,
140
- TaskStatus.Cancelled
141
- ]);
142
- function isTerminalStatus(status) {
143
- return TERMINAL_STATUSES.has(status);
144
- }
145
-
146
- // src/utils/git.ts
147
- import { spawnSync } from "child_process";
148
-
149
- // src/types/common.ts
150
- function ok(value) {
151
- return { ok: true, value };
152
- }
153
- function err(error) {
154
- return { ok: false, error };
155
- }
156
-
157
- // src/utils/git.ts
158
- function detectGitRemote(cwd) {
159
- try {
160
- const result = spawnSync("git", ["remote", "get-url", "origin"], {
161
- cwd: cwd ?? process.cwd(),
162
- encoding: "utf-8",
163
- timeout: 5e3
164
- });
165
- if (result.status !== 0 || result.error) {
166
- logger.info("detectGitRemote: no git remote found (non-zero exit or error)");
167
- return ok(null);
168
- }
169
- const remote = result.stdout.trim();
170
- if (!remote) {
171
- logger.info("detectGitRemote: empty stdout from git remote get-url");
172
- return ok(null);
173
- }
174
- logger.info(`detectGitRemote: found remote=${remote}`);
175
- return ok(remote);
176
- } catch (e) {
177
- logger.info(`detectGitRemote: exception during git detection: ${String(e)}`);
178
- return ok(null);
179
- }
180
- }
181
-
182
- export {
183
- logger,
184
- ok,
185
- err,
186
- TaskStatus,
187
- TaskType,
188
- TaskLevel,
189
- getTaskLevel,
190
- WORK_TYPES,
191
- DependencyType,
192
- UIDependencyType,
193
- RANK_GAP,
194
- midpoint,
195
- TERMINAL_STATUSES,
196
- isTerminalStatus,
197
- detectGitRemote
198
- };
199
- //# sourceMappingURL=chunk-74Q55TOV.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/logging/logger.ts","../src/types/enums.ts","../src/utils/git.ts","../src/types/common.ts"],"sourcesContent":["import { appendFileSync, readdirSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { trace, type Span, SpanStatusCode } from '@opentelemetry/api';\n\nconst tracer = trace.getTracer('task');\nconst LOG_RETENTION_DAYS = 7;\n\nexport interface LogAttributes {\n [key: string]: string | number | boolean;\n}\n\ntype LogLevel = 'INFO' | 'WARN' | 'ERROR';\n\nfunction formatTimestamp(): string {\n return new Date().toISOString();\n}\n\nfunction formatAttrs(attrs?: LogAttributes): string {\n if (!attrs || Object.keys(attrs).length === 0) return '';\n return ' ' + JSON.stringify(attrs);\n}\n\nclass Logger {\n private logFilePath: string | null = null;\n\n init(logDir: string): void {\n const date = new Date().toISOString().slice(0, 10); // YYYY-MM-DD\n this.logFilePath = join(logDir, `task-${date}.log`);\n this.pruneOldLogs(logDir);\n }\n\n info(message: string, attrs?: LogAttributes): void {\n this.write('INFO', message, attrs);\n const span = trace.getActiveSpan();\n if (span) {\n span.addEvent(message, attrs);\n }\n }\n\n warn(message: string, attrs?: LogAttributes): void {\n this.write('WARN', message, attrs);\n const span = trace.getActiveSpan();\n if (span) {\n span.addEvent(`WARN: ${message}`, attrs);\n }\n }\n\n error(message: string, error?: unknown, attrs?: LogAttributes): void {\n const errorDetail = error instanceof Error ? ` | ${error.stack ?? error.message}` : '';\n this.write('ERROR', `${message}${errorDetail}`, attrs);\n const span = trace.getActiveSpan();\n if (span) {\n span.addEvent(`ERROR: ${message}`, attrs);\n if (error instanceof Error) {\n span.recordException(error);\n }\n span.setStatus({ code: SpanStatusCode.ERROR, message });\n }\n }\n\n startSpan<T>(name: string, fn: (span: Span) => T): T {\n return tracer.startActiveSpan(name, (span) => {\n try {\n const result = fn(span);\n span.end();\n return result;\n } catch (e) {\n if (e instanceof Error) {\n span.recordException(e);\n }\n span.setStatus({ code: SpanStatusCode.ERROR });\n span.end();\n throw e;\n }\n });\n }\n\n private write(level: LogLevel, message: string, attrs?: LogAttributes): void {\n if (!this.logFilePath) return;\n const line = `${formatTimestamp()} [${level}] ${message}${formatAttrs(attrs)}\\n`;\n try {\n appendFileSync(this.logFilePath, line);\n } catch {\n // Swallowing here is intentional: logging must never crash the app.\n // If the log file is unwritable, the OTel span still captures the event.\n }\n }\n\n private pruneOldLogs(logDir: string): void {\n try {\n const cutoff = Date.now() - LOG_RETENTION_DAYS * 24 * 60 * 60 * 1000;\n const files = readdirSync(logDir).filter((f) => f.startsWith('task-') && f.endsWith('.log'));\n for (const file of files) {\n const dateStr = file.slice('task-'.length, -'.log'.length);\n const fileDate = new Date(dateStr).getTime();\n if (!isNaN(fileDate) && fileDate < cutoff) {\n unlinkSync(join(logDir, file));\n }\n }\n } catch {\n // Best-effort cleanup — don't crash if pruning fails\n }\n }\n}\n\nexport const logger = new Logger();\n","export const TaskStatus = {\n Backlog: 'backlog',\n Todo: 'todo',\n InProgress: 'in-progress',\n Review: 'review',\n Done: 'done',\n Cancelled: 'cancelled',\n} as const;\nexport type TaskStatus = (typeof TaskStatus)[keyof typeof TaskStatus];\n\nexport const TaskType = {\n Epic: 'epic',\n Story: 'story',\n TechDebt: 'tech-debt',\n Bug: 'bug',\n} as const;\nexport type TaskType = (typeof TaskType)[keyof typeof TaskType];\n\n/**\n * Task level derived from type.\n * Level 1: epics (grouping/planning layer)\n * Level 2: stories, tech-debt, bugs (execution layer)\n */\nexport const TaskLevel = {\n Epic: 1,\n Work: 2,\n} as const;\nexport type TaskLevel = (typeof TaskLevel)[keyof typeof TaskLevel];\n\nconst TYPE_TO_LEVEL: Record<string, TaskLevel> = {\n [TaskType.Epic]: TaskLevel.Epic,\n [TaskType.Story]: TaskLevel.Work,\n [TaskType.TechDebt]: TaskLevel.Work,\n [TaskType.Bug]: TaskLevel.Work,\n};\n\nexport function getTaskLevel(type: string): TaskLevel {\n return TYPE_TO_LEVEL[type] ?? TaskLevel.Work;\n}\n\n/** Types that belong to the work (level 2) execution layer. */\nexport const WORK_TYPES: ReadonlySet<string> = new Set([\n TaskType.Story,\n TaskType.TechDebt,\n TaskType.Bug,\n]);\n\n/** Types stored in the database. */\nexport const DependencyType = {\n Blocks: 'blocks',\n RelatesTo: 'relates-to',\n Duplicates: 'duplicates',\n} as const;\nexport type DependencyType = (typeof DependencyType)[keyof typeof DependencyType];\n\n/**\n * UI-level dependency types — includes BlockedBy which is a reverse-Blocks\n * relationship resolved before persisting to the database.\n */\nexport const UIDependencyType = {\n ...DependencyType,\n BlockedBy: 'blocked-by',\n} as const;\nexport type UIDependencyType = (typeof UIDependencyType)[keyof typeof UIDependencyType];\n\n/** Gap between consecutive rank values, used for insertion between neighbors. */\nexport const RANK_GAP = 1000.0;\n\n/**\n * Collapse-safe midpoint between two rank values. Returns `null` when\n * IEEE 754 double precision cannot represent a strictly-between value —\n * callers use this as a signal to rebalance the rank grid and retry.\n * Returning a number unconditionally would silently collide with an\n * endpoint and corrupt the task ordering.\n */\nexport function midpoint(a: number, b: number): number | null {\n const m = (a + b) / 2;\n return m > a && m < b ? m : null;\n}\n\n/** Statuses that represent terminal/completed task states. */\nexport const TERMINAL_STATUSES: ReadonlySet<string> = new Set([\n TaskStatus.Done,\n TaskStatus.Cancelled,\n]);\n\nexport function isTerminalStatus(status: string): boolean {\n return TERMINAL_STATUSES.has(status);\n}\n","import { spawnSync } from 'node:child_process';\nimport type { Result } from '../types/common.js';\nimport { ok } from '../types/common.js';\nimport { logger } from '../logging/logger.js';\n\n/**\n * Detect the git origin remote URL from the given directory.\n * Returns ok(url) if found, ok(null) if no git repo or no origin remote.\n * Never returns err() — all git failures are silent fallbacks.\n */\nexport function detectGitRemote(cwd?: string): Result<string | null> {\n try {\n const result = spawnSync('git', ['remote', 'get-url', 'origin'], {\n cwd: cwd ?? process.cwd(),\n encoding: 'utf-8',\n timeout: 5000,\n });\n\n if (result.status !== 0 || result.error) {\n logger.info('detectGitRemote: no git remote found (non-zero exit or error)');\n return ok(null);\n }\n\n const remote = result.stdout.trim();\n if (!remote) {\n logger.info('detectGitRemote: empty stdout from git remote get-url');\n return ok(null);\n }\n\n logger.info(`detectGitRemote: found remote=${remote}`);\n return ok(remote);\n } catch (e: unknown) {\n logger.info(`detectGitRemote: exception during git detection: ${String(e)}`);\n return ok(null);\n }\n}\n","import type { AppError } from '../errors/app-error.js';\n\nexport type Result<T, E = AppError> = { ok: true; value: T } | { ok: false; error: E };\n\nexport function ok<T>(value: T): Result<T, never> {\n return { ok: true, value };\n}\n\nexport function err<E>(error: E): Result<never, E> {\n return { ok: false, error };\n}\n\nexport interface CLIOutput<T> {\n ok: boolean;\n data?: T;\n error?: { code: string; message: string };\n}\n"],"mappings":"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","names":[]}