@metyatech/task-tracker 0.1.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/LICENSE +21 -0
- package/README.md +109 -0
- package/dist/cli.js +315 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 metyatech
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# task-tracker
|
|
2
|
+
|
|
3
|
+
Persistent task lifecycle tracker for AI agent sessions. Stores tasks in `.tasks.jsonl` at the root of your git repository — making task state version-controllable and syncable across PCs via git.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
When using AI agents (Claude, Codex, Gemini, Copilot), tasks from conversations get forgotten across sessions. Agents are forcefully terminated and cannot persist state on exit. `task-tracker` lets agents record tasks to disk immediately when they arise, and check for stale work at session start.
|
|
8
|
+
|
|
9
|
+
Tasks are stored per-repository in `.tasks.jsonl` alongside your code. Commit the file to track task state in version control; add it to `.gitignore` to keep it local.
|
|
10
|
+
|
|
11
|
+
## Compatibility
|
|
12
|
+
|
|
13
|
+
- Node.js 18+
|
|
14
|
+
- macOS, Linux, Windows
|
|
15
|
+
- Requires git (tasks are scoped to the current git repository)
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g @metyatech/task-tracker
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or link locally for development:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm link
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Storage
|
|
30
|
+
|
|
31
|
+
Tasks are stored in `<git-repo-root>/.tasks.jsonl` (JSONL format, one JSON object per line). All commands must be run from within a git repository. The file is created automatically on first `add`.
|
|
32
|
+
|
|
33
|
+
Each task: `{ id, description, stage, createdAt, updatedAt }`
|
|
34
|
+
|
|
35
|
+
To sync tasks across PCs, commit `.tasks.jsonl` and push/pull like any other file.
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
### Add a task
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
task-tracker add "Implement feature X"
|
|
43
|
+
task-tracker add "Deploy to staging" --stage in-progress
|
|
44
|
+
task-tracker add "Review PR #42" --json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### List tasks
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
task-tracker list # active tasks only
|
|
51
|
+
task-tracker list --all # include done tasks
|
|
52
|
+
task-tracker list --stage in-progress
|
|
53
|
+
task-tracker list --json
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Update a task
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
task-tracker update <id> --stage implemented
|
|
60
|
+
task-tracker update <id> --description "Updated description"
|
|
61
|
+
task-tracker update <id> --stage verified --json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Mark done
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
task-tracker done <id>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Remove a task
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
task-tracker remove <id>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Check for stale work
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
task-tracker check
|
|
80
|
+
task-tracker check --json
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The `check` command:
|
|
84
|
+
|
|
85
|
+
1. Lists all active (non-done) tasks from this repo's `.tasks.jsonl`
|
|
86
|
+
2. Runs `git status --porcelain` on the repo root
|
|
87
|
+
3. Runs `git log @{u}..HEAD --oneline` to show unpushed commits
|
|
88
|
+
4. Outputs a combined report
|
|
89
|
+
|
|
90
|
+
## Lifecycle Stages
|
|
91
|
+
|
|
92
|
+
`pending` → `in-progress` → `implemented` → `verified` → `committed` → `pushed` → `pr-created` → `merged` → `released` → `published` → `done`
|
|
93
|
+
|
|
94
|
+
Stages can be set in any order.
|
|
95
|
+
|
|
96
|
+
## Dev Commands
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npm run build # Build with tsup
|
|
100
|
+
npm run test # Run tests with vitest
|
|
101
|
+
npm run lint # ESLint
|
|
102
|
+
npm run format # Prettier (write)
|
|
103
|
+
npm run format:check # Prettier (check)
|
|
104
|
+
npm run verify # format:check + lint + build + test
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT © metyatech
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
7
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
8
|
+
|
|
9
|
+
// src/storage.ts
|
|
10
|
+
import { readFileSync, writeFileSync, existsSync, appendFileSync, mkdirSync } from "fs";
|
|
11
|
+
import { dirname, join } from "path";
|
|
12
|
+
|
|
13
|
+
// src/git.ts
|
|
14
|
+
import { execSync } from "child_process";
|
|
15
|
+
function tryExec(cmd, cwd) {
|
|
16
|
+
try {
|
|
17
|
+
return execSync(cmd, { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
18
|
+
} catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function getRepoRoot() {
|
|
23
|
+
try {
|
|
24
|
+
return execSync("git rev-parse --show-toplevel", {
|
|
25
|
+
encoding: "utf-8",
|
|
26
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
27
|
+
}).trim();
|
|
28
|
+
} catch {
|
|
29
|
+
throw new Error("Not in a git repository. task-tracker requires a git repository.");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function getRepoStatus(repoPath) {
|
|
33
|
+
const name = repoPath.split(/[\\/]/).pop() ?? repoPath;
|
|
34
|
+
const status = {
|
|
35
|
+
path: repoPath,
|
|
36
|
+
name,
|
|
37
|
+
dirty: false,
|
|
38
|
+
dirtyFiles: [],
|
|
39
|
+
unpushed: false,
|
|
40
|
+
unpushedCommits: []
|
|
41
|
+
};
|
|
42
|
+
const porcelain = tryExec("git status --porcelain", repoPath);
|
|
43
|
+
if (porcelain === null) {
|
|
44
|
+
status.error = "git status failed";
|
|
45
|
+
return status;
|
|
46
|
+
}
|
|
47
|
+
if (porcelain) {
|
|
48
|
+
status.dirty = true;
|
|
49
|
+
status.dirtyFiles = porcelain.split("\n").filter(Boolean);
|
|
50
|
+
}
|
|
51
|
+
const unpushed = tryExec("git log @{u}..HEAD --oneline", repoPath);
|
|
52
|
+
if (unpushed !== null && unpushed) {
|
|
53
|
+
status.unpushed = true;
|
|
54
|
+
status.unpushedCommits = unpushed.split("\n").filter(Boolean);
|
|
55
|
+
}
|
|
56
|
+
return status;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/storage.ts
|
|
60
|
+
function getStoragePath() {
|
|
61
|
+
const root = getRepoRoot();
|
|
62
|
+
return join(root, ".tasks.jsonl");
|
|
63
|
+
}
|
|
64
|
+
function ensureStorageDir(storagePath) {
|
|
65
|
+
const dir = dirname(storagePath);
|
|
66
|
+
if (!existsSync(dir)) {
|
|
67
|
+
mkdirSync(dir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function readTasks(storagePath) {
|
|
71
|
+
ensureStorageDir(storagePath);
|
|
72
|
+
if (!existsSync(storagePath)) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
const content = readFileSync(storagePath, "utf-8").trim();
|
|
76
|
+
if (!content) return [];
|
|
77
|
+
return content.split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
|
|
78
|
+
}
|
|
79
|
+
function writeTasks(storagePath, tasks) {
|
|
80
|
+
ensureStorageDir(storagePath);
|
|
81
|
+
const content = tasks.map((t) => JSON.stringify(t)).join("\n");
|
|
82
|
+
writeFileSync(storagePath, content ? content + "\n" : "", "utf-8");
|
|
83
|
+
}
|
|
84
|
+
function addTaskToStorage(storagePath, task) {
|
|
85
|
+
ensureStorageDir(storagePath);
|
|
86
|
+
appendFileSync(storagePath, JSON.stringify(task) + "\n", "utf-8");
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/tasks.ts
|
|
90
|
+
import { nanoid } from "nanoid";
|
|
91
|
+
|
|
92
|
+
// src/types.ts
|
|
93
|
+
var STAGES = [
|
|
94
|
+
"pending",
|
|
95
|
+
"in-progress",
|
|
96
|
+
"implemented",
|
|
97
|
+
"verified",
|
|
98
|
+
"committed",
|
|
99
|
+
"pushed",
|
|
100
|
+
"pr-created",
|
|
101
|
+
"merged",
|
|
102
|
+
"released",
|
|
103
|
+
"published",
|
|
104
|
+
"done"
|
|
105
|
+
];
|
|
106
|
+
var DONE_STAGES = ["done"];
|
|
107
|
+
|
|
108
|
+
// src/tasks.ts
|
|
109
|
+
function createTask(storagePath, description, options = {}) {
|
|
110
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
111
|
+
const task = {
|
|
112
|
+
id: nanoid(8),
|
|
113
|
+
description,
|
|
114
|
+
stage: options.stage ?? "pending",
|
|
115
|
+
createdAt: now,
|
|
116
|
+
updatedAt: now
|
|
117
|
+
};
|
|
118
|
+
addTaskToStorage(storagePath, task);
|
|
119
|
+
return task;
|
|
120
|
+
}
|
|
121
|
+
function listTasks(storagePath, options = {}) {
|
|
122
|
+
let tasks = readTasks(storagePath);
|
|
123
|
+
if (!options.all) {
|
|
124
|
+
tasks = tasks.filter((t) => !DONE_STAGES.includes(t.stage));
|
|
125
|
+
}
|
|
126
|
+
if (options.stage) {
|
|
127
|
+
tasks = tasks.filter((t) => t.stage === options.stage);
|
|
128
|
+
}
|
|
129
|
+
return tasks;
|
|
130
|
+
}
|
|
131
|
+
function updateTask(storagePath, id, updates) {
|
|
132
|
+
const tasks = readTasks(storagePath);
|
|
133
|
+
const idx = tasks.findIndex((t) => t.id === id);
|
|
134
|
+
if (idx === -1) return null;
|
|
135
|
+
const task = tasks[idx];
|
|
136
|
+
if (updates.stage !== void 0) task.stage = updates.stage;
|
|
137
|
+
if (updates.description !== void 0) task.description = updates.description;
|
|
138
|
+
task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
139
|
+
writeTasks(storagePath, tasks);
|
|
140
|
+
return task;
|
|
141
|
+
}
|
|
142
|
+
function removeTask(storagePath, id) {
|
|
143
|
+
const tasks = readTasks(storagePath);
|
|
144
|
+
const filtered = tasks.filter((t) => t.id !== id);
|
|
145
|
+
if (filtered.length === tasks.length) return false;
|
|
146
|
+
writeTasks(storagePath, filtered);
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/format.ts
|
|
151
|
+
import chalk from "chalk";
|
|
152
|
+
var STAGE_COLORS = {
|
|
153
|
+
pending: (s) => chalk.gray(s),
|
|
154
|
+
"in-progress": (s) => chalk.blue(s),
|
|
155
|
+
implemented: (s) => chalk.cyan(s),
|
|
156
|
+
verified: (s) => chalk.yellow(s),
|
|
157
|
+
committed: (s) => chalk.magenta(s),
|
|
158
|
+
pushed: (s) => chalk.green(s),
|
|
159
|
+
"pr-created": (s) => chalk.greenBright(s),
|
|
160
|
+
merged: (s) => chalk.green(s),
|
|
161
|
+
released: (s) => chalk.blueBright(s),
|
|
162
|
+
published: (s) => chalk.greenBright(s),
|
|
163
|
+
done: (s) => chalk.dim(s)
|
|
164
|
+
};
|
|
165
|
+
function stageColor(stage) {
|
|
166
|
+
const fn = STAGE_COLORS[stage] ?? ((s) => chalk.white(s));
|
|
167
|
+
return fn(stage);
|
|
168
|
+
}
|
|
169
|
+
function formatTask(task) {
|
|
170
|
+
const stage = stageColor(task.stage);
|
|
171
|
+
const id = chalk.bold(task.id);
|
|
172
|
+
return `${id} ${stage} ${task.description}`;
|
|
173
|
+
}
|
|
174
|
+
function formatTaskTable(tasks) {
|
|
175
|
+
if (tasks.length === 0) {
|
|
176
|
+
return chalk.dim("No tasks found.");
|
|
177
|
+
}
|
|
178
|
+
return tasks.map(formatTask).join("\n");
|
|
179
|
+
}
|
|
180
|
+
function formatCheckReport(activeTasks, repoStatus) {
|
|
181
|
+
const lines = [];
|
|
182
|
+
lines.push(chalk.bold("=== Task Tracker Check ==="));
|
|
183
|
+
lines.push("");
|
|
184
|
+
lines.push(chalk.bold(`Active Tasks (${activeTasks.length}):`));
|
|
185
|
+
if (activeTasks.length === 0) {
|
|
186
|
+
lines.push(chalk.dim(" No active tasks."));
|
|
187
|
+
} else {
|
|
188
|
+
for (const t of activeTasks) {
|
|
189
|
+
lines.push(" " + formatTask(t));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
lines.push("");
|
|
193
|
+
lines.push(chalk.bold(`Git Status (${repoStatus.name}):`));
|
|
194
|
+
if (repoStatus.error) {
|
|
195
|
+
lines.push(chalk.red(` Error: ${repoStatus.error}`));
|
|
196
|
+
} else if (!repoStatus.dirty && !repoStatus.unpushed) {
|
|
197
|
+
lines.push(chalk.green(" Clean."));
|
|
198
|
+
} else {
|
|
199
|
+
if (repoStatus.dirty) {
|
|
200
|
+
lines.push(chalk.red(` Uncommitted changes (${repoStatus.dirtyFiles.length} files):`));
|
|
201
|
+
for (const f of repoStatus.dirtyFiles.slice(0, 5)) {
|
|
202
|
+
lines.push(chalk.dim(` ${f}`));
|
|
203
|
+
}
|
|
204
|
+
if (repoStatus.dirtyFiles.length > 5) {
|
|
205
|
+
lines.push(chalk.dim(` ... and ${repoStatus.dirtyFiles.length - 5} more`));
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (repoStatus.unpushed) {
|
|
209
|
+
lines.push(chalk.blue(` Unpushed commits (${repoStatus.unpushedCommits.length}):`));
|
|
210
|
+
for (const c of repoStatus.unpushedCommits.slice(0, 5)) {
|
|
211
|
+
lines.push(chalk.dim(` ${c}`));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return lines.join("\n");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// src/cli.ts
|
|
219
|
+
import process from "process";
|
|
220
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
221
|
+
var __dirname = dirname2(__filename);
|
|
222
|
+
var version = "0.0.0";
|
|
223
|
+
try {
|
|
224
|
+
const pkg = JSON.parse(readFileSync2(join2(__dirname, "..", "package.json"), "utf-8"));
|
|
225
|
+
version = pkg.version;
|
|
226
|
+
} catch {
|
|
227
|
+
}
|
|
228
|
+
function isValidStage(s) {
|
|
229
|
+
return STAGES.includes(s);
|
|
230
|
+
}
|
|
231
|
+
function getStorage() {
|
|
232
|
+
try {
|
|
233
|
+
return getStoragePath();
|
|
234
|
+
} catch (e) {
|
|
235
|
+
console.error(e instanceof Error ? e.message : "Not in a git repository");
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
var program = new Command();
|
|
240
|
+
program.name("task-tracker").description("Persistent task lifecycle tracker for AI agent sessions").version(version, "-V, --version");
|
|
241
|
+
program.command("add <description>").description("Add a new task").option("--stage <stage>", "Initial stage", "pending").option("--json", "Output created task as JSON").action((description, opts) => {
|
|
242
|
+
if (!isValidStage(opts.stage)) {
|
|
243
|
+
console.error(`Invalid stage: ${opts.stage}
|
|
244
|
+
Valid stages: ${STAGES.join(", ")}`);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
const task = createTask(getStorage(), description, { stage: opts.stage });
|
|
248
|
+
if (opts.json) {
|
|
249
|
+
console.log(JSON.stringify(task, null, 2));
|
|
250
|
+
} else {
|
|
251
|
+
console.log("Created: " + formatTask(task));
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
program.command("list").description("List tasks").option("--all", "Include completed/done tasks").option("--stage <stage>", "Filter by stage").option("--json", "JSON output").action((opts) => {
|
|
255
|
+
const stage = opts.stage && isValidStage(opts.stage) ? opts.stage : void 0;
|
|
256
|
+
if (opts.stage && !stage) {
|
|
257
|
+
console.error(`Invalid stage: ${opts.stage}
|
|
258
|
+
Valid stages: ${STAGES.join(", ")}`);
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
const tasks = listTasks(getStorage(), { all: opts.all, stage });
|
|
262
|
+
if (opts.json) {
|
|
263
|
+
console.log(JSON.stringify(tasks, null, 2));
|
|
264
|
+
} else {
|
|
265
|
+
console.log(formatTaskTable(tasks));
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
program.command("update <id>").description("Update a task").option("--stage <stage>", "Set lifecycle stage").option("--description <text>", "Update description").option("--json", "JSON output").action((id, opts) => {
|
|
269
|
+
if (opts.stage && !isValidStage(opts.stage)) {
|
|
270
|
+
console.error(`Invalid stage: ${opts.stage}
|
|
271
|
+
Valid stages: ${STAGES.join(", ")}`);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
const updates = {};
|
|
275
|
+
if (opts.stage) updates.stage = opts.stage;
|
|
276
|
+
if (opts.description) updates.description = opts.description;
|
|
277
|
+
const task = updateTask(getStorage(), id, updates);
|
|
278
|
+
if (!task) {
|
|
279
|
+
console.error(`Task not found: ${id}`);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
if (opts.json) {
|
|
283
|
+
console.log(JSON.stringify(task, null, 2));
|
|
284
|
+
} else {
|
|
285
|
+
console.log("Updated: " + formatTask(task));
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
program.command("done <id>").description("Mark task as done").action((id) => {
|
|
289
|
+
const task = updateTask(getStorage(), id, { stage: "done" });
|
|
290
|
+
if (!task) {
|
|
291
|
+
console.error(`Task not found: ${id}`);
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
console.log("Done: " + formatTask(task));
|
|
295
|
+
});
|
|
296
|
+
program.command("remove <id>").description("Remove a task permanently").action((id) => {
|
|
297
|
+
const removed = removeTask(getStorage(), id);
|
|
298
|
+
if (!removed) {
|
|
299
|
+
console.error(`Task not found: ${id}`);
|
|
300
|
+
process.exit(1);
|
|
301
|
+
}
|
|
302
|
+
console.log(`Removed task: ${id}`);
|
|
303
|
+
});
|
|
304
|
+
program.command("check").description("Show active tasks and git status for this repo").option("--json", "JSON output").action((opts) => {
|
|
305
|
+
const storage = getStorage();
|
|
306
|
+
const repoRoot = dirname2(storage);
|
|
307
|
+
const activeTasks = listTasks(storage, { all: false });
|
|
308
|
+
const repoStatus = getRepoStatus(repoRoot);
|
|
309
|
+
if (opts.json) {
|
|
310
|
+
console.log(JSON.stringify({ activeTasks, repoStatus }, null, 2));
|
|
311
|
+
} else {
|
|
312
|
+
console.log(formatCheckReport(activeTasks, repoStatus));
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
program.parse(process.argv);
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@metyatech/task-tracker",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Persistent task lifecycle tracker for AI agent sessions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"task-tracker": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"LICENSE",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/metyatech/task-tracker.git"
|
|
20
|
+
},
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/metyatech/task-tracker/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/metyatech/task-tracker",
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"lint": "eslint src --ext .ts",
|
|
32
|
+
"format": "prettier --write \"src/**/*.ts\" \"*.json\" \"*.md\"",
|
|
33
|
+
"format:check": "prettier --check \"src/**/*.ts\" \"*.json\" \"*.md\"",
|
|
34
|
+
"verify": "npm run format:check && npm run lint && npm run build && npm run test"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"chalk": "^5.3.0",
|
|
38
|
+
"commander": "^12.0.0",
|
|
39
|
+
"nanoid": "^5.0.7"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^20.0.0",
|
|
43
|
+
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
44
|
+
"@typescript-eslint/parser": "^7.0.0",
|
|
45
|
+
"eslint": "^8.57.0",
|
|
46
|
+
"prettier": "^3.2.0",
|
|
47
|
+
"tsup": "^8.0.0",
|
|
48
|
+
"typescript": "^5.4.0",
|
|
49
|
+
"vitest": "^1.4.0"
|
|
50
|
+
}
|
|
51
|
+
}
|