@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.
- package/README.md +1 -11
- package/dist/build/section-loader.d.ts.map +1 -1
- package/dist/build/section-loader.js +18 -8
- package/dist/build/section-loader.js.map +1 -1
- package/dist/fs/materialize.d.ts.map +1 -1
- package/dist/fs/materialize.js +5 -0
- package/dist/fs/materialize.js.map +1 -1
- package/dist/scripts/session-update.cjs +7568 -0
- package/install-manifest.yaml +2 -0
- package/package.json +3 -2
- package/sources/defaults/config.yaml +7 -7
- package/sources/defaults/session.yaml +9 -16
- package/sources/scripts/session-update.js +351 -0
- package/sources/sections/activation-load-context.md +4 -0
- package/sources/sections/footer-next-steps.md +1 -1
- package/sources/sections/session-update.md +100 -32
- package/sources/skills/mvt-analyze/manifest.yaml +1 -0
- package/sources/skills/mvt-analyze-code/manifest.yaml +18 -4
- package/sources/skills/mvt-bug-detect/business.md +99 -101
- package/sources/skills/mvt-bug-detect/manifest.yaml +84 -84
- package/sources/skills/mvt-check-context/business.md +3 -5
- package/sources/skills/mvt-check-context/manifest.yaml +15 -8
- package/sources/skills/mvt-cleanup/business.md +49 -23
- package/sources/skills/mvt-cleanup/manifest.yaml +15 -10
- package/sources/skills/mvt-config/business.md +1 -2
- package/sources/skills/mvt-config/manifest.yaml +11 -4
- package/sources/skills/mvt-create-skill/business.md +6 -5
- package/sources/skills/mvt-create-skill/manifest.yaml +27 -11
- package/sources/skills/mvt-design/business.md +3 -6
- package/sources/skills/mvt-design/manifest.yaml +14 -1
- package/sources/skills/mvt-fix/business.md +2 -1
- package/sources/skills/mvt-fix/manifest.yaml +6 -3
- package/sources/skills/mvt-help/business.md +2 -4
- package/sources/skills/mvt-help/manifest.yaml +13 -5
- package/sources/skills/mvt-implement/business.md +4 -5
- package/sources/skills/mvt-implement/manifest.yaml +13 -0
- package/sources/skills/mvt-init/business.md +2 -2
- package/sources/skills/mvt-init/manifest.yaml +1 -0
- package/sources/skills/mvt-manage-context/business.md +11 -0
- package/sources/skills/mvt-manage-context/manifest.yaml +14 -3
- package/sources/skills/mvt-plan-dev/business.md +101 -20
- package/sources/skills/mvt-plan-dev/manifest.yaml +18 -19
- package/sources/skills/mvt-quick-dev/business.md +2 -1
- package/sources/skills/mvt-quick-dev/manifest.yaml +21 -6
- package/sources/skills/mvt-refactor/business.md +2 -1
- package/sources/skills/mvt-refactor/manifest.yaml +21 -3
- package/sources/skills/mvt-resume/business.md +28 -68
- package/sources/skills/mvt-resume/manifest.yaml +17 -7
- package/sources/skills/mvt-review/business.md +3 -3
- package/sources/skills/mvt-review/manifest.yaml +22 -1
- package/sources/skills/mvt-status/business.md +14 -18
- package/sources/skills/mvt-status/manifest.yaml +11 -3
- package/sources/skills/mvt-sync-context/business.md +14 -9
- package/sources/skills/mvt-sync-context/manifest.yaml +3 -0
- package/sources/skills/mvt-template/business.md +0 -2
- package/sources/skills/mvt-template/manifest.yaml +13 -8
- package/sources/skills/mvt-test/business.md +3 -3
- package/sources/skills/mvt-test/manifest.yaml +14 -1
- package/sources/skills/mvt-update-plan/business.md +14 -3
- package/sources/skills/mvt-update-plan/manifest.yaml +11 -19
- package/dist/build/plan-validator.d.ts +0 -26
- package/dist/build/plan-validator.d.ts.map +0 -1
- package/dist/build/plan-validator.js +0 -225
- package/dist/build/plan-validator.js.map +0 -1
- package/dist/commands/build.d.ts +0 -5
- package/dist/commands/build.d.ts.map +0 -1
- package/dist/commands/build.js +0 -46
- package/dist/commands/build.js.map +0 -1
- package/dist/commands/migrate.d.ts +0 -16
- package/dist/commands/migrate.d.ts.map +0 -1
- package/dist/commands/migrate.js +0 -118
- package/dist/commands/migrate.js.map +0 -1
package/install-manifest.yaml
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
|
|
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
|
-
|
|
23
|
-
#
|
|
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
|
-
- `
|
|
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`.
|
|
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
|
|
8
|
-
|
|
9
|
-
After
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
14
|
+
```
|
|
28
15
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
53
|
+
{{#update_active_change}}
|
|
54
|
+
### Parameter semantics
|
|
36
55
|
|
|
37
|
-
|
|
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
|
-
###
|
|
112
|
+
### Failure handling
|
|
41
113
|
|
|
42
|
-
-
|
|
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,10 +71,11 @@ sections:
|
|
|
71
71
|
|
|
72
72
|
- type: inline
|
|
73
73
|
content: |
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
-
|
|
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"
|