@uoyo/mvtt 2.0.0-beta.3 → 2.0.0-beta.6
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 +299 -64
- package/README.zh-CN.md +419 -0
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +27 -2
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +19 -7
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +4 -2
- package/dist/commands/update.js.map +1 -1
- package/dist/fs/install-manifest.d.ts +4 -1
- package/dist/fs/install-manifest.d.ts.map +1 -1
- package/dist/fs/install-manifest.js +13 -1
- package/dist/fs/install-manifest.js.map +1 -1
- package/dist/fs/materialize.d.ts +2 -0
- package/dist/fs/materialize.d.ts.map +1 -1
- package/dist/fs/materialize.js +46 -13
- 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 +220 -0
- package/dist/fs/registry-merge.js.map +1 -0
- package/dist/scripts/epic-update.cjs +7670 -0
- package/dist/scripts/plan-update.cjs +7736 -0
- package/dist/scripts/session-update.cjs +84 -6
- package/dist/types/platform.d.ts +12 -0
- package/dist/types/platform.d.ts.map +1 -0
- package/dist/types/platform.js +24 -0
- package/dist/types/platform.js.map +1 -0
- package/dist/types/registry.d.ts +3 -24
- package/dist/types/registry.d.ts.map +1 -1
- package/install-manifest.yaml +10 -2
- package/package.json +1 -1
- package/registry.yaml +72 -198
- package/sources/defaults/config.yaml +8 -13
- package/sources/defaults/project-context.yaml +2 -5
- package/sources/defaults/session.yaml +14 -2
- package/sources/knowledge/core/manifest.yaml +1 -4
- package/sources/scripts/epic-update.js +512 -0
- package/sources/scripts/plan-update.js +614 -0
- package/sources/scripts/session-update.js +102 -2
- package/sources/sections/activation-load-config.md +1 -1
- package/sources/sections/activation-load-context.md +42 -13
- package/sources/sections/activation-preflight.md +1 -1
- package/sources/sections/footer-next-steps.md +3 -2
- package/sources/sections/output-format-constraint.md +14 -0
- package/sources/sections/project-context-profile.md +29 -0
- package/sources/sections/session-update.md +41 -1
- package/sources/skills/mvt-analyze/business.md +46 -8
- package/sources/skills/mvt-analyze/manifest.yaml +8 -1
- package/sources/skills/mvt-analyze-code/business.md +18 -17
- package/sources/skills/mvt-analyze-code/manifest.yaml +9 -6
- package/sources/skills/mvt-check-context/business.md +13 -6
- package/sources/skills/mvt-check-context/manifest.yaml +0 -5
- package/sources/skills/mvt-cleanup/business.md +17 -2
- package/sources/skills/mvt-cleanup/manifest.yaml +3 -0
- package/sources/skills/mvt-config/business.md +5 -5
- package/sources/skills/mvt-config/manifest.yaml +3 -6
- package/sources/skills/mvt-create-skill/business.md +2 -14
- package/sources/skills/mvt-create-skill/manifest.yaml +2 -4
- package/sources/skills/mvt-decompose/business.md +94 -0
- package/sources/skills/mvt-decompose/manifest.yaml +121 -0
- package/sources/skills/mvt-design/manifest.yaml +3 -0
- package/sources/skills/mvt-fix/business.md +21 -6
- package/sources/skills/mvt-fix/manifest.yaml +4 -1
- package/sources/skills/mvt-help/business.md +11 -9
- package/sources/skills/mvt-help/manifest.yaml +0 -5
- package/sources/skills/mvt-implement/business.md +57 -10
- package/sources/skills/mvt-implement/manifest.yaml +3 -0
- package/sources/skills/mvt-init/business.md +23 -13
- package/sources/skills/mvt-init/manifest.yaml +4 -2
- package/sources/skills/mvt-manage-context/business.md +41 -14
- package/sources/skills/mvt-manage-context/manifest.yaml +7 -5
- package/sources/skills/mvt-plan-dev/business.md +17 -9
- package/sources/skills/mvt-plan-dev/manifest.yaml +3 -0
- package/sources/skills/mvt-quick-dev/business.md +22 -7
- package/sources/skills/mvt-quick-dev/manifest.yaml +3 -1
- package/sources/skills/mvt-refactor/business.md +32 -17
- package/sources/skills/mvt-refactor/manifest.yaml +2 -4
- package/sources/skills/mvt-resume/business.md +32 -12
- package/sources/skills/mvt-resume/manifest.yaml +3 -3
- package/sources/skills/mvt-review/business.md +24 -9
- package/sources/skills/mvt-review/manifest.yaml +3 -0
- package/sources/skills/mvt-status/business.md +37 -9
- package/sources/skills/mvt-status/manifest.yaml +2 -2
- package/sources/skills/mvt-sync-context/business.md +77 -34
- package/sources/skills/mvt-sync-context/manifest.yaml +6 -0
- package/sources/skills/mvt-template/business.md +1 -1
- package/sources/skills/mvt-template/manifest.yaml +0 -5
- package/sources/skills/mvt-test/business.md +30 -15
- package/sources/skills/mvt-test/manifest.yaml +3 -0
- package/sources/skills/mvt-update-plan/business.md +64 -33
- package/sources/skills/mvt-update-plan/manifest.yaml +10 -7
- package/sources/templates/decompose-output/body.md +13 -0
- package/sources/templates/decompose-output/manifest.yaml +11 -0
|
@@ -23,7 +23,12 @@
|
|
|
23
23
|
* [--set-change-status <status>] \
|
|
24
24
|
* [--no-change] \
|
|
25
25
|
* [--set-synced] \
|
|
26
|
-
* [--truncate-history <n>]
|
|
26
|
+
* [--truncate-history <n>] \
|
|
27
|
+
* [--new-epic <title> --epic-id <id>] \
|
|
28
|
+
* [--set-epic-path <path>] \
|
|
29
|
+
* [--set-epic-status <status>] \
|
|
30
|
+
* [--close-epic] \
|
|
31
|
+
* [--epic-id <id>] (with --new-change, links sub-change to epic)
|
|
27
32
|
*
|
|
28
33
|
* Output:
|
|
29
34
|
* Success (exit 0): {"ok":true}
|
|
@@ -48,6 +53,10 @@ const ERRORS = {
|
|
|
48
53
|
SESSION_WRITE_FAILED: (detail) => `Failed to write session.yaml: ${detail}`,
|
|
49
54
|
CONFIG_LIMIT_INVALID: (key, val, min, max, fallback) =>
|
|
50
55
|
`Warning: config history_limits.${key} value "${val}" is invalid (must be integer ${min}-${max}). Using default ${fallback}.`,
|
|
56
|
+
EPIC_ID_REQUIRED: () => "--new-epic requires --epic-id",
|
|
57
|
+
CLOSE_NEW_EPIC_CONFLICT: () => "--close-epic and --new-epic are mutually exclusive",
|
|
58
|
+
NO_ACTIVE_EPIC: (flag) => `${flag} requires an active epic (active_epic.id is empty)`,
|
|
59
|
+
EPIC_ID_ORPHAN: () => "--epic-id (for sub-change) requires --new-change",
|
|
51
60
|
};
|
|
52
61
|
|
|
53
62
|
// ── Defaults ────────────────────────────────────────────────────────────────
|
|
@@ -124,6 +133,12 @@ function validate(args) {
|
|
|
124
133
|
if (!args.skill) return ERRORS.MISSING_SKILL();
|
|
125
134
|
if (!args.summary) return ERRORS.MISSING_SUMMARY();
|
|
126
135
|
if (args["new-change"] && !args["change-id"]) return ERRORS.CHANGE_ID_REQUIRED();
|
|
136
|
+
|
|
137
|
+
// Epic combo validation (§9.1.1, ADR-10)
|
|
138
|
+
if (args["new-epic"] && !args["epic-id"]) return ERRORS.EPIC_ID_REQUIRED();
|
|
139
|
+
if (args["close-epic"] && args["new-epic"]) return ERRORS.CLOSE_NEW_EPIC_CONFLICT();
|
|
140
|
+
if (args["epic-id"] && !args["new-change"] && !args["new-epic"]) return ERRORS.EPIC_ID_ORPHAN();
|
|
141
|
+
|
|
127
142
|
return null;
|
|
128
143
|
}
|
|
129
144
|
|
|
@@ -197,6 +212,7 @@ function main() {
|
|
|
197
212
|
plan_path: session.active_change.plan_path || "",
|
|
198
213
|
status: "active",
|
|
199
214
|
updated_at: now,
|
|
215
|
+
epic_id: session.active_change.epic_id || "",
|
|
200
216
|
};
|
|
201
217
|
if (existingIdx >= 0) {
|
|
202
218
|
session.changes[existingIdx] = snapshotEntry;
|
|
@@ -215,6 +231,7 @@ function main() {
|
|
|
215
231
|
session.active_change.title = args["new-change"];
|
|
216
232
|
session.active_change.created_at = now;
|
|
217
233
|
session.active_change.plan_path = "";
|
|
234
|
+
session.active_change.epic_id = args["epic-id"] || "";
|
|
218
235
|
}
|
|
219
236
|
|
|
220
237
|
// --set-initialized
|
|
@@ -252,6 +269,7 @@ function main() {
|
|
|
252
269
|
plan_path: ac.plan_path || "",
|
|
253
270
|
status: "active",
|
|
254
271
|
updated_at: now,
|
|
272
|
+
epic_id: ac.epic_id || "",
|
|
255
273
|
};
|
|
256
274
|
if (existingIdx >= 0) {
|
|
257
275
|
session.changes[existingIdx] = entry;
|
|
@@ -281,6 +299,7 @@ function main() {
|
|
|
281
299
|
plan_path: ac.plan_path || "",
|
|
282
300
|
status: "done",
|
|
283
301
|
updated_at: now,
|
|
302
|
+
epic_id: ac.epic_id || "",
|
|
284
303
|
};
|
|
285
304
|
if (existingIdx >= 0) {
|
|
286
305
|
session.changes[existingIdx] = entry;
|
|
@@ -300,6 +319,7 @@ function main() {
|
|
|
300
319
|
title: "",
|
|
301
320
|
created_at: "",
|
|
302
321
|
plan_path: "",
|
|
322
|
+
epic_id: "",
|
|
303
323
|
};
|
|
304
324
|
}
|
|
305
325
|
|
|
@@ -329,11 +349,91 @@ function main() {
|
|
|
329
349
|
}
|
|
330
350
|
}
|
|
331
351
|
|
|
352
|
+
// ── Epic flags ──────────────────────────────────────────────────────────
|
|
353
|
+
|
|
354
|
+
// --new-epic: auto-snapshot old active_epic, then set new one
|
|
355
|
+
if (args["new-epic"]) {
|
|
356
|
+
session.active_epic = session.active_epic || {};
|
|
357
|
+
|
|
358
|
+
// Auto-snapshot: if there's an existing active_epic with an id, upsert into epics[]
|
|
359
|
+
if (session.active_epic.id) {
|
|
360
|
+
session.epics = session.epics || [];
|
|
361
|
+
const existingIdx = session.epics.findIndex(
|
|
362
|
+
(e) => e.id === session.active_epic.id
|
|
363
|
+
);
|
|
364
|
+
const snapshotEntry = {
|
|
365
|
+
id: session.active_epic.id,
|
|
366
|
+
title: session.active_epic.title || "",
|
|
367
|
+
epic_path: session.active_epic.epic_path || "",
|
|
368
|
+
status: "active",
|
|
369
|
+
updated_at: now,
|
|
370
|
+
};
|
|
371
|
+
if (existingIdx >= 0) {
|
|
372
|
+
session.epics[existingIdx] = snapshotEntry;
|
|
373
|
+
} else {
|
|
374
|
+
session.epics.push(snapshotEntry);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Set new active_epic
|
|
379
|
+
session.active_epic.id = args["epic-id"];
|
|
380
|
+
session.active_epic.title = args["new-epic"];
|
|
381
|
+
session.active_epic.created_at = now;
|
|
382
|
+
session.active_epic.epic_path = "";
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// --set-epic-path: set active_epic.epic_path
|
|
386
|
+
if (args["set-epic-path"]) {
|
|
387
|
+
session.active_epic = session.active_epic || {};
|
|
388
|
+
if (!session.active_epic.id && !args["new-epic"]) {
|
|
389
|
+
process.stderr.write(ERRORS.NO_ACTIVE_EPIC("--set-epic-path") + "\n");
|
|
390
|
+
process.exit(1);
|
|
391
|
+
}
|
|
392
|
+
session.active_epic.epic_path = args["set-epic-path"];
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// --set-epic-status: update matching epics[] entry status
|
|
396
|
+
if (args["set-epic-status"]) {
|
|
397
|
+
session.active_epic = session.active_epic || {};
|
|
398
|
+
if (!session.active_epic.id && !args["new-epic"]) {
|
|
399
|
+
process.stderr.write(ERRORS.NO_ACTIVE_EPIC("--set-epic-status") + "\n");
|
|
400
|
+
process.exit(1);
|
|
401
|
+
}
|
|
402
|
+
session.epics = session.epics || [];
|
|
403
|
+
const epicIdx = session.epics.findIndex(
|
|
404
|
+
(e) => e.id === session.active_epic.id
|
|
405
|
+
);
|
|
406
|
+
if (epicIdx >= 0) {
|
|
407
|
+
session.epics[epicIdx].status = args["set-epic-status"];
|
|
408
|
+
session.epics[epicIdx].updated_at = now;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// --close-epic: set matching epics[] entry to done, clear active_epic
|
|
413
|
+
if (args["close-epic"]) {
|
|
414
|
+
session.epics = session.epics || [];
|
|
415
|
+
session.active_epic = session.active_epic || {};
|
|
416
|
+
const aeId = session.active_epic.id;
|
|
417
|
+
if (aeId) {
|
|
418
|
+
const epicIdx = session.epics.findIndex((e) => e.id === aeId);
|
|
419
|
+
if (epicIdx >= 0) {
|
|
420
|
+
session.epics[epicIdx].status = "done";
|
|
421
|
+
session.epics[epicIdx].updated_at = now;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
session.active_epic = {
|
|
425
|
+
id: "",
|
|
426
|
+
title: "",
|
|
427
|
+
created_at: "",
|
|
428
|
+
epic_path: "",
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
332
432
|
// ── Write back atomically ─────────────────────────────────────────────
|
|
333
433
|
const tmpPath = sessionPath + ".tmp";
|
|
334
434
|
|
|
335
435
|
try {
|
|
336
|
-
writeFileSync(tmpPath, stringifyYaml(session), "utf-8");
|
|
436
|
+
writeFileSync(tmpPath, stringifyYaml(session, { lineWidth: 200 }), "utf-8");
|
|
337
437
|
renameSync(tmpPath, sessionPath);
|
|
338
438
|
} catch (e) {
|
|
339
439
|
try {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
## Activation Protocol
|
|
2
2
|
|
|
3
|
-
### Step 1: Load Context
|
|
4
|
-
Load
|
|
5
|
-
- `.ai-agents/workspace/session.yaml` -- Current workflow state
|
|
3
|
+
### Step 1: Load Context
|
|
4
|
+
Load these files as foundational context:
|
|
6
5
|
- `.ai-agents/workspace/project-context.yaml` -- Project index (structural info)
|
|
7
6
|
- `.ai-agents/registry.yaml` -- Available skills registry and knowledge declarations
|
|
8
7
|
{{?extended_context}}
|
|
@@ -13,18 +12,48 @@ Extended context for this skill:
|
|
|
13
12
|
- {{.}}
|
|
14
13
|
{{/extended_context}}
|
|
15
14
|
|
|
16
|
-
### Step 2:
|
|
15
|
+
### Step 2: Resolve Project Scope (PS)
|
|
17
16
|
|
|
18
|
-
Read
|
|
19
|
-
- `knowledge.shared` (loaded by all skills)
|
|
20
|
-
- `skills.<current-skill>.knowledge` (this skill's specific knowledge, if present)
|
|
17
|
+
Read `project-context.yaml > projects[]`.
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
- If the entry lists `files: [...]`, load those files.
|
|
24
|
-
- If the entry lists `files_from_manifest: true`, read `{source}/manifest.yaml` and load every `files[]` entry where `auto_load: true`.
|
|
19
|
+
**Single project** (`projects.length == 1`): Set PS = [sole project name]. Skip remaining PS steps.
|
|
25
20
|
|
|
26
|
-
|
|
21
|
+
**Multi-project** (`projects.length > 1`):
|
|
22
|
+
**Mode A -- Plan-driven** (active plan exists and skill operates on plan tasks):
|
|
23
|
+
1. **Plan signal**: PS = current task's `project` array from plan's `current_tasks`. Drop stale project names (not in `projects[]`), fall through.
|
|
24
|
+
2. **Path match**: Match current working paths against `projects[].path` and `source_paths`.
|
|
25
|
+
3. **Prompt**: If still unresolved, list candidates and ask user. Never silently load all projects.
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
**Mode B -- Non-plan** (no active plan or ad-hoc changes):
|
|
28
|
+
Defer PS to execution: identify change target, match against `projects[].path` and `source_paths`, load project-specific knowledge on demand (Step 3).
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
### Step 3: Load Knowledge
|
|
31
|
+
|
|
32
|
+
Registry uses project-keyed maps; `_all` is a reserved key (all projects). Applies to both top-level `knowledge` and `skills.<name>.knowledge`.
|
|
33
|
+
|
|
34
|
+
**Knowledge Loading Protocol**:
|
|
35
|
+
For each knowledge entry in the registry, follow these steps:
|
|
36
|
+
1. **Read the `source` field** from the registry entry (e.g., `knowledge/project/_generated/`).
|
|
37
|
+
2. **Construct the base directory**: join `.ai-agents/` with the `source` value → `.ai-agents/{source_value}/`.
|
|
38
|
+
3. **Load files**:
|
|
39
|
+
- `files: [a.md, b.md]` → load `.ai-agents/{source_value}/a.md`, `.ai-agents/{source_value}/b.md`.
|
|
40
|
+
- `files_from_manifest: true` → read `.ai-agents/{source_value}/manifest.yaml`, load entries with `auto_load: true`.
|
|
41
|
+
4. **Skip non-existent paths** silently (do not error or warn).
|
|
42
|
+
|
|
43
|
+
**Worked example**:
|
|
44
|
+
Given this registry entry:
|
|
45
|
+
```yaml
|
|
46
|
+
- id: project-context
|
|
47
|
+
source: knowledge/project/_generated/
|
|
48
|
+
files:
|
|
49
|
+
- project-context.md
|
|
50
|
+
```
|
|
51
|
+
Resolution: `.ai-agents/` + `knowledge/project/_generated/` + `project-context.md` = `.ai-agents/knowledge/project/_generated/project-context.md`
|
|
52
|
+
|
|
53
|
+
**Anti-pattern -- DO NOT**:
|
|
54
|
+
- Guess or hardcode base directories (e.g., `.ai-agents/workspace/`).
|
|
55
|
+
- Assume a default path structure. The `source` field value is the authoritative path component.
|
|
56
|
+
|
|
57
|
+
**At activation** (both modes): load `knowledge._all` + `skills.<current-skill>.knowledge._all`.
|
|
58
|
+
**Mode A** (additionally): for each P in PS, load `knowledge[P]` + `skills.<current-skill>.knowledge[P]`.
|
|
59
|
+
**Mode B** (during execution): on demand, load `knowledge[P]` + `skills.<current-skill>.knowledge[P]` for identified project(s).
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
## Suggested Next Steps
|
|
2
2
|
|
|
3
3
|
Recommend 2-3 relevant next skills based on the skill just completed (`{{current_skill}}`) and the current project state.
|
|
4
|
+
**Candidate set constraint (mandatory)**: Only recommend skills that are declared under `skills` in `.ai-agents/registry.yaml`.
|
|
4
5
|
{{#conditional_suggestions}}
|
|
5
6
|
|
|
6
7
|
### Conditional Recommendations
|
|
@@ -21,11 +22,11 @@ Match the current state to one of the conditions below. If none match, use `defa
|
|
|
21
22
|
|
|
22
23
|
### Resolution order
|
|
23
24
|
|
|
24
|
-
Infer 2-3 suggestions from
|
|
25
|
+
Infer 2-3 suggestions, choosing **only** from the skills declared under `skills` in `registry.yaml`:
|
|
25
26
|
- `history` in `session.yaml`
|
|
26
27
|
- `category` and `description` of each skill in `registry.yaml`
|
|
27
28
|
- The current `active_change` state (if in progress)
|
|
28
|
-
- The
|
|
29
|
+
- The standard workflow order (analyze → design → implement → review → test)
|
|
29
30
|
{{/conditional_suggestions}}
|
|
30
31
|
|
|
31
32
|
### Format
|
|
@@ -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.
|
|
@@ -9,7 +9,7 @@ This skill is read-only and does NOT modify `.ai-agents/workspace/session.yaml`.
|
|
|
9
9
|
After completing the skill's main task, run the session update script **exactly once** with the following arguments:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
node .ai-agents/scripts/session-update.cjs --skill <skill_command_name> --summary "<concise one-line summary>"{{#update_active_change}} --new-change "<active_change.title>" --change-id <active_change.id>{{/update_active_change}}{{#set_plan_path}} --set-plan-path ".ai-agents/workspace/artifacts/{active_change.id}/plan.yaml"{{/set_plan_path}}{{#update_change}} --update-change{{/update_change}}{{#close_change}} --close-change{{/close_change}}{{#set_change_status}} --set-change-status <status>{{/set_change_status}}{{#no_change}} --no-change{{/no_change}}{{#set_synced}} --set-synced{{/set_synced}}{{#truncate_history}} --truncate-history <count>{{/truncate_history}}{{#update_initialized_at}} --set-initialized{{/update_initialized_at}}
|
|
12
|
+
node .ai-agents/scripts/session-update.cjs --skill <skill_command_name> --summary "<concise one-line summary>"{{#update_active_change}} --new-change "<active_change.title>" --change-id <active_change.id>{{#link_subchange_to_epic}} --epic-id <active_epic.id>{{/link_subchange_to_epic}}{{/update_active_change}}{{#set_plan_path}} --set-plan-path ".ai-agents/workspace/artifacts/{active_change.id}/plan.yaml"{{/set_plan_path}}{{#update_change}} --update-change{{/update_change}}{{#close_change}} --close-change{{/close_change}}{{#set_change_status}} --set-change-status <status>{{/set_change_status}}{{#new_epic}} --new-epic "<epic_title>" --epic-id <epic_id>{{/new_epic}}{{#set_epic_path}} --set-epic-path <epic_path>{{/set_epic_path}}{{#set_epic_status}} --set-epic-status <status>{{/set_epic_status}}{{#close_epic}} --close-epic{{/close_epic}}{{#no_change}} --no-change{{/no_change}}{{#set_synced}} --set-synced{{/set_synced}}{{#truncate_history}} --truncate-history <count>{{/truncate_history}}{{#update_initialized_at}} --set-initialized{{/update_initialized_at}}
|
|
13
13
|
|
|
14
14
|
```
|
|
15
15
|
|
|
@@ -49,6 +49,22 @@ If the script exits with code 0, the state update was applied successfully; ther
|
|
|
49
49
|
{{#update_initialized_at}}
|
|
50
50
|
| `--set-initialized` | Flag only, no value. Set when this skill initializes the project for the first time. | — |
|
|
51
51
|
{{/update_initialized_at}}
|
|
52
|
+
{{#new_epic}}
|
|
53
|
+
| `--new-epic` | The title of the new epic being created (same value written to `active_epic.title`) | `"ecommerce platform"` |
|
|
54
|
+
| `--epic-id` | The unique identifier of the new epic. Required when using `--new-epic`. Format: `epic-{YYYYMMDD}-{slug}`. | `"epic-20260608-ecommerce-platform"` |
|
|
55
|
+
{{/new_epic}}
|
|
56
|
+
{{#set_epic_path}}
|
|
57
|
+
| `--set-epic-path` | The path to the written `epic.yaml` file. Sets `active_epic.epic_path`. | `".ai-agents/workspace/artifacts/epic-20260608-ecommerce-platform/epic.yaml"` |
|
|
58
|
+
{{/set_epic_path}}
|
|
59
|
+
{{#set_epic_status}}
|
|
60
|
+
| `--set-epic-status` | The status to set on the `epics[]` entry matching `active_epic.id`. Values: `in_progress`, `done`, `abandoned`. | `done` |
|
|
61
|
+
{{/set_epic_status}}
|
|
62
|
+
{{#close_epic}}
|
|
63
|
+
| `--close-epic` | Flag only, no value. Snapshots `active_epic` into `epics[]` with `status: done`, then clears all `active_epic` fields. | — |
|
|
64
|
+
{{/close_epic}}
|
|
65
|
+
{{#link_subchange_to_epic}}
|
|
66
|
+
| `--epic-id` (with `--new-change`) | The parent epic id that this new sub-change belongs to. Only valid when used together with `--new-change`. | `"epic-20260608-ecommerce-platform"` |
|
|
67
|
+
{{/link_subchange_to_epic}}
|
|
52
68
|
|
|
53
69
|
{{#update_active_change}}
|
|
54
70
|
### Parameter semantics
|
|
@@ -56,6 +72,9 @@ If the script exits with code 0, the state update was applied successfully; ther
|
|
|
56
72
|
| Argument | When to use | Effect on `session.yaml` |
|
|
57
73
|
|----------|-------------|--------------------------|
|
|
58
74
|
| `--new-change` + `--change-id` | Skill creates or identifies a new change | Sets `active_change.id`, `.title`, `.created_at`. Auto-snapshots old `active_change` into `changes[]` if non-empty. Requires both arguments together. |
|
|
75
|
+
{{#link_subchange_to_epic}}
|
|
76
|
+
| `--epic-id` (with `--new-change`) | Skill creates a new sub-change inside an existing epic (epic-child mode) | Writes `active_change.epic_id` so the new sub-change is linked to the parent epic. Only valid when used together with `--new-change`. |
|
|
77
|
+
{{/link_subchange_to_epic}}
|
|
59
78
|
{{#set_plan_path}}
|
|
60
79
|
| `--set-plan-path` | Skill creates a new `plan.yaml` for the active change | Sets `active_change.plan_path`. Must be used together with `--update-change`. |
|
|
61
80
|
{{/set_plan_path}}
|
|
@@ -68,6 +87,15 @@ If the script exits with code 0, the state update was applied successfully; ther
|
|
|
68
87
|
{{#set_change_status}}
|
|
69
88
|
| `--set-change-status` | Explicitly mark a change as `done` or `abandoned` | Sets `status` on the `changes[]` entry whose `id` matches `active_change.id`. |
|
|
70
89
|
{{/set_change_status}}
|
|
90
|
+
{{#set_epic_path}}
|
|
91
|
+
| `--set-epic-path` | Skill writes or moves the `epic.yaml` file (e.g., after `/mvt-decompose` writes the artifacts) | Sets `active_epic.epic_path`. |
|
|
92
|
+
{{/set_epic_path}}
|
|
93
|
+
{{#set_epic_status}}
|
|
94
|
+
| `--set-epic-status` | Skill marks the active epic as `done` or `abandoned` (rarely used directly — `epic-update.cjs` usually drives this) | Sets `status` on the `epics[]` entry whose `id` matches `active_epic.id`. |
|
|
95
|
+
{{/set_epic_status}}
|
|
96
|
+
{{#close_epic}}
|
|
97
|
+
| `--close-epic` | Skill closes the active epic (e.g., after archiving a completed epic) | Snapshots `active_epic` into `epics[]` with `status: done`, then clears all `active_epic` fields. |
|
|
98
|
+
{{/close_epic}}
|
|
71
99
|
{{#no_change}}
|
|
72
100
|
| `--no-change` | Skill should not be associated with any change | Forces `history[].change_id` to empty string, skipping the `active_change.id` fallback. |
|
|
73
101
|
{{/no_change}}
|
|
@@ -86,6 +114,9 @@ If the script exits with code 0, the state update was applied successfully; ther
|
|
|
86
114
|
|
|
87
115
|
| Argument | When to use | Effect on `session.yaml` |
|
|
88
116
|
|----------|-------------|--------------------------|
|
|
117
|
+
{{#new_epic}}
|
|
118
|
+
| `--new-epic` + `--epic-id` | Skill creates a new epic (e.g., `/mvt-decompose`) | Sets `active_epic.{id,title,created_at}`. Auto-snapshots old `active_epic` into `epics[]` if non-empty. Requires both arguments together. |
|
|
119
|
+
{{/new_epic}}
|
|
89
120
|
{{#update_change}}
|
|
90
121
|
| `--update-change` | Skill modifies a plan (i.e., after `plan.yaml` is updated) | Upserts current `active_change` into `changes[]` (with `status: active`), sets `updated_at`, sorts ascending, truncates to configured limit. |
|
|
91
122
|
{{/update_change}}
|
|
@@ -95,6 +126,15 @@ If the script exits with code 0, the state update was applied successfully; ther
|
|
|
95
126
|
{{#set_change_status}}
|
|
96
127
|
| `--set-change-status` | Explicitly mark a change as `done` or `abandoned` | Sets `status` on the `changes[]` entry whose `id` matches `active_change.id`. |
|
|
97
128
|
{{/set_change_status}}
|
|
129
|
+
{{#set_epic_path}}
|
|
130
|
+
| `--set-epic-path` | Skill writes or moves the `epic.yaml` file (e.g., after `/mvt-decompose` writes the artifacts) | Sets `active_epic.epic_path`. |
|
|
131
|
+
{{/set_epic_path}}
|
|
132
|
+
{{#set_epic_status}}
|
|
133
|
+
| `--set-epic-status` | Skill marks the active epic as `done` or `abandoned` (rarely used directly — `epic-update.cjs` usually drives this) | Sets `status` on the `epics[]` entry whose `id` matches `active_epic.id`. |
|
|
134
|
+
{{/set_epic_status}}
|
|
135
|
+
{{#close_epic}}
|
|
136
|
+
| `--close-epic` | Skill closes the active epic (e.g., after archiving a completed epic) | Snapshots `active_epic` into `epics[]` with `status: done`, then clears all `active_epic` fields. |
|
|
137
|
+
{{/close_epic}}
|
|
98
138
|
{{#no_change}}
|
|
99
139
|
| `--no-change` | Skill should not be associated with any change | Forces `history[].change_id` to empty string, skipping the `active_change.id` fallback. |
|
|
100
140
|
{{/no_change}}
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
## Epic-Child Mode (Pre-check)
|
|
2
|
+
|
|
3
|
+
**When**: `active_epic.id` is non-empty AND `active_change.id` is empty.
|
|
4
|
+
|
|
5
|
+
In this state the user is starting a new sub-change within an existing epic. Read `epic.yaml` via `active_epic.epic_path` and determine the scenario:
|
|
6
|
+
|
|
7
|
+
| Scenario | User message | Handling |
|
|
8
|
+
|----------|-------------|----------|
|
|
9
|
+
| A | Empty | Auto-use `current_change` child's scope from `epic.yaml` as the requirement input. Proceed to Step 3. |
|
|
10
|
+
| B | Supplements current child | Merge user message with `current_change` child's scope. Proceed to Step 3. |
|
|
11
|
+
| C | Points to different child | Locate target in `children[]`. If `depends_on` has unfinished prerequisites → warn and ask to confirm forced reorder (y/n). If deps satisfied → confirm switch (y/n). On confirmed reorder: call `epic-update.cjs --epic <epic_path> --switch-active <target_id>`. If target not in `children[]` → offer to treat as independent change (exit epic-child mode) or `--add-child`. |
|
|
12
|
+
|
|
1
13
|
## Execution Flow
|
|
2
14
|
|
|
3
15
|
### Step 1: Load Requirements
|
|
@@ -10,7 +22,33 @@
|
|
|
10
22
|
- Extract business rules and constraints
|
|
11
23
|
- Note assumptions made
|
|
12
24
|
|
|
13
|
-
### Step 3: Assess
|
|
25
|
+
### Step 3: Assess Scale (Epic Detection)
|
|
26
|
+
- **What**: evaluate whether the input is an epic-scale requirement that should be decomposed into multiple sub-changes via `/mvt-decompose`.
|
|
27
|
+
- **Signals**:
|
|
28
|
+
|
|
29
|
+
| Signal type | Signal | Example |
|
|
30
|
+
|-------------|--------|---------|
|
|
31
|
+
| Strong | Whole system / platform scope | "Build an e-commerce system" |
|
|
32
|
+
| Strong | Input is a multi-feature design manual | "Implement based on this design manual" |
|
|
33
|
+
| Strong | Multiple independent deliverable capability domains | Auth + Catalog + Cart + Payment |
|
|
34
|
+
| Weak (corroboration only) | Multiple actors with multiple independent main flows | -- |
|
|
35
|
+
| Weak (corroboration only) | No single cohesive acceptance criterion | -- |
|
|
36
|
+
|
|
37
|
+
- **Trigger**: any strong signal, OR (strong + 2+ weak). Weak signals alone never trigger.
|
|
38
|
+
|
|
39
|
+
- **Branches**:
|
|
40
|
+
|
|
41
|
+
| Condition | Action |
|
|
42
|
+
|-----------|--------|
|
|
43
|
+
| Epic detection hits | Ask: "This looks like an epic-level requirement (multiple independent capability domains). Use `/mvt-decompose` to decompose it first? (y / n / show-signals)" |
|
|
44
|
+
| `y` | Do NOT write `analysis.md`. Guide to `/mvt-decompose`. |
|
|
45
|
+
| `n` | Continue standard analysis (Steps 4-7). Cheap reversal path. |
|
|
46
|
+
| `show-signals` | Display matched signals, re-prompt. |
|
|
47
|
+
| Epic misses | Fall through to Step 4 (Quick Path Detection). |
|
|
48
|
+
|
|
49
|
+
- **Epic-child mode note**: When operating in epic-child mode (scenarios A or B from the pre-check), Step 3 should treat the selected child scope as the intended change boundary. Do not re-route to `/mvt-decompose` unless the user explicitly expands the request beyond that child or the scope is clearly still epic-scale (e.g., the child scope itself contains multiple independent capability domains that were not part of the original decomposition rationale).
|
|
50
|
+
|
|
51
|
+
### Step 4: Assess Complexity (Quick Path Detection)
|
|
14
52
|
- **What**: evaluate whether this requirement qualifies as a simple change suitable for the quick development path via `/mvt-quick-dev`.
|
|
15
53
|
- **How**: check each criterion in the table below. ALL criteria must pass for the quick path to be offered.
|
|
16
54
|
|
|
@@ -40,30 +78,30 @@
|
|
|
40
78
|
- Scope: ✗ touches auth middleware, user model, login UI, OAuth callback handler, config (5+ files)
|
|
41
79
|
- No new concepts: ✗ introduces external IdP and OAuth callback contract
|
|
42
80
|
- No integration concerns: ✗ new external dependency (Google IdP)
|
|
43
|
-
→ Proceed with standard analysis flow (Steps
|
|
81
|
+
→ Proceed with standard analysis flow (Steps 5-7).
|
|
44
82
|
|
|
45
83
|
- **Branches**:
|
|
46
84
|
|
|
47
85
|
| Condition | Action |
|
|
48
86
|
|-----------|--------|
|
|
49
87
|
| ALL criteria pass | Ask user: "This appears to be a simple change (1-3 files, no architectural impact). Use /mvt-quick-dev for faster execution? (y / n / show-criteria)" |
|
|
50
|
-
| ANY criterion fails | Proceed with standard analysis flow (Steps
|
|
88
|
+
| ANY criterion fails | Proceed with standard analysis flow (Steps 5-7) |
|
|
51
89
|
| Ambiguous (2-3 criteria unclear) | Proceed with standard analysis; do NOT offer quick path |
|
|
52
90
|
|
|
53
91
|
- **On user choice**:
|
|
54
92
|
- "y" -- Do NOT write an analysis artifact. Summarize the requirement understanding in conversation and recommend `/mvt-quick-dev` directly. Set `active_change` if one doesn't exist, so `/mvt-quick-dev` can reference the current work context.
|
|
55
|
-
- "n" -- Continue with full analysis flow (Steps
|
|
93
|
+
- "n" -- Continue with full analysis flow (Steps 5-7).
|
|
56
94
|
- "show-criteria" -- Display the assessment results (pass/fail per criterion), then re-prompt with y/n.
|
|
57
95
|
|
|
58
|
-
### Step
|
|
96
|
+
### Step 5: Detect Ambiguities
|
|
59
97
|
- Check for unclear requirements
|
|
60
98
|
- Check for missing information
|
|
61
99
|
- Check for conflicting requirements
|
|
62
100
|
|
|
63
|
-
### Step
|
|
101
|
+
### Step 6: Generate Clarification Questions
|
|
64
102
|
- If ambiguities found -> List each with specific question, prioritized by impact
|
|
65
103
|
- If no ambiguities -> Skip this step
|
|
66
104
|
|
|
67
|
-
### Step
|
|
68
|
-
1. Generate change-id: `{YYYYMMDD}-{slug}` format (e.g., `20260425-user-authentication`)
|
|
105
|
+
### Step 7: Update Workspace
|
|
106
|
+
1. Generate change-id: `{YYYYMMDD}-{slug}` format (e.g., `20260425-user-authentication`). Slug constraints: lowercase ASCII, kebab-case, `[a-z0-9-]+`, 1-4 words.
|
|
69
107
|
2. Write artifact: `.ai-agents/workspace/artifacts/{change-id}/analysis.md`
|
|
@@ -44,6 +44,9 @@ sections:
|
|
|
44
44
|
- type: shared
|
|
45
45
|
source: sections/output-language-constraint.md
|
|
46
46
|
|
|
47
|
+
- type: shared
|
|
48
|
+
source: sections/output-format-constraint.md
|
|
49
|
+
|
|
47
50
|
- type: shared
|
|
48
51
|
source: sections/activation-preflight.md
|
|
49
52
|
params:
|
|
@@ -73,6 +76,7 @@ sections:
|
|
|
73
76
|
params:
|
|
74
77
|
current_skill: mvt-analyze
|
|
75
78
|
update_active_change: true
|
|
79
|
+
link_subchange_to_epic: true
|
|
76
80
|
|
|
77
81
|
- type: shared
|
|
78
82
|
source: sections/footer-next-steps.md
|
|
@@ -80,7 +84,10 @@ sections:
|
|
|
80
84
|
current_skill: mvt-analyze
|
|
81
85
|
conditional_suggestions:
|
|
82
86
|
conditions:
|
|
83
|
-
- condition: "
|
|
87
|
+
- condition: "epic-scale detected in Step 3 (Epic Detection) and user chose y"
|
|
88
|
+
primary: "mvt-decompose"
|
|
89
|
+
primary_desc: "Decompose this epic-scale requirement into sub-changes"
|
|
90
|
+
- condition: "user chose quick path in Step 4 (Quick Path Detection)"
|
|
84
91
|
primary: "mvt-quick-dev"
|
|
85
92
|
primary_desc: "Implement this simple change quickly"
|
|
86
93
|
- condition: "default"
|
|
@@ -2,17 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
### Step 1: Determine Analysis Target
|
|
4
4
|
|
|
5
|
-
Identify which project(s) to analyze:
|
|
5
|
+
Identify which project(s) to analyze using interactive routing:
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
For each
|
|
14
|
-
1. Read its `path` from `project-context.yaml`
|
|
15
|
-
2. Use `path` as the source directory for analysis
|
|
7
|
+
1. Read `project-context.yaml > projects[]` to get the list of registered projects.
|
|
8
|
+
2. **Single project** (only one entry): automatically select it — no prompt needed.
|
|
9
|
+
3. **Multiple projects**: present an interactive selection menu:
|
|
10
|
+
- List each project by `name` with its `path`
|
|
11
|
+
- Include an option to **analyze all projects**
|
|
12
|
+
- Wait for user selection before proceeding
|
|
13
|
+
4. For each selected project, read its `path` and use it as the source directory for analysis.
|
|
16
14
|
|
|
17
15
|
### Step 2: Load Template
|
|
18
16
|
|
|
@@ -72,11 +70,14 @@ Identify public interfaces:
|
|
|
72
70
|
- Fill each template section with analysis results
|
|
73
71
|
- If a section has no relevant content, include the heading with "(No relevant content detected)"
|
|
74
72
|
|
|
75
|
-
2. Write the output to `.ai-agents/knowledge/project/_generated/project-context.md
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
2. Write the output to `.ai-agents/knowledge/project/_generated/project-context.md` (always the flat path, regardless of project count).
|
|
74
|
+
- **Single-project**: write the full document.
|
|
75
|
+
- **Multi-project**: use `# Project: {name}` as the top-level heading to separate each project's content sections within the single file.
|
|
76
|
+
|
|
77
|
+
3. If the output file already exists:
|
|
78
|
+
- **Single-project**: replace the whole file.
|
|
79
|
+
- **Multi-project**: replace only the `# Project: {name}` section(s) for re-analyzed projects; preserve sections for projects NOT in this analysis run.
|
|
80
|
+
|
|
81
|
+
4. **Populate `source_paths`** in `project-context.yaml`: after analyzing each project, update the matching project entry's `source_paths` array with the detected source directories (e.g., `["src/", "test/"]`). This overwrites the empty default set by `/mvt-init`.
|
|
81
82
|
|
|
82
|
-
|
|
83
|
+
5. Do NOT update other fields in `project-context.yaml` -- only `source_paths` is touched by this skill.
|
|
@@ -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:
|
|
@@ -34,13 +37,10 @@ sections:
|
|
|
34
37
|
|
|
35
38
|
- type: inline
|
|
36
39
|
content: |
|
|
37
|
-
##
|
|
40
|
+
## Invocation
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
| `/mvt-analyze-code` | Analyze the first (or only) project |
|
|
42
|
-
| `/mvt-analyze-code --all` | Analyze all projects in the workspace |
|
|
43
|
-
| `/mvt-analyze-code {name}` | Analyze a specific project by name |
|
|
42
|
+
`/mvt-analyze-code` — single entry point, no arguments or flags.
|
|
43
|
+
When multiple projects exist, the skill interactively prompts the user to select the target(s).
|
|
44
44
|
|
|
45
45
|
- type: shared
|
|
46
46
|
source: sections/activation-load-context.md
|
|
@@ -56,6 +56,9 @@ sections:
|
|
|
56
56
|
- type: shared
|
|
57
57
|
source: sections/output-language-constraint.md
|
|
58
58
|
|
|
59
|
+
- type: shared
|
|
60
|
+
source: sections/output-format-constraint.md
|
|
61
|
+
|
|
59
62
|
- type: shared
|
|
60
63
|
source: sections/activation-preflight.md
|
|
61
64
|
params:
|