@dreki-gg/taskman 0.2.2 → 0.4.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/README.md +9 -0
- package/dist/cli.mjs +141 -1
- package/dist/index.d.mts +28 -4
- package/dist/index.mjs +2 -2
- package/dist/{initiatives-Ij_teFl_.mjs → initiatives-AtmFo2DU.mjs} +76 -48
- package/package.json +1 -1
- package/skills/taskman/core/SKILL.md +23 -5
package/README.md
CHANGED
|
@@ -46,6 +46,15 @@ taskman <command> --help # flags + arguments for one command
|
|
|
46
46
|
duplicate it (so it cannot drift). Every command prints human text by default
|
|
47
47
|
and accepts `--json` for machine consumption.
|
|
48
48
|
|
|
49
|
+
Create a plan from any harness (handoff/tasks accept an inline value, a
|
|
50
|
+
`--*-file <path>`, or piped stdin):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
echo "$MARKDOWN" | taskman create-plan --name my-plan --title "My Plan" \
|
|
54
|
+
--handoff-file - --tasks '[{"description":"do it"}]'
|
|
55
|
+
taskman create-handoff --plan my-plan --file HANDOFF.md # write/replace prose
|
|
56
|
+
```
|
|
57
|
+
|
|
49
58
|
A typical execution loop:
|
|
50
59
|
|
|
51
60
|
```bash
|
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { N as readInitiativesManifest, P as upsertInitiativeEntry, S as initiativeRollup, T as reconcileInitiativeForPlan, V as upsertPlanEntry, X as makePlanRuntime, _ as applyInitiativeReconcile, a as filterPlans, b as collectPlanDrift, d as setTaskStatus, g as resolvePlanByName, i as loadInitiativeListItems, l as sortPlans, m as loadPlanData, n as formatInitiativeList, o as formatPlanList, s as loadPlanListItems, t as filterInitiatives, u as appendDeferredTask, v as applyReconcile, y as collectInitiativeDrift, z as readPlansManifest } from "./initiatives-
|
|
2
|
+
import { J as writeTasksJsonl, N as readInitiativesManifest, P as upsertInitiativeEntry, S as initiativeRollup, T as reconcileInitiativeForPlan, V as upsertPlanEntry, W as saveHandoff, X as makePlanRuntime, _ as applyInitiativeReconcile, a as filterPlans, b as collectPlanDrift, d as setTaskStatus, f as nextTaskId, g as resolvePlanByName, i as loadInitiativeListItems, l as sortPlans, m as loadPlanData, n as formatInitiativeList, o as formatPlanList, p as toKebabCase, s as loadPlanListItems, t as filterInitiatives, u as appendDeferredTask, v as applyReconcile, y as collectInitiativeDrift, z as readPlansManifest } from "./initiatives-AtmFo2DU.mjs";
|
|
3
3
|
import { Effect } from "effect";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
4
5
|
import { readFileSync } from "node:fs";
|
|
5
6
|
import { Command } from "commander";
|
|
6
7
|
//#region src/cli/runtime.ts
|
|
@@ -253,6 +254,143 @@ async function closeInitiativeCommand(status, name, opts) {
|
|
|
253
254
|
}, `Initiative ${name} → ${s}${opts.reason ? ` (${opts.reason})` : ""}.`);
|
|
254
255
|
}
|
|
255
256
|
//#endregion
|
|
257
|
+
//#region src/cli/input.ts
|
|
258
|
+
/**
|
|
259
|
+
* Content resolution for CLI commands that accept markdown / JSON payloads from
|
|
260
|
+
* a foreign harness: an inline string, a file path, or piped stdin.
|
|
261
|
+
*/
|
|
262
|
+
/** Read all of stdin as a UTF-8 string (used when neither inline nor file is given). */
|
|
263
|
+
async function readStdin() {
|
|
264
|
+
const chunks = [];
|
|
265
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
266
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Resolve payload content from, in priority order: an inline value, a `--file`
|
|
270
|
+
* path (or `-` for stdin), else piped stdin. Throws `CliError` when nothing is
|
|
271
|
+
* provided and stdin is a TTY (no piped input).
|
|
272
|
+
*/
|
|
273
|
+
async function resolveContent(inline, file, label) {
|
|
274
|
+
if (inline !== void 0) return inline;
|
|
275
|
+
if (file !== void 0 && file !== "-") try {
|
|
276
|
+
return await readFile(file, "utf8");
|
|
277
|
+
} catch {
|
|
278
|
+
throw new CliError(`Could not read ${label} file: ${file}`);
|
|
279
|
+
}
|
|
280
|
+
if (process.stdin.isTTY) throw new CliError(`No ${label} provided. Pass it inline, via --${label}-file, or on stdin.`);
|
|
281
|
+
return readStdin();
|
|
282
|
+
}
|
|
283
|
+
//#endregion
|
|
284
|
+
//#region src/cli/commands/create-plan.ts
|
|
285
|
+
/**
|
|
286
|
+
* `taskman create-plan` — create a plan from a foreign harness.
|
|
287
|
+
*
|
|
288
|
+
* The CLI sibling of plan-mode's `submit_plan` tool: writes tasks.jsonl,
|
|
289
|
+
* HANDOFF.md, and a plans.jsonl registry entry in one transaction, then
|
|
290
|
+
* re-projects the parent initiative (when linked). Handoff/tasks payloads come
|
|
291
|
+
* from inline values, files, or stdin so any harness can drive it.
|
|
292
|
+
*/
|
|
293
|
+
/** Parse and validate the `--tasks` JSON payload into TaskInput[]. */
|
|
294
|
+
function parseTasks(raw) {
|
|
295
|
+
let parsed;
|
|
296
|
+
try {
|
|
297
|
+
parsed = JSON.parse(raw);
|
|
298
|
+
} catch {
|
|
299
|
+
throw new CliError("--tasks must be a JSON array of { description, ... } objects.");
|
|
300
|
+
}
|
|
301
|
+
if (!Array.isArray(parsed) || parsed.length === 0) throw new CliError("--tasks must be a non-empty JSON array.");
|
|
302
|
+
return parsed.map((entry, i) => {
|
|
303
|
+
if (typeof entry !== "object" || entry === null) throw new CliError(`Task at index ${i} is not an object.`);
|
|
304
|
+
const { description } = entry;
|
|
305
|
+
if (typeof description !== "string" || description.trim() === "") throw new CliError(`Task at index ${i} is missing a "description".`);
|
|
306
|
+
const t = entry;
|
|
307
|
+
return {
|
|
308
|
+
id: t.id,
|
|
309
|
+
description: t.description,
|
|
310
|
+
details: t.details,
|
|
311
|
+
depends_on: t.depends_on
|
|
312
|
+
};
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
/** Assign explicit IDs where given, generate t-NNN for the rest. */
|
|
316
|
+
function assignIds(inputs, now) {
|
|
317
|
+
const ids = inputs.map((t) => t.id).filter((id) => Boolean(id));
|
|
318
|
+
const records = [];
|
|
319
|
+
for (const input of inputs) {
|
|
320
|
+
const id = input.id ?? nextTaskId(ids);
|
|
321
|
+
if (!input.id) ids.push(id);
|
|
322
|
+
records.push({
|
|
323
|
+
_type: "task",
|
|
324
|
+
id,
|
|
325
|
+
description: input.description.slice(0, 60),
|
|
326
|
+
details: input.details ?? "",
|
|
327
|
+
status: "pending",
|
|
328
|
+
depends_on: input.depends_on,
|
|
329
|
+
created_at: now,
|
|
330
|
+
updated_at: now
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
return records;
|
|
334
|
+
}
|
|
335
|
+
async function createPlanCommand(opts) {
|
|
336
|
+
if (!opts.name) throw new CliError("--name is required.");
|
|
337
|
+
if (!opts.title) throw new CliError("--title is required.");
|
|
338
|
+
const planName = toKebabCase(opts.name);
|
|
339
|
+
const planDir = `.plans/${planName}`;
|
|
340
|
+
const initiative = opts.initiative ? toKebabCase(opts.initiative) : void 0;
|
|
341
|
+
const dependsOnPlans = opts.dependsOn ? opts.dependsOn.split(",").map((s) => toKebabCase(s.trim())).filter(Boolean) : void 0;
|
|
342
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
343
|
+
const handoff = await resolveContent(opts.handoff, opts.handoffFile, "handoff");
|
|
344
|
+
const tasks = assignIds(parseTasks(await resolveContent(opts.tasks, opts.tasksFile, "tasks")), now);
|
|
345
|
+
const meta = {
|
|
346
|
+
_type: "meta",
|
|
347
|
+
title: opts.title,
|
|
348
|
+
plan_name: planName,
|
|
349
|
+
created_at: now
|
|
350
|
+
};
|
|
351
|
+
const unknownInitiative = await runPlanIO(Effect.gen(function* () {
|
|
352
|
+
yield* writeTasksJsonl(planDir, meta, tasks);
|
|
353
|
+
yield* saveHandoff(planDir, handoff);
|
|
354
|
+
yield* upsertPlanEntry(planName, {
|
|
355
|
+
status: "in-progress",
|
|
356
|
+
title: opts.title,
|
|
357
|
+
initiative,
|
|
358
|
+
depends_on: dependsOnPlans
|
|
359
|
+
});
|
|
360
|
+
yield* reconcileInitiativeForPlan(planName);
|
|
361
|
+
if (!initiative) return false;
|
|
362
|
+
return !(yield* readInitiativesManifest()).some((entry) => entry.name === initiative);
|
|
363
|
+
}));
|
|
364
|
+
const linkSuffix = initiative ? ` Linked to initiative "${initiative}"${unknownInitiative ? " (no initiatives.jsonl entry yet — create it with submit_initiative)" : ""}.` : "";
|
|
365
|
+
emit(Boolean(opts.json), {
|
|
366
|
+
plan_name: planName,
|
|
367
|
+
plan_dir: planDir,
|
|
368
|
+
task_count: tasks.length,
|
|
369
|
+
task_ids: tasks.map((t) => t.id),
|
|
370
|
+
initiative: initiative ?? null,
|
|
371
|
+
depends_on: dependsOnPlans ?? null,
|
|
372
|
+
unknown_initiative: unknownInitiative
|
|
373
|
+
}, `Plan "${opts.title}" saved with ${tasks.length} tasks in ${planDir}.${linkSuffix}`);
|
|
374
|
+
}
|
|
375
|
+
//#endregion
|
|
376
|
+
//#region src/cli/commands/create-handoff.ts
|
|
377
|
+
/**
|
|
378
|
+
* `taskman create-handoff [content]` — write/replace HANDOFF.md for a plan.
|
|
379
|
+
*
|
|
380
|
+
* Lets a foreign harness hand off markdown without going through the plan-mode
|
|
381
|
+
* extension: content comes from an inline argument, `--file <path>`, or stdin.
|
|
382
|
+
*/
|
|
383
|
+
async function createHandoffCommand(content, opts) {
|
|
384
|
+
const { planName, planDir } = await resolvePlanDir(opts.plan);
|
|
385
|
+
const markdown = await resolveContent(content, opts.file, "handoff");
|
|
386
|
+
await runPlanIO(saveHandoff(planDir, markdown));
|
|
387
|
+
emit(Boolean(opts.json), {
|
|
388
|
+
plan_name: planName,
|
|
389
|
+
path: `${planDir}/HANDOFF.md`,
|
|
390
|
+
bytes: Buffer.byteLength(markdown)
|
|
391
|
+
}, `Wrote HANDOFF.md (${Buffer.byteLength(markdown)} bytes) for ${planName}.`);
|
|
392
|
+
}
|
|
393
|
+
//#endregion
|
|
256
394
|
//#region src/cli.ts
|
|
257
395
|
/**
|
|
258
396
|
* `taskman` CLI — drive the `.plans/` task ledger from any Node harness.
|
|
@@ -279,6 +417,8 @@ function buildProgram() {
|
|
|
279
417
|
program.command("update-task").description("Set a task status (done|skipped|blocked|pending)").argument("<id>", "task id, e.g. t-001").argument("<status>", "done|skipped|blocked|pending").option("--plan <name>", "plan to target").option("--notes <text>", "notes recorded on the task").option("--json", "machine-readable JSON output").action((id, status, opts) => updateTaskCommand(id, status, opts));
|
|
280
418
|
program.command("add-task").description("Append a deferred follow-up task").argument("<description>", "short task label").requiredOption("--reason <text>", "why this follow-up matters").option("--plan <name>", "plan to target").option("--details <text>", "fuller implementation notes").option("--json", "machine-readable JSON output").action((description, opts) => addTaskCommand(description, opts));
|
|
281
419
|
program.command("reconcile").description("Detect (and with --apply, repair) status drift").option("--apply", "repair safe in-progress→done drift").option("--json", "machine-readable JSON output").action((opts) => reconcileCommand(opts));
|
|
420
|
+
program.command("create-plan").description("Create a plan (tasks.jsonl + HANDOFF.md + registry entry) from any harness").requiredOption("--name <name>", "short kebab-case plan name").requiredOption("--title <title>", "human-readable plan title").option("--handoff <text>", "HANDOFF.md markdown (inline)").option("--handoff-file <path>", "read HANDOFF.md markdown from a file (\"-\" for stdin)").option("--tasks <json>", "tasks as an inline JSON array of { description, ... }").option("--tasks-file <path>", "read the tasks JSON array from a file (\"-\" for stdin)").option("--initiative <name>", "parent initiative name to link this plan to").option("--depends-on <names>", "comma-separated plan names this plan depends on").option("--json", "machine-readable JSON output").action((opts) => createPlanCommand(opts));
|
|
421
|
+
program.command("create-handoff").description("Write/replace HANDOFF.md for a plan from any harness").argument("[content]", "HANDOFF.md markdown (inline); else use --file or stdin").option("--plan <name>", "plan to target").option("--file <path>", "read markdown from a file (\"-\" for stdin)").option("--json", "machine-readable JSON output").action((content, opts) => createHandoffCommand(content, opts));
|
|
282
422
|
program.command("close").description("Set a plan lifecycle status").argument("<status>", "done|superseded|abandoned|in-progress").option("--plan <name>", "plan to target").option("--reason <text>", "why (recorded in the registry)").option("--json", "machine-readable JSON output").action((status, opts) => closePlanCommand(status, opts));
|
|
283
423
|
program.command("close-initiative").description("Set an initiative lifecycle status").argument("<status>", "done|superseded|abandoned|in-progress").argument("<name>", "initiative name").option("--reason <text>", "why (recorded in the registry)").option("--json", "machine-readable JSON output").action((status, name, opts) => closeInitiativeCommand(status, name, opts));
|
|
284
424
|
return program;
|
package/dist/index.d.mts
CHANGED
|
@@ -301,12 +301,36 @@ interface FileSystemService {
|
|
|
301
301
|
}
|
|
302
302
|
declare const FileSystem_base: Context.TagClass<FileSystem, "PlanMode/FileSystem", FileSystemService>;
|
|
303
303
|
declare class FileSystem extends FileSystem_base {}
|
|
304
|
+
/**
|
|
305
|
+
* Build a node-backed filesystem service whose relative paths resolve against
|
|
306
|
+
* `root`. All storage programs use relative `.plans/...` paths, so prefixing a
|
|
307
|
+
* root relocates the entire plan registry coherently (manifests, plan dirs,
|
|
308
|
+
* handoffs) to another working directory.
|
|
309
|
+
*
|
|
310
|
+
* `resolve(root, p)` is a no-op for already-absolute paths and reproduces the
|
|
311
|
+
* default `process.cwd()` behaviour exactly when `root === process.cwd()`, so
|
|
312
|
+
* the common (no-target) path is unchanged.
|
|
313
|
+
*/
|
|
314
|
+
declare function makeNodeFileSystemService(root: string): FileSystemService;
|
|
315
|
+
/**
|
|
316
|
+
* Default service: relative paths resolve against the current working directory
|
|
317
|
+
* at call time. `resolve('.', p)` is equivalent to `resolve(process.cwd(), p)`,
|
|
318
|
+
* so this matches the historical behaviour of passing raw relative paths to fs.
|
|
319
|
+
*/
|
|
304
320
|
declare const nodeFileSystemService: FileSystemService;
|
|
305
321
|
//#endregion
|
|
306
322
|
//#region src/effects/runtime.d.ts
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
323
|
+
/**
|
|
324
|
+
* Build the live filesystem layer. Pass `root` to relocate the whole `.plans/`
|
|
325
|
+
* registry under another working directory; omit it for the default (relative
|
|
326
|
+
* paths resolve against the current working directory).
|
|
327
|
+
*/
|
|
328
|
+
declare function makeRuntimeLayer(root?: string): Layer.Layer<FileSystem>;
|
|
329
|
+
/**
|
|
330
|
+
* Build a bridge that runs storage programs against the live filesystem layer.
|
|
331
|
+
* Pass `root` to target an external working directory's `.plans/` registry.
|
|
332
|
+
*/
|
|
333
|
+
declare function makePlanRuntime(root?: string): <A, E>(program: Effect.Effect<A, E, FileSystem>) => Promise<A>;
|
|
310
334
|
type RunPlanIO = ReturnType<typeof makePlanRuntime>;
|
|
311
335
|
//#endregion
|
|
312
336
|
//#region src/storage/task-storage.d.ts
|
|
@@ -679,4 +703,4 @@ declare function toKebabCase(name: string): string;
|
|
|
679
703
|
*/
|
|
680
704
|
declare function nextTaskId(existingIds: readonly string[]): string;
|
|
681
705
|
//#endregion
|
|
682
|
-
export { AddTaskInput, type ExecPendingConfig, ExecPendingConfigSchema, FileSystem, type FileSystemService, InitiativeDriftRow, initiatives_d_exports as InitiativeListing, type InitiativeManifestEntry, InitiativeManifestEntrySchema, InitiativeMemberRow, InitiativeRollup, type InitiativeStatus, InitiativeStatusSchema, type InitiativeUpsert, JsonlParseError, JsonlValidationError, MissingMetaRecord, type PlanData, PlanDriftRow, plans_d_exports as PlanListing, type PlanManifestEntry, PlanManifestEntrySchema, PlanReadError, PlanReadiness, type PlanStatus, PlanStatusSchema, PlanStorageError, type PlanUpsert, PlanWriteError, ResolvedPlanName, type RunPlanIO, type TaskMeta, TaskMetaSchema, TaskNotFound, type TaskOrigin, TaskOriginSchema, type TaskRecord, TaskRecordSchema, type TaskStatus, TaskStatusSchema, TasksFileNotFound, TasksLineSchema, TasksSnapshot, type ThinkingLevel, UpdatedTaskResult, activeTasksResolved, appendDeferredTask, applyInitiativeReconcile, applyInitiativeUpsert, applyPlanUpsert, applyReconcile, causeMessage, collectInitiativeDrift, collectPlanDrift, computePlanReadiness, decodeExecPendingConfig, decodeInitiativeManifestEntry, decodePlanManifestEntry, decodeTaskMeta, decodeTaskRecord, decodeTasksLine, deferredTasks, errorMessage, initiativeRollup, isInitiativeFinalizable, isPlanFinalizable, isTerminalStatus, loadHandoff, loadPlanData, makePlanRuntime, makeRuntimeLayer, membersOf, mutateInitiativesManifest, mutatePlansManifest, nextTaskId, nodeFileSystemService, normalizePlanName, reactivateForExecution, readInitiativesManifest, readPlansManifest, readTasksJsonl, reconcileInitiativeForPlan, reconcileInitiativeStatus, reconcilePlanStatus, resolvePlanByName, saveHandoff, saveInitiative, setTaskStatus, toKebabCase, toNativeError, updateTask, upsertInitiativeEntry, upsertPlanEntry, withFileLock, writeFileAtomic, writeInitiativesManifest, writePlansManifest, writeTasksJsonl };
|
|
706
|
+
export { AddTaskInput, type ExecPendingConfig, ExecPendingConfigSchema, FileSystem, type FileSystemService, InitiativeDriftRow, initiatives_d_exports as InitiativeListing, type InitiativeManifestEntry, InitiativeManifestEntrySchema, InitiativeMemberRow, InitiativeRollup, type InitiativeStatus, InitiativeStatusSchema, type InitiativeUpsert, JsonlParseError, JsonlValidationError, MissingMetaRecord, type PlanData, PlanDriftRow, plans_d_exports as PlanListing, type PlanManifestEntry, PlanManifestEntrySchema, PlanReadError, PlanReadiness, type PlanStatus, PlanStatusSchema, PlanStorageError, type PlanUpsert, PlanWriteError, ResolvedPlanName, type RunPlanIO, type TaskMeta, TaskMetaSchema, TaskNotFound, type TaskOrigin, TaskOriginSchema, type TaskRecord, TaskRecordSchema, type TaskStatus, TaskStatusSchema, TasksFileNotFound, TasksLineSchema, TasksSnapshot, type ThinkingLevel, UpdatedTaskResult, activeTasksResolved, appendDeferredTask, applyInitiativeReconcile, applyInitiativeUpsert, applyPlanUpsert, applyReconcile, causeMessage, collectInitiativeDrift, collectPlanDrift, computePlanReadiness, decodeExecPendingConfig, decodeInitiativeManifestEntry, decodePlanManifestEntry, decodeTaskMeta, decodeTaskRecord, decodeTasksLine, deferredTasks, errorMessage, initiativeRollup, isInitiativeFinalizable, isPlanFinalizable, isTerminalStatus, loadHandoff, loadPlanData, makeNodeFileSystemService, makePlanRuntime, makeRuntimeLayer, membersOf, mutateInitiativesManifest, mutatePlansManifest, nextTaskId, nodeFileSystemService, normalizePlanName, reactivateForExecution, readInitiativesManifest, readPlansManifest, readTasksJsonl, reconcileInitiativeForPlan, reconcileInitiativeStatus, reconcilePlanStatus, resolvePlanByName, saveHandoff, saveInitiative, setTaskStatus, toKebabCase, toNativeError, updateTask, upsertInitiativeEntry, upsertPlanEntry, withFileLock, writeFileAtomic, writeInitiativesManifest, writePlansManifest, writeTasksJsonl };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { $ as
|
|
2
|
-
export { ExecPendingConfigSchema, FileSystem, initiatives_exports as InitiativeListing, InitiativeManifestEntrySchema, InitiativeStatusSchema, JsonlParseError, JsonlValidationError, MissingMetaRecord, plans_exports as PlanListing, PlanManifestEntrySchema, PlanReadError, PlanStatusSchema, PlanWriteError, TaskMetaSchema, TaskNotFound, TaskOriginSchema, TaskRecordSchema, TaskStatusSchema, TasksFileNotFound, TasksLineSchema, activeTasksResolved, appendDeferredTask, applyInitiativeReconcile, applyInitiativeUpsert, applyPlanUpsert, applyReconcile, causeMessage, collectInitiativeDrift, collectPlanDrift, computePlanReadiness, decodeExecPendingConfig, decodeInitiativeManifestEntry, decodePlanManifestEntry, decodeTaskMeta, decodeTaskRecord, decodeTasksLine, deferredTasks, errorMessage, initiativeRollup, isInitiativeFinalizable, isPlanFinalizable, isTerminalStatus, loadHandoff, loadPlanData, makePlanRuntime, makeRuntimeLayer, membersOf, mutateInitiativesManifest, mutatePlansManifest, nextTaskId, nodeFileSystemService, normalizePlanName, reactivateForExecution, readInitiativesManifest, readPlansManifest, readTasksJsonl, reconcileInitiativeForPlan, reconcileInitiativeStatus, reconcilePlanStatus, resolvePlanByName, saveHandoff, saveInitiative, setTaskStatus, toKebabCase, toNativeError, updateTask, upsertInitiativeEntry, upsertPlanEntry, withFileLock, writeFileAtomic, writeInitiativesManifest, writePlansManifest, writeTasksJsonl };
|
|
1
|
+
import { $ as makeNodeFileSystemService, A as reactivateForExecution, B as reconcilePlanStatus, C as isInitiativeFinalizable, Ct as TaskNotFound, D as activeTasksResolved, Dt as toNativeError, E as reconcileInitiativeStatus, Et as errorMessage, F as writeInitiativesManifest, G as saveInitiative, H as writePlansManifest, I as applyPlanUpsert, J as writeTasksJsonl, K as readTasksJsonl, L as isTerminalStatus, M as mutateInitiativesManifest, N as readInitiativesManifest, O as deferredTasks, P as upsertInitiativeEntry, Q as FileSystem, R as mutatePlansManifest, S as initiativeRollup, St as PlanWriteError, T as reconcileInitiativeForPlan, Tt as causeMessage, U as loadHandoff, V as upsertPlanEntry, W as saveHandoff, X as makePlanRuntime, Y as withFileLock, Z as makeRuntimeLayer, _ as applyInitiativeReconcile, _t as decodeTasksLine, at as PlanManifestEntrySchema, b as collectPlanDrift, bt as MissingMetaRecord, c as plans_exports, ct as TaskOriginSchema, d as setTaskStatus, dt as TasksLineSchema, et as nodeFileSystemService, f as nextTaskId, ft as decodeExecPendingConfig, g as resolvePlanByName, gt as decodeTaskRecord, h as normalizePlanName, ht as decodeTaskMeta, it as InitiativeStatusSchema, j as applyInitiativeUpsert, k as isPlanFinalizable, lt as TaskRecordSchema, m as loadPlanData, mt as decodePlanManifestEntry, nt as ExecPendingConfigSchema, ot as PlanStatusSchema, p as toKebabCase, pt as decodeInitiativeManifestEntry, q as updateTask, r as initiatives_exports, rt as InitiativeManifestEntrySchema, st as TaskMetaSchema, tt as writeFileAtomic, u as appendDeferredTask, ut as TaskStatusSchema, v as applyReconcile, vt as JsonlParseError, w as membersOf, wt as TasksFileNotFound, x as computePlanReadiness, xt as PlanReadError, y as collectInitiativeDrift, yt as JsonlValidationError, z as readPlansManifest } from "./initiatives-AtmFo2DU.mjs";
|
|
2
|
+
export { ExecPendingConfigSchema, FileSystem, initiatives_exports as InitiativeListing, InitiativeManifestEntrySchema, InitiativeStatusSchema, JsonlParseError, JsonlValidationError, MissingMetaRecord, plans_exports as PlanListing, PlanManifestEntrySchema, PlanReadError, PlanStatusSchema, PlanWriteError, TaskMetaSchema, TaskNotFound, TaskOriginSchema, TaskRecordSchema, TaskStatusSchema, TasksFileNotFound, TasksLineSchema, activeTasksResolved, appendDeferredTask, applyInitiativeReconcile, applyInitiativeUpsert, applyPlanUpsert, applyReconcile, causeMessage, collectInitiativeDrift, collectPlanDrift, computePlanReadiness, decodeExecPendingConfig, decodeInitiativeManifestEntry, decodePlanManifestEntry, decodeTaskMeta, decodeTaskRecord, decodeTasksLine, deferredTasks, errorMessage, initiativeRollup, isInitiativeFinalizable, isPlanFinalizable, isTerminalStatus, loadHandoff, loadPlanData, makeNodeFileSystemService, makePlanRuntime, makeRuntimeLayer, membersOf, mutateInitiativesManifest, mutatePlansManifest, nextTaskId, nodeFileSystemService, normalizePlanName, reactivateForExecution, readInitiativesManifest, readPlansManifest, readTasksJsonl, reconcileInitiativeForPlan, reconcileInitiativeStatus, reconcilePlanStatus, resolvePlanByName, saveHandoff, saveInitiative, setTaskStatus, toKebabCase, toNativeError, updateTask, upsertInitiativeEntry, upsertPlanEntry, withFileLock, writeFileAtomic, writeInitiativesManifest, writePlansManifest, writeTasksJsonl };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-CfYAbeIz.mjs";
|
|
2
2
|
import { Context, Data, Effect, Either, Layer, Option, Schema } from "effect";
|
|
3
3
|
import { mkdir, open, readFile, readdir, rename, rm, unlink, writeFile } from "node:fs/promises";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
5
|
import { createWriteStream } from "node:fs";
|
|
5
|
-
import { dirname, join } from "node:path";
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
//#region src/errors.ts
|
|
8
8
|
/**
|
|
@@ -220,48 +220,67 @@ async function syncDirectory(dir) {
|
|
|
220
220
|
* all failure modes typed (`PlanReadError` / `PlanWriteError`).
|
|
221
221
|
*/
|
|
222
222
|
var FileSystem = class extends Context.Tag("PlanMode/FileSystem")() {};
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
},
|
|
243
|
-
|
|
244
|
-
path,
|
|
245
|
-
cause
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
223
|
+
/**
|
|
224
|
+
* Build a node-backed filesystem service whose relative paths resolve against
|
|
225
|
+
* `root`. All storage programs use relative `.plans/...` paths, so prefixing a
|
|
226
|
+
* root relocates the entire plan registry coherently (manifests, plan dirs,
|
|
227
|
+
* handoffs) to another working directory.
|
|
228
|
+
*
|
|
229
|
+
* `resolve(root, p)` is a no-op for already-absolute paths and reproduces the
|
|
230
|
+
* default `process.cwd()` behaviour exactly when `root === process.cwd()`, so
|
|
231
|
+
* the common (no-target) path is unchanged.
|
|
232
|
+
*/
|
|
233
|
+
function makeNodeFileSystemService(root) {
|
|
234
|
+
const at = (path) => resolve(root, path);
|
|
235
|
+
return {
|
|
236
|
+
readFileString: (path) => Effect.tryPromise({
|
|
237
|
+
try: () => readFile(at(path), "utf-8"),
|
|
238
|
+
catch: (cause) => new PlanReadError({
|
|
239
|
+
path,
|
|
240
|
+
cause
|
|
241
|
+
})
|
|
242
|
+
}),
|
|
243
|
+
writeFileString: (path, data) => Effect.tryPromise({
|
|
244
|
+
try: () => writeFile(at(path), data, "utf-8"),
|
|
245
|
+
catch: (cause) => new PlanWriteError({
|
|
246
|
+
path,
|
|
247
|
+
cause
|
|
248
|
+
})
|
|
249
|
+
}),
|
|
250
|
+
writeFileAtomic: (path, data) => writeFileAtomic(at(path), data),
|
|
251
|
+
makeDir: (path) => Effect.tryPromise({
|
|
252
|
+
try: async () => {
|
|
253
|
+
await mkdir(at(path), { recursive: true });
|
|
254
|
+
},
|
|
255
|
+
catch: (cause) => new PlanWriteError({
|
|
256
|
+
path,
|
|
257
|
+
cause
|
|
258
|
+
})
|
|
259
|
+
}),
|
|
260
|
+
listDirectories: (path) => Effect.tryPromise({
|
|
261
|
+
try: async () => {
|
|
262
|
+
return (await readdir(at(path), { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
263
|
+
},
|
|
264
|
+
catch: (cause) => new PlanReadError({
|
|
265
|
+
path,
|
|
266
|
+
cause
|
|
267
|
+
})
|
|
268
|
+
}),
|
|
269
|
+
removeFile: (path) => Effect.tryPromise({
|
|
270
|
+
try: () => unlink(at(path)),
|
|
271
|
+
catch: (cause) => new PlanWriteError({
|
|
272
|
+
path,
|
|
273
|
+
cause
|
|
274
|
+
})
|
|
262
275
|
})
|
|
263
|
-
}
|
|
264
|
-
}
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Default service: relative paths resolve against the current working directory
|
|
280
|
+
* at call time. `resolve('.', p)` is equivalent to `resolve(process.cwd(), p)`,
|
|
281
|
+
* so this matches the historical behaviour of passing raw relative paths to fs.
|
|
282
|
+
*/
|
|
283
|
+
const nodeFileSystemService = makeNodeFileSystemService(".");
|
|
265
284
|
//#endregion
|
|
266
285
|
//#region src/effects/runtime.ts
|
|
267
286
|
/**
|
|
@@ -271,12 +290,21 @@ const nodeFileSystemService = {
|
|
|
271
290
|
* through the `runPlanIO` bridge so the imperative pi event handlers keep their
|
|
272
291
|
* `await fn(...)` shape.
|
|
273
292
|
*/
|
|
274
|
-
|
|
275
|
-
|
|
293
|
+
/**
|
|
294
|
+
* Build the live filesystem layer. Pass `root` to relocate the whole `.plans/`
|
|
295
|
+
* registry under another working directory; omit it for the default (relative
|
|
296
|
+
* paths resolve against the current working directory).
|
|
297
|
+
*/
|
|
298
|
+
function makeRuntimeLayer(root) {
|
|
299
|
+
const service = root === void 0 ? nodeFileSystemService : makeNodeFileSystemService(root);
|
|
300
|
+
return Layer.succeed(FileSystem, service);
|
|
276
301
|
}
|
|
277
|
-
/**
|
|
278
|
-
|
|
279
|
-
|
|
302
|
+
/**
|
|
303
|
+
* Build a bridge that runs storage programs against the live filesystem layer.
|
|
304
|
+
* Pass `root` to target an external working directory's `.plans/` registry.
|
|
305
|
+
*/
|
|
306
|
+
function makePlanRuntime(root) {
|
|
307
|
+
const layer = makeRuntimeLayer(root);
|
|
280
308
|
return function runPlanIO(program) {
|
|
281
309
|
return Effect.runPromise(program.pipe(Effect.provide(layer)));
|
|
282
310
|
};
|
|
@@ -1250,4 +1278,4 @@ function parseInitiativeFilter(raw) {
|
|
|
1250
1278
|
return "all";
|
|
1251
1279
|
}
|
|
1252
1280
|
//#endregion
|
|
1253
|
-
export {
|
|
1281
|
+
export { makeNodeFileSystemService as $, reactivateForExecution as A, reconcilePlanStatus as B, isInitiativeFinalizable as C, TaskNotFound as Ct, activeTasksResolved as D, toNativeError as Dt, reconcileInitiativeStatus as E, errorMessage as Et, writeInitiativesManifest as F, saveInitiative as G, writePlansManifest as H, applyPlanUpsert as I, writeTasksJsonl as J, readTasksJsonl as K, isTerminalStatus$1 as L, mutateInitiativesManifest as M, readInitiativesManifest as N, deferredTasks as O, upsertInitiativeEntry as P, FileSystem as Q, mutatePlansManifest as R, initiativeRollup as S, PlanWriteError as St, reconcileInitiativeForPlan as T, causeMessage as Tt, loadHandoff as U, upsertPlanEntry as V, saveHandoff as W, makePlanRuntime as X, withFileLock as Y, makeRuntimeLayer as Z, applyInitiativeReconcile as _, decodeTasksLine as _t, filterPlans as a, PlanManifestEntrySchema as at, collectPlanDrift as b, MissingMetaRecord as bt, plans_exports as c, TaskOriginSchema as ct, setTaskStatus as d, TasksLineSchema as dt, nodeFileSystemService as et, nextTaskId as f, decodeExecPendingConfig as ft, resolvePlanByName as g, decodeTaskRecord as gt, normalizePlanName as h, decodeTaskMeta as ht, loadInitiativeListItems as i, InitiativeStatusSchema as it, applyInitiativeUpsert as j, isPlanFinalizable as k, sortPlans as l, TaskRecordSchema as lt, loadPlanData as m, decodePlanManifestEntry as mt, formatInitiativeList as n, ExecPendingConfigSchema as nt, formatPlanList as o, PlanStatusSchema as ot, toKebabCase as p, decodeInitiativeManifestEntry as pt, updateTask as q, initiatives_exports as r, InitiativeManifestEntrySchema as rt, loadPlanListItems as s, TaskMetaSchema as st, filterInitiatives as t, writeFileAtomic as tt, appendDeferredTask as u, TaskStatusSchema as ut, applyReconcile as v, JsonlParseError as vt, membersOf as w, TasksFileNotFound as wt, computePlanReadiness as x, PlanReadError as xt, collectInitiativeDrift as y, JsonlValidationError as yt, readPlansManifest as z };
|
package/package.json
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
name: taskman/core
|
|
3
3
|
description: >
|
|
4
4
|
Drive the taskman CLI and engine over a .plans/ JSONL ledger — plans,
|
|
5
|
-
initiatives, and tasks. Load when running `taskman` commands (
|
|
6
|
-
update-task, add-task, reconcile, close),
|
|
7
|
-
sessions or harnesses, or calling
|
|
8
|
-
status-is-a-projection model,
|
|
5
|
+
initiatives, and tasks. Load when running `taskman` commands (create-plan,
|
|
6
|
+
create-handoff, status, list, update-task, add-task, reconcile, close),
|
|
7
|
+
tracking plan/task progress across sessions or harnesses, or calling
|
|
8
|
+
@dreki-gg/taskman as a library. Covers the status-is-a-projection model,
|
|
9
|
+
stateless plan resolution, and reconcile.
|
|
9
10
|
type: core
|
|
10
11
|
library: "@dreki-gg/taskman"
|
|
11
|
-
library_version: "0.
|
|
12
|
+
library_version: "0.4.0"
|
|
12
13
|
sources:
|
|
13
14
|
- "dreki-gg/pi-extensions:packages/taskman/README.md"
|
|
14
15
|
- "dreki-gg/pi-extensions:packages/taskman/src/cli.ts"
|
|
@@ -65,6 +66,23 @@ taskman add-task "handle empty input" --reason "found gap while implementing"
|
|
|
65
66
|
taskman reconcile --apply # repair safe (in-progress→done) drift
|
|
66
67
|
```
|
|
67
68
|
|
|
69
|
+
### Creating plans from any harness
|
|
70
|
+
|
|
71
|
+
Plans don't have to originate in plan-mode's `submit_plan` tool. The CLI creates
|
|
72
|
+
the same durable artifacts (tasks.jsonl + HANDOFF.md + registry entry) so a
|
|
73
|
+
foreign harness can seed the ledger directly. Handoff/task payloads accept an
|
|
74
|
+
inline value, a `--*-file <path>`, or piped stdin.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
echo "$MARKDOWN" | taskman create-plan --name my-plan --title "My Plan" \
|
|
78
|
+
--handoff-file - --tasks '[{"description":"do it"}]' # tasks get t-NNN ids
|
|
79
|
+
taskman create-handoff --plan my-plan --file HANDOFF.md # write/replace prose
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Use `--initiative <name>` to link the plan (create the initiative first) and
|
|
83
|
+
`--depends-on a,b` for plan-level ordering. Task creation here is plan setup —
|
|
84
|
+
distinct from `add-task`, which records a *deferred* follow-up (see below).
|
|
85
|
+
|
|
68
86
|
### Machine-readable output
|
|
69
87
|
|
|
70
88
|
Every command prints human text by default and accepts `--json`:
|