@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.
Files changed (86) hide show
  1. package/README.md +299 -64
  2. package/README.zh-CN.md +419 -0
  3. package/dist/commands/install.d.ts.map +1 -1
  4. package/dist/commands/install.js +27 -2
  5. package/dist/commands/install.js.map +1 -1
  6. package/dist/commands/uninstall.d.ts.map +1 -1
  7. package/dist/commands/uninstall.js +19 -7
  8. package/dist/commands/uninstall.js.map +1 -1
  9. package/dist/commands/update.d.ts.map +1 -1
  10. package/dist/commands/update.js +4 -2
  11. package/dist/commands/update.js.map +1 -1
  12. package/dist/fs/install-manifest.d.ts +4 -1
  13. package/dist/fs/install-manifest.d.ts.map +1 -1
  14. package/dist/fs/install-manifest.js +13 -1
  15. package/dist/fs/install-manifest.js.map +1 -1
  16. package/dist/fs/materialize.d.ts +2 -0
  17. package/dist/fs/materialize.d.ts.map +1 -1
  18. package/dist/fs/materialize.js +39 -9
  19. package/dist/fs/materialize.js.map +1 -1
  20. package/dist/fs/registry-merge.d.ts.map +1 -1
  21. package/dist/fs/registry-merge.js +72 -29
  22. package/dist/fs/registry-merge.js.map +1 -1
  23. package/dist/scripts/epic-update.cjs +7670 -0
  24. package/dist/scripts/plan-update.cjs +255 -82
  25. package/dist/scripts/session-update.cjs +84 -6
  26. package/dist/types/platform.d.ts +12 -0
  27. package/dist/types/platform.d.ts.map +1 -0
  28. package/dist/types/platform.js +24 -0
  29. package/dist/types/platform.js.map +1 -0
  30. package/dist/types/registry.d.ts +3 -24
  31. package/dist/types/registry.d.ts.map +1 -1
  32. package/install-manifest.yaml +4 -0
  33. package/package.json +1 -1
  34. package/registry.yaml +72 -198
  35. package/sources/defaults/config.yaml +8 -13
  36. package/sources/defaults/project-context.yaml +2 -5
  37. package/sources/defaults/session.yaml +14 -2
  38. package/sources/knowledge/core/manifest.yaml +1 -4
  39. package/sources/scripts/epic-update.js +512 -0
  40. package/sources/scripts/plan-update.js +614 -353
  41. package/sources/scripts/session-update.js +102 -2
  42. package/sources/sections/activation-load-config.md +1 -1
  43. package/sources/sections/activation-load-context.md +42 -13
  44. package/sources/sections/activation-preflight.md +1 -1
  45. package/sources/sections/footer-next-steps.md +3 -2
  46. package/sources/sections/session-update.md +41 -1
  47. package/sources/skills/mvt-analyze/business.md +46 -8
  48. package/sources/skills/mvt-analyze/manifest.yaml +5 -1
  49. package/sources/skills/mvt-analyze-code/business.md +18 -17
  50. package/sources/skills/mvt-analyze-code/manifest.yaml +3 -6
  51. package/sources/skills/mvt-check-context/business.md +13 -6
  52. package/sources/skills/mvt-check-context/manifest.yaml +0 -5
  53. package/sources/skills/mvt-cleanup/business.md +17 -2
  54. package/sources/skills/mvt-config/business.md +5 -5
  55. package/sources/skills/mvt-config/manifest.yaml +3 -6
  56. package/sources/skills/mvt-create-skill/business.md +2 -14
  57. package/sources/skills/mvt-create-skill/manifest.yaml +0 -5
  58. package/sources/skills/mvt-decompose/business.md +94 -0
  59. package/sources/skills/mvt-decompose/manifest.yaml +121 -0
  60. package/sources/skills/mvt-fix/business.md +21 -6
  61. package/sources/skills/mvt-fix/manifest.yaml +1 -1
  62. package/sources/skills/mvt-help/business.md +11 -9
  63. package/sources/skills/mvt-help/manifest.yaml +0 -5
  64. package/sources/skills/mvt-implement/business.md +51 -8
  65. package/sources/skills/mvt-init/business.md +23 -13
  66. package/sources/skills/mvt-init/manifest.yaml +1 -2
  67. package/sources/skills/mvt-manage-context/business.md +41 -14
  68. package/sources/skills/mvt-manage-context/manifest.yaml +2 -6
  69. package/sources/skills/mvt-plan-dev/business.md +17 -9
  70. package/sources/skills/mvt-quick-dev/business.md +22 -7
  71. package/sources/skills/mvt-quick-dev/manifest.yaml +0 -1
  72. package/sources/skills/mvt-refactor/business.md +32 -17
  73. package/sources/skills/mvt-refactor/manifest.yaml +0 -5
  74. package/sources/skills/mvt-resume/business.md +32 -12
  75. package/sources/skills/mvt-resume/manifest.yaml +3 -3
  76. package/sources/skills/mvt-review/business.md +24 -9
  77. package/sources/skills/mvt-status/business.md +37 -9
  78. package/sources/skills/mvt-status/manifest.yaml +2 -2
  79. package/sources/skills/mvt-sync-context/business.md +30 -16
  80. package/sources/skills/mvt-template/business.md +1 -1
  81. package/sources/skills/mvt-template/manifest.yaml +0 -5
  82. package/sources/skills/mvt-test/business.md +30 -15
  83. package/sources/skills/mvt-update-plan/business.md +41 -12
  84. package/sources/skills/mvt-update-plan/manifest.yaml +7 -7
  85. package/sources/templates/decompose-output/body.md +13 -0
  86. package/sources/templates/decompose-output/manifest.yaml +11 -0
@@ -23,7 +23,12 @@
23
23
  * [--set-change-status <status>] \
24
24
  * [--no-change] \
25
25
  * [--set-synced] \
26
- * [--truncate-history <n>]
26
+ * [--truncate-history <n>] \
27
+ * [--new-epic <title> --epic-id <id>] \
28
+ * [--set-epic-path <path>] \
29
+ * [--set-epic-status <status>] \
30
+ * [--close-epic] \
31
+ * [--epic-id <id>] (with --new-change, links sub-change to epic)
27
32
  *
28
33
  * Output:
29
34
  * Success (exit 0): {"ok":true}
@@ -48,6 +53,10 @@ const ERRORS = {
48
53
  SESSION_WRITE_FAILED: (detail) => `Failed to write session.yaml: ${detail}`,
49
54
  CONFIG_LIMIT_INVALID: (key, val, min, max, fallback) =>
50
55
  `Warning: config history_limits.${key} value "${val}" is invalid (must be integer ${min}-${max}). Using default ${fallback}.`,
56
+ EPIC_ID_REQUIRED: () => "--new-epic requires --epic-id",
57
+ CLOSE_NEW_EPIC_CONFLICT: () => "--close-epic and --new-epic are mutually exclusive",
58
+ NO_ACTIVE_EPIC: (flag) => `${flag} requires an active epic (active_epic.id is empty)`,
59
+ EPIC_ID_ORPHAN: () => "--epic-id (for sub-change) requires --new-change",
51
60
  };
52
61
 
53
62
  // ── Defaults ────────────────────────────────────────────────────────────────
@@ -124,6 +133,12 @@ function validate(args) {
124
133
  if (!args.skill) return ERRORS.MISSING_SKILL();
125
134
  if (!args.summary) return ERRORS.MISSING_SUMMARY();
126
135
  if (args["new-change"] && !args["change-id"]) return ERRORS.CHANGE_ID_REQUIRED();
136
+
137
+ // Epic combo validation (§9.1.1, ADR-10)
138
+ if (args["new-epic"] && !args["epic-id"]) return ERRORS.EPIC_ID_REQUIRED();
139
+ if (args["close-epic"] && args["new-epic"]) return ERRORS.CLOSE_NEW_EPIC_CONFLICT();
140
+ if (args["epic-id"] && !args["new-change"] && !args["new-epic"]) return ERRORS.EPIC_ID_ORPHAN();
141
+
127
142
  return null;
128
143
  }
129
144
 
@@ -197,6 +212,7 @@ function main() {
197
212
  plan_path: session.active_change.plan_path || "",
198
213
  status: "active",
199
214
  updated_at: now,
215
+ epic_id: session.active_change.epic_id || "",
200
216
  };
201
217
  if (existingIdx >= 0) {
202
218
  session.changes[existingIdx] = snapshotEntry;
@@ -215,6 +231,7 @@ function main() {
215
231
  session.active_change.title = args["new-change"];
216
232
  session.active_change.created_at = now;
217
233
  session.active_change.plan_path = "";
234
+ session.active_change.epic_id = args["epic-id"] || "";
218
235
  }
219
236
 
220
237
  // --set-initialized
@@ -252,6 +269,7 @@ function main() {
252
269
  plan_path: ac.plan_path || "",
253
270
  status: "active",
254
271
  updated_at: now,
272
+ epic_id: ac.epic_id || "",
255
273
  };
256
274
  if (existingIdx >= 0) {
257
275
  session.changes[existingIdx] = entry;
@@ -281,6 +299,7 @@ function main() {
281
299
  plan_path: ac.plan_path || "",
282
300
  status: "done",
283
301
  updated_at: now,
302
+ epic_id: ac.epic_id || "",
284
303
  };
285
304
  if (existingIdx >= 0) {
286
305
  session.changes[existingIdx] = entry;
@@ -300,6 +319,7 @@ function main() {
300
319
  title: "",
301
320
  created_at: "",
302
321
  plan_path: "",
322
+ epic_id: "",
303
323
  };
304
324
  }
305
325
 
@@ -329,11 +349,91 @@ function main() {
329
349
  }
330
350
  }
331
351
 
352
+ // ── Epic flags ──────────────────────────────────────────────────────────
353
+
354
+ // --new-epic: auto-snapshot old active_epic, then set new one
355
+ if (args["new-epic"]) {
356
+ session.active_epic = session.active_epic || {};
357
+
358
+ // Auto-snapshot: if there's an existing active_epic with an id, upsert into epics[]
359
+ if (session.active_epic.id) {
360
+ session.epics = session.epics || [];
361
+ const existingIdx = session.epics.findIndex(
362
+ (e) => e.id === session.active_epic.id
363
+ );
364
+ const snapshotEntry = {
365
+ id: session.active_epic.id,
366
+ title: session.active_epic.title || "",
367
+ epic_path: session.active_epic.epic_path || "",
368
+ status: "active",
369
+ updated_at: now,
370
+ };
371
+ if (existingIdx >= 0) {
372
+ session.epics[existingIdx] = snapshotEntry;
373
+ } else {
374
+ session.epics.push(snapshotEntry);
375
+ }
376
+ }
377
+
378
+ // Set new active_epic
379
+ session.active_epic.id = args["epic-id"];
380
+ session.active_epic.title = args["new-epic"];
381
+ session.active_epic.created_at = now;
382
+ session.active_epic.epic_path = "";
383
+ }
384
+
385
+ // --set-epic-path: set active_epic.epic_path
386
+ if (args["set-epic-path"]) {
387
+ session.active_epic = session.active_epic || {};
388
+ if (!session.active_epic.id && !args["new-epic"]) {
389
+ process.stderr.write(ERRORS.NO_ACTIVE_EPIC("--set-epic-path") + "\n");
390
+ process.exit(1);
391
+ }
392
+ session.active_epic.epic_path = args["set-epic-path"];
393
+ }
394
+
395
+ // --set-epic-status: update matching epics[] entry status
396
+ if (args["set-epic-status"]) {
397
+ session.active_epic = session.active_epic || {};
398
+ if (!session.active_epic.id && !args["new-epic"]) {
399
+ process.stderr.write(ERRORS.NO_ACTIVE_EPIC("--set-epic-status") + "\n");
400
+ process.exit(1);
401
+ }
402
+ session.epics = session.epics || [];
403
+ const epicIdx = session.epics.findIndex(
404
+ (e) => e.id === session.active_epic.id
405
+ );
406
+ if (epicIdx >= 0) {
407
+ session.epics[epicIdx].status = args["set-epic-status"];
408
+ session.epics[epicIdx].updated_at = now;
409
+ }
410
+ }
411
+
412
+ // --close-epic: set matching epics[] entry to done, clear active_epic
413
+ if (args["close-epic"]) {
414
+ session.epics = session.epics || [];
415
+ session.active_epic = session.active_epic || {};
416
+ const aeId = session.active_epic.id;
417
+ if (aeId) {
418
+ const epicIdx = session.epics.findIndex((e) => e.id === aeId);
419
+ if (epicIdx >= 0) {
420
+ session.epics[epicIdx].status = "done";
421
+ session.epics[epicIdx].updated_at = now;
422
+ }
423
+ }
424
+ session.active_epic = {
425
+ id: "",
426
+ title: "",
427
+ created_at: "",
428
+ epic_path: "",
429
+ };
430
+ }
431
+
332
432
  // ── Write back atomically ─────────────────────────────────────────────
333
433
  const tmpPath = sessionPath + ".tmp";
334
434
 
335
435
  try {
336
- writeFileSync(tmpPath, stringifyYaml(session), "utf-8");
436
+ writeFileSync(tmpPath, stringifyYaml(session, { lineWidth: 200 }), "utf-8");
337
437
  renameSync(tmpPath, sessionPath);
338
438
  } catch (e) {
339
439
  try {
@@ -1,4 +1,4 @@
1
- ### Step 3: Load Config & Apply Preferences (Config Foundation)
1
+ ### Step 4: Load Config & Apply Preferences (Config Foundation)
2
2
  Read `.ai-agents/config.yaml` and enforce the following throughout this entire session:
3
3
 
4
4
  **Language**:
@@ -1,8 +1,7 @@
1
1
  ## Activation Protocol
2
2
 
3
- ### Step 1: Load Context (Context Foundation)
4
- Load the following files as foundational context:
5
- - `.ai-agents/workspace/session.yaml` -- Current workflow state
3
+ ### Step 1: Load Context
4
+ Load these files as foundational context:
6
5
  - `.ai-agents/workspace/project-context.yaml` -- Project index (structural info)
7
6
  - `.ai-agents/registry.yaml` -- Available skills registry and knowledge declarations
8
7
  {{?extended_context}}
@@ -13,18 +12,48 @@ Extended context for this skill:
13
12
  - {{.}}
14
13
  {{/extended_context}}
15
14
 
16
- ### Step 2: Load Knowledge
15
+ ### Step 2: Resolve Project Scope (PS)
17
16
 
18
- Read `.ai-agents/registry.yaml` and load every file referenced under:
19
- - `knowledge.shared` (loaded by all skills)
20
- - `skills.<current-skill>.knowledge` (this skill's specific knowledge, if present)
17
+ Read `project-context.yaml > projects[]`.
21
18
 
22
- For each entry, resolve files relative to `.ai-agents/{source}`:
23
- - If the entry lists `files: [...]`, load those files.
24
- - If the entry lists `files_from_manifest: true`, read `{source}/manifest.yaml` and load every `files[]` entry where `auto_load: true`.
19
+ **Single project** (`projects.length == 1`): Set PS = [sole project name]. Skip remaining PS steps.
25
20
 
26
- Skip any path that does not exist.
21
+ **Multi-project** (`projects.length > 1`):
22
+ **Mode A -- Plan-driven** (active plan exists and skill operates on plan tasks):
23
+ 1. **Plan signal**: PS = current task's `project` array from plan's `current_tasks`. Drop stale project names (not in `projects[]`), fall through.
24
+ 2. **Path match**: Match current working paths against `projects[].path` and `source_paths`.
25
+ 3. **Prompt**: If still unresolved, list candidates and ask user. Never silently load all projects.
27
26
 
28
- ### Archived Artifacts Convention
27
+ **Mode B -- Non-plan** (no active plan or ad-hoc changes):
28
+ Defer PS to execution: identify change target, match against `projects[].path` and `source_paths`, load project-specific knowledge on demand (Step 3).
29
29
 
30
- The directory `.ai-agents/workspace/artifacts/_archived/` contains change-id directories that have been archived by `/mvt-cleanup`. All skills that scan `artifacts/` MUST exclude `_archived/` from their scan scope unless explicitly inspecting archived content.
30
+ ### Step 3: Load Knowledge
31
+
32
+ Registry uses project-keyed maps; `_all` is a reserved key (all projects). Applies to both top-level `knowledge` and `skills.<name>.knowledge`.
33
+
34
+ **Knowledge Loading Protocol**:
35
+ For each knowledge entry in the registry, follow these steps:
36
+ 1. **Read the `source` field** from the registry entry (e.g., `knowledge/project/_generated/`).
37
+ 2. **Construct the base directory**: join `.ai-agents/` with the `source` value → `.ai-agents/{source_value}/`.
38
+ 3. **Load files**:
39
+ - `files: [a.md, b.md]` → load `.ai-agents/{source_value}/a.md`, `.ai-agents/{source_value}/b.md`.
40
+ - `files_from_manifest: true` → read `.ai-agents/{source_value}/manifest.yaml`, load entries with `auto_load: true`.
41
+ 4. **Skip non-existent paths** silently (do not error or warn).
42
+
43
+ **Worked example**:
44
+ Given this registry entry:
45
+ ```yaml
46
+ - id: project-context
47
+ source: knowledge/project/_generated/
48
+ files:
49
+ - project-context.md
50
+ ```
51
+ Resolution: `.ai-agents/` + `knowledge/project/_generated/` + `project-context.md` = `.ai-agents/knowledge/project/_generated/project-context.md`
52
+
53
+ **Anti-pattern -- DO NOT**:
54
+ - Guess or hardcode base directories (e.g., `.ai-agents/workspace/`).
55
+ - Assume a default path structure. The `source` field value is the authoritative path component.
56
+
57
+ **At activation** (both modes): load `knowledge._all` + `skills.<current-skill>.knowledge._all`.
58
+ **Mode A** (additionally): for each P in PS, load `knowledge[P]` + `skills.<current-skill>.knowledge[P]`.
59
+ **Mode B** (during execution): on demand, load `knowledge[P]` + `skills.<current-skill>.knowledge[P]` for identified project(s).
@@ -1,4 +1,4 @@
1
- ### Step 4: Pre-flight Checks
1
+ ### Step 5: Pre-flight Checks
2
2
 
3
3
  For each check below, if the condition holds, perform the action implied by its **Level**:
4
4
 
@@ -1,6 +1,7 @@
1
1
  ## Suggested Next Steps
2
2
 
3
3
  Recommend 2-3 relevant next skills based on the skill just completed (`{{current_skill}}`) and the current project state.
4
+ **Candidate set constraint (mandatory)**: Only recommend skills that are declared under `skills` in `.ai-agents/registry.yaml`.
4
5
  {{#conditional_suggestions}}
5
6
 
6
7
  ### Conditional Recommendations
@@ -21,11 +22,11 @@ Match the current state to one of the conditions below. If none match, use `defa
21
22
 
22
23
  ### Resolution order
23
24
 
24
- Infer 2-3 suggestions from:
25
+ Infer 2-3 suggestions, choosing **only** from the skills declared under `skills` in `registry.yaml`:
25
26
  - `history` in `session.yaml`
26
27
  - `category` and `description` of each skill in `registry.yaml`
27
28
  - The current `active_change` state (if in progress)
28
- - The `depends_on` relationships between skills
29
+ - The standard workflow order (analyze → design → implement → review → test)
29
30
  {{/conditional_suggestions}}
30
31
 
31
32
  ### Format
@@ -9,7 +9,7 @@ This skill is read-only and does NOT modify `.ai-agents/workspace/session.yaml`.
9
9
  After completing the skill's main task, run the session update script **exactly once** with the following arguments:
10
10
 
11
11
  ```bash
12
- node .ai-agents/scripts/session-update.cjs --skill <skill_command_name> --summary "<concise one-line summary>"{{#update_active_change}} --new-change "<active_change.title>" --change-id <active_change.id>{{/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}}{{#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}}
12
+ node .ai-agents/scripts/session-update.cjs --skill <skill_command_name> --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}}
13
13
 
14
14
  ```
15
15
 
@@ -49,6 +49,22 @@ If the script exits with code 0, the state update was applied successfully; ther
49
49
  {{#update_initialized_at}}
50
50
  | `--set-initialized` | Flag only, no value. Set when this skill initializes the project for the first time. | — |
51
51
  {{/update_initialized_at}}
52
+ {{#new_epic}}
53
+ | `--new-epic` | The title of the new epic being created (same value written to `active_epic.title`) | `"ecommerce platform"` |
54
+ | `--epic-id` | The unique identifier of the new epic. Required when using `--new-epic`. Format: `epic-{YYYYMMDD}-{slug}`. | `"epic-20260608-ecommerce-platform"` |
55
+ {{/new_epic}}
56
+ {{#set_epic_path}}
57
+ | `--set-epic-path` | The path to the written `epic.yaml` file. Sets `active_epic.epic_path`. | `".ai-agents/workspace/artifacts/epic-20260608-ecommerce-platform/epic.yaml"` |
58
+ {{/set_epic_path}}
59
+ {{#set_epic_status}}
60
+ | `--set-epic-status` | The status to set on the `epics[]` entry matching `active_epic.id`. Values: `in_progress`, `done`, `abandoned`. | `done` |
61
+ {{/set_epic_status}}
62
+ {{#close_epic}}
63
+ | `--close-epic` | Flag only, no value. Snapshots `active_epic` into `epics[]` with `status: done`, then clears all `active_epic` fields. | — |
64
+ {{/close_epic}}
65
+ {{#link_subchange_to_epic}}
66
+ | `--epic-id` (with `--new-change`) | The parent epic id that this new sub-change belongs to. Only valid when used together with `--new-change`. | `"epic-20260608-ecommerce-platform"` |
67
+ {{/link_subchange_to_epic}}
52
68
 
53
69
  {{#update_active_change}}
54
70
  ### Parameter semantics
@@ -56,6 +72,9 @@ If the script exits with code 0, the state update was applied successfully; ther
56
72
  | Argument | When to use | Effect on `session.yaml` |
57
73
  |----------|-------------|--------------------------|
58
74
  | `--new-change` + `--change-id` | Skill creates or identifies a new change | Sets `active_change.id`, `.title`, `.created_at`. Auto-snapshots old `active_change` into `changes[]` if non-empty. Requires both arguments together. |
75
+ {{#link_subchange_to_epic}}
76
+ | `--epic-id` (with `--new-change`) | Skill creates a new sub-change inside an existing epic (epic-child mode) | Writes `active_change.epic_id` so the new sub-change is linked to the parent epic. Only valid when used together with `--new-change`. |
77
+ {{/link_subchange_to_epic}}
59
78
  {{#set_plan_path}}
60
79
  | `--set-plan-path` | Skill creates a new `plan.yaml` for the active change | Sets `active_change.plan_path`. Must be used together with `--update-change`. |
61
80
  {{/set_plan_path}}
@@ -68,6 +87,15 @@ If the script exits with code 0, the state update was applied successfully; ther
68
87
  {{#set_change_status}}
69
88
  | `--set-change-status` | Explicitly mark a change as `done` or `abandoned` | Sets `status` on the `changes[]` entry whose `id` matches `active_change.id`. |
70
89
  {{/set_change_status}}
90
+ {{#set_epic_path}}
91
+ | `--set-epic-path` | Skill writes or moves the `epic.yaml` file (e.g., after `/mvt-decompose` writes the artifacts) | Sets `active_epic.epic_path`. |
92
+ {{/set_epic_path}}
93
+ {{#set_epic_status}}
94
+ | `--set-epic-status` | Skill marks the active epic as `done` or `abandoned` (rarely used directly — `epic-update.cjs` usually drives this) | Sets `status` on the `epics[]` entry whose `id` matches `active_epic.id`. |
95
+ {{/set_epic_status}}
96
+ {{#close_epic}}
97
+ | `--close-epic` | Skill closes the active epic (e.g., after archiving a completed epic) | Snapshots `active_epic` into `epics[]` with `status: done`, then clears all `active_epic` fields. |
98
+ {{/close_epic}}
71
99
  {{#no_change}}
72
100
  | `--no-change` | Skill should not be associated with any change | Forces `history[].change_id` to empty string, skipping the `active_change.id` fallback. |
73
101
  {{/no_change}}
@@ -86,6 +114,9 @@ If the script exits with code 0, the state update was applied successfully; ther
86
114
 
87
115
  | Argument | When to use | Effect on `session.yaml` |
88
116
  |----------|-------------|--------------------------|
117
+ {{#new_epic}}
118
+ | `--new-epic` + `--epic-id` | Skill creates a new epic (e.g., `/mvt-decompose`) | Sets `active_epic.{id,title,created_at}`. Auto-snapshots old `active_epic` into `epics[]` if non-empty. Requires both arguments together. |
119
+ {{/new_epic}}
89
120
  {{#update_change}}
90
121
  | `--update-change` | Skill modifies a plan (i.e., after `plan.yaml` is updated) | Upserts current `active_change` into `changes[]` (with `status: active`), sets `updated_at`, sorts ascending, truncates to configured limit. |
91
122
  {{/update_change}}
@@ -95,6 +126,15 @@ If the script exits with code 0, the state update was applied successfully; ther
95
126
  {{#set_change_status}}
96
127
  | `--set-change-status` | Explicitly mark a change as `done` or `abandoned` | Sets `status` on the `changes[]` entry whose `id` matches `active_change.id`. |
97
128
  {{/set_change_status}}
129
+ {{#set_epic_path}}
130
+ | `--set-epic-path` | Skill writes or moves the `epic.yaml` file (e.g., after `/mvt-decompose` writes the artifacts) | Sets `active_epic.epic_path`. |
131
+ {{/set_epic_path}}
132
+ {{#set_epic_status}}
133
+ | `--set-epic-status` | Skill marks the active epic as `done` or `abandoned` (rarely used directly — `epic-update.cjs` usually drives this) | Sets `status` on the `epics[]` entry whose `id` matches `active_epic.id`. |
134
+ {{/set_epic_status}}
135
+ {{#close_epic}}
136
+ | `--close-epic` | Skill closes the active epic (e.g., after archiving a completed epic) | Snapshots `active_epic` into `epics[]` with `status: done`, then clears all `active_epic` fields. |
137
+ {{/close_epic}}
98
138
  {{#no_change}}
99
139
  | `--no-change` | Skill should not be associated with any change | Forces `history[].change_id` to empty string, skipping the `active_change.id` fallback. |
100
140
  {{/no_change}}
@@ -1,3 +1,15 @@
1
+ ## Epic-Child Mode (Pre-check)
2
+
3
+ **When**: `active_epic.id` is non-empty AND `active_change.id` is empty.
4
+
5
+ In this state the user is starting a new sub-change within an existing epic. Read `epic.yaml` via `active_epic.epic_path` and determine the scenario:
6
+
7
+ | Scenario | User message | Handling |
8
+ |----------|-------------|----------|
9
+ | A | Empty | Auto-use `current_change` child's scope from `epic.yaml` as the requirement input. Proceed to Step 3. |
10
+ | B | Supplements current child | Merge user message with `current_change` child's scope. Proceed to Step 3. |
11
+ | C | Points to different child | Locate target in `children[]`. If `depends_on` has unfinished prerequisites → warn and ask to confirm forced reorder (y/n). If deps satisfied → confirm switch (y/n). On confirmed reorder: call `epic-update.cjs --epic <epic_path> --switch-active <target_id>`. If target not in `children[]` → offer to treat as independent change (exit epic-child mode) or `--add-child`. |
12
+
1
13
  ## Execution Flow
2
14
 
3
15
  ### Step 1: Load Requirements
@@ -10,7 +22,33 @@
10
22
  - Extract business rules and constraints
11
23
  - Note assumptions made
12
24
 
13
- ### Step 3: Assess Complexity (Quick Path Detection)
25
+ ### Step 3: Assess Scale (Epic Detection)
26
+ - **What**: evaluate whether the input is an epic-scale requirement that should be decomposed into multiple sub-changes via `/mvt-decompose`.
27
+ - **Signals**:
28
+
29
+ | Signal type | Signal | Example |
30
+ |-------------|--------|---------|
31
+ | Strong | Whole system / platform scope | "Build an e-commerce system" |
32
+ | Strong | Input is a multi-feature design manual | "Implement based on this design manual" |
33
+ | Strong | Multiple independent deliverable capability domains | Auth + Catalog + Cart + Payment |
34
+ | Weak (corroboration only) | Multiple actors with multiple independent main flows | -- |
35
+ | Weak (corroboration only) | No single cohesive acceptance criterion | -- |
36
+
37
+ - **Trigger**: any strong signal, OR (strong + 2+ weak). Weak signals alone never trigger.
38
+
39
+ - **Branches**:
40
+
41
+ | Condition | Action |
42
+ |-----------|--------|
43
+ | Epic detection hits | Ask: "This looks like an epic-level requirement (multiple independent capability domains). Use `/mvt-decompose` to decompose it first? (y / n / show-signals)" |
44
+ | `y` | Do NOT write `analysis.md`. Guide to `/mvt-decompose`. |
45
+ | `n` | Continue standard analysis (Steps 4-7). Cheap reversal path. |
46
+ | `show-signals` | Display matched signals, re-prompt. |
47
+ | Epic misses | Fall through to Step 4 (Quick Path Detection). |
48
+
49
+ - **Epic-child mode note**: When operating in epic-child mode (scenarios A or B from the pre-check), Step 3 should treat the selected child scope as the intended change boundary. Do not re-route to `/mvt-decompose` unless the user explicitly expands the request beyond that child or the scope is clearly still epic-scale (e.g., the child scope itself contains multiple independent capability domains that were not part of the original decomposition rationale).
50
+
51
+ ### Step 4: Assess Complexity (Quick Path Detection)
14
52
  - **What**: evaluate whether this requirement qualifies as a simple change suitable for the quick development path via `/mvt-quick-dev`.
15
53
  - **How**: check each criterion in the table below. ALL criteria must pass for the quick path to be offered.
16
54
 
@@ -40,30 +78,30 @@
40
78
  - Scope: ✗ touches auth middleware, user model, login UI, OAuth callback handler, config (5+ files)
41
79
  - No new concepts: ✗ introduces external IdP and OAuth callback contract
42
80
  - No integration concerns: ✗ new external dependency (Google IdP)
43
- → Proceed with standard analysis flow (Steps 4-6).
81
+ → Proceed with standard analysis flow (Steps 5-7).
44
82
 
45
83
  - **Branches**:
46
84
 
47
85
  | Condition | Action |
48
86
  |-----------|--------|
49
87
  | ALL criteria pass | Ask user: "This appears to be a simple change (1-3 files, no architectural impact). Use /mvt-quick-dev for faster execution? (y / n / show-criteria)" |
50
- | ANY criterion fails | Proceed with standard analysis flow (Steps 4-6) |
88
+ | ANY criterion fails | Proceed with standard analysis flow (Steps 5-7) |
51
89
  | Ambiguous (2-3 criteria unclear) | Proceed with standard analysis; do NOT offer quick path |
52
90
 
53
91
  - **On user choice**:
54
92
  - "y" -- Do NOT write an analysis artifact. Summarize the requirement understanding in conversation and recommend `/mvt-quick-dev` directly. Set `active_change` if one doesn't exist, so `/mvt-quick-dev` can reference the current work context.
55
- - "n" -- Continue with full analysis flow (Steps 4-6).
93
+ - "n" -- Continue with full analysis flow (Steps 5-7).
56
94
  - "show-criteria" -- Display the assessment results (pass/fail per criterion), then re-prompt with y/n.
57
95
 
58
- ### Step 4: Detect Ambiguities
96
+ ### Step 5: Detect Ambiguities
59
97
  - Check for unclear requirements
60
98
  - Check for missing information
61
99
  - Check for conflicting requirements
62
100
 
63
- ### Step 5: Generate Clarification Questions
101
+ ### Step 6: Generate Clarification Questions
64
102
  - If ambiguities found -> List each with specific question, prioritized by impact
65
103
  - If no ambiguities -> Skip this step
66
104
 
67
- ### Step 6: Update Workspace
68
- 1. Generate change-id: `{YYYYMMDD}-{slug}` format (e.g., `20260425-user-authentication`)
105
+ ### Step 7: Update Workspace
106
+ 1. Generate change-id: `{YYYYMMDD}-{slug}` format (e.g., `20260425-user-authentication`). Slug constraints: lowercase ASCII, kebab-case, `[a-z0-9-]+`, 1-4 words.
69
107
  2. Write artifact: `.ai-agents/workspace/artifacts/{change-id}/analysis.md`
@@ -76,6 +76,7 @@ sections:
76
76
  params:
77
77
  current_skill: mvt-analyze
78
78
  update_active_change: true
79
+ link_subchange_to_epic: true
79
80
 
80
81
  - type: shared
81
82
  source: sections/footer-next-steps.md
@@ -83,7 +84,10 @@ sections:
83
84
  current_skill: mvt-analyze
84
85
  conditional_suggestions:
85
86
  conditions:
86
- - condition: "user chose quick path in Step 2.5"
87
+ - condition: "epic-scale detected in Step 3 (Epic Detection) and user chose y"
88
+ primary: "mvt-decompose"
89
+ primary_desc: "Decompose this epic-scale requirement into sub-changes"
90
+ - condition: "user chose quick path in Step 4 (Quick Path Detection)"
87
91
  primary: "mvt-quick-dev"
88
92
  primary_desc: "Implement this simple change quickly"
89
93
  - condition: "default"
@@ -2,17 +2,15 @@
2
2
 
3
3
  ### Step 1: Determine Analysis Target
4
4
 
5
- Identify which project(s) to analyze:
5
+ Identify which project(s) to analyze using interactive routing:
6
6
 
7
- | Variant | Target |
8
- |---------|--------|
9
- | `/mvt-analyze-code` | Analyze the first project in `project-context.yaml` (or the only one) |
10
- | `/mvt-analyze-code --all` | Analyze all projects listed in `project-context.yaml` |
11
- | `/mvt-analyze-code {name}` | Analyze the project matching the given name |
12
-
13
- For each target project:
14
- 1. Read its `path` from `project-context.yaml`
15
- 2. Use `path` as the source directory for analysis
7
+ 1. Read `project-context.yaml > projects[]` to get the list of registered projects.
8
+ 2. **Single project** (only one entry): automatically select it — no prompt needed.
9
+ 3. **Multiple projects**: present an interactive selection menu:
10
+ - List each project by `name` with its `path`
11
+ - Include an option to **analyze all projects**
12
+ - Wait for user selection before proceeding
13
+ 4. For each selected project, read its `path` and use it as the source directory for analysis.
16
14
 
17
15
  ### Step 2: Load Template
18
16
 
@@ -72,11 +70,14 @@ Identify public interfaces:
72
70
  - Fill each template section with analysis results
73
71
  - If a section has no relevant content, include the heading with "(No relevant content detected)"
74
72
 
75
- 2. Write the output to `.ai-agents/knowledge/project/_generated/project-context.md`:
76
- - If analyzing a single project, write that project's section
77
- - If analyzing multiple projects (`--all`), write all sections separated by `---`
78
- - If the file already exists, merge with existing content:
79
- - Replace sections for re-analyzed projects
80
- - Preserve sections for projects NOT in this analysis run
73
+ 2. Write the output to `.ai-agents/knowledge/project/_generated/project-context.md` (always the flat path, regardless of project count).
74
+ - **Single-project**: write the full document.
75
+ - **Multi-project**: use `# Project: {name}` as the top-level heading to separate each project's content sections within the single file.
76
+
77
+ 3. If the output file already exists:
78
+ - **Single-project**: replace the whole file.
79
+ - **Multi-project**: replace only the `# Project: {name}` section(s) for re-analyzed projects; preserve sections for projects NOT in this analysis run.
80
+
81
+ 4. **Populate `source_paths`** in `project-context.yaml`: after analyzing each project, update the matching project entry's `source_paths` array with the detected source directories (e.g., `["src/", "test/"]`). This overwrites the empty default set by `/mvt-init`.
81
82
 
82
- 3. Do NOT update `project-context.yaml` -- it is the lean index, managed by `/mvt-init` and `/mvt-sync-context` only
83
+ 5. Do NOT update other fields in `project-context.yaml` -- only `source_paths` is touched by this skill.
@@ -37,13 +37,10 @@ sections:
37
37
 
38
38
  - type: inline
39
39
  content: |
40
- ## Variants
40
+ ## Invocation
41
41
 
42
- | Variant | Description |
43
- |---------|-------------|
44
- | `/mvt-analyze-code` | Analyze the first (or only) project |
45
- | `/mvt-analyze-code --all` | Analyze all projects in the workspace |
46
- | `/mvt-analyze-code {name}` | Analyze a specific project by name |
42
+ `/mvt-analyze-code` single entry point, no arguments or flags.
43
+ When multiple projects exist, the skill interactively prompts the user to select the target(s).
47
44
 
48
45
  - type: shared
49
46
  source: sections/activation-load-context.md
@@ -10,9 +10,9 @@ This skill measures only files the **user** can reduce or relocate. Framework-fi
10
10
 
11
11
  **In scope (user-actionable):**
12
12
  - Index: `.ai-agents/workspace/project-context.yaml`.
13
- - Semantic context: `.ai-agents/knowledge/project/_generated/project-context.md`.
14
- - Shared knowledge: every entry in `registry.yaml > knowledge.shared`. For the `core` entry, scan only files marked as user-origin per `core/manifest.yaml` (or whose path begins with `user/`); skip files under `core/_framework/`.
15
- - Per-skill knowledge: every entry in `registry.yaml > skills.*.knowledge`, grouped by skill.
13
+ - Semantic context: `.ai-agents/knowledge/project/_generated/project-context.md` (always the flat path, regardless of project count).
14
+ - Shared knowledge: every entry in `registry.yaml > knowledge._all` and `knowledge.{projectName}` (map-aware -- traverse ALL project keys in the knowledge map). For the `core` entry, scan only files marked as user-origin per `core/manifest.yaml` (or whose path begins with `user/`); skip files under `core/_framework/`.
15
+ - Per-skill knowledge: every entry in `registry.yaml > skills.*.knowledge._all` and `skills.*.knowledge.{projectName}` (map-aware -- traverse ALL project keys for each skill), grouped by skill.
16
16
  - Artifacts: all files under `.ai-agents/workspace/artifacts/` recursively. **Exclude the `_archived/` subdirectory** — it contains completed changes archived by `/mvt-cleanup` and should not count toward the active workspace token budget.
17
17
 
18
18
  **Out of scope (do NOT scan):**
@@ -21,13 +21,19 @@ This skill measures only files the **user** can reduce or relocate. Framework-fi
21
21
  - `.ai-agents/config.yaml`, `.ai-agents/workspace/session.yaml`, `.ai-agents/registry.yaml` -- small, required, and addressed via `/mvt-config` or `/mvt-manage-context`, not here.
22
22
 
23
23
  ### Step 3: Estimate Token Consumption
24
- - **What**: produce a per-file tokens estimate and per-category subtotals.
24
+ - **What**: produce a per-file tokens estimate and per-category subtotals, with **per-project breakdown**.
25
25
  - **How**:
26
26
  1. For each in-scope file: tokens ~= `characters / 4`.
27
27
  2. Group by category: `Index`, `Semantic Context`, `Shared Knowledge`, `Per-Skill Knowledge`, `Artifacts`.
28
28
  3. For Shared Knowledge, compute total once -- this is per-skill overhead (loaded by every skill invocation).
29
29
  4. For Per-Skill Knowledge, compute totals per skill so users can see which skill is heaviest.
30
30
  5. Identify the Top 5 largest single files across the whole in-scope set.
31
+ 6. **Per-project breakdown**: for multi-project workspaces, also compute token costs per project:
32
+ - `knowledge._all` = shared across all projects
33
+ - `knowledge.{projectName}` = project-specific overhead
34
+ - `skills.*.knowledge.{projectName}` = per-skill per-project overhead
35
+ Display as a separate table: `project | knowledge tokens | per-skill tokens | total`.
36
+ 7. **Global summary**: total tokens across all projects + `_all` overhead loaded every time.
31
37
 
32
38
  ### Step 4: Apply Thresholds and Health Status
33
39
  - **What**: assign each file/category a status of `healthy | borderline | oversized`.
@@ -70,8 +76,9 @@ This skill measures only files the **user** can reduce or relocate. Framework-fi
70
76
  2. **Per-Category Breakdown** -- table: `category | files | tokens | status`.
71
77
  3. **Top 5 Largest Files** -- table: `path | tokens | category | status`.
72
78
  4. **Per-Skill Knowledge Cost** -- table: `skill | tokens` (sorted desc); include shared knowledge as a separate row labeled `(shared, loaded every time)`.
73
- 5. **Recommendations** -- numbered list from Step 5; if empty, render the healthy line.
74
- 6. **Excluded Scope Note** -- one paragraph reminding the user that framework files (`_framework/`, `mvt-*/SKILL.md`, `config.yaml`, `session.yaml`, `registry.yaml`) were not measured here.
79
+ 5. **Per-Project Token Accounting** -- table: `project | knowledge tokens | per-skill tokens | total` (only for multi-project workspaces; for single-project, omit this section).
80
+ 6. **Recommendations** -- numbered list from Step 5; if empty, render the healthy line.
81
+ 7. **Excluded Scope Note** -- one paragraph reminding the user that framework files (`_framework/`, `mvt-*/SKILL.md`, `config.yaml`, `session.yaml`, `registry.yaml`) were not measured here.
75
82
  - The report is conversation output; this skill does NOT write any artifact.
76
83
 
77
84
  ## Edge Cases & Errors
@@ -40,11 +40,6 @@ sections:
40
40
  - type: shared
41
41
  source: sections/activation-load-config.md
42
42
 
43
- - type: inline
44
- content: |
45
- ### Step 4: Pre-flight Checks
46
- - No blocking checks required.
47
-
48
43
  - type: file
49
44
  source: ./business.md
50
45