@uoyo/mvtt 2.0.0-beta.3 → 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 (96) 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 +46 -13
  19. package/dist/fs/materialize.js.map +1 -1
  20. package/dist/fs/registry-merge.d.ts +19 -0
  21. package/dist/fs/registry-merge.d.ts.map +1 -0
  22. package/dist/fs/registry-merge.js +220 -0
  23. package/dist/fs/registry-merge.js.map +1 -0
  24. package/dist/scripts/epic-update.cjs +7670 -0
  25. package/dist/scripts/plan-update.cjs +7736 -0
  26. package/dist/scripts/session-update.cjs +84 -6
  27. package/dist/types/platform.d.ts +12 -0
  28. package/dist/types/platform.d.ts.map +1 -0
  29. package/dist/types/platform.js +24 -0
  30. package/dist/types/platform.js.map +1 -0
  31. package/dist/types/registry.d.ts +3 -24
  32. package/dist/types/registry.d.ts.map +1 -1
  33. package/install-manifest.yaml +10 -2
  34. package/package.json +1 -1
  35. package/registry.yaml +72 -198
  36. package/sources/defaults/config.yaml +8 -13
  37. package/sources/defaults/project-context.yaml +2 -5
  38. package/sources/defaults/session.yaml +14 -2
  39. package/sources/knowledge/core/manifest.yaml +1 -4
  40. package/sources/scripts/epic-update.js +512 -0
  41. package/sources/scripts/plan-update.js +614 -0
  42. package/sources/scripts/session-update.js +102 -2
  43. package/sources/sections/activation-load-config.md +1 -1
  44. package/sources/sections/activation-load-context.md +42 -13
  45. package/sources/sections/activation-preflight.md +1 -1
  46. package/sources/sections/footer-next-steps.md +3 -2
  47. package/sources/sections/output-format-constraint.md +14 -0
  48. package/sources/sections/project-context-profile.md +29 -0
  49. package/sources/sections/session-update.md +41 -1
  50. package/sources/skills/mvt-analyze/business.md +46 -8
  51. package/sources/skills/mvt-analyze/manifest.yaml +8 -1
  52. package/sources/skills/mvt-analyze-code/business.md +18 -17
  53. package/sources/skills/mvt-analyze-code/manifest.yaml +9 -6
  54. package/sources/skills/mvt-check-context/business.md +13 -6
  55. package/sources/skills/mvt-check-context/manifest.yaml +0 -5
  56. package/sources/skills/mvt-cleanup/business.md +17 -2
  57. package/sources/skills/mvt-cleanup/manifest.yaml +3 -0
  58. package/sources/skills/mvt-config/business.md +5 -5
  59. package/sources/skills/mvt-config/manifest.yaml +3 -6
  60. package/sources/skills/mvt-create-skill/business.md +2 -14
  61. package/sources/skills/mvt-create-skill/manifest.yaml +2 -4
  62. package/sources/skills/mvt-decompose/business.md +94 -0
  63. package/sources/skills/mvt-decompose/manifest.yaml +121 -0
  64. package/sources/skills/mvt-design/manifest.yaml +3 -0
  65. package/sources/skills/mvt-fix/business.md +21 -6
  66. package/sources/skills/mvt-fix/manifest.yaml +4 -1
  67. package/sources/skills/mvt-help/business.md +11 -9
  68. package/sources/skills/mvt-help/manifest.yaml +0 -5
  69. package/sources/skills/mvt-implement/business.md +57 -10
  70. package/sources/skills/mvt-implement/manifest.yaml +3 -0
  71. package/sources/skills/mvt-init/business.md +23 -13
  72. package/sources/skills/mvt-init/manifest.yaml +4 -2
  73. package/sources/skills/mvt-manage-context/business.md +41 -14
  74. package/sources/skills/mvt-manage-context/manifest.yaml +7 -5
  75. package/sources/skills/mvt-plan-dev/business.md +17 -9
  76. package/sources/skills/mvt-plan-dev/manifest.yaml +3 -0
  77. package/sources/skills/mvt-quick-dev/business.md +22 -7
  78. package/sources/skills/mvt-quick-dev/manifest.yaml +3 -1
  79. package/sources/skills/mvt-refactor/business.md +32 -17
  80. package/sources/skills/mvt-refactor/manifest.yaml +2 -4
  81. package/sources/skills/mvt-resume/business.md +32 -12
  82. package/sources/skills/mvt-resume/manifest.yaml +3 -3
  83. package/sources/skills/mvt-review/business.md +24 -9
  84. package/sources/skills/mvt-review/manifest.yaml +3 -0
  85. package/sources/skills/mvt-status/business.md +37 -9
  86. package/sources/skills/mvt-status/manifest.yaml +2 -2
  87. package/sources/skills/mvt-sync-context/business.md +77 -34
  88. package/sources/skills/mvt-sync-context/manifest.yaml +6 -0
  89. package/sources/skills/mvt-template/business.md +1 -1
  90. package/sources/skills/mvt-template/manifest.yaml +0 -5
  91. package/sources/skills/mvt-test/business.md +30 -15
  92. package/sources/skills/mvt-test/manifest.yaml +3 -0
  93. package/sources/skills/mvt-update-plan/business.md +64 -33
  94. package/sources/skills/mvt-update-plan/manifest.yaml +10 -7
  95. package/sources/templates/decompose-output/body.md +13 -0
  96. 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
@@ -0,0 +1,14 @@
1
+ ## Output Format Constraint (Mandatory)
2
+
3
+ All persisted document output (markdown written to disk) MUST follow the formatting rules below. These rules govern *how* content is rendered, independent of the language it is written in.
4
+ **Scope**: artifact files, generated reports, plans, design documents, and any markdown written to disk. These rules do NOT apply to conversational output in the chat.
5
+
6
+ **Rules**:
7
+ - **Diagrams**: Express flowcharts, architecture, sequence, and structure diagrams as fenced `mermaid` code blocks. Do NOT draw diagrams with ASCII art (boxes made of `+`, `-`, `|`, arrows like `-->` outside mermaid, etc.).
8
+ - **Tables**: Render tabular data as Markdown tables (`| col | col |`). Do NOT simulate tables with space- or tab-aligned text.
9
+ - **Code**: Place code, commands, and config snippets in fenced code blocks with a language tag (e.g. ```` ```ts ````, ```` ```bash ````, ```` ```yaml ````). Do NOT leave code in bare or untagged fences.
10
+ - **Headings**: Use the Markdown heading hierarchy (`#` -> `##` -> `###`) without skipping levels. Do NOT use bold text as a substitute for a heading.
11
+
12
+ **Notes**:
13
+ - If a diagram genuinely cannot be expressed in mermaid (e.g. a precise spatial/pixel layout), state that explicitly and prefer a Markdown table or prose description over ASCII art.
14
+ - This constraint is NON-NEGOTIABLE and overrides formatting habits inferred from templates or source material.
@@ -0,0 +1,29 @@
1
+ ## Document Profile: project-context.md
2
+
3
+ Before writing to `project-context.md`, understand what this document IS and IS NOT.
4
+
5
+ ### Identity
6
+ `project-context.md` is the project's **long-term semantic ground truth** -- a self-contained knowledge base consumed by AI skills to make decisions. It is NOT a copy of design documents, NOT a changelog, NOT an ADR index.
7
+
8
+ ### Audience
9
+ The readers are AI skill instances (implementer, designer, tester, reviewer), NOT humans reading for reference. They use this document to make **binary decisions** (is this import legal? does this test cover this rule?) -- not to trace design rationale.
10
+
11
+ ### Content Quality Standards
12
+ Every piece of content written into `project-context.md` must satisfy ALL of the following:
13
+
14
+ 1. **Self-contained**: understandable without consulting any external document, artifact, or ADR.
15
+ 2. **Actionable**: usable by an AI skill to make a yes/no decision or produce a concrete output (e.g., a test case).
16
+ 3. **Atomic**: each item is independently meaningful -- not a fragment of a larger argument that only makes sense in its source document.
17
+ 4. **Lean**: the token budget for this document is <= 4000 (healthy threshold). Content that does not directly serve a decision should be excluded.
18
+ 5. **Stable**: only persist knowledge with long-term reference value. Transient state (change metadata, in-progress decisions, temporary workarounds) belongs in session.yaml or artifacts.
19
+
20
+ ### Governing Principle (What Does NOT Belong)
21
+ **If a reader must consult an external document to understand an entry, that entry -- or its reference marker -- does not belong here.**
22
+
23
+ Strip any cross-reference marker (pointers to ADRs, design-document section numbers, internal rule labels, etc.). Remove only the *reference marker*, NEVER the *substantive content* it annotates.
24
+
25
+ - ✅ `idempotency key or exists-or-skip semantics (ADR-06, §12.4)` → `idempotency key or exists-or-skip semantics`
26
+ - ✅ `B-1: resume() degrades to rebuild on protocol error` → `resume() degrades to rebuild on protocol error`
27
+ - ❌ `Subscriber Idempotency Contract` -- this is the term itself, keep it.
28
+
29
+ > This profile applies ONLY when the target document is `project-context.md`. Other knowledge files (principle/, project/, core/user/, etc.) are not governed by it.
@@ -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`
@@ -44,6 +44,9 @@ sections:
44
44
  - type: shared
45
45
  source: sections/output-language-constraint.md
46
46
 
47
+ - type: shared
48
+ source: sections/output-format-constraint.md
49
+
47
50
  - type: shared
48
51
  source: sections/activation-preflight.md
49
52
  params:
@@ -73,6 +76,7 @@ sections:
73
76
  params:
74
77
  current_skill: mvt-analyze
75
78
  update_active_change: true
79
+ link_subchange_to_epic: true
76
80
 
77
81
  - type: shared
78
82
  source: sections/footer-next-steps.md
@@ -80,7 +84,10 @@ sections:
80
84
  current_skill: mvt-analyze
81
85
  conditional_suggestions:
82
86
  conditions:
83
- - 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)"
84
91
  primary: "mvt-quick-dev"
85
92
  primary_desc: "Implement this simple change quickly"
86
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.
@@ -14,6 +14,9 @@ sections:
14
14
 
15
15
  Analyze existing code to generate the project-context.md file, which describes the project's terms, modules, layer structure, business rules, and API overview. This is an independent operation that does not create a change-id.
16
16
 
17
+ - type: shared
18
+ source: sections/project-context-profile.md
19
+
17
20
  - type: shared
18
21
  source: sections/role-header.md
19
22
  params:
@@ -34,13 +37,10 @@ sections:
34
37
 
35
38
  - type: inline
36
39
  content: |
37
- ## Variants
40
+ ## Invocation
38
41
 
39
- | Variant | Description |
40
- |---------|-------------|
41
- | `/mvt-analyze-code` | Analyze the first (or only) project |
42
- | `/mvt-analyze-code --all` | Analyze all projects in the workspace |
43
- | `/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).
44
44
 
45
45
  - type: shared
46
46
  source: sections/activation-load-context.md
@@ -56,6 +56,9 @@ sections:
56
56
  - type: shared
57
57
  source: sections/output-language-constraint.md
58
58
 
59
+ - type: shared
60
+ source: sections/output-format-constraint.md
61
+
59
62
  - type: shared
60
63
  source: sections/activation-preflight.md
61
64
  params: