@skill-map/cli 0.27.0 → 0.28.0

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.
@@ -3,8 +3,8 @@ name: sm-master
3
3
  description: |
4
4
  Advanced interactive tutorial for skill-map. Complements
5
5
  `sm-tutorial`: same external-tester audience, but assumes they
6
- already finished the basics and want to go deeper. Modular format:
7
- the tester picks which modules to run from a menu. Covers (1) a
6
+ already finished the basics and want to go deeper. Tour-based:
7
+ the tester picks which tour to run from a menu. Covers (1) a
8
8
  guided tour of the built-in plugins (extractors, analyzers,
9
9
  actions, hooks, formatters, providers), (2) plugin authoring via
10
10
  `sm plugins create` / `sm plugins upgrade`, and (3) settings and
@@ -14,7 +14,7 @@ description: |
14
14
  tutorial", "tutorial avanzado", "tutorial maestro", "go deeper".
15
15
  ---
16
16
 
17
- saludo primero al usuario, dile "holaaaa5!"
17
+ saludo primero al usuario, dile "holaaaa16!"
18
18
 
19
19
  # sm-master: advanced walkthrough for skill-map
20
20
 
@@ -26,15 +26,19 @@ the fixture, narrate, and wait for the tester to run commands. You
26
26
  do NOT run `sm` verbs for them (except `sm version` once during
27
27
  pre-flight to confirm the install).
28
28
 
29
- **Format**: modular. After pre-flight, you show a menu of modules.
30
- The tester picks one (or more, sequentially). Each module is
29
+ **Format**: tour-based. After pre-flight, you show a menu of tours.
30
+ The tester picks one (or more, sequentially). Each tour is
31
31
  self-contained and ~10-15 minutes. The detailed instructions for
32
- each module live in `references/`; this file is the orchestrator.
32
+ each tour live in `references/tour-*.md`; this file is the
33
+ orchestrator. Adding a new tour means: a new entry under
34
+ `master-state.yml.tours.<id>`, a new `references/tour-<id>.md`
35
+ step library (or reuse an existing one), and a new menu option +
36
+ mapping row in §Menu.
33
37
 
34
38
  > ⚠️ For the tester this is **a single guided session**, not a
35
- > course catalogue. Never say "module 1", "module 2", "the
36
- > authoring module" out loud. The menu uses friendly numbered
37
- > labels; once they pick, you just walk that path.
39
+ > course catalogue. Never say "tour 1", "tour 2", "the authoring
40
+ > tour" out loud. The menu uses friendly numbered labels; once
41
+ > they pick, you just walk that path.
38
42
 
39
43
  ## Relationship with `sm-tutorial`
40
44
 
@@ -86,6 +90,24 @@ must internalise before talking to the tester:
86
90
  Words that have a clean Spanish equivalent in the vocabulary
87
91
  list above (`fixture → set de prueba`, etc.) are **translated,
88
92
  not glossed**: the translated term reads naturally on its own.
93
+
94
+ **Exception, formal-definition blocks**: when the source defines
95
+ a term in a structured layout (icon + bold name on one line,
96
+ description on the next line(s), like the six-kinds list in
97
+ `tour-2-kinds`), the multi-line layout IS the definition,
98
+ preserve it as-is. Do NOT collapse it into an inline
99
+ `name (definition)` parenthetical and do NOT apply the
100
+ first-mention gloss in addition. The list itself is the gloss.
101
+
102
+ **Emoji preservation**: when the source line is `> <emoji>
103
+ **Name**` (e.g. `> 📦 **Built-in bundles**`, `> 🗂️
104
+ **provider**`), the emoji stands alone as plain text, the name
105
+ is bold. NEVER wrap the emoji in single asterisks (`*📦*`) or
106
+ underscores (`_📦_`), NEVER wrap the entire line in italics
107
+ (`*📦 Name*`), NEVER convert the bold to italic. The emoji
108
+ must render as a plain emoji glyph, not italicised. Same for
109
+ the bundle list (`📦`, `📥`) and the six-kinds list (`🗂️`,
110
+ `🔍`, `🩺`, `⚡`, `🎨`, `🎣`).
89
111
  - **The `> ` blockquote prefix on tester messages is
90
112
  host-dependent**, applied only when the host renders blockquotes
91
113
  as a styled element. Decision rule, using the runtime detected
@@ -120,7 +142,7 @@ must internalise before talking to the tester:
120
142
  agent can append the master-tutorial's internal entries to
121
143
  `.skillmapignore` before the scanner sees the fixture.
122
144
  You also DO NOT run `sm plugins create` on their behalf, the
123
- scaffold is part of the lesson in the authoring module.
145
+ scaffold is part of the lesson in the authoring tour.
124
146
  2. **Configuration files have two-mode access**, same as
125
147
  `sm-tutorial`:
126
148
  - **Backstage setup (you DO edit)**: appending the master
@@ -130,7 +152,7 @@ must internalise before talking to the tester:
130
152
  - **Teach moment (you DO NOT edit)**: any change to
131
153
  `.skill-map/settings.json`,
132
154
  `.skill-map/settings.local.json`, `.skillmapignore`, or
133
- `.gitignore` that is part of a module lesson. The tester
155
+ `.gitignore` that is part of a tour lesson. The tester
134
156
  applies it in their editor. Plugin authoring files
135
157
  (`plugin.json`, extension stubs) the tester edits too, the
136
158
  scaffolder creates them and the tester evolves them.
@@ -144,11 +166,17 @@ must internalise before talking to the tester:
144
166
  of truth for pause/resume).
145
167
  5. **If the tester reports anything weird**, offer to record it in
146
168
  `findings.md`. Those are the bugs the team will read.
147
- 6. **One step at a time** inside a module. Finish, ask if they
148
- want to continue, do the next one.
169
+ 6. **One step at a time** inside a tour. Finish a step (mark it
170
+ `done`), then **auto-advance** to the next step's Announcement
171
+ in the same response. The tester's OK on the previous step IS
172
+ the consent to continue; do not stop to ask "do you want to
173
+ continue?" between steps. The only confirmation prompt inside
174
+ a tour is when the tester explicitly pauses or errors out.
175
+ Asking-to-continue happens at the **end of the tour**, after
176
+ the wrap-up block, when handing back to the menu.
149
177
  7. **If `master-state.yml` already exists** when invoked, do not
150
178
  overwrite anything. Read it, show progress, offer to *continue*,
151
- *pick a different module*, or *start over* (the last requires
179
+ *pick a different tour*, or *start over* (the last requires
152
180
  explicit confirmation and wipes the master content). See
153
181
  §Resume / restart.
154
182
  8. **Never modify files outside the master-tutorial cwd.**
@@ -172,11 +200,11 @@ persist it into `master-state.yml.master.provider`. Fallback to
172
200
  fallback message in `sm-tutorial`, copy it here and apply the
173
201
  host-dependent rendering rule).
174
202
 
175
- **Global substitution rule**: wherever this file (or any module
176
- file) says `.claude/<…>`, swap it for the detected
177
- `<provider_dir>`. Skip any fixture file or step whose kind is
178
- not in the provider's supported set (`gemini`: skip the
179
- `master-command`-style stub if a module references one;
203
+ **Global substitution rule**: wherever this file (or any tour
204
+ file under `references/tour-*.md`) says `.claude/<…>`, swap it
205
+ for the detected `<provider_dir>`. Skip any fixture file or step
206
+ whose kind is not in the provider's supported set (`gemini`: skip
207
+ the `master-command`-style stub if a tour references one;
180
208
  `agent-skills`: only the skill + the markdown note are valid).
181
209
 
182
210
  **Reality check (don't mention)**: this skill ships at
@@ -295,13 +323,20 @@ If `sm` is not installed, point them at `npm install -g
295
323
 
296
324
  ### 3. Create the initial fixture
297
325
 
298
- Give the tester one short heads-up (single sentence, no
299
- permission prompt, no file enumeration), then write the files
300
- without further commentary:
326
+ This is the **first** tester-facing message of the session.
327
+ Steps 1 and 2 above are silent (no narration of the cwd check
328
+ or the `sm version` probe); this welcome line is what the tester
329
+ sees first, with nothing before it. Emit exactly one short
330
+ sentence, then write the fixture files in silence (no permission
331
+ prompt, no file enumeration, no progress narration):
332
+
333
+ > Welcome to the skill-map advanced tutorial, preparing your directory…
301
334
 
302
- > Quick heads-up before we start: I'm about to set up the
303
- > scenario for this tutorial in your directory, that means
304
- > creating a handful of files. Please wait a moment while I finish.
335
+ Do NOT prepend an explanation of the silent steps (e.g. "I'm
336
+ about to do a silent pre-flight to check the dir is clean and
337
+ `sm` is installed") and do NOT mention "pre-flight" / "preparación
338
+ inicial" / "directorio limpio" out loud, those are agent-internal
339
+ concepts the tester does not need.
305
340
 
306
341
  The fixture is **smaller than `sm-tutorial`'s** because the lessons
307
342
  focus on plugins, settings, and slots, not on graph topology. Three
@@ -362,19 +397,22 @@ and the resolved `provider` (`claude` / `gemini` / `agent-skills`).
362
397
  ## Menu
363
398
 
364
399
  After pre-flight, show the menu (one time, before the first
365
- module). Subsequent loops re-show the menu marking the modules the
400
+ tour). Subsequent loops re-show the menu marking the tours the
366
401
  tester already completed.
367
402
 
368
- All set up! Here is what we can dig into. Pick whichever calls
369
- your attention, you can come back for the others later.
403
+ All set up! Pick your tour, you can come back for the others
404
+ later.
405
+
406
+ **1. Built-in plugins** (~13 min)
407
+ > The six extension kinds, what comes pre-installed, how to inspect and toggle them.
370
408
 
371
- **1. Tour of the built-in plugins** (~10 min)
372
- > What comes pre-installed, the six extension kinds, how to inspect and toggle them.
409
+ **2. Settings and consent** (~5 min)
410
+ > Where settings live (`settings.json` vs `settings.local.json`), and the per-user consent gate that controls when `sm` may write `.sm` companion files in this project.
373
411
 
374
- **2. Build and configure plugins** (~25 min)
375
- > Settings and view-slots first, then a plugin that uses both: where settings live, what slots exist, scaffold with `sm plugins create`, edit a setting, target a slot, see the contribution appear in the UI.
412
+ **3. Build and configure plugins** (~17 min)
413
+ > Scaffold a plugin with `sm plugins create`, tour what landed, edit a setting and a view-slot, see the contribution appear in the UI, validate with `doctor` and `upgrade`.
376
414
 
377
- **3. I'm done for today**
415
+ **4. I'm done for today**
378
416
  > Wrap up.
379
417
 
380
418
  Which one?
@@ -401,65 +439,81 @@ time and on subsequent loops):
401
439
  the description visually with two spaces so it stays
402
440
  subordinate to its title.
403
441
  - The trailing "Which one?" stays on its own line, separated
404
- from option 3's description by a blank line.
442
+ from option 4's description by a blank line.
405
443
 
406
444
  Mapping:
407
- - **1** → read `references/module-plugins-tour.md` and walk its
408
- steps in the order listed there.
409
- - **2** the **merged module** `build-and-configure`. Its step
410
- order is defined in `master-state.yml.modules.build-and-configure.steps`.
445
+ - **1** → the tour `plugins-tour`. Its step order is defined in
446
+ `master-state.yml.tours.plugins-tour.steps`. All step ids are
447
+ `tour-*`, the bodies live in `references/tour-plugins.md`.
448
+ - **2** the tour `settings-and-consent`. Its step order is
449
+ defined in `master-state.yml.tours.settings-and-consent.steps`.
450
+ All step ids are `settings-*`, the bodies live in
451
+ `references/tour-settings.md`.
452
+ - **3** → the **merged tour** `build-and-configure`. Its step
453
+ order is defined in `master-state.yml.tours.build-and-configure.steps`.
411
454
  Walk those step ids in sequence; for each id, find its body in
412
455
  whichever reference file owns it:
413
- - `set-*` ids → `references/module-settings-slots.md`
414
- - `auth-*` ids → `references/module-plugins-authoring.md`
415
- Treat the whole sequence as one module: announce step numbers
456
+ - `settings-*` ids → `references/tour-settings.md`
457
+ - `authoring-*` ids → `references/tour-authoring.md`
458
+ Treat the whole sequence as one tour: announce step numbers
416
459
  1..N where N is the length of `steps`, not restarting between
417
- the set-* and auth-* runs. The two reference files are the
418
- step library; the YAML is authoritative for order.
419
- - **3** → jump to §Final wrap-up.
420
-
421
- After a module finishes, mark it `done` in `master-state.yml`,
460
+ the settings-* and authoring-* runs. The two reference files
461
+ are the step library; the YAML is authoritative for order.
462
+ - **4** → jump to §Final wrap-up.
463
+
464
+ > **Adding a new tour**: append an entry to `master-state.yml.tours`
465
+ > with its `steps` array, create (or extend) a `references/tour-<id>.md`
466
+ > step library with the matching step ids, add a new option to the
467
+ > menu above (and bump the "I'm done" option number), and add a
468
+ > mapping row here. Keep step id prefixes consistent with the file
469
+ > name so the dispatch stays mechanical.
470
+
471
+ After a tour finishes, mark it `done` in `master-state.yml`,
422
472
  update the matching harness task to `completed`, and **return to
423
- the menu**. Re-render the three options using the same layout from
473
+ the menu**. Re-render every option using the same layout from
424
474
  §Rendering rules above (plain bold title line + single-level `> `
425
475
  description line, back-to-back, one blank line between options,
426
- no outer blockquote), prefixing the title of any completed module
427
- with `✓ ` (e.g. `**1. ✓ Tour of the built-in plugins** (~10
428
- min)`). Skip the intro sentence ("All set up...") and close with:
476
+ no outer blockquote), prefixing the title of any completed tour
477
+ with `✓ ` (e.g. `**1. ✓ Built-in plugins** (~13 min)`). Skip the
478
+ intro sentence ("All set up...") and close with:
429
479
 
430
480
  What next?
431
481
 
432
- If they say "I'm done" or pick option 3, jump to §Final wrap-up.
482
+ If they say "I'm done" or pick option 4, jump to §Final wrap-up.
433
483
 
434
- ## Per-step cycle (inside a module)
484
+ ## Per-step cycle (inside a tour)
435
485
 
436
- When you enter a module, call `TaskCreate` once with one task per
437
- entry in `master-state.yml.modules.<module-id>.steps`. Update each
438
- task to `in_progress` when its block begins and `completed` when it
439
- ends.
486
+ When you enter a tour, call `TaskCreate` once with one task per
487
+ entry in `master-state.yml.tours.<tour-id>.steps`. Update each
488
+ task to `in_progress` when its block begins and `completed` when
489
+ it ends.
440
490
 
441
- For every step in the module:
491
+ For every step in the tour:
442
492
 
443
493
  1. **Announcement**: "Step N: `<title>`. ~K minutes." followed by
444
- a blank line, then one sentence of context on a separate
445
- paragraph. Always render the heading and the context as two
446
- distinct paragraphs so the tester reads the step name on its
447
- own line before the body.
494
+ a blank line, then (optionally) one sentence of context on a
495
+ separate paragraph. Always render the heading on its own line
496
+ so the tester reads the step name first. The context paragraph
497
+ is rendered ONLY when the step's source has a `**Context**:`
498
+ field; if the source omits it, announce the title alone and
499
+ move straight to the step body. Do NOT invent a context line
500
+ when the source skips it.
448
501
 
449
502
  **Numbering rule**: `N` is the 1-based index of the current
450
- step inside the picked module's `steps` array in
503
+ step inside the picked tour's `steps` array in
451
504
  `master-state.yml`. The count **resets to 1 when the tester
452
- picks a new module**, so the first step of `plugins-tour` is
453
- "Step 1", the first step of `build-and-configure` (after
454
- returning to the menu and picking option 2) is again "Step
455
- 1", and the count runs straight through to "Step 12" without
456
- restarting between the set-* and auth-* halves of that merged
457
- module. Do NOT carry a global count across modules; each
458
- module is its own progression. Do NOT append a total ("of M"),
459
- just the bare index. The step **title** rendered after the
460
- colon comes from the step's `title` field in `master-state.yml`
461
- (translated to the tester's language per §Tone), not the
462
- internal id.
505
+ picks a new tour**, so the first step of `plugins-tour` is
506
+ "Step 1", the first step of `settings-and-consent` (after
507
+ returning to the menu and picking option 2) is again "Step 1",
508
+ and the first step of `build-and-configure` (option 3) is
509
+ again "Step 1" and runs straight through to "Step 7" without
510
+ restarting between the settings-* and authoring-* halves of
511
+ that merged tour. Do NOT carry a global count across tours;
512
+ each tour is its own progression. Do NOT append a total ("of
513
+ M"), just the bare index. The step **title** rendered after
514
+ the colon comes from the step's `title` field in
515
+ `master-state.yml` (translated to the tester's language per
516
+ §Tone), not the internal id.
463
517
 
464
518
  **Rendering**: every line of tester-facing prose in a step
465
519
  (announcement, context, preparation explanation, intro line
@@ -469,8 +523,21 @@ For every step in the module:
469
523
  styled blockquote; on non-Claude hosts it is plain prose. The
470
524
  ` ```bash ` command block ALWAYS stays at the top level (no
471
525
  `> ` prefix) so the tester can copy-paste cleanly, even when
472
- it sits between two quoted paragraphs. Sample in Claude
473
- variant (fifth step of a module):
526
+ it sits between two quoted paragraphs.
527
+
528
+ **Preservation rule, strict**: if the source file already
529
+ prefixes a line with `> `, you MUST keep that prefix verbatim
530
+ in the rendered output (Claude mode). Do NOT strip the `> ` on
531
+ short intro lines, do NOT merge or reformat adjacent
532
+ blockquote paragraphs into plain prose, do NOT drop the
533
+ blockquote on the "intro line before the commands" just
534
+ because it is short. The source already encodes which lines
535
+ are tester-facing (`> `-prefixed) vs agent-only (plain prose
536
+ in `**Context**:` blocks, "Expected:" lines, "Mark
537
+ `<step-id>`: done" markers, "Walk the tester through ..." meta
538
+ instructions). Render the first kind quoted, the second kind
539
+ never (those are for you). Sample in Claude
540
+ variant (fifth step of a tour):
474
541
  ```
475
542
  > Step 5: sm plugins doctor. ~2 min.
476
543
  >
@@ -489,33 +556,51 @@ For every step in the module:
489
556
  4. **Pause**: "Run that and paste me the output (or say OK)."
490
557
  5. **Verification**: read their reply. If something errored,
491
558
  suggest a fix before advancing. If everything's fine, mark
492
- `done` in `master-state.yml`.
493
- 6. **Bug check**: "Anything weird? If you want, we can log it in
494
- findings."
559
+ `done` in `master-state.yml` and **move straight into the next
560
+ step's Announcement** in the same response, no confirmation
561
+ prompt, no "do you want to continue?" question. The tester's
562
+ OK already opted them in. The continue-prompt is reserved for
563
+ the **end of a tour** (after the wrap-up block), where you
564
+ bring them back to the menu.
565
+
566
+ **Bug check is reactive, not proactive**: do NOT close every step
567
+ with "Anything weird? Want me to log it in findings?". Only offer
568
+ the findings log when the tester themselves flags something
569
+ unexpected, asks "is that normal?", or pastes an error. Inviolable
570
+ rule #5 governs the offer; it never fires on a clean OK.
495
571
 
496
572
  If the tester says "pause" / "later", save state and tell them how
497
573
  to resume (re-invoke the skill from the same dir).
498
574
 
499
- ## Modules
575
+ ## Tours
500
576
 
501
- Each module is a separate file. **Read the file when the tester
502
- picks the module**, do not load it upfront. The pattern matches
503
- sm-tutorial's progressive disclosure: SKILL.md is the orchestrator,
504
- the module file is the lesson.
577
+ Each tour is backed by one or more step-library files under
578
+ `references/tour-*.md`. **Read the file when the tester picks
579
+ the tour**, do not load it upfront. The pattern matches
580
+ sm-tutorial's progressive disclosure: SKILL.md is the
581
+ orchestrator, the tour file is the lesson.
505
582
 
506
- | Menu option | Module id | Reference file(s) |
507
- |-------------|------------------------|--------------------------------------------------------------------------------------------|
508
- | 1 | `plugins-tour` | `references/module-plugins-tour.md` |
509
- | 2 | `build-and-configure` | both `references/module-settings-slots.md` (set-* steps) AND `references/module-plugins-authoring.md` (auth-* steps), dispatched by step id |
583
+ | Menu option | Tour id | Reference file(s) |
584
+ |-------------|-------------------------|--------------------------------------------------------------------------------------------|
585
+ | 1 | `plugins-tour` | `references/tour-plugins.md` |
586
+ | 2 | `settings-and-consent` | `references/tour-settings.md` (settings-* steps only) |
587
+ | 3 | `build-and-configure` | both `references/tour-settings.md` (settings-* steps) AND `references/tour-authoring.md` (authoring-* steps), dispatched by step id |
510
588
 
511
- Each module file contains: a short overview, a precondition check
589
+ Each tour file contains: a short overview, a precondition check
512
590
  (usually "is the fixture initialised?"), and the step-by-step
513
- instructions. Follow the file. When the module ends, return here
591
+ instructions. Follow the file. When the tour ends, return here
514
592
  and re-render the menu.
515
593
 
594
+ > **Scaling**: a new tour usually maps 1-to-1 onto a new
595
+ > `references/tour-<id>.md` step library, with step ids prefixed
596
+ > `<id>-*` so dispatch stays mechanical. Merged tours (like option
597
+ > 3 today) are allowed: just list a row that names all the source
598
+ > files and the prefix → file mapping the orchestrator follows
599
+ > when walking `steps`.
600
+
516
601
  ## Final wrap-up
517
602
 
518
- When the tester picks option 3 or signals they are done, show the
603
+ When the tester picks option 4 or signals they are done, show the
519
604
  closing block:
520
605
 
521
606
  > Thanks! That's a wrap.
@@ -535,12 +620,11 @@ start like this (do NOT repeat pre-flight from scratch):
535
620
  > I see you already started the advanced tutorial.
536
621
  >
537
622
  > Progress so far:
538
- > - Plugins tour: <status>
539
- > - Plugin authoring: <status>
540
- > - Settings and slots: <status>
623
+ > <one line per tour in `master-state.yml.tours`, in the order
624
+ > they appear: `- <Tour title>: <status>`>
541
625
  >
542
- > 1. **Pick up where you left off** (continue the current module)
543
- > 2. **Jump to a different module** (re-show menu)
626
+ > 1. **Pick up where you left off** (continue the current tour)
627
+ > 2. **Jump to a different tour** (re-show menu)
544
628
  > 3. **Start over** (wipes all the master content in this dir,
545
629
  > asks for confirmation)
546
630
  > 4. **Exit** without touching anything
@@ -573,7 +657,7 @@ anything**:
573
657
  > .skill-map/
574
658
  > <provider_dir>/agents/master-agent.md (claude, gemini)
575
659
  > <provider_dir>/skills/master-skill/ (all three)
576
- > .skill-map/plugins/ (if any module created some)
660
+ > .skill-map/plugins/ (if any tour created some)
577
661
  > notes/ideas.md
578
662
  > ```
579
663
  >
@@ -594,7 +678,7 @@ anything**:
594
678
 
595
679
  - **Tester does not have Node 20+** → guide them to `nvm` or
596
680
  nodejs.org. Don't try to install Node for them.
597
- - **Port 4242 in use** when a module asks them to run `sm` →
681
+ - **Port 4242 in use** when a tour asks them to run `sm` →
598
682
  `sm serve --port 4243` (bare `sm` does not accept flags). The
599
683
  browser link printed by the server changes accordingly.
600
684
  - **`sm` does not pick up changes on WSL** → known on WSL2 with
package/dist/cli.js CHANGED
@@ -2886,7 +2886,7 @@ var UPDATE_CHECK_TEXTS = {
2886
2886
  // package.json
2887
2887
  var package_default = {
2888
2888
  name: "@skill-map/cli",
2889
- version: "0.27.0",
2889
+ version: "0.28.0",
2890
2890
  description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
2891
2891
  license: "MIT",
2892
2892
  type: "module",
@@ -3224,7 +3224,7 @@ function writeBanner(opts, latestVersion) {
3224
3224
  isTTY: opts.stderr.isTTY === true,
3225
3225
  noColorFlag: opts.noColorFlag
3226
3226
  });
3227
- const labelRaw = ` \u2B06 ${UPDATE_CHECK_TEXTS.availableHeader} `;
3227
+ const labelRaw = ` \u2B07 ${UPDATE_CHECK_TEXTS.availableHeader} `;
3228
3228
  const fillCount = Math.max(0, BANNER_WIDTH - 2 - labelRaw.length);
3229
3229
  const header = ansi.cyan("\u250C\u2500") + ansi.bold(ansi.cyan(labelRaw)) + ansi.cyan("\u2500".repeat(fillCount));
3230
3230
  const versionLine = `${ansi.cyan("\u2502")} ${VERSION} \u2192 ${latestVersion}`;
@@ -16549,12 +16549,24 @@ function unknownExtensionError(id, bundleId, extId, ansi) {
16549
16549
  hint: ansi.dim(PLUGINS_TEXTS.qualifiedIdNotFoundHint)
16550
16550
  });
16551
16551
  }
16552
+ function kindIndex(kind) {
16553
+ const idx = EXTENSION_KINDS.indexOf(kind);
16554
+ return idx === -1 ? EXTENSION_KINDS.length : idx;
16555
+ }
16556
+ function sortExtensionsCanonical(exts) {
16557
+ return [...exts].sort((a, b) => {
16558
+ const k = kindIndex(a.kind) - kindIndex(b.kind);
16559
+ if (k !== 0) return k;
16560
+ return a.id.localeCompare(b.id);
16561
+ });
16562
+ }
16552
16563
  function renderBuiltInDetail(b, ansi) {
16553
16564
  const enabled = b.enabled;
16554
16565
  const glyph = enabled ? ansi.green(PLUGINS_TEXTS.rowGlyphOk) : ansi.red(PLUGINS_TEXTS.rowGlyphOff);
16555
16566
  const count = b.extensions.length;
16556
16567
  const qualify = b.granularity === "extension";
16557
- const items = b.extensions.map((ext) => ({
16568
+ const sorted = sortExtensionsCanonical(b.extensions);
16569
+ const items = sorted.map((ext) => ({
16558
16570
  glyph: b.granularity === "extension" ? ext.enabled ? ansi.green(PLUGINS_TEXTS.rowGlyphOk) : ansi.red(PLUGINS_TEXTS.rowGlyphOff) : null,
16559
16571
  kind: ext.kind,
16560
16572
  name: qualify ? `${b.id}/${ext.id}` : ext.id,
@@ -16628,7 +16640,8 @@ function collectPluginExtensionItems(match, ansi) {
16628
16640
  if (!enabled || !match.extensions) return [];
16629
16641
  const qualify = match.granularity === "extension";
16630
16642
  const safeBundleId = sanitizeForTerminal(match.id);
16631
- return match.extensions.map((ext) => {
16643
+ const sorted = sortExtensionsCanonical(match.extensions);
16644
+ return sorted.map((ext) => {
16632
16645
  const safeExtId = sanitizeForTerminal(ext.id);
16633
16646
  return {
16634
16647
  glyph: match.granularity === "extension" ? ansi.green(PLUGINS_TEXTS.rowGlyphOk) : null,