taskchef 4.0.0 → 4.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskchef",
3
- "version": "4.0.0",
3
+ "version": "4.1.1",
4
4
  "description": "Dispatch work from a data-only workspace to visible Codex project tasks.",
5
5
  "author": {
6
6
  "name": "Favo Yang",
package/README.md CHANGED
@@ -253,6 +253,19 @@ taskchef project list --workspace <workspace>
253
253
  taskchef project remove payments --workspace <workspace>
254
254
  ```
255
255
 
256
+ Human-readable project listings show one parent row per project. Configured
257
+ GitHub repositories appear beneath it as indented tree rows, with repeated kind
258
+ and path cells left blank. A project without a configured repository has only
259
+ its parent row, containing `-` in the repository column:
260
+
261
+ ```text
262
+ NAME KIND GITHUB REPOSITORY PATH
263
+ notes folder - /workspace/notes
264
+ payments git - /workspace/payments
265
+ ├─ repository https://github.com/example/payments-api
266
+ └─ repository https://github.com/example/payments-sdk
267
+ ```
268
+
256
269
  Import projects as a JSON array from a file or standard input:
257
270
 
258
271
  ```sh
@@ -296,9 +309,20 @@ Inspect the task history without querying Codex tasks:
296
309
  taskchef task show t1 --workspace <workspace>
297
310
  taskchef task list --workspace <workspace>
298
311
  taskchef task list --project payments --workspace <workspace>
312
+ taskchef task list --ascending --workspace <workspace>
299
313
  taskchef task summary --workspace <workspace>
300
314
  ```
301
315
 
316
+ Human-readable task listings put the scannable fields first and the durable ID
317
+ last. Values are kept in full and aligned in columns. Tasks are newest-first by
318
+ default; pass `--ascending` to list them from oldest to newest. The same order
319
+ applies to the `tasks` array in `--json` output.
320
+
321
+ ```text
322
+ TITLE PROJECT CREATED ID
323
+ Add retry logs payments 2026-08-12T10:00:00.000Z c0f010ff-84f2-4838-a69d-0ff1f5d721d7
324
+ ```
325
+
302
326
  The complete data contract is in [SPEC.md](SPEC.md). Deferred ideas are in
303
327
  [BACKLOG.md](BACKLOG.md).
304
328
 
package/SPEC.md CHANGED
@@ -201,8 +201,9 @@ it was not recorded.
201
201
  The CLI reads persisted history without contacting Codex:
202
202
 
203
203
  - `task show <id>` returns one entry.
204
- - `task list` returns entries in append order, optionally filtered by
205
- historical project name or exact path.
204
+ - `task list` returns entries newest-first by creation time, optionally filtered
205
+ by historical project name or exact path. `--ascending` returns oldest-first.
206
+ The selected order applies to both human rows and the JSON `tasks` array.
206
207
  - `task summary` returns the total and per-project counts.
207
208
  - `task resolve <id> --thread-id <thread-id>` atomically fills one nullable
208
209
  thread ID after Codex verifies the exact structured marker match.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskchef",
3
- "version": "4.0.0",
3
+ "version": "4.1.1",
4
4
  "description": "A non-blocking interactive dispatcher for visible Codex tasks.",
5
5
  "license": "MIT",
6
6
  "author": "Favo Yang",
package/src/cli.js CHANGED
@@ -15,6 +15,8 @@ import {
15
15
  resolveTask,
16
16
  } from "./workspace.js";
17
17
 
18
+ const BLANK_TABLE_CELL = Symbol("blank table cell");
19
+
18
20
  async function readStdin() {
19
21
  let input = "";
20
22
  process.stdin.setEncoding("utf8");
@@ -88,12 +90,51 @@ function print(value, args, human) {
88
90
  }
89
91
 
90
92
  function table(headers, rows) {
93
+ const display = (value) => {
94
+ if (value === BLANK_TABLE_CELL) return "";
95
+ if (value === null || value === undefined || value === "") return "-";
96
+ return String(value);
97
+ };
91
98
  const widths = headers.map((header, index) =>
92
- Math.max(header.length, ...rows.map((row) => String(row[index]).length)));
93
- const format = (row) => row.map((value, index) => String(value).padEnd(widths[index])).join(" ");
99
+ Math.max(header.length, ...rows.map((row) => display(row[index]).length)));
100
+ const format = (row) => row.map((value, index) => index === row.length - 1
101
+ ? display(value)
102
+ : display(value).padEnd(widths[index])).join(" ").trimEnd();
94
103
  return [format(headers), ...rows.map(format)].join("\n");
95
104
  }
96
105
 
106
+ function projectRows(projects) {
107
+ return projects.flatMap((project) => [
108
+ [
109
+ project.name,
110
+ project.isGitRepository ? "git" : "folder",
111
+ null,
112
+ project.path,
113
+ ],
114
+ ...project.githubRepos.map((repository, index) => [
115
+ ` ${index === project.githubRepos.length - 1 ? "└─" : "├─"} repository`,
116
+ BLANK_TABLE_CELL,
117
+ repository,
118
+ BLANK_TABLE_CELL,
119
+ ]),
120
+ ]);
121
+ }
122
+
123
+ function sortTasksByCreatedAt(tasks, ascending) {
124
+ return tasks
125
+ .map((task, index) => ({ task, index }))
126
+ .sort((left, right) => {
127
+ const leftCreatedAt = left.task.createdAt;
128
+ const rightCreatedAt = right.task.createdAt;
129
+ if (!leftCreatedAt && !rightCreatedAt) return left.index - right.index;
130
+ if (!leftCreatedAt) return 1;
131
+ if (!rightCreatedAt) return -1;
132
+ const chronological = Date.parse(leftCreatedAt) - Date.parse(rightCreatedAt);
133
+ return (ascending ? chronological : -chronological) || left.index - right.index;
134
+ })
135
+ .map(({ task }) => task);
136
+ }
137
+
97
138
  async function initialize(args) {
98
139
  validateCommandArgs(args, 2, { values: ["--workspace"], switches: ["--json"] });
99
140
  const result = await initializeWorkspace(workspaceRoot(args));
@@ -161,13 +202,8 @@ async function projectList(args) {
161
202
  validateCommandArgs(args, 2, { values: ["--workspace"], switches: ["--json"] });
162
203
  const projects = await listProjects(workspaceRoot(args));
163
204
  print({ projectCount: projects.length, projects }, args, (value) => table(
164
- ["NAME", "KIND", "GITHUB REPOSITORIES", "PATH"],
165
- value.projects.map((project) => [
166
- project.name,
167
- project.isGitRepository ? "git" : "folder",
168
- project.githubRepos.join(", ") || "-",
169
- project.path,
170
- ]),
205
+ ["NAME", "KIND", "GITHUB REPOSITORY", "PATH"],
206
+ projectRows(value.projects),
171
207
  ));
172
208
  return 0;
173
209
  }
@@ -215,19 +251,20 @@ async function taskShow(args) {
215
251
  async function taskList(args) {
216
252
  validateCommandArgs(args, 2, {
217
253
  values: ["--workspace", "--project"],
218
- switches: ["--json"],
254
+ switches: ["--ascending", "--json"],
219
255
  });
220
- const dispatches = await filterTasks(workspaceRoot(args), {
256
+ const filtered = await filterTasks(workspaceRoot(args), {
221
257
  project: option(args, "--project", null),
222
258
  });
259
+ const dispatches = sortTasksByCreatedAt(filtered, args.includes("--ascending"));
223
260
  const result = { taskCount: dispatches.length, tasks: dispatches };
224
261
  print(result, args, (value) => table(
225
- ["ID", "CREATED", "PROJECT", "TITLE"],
262
+ ["TITLE", "PROJECT", "CREATED", "ID"],
226
263
  value.tasks.map((dispatch) => [
227
- dispatch.id,
228
- dispatch.createdAt,
229
- dispatch.project.name,
230
264
  dispatch.title,
265
+ dispatch.project?.name,
266
+ dispatch.createdAt,
267
+ dispatch.id,
231
268
  ]),
232
269
  ));
233
270
  return 0;
@@ -257,7 +294,7 @@ Usage:
257
294
  taskchef task record [--json] [--workspace <path>]
258
295
  taskchef task resolve <task-id> --thread-id <thread-id> [--json] [--workspace <path>]
259
296
  taskchef task show <task-id> [--json] [--workspace <path>]
260
- taskchef task list [--project <name-or-path>] [--json] [--workspace <path>]
297
+ taskchef task list [--project <name-or-path>] [--ascending] [--json] [--workspace <path>]
261
298
  taskchef task summary [--json] [--workspace <path>]
262
299
 
263
300
  Task record reads JSON from standard input. Project import reads a JSON