@uoyo/mvtt 2.0.0-beta.4 → 2.0.0-beta.6
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 +299 -64
- package/README.zh-CN.md +419 -0
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +27 -2
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +19 -7
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +4 -2
- package/dist/commands/update.js.map +1 -1
- package/dist/fs/install-manifest.d.ts +4 -1
- package/dist/fs/install-manifest.d.ts.map +1 -1
- package/dist/fs/install-manifest.js +13 -1
- package/dist/fs/install-manifest.js.map +1 -1
- package/dist/fs/materialize.d.ts +2 -0
- package/dist/fs/materialize.d.ts.map +1 -1
- package/dist/fs/materialize.js +39 -9
- package/dist/fs/materialize.js.map +1 -1
- package/dist/fs/registry-merge.d.ts.map +1 -1
- package/dist/fs/registry-merge.js +72 -29
- package/dist/fs/registry-merge.js.map +1 -1
- package/dist/scripts/epic-update.cjs +7670 -0
- package/dist/scripts/plan-update.cjs +255 -82
- package/dist/scripts/session-update.cjs +84 -6
- package/dist/types/platform.d.ts +12 -0
- package/dist/types/platform.d.ts.map +1 -0
- package/dist/types/platform.js +24 -0
- package/dist/types/platform.js.map +1 -0
- package/dist/types/registry.d.ts +3 -24
- package/dist/types/registry.d.ts.map +1 -1
- package/install-manifest.yaml +4 -0
- package/package.json +1 -1
- package/registry.yaml +72 -198
- package/sources/defaults/config.yaml +8 -13
- package/sources/defaults/project-context.yaml +2 -5
- package/sources/defaults/session.yaml +14 -2
- package/sources/knowledge/core/manifest.yaml +1 -4
- package/sources/scripts/epic-update.js +512 -0
- package/sources/scripts/plan-update.js +614 -353
- package/sources/scripts/session-update.js +102 -2
- package/sources/sections/activation-load-config.md +1 -1
- package/sources/sections/activation-load-context.md +42 -13
- package/sources/sections/activation-preflight.md +1 -1
- package/sources/sections/footer-next-steps.md +3 -2
- package/sources/sections/session-update.md +41 -1
- package/sources/skills/mvt-analyze/business.md +46 -8
- package/sources/skills/mvt-analyze/manifest.yaml +5 -1
- package/sources/skills/mvt-analyze-code/business.md +18 -17
- package/sources/skills/mvt-analyze-code/manifest.yaml +3 -6
- package/sources/skills/mvt-check-context/business.md +13 -6
- package/sources/skills/mvt-check-context/manifest.yaml +0 -5
- package/sources/skills/mvt-cleanup/business.md +17 -2
- package/sources/skills/mvt-config/business.md +5 -5
- package/sources/skills/mvt-config/manifest.yaml +3 -6
- package/sources/skills/mvt-create-skill/business.md +2 -14
- package/sources/skills/mvt-create-skill/manifest.yaml +0 -5
- package/sources/skills/mvt-decompose/business.md +94 -0
- package/sources/skills/mvt-decompose/manifest.yaml +121 -0
- package/sources/skills/mvt-fix/business.md +21 -6
- package/sources/skills/mvt-fix/manifest.yaml +1 -1
- package/sources/skills/mvt-help/business.md +11 -9
- package/sources/skills/mvt-help/manifest.yaml +0 -5
- package/sources/skills/mvt-implement/business.md +51 -8
- package/sources/skills/mvt-init/business.md +23 -13
- package/sources/skills/mvt-init/manifest.yaml +1 -2
- package/sources/skills/mvt-manage-context/business.md +41 -14
- package/sources/skills/mvt-manage-context/manifest.yaml +2 -6
- package/sources/skills/mvt-plan-dev/business.md +17 -9
- package/sources/skills/mvt-quick-dev/business.md +22 -7
- package/sources/skills/mvt-quick-dev/manifest.yaml +0 -1
- package/sources/skills/mvt-refactor/business.md +32 -17
- package/sources/skills/mvt-refactor/manifest.yaml +0 -5
- package/sources/skills/mvt-resume/business.md +32 -12
- package/sources/skills/mvt-resume/manifest.yaml +3 -3
- package/sources/skills/mvt-review/business.md +24 -9
- package/sources/skills/mvt-status/business.md +37 -9
- package/sources/skills/mvt-status/manifest.yaml +2 -2
- package/sources/skills/mvt-sync-context/business.md +30 -16
- package/sources/skills/mvt-template/business.md +1 -1
- package/sources/skills/mvt-template/manifest.yaml +0 -5
- package/sources/skills/mvt-test/business.md +30 -15
- package/sources/skills/mvt-update-plan/business.md +41 -12
- package/sources/skills/mvt-update-plan/manifest.yaml +7 -7
- package/sources/templates/decompose-output/body.md +13 -0
- package/sources/templates/decompose-output/manifest.yaml +11 -0
|
@@ -7316,9 +7316,36 @@ var require_dist = __commonJS({
|
|
|
7316
7316
|
|
|
7317
7317
|
// sources/scripts/plan-update.js
|
|
7318
7318
|
var import_node_fs = require("node:fs");
|
|
7319
|
+
var import_node_path = require("node:path");
|
|
7319
7320
|
var import_yaml = __toESM(require_dist(), 1);
|
|
7321
|
+
function findProjectRootFromPath(filePath) {
|
|
7322
|
+
let dir = (0, import_node_path.resolve)((0, import_node_path.dirname)(filePath));
|
|
7323
|
+
while (true) {
|
|
7324
|
+
if ((0, import_node_fs.existsSync)((0, import_node_path.join)(dir, ".ai-agents"))) return dir;
|
|
7325
|
+
const parent = (0, import_node_path.dirname)(dir);
|
|
7326
|
+
if (parent === dir) return null;
|
|
7327
|
+
dir = parent;
|
|
7328
|
+
}
|
|
7329
|
+
}
|
|
7330
|
+
function loadSoleProject(projectRoot) {
|
|
7331
|
+
if (!projectRoot) return null;
|
|
7332
|
+
const ctxPath = (0, import_node_path.join)(projectRoot, ".ai-agents/workspace/project-context.yaml");
|
|
7333
|
+
if (!(0, import_node_fs.existsSync)(ctxPath)) return null;
|
|
7334
|
+
try {
|
|
7335
|
+
const ctx = (0, import_yaml.parse)((0, import_node_fs.readFileSync)(ctxPath, "utf-8"));
|
|
7336
|
+
const projects = ctx?.projects;
|
|
7337
|
+
if (!Array.isArray(projects) || projects.length !== 1) return null;
|
|
7338
|
+
const name = projects[0]?.name;
|
|
7339
|
+
if (typeof name !== "string" || name === "") return null;
|
|
7340
|
+
return [name];
|
|
7341
|
+
} catch {
|
|
7342
|
+
return null;
|
|
7343
|
+
}
|
|
7344
|
+
}
|
|
7320
7345
|
var VALID_STATUSES = ["pending", "in_progress", "done", "blocked", "skipped"];
|
|
7321
7346
|
var TERMINAL_STATUSES = ["done", "blocked", "skipped"];
|
|
7347
|
+
var PROJECT_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
|
|
7348
|
+
var VALID_FRESHNESS = ["current", "stale"];
|
|
7322
7349
|
var ERRORS = {
|
|
7323
7350
|
MISSING_PLAN: () => "Missing required argument: --plan",
|
|
7324
7351
|
MISSING_TASK: () => "Missing required argument: --task",
|
|
@@ -7329,37 +7356,42 @@ var ERRORS = {
|
|
|
7329
7356
|
TASK_NOT_FOUND: (id, valid) => `Task "${id}" not found. Valid task ids: ${valid.length ? valid.join(", ") : "(none)"}.`,
|
|
7330
7357
|
VALIDATION_FAILED: (errs) => `Plan validation failed; file not written:
|
|
7331
7358
|
- ${errs.join("\n - ")}`,
|
|
7332
|
-
PLAN_WRITE_FAILED: (detail) => `Failed to write plan.yaml: ${detail}
|
|
7359
|
+
PLAN_WRITE_FAILED: (detail) => `Failed to write plan.yaml: ${detail}`,
|
|
7360
|
+
INVALID_PROJECT_NAME: (name) => `Invalid project name "${name}". Must match ${PROJECT_NAME_RE.source} (no leading underscore).`,
|
|
7361
|
+
INVALID_TASK_PROJECT: (taskId, proj, valid) => `Task "${taskId}" has project "${proj}" not in --projects list: ${valid.join(", ")}.`,
|
|
7362
|
+
INVALID_FRESHNESS: (taskId, val) => `Task "${taskId}" has invalid deliverables.freshness "${val}". Must be one of: ${VALID_FRESHNESS.join(", ")}.`,
|
|
7363
|
+
STALE_TASK_NOT_FOUND: (id, valid) => `--mark-deliverable-stale task "${id}" not found. Valid task ids: ${valid.length ? valid.join(", ") : "(none)"}.`,
|
|
7364
|
+
INVALID_DELIVERABLES_POINTER: (val) => `Invalid --deliverables-pointer "${val}". Only "current" is supported.`
|
|
7333
7365
|
};
|
|
7334
7366
|
function parseArgs(argv) {
|
|
7335
|
-
const
|
|
7367
|
+
const args2 = {};
|
|
7336
7368
|
for (let i = 2; i < argv.length; i++) {
|
|
7337
7369
|
if (argv[i].startsWith("--")) {
|
|
7338
7370
|
const key = argv[i].slice(2);
|
|
7339
7371
|
const next = argv[i + 1];
|
|
7340
7372
|
if (next && !next.startsWith("--")) {
|
|
7341
|
-
|
|
7373
|
+
args2[key] = next;
|
|
7342
7374
|
i++;
|
|
7343
7375
|
} else {
|
|
7344
|
-
|
|
7376
|
+
args2[key] = true;
|
|
7345
7377
|
}
|
|
7346
7378
|
}
|
|
7347
7379
|
}
|
|
7348
|
-
return
|
|
7380
|
+
return args2;
|
|
7349
7381
|
}
|
|
7350
|
-
function validateArgs(
|
|
7351
|
-
if (!
|
|
7352
|
-
if (!
|
|
7353
|
-
if (!
|
|
7354
|
-
if (!VALID_STATUSES.includes(
|
|
7382
|
+
function validateArgs(args2) {
|
|
7383
|
+
if (!args2.plan || args2.plan === true) return ERRORS.MISSING_PLAN();
|
|
7384
|
+
if (!args2.task || args2.task === true) return ERRORS.MISSING_TASK();
|
|
7385
|
+
if (!args2.status || args2.status === true) return ERRORS.MISSING_STATUS();
|
|
7386
|
+
if (!VALID_STATUSES.includes(args2.status)) return ERRORS.INVALID_STATUS(args2.status);
|
|
7355
7387
|
return null;
|
|
7356
7388
|
}
|
|
7357
|
-
function applyUpdate(plan,
|
|
7358
|
-
const task = plan.tasks.find((t) => t.id ===
|
|
7389
|
+
function applyUpdate(plan, args2, now) {
|
|
7390
|
+
const task = plan.tasks.find((t) => t.id === args2.task);
|
|
7359
7391
|
const oldStatus = task.status;
|
|
7360
|
-
task.status =
|
|
7361
|
-
if (
|
|
7362
|
-
const incoming =
|
|
7392
|
+
task.status = args2.status;
|
|
7393
|
+
if (args2.artifacts && args2.artifacts !== true) {
|
|
7394
|
+
const incoming = args2.artifacts.split(",").map((s) => s.trim()).filter(Boolean);
|
|
7363
7395
|
if (incoming.length) {
|
|
7364
7396
|
if (!task.artifacts || typeof task.artifacts !== "object") {
|
|
7365
7397
|
task.artifacts = { files: [] };
|
|
@@ -7376,48 +7408,110 @@ function applyUpdate(plan, args, now) {
|
|
|
7376
7408
|
}
|
|
7377
7409
|
}
|
|
7378
7410
|
}
|
|
7379
|
-
if (
|
|
7380
|
-
task.notes =
|
|
7411
|
+
if (args2.notes && args2.notes !== true) {
|
|
7412
|
+
task.notes = args2.notes;
|
|
7413
|
+
}
|
|
7414
|
+
if (args2.status === "done" && !task.completed_at) {
|
|
7415
|
+
task.completed_at = now;
|
|
7416
|
+
} else if (args2.status !== "done") {
|
|
7417
|
+
task.completed_at = null;
|
|
7418
|
+
}
|
|
7419
|
+
if (args2["deliverables-pointer"] && args2["deliverables-pointer"] !== true) {
|
|
7420
|
+
if (args2["deliverables-pointer"] !== "current") {
|
|
7421
|
+
return { error: ERRORS.INVALID_DELIVERABLES_POINTER(args2["deliverables-pointer"]) };
|
|
7422
|
+
}
|
|
7423
|
+
task.deliverables = { freshness: "current" };
|
|
7424
|
+
}
|
|
7425
|
+
if (args2["mark-deliverable-stale"] && args2["mark-deliverable-stale"] !== true) {
|
|
7426
|
+
const staleIds = args2["mark-deliverable-stale"].split(",").map((s) => s.trim()).filter(Boolean);
|
|
7427
|
+
for (const staleTaskId of staleIds) {
|
|
7428
|
+
const staleTask = plan.tasks.find((t) => t.id === staleTaskId);
|
|
7429
|
+
if (staleTask) {
|
|
7430
|
+
if (!staleTask.deliverables || typeof staleTask.deliverables !== "object") {
|
|
7431
|
+
staleTask.deliverables = { freshness: "stale" };
|
|
7432
|
+
} else {
|
|
7433
|
+
staleTask.deliverables.freshness = "stale";
|
|
7434
|
+
}
|
|
7435
|
+
}
|
|
7436
|
+
}
|
|
7381
7437
|
}
|
|
7382
|
-
task.completed_at = args.status === "done" ? now : null;
|
|
7383
7438
|
plan.updated_at = now;
|
|
7384
|
-
return { id: task.id, title: task.title || "", old_status: oldStatus, new_status:
|
|
7439
|
+
return { id: task.id, title: task.title || "", old_status: oldStatus, new_status: args2.status };
|
|
7385
7440
|
}
|
|
7386
|
-
function
|
|
7441
|
+
function recomputeCurrentTasks(plan, changedTaskId, projectList) {
|
|
7387
7442
|
let warning = null;
|
|
7388
7443
|
const changedTask = plan.tasks.find((t) => t.id === changedTaskId);
|
|
7389
7444
|
const changedToTerminal = changedTask && TERMINAL_STATUSES.includes(changedTask.status);
|
|
7390
|
-
const
|
|
7391
|
-
|
|
7392
|
-
);
|
|
7393
|
-
if (activeInProgress) {
|
|
7394
|
-
plan.current_task = activeInProgress.id;
|
|
7395
|
-
plan.status = "in_progress";
|
|
7396
|
-
return { warning };
|
|
7397
|
-
}
|
|
7398
|
-
const doneIds = new Set(
|
|
7399
|
-
plan.tasks.filter((t) => t.status === "done").map((t) => t.id)
|
|
7445
|
+
const priorActiveProjects = new Set(
|
|
7446
|
+
Object.keys(plan.current_tasks || {})
|
|
7400
7447
|
);
|
|
7401
|
-
const
|
|
7402
|
-
(t) => t.status === "
|
|
7448
|
+
const resolvedIds = new Set(
|
|
7449
|
+
plan.tasks.filter((t) => t.status === "done" || t.status === "skipped").map((t) => t.id)
|
|
7403
7450
|
);
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7451
|
+
const projects = projectList && projectList.length > 0 ? projectList : loadSoleProject(findProjectRootFromPath(args.plan)) || ["default"];
|
|
7452
|
+
const currentTasks = {};
|
|
7453
|
+
for (const proj of projects) {
|
|
7454
|
+
const inProgressForProject = plan.tasks.filter(
|
|
7455
|
+
(t) => t.status === "in_progress" && getTaskProjects(t).includes(proj)
|
|
7456
|
+
);
|
|
7457
|
+
if (inProgressForProject.length > 0) {
|
|
7458
|
+
currentTasks[proj] = inProgressForProject[0].id;
|
|
7459
|
+
continue;
|
|
7460
|
+
}
|
|
7461
|
+
const nextPending = plan.tasks.find(
|
|
7462
|
+
(t) => t.status === "pending" && getTaskProjects(t).includes(proj) && (t.depends_on || []).every((d) => resolvedIds.has(d))
|
|
7463
|
+
);
|
|
7464
|
+
if (nextPending) {
|
|
7465
|
+
nextPending.status = "in_progress";
|
|
7466
|
+
currentTasks[proj] = nextPending.id;
|
|
7467
|
+
}
|
|
7409
7468
|
}
|
|
7410
|
-
|
|
7469
|
+
let switchNotification = null;
|
|
7470
|
+
if (changedToTerminal && changedTask) {
|
|
7471
|
+
const newActiveProjects = new Set(Object.keys(currentTasks));
|
|
7472
|
+
const newlyActive = [...newActiveProjects].filter((p) => !priorActiveProjects.has(p));
|
|
7473
|
+
if (newlyActive.length > 0) {
|
|
7474
|
+
switchNotification = {
|
|
7475
|
+
project_switch: {
|
|
7476
|
+
from: [...priorActiveProjects].filter((p) => !newActiveProjects.has(p)),
|
|
7477
|
+
to: newlyActive
|
|
7478
|
+
}
|
|
7479
|
+
};
|
|
7480
|
+
}
|
|
7481
|
+
}
|
|
7482
|
+
const allDone = plan.tasks.every((t) => t.status === "done");
|
|
7483
|
+
const anyInProgress = plan.tasks.some((t) => t.status === "in_progress");
|
|
7484
|
+
const anyPending = plan.tasks.some((t) => t.status === "pending");
|
|
7485
|
+
if (allDone) {
|
|
7411
7486
|
plan.status = "done";
|
|
7412
|
-
plan.
|
|
7413
|
-
|
|
7487
|
+
plan.current_tasks = {};
|
|
7488
|
+
} else {
|
|
7489
|
+
plan.current_tasks = currentTasks;
|
|
7490
|
+
if (anyInProgress || Object.keys(currentTasks).length > 0) {
|
|
7491
|
+
plan.status = "in_progress";
|
|
7492
|
+
} else if (anyPending) {
|
|
7493
|
+
plan.status = "in_progress";
|
|
7494
|
+
warning = "All remaining tasks are blocked by dependencies; resolve a blocker before continuing.";
|
|
7495
|
+
}
|
|
7414
7496
|
}
|
|
7415
|
-
|
|
7416
|
-
plan.status = "in_progress";
|
|
7417
|
-
warning = "All remaining tasks are blocked by dependencies; resolve a blocker before continuing.";
|
|
7418
|
-
return { warning };
|
|
7497
|
+
return { warning, project_switch: switchNotification };
|
|
7419
7498
|
}
|
|
7420
|
-
function
|
|
7499
|
+
function getTaskProjects(task) {
|
|
7500
|
+
if (Array.isArray(task.project) && task.project.length > 0) {
|
|
7501
|
+
return task.project;
|
|
7502
|
+
}
|
|
7503
|
+
return ["default"];
|
|
7504
|
+
}
|
|
7505
|
+
function deriveProjectList(tasks) {
|
|
7506
|
+
const projects = /* @__PURE__ */ new Set();
|
|
7507
|
+
for (const t of tasks) {
|
|
7508
|
+
for (const p of getTaskProjects(t)) {
|
|
7509
|
+
projects.add(p);
|
|
7510
|
+
}
|
|
7511
|
+
}
|
|
7512
|
+
return [...projects];
|
|
7513
|
+
}
|
|
7514
|
+
function validatePlan(plan, projectList) {
|
|
7421
7515
|
const errors = [];
|
|
7422
7516
|
const tasks = Array.isArray(plan.tasks) ? plan.tasks : [];
|
|
7423
7517
|
const ids = tasks.map((t) => t.id);
|
|
@@ -7433,15 +7527,38 @@ function validatePlan(plan) {
|
|
|
7433
7527
|
}
|
|
7434
7528
|
}
|
|
7435
7529
|
}
|
|
7436
|
-
const cycle = findCycle(tasks);
|
|
7530
|
+
const cycle = findCycle(tasks, projectList);
|
|
7437
7531
|
if (cycle) {
|
|
7438
7532
|
errors.push(`Dependency cycle detected: ${cycle.join(" -> ")}`);
|
|
7439
7533
|
}
|
|
7440
|
-
const
|
|
7441
|
-
|
|
7442
|
-
|
|
7443
|
-
|
|
7534
|
+
const projects = projectList && projectList.length > 0 ? projectList : loadSoleProject(findProjectRootFromPath(args.plan)) || ["default"];
|
|
7535
|
+
for (const proj of projects) {
|
|
7536
|
+
const inProgressForProject = tasks.filter(
|
|
7537
|
+
(t) => t.status === "in_progress" && getTaskProjects(t).includes(proj)
|
|
7444
7538
|
);
|
|
7539
|
+
if (inProgressForProject.length > 1) {
|
|
7540
|
+
errors.push(
|
|
7541
|
+
`More than one task is in_progress for project "${proj}": ${inProgressForProject.map((t) => t.id).join(", ")}`
|
|
7542
|
+
);
|
|
7543
|
+
}
|
|
7544
|
+
}
|
|
7545
|
+
if (projectList && projectList.length > 0) {
|
|
7546
|
+
for (const t of tasks) {
|
|
7547
|
+
if (Array.isArray(t.project)) {
|
|
7548
|
+
for (const p of t.project) {
|
|
7549
|
+
if (!projectList.includes(p)) {
|
|
7550
|
+
errors.push(ERRORS.INVALID_TASK_PROJECT(t.id, p, projectList));
|
|
7551
|
+
}
|
|
7552
|
+
}
|
|
7553
|
+
}
|
|
7554
|
+
}
|
|
7555
|
+
}
|
|
7556
|
+
if (projectList && projectList.length > 0) {
|
|
7557
|
+
for (const p of projectList) {
|
|
7558
|
+
if (!PROJECT_NAME_RE.test(p)) {
|
|
7559
|
+
errors.push(ERRORS.INVALID_PROJECT_NAME(p));
|
|
7560
|
+
}
|
|
7561
|
+
}
|
|
7445
7562
|
}
|
|
7446
7563
|
for (const t of tasks) {
|
|
7447
7564
|
if (!Array.isArray(t.acceptance) || t.acceptance.length === 0) {
|
|
@@ -7453,27 +7570,63 @@ function validatePlan(plan) {
|
|
|
7453
7570
|
errors.push(`Task "${t.id}" is not done but has completed_at set`);
|
|
7454
7571
|
}
|
|
7455
7572
|
}
|
|
7573
|
+
for (const t of tasks) {
|
|
7574
|
+
if (t.deliverables && typeof t.deliverables === "object") {
|
|
7575
|
+
if (!VALID_FRESHNESS.includes(t.deliverables.freshness)) {
|
|
7576
|
+
errors.push(ERRORS.INVALID_FRESHNESS(t.id, t.deliverables.freshness));
|
|
7577
|
+
}
|
|
7578
|
+
}
|
|
7579
|
+
}
|
|
7456
7580
|
if (plan.status === "done") {
|
|
7457
|
-
if (plan.
|
|
7458
|
-
errors.push("plan.status is done but
|
|
7459
|
-
}
|
|
7460
|
-
} else if (plan.
|
|
7461
|
-
const
|
|
7462
|
-
|
|
7463
|
-
|
|
7464
|
-
|
|
7465
|
-
|
|
7466
|
-
|
|
7467
|
-
|
|
7581
|
+
if (plan.current_tasks && Object.keys(plan.current_tasks).length > 0) {
|
|
7582
|
+
errors.push("plan.status is done but current_tasks is not empty");
|
|
7583
|
+
}
|
|
7584
|
+
} else if (plan.current_tasks && typeof plan.current_tasks === "object") {
|
|
7585
|
+
for (const [proj, taskId] of Object.entries(plan.current_tasks)) {
|
|
7586
|
+
const ct = tasks.find((t) => t.id === taskId);
|
|
7587
|
+
if (!ct) {
|
|
7588
|
+
errors.push(`current_tasks["${proj}"] = "${taskId}" does not reference a task`);
|
|
7589
|
+
} else if (ct.status !== "pending" && ct.status !== "in_progress") {
|
|
7590
|
+
errors.push(
|
|
7591
|
+
`current_tasks["${proj}"] = "${taskId}" has status "${ct.status}" (must be pending or in_progress)`
|
|
7592
|
+
);
|
|
7593
|
+
}
|
|
7468
7594
|
}
|
|
7469
7595
|
}
|
|
7470
7596
|
return errors;
|
|
7471
7597
|
}
|
|
7472
|
-
function findCycle(tasks) {
|
|
7598
|
+
function findCycle(tasks, projectList) {
|
|
7599
|
+
if (!projectList || projectList.length <= 1) {
|
|
7600
|
+
return findCycleInSubgraph(tasks, tasks.map((t) => t.id));
|
|
7601
|
+
}
|
|
7602
|
+
const taskMap = new Map(tasks.map((t) => [t.id, t]));
|
|
7603
|
+
for (const proj of projectList) {
|
|
7604
|
+
const idSet = new Set(
|
|
7605
|
+
tasks.filter((t) => getTaskProjects(t).includes(proj)).map((t) => t.id)
|
|
7606
|
+
);
|
|
7607
|
+
const queue = [...idSet];
|
|
7608
|
+
for (const id of queue) {
|
|
7609
|
+
for (const dep of taskMap.get(id)?.depends_on || []) {
|
|
7610
|
+
if (!idSet.has(dep)) {
|
|
7611
|
+
idSet.add(dep);
|
|
7612
|
+
queue.push(dep);
|
|
7613
|
+
}
|
|
7614
|
+
}
|
|
7615
|
+
}
|
|
7616
|
+
const cycle = findCycleInSubgraph(tasks, [...idSet]);
|
|
7617
|
+
if (cycle) return cycle;
|
|
7618
|
+
}
|
|
7619
|
+
return null;
|
|
7620
|
+
}
|
|
7621
|
+
function findCycleInSubgraph(tasks, taskIds) {
|
|
7622
|
+
const idSet = new Set(taskIds);
|
|
7473
7623
|
const adj = /* @__PURE__ */ new Map();
|
|
7474
|
-
for (const t of tasks)
|
|
7624
|
+
for (const t of tasks) {
|
|
7625
|
+
if (!idSet.has(t.id)) continue;
|
|
7626
|
+
adj.set(t.id, (t.depends_on || []).filter((d) => idSet.has(d)));
|
|
7627
|
+
}
|
|
7475
7628
|
const WHITE = 0, GRAY = 1, BLACK = 2;
|
|
7476
|
-
const color = new Map(
|
|
7629
|
+
const color = new Map(taskIds.map((id) => [id, WHITE]));
|
|
7477
7630
|
const stack = [];
|
|
7478
7631
|
function dfs(node) {
|
|
7479
7632
|
color.set(node, GRAY);
|
|
@@ -7493,28 +7646,28 @@ function findCycle(tasks) {
|
|
|
7493
7646
|
color.set(node, BLACK);
|
|
7494
7647
|
return null;
|
|
7495
7648
|
}
|
|
7496
|
-
for (const
|
|
7497
|
-
if (color.get(
|
|
7498
|
-
const found = dfs(
|
|
7649
|
+
for (const id of taskIds) {
|
|
7650
|
+
if (color.get(id) === WHITE) {
|
|
7651
|
+
const found = dfs(id);
|
|
7499
7652
|
if (found) return found;
|
|
7500
7653
|
}
|
|
7501
7654
|
}
|
|
7502
7655
|
return null;
|
|
7503
7656
|
}
|
|
7504
7657
|
function main() {
|
|
7505
|
-
const
|
|
7506
|
-
const argErr = validateArgs(
|
|
7658
|
+
const args2 = parseArgs(process.argv);
|
|
7659
|
+
const argErr = validateArgs(args2);
|
|
7507
7660
|
if (argErr) {
|
|
7508
7661
|
process.stderr.write(argErr + "\n");
|
|
7509
7662
|
process.exit(1);
|
|
7510
7663
|
}
|
|
7511
|
-
if (!(0, import_node_fs.existsSync)(
|
|
7512
|
-
process.stderr.write(ERRORS.PLAN_NOT_FOUND(
|
|
7664
|
+
if (!(0, import_node_fs.existsSync)(args2.plan)) {
|
|
7665
|
+
process.stderr.write(ERRORS.PLAN_NOT_FOUND(args2.plan) + "\n");
|
|
7513
7666
|
process.exit(1);
|
|
7514
7667
|
}
|
|
7515
7668
|
let plan;
|
|
7516
7669
|
try {
|
|
7517
|
-
plan = (0, import_yaml.parse)((0, import_node_fs.readFileSync)(
|
|
7670
|
+
plan = (0, import_yaml.parse)((0, import_node_fs.readFileSync)(args2.plan, "utf-8"));
|
|
7518
7671
|
} catch (e) {
|
|
7519
7672
|
process.stderr.write(ERRORS.PLAN_PARSE_FAILED(e.message) + "\n");
|
|
7520
7673
|
process.exit(1);
|
|
@@ -7523,24 +7676,43 @@ function main() {
|
|
|
7523
7676
|
process.stderr.write(ERRORS.PLAN_PARSE_FAILED("missing tasks[]") + "\n");
|
|
7524
7677
|
process.exit(1);
|
|
7525
7678
|
}
|
|
7526
|
-
if (!plan.tasks.some((t) => t.id ===
|
|
7679
|
+
if (!plan.tasks.some((t) => t.id === args2.task)) {
|
|
7527
7680
|
process.stderr.write(
|
|
7528
|
-
ERRORS.TASK_NOT_FOUND(
|
|
7681
|
+
ERRORS.TASK_NOT_FOUND(args2.task, plan.tasks.map((t) => t.id)) + "\n"
|
|
7529
7682
|
);
|
|
7530
7683
|
process.exit(1);
|
|
7531
7684
|
}
|
|
7685
|
+
let projectList = null;
|
|
7686
|
+
if (args2.projects && args2.projects !== true) {
|
|
7687
|
+
projectList = args2.projects.split(",").map((s) => s.trim()).filter(Boolean);
|
|
7688
|
+
} else {
|
|
7689
|
+
projectList = deriveProjectList(plan.tasks);
|
|
7690
|
+
}
|
|
7691
|
+
if (plan.current_task != null && (!plan.current_tasks || typeof plan.current_tasks !== "object")) {
|
|
7692
|
+
plan.current_tasks = { default: plan.current_task };
|
|
7693
|
+
}
|
|
7694
|
+
if ("current_task" in plan) {
|
|
7695
|
+
delete plan.current_task;
|
|
7696
|
+
}
|
|
7697
|
+
if (!plan.current_tasks) {
|
|
7698
|
+
plan.current_tasks = {};
|
|
7699
|
+
}
|
|
7532
7700
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7533
|
-
const taskChange = applyUpdate(plan,
|
|
7534
|
-
|
|
7535
|
-
|
|
7701
|
+
const taskChange = applyUpdate(plan, args2, now);
|
|
7702
|
+
if (taskChange.error) {
|
|
7703
|
+
process.stderr.write(taskChange.error + "\n");
|
|
7704
|
+
process.exit(1);
|
|
7705
|
+
}
|
|
7706
|
+
const { warning, project_switch: switchNotif } = recomputeCurrentTasks(plan, args2.task, projectList);
|
|
7707
|
+
const validationErrors = validatePlan(plan, projectList);
|
|
7536
7708
|
if (validationErrors.length) {
|
|
7537
7709
|
process.stderr.write(ERRORS.VALIDATION_FAILED(validationErrors) + "\n");
|
|
7538
7710
|
process.exit(1);
|
|
7539
7711
|
}
|
|
7540
|
-
const tmpPath =
|
|
7712
|
+
const tmpPath = args2.plan + ".tmp";
|
|
7541
7713
|
try {
|
|
7542
|
-
(0, import_node_fs.writeFileSync)(tmpPath, (0, import_yaml.stringify)(plan), "utf-8");
|
|
7543
|
-
(0, import_node_fs.renameSync)(tmpPath,
|
|
7714
|
+
(0, import_node_fs.writeFileSync)(tmpPath, (0, import_yaml.stringify)(plan, { lineWidth: 200 }), "utf-8");
|
|
7715
|
+
(0, import_node_fs.renameSync)(tmpPath, args2.plan);
|
|
7544
7716
|
} catch (e) {
|
|
7545
7717
|
try {
|
|
7546
7718
|
if ((0, import_node_fs.existsSync)(tmpPath)) (0, import_node_fs.unlinkSync)(tmpPath);
|
|
@@ -7553,10 +7725,11 @@ function main() {
|
|
|
7553
7725
|
const result = {
|
|
7554
7726
|
ok: true,
|
|
7555
7727
|
task: taskChange,
|
|
7556
|
-
|
|
7728
|
+
current_tasks: plan.current_tasks,
|
|
7557
7729
|
plan_status: plan.status,
|
|
7558
7730
|
progress: { done: doneCount, total: plan.tasks.length },
|
|
7559
|
-
warning
|
|
7731
|
+
...warning ? { warning } : {},
|
|
7732
|
+
...switchNotif ? switchNotif : {}
|
|
7560
7733
|
};
|
|
7561
7734
|
process.stdout.write(JSON.stringify(result) + "\n");
|
|
7562
7735
|
}
|
|
@@ -7326,7 +7326,11 @@ var ERRORS = {
|
|
|
7326
7326
|
NO_SESSION_YAML: () => "session.yaml not found. Run /mvt-init first to initialize the project.",
|
|
7327
7327
|
SESSION_PARSE_FAILED: (detail) => `Failed to parse session.yaml: ${detail}. Check the file for syntax errors.`,
|
|
7328
7328
|
SESSION_WRITE_FAILED: (detail) => `Failed to write session.yaml: ${detail}`,
|
|
7329
|
-
CONFIG_LIMIT_INVALID: (key, val, min, max, fallback) => `Warning: config history_limits.${key} value "${val}" is invalid (must be integer ${min}-${max}). Using default ${fallback}
|
|
7329
|
+
CONFIG_LIMIT_INVALID: (key, val, min, max, fallback) => `Warning: config history_limits.${key} value "${val}" is invalid (must be integer ${min}-${max}). Using default ${fallback}.`,
|
|
7330
|
+
EPIC_ID_REQUIRED: () => "--new-epic requires --epic-id",
|
|
7331
|
+
CLOSE_NEW_EPIC_CONFLICT: () => "--close-epic and --new-epic are mutually exclusive",
|
|
7332
|
+
NO_ACTIVE_EPIC: (flag) => `${flag} requires an active epic (active_epic.id is empty)`,
|
|
7333
|
+
EPIC_ID_ORPHAN: () => "--epic-id (for sub-change) requires --new-change"
|
|
7330
7334
|
};
|
|
7331
7335
|
var DEFAULT_LIMITS = {
|
|
7332
7336
|
history: 20,
|
|
@@ -7388,6 +7392,9 @@ function validate(args) {
|
|
|
7388
7392
|
if (!args.skill) return ERRORS.MISSING_SKILL();
|
|
7389
7393
|
if (!args.summary) return ERRORS.MISSING_SUMMARY();
|
|
7390
7394
|
if (args["new-change"] && !args["change-id"]) return ERRORS.CHANGE_ID_REQUIRED();
|
|
7395
|
+
if (args["new-epic"] && !args["epic-id"]) return ERRORS.EPIC_ID_REQUIRED();
|
|
7396
|
+
if (args["close-epic"] && args["new-epic"]) return ERRORS.CLOSE_NEW_EPIC_CONFLICT();
|
|
7397
|
+
if (args["epic-id"] && !args["new-change"] && !args["new-epic"]) return ERRORS.EPIC_ID_ORPHAN();
|
|
7391
7398
|
return null;
|
|
7392
7399
|
}
|
|
7393
7400
|
function main() {
|
|
@@ -7440,7 +7447,8 @@ function main() {
|
|
|
7440
7447
|
title: session.active_change.title || "",
|
|
7441
7448
|
plan_path: session.active_change.plan_path || "",
|
|
7442
7449
|
status: "active",
|
|
7443
|
-
updated_at: now
|
|
7450
|
+
updated_at: now,
|
|
7451
|
+
epic_id: session.active_change.epic_id || ""
|
|
7444
7452
|
};
|
|
7445
7453
|
if (existingIdx >= 0) {
|
|
7446
7454
|
session.changes[existingIdx] = snapshotEntry;
|
|
@@ -7456,6 +7464,7 @@ function main() {
|
|
|
7456
7464
|
session.active_change.title = args["new-change"];
|
|
7457
7465
|
session.active_change.created_at = now;
|
|
7458
7466
|
session.active_change.plan_path = "";
|
|
7467
|
+
session.active_change.epic_id = args["epic-id"] || "";
|
|
7459
7468
|
}
|
|
7460
7469
|
if (args["set-initialized"]) {
|
|
7461
7470
|
session.session = session.session || {};
|
|
@@ -7482,7 +7491,8 @@ function main() {
|
|
|
7482
7491
|
title: ac.title || "",
|
|
7483
7492
|
plan_path: ac.plan_path || "",
|
|
7484
7493
|
status: "active",
|
|
7485
|
-
updated_at: now
|
|
7494
|
+
updated_at: now,
|
|
7495
|
+
epic_id: ac.epic_id || ""
|
|
7486
7496
|
};
|
|
7487
7497
|
if (existingIdx >= 0) {
|
|
7488
7498
|
session.changes[existingIdx] = entry;
|
|
@@ -7508,7 +7518,8 @@ function main() {
|
|
|
7508
7518
|
title: ac.title || "",
|
|
7509
7519
|
plan_path: ac.plan_path || "",
|
|
7510
7520
|
status: "done",
|
|
7511
|
-
updated_at: now
|
|
7521
|
+
updated_at: now,
|
|
7522
|
+
epic_id: ac.epic_id || ""
|
|
7512
7523
|
};
|
|
7513
7524
|
if (existingIdx >= 0) {
|
|
7514
7525
|
session.changes[existingIdx] = entry;
|
|
@@ -7526,7 +7537,8 @@ function main() {
|
|
|
7526
7537
|
id: "",
|
|
7527
7538
|
title: "",
|
|
7528
7539
|
created_at: "",
|
|
7529
|
-
plan_path: ""
|
|
7540
|
+
plan_path: "",
|
|
7541
|
+
epic_id: ""
|
|
7530
7542
|
};
|
|
7531
7543
|
}
|
|
7532
7544
|
if (args["set-change-status"]) {
|
|
@@ -7551,9 +7563,75 @@ function main() {
|
|
|
7551
7563
|
}
|
|
7552
7564
|
}
|
|
7553
7565
|
}
|
|
7566
|
+
if (args["new-epic"]) {
|
|
7567
|
+
session.active_epic = session.active_epic || {};
|
|
7568
|
+
if (session.active_epic.id) {
|
|
7569
|
+
session.epics = session.epics || [];
|
|
7570
|
+
const existingIdx = session.epics.findIndex(
|
|
7571
|
+
(e) => e.id === session.active_epic.id
|
|
7572
|
+
);
|
|
7573
|
+
const snapshotEntry = {
|
|
7574
|
+
id: session.active_epic.id,
|
|
7575
|
+
title: session.active_epic.title || "",
|
|
7576
|
+
epic_path: session.active_epic.epic_path || "",
|
|
7577
|
+
status: "active",
|
|
7578
|
+
updated_at: now
|
|
7579
|
+
};
|
|
7580
|
+
if (existingIdx >= 0) {
|
|
7581
|
+
session.epics[existingIdx] = snapshotEntry;
|
|
7582
|
+
} else {
|
|
7583
|
+
session.epics.push(snapshotEntry);
|
|
7584
|
+
}
|
|
7585
|
+
}
|
|
7586
|
+
session.active_epic.id = args["epic-id"];
|
|
7587
|
+
session.active_epic.title = args["new-epic"];
|
|
7588
|
+
session.active_epic.created_at = now;
|
|
7589
|
+
session.active_epic.epic_path = "";
|
|
7590
|
+
}
|
|
7591
|
+
if (args["set-epic-path"]) {
|
|
7592
|
+
session.active_epic = session.active_epic || {};
|
|
7593
|
+
if (!session.active_epic.id && !args["new-epic"]) {
|
|
7594
|
+
process.stderr.write(ERRORS.NO_ACTIVE_EPIC("--set-epic-path") + "\n");
|
|
7595
|
+
process.exit(1);
|
|
7596
|
+
}
|
|
7597
|
+
session.active_epic.epic_path = args["set-epic-path"];
|
|
7598
|
+
}
|
|
7599
|
+
if (args["set-epic-status"]) {
|
|
7600
|
+
session.active_epic = session.active_epic || {};
|
|
7601
|
+
if (!session.active_epic.id && !args["new-epic"]) {
|
|
7602
|
+
process.stderr.write(ERRORS.NO_ACTIVE_EPIC("--set-epic-status") + "\n");
|
|
7603
|
+
process.exit(1);
|
|
7604
|
+
}
|
|
7605
|
+
session.epics = session.epics || [];
|
|
7606
|
+
const epicIdx = session.epics.findIndex(
|
|
7607
|
+
(e) => e.id === session.active_epic.id
|
|
7608
|
+
);
|
|
7609
|
+
if (epicIdx >= 0) {
|
|
7610
|
+
session.epics[epicIdx].status = args["set-epic-status"];
|
|
7611
|
+
session.epics[epicIdx].updated_at = now;
|
|
7612
|
+
}
|
|
7613
|
+
}
|
|
7614
|
+
if (args["close-epic"]) {
|
|
7615
|
+
session.epics = session.epics || [];
|
|
7616
|
+
session.active_epic = session.active_epic || {};
|
|
7617
|
+
const aeId = session.active_epic.id;
|
|
7618
|
+
if (aeId) {
|
|
7619
|
+
const epicIdx = session.epics.findIndex((e) => e.id === aeId);
|
|
7620
|
+
if (epicIdx >= 0) {
|
|
7621
|
+
session.epics[epicIdx].status = "done";
|
|
7622
|
+
session.epics[epicIdx].updated_at = now;
|
|
7623
|
+
}
|
|
7624
|
+
}
|
|
7625
|
+
session.active_epic = {
|
|
7626
|
+
id: "",
|
|
7627
|
+
title: "",
|
|
7628
|
+
created_at: "",
|
|
7629
|
+
epic_path: ""
|
|
7630
|
+
};
|
|
7631
|
+
}
|
|
7554
7632
|
const tmpPath = sessionPath + ".tmp";
|
|
7555
7633
|
try {
|
|
7556
|
-
(0, import_node_fs.writeFileSync)(tmpPath, (0, import_yaml.stringify)(session), "utf-8");
|
|
7634
|
+
(0, import_node_fs.writeFileSync)(tmpPath, (0, import_yaml.stringify)(session, { lineWidth: 200 }), "utf-8");
|
|
7557
7635
|
(0, import_node_fs.renameSync)(tmpPath, sessionPath);
|
|
7558
7636
|
} catch (e) {
|
|
7559
7637
|
try {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type PlatformId = "claude" | "qoder";
|
|
2
|
+
export interface PlatformDef {
|
|
3
|
+
id: PlatformId;
|
|
4
|
+
dir: string;
|
|
5
|
+
skillDir: string;
|
|
6
|
+
description: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const PLATFORMS: PlatformDef[];
|
|
9
|
+
export declare const DEFAULT_PLATFORMS: PlatformId[];
|
|
10
|
+
export declare function getPlatformById(id: string): PlatformDef | undefined;
|
|
11
|
+
export declare function getPlatformsByIds(ids: PlatformId[]): PlatformDef[];
|
|
12
|
+
//# sourceMappingURL=platform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../../src/types/platform.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE5C,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,UAAU,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,SAAS,EAAE,WAAW,EAalC,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,UAAU,EAAe,CAAC;AAE1D,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAEnE;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,WAAW,EAAE,CAIlE"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const PLATFORMS = [
|
|
2
|
+
{
|
|
3
|
+
id: "claude",
|
|
4
|
+
dir: ".claude",
|
|
5
|
+
skillDir: ".claude/skills",
|
|
6
|
+
description: "Claude Code / Claude Desktop / Github Copilot",
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
id: "qoder",
|
|
10
|
+
dir: ".qoder",
|
|
11
|
+
skillDir: ".qoder/skills",
|
|
12
|
+
description: "Qoder IDE",
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
export const DEFAULT_PLATFORMS = ["claude"];
|
|
16
|
+
export function getPlatformById(id) {
|
|
17
|
+
return PLATFORMS.find((p) => p.id === id);
|
|
18
|
+
}
|
|
19
|
+
export function getPlatformsByIds(ids) {
|
|
20
|
+
return ids
|
|
21
|
+
.map((id) => getPlatformById(id))
|
|
22
|
+
.filter((p) => p !== undefined);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=platform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../../src/types/platform.ts"],"names":[],"mappings":"AASA,MAAM,CAAC,MAAM,SAAS,GAAkB;IACtC;QACE,EAAE,EAAE,QAAQ;QACZ,GAAG,EAAE,SAAS;QACd,QAAQ,EAAE,gBAAgB;QAC1B,WAAW,EAAE,+CAA+C;KAC7D;IACD;QACE,EAAE,EAAE,OAAO;QACX,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,eAAe;QACzB,WAAW,EAAE,WAAW;KACzB;CACF,CAAC;AAEF,MAAM,CAAC,MAAM,iBAAiB,GAAiB,CAAC,QAAQ,CAAC,CAAC;AAE1D,MAAM,UAAU,eAAe,CAAC,EAAU;IACxC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,GAAiB;IACjD,OAAO,GAAG;SACP,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC,CAAC;SAChC,MAAM,CAAC,CAAC,CAAC,EAAoB,EAAE,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;AACtD,CAAC"}
|