@uoyo/mvtt 2.2.0 → 2.2.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.
@@ -7397,12 +7397,15 @@ function parseArgs(argv) {
7397
7397
  }
7398
7398
  return args2;
7399
7399
  }
7400
+ function hasValue(value) {
7401
+ return value !== void 0 && value !== true && String(value).trim() !== "";
7402
+ }
7400
7403
  function validateArgs(args2) {
7401
7404
  if (args2.validate) return null;
7402
- if (!args2.plan || args2.plan === true) return ERRORS.MISSING_PLAN();
7403
- if (!args2.task || args2.task === true) return ERRORS.MISSING_TASK();
7404
- const hasStatus = args2.status && args2.status !== true;
7405
- const hasMutation = hasStatus || args2.artifacts && args2.artifacts !== true || args2.notes && args2.notes !== true || args2["deliverables-pointer"] && args2["deliverables-pointer"] !== true || args2["mark-deliverable-stale"] && args2["mark-deliverable-stale"] !== true;
7405
+ if (!hasValue(args2.plan)) return ERRORS.MISSING_PLAN();
7406
+ if (!hasValue(args2.task)) return ERRORS.MISSING_TASK();
7407
+ const hasStatus = hasValue(args2.status);
7408
+ const hasMutation = hasStatus || hasValue(args2.artifacts) || hasValue(args2.notes) || hasValue(args2["deliverables-pointer"]) || hasValue(args2["mark-deliverable-stale"]);
7406
7409
  if (!hasMutation) return ERRORS.MISSING_STATUS();
7407
7410
  if (args2.status === true) return ERRORS.MISSING_STATUS();
7408
7411
  if (hasStatus && !VALID_STATUSES.includes(args2.status)) return ERRORS.INVALID_STATUS(args2.status);
@@ -7411,10 +7414,10 @@ function validateArgs(args2) {
7411
7414
  function applyUpdate(plan, args2, now) {
7412
7415
  const task = plan.tasks.find((t) => t.id === args2.task);
7413
7416
  const oldStatus = task.status;
7414
- if (args2.status && args2.status !== true) {
7417
+ if (hasValue(args2.status)) {
7415
7418
  task.status = args2.status;
7416
7419
  }
7417
- if (args2.artifacts && args2.artifacts !== true) {
7420
+ if (hasValue(args2.artifacts)) {
7418
7421
  const incoming = args2.artifacts.split(",").map((s) => s.trim()).filter(Boolean);
7419
7422
  if (incoming.length) {
7420
7423
  if (!task.artifacts || typeof task.artifacts !== "object") {
@@ -7432,23 +7435,23 @@ function applyUpdate(plan, args2, now) {
7432
7435
  }
7433
7436
  }
7434
7437
  }
7435
- if (args2.notes && args2.notes !== true) {
7438
+ if (hasValue(args2.notes)) {
7436
7439
  task.notes = args2.notes;
7437
7440
  }
7438
- if (args2.status && args2.status !== true) {
7441
+ if (hasValue(args2.status)) {
7439
7442
  if (args2.status === "done" && !task.completed_at) {
7440
7443
  task.completed_at = now;
7441
7444
  } else if (args2.status !== "done") {
7442
7445
  task.completed_at = null;
7443
7446
  }
7444
7447
  }
7445
- if (args2["deliverables-pointer"] && args2["deliverables-pointer"] !== true) {
7448
+ if (hasValue(args2["deliverables-pointer"])) {
7446
7449
  if (args2["deliverables-pointer"] !== "current") {
7447
7450
  return { error: ERRORS.INVALID_DELIVERABLES_POINTER(args2["deliverables-pointer"]) };
7448
7451
  }
7449
7452
  task.deliverables = { freshness: "current" };
7450
7453
  }
7451
- if (args2["mark-deliverable-stale"] && args2["mark-deliverable-stale"] !== true) {
7454
+ if (hasValue(args2["mark-deliverable-stale"])) {
7452
7455
  const staleIds = args2["mark-deliverable-stale"].split(",").map((s) => s.trim()).filter(Boolean);
7453
7456
  for (const staleTaskId of staleIds) {
7454
7457
  const staleTask = plan.tasks.find((t) => t.id === staleTaskId);
@@ -7706,7 +7709,7 @@ function main() {
7706
7709
  process.exit(1);
7707
7710
  }
7708
7711
  let projectList = null;
7709
- if (args2.projects && args2.projects !== true) {
7712
+ if (hasValue(args2.projects)) {
7710
7713
  projectList = args2.projects.split(",").map((s) => s.trim()).filter(Boolean);
7711
7714
  } else {
7712
7715
  projectList = deriveProjectList(plan.tasks);
@@ -7349,7 +7349,8 @@ var ERRORS = {
7349
7349
  CLOSE_NEW_EPIC_CONFLICT: () => "--close-epic and --new-epic are mutually exclusive",
7350
7350
  NO_ACTIVE_EPIC: (flag) => `${flag} requires an active epic (active_epic.id is empty)`,
7351
7351
  EPIC_ID_ORPHAN: () => "--epic-id (for sub-change) requires --new-change",
7352
- MISSING_REMOVE_VALUE: () => "--remove-change / --remove-epic requires a non-empty value"
7352
+ MISSING_REMOVE_VALUE: () => "--remove-change / --remove-epic requires a non-empty value",
7353
+ MISSING_FLAG_VALUE: (flag) => `${flag} requires a non-empty value`
7353
7354
  };
7354
7355
  var DEFAULT_LIMITS = {
7355
7356
  history: 20,
@@ -7388,6 +7389,9 @@ function parseIdList(value) {
7388
7389
  if (value == null) return [];
7389
7390
  return String(value).split(",").map((s) => s.trim()).filter(Boolean);
7390
7391
  }
7392
+ function hasValue(value) {
7393
+ return value !== void 0 && value !== true && String(value).trim() !== "";
7394
+ }
7391
7395
  function loadHistoryLimits(configPath) {
7392
7396
  const limits = { ...DEFAULT_LIMITS };
7393
7397
  if (!(0, import_node_fs.existsSync)(configPath)) return limits;
@@ -7413,11 +7417,22 @@ function loadHistoryLimits(configPath) {
7413
7417
  }
7414
7418
  function validate(args) {
7415
7419
  if (!args.skill) return ERRORS.MISSING_SKILL();
7420
+ if (args.skill === true) return ERRORS.MISSING_FLAG_VALUE("--skill");
7416
7421
  if (!args.summary) return ERRORS.MISSING_SUMMARY();
7422
+ if (args.summary === true) return ERRORS.MISSING_FLAG_VALUE("--summary");
7423
+ if (args["new-change"] !== void 0 && !hasValue(args["new-change"])) return ERRORS.MISSING_FLAG_VALUE("--new-change");
7417
7424
  if (args["new-change"] && !args["change-id"]) return ERRORS.CHANGE_ID_REQUIRED();
7425
+ if (args["change-id"] !== void 0 && !hasValue(args["change-id"])) return ERRORS.MISSING_FLAG_VALUE("--change-id");
7418
7426
  if (args["new-epic"] && !args["epic-id"]) return ERRORS.EPIC_ID_REQUIRED();
7427
+ if (args["new-epic"] !== void 0 && !hasValue(args["new-epic"])) return ERRORS.MISSING_FLAG_VALUE("--new-epic");
7428
+ if (args["epic-id"] !== void 0 && !hasValue(args["epic-id"])) return ERRORS.MISSING_FLAG_VALUE("--epic-id");
7419
7429
  if (args["close-epic"] && args["new-epic"]) return ERRORS.CLOSE_NEW_EPIC_CONFLICT();
7420
7430
  if (args["epic-id"] && !args["new-change"] && !args["new-epic"]) return ERRORS.EPIC_ID_ORPHAN();
7431
+ for (const flag of ["set-plan-path", "set-change-status", "truncate-history", "set-epic-path", "set-epic-status"]) {
7432
+ if (args[flag] !== void 0 && !hasValue(args[flag])) {
7433
+ return ERRORS.MISSING_FLAG_VALUE(`--${flag}`);
7434
+ }
7435
+ }
7421
7436
  if (args["remove-change"] !== void 0 && (args["remove-change"] === true || !String(args["remove-change"]).trim())) {
7422
7437
  return ERRORS.MISSING_REMOVE_VALUE();
7423
7438
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uoyo/mvtt",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "My Virtual Tech Team - AI-guided prompt orchestration framework",
5
5
  "type": "module",
6
6
  "bin": {
@@ -43,10 +43,10 @@
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^25.6.0",
46
- "@vitest/coverage-v8": "^2.1.9",
46
+ "@vitest/coverage-v8": "^4.1.9",
47
47
  "esbuild": "^0.28.0",
48
48
  "typescript": "^5.4.0",
49
- "vitest": "^2.0.0"
49
+ "vitest": "^4.1.9"
50
50
  },
51
51
  "dependencies": {
52
52
  "@clack/prompts": "^1.5.1",
@@ -116,16 +116,20 @@ function parseArgs(argv) {
116
116
  return args;
117
117
  }
118
118
 
119
+ function hasValue(value) {
120
+ return value !== undefined && value !== true && String(value).trim() !== "";
121
+ }
122
+
119
123
  function validateArgs(args) {
120
124
  if (args.validate) return null;
121
- if (!args.plan || args.plan === true) return ERRORS.MISSING_PLAN();
122
- if (!args.task || args.task === true) return ERRORS.MISSING_TASK();
123
- const hasStatus = args.status && args.status !== true;
125
+ if (!hasValue(args.plan)) return ERRORS.MISSING_PLAN();
126
+ if (!hasValue(args.task)) return ERRORS.MISSING_TASK();
127
+ const hasStatus = hasValue(args.status);
124
128
  const hasMutation = hasStatus ||
125
- (args.artifacts && args.artifacts !== true) ||
126
- (args.notes && args.notes !== true) ||
127
- (args["deliverables-pointer"] && args["deliverables-pointer"] !== true) ||
128
- (args["mark-deliverable-stale"] && args["mark-deliverable-stale"] !== true);
129
+ hasValue(args.artifacts) ||
130
+ hasValue(args.notes) ||
131
+ hasValue(args["deliverables-pointer"]) ||
132
+ hasValue(args["mark-deliverable-stale"]);
129
133
  if (!hasMutation) return ERRORS.MISSING_STATUS();
130
134
  if (args.status === true) return ERRORS.MISSING_STATUS();
131
135
  if (hasStatus && !VALID_STATUSES.includes(args.status)) return ERRORS.INVALID_STATUS(args.status);
@@ -137,11 +141,11 @@ function applyUpdate(plan, args, now) {
137
141
  const task = plan.tasks.find((t) => t.id === args.task);
138
142
 
139
143
  const oldStatus = task.status;
140
- if (args.status && args.status !== true) {
144
+ if (hasValue(args.status)) {
141
145
  task.status = args.status;
142
146
  }
143
147
 
144
- if (args.artifacts && args.artifacts !== true) {
148
+ if (hasValue(args.artifacts)) {
145
149
  const incoming = args.artifacts
146
150
  .split(",")
147
151
  .map((s) => s.trim())
@@ -163,12 +167,12 @@ function applyUpdate(plan, args, now) {
163
167
  }
164
168
  }
165
169
 
166
- if (args.notes && args.notes !== true) {
170
+ if (hasValue(args.notes)) {
167
171
  task.notes = args.notes;
168
172
  }
169
173
 
170
174
  // completed_at consistency: set only on status updates.
171
- if (args.status && args.status !== true) {
175
+ if (hasValue(args.status)) {
172
176
  if (args.status === "done" && !task.completed_at) {
173
177
  task.completed_at = now;
174
178
  } else if (args.status !== "done") {
@@ -177,7 +181,7 @@ function applyUpdate(plan, args, now) {
177
181
  }
178
182
 
179
183
  // --deliverables-pointer current
180
- if (args["deliverables-pointer"] && args["deliverables-pointer"] !== true) {
184
+ if (hasValue(args["deliverables-pointer"])) {
181
185
  if (args["deliverables-pointer"] !== "current") {
182
186
  return { error: ERRORS.INVALID_DELIVERABLES_POINTER(args["deliverables-pointer"]) };
183
187
  }
@@ -186,7 +190,7 @@ function applyUpdate(plan, args, now) {
186
190
 
187
191
  // --mark-deliverable-stale <task_id>[,task_id2,...]
188
192
  // Supports comma-separated list of downstream task ids.
189
- if (args["mark-deliverable-stale"] && args["mark-deliverable-stale"] !== true) {
193
+ if (hasValue(args["mark-deliverable-stale"])) {
190
194
  const staleIds = args["mark-deliverable-stale"]
191
195
  .split(",")
192
196
  .map((s) => s.trim())
@@ -551,7 +555,7 @@ function main() {
551
555
 
552
556
  // Parse --projects; if not provided, derive from tasks
553
557
  let projectList = null;
554
- if (args.projects && args.projects !== true) {
558
+ if (hasValue(args.projects)) {
555
559
  projectList = args.projects.split(",").map((s) => s.trim()).filter(Boolean);
556
560
  } else {
557
561
  // Derive project list from task.project arrays so that validation
@@ -41,6 +41,7 @@ const ERRORS = {
41
41
  NO_ACTIVE_EPIC: (flag) => `${flag} requires an active epic (active_epic.id is empty)`,
42
42
  EPIC_ID_ORPHAN: () => "--epic-id (for sub-change) requires --new-change",
43
43
  MISSING_REMOVE_VALUE: () => "--remove-change / --remove-epic requires a non-empty value",
44
+ MISSING_FLAG_VALUE: (flag) => `${flag} requires a non-empty value`,
44
45
  };
45
46
 
46
47
  // ── Defaults ────────────────────────────────────────────────────────────────
@@ -94,6 +95,10 @@ function parseIdList(value) {
94
95
  .filter(Boolean);
95
96
  }
96
97
 
98
+ function hasValue(value) {
99
+ return value !== undefined && value !== true && String(value).trim() !== "";
100
+ }
101
+
97
102
  // ── Config Loading ──────────────────────────────────────────────────────────
98
103
  function loadHistoryLimits(configPath) {
99
104
  const limits = { ...DEFAULT_LIMITS };
@@ -126,14 +131,26 @@ function loadHistoryLimits(configPath) {
126
131
  // ── Validation ──────────────────────────────────────────────────────────────
127
132
  function validate(args) {
128
133
  if (!args.skill) return ERRORS.MISSING_SKILL();
134
+ if (args.skill === true) return ERRORS.MISSING_FLAG_VALUE("--skill");
129
135
  if (!args.summary) return ERRORS.MISSING_SUMMARY();
136
+ if (args.summary === true) return ERRORS.MISSING_FLAG_VALUE("--summary");
137
+ if (args["new-change"] !== undefined && !hasValue(args["new-change"])) return ERRORS.MISSING_FLAG_VALUE("--new-change");
130
138
  if (args["new-change"] && !args["change-id"]) return ERRORS.CHANGE_ID_REQUIRED();
139
+ if (args["change-id"] !== undefined && !hasValue(args["change-id"])) return ERRORS.MISSING_FLAG_VALUE("--change-id");
131
140
 
132
141
  // Epic combo validation
133
142
  if (args["new-epic"] && !args["epic-id"]) return ERRORS.EPIC_ID_REQUIRED();
143
+ if (args["new-epic"] !== undefined && !hasValue(args["new-epic"])) return ERRORS.MISSING_FLAG_VALUE("--new-epic");
144
+ if (args["epic-id"] !== undefined && !hasValue(args["epic-id"])) return ERRORS.MISSING_FLAG_VALUE("--epic-id");
134
145
  if (args["close-epic"] && args["new-epic"]) return ERRORS.CLOSE_NEW_EPIC_CONFLICT();
135
146
  if (args["epic-id"] && !args["new-change"] && !args["new-epic"]) return ERRORS.EPIC_ID_ORPHAN();
136
147
 
148
+ for (const flag of ["set-plan-path", "set-change-status", "truncate-history", "set-epic-path", "set-epic-status"]) {
149
+ if (args[flag] !== undefined && !hasValue(args[flag])) {
150
+ return ERRORS.MISSING_FLAG_VALUE(`--${flag}`);
151
+ }
152
+ }
153
+
137
154
  // Remove flags require non-empty values
138
155
  if (
139
156
  args["remove-change"] !== undefined
@@ -9,7 +9,7 @@ This skill is read-only and does NOT modify `.ai-agents/workspace/session.yaml`.
9
9
  After the skill's main task, run the session update script **exactly once**:
10
10
 
11
11
  ```bash
12
- node .ai-agents/scripts/session-update.cjs --skill {{current_skill}} --summary "<concise one-line summary>"{{#update_active_change}} --new-change "<active_change.title>" --change-id <active_change.id>{{#link_subchange_to_epic}} --epic-id <active_epic.id>{{/link_subchange_to_epic}}{{/update_active_change}}{{#set_plan_path}} --set-plan-path ".ai-agents/workspace/artifacts/{active_change.id}/plan.yaml"{{/set_plan_path}}{{#update_change}} --update-change{{/update_change}}{{#close_change}} --close-change{{/close_change}}{{#set_change_status}} --set-change-status <status>{{/set_change_status}}{{#new_epic}} --new-epic "<epic_title>" --epic-id <epic_id>{{/new_epic}}{{#set_epic_path}} --set-epic-path <epic_path>{{/set_epic_path}}{{#set_epic_status}} --set-epic-status <status>{{/set_epic_status}}{{#close_epic}} --close-epic{{/close_epic}}{{#no_change}} --no-change{{/no_change}}{{#set_synced}} --set-synced{{/set_synced}}{{#truncate_history}} --truncate-history <count>{{/truncate_history}}{{#update_initialized_at}} --set-initialized{{/update_initialized_at}}{{#remove_change}} --remove-change <ids>{{/remove_change}}{{#remove_epic}} --remove-epic <ids>{{/remove_epic}}
12
+ node .ai-agents/scripts/session-update.cjs --skill {{current_skill}} --summary "<concise one-line summary>"{{#update_active_change}} --new-change "<active_change.title>" --change-id <active_change.id>{{#link_subchange_to_epic}} [--epic-id <active_epic.id>]{{/link_subchange_to_epic}}{{/update_active_change}}{{#set_plan_path}} --set-plan-path ".ai-agents/workspace/artifacts/{active_change.id}/plan.yaml"{{/set_plan_path}}{{#update_change}} --update-change{{/update_change}}{{#close_change}} --close-change{{/close_change}}{{#set_change_status}} --set-change-status <status>{{/set_change_status}}{{#new_epic}} --new-epic "<epic_title>" --epic-id <epic_id>{{/new_epic}}{{#set_epic_path}} --set-epic-path <epic_path>{{/set_epic_path}}{{#set_epic_status}} --set-epic-status <status>{{/set_epic_status}}{{#close_epic}} --close-epic{{/close_epic}}{{#no_change}} --no-change{{/no_change}}{{#set_synced}} --set-synced{{/set_synced}}{{#truncate_history}} --truncate-history <count>{{/truncate_history}}{{#update_initialized_at}} --set-initialized{{/update_initialized_at}}{{#remove_change}} --remove-change <ids>{{/remove_change}}{{#remove_epic}} --remove-epic <ids>{{/remove_epic}}
13
13
 
14
14
  ```
15
15
 
@@ -64,7 +64,7 @@ Write `--summary` as one concise line in the configured `interaction_language`.
64
64
  - `--remove-epic <ids>` removes entries with matching `id` from `session.epics[]` (comma-separated for multiple ids); does NOT touch `active_epic`. Unknown ids are silently skipped; if all ids are unknown, a warning is written to stderr (exit code remains 0).
65
65
  {{/remove_epic}}
66
66
  {{#link_subchange_to_epic}}
67
- - `--epic-id` with `--new-change` links the new active change to its parent epic; do not use it outside `--new-epic` or `--new-change`.
67
+ - `--epic-id` with `--new-change` links the new active change to its parent epic; include it only when `active_epic.id` is non-empty. Do not pass `--epic-id` with an empty placeholder.
68
68
  {{/link_subchange_to_epic}}
69
69
 
70
70
  If the script exits with code 0, the state update was applied successfully; do not read or verify the session file.