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

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 (72) 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 +5 -0
  7. package/dist/fs/materialize.js.map +1 -1
  8. package/dist/scripts/session-update.cjs +7568 -0
  9. package/install-manifest.yaml +2 -0
  10. package/package.json +3 -2
  11. package/sources/defaults/config.yaml +7 -7
  12. package/sources/defaults/session.yaml +9 -16
  13. package/sources/scripts/session-update.js +351 -0
  14. package/sources/sections/activation-load-context.md +4 -0
  15. package/sources/sections/footer-next-steps.md +1 -1
  16. package/sources/sections/session-update.md +100 -32
  17. package/sources/skills/mvt-analyze/manifest.yaml +1 -0
  18. package/sources/skills/mvt-analyze-code/manifest.yaml +18 -4
  19. package/sources/skills/mvt-bug-detect/business.md +99 -101
  20. package/sources/skills/mvt-bug-detect/manifest.yaml +84 -84
  21. package/sources/skills/mvt-check-context/business.md +3 -5
  22. package/sources/skills/mvt-check-context/manifest.yaml +15 -8
  23. package/sources/skills/mvt-cleanup/business.md +49 -23
  24. package/sources/skills/mvt-cleanup/manifest.yaml +15 -10
  25. package/sources/skills/mvt-config/business.md +1 -2
  26. package/sources/skills/mvt-config/manifest.yaml +11 -4
  27. package/sources/skills/mvt-create-skill/business.md +6 -5
  28. package/sources/skills/mvt-create-skill/manifest.yaml +27 -11
  29. package/sources/skills/mvt-design/business.md +3 -6
  30. package/sources/skills/mvt-design/manifest.yaml +14 -1
  31. package/sources/skills/mvt-fix/business.md +2 -1
  32. package/sources/skills/mvt-fix/manifest.yaml +6 -3
  33. package/sources/skills/mvt-help/business.md +2 -4
  34. package/sources/skills/mvt-help/manifest.yaml +13 -5
  35. package/sources/skills/mvt-implement/business.md +4 -5
  36. package/sources/skills/mvt-implement/manifest.yaml +13 -0
  37. package/sources/skills/mvt-init/business.md +2 -2
  38. package/sources/skills/mvt-init/manifest.yaml +1 -0
  39. package/sources/skills/mvt-manage-context/business.md +11 -0
  40. package/sources/skills/mvt-manage-context/manifest.yaml +14 -3
  41. package/sources/skills/mvt-plan-dev/business.md +101 -20
  42. package/sources/skills/mvt-plan-dev/manifest.yaml +18 -19
  43. package/sources/skills/mvt-quick-dev/business.md +2 -1
  44. package/sources/skills/mvt-quick-dev/manifest.yaml +21 -6
  45. package/sources/skills/mvt-refactor/business.md +2 -1
  46. package/sources/skills/mvt-refactor/manifest.yaml +21 -3
  47. package/sources/skills/mvt-resume/business.md +28 -68
  48. package/sources/skills/mvt-resume/manifest.yaml +17 -7
  49. package/sources/skills/mvt-review/business.md +3 -3
  50. package/sources/skills/mvt-review/manifest.yaml +22 -1
  51. package/sources/skills/mvt-status/business.md +14 -18
  52. package/sources/skills/mvt-status/manifest.yaml +11 -3
  53. package/sources/skills/mvt-sync-context/business.md +14 -9
  54. package/sources/skills/mvt-sync-context/manifest.yaml +3 -0
  55. package/sources/skills/mvt-template/business.md +0 -2
  56. package/sources/skills/mvt-template/manifest.yaml +13 -8
  57. package/sources/skills/mvt-test/business.md +3 -3
  58. package/sources/skills/mvt-test/manifest.yaml +14 -1
  59. package/sources/skills/mvt-update-plan/business.md +14 -3
  60. package/sources/skills/mvt-update-plan/manifest.yaml +11 -19
  61. package/dist/build/plan-validator.d.ts +0 -26
  62. package/dist/build/plan-validator.d.ts.map +0 -1
  63. package/dist/build/plan-validator.js +0 -225
  64. package/dist/build/plan-validator.js.map +0 -1
  65. package/dist/commands/build.d.ts +0 -5
  66. package/dist/commands/build.d.ts.map +0 -1
  67. package/dist/commands/build.js +0 -46
  68. package/dist/commands/build.js.map +0 -1
  69. package/dist/commands/migrate.d.ts +0 -16
  70. package/dist/commands/migrate.d.ts.map +0 -1
  71. package/dist/commands/migrate.js +0 -118
  72. package/dist/commands/migrate.js.map +0 -1
@@ -11,6 +11,8 @@ generated:
11
11
  source: "copy:sources/knowledge/core/_framework/"
12
12
  - pattern: ".ai-agents/registry.yaml"
13
13
  source: "copy:registry.yaml"
14
+ - pattern: ".ai-agents/scripts/session-update.cjs"
15
+ source: "bundle:sources/scripts/session-update.js"
14
16
 
15
17
  create_once:
16
18
  - path: ".ai-agents/config.yaml"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uoyo/mvtt",
3
- "version": "2.0.0-beta.2",
3
+ "version": "2.0.0-beta.3",
4
4
  "description": "My Virtual Tech Team - AI-guided prompt orchestration framework",
5
5
  "type": "module",
6
6
  "bin": {
@@ -35,7 +35,7 @@
35
35
  "access": "public"
36
36
  },
37
37
  "scripts": {
38
- "build": "tsc -p tsconfig.build.json",
38
+ "build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json && node build-scripts.js",
39
39
  "test": "vitest run",
40
40
  "test:watch": "vitest",
41
41
  "test:coverage": "vitest run --coverage",
@@ -45,6 +45,7 @@
45
45
  "@types/node": "^25.6.0",
46
46
  "@types/prompts": "^2.4.9",
47
47
  "@vitest/coverage-v8": "^2.1.9",
48
+ "esbuild": "^0.28.0",
48
49
  "typescript": "^5.4.0",
49
50
  "vitest": "^2.0.0"
50
51
  },
@@ -1,18 +1,13 @@
1
1
  # MVTT Framework - User Configuration
2
- # Unified config center: all skills read and enforce these settings via Activation Protocol
3
- # Modify via: /mvt-config or edit directly
4
2
 
5
3
  version: "2.0"
6
4
 
7
- # ============================================================
8
- # User Preferences (enforced by Activation Protocol Step 2)
9
- # ============================================================
10
5
  preferences:
11
- # Language used for interactive responses (chat replies, prompts, tables shown to the user)
6
+ # Language used for interactive responses
12
7
  # Options: en-US, zh-CN
13
8
  interaction_language: en-US
14
9
 
15
- # Language used for persistent document output (artifacts, project-context.md, generated reports)
10
+ # Language used for persistent document output
16
11
  # If absent, falls back to interaction_language.
17
12
  # Options: en-US, zh-CN
18
13
  document_output_language: en-US
@@ -25,3 +20,8 @@ preferences:
25
20
  # AI routing for /mvt-manage-context add
26
21
  context_routing:
27
22
  relevance_threshold: 70 # Skills scored >= this are pre-checked; below are folded
23
+
24
+ # Session history limits
25
+ history_limits:
26
+ history: 20 # Max history entries (1-100)
27
+ changes: 20 # Max changes entries (1-100)
@@ -1,31 +1,24 @@
1
1
  # Workspace Session State
2
- # Supports flexible workflows -- no longer bound to a fixed pipeline
3
2
 
4
3
  session:
5
4
  initialized_at: ""
6
- last_command: ""
5
+ last_synced_at: ""
7
6
 
8
- # Current active change (id/title/created_at set by /mvt-analyze;
9
- # plan_path/has_plan set by /mvt-plan-dev once a plan is generated)
10
7
  active_change:
11
8
  id: ""
12
9
  title: ""
13
10
  created_at: ""
14
11
  plan_path: ""
15
- has_plan: false
16
12
 
17
- # Recent changes with active plans (max 5, owned by /mvt-plan-dev and /mvt-update-plan)
18
- # Each entry: { id, title, plan_path, last_updated }
19
- # Status is the responsibility of plan.yaml (read on demand by /mvt-resume).
20
- recent_changes: []
13
+ changes: []
14
+ # - id: "chg-001"
15
+ # title: "User authentication"
16
+ # plan_path: ".ai-agents/workspace/artifacts/chg-001/plan.yaml"
17
+ # status: "active" # active | done | abandoned
18
+ # updated_at: "2026-05-23T14:30:00"
21
19
 
22
- # Skill execution history (append-only, max 10)
23
- # Records which skills have been executed, replacing the old progress field
24
- skill_history: []
25
- # - command: "/mvt-analyze"
20
+ history: []
21
+ # - skill: "/mvt-analyze"
26
22
  # completed_at: "2026-05-23T14:30:00"
27
23
  # summary: "Analyzed user authentication requirements"
28
24
  # change_id: "" # set when work belongs to an active change
29
-
30
- # Recent actions (append-only, max 5)
31
- recent_actions: []
@@ -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
@@ -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}}
@@ -71,6 +71,7 @@ sections:
71
71
  - type: shared
72
72
  source: sections/session-update.md
73
73
  params:
74
+ current_skill: mvt-analyze
74
75
  update_active_change: true
75
76
 
76
77
  - type: shared
@@ -71,10 +71,11 @@ sections:
71
71
 
72
72
  - type: inline
73
73
  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`
74
+ ## Operation Mode: Independent
75
+
76
+ This is an independent operation — no workflow prerequisites required.
77
+ - Does NOT create a change-id.
78
+ - Output is written to `.ai-agents/knowledge/project/_generated/project-context.md`.
78
79
 
79
80
  - type: file
80
81
  source: ./business.md
@@ -89,8 +90,21 @@ sections:
89
90
 
90
91
  - type: shared
91
92
  source: sections/session-update.md
93
+ params:
94
+ current_skill: mvt-analyze-code
92
95
 
93
96
  - type: shared
94
97
  source: sections/footer-next-steps.md
95
98
  params:
96
99
  current_skill: mvt-analyze-code
100
+ conditional_suggestions:
101
+ conditions:
102
+ - condition: "project-context.md generated, no active change"
103
+ primary: mvt-analyze
104
+ primary_desc: "Analyze requirements for a new feature"
105
+ - condition: "project-context.md generated, active change exists"
106
+ primary: mvt-design
107
+ primary_desc: "Design with the updated project context"
108
+ - condition: "analysis revealed outdated knowledge"
109
+ primary: mvt-manage-context
110
+ primary_desc: "Update knowledge entries"