@uoyo/mvtt 2.0.0-beta.3 → 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.
- package/dist/fs/materialize.d.ts.map +1 -1
- package/dist/fs/materialize.js +7 -4
- package/dist/fs/materialize.js.map +1 -1
- package/dist/fs/registry-merge.d.ts +19 -0
- package/dist/fs/registry-merge.d.ts.map +1 -0
- package/dist/fs/registry-merge.js +177 -0
- package/dist/fs/registry-merge.js.map +1 -0
- package/dist/scripts/plan-update.cjs +7563 -0
- package/install-manifest.yaml +6 -2
- package/package.json +1 -1
- package/sources/scripts/plan-update.js +353 -0
- package/sources/sections/output-format-constraint.md +14 -0
- package/sources/sections/project-context-profile.md +29 -0
- package/sources/skills/mvt-analyze/manifest.yaml +3 -0
- package/sources/skills/mvt-analyze-code/manifest.yaml +6 -0
- package/sources/skills/mvt-cleanup/manifest.yaml +3 -0
- package/sources/skills/mvt-create-skill/manifest.yaml +3 -0
- package/sources/skills/mvt-design/manifest.yaml +3 -0
- package/sources/skills/mvt-fix/manifest.yaml +3 -0
- package/sources/skills/mvt-implement/business.md +9 -5
- package/sources/skills/mvt-implement/manifest.yaml +3 -0
- package/sources/skills/mvt-init/manifest.yaml +3 -0
- package/sources/skills/mvt-manage-context/manifest.yaml +6 -0
- package/sources/skills/mvt-plan-dev/manifest.yaml +3 -0
- package/sources/skills/mvt-quick-dev/manifest.yaml +3 -0
- package/sources/skills/mvt-refactor/manifest.yaml +3 -0
- package/sources/skills/mvt-review/manifest.yaml +3 -0
- package/sources/skills/mvt-sync-context/business.md +58 -29
- package/sources/skills/mvt-sync-context/manifest.yaml +6 -0
- package/sources/skills/mvt-test/manifest.yaml +3 -0
- package/sources/skills/mvt-update-plan/business.md +30 -28
- package/sources/skills/mvt-update-plan/manifest.yaml +3 -0
package/install-manifest.yaml
CHANGED
|
@@ -9,10 +9,10 @@ generated:
|
|
|
9
9
|
source: "build:templates"
|
|
10
10
|
- pattern: ".ai-agents/knowledge/core/_framework/**"
|
|
11
11
|
source: "copy:sources/knowledge/core/_framework/"
|
|
12
|
-
- pattern: ".ai-agents/registry.yaml"
|
|
13
|
-
source: "copy:registry.yaml"
|
|
14
12
|
- pattern: ".ai-agents/scripts/session-update.cjs"
|
|
15
13
|
source: "bundle:sources/scripts/session-update.js"
|
|
14
|
+
- pattern: ".ai-agents/scripts/plan-update.cjs"
|
|
15
|
+
source: "bundle:sources/scripts/plan-update.js"
|
|
16
16
|
|
|
17
17
|
create_once:
|
|
18
18
|
- path: ".ai-agents/config.yaml"
|
|
@@ -23,6 +23,10 @@ create_once:
|
|
|
23
23
|
source: "sources/defaults/project-context.yaml"
|
|
24
24
|
- path: ".ai-agents/knowledge/core/manifest.yaml"
|
|
25
25
|
source: "sources/knowledge/core/manifest.yaml"
|
|
26
|
+
# Reconcile-merged on update (see src/fs/registry-merge.ts): framework skills
|
|
27
|
+
# are refreshed, user `custom: true` skills and added knowledge bindings kept.
|
|
28
|
+
- path: ".ai-agents/registry.yaml"
|
|
29
|
+
source: "registry.yaml"
|
|
26
30
|
|
|
27
31
|
user_data_dirs:
|
|
28
32
|
- ".ai-agents/workspace/artifacts/"
|
package/package.json
CHANGED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* plan-update.js — Mechanical plan.yaml mutation script
|
|
5
|
+
*
|
|
6
|
+
* Replaces AI-driven plan.yaml mutation with a deterministic Node.js script.
|
|
7
|
+
* /mvt-update-plan calls this instead of hand-editing the plan: it applies a
|
|
8
|
+
* single task status change, recomputes current_task via the DAG rules, runs
|
|
9
|
+
* the full plan validator, and writes back atomically.
|
|
10
|
+
*
|
|
11
|
+
* The LLM stays responsible for the semantic parts (resolving a default task,
|
|
12
|
+
* mapping natural-language "done"/"blocked: <reason>" to arguments, rendering
|
|
13
|
+
* the JSON result into the user-facing summary). This script does only the
|
|
14
|
+
* mechanical, rule-driven work.
|
|
15
|
+
*
|
|
16
|
+
* NOTE: This source file uses `import from "yaml"`. During the build pipeline,
|
|
17
|
+
* esbuild bundles it into a zero-dependency single file deployed to
|
|
18
|
+
* .ai-agents/scripts/plan-update.cjs.
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* node .ai-agents/scripts/plan-update.cjs \
|
|
22
|
+
* --plan <path-to-plan.yaml> \
|
|
23
|
+
* --task <task_id> \
|
|
24
|
+
* --status <pending|in_progress|done|blocked|skipped> \
|
|
25
|
+
* [--artifacts "<comma,separated,paths>"] \
|
|
26
|
+
* [--notes "<free-form text>"]
|
|
27
|
+
*
|
|
28
|
+
* Output:
|
|
29
|
+
* Success (exit 0): one-line JSON on stdout, e.g.
|
|
30
|
+
* {"ok":true,"task":{...},"current_task":"t2","plan_status":"in_progress",...}
|
|
31
|
+
* Failure (exit 1): plain-text error message(s) on stderr
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { readFileSync, writeFileSync, renameSync, unlinkSync, existsSync } from "node:fs";
|
|
35
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
36
|
+
|
|
37
|
+
// ── Constants ─────────────────────────────────────────────────────────────
|
|
38
|
+
const VALID_STATUSES = ["pending", "in_progress", "done", "blocked", "skipped"];
|
|
39
|
+
const TERMINAL_STATUSES = ["done", "blocked", "skipped"];
|
|
40
|
+
|
|
41
|
+
const ERRORS = {
|
|
42
|
+
MISSING_PLAN: () => "Missing required argument: --plan",
|
|
43
|
+
MISSING_TASK: () => "Missing required argument: --task",
|
|
44
|
+
MISSING_STATUS: () => "Missing required argument: --status",
|
|
45
|
+
INVALID_STATUS: (val) =>
|
|
46
|
+
`Invalid --status "${val}". Must be one of: ${VALID_STATUSES.join(", ")}.`,
|
|
47
|
+
PLAN_NOT_FOUND: (p) => `Plan not found at ${p}. Run /mvt-plan-dev to create one.`,
|
|
48
|
+
PLAN_PARSE_FAILED: (detail) =>
|
|
49
|
+
`Failed to parse plan.yaml: ${detail}. Fix the file manually; not repairing silently.`,
|
|
50
|
+
TASK_NOT_FOUND: (id, valid) =>
|
|
51
|
+
`Task "${id}" not found. Valid task ids: ${valid.length ? valid.join(", ") : "(none)"}.`,
|
|
52
|
+
VALIDATION_FAILED: (errs) =>
|
|
53
|
+
`Plan validation failed; file not written:\n - ${errs.join("\n - ")}`,
|
|
54
|
+
PLAN_WRITE_FAILED: (detail) => `Failed to write plan.yaml: ${detail}`,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// ── CLI Parsing ─────────────────────────────────────────────────────────────
|
|
58
|
+
function parseArgs(argv) {
|
|
59
|
+
const args = {};
|
|
60
|
+
for (let i = 2; i < argv.length; i++) {
|
|
61
|
+
if (argv[i].startsWith("--")) {
|
|
62
|
+
const key = argv[i].slice(2);
|
|
63
|
+
const next = argv[i + 1];
|
|
64
|
+
if (next && !next.startsWith("--")) {
|
|
65
|
+
args[key] = next;
|
|
66
|
+
i++;
|
|
67
|
+
} else {
|
|
68
|
+
args[key] = true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return args;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function validateArgs(args) {
|
|
76
|
+
if (!args.plan || args.plan === true) return ERRORS.MISSING_PLAN();
|
|
77
|
+
if (!args.task || args.task === true) return ERRORS.MISSING_TASK();
|
|
78
|
+
if (!args.status || args.status === true) return ERRORS.MISSING_STATUS();
|
|
79
|
+
if (!VALID_STATUSES.includes(args.status)) return ERRORS.INVALID_STATUS(args.status);
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── Mutation ─────────────────────────────────────────────────────────────────
|
|
84
|
+
function applyUpdate(plan, args, now) {
|
|
85
|
+
const task = plan.tasks.find((t) => t.id === args.task);
|
|
86
|
+
|
|
87
|
+
const oldStatus = task.status;
|
|
88
|
+
task.status = args.status;
|
|
89
|
+
|
|
90
|
+
if (args.artifacts && args.artifacts !== true) {
|
|
91
|
+
const incoming = args.artifacts
|
|
92
|
+
.split(",")
|
|
93
|
+
.map((s) => s.trim())
|
|
94
|
+
.filter(Boolean);
|
|
95
|
+
if (incoming.length) {
|
|
96
|
+
// artifacts may be null or missing a `files` key on initial plans.
|
|
97
|
+
if (!task.artifacts || typeof task.artifacts !== "object") {
|
|
98
|
+
task.artifacts = { files: [] };
|
|
99
|
+
}
|
|
100
|
+
if (!Array.isArray(task.artifacts.files)) {
|
|
101
|
+
task.artifacts.files = [];
|
|
102
|
+
}
|
|
103
|
+
const seen = new Set(task.artifacts.files);
|
|
104
|
+
for (const f of incoming) {
|
|
105
|
+
if (!seen.has(f)) {
|
|
106
|
+
task.artifacts.files.push(f);
|
|
107
|
+
seen.add(f);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (args.notes && args.notes !== true) {
|
|
114
|
+
task.notes = args.notes;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// completed_at consistency: set only when transitioning to done, else null.
|
|
118
|
+
task.completed_at = args.status === "done" ? now : null;
|
|
119
|
+
|
|
120
|
+
plan.updated_at = now;
|
|
121
|
+
|
|
122
|
+
return { id: task.id, title: task.title || "", old_status: oldStatus, new_status: args.status };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── current_task recomputation (mirrors mvt-update-plan Step 4) ────────────────
|
|
126
|
+
function recomputeCurrentTask(plan, changedTaskId) {
|
|
127
|
+
let warning = null;
|
|
128
|
+
|
|
129
|
+
const changedTask = plan.tasks.find((t) => t.id === changedTaskId);
|
|
130
|
+
const changedToTerminal =
|
|
131
|
+
changedTask && TERMINAL_STATUSES.includes(changedTask.status);
|
|
132
|
+
|
|
133
|
+
// 1. An in_progress task that is NOT the one we just moved to terminal wins.
|
|
134
|
+
const activeInProgress = plan.tasks.find(
|
|
135
|
+
(t) => t.status === "in_progress" && !(t.id === changedTaskId && changedToTerminal)
|
|
136
|
+
);
|
|
137
|
+
if (activeInProgress) {
|
|
138
|
+
plan.current_task = activeInProgress.id;
|
|
139
|
+
plan.status = "in_progress";
|
|
140
|
+
return { warning };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// 2. First pending task whose deps are all done.
|
|
144
|
+
const doneIds = new Set(
|
|
145
|
+
plan.tasks.filter((t) => t.status === "done").map((t) => t.id)
|
|
146
|
+
);
|
|
147
|
+
const nextPending = plan.tasks.find(
|
|
148
|
+
(t) =>
|
|
149
|
+
t.status === "pending" &&
|
|
150
|
+
(t.depends_on || []).every((d) => doneIds.has(d))
|
|
151
|
+
);
|
|
152
|
+
if (nextPending) {
|
|
153
|
+
nextPending.status = "in_progress";
|
|
154
|
+
plan.current_task = nextPending.id;
|
|
155
|
+
plan.status = "in_progress";
|
|
156
|
+
return { warning };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 3. Everything done -> plan complete.
|
|
160
|
+
if (plan.tasks.every((t) => t.status === "done")) {
|
|
161
|
+
plan.status = "done";
|
|
162
|
+
plan.current_task = null;
|
|
163
|
+
return { warning };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 4. Pending tasks remain but none are executable (blocked by deps).
|
|
167
|
+
plan.current_task = null;
|
|
168
|
+
plan.status = "in_progress";
|
|
169
|
+
warning =
|
|
170
|
+
"All remaining tasks are blocked by dependencies; resolve a blocker before continuing.";
|
|
171
|
+
return { warning };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── Validation (mirrors mvt-plan-dev Step 5 + mvt-update-plan Step 5) ──────────
|
|
175
|
+
function validatePlan(plan) {
|
|
176
|
+
const errors = [];
|
|
177
|
+
const tasks = Array.isArray(plan.tasks) ? plan.tasks : [];
|
|
178
|
+
|
|
179
|
+
// Unique ids
|
|
180
|
+
const ids = tasks.map((t) => t.id);
|
|
181
|
+
const dupes = ids.filter((id, i) => ids.indexOf(id) !== i);
|
|
182
|
+
if (dupes.length) {
|
|
183
|
+
errors.push(`Duplicate task ids: ${[...new Set(dupes)].join(", ")}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const idSet = new Set(ids);
|
|
187
|
+
|
|
188
|
+
// Valid depends_on references
|
|
189
|
+
for (const t of tasks) {
|
|
190
|
+
for (const d of t.depends_on || []) {
|
|
191
|
+
if (!idSet.has(d)) {
|
|
192
|
+
errors.push(`Task "${t.id}" depends_on unknown task "${d}"`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// DAG (no cycles) — only meaningful if all references resolve.
|
|
198
|
+
const cycle = findCycle(tasks);
|
|
199
|
+
if (cycle) {
|
|
200
|
+
errors.push(`Dependency cycle detected: ${cycle.join(" -> ")}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// At most one in_progress
|
|
204
|
+
const inProgress = tasks.filter((t) => t.status === "in_progress");
|
|
205
|
+
if (inProgress.length > 1) {
|
|
206
|
+
errors.push(
|
|
207
|
+
`More than one task is in_progress: ${inProgress.map((t) => t.id).join(", ")}`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Acceptance required
|
|
212
|
+
for (const t of tasks) {
|
|
213
|
+
if (!Array.isArray(t.acceptance) || t.acceptance.length === 0) {
|
|
214
|
+
errors.push(`Task "${t.id}" has no acceptance criteria`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// completed_at consistency
|
|
219
|
+
for (const t of tasks) {
|
|
220
|
+
if (t.status !== "done" && t.completed_at != null) {
|
|
221
|
+
errors.push(`Task "${t.id}" is not done but has completed_at set`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// current_task validity
|
|
226
|
+
if (plan.status === "done") {
|
|
227
|
+
if (plan.current_task != null) {
|
|
228
|
+
errors.push("plan.status is done but current_task is not null");
|
|
229
|
+
}
|
|
230
|
+
} else if (plan.current_task != null) {
|
|
231
|
+
const ct = tasks.find((t) => t.id === plan.current_task);
|
|
232
|
+
if (!ct) {
|
|
233
|
+
errors.push(`current_task "${plan.current_task}" does not reference a task`);
|
|
234
|
+
} else if (ct.status !== "pending" && ct.status !== "in_progress") {
|
|
235
|
+
errors.push(
|
|
236
|
+
`current_task "${plan.current_task}" has status "${ct.status}" (must be pending or in_progress)`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return errors;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Returns an array describing a cycle path, or null if the graph is a DAG.
|
|
245
|
+
function findCycle(tasks) {
|
|
246
|
+
const adj = new Map();
|
|
247
|
+
for (const t of tasks) adj.set(t.id, t.depends_on || []);
|
|
248
|
+
|
|
249
|
+
const WHITE = 0, GRAY = 1, BLACK = 2;
|
|
250
|
+
const color = new Map(tasks.map((t) => [t.id, WHITE]));
|
|
251
|
+
const stack = [];
|
|
252
|
+
|
|
253
|
+
function dfs(node) {
|
|
254
|
+
color.set(node, GRAY);
|
|
255
|
+
stack.push(node);
|
|
256
|
+
for (const dep of adj.get(node) || []) {
|
|
257
|
+
if (!color.has(dep)) continue; // unresolved ref handled elsewhere
|
|
258
|
+
if (color.get(dep) === GRAY) {
|
|
259
|
+
const start = stack.indexOf(dep);
|
|
260
|
+
return [...stack.slice(start), dep];
|
|
261
|
+
}
|
|
262
|
+
if (color.get(dep) === WHITE) {
|
|
263
|
+
const found = dfs(dep);
|
|
264
|
+
if (found) return found;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
stack.pop();
|
|
268
|
+
color.set(node, BLACK);
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
for (const t of tasks) {
|
|
273
|
+
if (color.get(t.id) === WHITE) {
|
|
274
|
+
const found = dfs(t.id);
|
|
275
|
+
if (found) return found;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
282
|
+
function main() {
|
|
283
|
+
const args = parseArgs(process.argv);
|
|
284
|
+
|
|
285
|
+
const argErr = validateArgs(args);
|
|
286
|
+
if (argErr) {
|
|
287
|
+
process.stderr.write(argErr + "\n");
|
|
288
|
+
process.exit(1);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (!existsSync(args.plan)) {
|
|
292
|
+
process.stderr.write(ERRORS.PLAN_NOT_FOUND(args.plan) + "\n");
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let plan;
|
|
297
|
+
try {
|
|
298
|
+
plan = parseYaml(readFileSync(args.plan, "utf-8"));
|
|
299
|
+
} catch (e) {
|
|
300
|
+
process.stderr.write(ERRORS.PLAN_PARSE_FAILED(e.message) + "\n");
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (!plan || !Array.isArray(plan.tasks)) {
|
|
305
|
+
process.stderr.write(ERRORS.PLAN_PARSE_FAILED("missing tasks[]") + "\n");
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (!plan.tasks.some((t) => t.id === args.task)) {
|
|
310
|
+
process.stderr.write(
|
|
311
|
+
ERRORS.TASK_NOT_FOUND(args.task, plan.tasks.map((t) => t.id)) + "\n"
|
|
312
|
+
);
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const now = new Date().toISOString();
|
|
317
|
+
|
|
318
|
+
const taskChange = applyUpdate(plan, args, now);
|
|
319
|
+
const { warning } = recomputeCurrentTask(plan, args.task);
|
|
320
|
+
|
|
321
|
+
const validationErrors = validatePlan(plan);
|
|
322
|
+
if (validationErrors.length) {
|
|
323
|
+
process.stderr.write(ERRORS.VALIDATION_FAILED(validationErrors) + "\n");
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const tmpPath = args.plan + ".tmp";
|
|
328
|
+
try {
|
|
329
|
+
writeFileSync(tmpPath, stringifyYaml(plan), "utf-8");
|
|
330
|
+
renameSync(tmpPath, args.plan);
|
|
331
|
+
} catch (e) {
|
|
332
|
+
try {
|
|
333
|
+
if (existsSync(tmpPath)) unlinkSync(tmpPath);
|
|
334
|
+
} catch {
|
|
335
|
+
// best effort
|
|
336
|
+
}
|
|
337
|
+
process.stderr.write(ERRORS.PLAN_WRITE_FAILED(e.message) + "\n");
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const doneCount = plan.tasks.filter((t) => t.status === "done").length;
|
|
342
|
+
const result = {
|
|
343
|
+
ok: true,
|
|
344
|
+
task: taskChange,
|
|
345
|
+
current_task: plan.current_task ?? null,
|
|
346
|
+
plan_status: plan.status,
|
|
347
|
+
progress: { done: doneCount, total: plan.tasks.length },
|
|
348
|
+
warning,
|
|
349
|
+
};
|
|
350
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
main();
|
|
@@ -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.
|
|
@@ -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:
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
2. Topologically order files by dependency: domain entities -> repositories/adapters -> use-case/services -> controllers/UI.
|
|
15
15
|
3. Group consecutive files that share a single conceptual change into one commit boundary.
|
|
16
16
|
4. For each file, decide: `create | modify | delete`, and write a one-line intent.
|
|
17
|
-
- **Plan-aware behavior**: if `plan.yaml` is present,
|
|
17
|
+
- **Plan-aware behavior**: if `plan.yaml` is present, treat `current_task`'s `artifacts.files` (cross-reference `plan.tasks[*].artifacts.files`) as a **starting-scope hint, not a hard ceiling**. The source of truth for scope remains `design.md`'s `Change Tracking` (per Step 2.1). When `artifacts.files` is `null` or empty, derive scope entirely from `Change Tracking`. If implementation reveals files beyond this hint are genuinely required, do NOT silently expand — surface them via Step 3 confirmation and never absorb files that clearly belong to a different task.
|
|
18
18
|
- **Output of this step**: an in-conversation list shown to user as a preview, with no write yet.
|
|
19
19
|
|
|
20
20
|
### Step 3: Confirm Scope (when needed)
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
- The plan introduces a new public API (exported symbol, HTTP endpoint, CLI flag).
|
|
24
24
|
- The plan deletes existing code (delete count > 0).
|
|
25
25
|
- The plan deviates from `design.md` (e.g., adds files not in `Change Tracking` or skips files listed there).
|
|
26
|
+
- The plan touches files beyond `current_task`'s `artifacts.files` hint (state which files are added and why, in one line each).
|
|
26
27
|
- **Otherwise**: proceed silently.
|
|
27
28
|
- **On deviation from design**: explain the deviation reason in one line; if the deviation is structural (new module, layer change, interface break), STOP and recommend re-running `/mvt-design`.
|
|
28
29
|
|
|
@@ -59,7 +60,12 @@
|
|
|
59
60
|
3. UI/frontend changes: per project rules, ask user to verify in browser; do NOT claim "tested" if you only ran type-check.
|
|
60
61
|
|
|
61
62
|
### Step 7: Write Artifact
|
|
62
|
-
- **Path and template**: as defined in the **Artifact Structure** section below.
|
|
63
|
+
- **Path and template**: as defined in the **Artifact Structure** section below. The artifact filename is ALWAYS `implementation.md` — one file per change, never per task. Do NOT invent task-suffixed names like `implementation-t1.md`.
|
|
64
|
+
- **Multi-task plans (single-file accumulation)**: when `plan.yaml` drives the change and you implement tasks across separate invocations, all task implementations accumulate into the **same** `implementation.md`:
|
|
65
|
+
1. If `implementation.md` does not yet exist -> create it from the template.
|
|
66
|
+
2. If it already exists -> read it, then **append** a new `## Task: {current_task_id} — {task_title}` section for this task. Do NOT overwrite prior tasks' sections.
|
|
67
|
+
3. Under that task section, place this invocation's required content (the headings below). Keep earlier tasks' sections intact.
|
|
68
|
+
4. For single-task or plan-less changes, write the content at the top level without a per-task wrapper (existing behavior).
|
|
63
69
|
- **Required content** (mapped to template headings):
|
|
64
70
|
- `Implementation Summary` -- one paragraph: what was built, scope.
|
|
65
71
|
- `Files Touched` -- table: path | create/modify/delete | one-line intent.
|
|
@@ -71,12 +77,10 @@
|
|
|
71
77
|
|
|
72
78
|
### Step 8: Plan-Aware Progress Hint (if applicable)
|
|
73
79
|
- If `plan.yaml` exists and a single `current_task` covers this implementation, suggest the user run `/mvt-update-plan <task-id> done` (or `blocked` with reason).
|
|
80
|
+
- If the files actually touched differ from `current_task`'s `artifacts.files` (extra files added during Step 3, or planned files left untouched), explicitly remind the user to run `/mvt-update-plan` so the plan's `artifacts.files` reflects reality for `/mvt-resume` and future sessions.
|
|
74
81
|
- Do NOT modify `plan.yaml` directly from this skill; it is owned by `/mvt-update-plan`.
|
|
75
82
|
- Do NOT modify `changes` directly; it is owned by `/mvt-plan-dev` / `/mvt-update-plan`.
|
|
76
83
|
|
|
77
|
-
### Step 9: State Update
|
|
78
|
-
Apply the State Update rules defined in the **State Update** section below.
|
|
79
|
-
|
|
80
84
|
## Edge Cases & Errors
|
|
81
85
|
|
|
82
86
|
| Case | Handling |
|
|
@@ -14,6 +14,9 @@ sections:
|
|
|
14
14
|
|
|
15
15
|
Unified CRUD entry point for project context and knowledge entries. Handles add (with AI-driven skill routing), remove, move, rename, and list operations across `project-context.yaml`, `project-context.md`, `knowledge/principle/`, `knowledge/project/`, `knowledge/core/user/`, and the corresponding `registry.yaml` / `core/manifest.yaml` references.
|
|
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:
|
|
@@ -46,6 +49,9 @@ sections:
|
|
|
46
49
|
- type: shared
|
|
47
50
|
source: sections/output-language-constraint.md
|
|
48
51
|
|
|
52
|
+
- type: shared
|
|
53
|
+
source: sections/output-format-constraint.md
|
|
54
|
+
|
|
49
55
|
- type: inline
|
|
50
56
|
content: |
|
|
51
57
|
### Step 4: Pre-flight Checks
|