@uoyo/mvtt 2.0.0-beta.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +299 -64
  2. package/README.zh-CN.md +419 -0
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +14 -6
  5. package/dist/cli.js.map +1 -1
  6. package/dist/commands/doctor.d.ts.map +1 -1
  7. package/dist/commands/doctor.js +28 -16
  8. package/dist/commands/doctor.js.map +1 -1
  9. package/dist/commands/install.d.ts.map +1 -1
  10. package/dist/commands/install.js +71 -41
  11. package/dist/commands/install.js.map +1 -1
  12. package/dist/commands/uninstall.d.ts.map +1 -1
  13. package/dist/commands/uninstall.js +75 -30
  14. package/dist/commands/uninstall.js.map +1 -1
  15. package/dist/commands/update.d.ts.map +1 -1
  16. package/dist/commands/update.js +34 -21
  17. package/dist/commands/update.js.map +1 -1
  18. package/dist/fs/install-manifest.d.ts +4 -1
  19. package/dist/fs/install-manifest.d.ts.map +1 -1
  20. package/dist/fs/install-manifest.js +13 -1
  21. package/dist/fs/install-manifest.js.map +1 -1
  22. package/dist/fs/materialize.d.ts +2 -0
  23. package/dist/fs/materialize.d.ts.map +1 -1
  24. package/dist/fs/materialize.js +39 -9
  25. package/dist/fs/materialize.js.map +1 -1
  26. package/dist/fs/registry-merge.d.ts.map +1 -1
  27. package/dist/fs/registry-merge.js +72 -29
  28. package/dist/fs/registry-merge.js.map +1 -1
  29. package/dist/index.js +0 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/scripts/epic-update.cjs +7713 -0
  32. package/dist/scripts/plan-update.cjs +491 -275
  33. package/dist/scripts/session-update.cjs +320 -199
  34. package/dist/types/platform.d.ts +12 -0
  35. package/dist/types/platform.d.ts.map +1 -0
  36. package/dist/types/platform.js +36 -0
  37. package/dist/types/platform.js.map +1 -0
  38. package/dist/types/registry.d.ts +3 -24
  39. package/dist/types/registry.d.ts.map +1 -1
  40. package/dist/util/bilingual.d.ts +10 -0
  41. package/dist/util/bilingual.d.ts.map +1 -0
  42. package/dist/util/bilingual.js +14 -0
  43. package/dist/util/bilingual.js.map +1 -0
  44. package/dist/util/cancel.d.ts +2 -0
  45. package/dist/util/cancel.d.ts.map +1 -0
  46. package/dist/util/cancel.js +6 -0
  47. package/dist/util/cancel.js.map +1 -0
  48. package/dist/util/color.d.ts +9 -6
  49. package/dist/util/color.d.ts.map +1 -1
  50. package/dist/util/color.js +10 -10
  51. package/dist/util/color.js.map +1 -1
  52. package/dist/util/spinner.d.ts +8 -0
  53. package/dist/util/spinner.d.ts.map +1 -0
  54. package/dist/util/spinner.js +17 -0
  55. package/dist/util/spinner.js.map +1 -0
  56. package/install-manifest.yaml +4 -0
  57. package/package.json +4 -3
  58. package/registry.yaml +33 -159
  59. package/sources/defaults/config.yaml +8 -13
  60. package/sources/defaults/project-context.yaml +2 -5
  61. package/sources/defaults/session.yaml +14 -2
  62. package/sources/knowledge/core/manifest.yaml +1 -4
  63. package/sources/scripts/epic-update.js +512 -0
  64. package/sources/scripts/plan-update.js +614 -353
  65. package/sources/scripts/session-update.js +102 -2
  66. package/sources/sections/activation-load-config.md +3 -3
  67. package/sources/sections/activation-load-context.md +42 -13
  68. package/sources/sections/activation-preflight.md +1 -1
  69. package/sources/sections/footer-next-steps.md +3 -2
  70. package/sources/sections/language-constraint.md +26 -0
  71. package/sources/sections/output-format-constraint.md +14 -14
  72. package/sources/sections/project-context-profile.md +29 -29
  73. package/sources/sections/session-update.md +41 -1
  74. package/sources/skills/mvt-analyze/business.md +46 -8
  75. package/sources/skills/mvt-analyze/manifest.yaml +6 -2
  76. package/sources/skills/mvt-analyze-code/business.md +18 -17
  77. package/sources/skills/mvt-analyze-code/manifest.yaml +4 -7
  78. package/sources/skills/mvt-bug-detect/manifest.yaml +3 -0
  79. package/sources/skills/mvt-check-context/business.md +13 -6
  80. package/sources/skills/mvt-check-context/manifest.yaml +2 -4
  81. package/sources/skills/mvt-cleanup/business.md +17 -2
  82. package/sources/skills/mvt-cleanup/manifest.yaml +1 -1
  83. package/sources/skills/mvt-config/business.md +5 -5
  84. package/sources/skills/mvt-config/manifest.yaml +6 -6
  85. package/sources/skills/mvt-create-skill/business.md +3 -15
  86. package/sources/skills/mvt-create-skill/manifest.yaml +1 -6
  87. package/sources/skills/mvt-decompose/business.md +94 -0
  88. package/sources/skills/mvt-decompose/manifest.yaml +121 -0
  89. package/sources/skills/mvt-design/manifest.yaml +1 -1
  90. package/sources/skills/mvt-fix/business.md +21 -6
  91. package/sources/skills/mvt-fix/manifest.yaml +2 -2
  92. package/sources/skills/mvt-help/business.md +11 -9
  93. package/sources/skills/mvt-help/manifest.yaml +2 -4
  94. package/sources/skills/mvt-implement/business.md +51 -8
  95. package/sources/skills/mvt-implement/manifest.yaml +1 -1
  96. package/sources/skills/mvt-init/business.md +23 -13
  97. package/sources/skills/mvt-init/manifest.yaml +2 -3
  98. package/sources/skills/mvt-manage-context/business.md +41 -14
  99. package/sources/skills/mvt-manage-context/manifest.yaml +3 -7
  100. package/sources/skills/mvt-plan-dev/business.md +17 -9
  101. package/sources/skills/mvt-plan-dev/manifest.yaml +1 -1
  102. package/sources/skills/mvt-quick-dev/business.md +22 -7
  103. package/sources/skills/mvt-quick-dev/manifest.yaml +1 -2
  104. package/sources/skills/mvt-refactor/business.md +32 -17
  105. package/sources/skills/mvt-refactor/manifest.yaml +1 -6
  106. package/sources/skills/mvt-resume/business.md +32 -12
  107. package/sources/skills/mvt-resume/manifest.yaml +6 -3
  108. package/sources/skills/mvt-review/business.md +24 -9
  109. package/sources/skills/mvt-review/manifest.yaml +1 -1
  110. package/sources/skills/mvt-status/business.md +37 -9
  111. package/sources/skills/mvt-status/manifest.yaml +5 -2
  112. package/sources/skills/mvt-sync-context/business.md +30 -16
  113. package/sources/skills/mvt-sync-context/manifest.yaml +1 -1
  114. package/sources/skills/mvt-template/business.md +1 -1
  115. package/sources/skills/mvt-template/manifest.yaml +2 -4
  116. package/sources/skills/mvt-test/business.md +30 -15
  117. package/sources/skills/mvt-test/manifest.yaml +1 -1
  118. package/sources/skills/mvt-update-plan/business.md +41 -12
  119. package/sources/skills/mvt-update-plan/manifest.yaml +8 -8
  120. package/sources/templates/decompose-output/body.md +13 -0
  121. package/sources/templates/decompose-output/manifest.yaml +11 -0
  122. package/sources/sections/output-language-constraint.md +0 -11
@@ -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,9 +1,9 @@
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**:
5
- - `preferences.interaction_language` → Use for everything spoken to the user (chat, prompts, tables); NOT for files written to disk.
6
- - `preferences.document_output_language` → See **Output Language Constraint** section below for the full rules governing files written to disk.
5
+ - `preferences.interaction_language` → Language for everything spoken to the user (chat, prompts, tables); NOT for files written to disk. See the **Language Constraint** section below for the full, non-negotiable rules.
6
+ - `preferences.document_output_language` → Language for files written to disk. See the **Language Constraint** section below for the full rules.
7
7
 
8
8
  **Other preferences**:
9
9
  - `preferences.output.no_emojis` → If true, never use emojis
@@ -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,26 @@
1
+ ## Language Constraint (Mandatory)
2
+
3
+ This constraint governs the language of **everything** this skill produces. It has two independent scopes — interactive output (what you say to the user) and persisted document output (what you write to disk). Both are NON-NEGOTIABLE and override any other language signals.
4
+
5
+ ### Interactive Output (spoken to the user)
6
+
7
+ All interactive output — chat replies, questions, prompts, status lines, tables, and summaries shown in the conversation — MUST be written in the language specified by `preferences.interaction_language` from config.yaml.
8
+
9
+ **Rules**:
10
+ - This applies to EVERY message in the conversation, not just the first — re-assert it on every turn, including long sessions.
11
+ - Do NOT mirror the language of: the user's prompt, the source code or its comments, this skill's own English body, file contents you just read, or tool output. None of these are language signals.
12
+ - If the user writes to you in a different language, still reply in the configured `interaction_language` (unless they explicitly ask you to switch).
13
+ - If `interaction_language` is not set, fall back to `en-US`.
14
+ - This constraint is NON-NEGOTIABLE and overrides any other language signals.
15
+
16
+ ### Persisted Document Output (files written to disk)
17
+
18
+ All persisted document output (files written to disk) MUST be written in the language specified by `preferences.document_output_language` from config.yaml.
19
+
20
+ **Scope**: artifact files, generated reports, plans, and any markdown written to disk.
21
+
22
+ **Rules**:
23
+ - Section headings defined in templates may remain in their original language, but all generated **content** MUST use the configured language
24
+ - If `document_output_language` is not set, fall back to `interaction_language`
25
+ - Do NOT infer output language from template headings, user prompt language, or source code comments
26
+ - This constraint is NON-NEGOTIABLE and overrides any other language signals
@@ -1,14 +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.
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.
@@ -1,29 +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.
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`
@@ -42,7 +42,7 @@ sections:
42
42
  source: sections/activation-load-config.md
43
43
 
44
44
  - type: shared
45
- source: sections/output-language-constraint.md
45
+ source: sections/language-constraint.md
46
46
 
47
47
  - type: shared
48
48
  source: sections/output-format-constraint.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"