@uoyo/mvtt 2.0.0-beta.2 → 2.0.0-beta.4

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 (80) hide show
  1. package/README.md +1 -11
  2. package/dist/build/section-loader.d.ts.map +1 -1
  3. package/dist/build/section-loader.js +18 -8
  4. package/dist/build/section-loader.js.map +1 -1
  5. package/dist/fs/materialize.d.ts.map +1 -1
  6. package/dist/fs/materialize.js +12 -4
  7. package/dist/fs/materialize.js.map +1 -1
  8. package/dist/fs/registry-merge.d.ts +19 -0
  9. package/dist/fs/registry-merge.d.ts.map +1 -0
  10. package/dist/fs/registry-merge.js +177 -0
  11. package/dist/fs/registry-merge.js.map +1 -0
  12. package/dist/scripts/plan-update.cjs +7563 -0
  13. package/dist/scripts/session-update.cjs +7568 -0
  14. package/install-manifest.yaml +8 -2
  15. package/package.json +3 -2
  16. package/sources/defaults/config.yaml +7 -7
  17. package/sources/defaults/session.yaml +9 -16
  18. package/sources/scripts/plan-update.js +353 -0
  19. package/sources/scripts/session-update.js +351 -0
  20. package/sources/sections/activation-load-context.md +4 -0
  21. package/sources/sections/footer-next-steps.md +1 -1
  22. package/sources/sections/output-format-constraint.md +14 -0
  23. package/sources/sections/project-context-profile.md +29 -0
  24. package/sources/sections/session-update.md +100 -32
  25. package/sources/skills/mvt-analyze/manifest.yaml +4 -0
  26. package/sources/skills/mvt-analyze-code/manifest.yaml +24 -4
  27. package/sources/skills/mvt-bug-detect/business.md +99 -101
  28. package/sources/skills/mvt-bug-detect/manifest.yaml +84 -84
  29. package/sources/skills/mvt-check-context/business.md +3 -5
  30. package/sources/skills/mvt-check-context/manifest.yaml +15 -8
  31. package/sources/skills/mvt-cleanup/business.md +49 -23
  32. package/sources/skills/mvt-cleanup/manifest.yaml +18 -10
  33. package/sources/skills/mvt-config/business.md +1 -2
  34. package/sources/skills/mvt-config/manifest.yaml +11 -4
  35. package/sources/skills/mvt-create-skill/business.md +6 -5
  36. package/sources/skills/mvt-create-skill/manifest.yaml +30 -11
  37. package/sources/skills/mvt-design/business.md +3 -6
  38. package/sources/skills/mvt-design/manifest.yaml +17 -1
  39. package/sources/skills/mvt-fix/business.md +2 -1
  40. package/sources/skills/mvt-fix/manifest.yaml +9 -3
  41. package/sources/skills/mvt-help/business.md +2 -4
  42. package/sources/skills/mvt-help/manifest.yaml +13 -5
  43. package/sources/skills/mvt-implement/business.md +10 -7
  44. package/sources/skills/mvt-implement/manifest.yaml +16 -0
  45. package/sources/skills/mvt-init/business.md +2 -2
  46. package/sources/skills/mvt-init/manifest.yaml +4 -0
  47. package/sources/skills/mvt-manage-context/business.md +11 -0
  48. package/sources/skills/mvt-manage-context/manifest.yaml +20 -3
  49. package/sources/skills/mvt-plan-dev/business.md +101 -20
  50. package/sources/skills/mvt-plan-dev/manifest.yaml +21 -19
  51. package/sources/skills/mvt-quick-dev/business.md +2 -1
  52. package/sources/skills/mvt-quick-dev/manifest.yaml +24 -6
  53. package/sources/skills/mvt-refactor/business.md +2 -1
  54. package/sources/skills/mvt-refactor/manifest.yaml +24 -3
  55. package/sources/skills/mvt-resume/business.md +28 -68
  56. package/sources/skills/mvt-resume/manifest.yaml +17 -7
  57. package/sources/skills/mvt-review/business.md +3 -3
  58. package/sources/skills/mvt-review/manifest.yaml +25 -1
  59. package/sources/skills/mvt-status/business.md +14 -18
  60. package/sources/skills/mvt-status/manifest.yaml +11 -3
  61. package/sources/skills/mvt-sync-context/business.md +69 -35
  62. package/sources/skills/mvt-sync-context/manifest.yaml +9 -0
  63. package/sources/skills/mvt-template/business.md +0 -2
  64. package/sources/skills/mvt-template/manifest.yaml +13 -8
  65. package/sources/skills/mvt-test/business.md +3 -3
  66. package/sources/skills/mvt-test/manifest.yaml +17 -1
  67. package/sources/skills/mvt-update-plan/business.md +41 -28
  68. package/sources/skills/mvt-update-plan/manifest.yaml +14 -19
  69. package/dist/build/plan-validator.d.ts +0 -26
  70. package/dist/build/plan-validator.d.ts.map +0 -1
  71. package/dist/build/plan-validator.js +0 -225
  72. package/dist/build/plan-validator.js.map +0 -1
  73. package/dist/commands/build.d.ts +0 -5
  74. package/dist/commands/build.d.ts.map +0 -1
  75. package/dist/commands/build.js +0 -46
  76. package/dist/commands/build.js.map +0 -1
  77. package/dist/commands/migrate.d.ts +0 -16
  78. package/dist/commands/migrate.d.ts.map +0 -1
  79. package/dist/commands/migrate.js +0 -118
  80. package/dist/commands/migrate.js.map +0 -1
@@ -0,0 +1,351 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * session-update.js — Mechanical session state mutation script
5
+ *
6
+ * Replaces AI-driven YAML mutation with a deterministic Node.js script.
7
+ * All skills call this script instead of manually editing session.yaml.
8
+ *
9
+ * NOTE: This source file uses `import from "yaml"`. During the build
10
+ * pipeline, esbuild bundles it into a zero-dependency single file that
11
+ * gets deployed to .ai-agents/scripts/session-update.cjs.
12
+ *
13
+ * Usage:
14
+ * node .ai-agents/scripts/session-update.cjs \
15
+ * --skill <name> \
16
+ * --summary <text> \
17
+ * [--change-id <id>] \
18
+ * [--new-change <title>] \
19
+ * [--set-initialized] \
20
+ * [--update-change] \
21
+ * [--set-plan-path <path>] \
22
+ * [--close-change] \
23
+ * [--set-change-status <status>] \
24
+ * [--no-change] \
25
+ * [--set-synced] \
26
+ * [--truncate-history <n>]
27
+ *
28
+ * Output:
29
+ * Success (exit 0): {"ok":true}
30
+ * Failure (exit 1): plain text error message on stderr
31
+ */
32
+
33
+ import { readFileSync, writeFileSync, renameSync, unlinkSync, existsSync } from "node:fs";
34
+ import path from "node:path";
35
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
36
+
37
+ // ── Error Messages ──────────────────────────────────────────────────────────
38
+ // All error messages centralized here for visibility and easy maintenance.
39
+ // Each key maps to a function that returns the stderr message string.
40
+
41
+ const ERRORS = {
42
+ MISSING_SKILL: () => "Missing required argument: --skill",
43
+ MISSING_SUMMARY: () => "Missing required argument: --summary",
44
+ CHANGE_ID_REQUIRED: () => "--new-change requires --change-id",
45
+ NO_PROJECT_ROOT: () => "Could not find project root (.ai-agents/ directory not found). Make sure you are inside an MVTT project.",
46
+ NO_SESSION_YAML: () => "session.yaml not found. Run /mvt-init first to initialize the project.",
47
+ SESSION_PARSE_FAILED: (detail) => `Failed to parse session.yaml: ${detail}. Check the file for syntax errors.`,
48
+ SESSION_WRITE_FAILED: (detail) => `Failed to write session.yaml: ${detail}`,
49
+ CONFIG_LIMIT_INVALID: (key, val, min, max, fallback) =>
50
+ `Warning: config history_limits.${key} value "${val}" is invalid (must be integer ${min}-${max}). Using default ${fallback}.`,
51
+ };
52
+
53
+ // ── Defaults ────────────────────────────────────────────────────────────────
54
+ const DEFAULT_LIMITS = {
55
+ history: 20,
56
+ changes: 20,
57
+ };
58
+
59
+ const LIMIT_RANGES = {
60
+ history: { min: 1, max: 100 },
61
+ changes: { min: 1, max: 100 },
62
+ };
63
+
64
+ // ── Project Root Resolution ─────────────────────────────────────────────────
65
+ function findProjectRoot(cwd) {
66
+ let dir = cwd;
67
+ while (true) {
68
+ if (existsSync(path.join(dir, ".ai-agents"))) return dir;
69
+ const parent = path.dirname(dir);
70
+ if (parent === dir) return null;
71
+ dir = parent;
72
+ }
73
+ }
74
+
75
+ // ── CLI Parsing ─────────────────────────────────────────────────────────────
76
+ function parseArgs(argv) {
77
+ const args = {};
78
+ for (let i = 2; i < argv.length; i++) {
79
+ if (argv[i].startsWith("--")) {
80
+ const key = argv[i].slice(2);
81
+ const next = argv[i + 1];
82
+ if (next && !next.startsWith("--")) {
83
+ args[key] = next;
84
+ i++;
85
+ } else {
86
+ args[key] = true;
87
+ }
88
+ }
89
+ }
90
+ return args;
91
+ }
92
+
93
+ // ── Config Loading ──────────────────────────────────────────────────────────
94
+ function loadHistoryLimits(configPath) {
95
+ const limits = { ...DEFAULT_LIMITS };
96
+ if (!existsSync(configPath)) return limits;
97
+
98
+ try {
99
+ const raw = readFileSync(configPath, "utf-8");
100
+ const config = parseYaml(raw);
101
+ const configured = config?.preferences?.history_limits;
102
+ if (!configured || typeof configured !== "object") return limits;
103
+
104
+ for (const key of Object.keys(DEFAULT_LIMITS)) {
105
+ const val = configured[key];
106
+ if (val == null) continue;
107
+
108
+ const num = Number(val);
109
+ const range = LIMIT_RANGES[key];
110
+ if (!Number.isInteger(num) || num < range.min || num > range.max) {
111
+ console.warn(ERRORS.CONFIG_LIMIT_INVALID(key, val, range.min, range.max, DEFAULT_LIMITS[key]));
112
+ continue;
113
+ }
114
+ limits[key] = num;
115
+ }
116
+ } catch {
117
+ // If config can't be parsed, use defaults silently
118
+ }
119
+ return limits;
120
+ }
121
+
122
+ // ── Validation ──────────────────────────────────────────────────────────────
123
+ function validate(args) {
124
+ if (!args.skill) return ERRORS.MISSING_SKILL();
125
+ if (!args.summary) return ERRORS.MISSING_SUMMARY();
126
+ if (args["new-change"] && !args["change-id"]) return ERRORS.CHANGE_ID_REQUIRED();
127
+ return null;
128
+ }
129
+
130
+ // ── Main ────────────────────────────────────────────────────────────────────
131
+ function main() {
132
+ const args = parseArgs(process.argv);
133
+
134
+ const validationError = validate(args);
135
+ if (validationError) {
136
+ process.stderr.write(validationError + "\n");
137
+ process.exit(1);
138
+ }
139
+
140
+ const projectRoot = findProjectRoot(process.cwd());
141
+ if (!projectRoot) {
142
+ process.stderr.write(ERRORS.NO_PROJECT_ROOT() + "\n");
143
+ process.exit(1);
144
+ }
145
+
146
+ const sessionPath = path.join(projectRoot, ".ai-agents/workspace/session.yaml");
147
+ if (!existsSync(sessionPath)) {
148
+ process.stderr.write(ERRORS.NO_SESSION_YAML() + "\n");
149
+ process.exit(1);
150
+ }
151
+
152
+ const configPath = path.join(projectRoot, ".ai-agents/config.yaml");
153
+ const limits = loadHistoryLimits(configPath);
154
+
155
+ // Read session
156
+ let session;
157
+ try {
158
+ session = parseYaml(readFileSync(sessionPath, "utf-8"));
159
+ } catch (e) {
160
+ process.stderr.write(ERRORS.SESSION_PARSE_FAILED(e.message) + "\n");
161
+ process.exit(1);
162
+ }
163
+
164
+ const now = new Date().toISOString();
165
+
166
+ // ── Mandatory updates ──────────────────────────────────────────────────
167
+
168
+ // history append + truncate
169
+ session.history = session.history || [];
170
+ // Use --no-change to force empty change_id, otherwise fall back to active_change.id
171
+ const activeChangeId = args["no-change"] ? "" : (args["change-id"] || session.active_change?.id || "");
172
+ session.history.push({
173
+ skill: `/${args.skill}`,
174
+ completed_at: now,
175
+ summary: args.summary,
176
+ change_id: activeChangeId,
177
+ });
178
+ if (session.history.length > limits.history) {
179
+ session.history = session.history.slice(-limits.history);
180
+ }
181
+
182
+ // ── Conditional updates ────────────────────────────────────────────────
183
+
184
+ // --new-change: auto-snapshot old active_change, then set new one
185
+ if (args["new-change"]) {
186
+ session.active_change = session.active_change || {};
187
+
188
+ // Auto-snapshot: if there's an existing active_change with an id, upsert into changes[]
189
+ if (session.active_change.id) {
190
+ session.changes = session.changes || [];
191
+ const existingIdx = session.changes.findIndex(
192
+ (e) => e.id === session.active_change.id
193
+ );
194
+ const snapshotEntry = {
195
+ id: session.active_change.id,
196
+ title: session.active_change.title || "",
197
+ plan_path: session.active_change.plan_path || "",
198
+ status: "active",
199
+ updated_at: now,
200
+ };
201
+ if (existingIdx >= 0) {
202
+ session.changes[existingIdx] = snapshotEntry;
203
+ } else {
204
+ session.changes.push(snapshotEntry);
205
+ }
206
+ // Sort + truncate changes
207
+ session.changes.sort((a, b) => a.updated_at.localeCompare(b.updated_at));
208
+ if (session.changes.length > limits.changes) {
209
+ session.changes = session.changes.slice(-limits.changes);
210
+ }
211
+ }
212
+
213
+ // Now set new active_change
214
+ session.active_change.id = args["change-id"];
215
+ session.active_change.title = args["new-change"];
216
+ session.active_change.created_at = now;
217
+ session.active_change.plan_path = "";
218
+ }
219
+
220
+ // --set-initialized
221
+ if (args["set-initialized"]) {
222
+ session.session = session.session || {};
223
+ if (!session.session.initialized_at) {
224
+ session.session.initialized_at = now;
225
+ }
226
+ }
227
+
228
+ // --set-synced: set session.last_synced_at to current time
229
+ if (args["set-synced"]) {
230
+ session.session = session.session || {};
231
+ session.session.last_synced_at = now;
232
+ }
233
+
234
+ // --set-plan-path: set active_change.plan_path
235
+ // NOTE: Must execute BEFORE --update-change so that
236
+ // the upserted changes entry contains the correct plan_path.
237
+ if (args["set-plan-path"]) {
238
+ session.active_change = session.active_change || {};
239
+ session.active_change.plan_path = args["set-plan-path"];
240
+ }
241
+
242
+ // --update-change: upsert active_change into changes[] + truncate
243
+ if (args["update-change"]) {
244
+ session.changes = session.changes || [];
245
+ const ac = session.active_change || {};
246
+ const existingIdx = session.changes.findIndex(
247
+ (e) => e.id === ac.id
248
+ );
249
+ const entry = {
250
+ id: ac.id || "",
251
+ title: ac.title || "",
252
+ plan_path: ac.plan_path || "",
253
+ status: "active",
254
+ updated_at: now,
255
+ };
256
+ if (existingIdx >= 0) {
257
+ session.changes[existingIdx] = entry;
258
+ } else {
259
+ session.changes.push(entry);
260
+ }
261
+ // Sort by updated_at ascending, then truncate to limit
262
+ session.changes.sort(
263
+ (a, b) => a.updated_at.localeCompare(b.updated_at)
264
+ );
265
+ if (session.changes.length > limits.changes) {
266
+ session.changes = session.changes.slice(-limits.changes);
267
+ }
268
+ }
269
+
270
+ // --close-change: snapshot active_change to changes[] with status:done, clear active_change
271
+ if (args["close-change"]) {
272
+ session.changes = session.changes || [];
273
+ const ac = session.active_change || {};
274
+ if (ac.id) {
275
+ const existingIdx = session.changes.findIndex(
276
+ (e) => e.id === ac.id
277
+ );
278
+ const entry = {
279
+ id: ac.id,
280
+ title: ac.title || "",
281
+ plan_path: ac.plan_path || "",
282
+ status: "done",
283
+ updated_at: now,
284
+ };
285
+ if (existingIdx >= 0) {
286
+ session.changes[existingIdx] = entry;
287
+ } else {
288
+ session.changes.push(entry);
289
+ }
290
+ session.changes.sort(
291
+ (a, b) => a.updated_at.localeCompare(b.updated_at)
292
+ );
293
+ if (session.changes.length > limits.changes) {
294
+ session.changes = session.changes.slice(-limits.changes);
295
+ }
296
+ }
297
+ // Clear active_change
298
+ session.active_change = {
299
+ id: "",
300
+ title: "",
301
+ created_at: "",
302
+ plan_path: "",
303
+ };
304
+ }
305
+
306
+ // --set-change-status: set status on changes[] entry matching active_change.id
307
+ if (args["set-change-status"]) {
308
+ session.changes = session.changes || [];
309
+ const ac = session.active_change || {};
310
+ if (ac.id) {
311
+ const existingIdx = session.changes.findIndex(
312
+ (e) => e.id === ac.id
313
+ );
314
+ if (existingIdx >= 0) {
315
+ session.changes[existingIdx].status = args["set-change-status"];
316
+ session.changes[existingIdx].updated_at = now;
317
+ }
318
+ }
319
+ }
320
+
321
+ // --truncate-history: keep last N history entries, discard older
322
+ if (args["truncate-history"]) {
323
+ const n = Number(args["truncate-history"]);
324
+ if (Number.isInteger(n) && n > 0) {
325
+ session.history = session.history || [];
326
+ if (session.history.length > n) {
327
+ session.history = session.history.slice(-n);
328
+ }
329
+ }
330
+ }
331
+
332
+ // ── Write back atomically ─────────────────────────────────────────────
333
+ const tmpPath = sessionPath + ".tmp";
334
+
335
+ try {
336
+ writeFileSync(tmpPath, stringifyYaml(session), "utf-8");
337
+ renameSync(tmpPath, sessionPath);
338
+ } catch (e) {
339
+ try {
340
+ if (existsSync(tmpPath)) unlinkSync(tmpPath);
341
+ } catch {
342
+ // Best effort cleanup
343
+ }
344
+ process.stderr.write(ERRORS.SESSION_WRITE_FAILED(e.message) + "\n");
345
+ process.exit(1);
346
+ }
347
+
348
+ process.stdout.write('{"ok":true}\n');
349
+ }
350
+
351
+ main();
@@ -24,3 +24,7 @@ For each entry, resolve files relative to `.ai-agents/{source}`:
24
24
  - If the entry lists `files_from_manifest: true`, read `{source}/manifest.yaml` and load every `files[]` entry where `auto_load: true`.
25
25
 
26
26
  Skip any path that does not exist.
27
+
28
+ ### Archived Artifacts Convention
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.
@@ -22,7 +22,7 @@ Match the current state to one of the conditions below. If none match, use `defa
22
22
  ### Resolution order
23
23
 
24
24
  Infer 2-3 suggestions from:
25
- - `skill_history` in `session.yaml`
25
+ - `history` in `session.yaml`
26
26
  - `category` and `description` of each skill in `registry.yaml`
27
27
  - The current `active_change` state (if in progress)
28
28
  - The `depends_on` relationships between skills
@@ -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.
@@ -1,47 +1,115 @@
1
1
  {{?read_only}}
2
2
  ## State Update
3
3
 
4
- This skill is read-only and does NOT modify `.ai-agents/workspace/session.yaml`. No state mutation, no `skill_history` append, no `recent_actions` append.
4
+ This skill is read-only and does NOT modify `.ai-agents/workspace/session.yaml`.
5
5
  {{/read_only}}
6
6
  {{^read_only}}
7
- ## State Update (Required)
8
-
9
- After execution, update `.ai-agents/workspace/session.yaml` with the following fields.
10
-
11
- ### Mandatory (every skill must set)
12
-
13
- - `session.last_command`: Set to the current skill command (e.g., `"/mvt-analyze"`)
14
- - `skill_history`: Append entry:
15
- ```yaml
16
- - command: "/{skill-name}"
17
- completed_at: "{current timestamp ISO 8601}"
18
- summary: "{one-line summary of what was accomplished}"
19
- change_id: "{active_change.id if set, otherwise empty string}"
20
- ```
21
- Keep max 10 entries. If exceeds, drop the oldest. The `change_id` field enables `/mvt-resume` to filter history per change when multiple changes are in flight.
22
- - `recent_actions`: Append one-line summary with format:
23
- `[{YYYY-MM-DD HH:MM}] /{command}: {one-line summary}`
24
- Keep max 5 entries. If exceeds, drop the oldest.
25
- {{#update_active_change}}
7
+ ## State Update
8
+
9
+ After completing the skill's main task, run the session update script **exactly once** with the following arguments:
10
+
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}}
26
13
 
27
- ### Conditional (set only when applicable)
14
+ ```
28
15
 
29
- - `active_change.id`: Set when this skill creates a new change
30
- - `active_change.title`: Set when this skill creates a new change
31
- - `active_change.created_at`: Set when this skill creates a new change
16
+ If the script exits with code 0, the state update was applied successfully; there is no need to read or verify the session file.
17
+
18
+ ### Argument values
19
+
20
+ | Argument | Value source | Example |
21
+ |----------|-------------|---------|
22
+ | `--skill` | The exact skill command name without the leading `/` | `{{current_skill}}` |
23
+ | `--summary` | A concise one-line description of what this invocation accomplished, in the configured `interaction_language` | `"Identified auth requirements and created change chg-001"` |
24
+ {{#update_active_change}}
25
+ | `--new-change` | The title of the new change being created (same value written to `active_change.title`) | `"User authentication system"` |
26
+ | `--change-id` | The unique identifier of the new change (same value written to `active_change.id`) | `chg-001` |
32
27
  {{/update_active_change}}
28
+ {{#set_plan_path}}
29
+ | `--set-plan-path` | The path to the newly created plan.yaml | `".ai-agents/workspace/artifacts/chg-001/plan.yaml"` |
30
+ {{/set_plan_path}}
31
+ {{#update_change}}
32
+ | `--update-change` | Flag only, no value. Upserts the current `active_change` into `changes[]`. | — |
33
+ {{/update_change}}
34
+ {{#close_change}}
35
+ | `--close-change` | Flag only, no value. Snapshots `active_change` into `changes[]` with `status: done`, then clears `active_change`. | — |
36
+ {{/close_change}}
37
+ {{#set_change_status}}
38
+ | `--set-change-status` | The status to set on the `changes[]` entry matching `active_change.id`. Values: `active`, `done`, `abandoned`. | `done` |
39
+ {{/set_change_status}}
40
+ {{#no_change}}
41
+ | `--no-change` | Flag only, no value. Forces `history[].change_id` to empty string (skips `active_change.id` fallback). | — |
42
+ {{/no_change}}
43
+ {{#set_synced}}
44
+ | `--set-synced` | Flag only, no value. Sets `session.last_synced_at` to current time. | — |
45
+ {{/set_synced}}
46
+ {{#truncate_history}}
47
+ | `--truncate-history` | Number of most recent history entries to keep (read from `config.yaml > preferences.history_limits.history`, default 20); older entries are discarded. | `20` |
48
+ {{/truncate_history}}
33
49
  {{#update_initialized_at}}
50
+ | `--set-initialized` | Flag only, no value. Set when this skill initializes the project for the first time. | — |
51
+ {{/update_initialized_at}}
34
52
 
35
- ### Conditional (set only when applicable)
53
+ {{#update_active_change}}
54
+ ### Parameter semantics
36
55
 
37
- - `session.initialized_at`: Set to current timestamp when this skill initializes the project
56
+ | Argument | When to use | Effect on `session.yaml` |
57
+ |----------|-------------|--------------------------|
58
+ | `--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. |
59
+ {{#set_plan_path}}
60
+ | `--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
+ {{/set_plan_path}}
62
+ {{#update_change}}
63
+ | `--update-change` | Skill creates or modifies a plan (i.e., after `plan.yaml` is written/updated) | Upserts current `active_change` into `changes[]` (with `status: active`), sets `updated_at`, sorts ascending, truncates to configured limit. |
64
+ {{/update_change}}
65
+ {{#close_change}}
66
+ | `--close-change` | All plan tasks are completed | Snapshots `active_change` into `changes[]` with `status: done`, then clears all `active_change` fields. |
67
+ {{/close_change}}
68
+ {{#set_change_status}}
69
+ | `--set-change-status` | Explicitly mark a change as `done` or `abandoned` | Sets `status` on the `changes[]` entry whose `id` matches `active_change.id`. |
70
+ {{/set_change_status}}
71
+ {{#no_change}}
72
+ | `--no-change` | Skill should not be associated with any change | Forces `history[].change_id` to empty string, skipping the `active_change.id` fallback. |
73
+ {{/no_change}}
74
+ {{#set_synced}}
75
+ | `--set-synced` | Skill synchronizes context files | Sets `session.last_synced_at` to the current time. |
76
+ {{/set_synced}}
77
+ {{#truncate_history}}
78
+ | `--truncate-history` | Maintenance: trim old history entries | Keeps the most recent N entries in `history[]`, discards older ones. |
79
+ {{/truncate_history}}
80
+ {{#update_initialized_at}}
81
+ | `--set-initialized` | Skill initializes the project for the first time | Sets `session.initialized_at` (idempotent — only writes if empty). |
38
82
  {{/update_initialized_at}}
83
+ {{/update_active_change}}
84
+ {{^update_active_change}}
85
+ ### Parameter semantics
86
+
87
+ | Argument | When to use | Effect on `session.yaml` |
88
+ |----------|-------------|--------------------------|
89
+ {{#update_change}}
90
+ | `--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
+ {{/update_change}}
92
+ {{#close_change}}
93
+ | `--close-change` | All plan tasks are completed | Snapshots `active_change` into `changes[]` with `status: done`, then clears all `active_change` fields. |
94
+ {{/close_change}}
95
+ {{#set_change_status}}
96
+ | `--set-change-status` | Explicitly mark a change as `done` or `abandoned` | Sets `status` on the `changes[]` entry whose `id` matches `active_change.id`. |
97
+ {{/set_change_status}}
98
+ {{#no_change}}
99
+ | `--no-change` | Skill should not be associated with any change | Forces `history[].change_id` to empty string, skipping the `active_change.id` fallback. |
100
+ {{/no_change}}
101
+ {{#set_synced}}
102
+ | `--set-synced` | Skill synchronizes context files | Sets `session.last_synced_at` to the current time. |
103
+ {{/set_synced}}
104
+ {{#truncate_history}}
105
+ | `--truncate-history` | Maintenance: trim old history entries | Keeps the most recent N entries in `history[]`, discards older ones. |
106
+ {{/truncate_history}}
107
+ {{#update_initialized_at}}
108
+ | `--set-initialized` | Skill initializes the project for the first time | Sets `session.initialized_at` (idempotent — only writes if empty). |
109
+ {{/update_initialized_at}}
110
+ {{/update_active_change}}
39
111
 
40
- ### Forbidden
112
+ ### Failure handling
41
113
 
42
- - Do NOT update fields not listed above
43
- - Do NOT overwrite `active_change` unless this skill creates a new change
44
- - Do NOT modify `skill_history` entries other than appending a new one
45
- - Do NOT modify `recent_changes` -- it is owned by `/mvt-plan-dev` and `/mvt-update-plan`
46
- - Do NOT modify `active_change.plan_path` or `active_change.has_plan` -- these are owned by `/mvt-plan-dev`
114
+ If the script fails (non-zero exit), do NOT abort the skill's main task. Continue execution and add a brief note at the end of your response that the session could not be updated.
47
115
  {{/read_only}}
@@ -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:
@@ -71,6 +74,7 @@ sections:
71
74
  - type: shared
72
75
  source: sections/session-update.md
73
76
  params:
77
+ current_skill: mvt-analyze
74
78
  update_active_change: true
75
79
 
76
80
  - type: shared
@@ -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:
@@ -56,6 +59,9 @@ sections:
56
59
  - type: shared
57
60
  source: sections/output-language-constraint.md
58
61
 
62
+ - type: shared
63
+ source: sections/output-format-constraint.md
64
+
59
65
  - type: shared
60
66
  source: sections/activation-preflight.md
61
67
  params:
@@ -71,10 +77,11 @@ sections:
71
77
 
72
78
  - type: inline
73
79
  content: |
74
- ### Independent Operation Rules
75
- - This is an independent operation -- no workflow prerequisites required
76
- - Does NOT create a change-id
77
- - Output is written to `.ai-agents/knowledge/project/_generated/project-context.md`
80
+ ## Operation Mode: Independent
81
+
82
+ This is an independent operation — no workflow prerequisites required.
83
+ - Does NOT create a change-id.
84
+ - Output is written to `.ai-agents/knowledge/project/_generated/project-context.md`.
78
85
 
79
86
  - type: file
80
87
  source: ./business.md
@@ -89,8 +96,21 @@ sections:
89
96
 
90
97
  - type: shared
91
98
  source: sections/session-update.md
99
+ params:
100
+ current_skill: mvt-analyze-code
92
101
 
93
102
  - type: shared
94
103
  source: sections/footer-next-steps.md
95
104
  params:
96
105
  current_skill: mvt-analyze-code
106
+ conditional_suggestions:
107
+ conditions:
108
+ - condition: "project-context.md generated, no active change"
109
+ primary: mvt-analyze
110
+ primary_desc: "Analyze requirements for a new feature"
111
+ - condition: "project-context.md generated, active change exists"
112
+ primary: mvt-design
113
+ primary_desc: "Design with the updated project context"
114
+ - condition: "analysis revealed outdated knowledge"
115
+ primary: mvt-manage-context
116
+ primary_desc: "Update knowledge entries"