@skilly-hand/skilly-hand 0.17.0 → 0.18.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.
package/CHANGELOG.md CHANGED
@@ -16,6 +16,26 @@ All notable changes to this project are documented in this file.
16
16
  ### Removed
17
17
  - _None._
18
18
 
19
+ ## [0.18.0] - 2026-04-08
20
+ [View on npm](https://www.npmjs.com/package/@skilly-hand/skilly-hand/v/0.18.0)
21
+
22
+ ### Added
23
+ - Added `sync-catalog` orchestration script to compute catalog README + skill frontmatter updates up front and apply writes atomically with rollback on failure.
24
+ - Added `sync-skill-frontmatter` CLI script with `--check`, `--json`, and `--skill` filtering support.
25
+ - Added regression coverage for catalog sync rollback/idempotency and frontmatter normalization edge cases (`tests/sync-catalog.test.js`, `tests/skill-frontmatter.test.js`).
26
+
27
+ ### Changed
28
+ - Updated root `catalog:sync` script to run `scripts/sync-catalog.mjs` for unified catalog synchronization.
29
+ - Expanded script JSON contract coverage for `sync-catalog` and `sync-skill-frontmatter` in `tests/scripts-output.test.js`.
30
+ - Updated catalog validation flow to verify catalog README drift through dry-run sync checks.
31
+
32
+ ### Fixed
33
+ - Hardened skill frontmatter parsing and verification to avoid false frontmatter detection and preserve markdown content for malformed leading YAML-like blocks.
34
+ - Improved catalog README sync behavior to treat CRLF/LF-equivalent content as in sync.
35
+
36
+ ### Removed
37
+ - _None._
38
+
19
39
  ## [0.17.0] - 2026-04-08
20
40
  [View on npm](https://www.npmjs.com/package/@skilly-hand/skilly-hand/v/0.17.0)
21
41
 
@@ -1,3 +1,24 @@
1
+ ---
2
+ description: "Audit web accessibility against W3C WCAG 2.2 Level AA using framework-agnostic checks, remediation patterns, and portable command-line scanning."
3
+ skillMetadata:
4
+ author: "skilly-hand"
5
+ last-edit: "2026-04-04"
6
+ license: "Apache-2.0"
7
+ version: "1.0.0"
8
+ changelog: "Added portable WCAG 2.2 Level AA accessibility auditing skill with W3C-only references and scanner script; enables consistent web accessibility review across frameworks; affects catalog skill coverage and install plans for stacks recommending accessibility-audit"
9
+ auto-invoke: "Auditing, reviewing, or implementing web accessibility against WCAG 2.2 Level AA"
10
+ allowed-tools:
11
+ - "Read"
12
+ - "Edit"
13
+ - "Write"
14
+ - "Glob"
15
+ - "Grep"
16
+ - "Bash"
17
+ - "WebFetch"
18
+ - "WebSearch"
19
+ - "Task"
20
+ - "SubAgent"
21
+ ---
1
22
  # Accessibility Audit Guide
2
23
 
3
24
  ## When to Use
@@ -1,3 +1,22 @@
1
+ ---
2
+ description: "Author root AGENTS.md as a Where/What/When orchestrator that routes tasks and skill invocation clearly."
3
+ skillMetadata:
4
+ author: "skilly-hand"
5
+ last-edit: "2026-04-03"
6
+ license: "Apache-2.0"
7
+ version: "1.0.0"
8
+ changelog: "Added root AGENTS orchestration guidance around Where/What/When structure; improves AI task routing clarity and trigger recognition; affects root AGENTS authoring workflow"
9
+ auto-invoke: "Creating or updating root AGENTS.md orchestration guidance"
10
+ allowed-tools:
11
+ - "Read"
12
+ - "Edit"
13
+ - "Write"
14
+ - "Glob"
15
+ - "Grep"
16
+ - "Bash"
17
+ - "Task"
18
+ - "SubAgent"
19
+ ---
1
20
  # AGENTS Root Orchestrator Guide
2
21
 
3
22
  ## When to Use
@@ -1,3 +1,24 @@
1
+ ---
2
+ description: "Guide Angular code generation and review using latest stable Angular verification and modern framework best practices."
3
+ skillMetadata:
4
+ author: "skilly-hand"
5
+ last-edit: "2026-04-03"
6
+ license: "Apache-2.0"
7
+ version: "1.1.1"
8
+ changelog: "Added allowed-modes metadata to declare angular-guidelines sub-agent routing targets; improves discoverability of component-creator and angular-tester delegation modes; affects angular-guidelines manifest metadata"
9
+ auto-invoke: "Generating, reviewing, or refactoring Angular code artifacts in Angular projects"
10
+ allowed-tools:
11
+ - "Read"
12
+ - "Edit"
13
+ - "Write"
14
+ - "Glob"
15
+ - "Grep"
16
+ - "Bash"
17
+ - "WebFetch"
18
+ - "WebSearch"
19
+ - "Task"
20
+ - "SubAgent"
21
+ ---
1
22
  # Angular Guidelines
2
23
 
3
24
  ## When to Use
@@ -1,3 +1,24 @@
1
+ ---
2
+ description: "Guide users from Figma MCP installation and authentication through first canvas creation, with function-level tool coverage and operational recovery patterns."
3
+ skillMetadata:
4
+ author: "skilly-hand"
5
+ last-edit: "2026-04-03"
6
+ license: "Apache-2.0"
7
+ version: "1.0.1"
8
+ changelog: "Added allowed-modes metadata to declare figma-mcp-0to1 sub-agent routing targets; improves discoverability of install-auth, tool-function-catalog, canvas-creation-playbook, and troubleshooting-ops delegation modes; affects figma-mcp-0to1 manifest metadata"
9
+ auto-invoke: "Installing, configuring, or using Figma MCP from setup through first canvas creation"
10
+ allowed-tools:
11
+ - "Read"
12
+ - "Edit"
13
+ - "Write"
14
+ - "Glob"
15
+ - "Grep"
16
+ - "Bash"
17
+ - "WebFetch"
18
+ - "WebSearch"
19
+ - "Task"
20
+ - "SubAgent"
21
+ ---
1
22
  # Figma MCP 0-to-1 Guide
2
23
 
3
24
  ## When to Use
@@ -1,3 +1,20 @@
1
+ ---
2
+ description: "Project-aware frontend design skill that detects the existing tech stack, UI libraries, CSS variables, and design tokens before proposing any UI work. Supports greenfield projects via DESIGN.md context setup, and includes post-generation motion and visual refinement phases."
3
+ skillMetadata:
4
+ author: "skilly-hand"
5
+ last-edit: "2026-04-05"
6
+ license: "Apache-2.0"
7
+ version: "1.1.0"
8
+ changelog: "v1.1.0: Added design-context-setter agent for greenfield/DESIGN.md workflow; added visual-refiner agent for post-generation quality evaluation; added motion-designer agent for stack-aware micro-interactions; added aesthetic-archetypes reference asset; expanded SKILL.md routing map with optional motion and refinement phases; upgraded component-designer with interaction states checklist and aesthetic principles"
9
+ auto-invoke: "Designing or generating UI components, pages, or layouts in a web or mobile project; setting up visual direction for a greenfield project; adding motion or micro-interactions to existing UI; refining or polishing generated UI output"
10
+ allowed-tools:
11
+ - "Read"
12
+ - "Grep"
13
+ - "Glob"
14
+ - "Bash"
15
+ - "Edit"
16
+ - "Write"
17
+ ---
1
18
  # Frontend Design Guide
2
19
 
3
20
  ## When to Use
@@ -1,3 +1,21 @@
1
+ ---
2
+ description: "Optimize output token consumption through compact interpreter modes with controlled expansion when complexity, ambiguity, or risk requires more detail. Trigger: minimizing response verbosity while preserving clarity and correctness."
3
+ skillMetadata:
4
+ author: "skilly-hand"
5
+ last-edit: "2026-04-07"
6
+ license: "Apache-2.0"
7
+ version: "1.0.0"
8
+ changelog: "Added a new portable output compression skill with deterministic interpreter modes and guarded detail expansion; reduces response token costs while preserving safety and clarity; affects response shaping workflows and catalog routing"
9
+ auto-invoke: "When minimizing output verbosity or selecting compact communication modes"
10
+ allowed-tools:
11
+ - "Read"
12
+ - "Edit"
13
+ - "Write"
14
+ - "Glob"
15
+ - "Grep"
16
+ - "Bash"
17
+ - "Task"
18
+ ---
1
19
  # Output Optimizer Guide
2
20
 
3
21
  ## When to Use
@@ -1,3 +1,22 @@
1
+ ---
2
+ description: "Scan project configuration and release surfaces for leak and security risks, and enforce security gates on commit, push, and publish workflows across GitHub, GitLab, npm, pnpm, yarn, and generic CI. Trigger: validating repository security posture, preventing secret leaks, or hardening delivery pipelines."
3
+ skillMetadata:
4
+ author: "skilly-hand"
5
+ last-edit: "2026-04-07"
6
+ license: "Apache-2.0"
7
+ version: "1.0.0"
8
+ changelog: "Added portable project-security skill with commit/push/publish gating assets and CI templates; reduces secret leak and misconfiguration risk before delivery; affects catalog security workflow coverage and auto-invoke routing"
9
+ auto-invoke: "Scanning project configuration and delivery workflows for leaks or security issues before commit, push, or publish"
10
+ allowed-tools:
11
+ - "Read"
12
+ - "Edit"
13
+ - "Write"
14
+ - "Glob"
15
+ - "Grep"
16
+ - "Bash"
17
+ - "Task"
18
+ - "SubAgent"
19
+ ---
1
20
  # Project Security Guide
2
21
 
3
22
  ## When to Use
@@ -1,3 +1,20 @@
1
+ ---
2
+ description: "Scan the active project and teach any concept, code path, or decision using verified information, interactive questions, and simple explanations. Trigger: user asks to explain, understand, clarify, or learn about anything in the project or codebase."
3
+ skillMetadata:
4
+ author: "skilly-hand"
5
+ last-edit: "2026-04-04"
6
+ license: "Apache-2.0"
7
+ version: "1.0.0"
8
+ changelog: "Initial release of project-teacher skill; provides interactive, project-grounded teaching for any concept or code path; affects education and clarification workflows across all projects"
9
+ auto-invoke: "User needs to understand, explain, or learn about any aspect of the project or codebase"
10
+ allowed-tools:
11
+ - "Read"
12
+ - "Glob"
13
+ - "Grep"
14
+ - "Bash"
15
+ - "WebFetch"
16
+ - "WebSearch"
17
+ ---
1
18
  # Project Teacher Guide
2
19
 
3
20
  ## When to Use
@@ -1,3 +1,24 @@
1
+ ---
2
+ description: "Guide React code generation and review using latest stable React verification and modern framework best practices."
3
+ skillMetadata:
4
+ author: "skilly-hand"
5
+ last-edit: "2026-04-04"
6
+ license: "Apache-2.0"
7
+ version: "1.0.0"
8
+ changelog: "Added new react-guidelines skill with component and testing sub-agent routing; improves React-specific generation and review consistency with latest-stable preflight checks; affects portable catalog skill discovery and React workflow guidance"
9
+ auto-invoke: "Generating, reviewing, or refactoring React code artifacts in React projects"
10
+ allowed-tools:
11
+ - "Read"
12
+ - "Edit"
13
+ - "Write"
14
+ - "Glob"
15
+ - "Grep"
16
+ - "Bash"
17
+ - "WebFetch"
18
+ - "WebSearch"
19
+ - "Task"
20
+ - "SubAgent"
21
+ ---
1
22
  # React Guidelines
2
23
 
3
24
  ## When to Use
@@ -1,3 +1,20 @@
1
+ ---
2
+ description: "Review code, decisions, and artifacts through a multi-perspective committee and a domain expert safety guard, then synthesize a structured verdict."
3
+ skillMetadata:
4
+ author: "skilly-hand"
5
+ last-edit: "2026-04-04"
6
+ license: "Apache-2.0"
7
+ version: "1.0.0"
8
+ changelog: "Added multi-perspective review skill with committee + safety guard synthesis; enables adversarial evaluation without permanent agent files; affects catalog skill coverage for review and quality workflows"
9
+ auto-invoke: "Reviewing code, decisions, or artifacts where adversarial multi-perspective evaluation adds value"
10
+ allowed-tools:
11
+ - "Read"
12
+ - "Grep"
13
+ - "Glob"
14
+ - "Bash"
15
+ - "Task"
16
+ - "SubAgent"
17
+ ---
1
18
  # Review Rangers Guide
2
19
 
3
20
  ## When to Use
@@ -1,3 +1,24 @@
1
+ ---
2
+ description: "Create and standardize AI skills with reusable structure, metadata rules, and templates."
3
+ skillMetadata:
4
+ author: "skilly-hand"
5
+ last-edit: "2026-03-27"
6
+ license: "Apache-2.0"
7
+ version: "1.2.3"
8
+ changelog: "Metadata updated to ensure compliance with current standards; maintains skill integrity and version tracking; affects metadata section"
9
+ auto-invoke: "Creating a new skill"
10
+ allowed-tools:
11
+ - "Read"
12
+ - "Edit"
13
+ - "Write"
14
+ - "Glob"
15
+ - "Grep"
16
+ - "Bash"
17
+ - "WebFetch"
18
+ - "WebSearch"
19
+ - "Task"
20
+ - "SubAgent"
21
+ ---
1
22
  # Skill Creator Guide
2
23
 
3
24
  ## When to Create a Skill
@@ -87,6 +108,17 @@ Generic skill needs {product-name} info? -> Add references/ pointing to {produ
87
108
  | `skillMetadata.allowed-tools` | Yes | String list | All tools this skill can invoke (e.g., `Read`, `Edit`, `Write`, `SubAgent`) |
88
109
  | `skillMetadata.allowed-modes` | Optional | String list | Use only when skill has an `agents/` folder |
89
110
 
111
+ ### SKILL.md Frontmatter Mirroring
112
+
113
+ Top-level `SKILL.md` files now include managed YAML frontmatter mirrored from `manifest.json`.
114
+
115
+ Rules:
116
+
117
+ - `manifest.json` is the single source of truth.
118
+ - Mirror only `description` and `skillMetadata.{author,last-edit,license,version,changelog,auto-invoke,allowed-tools}`.
119
+ - Do not manually edit mirrored frontmatter in `SKILL.md`; run sync automation instead.
120
+ - Keep instruction body content in `SKILL.md` focused on workflow guidance.
121
+
90
122
  ---
91
123
 
92
124
  ## Metadata Standards
@@ -153,6 +185,7 @@ Do not:
153
185
  - Use web URLs in references.
154
186
  - Leave `changelog` empty or informal.
155
187
  - Use non-ISO date formats.
188
+ - Manually drift `SKILL.md` frontmatter away from `manifest.json`.
156
189
 
157
190
  ---
158
191
 
@@ -167,6 +200,7 @@ Do not:
167
200
  - [ ] `changelog` uses structured format: `what; why; where`.
168
201
  - [ ] `allowed-modes` is present only when `agents/` exists.
169
202
  - [ ] `allowed-tools` matches actual tool usage.
203
+ - [ ] `SKILL.md` frontmatter is synced from `manifest.json`.
170
204
  - [ ] Critical patterns are clear and concise.
171
205
  - [ ] Code examples are minimal and focused.
172
206
  - [ ] Commands section exists with copy-paste commands.
@@ -1,5 +1,10 @@
1
1
  # {Name of the Skill} Guide
2
2
 
3
+ <!--
4
+ Managed frontmatter is mirrored from manifest.json by automation.
5
+ Do not hand-author frontmatter in this template.
6
+ -->
7
+
3
8
  ## When to Use
4
9
 
5
10
  Use this skill when:
@@ -74,3 +79,4 @@ Otherwise -> {Default action}
74
79
 
75
80
  - Template assets: Place reusable templates, schemas, and examples in `assets/`.
76
81
  - Define metadata in `manifest.json` (`id`, `description`, `skillMetadata`, `allowed-tools`, optional `allowed-modes`).
82
+ - Run skill frontmatter sync so top-level `SKILL.md` mirrors manifest metadata.
@@ -1,3 +1,22 @@
1
+ ---
2
+ description: "Plan, execute, and verify multi-step work through versioned specs with small, testable tasks."
3
+ skillMetadata:
4
+ author: "skilly-hand"
5
+ last-edit: "2026-04-03"
6
+ license: "Apache-2.0"
7
+ version: "1.0.3"
8
+ changelog: "Added OpenSpec complementary support routing guidance to spec-driven-development instructions; improves planning continuity and review clarity when local SDD needs reinforcement; affects spec-driven-development SKILL guidance and manifest metadata"
9
+ auto-invoke: "Planning or executing feature work, bug fixes, and multi-phase implementation"
10
+ allowed-tools:
11
+ - "Read"
12
+ - "Edit"
13
+ - "Write"
14
+ - "Glob"
15
+ - "Grep"
16
+ - "Bash"
17
+ - "Task"
18
+ - "SubAgent"
19
+ ---
1
20
  # Spec-Driven Development Guide
2
21
 
3
22
  ## When to Use
@@ -1,3 +1,20 @@
1
+ ---
2
+ description: "Guide implementation using the RED → GREEN → REFACTOR TDD cycle: write a failing test first, write the minimum code to pass, then refactor while tests stay green."
3
+ skillMetadata:
4
+ author: "skilly-hand"
5
+ last-edit: "2026-04-04"
6
+ license: "Apache-2.0"
7
+ version: "1.0.0"
8
+ changelog: "Initial TDD skill ported from legacy scannlab-sdd tdd-templates; enables RED→GREEN→REFACTOR workflow across any stack; affects catalog skill coverage for test-first development"
9
+ auto-invoke: "Implementing features, services, or components using test-driven development (TDD) or RED→GREEN→REFACTOR cycles"
10
+ allowed-tools:
11
+ - "Read"
12
+ - "Edit"
13
+ - "Write"
14
+ - "Glob"
15
+ - "Grep"
16
+ - "Bash"
17
+ ---
1
18
  # Test-Driven Development Guide
2
19
 
3
20
  ## When to Use
@@ -1,3 +1,21 @@
1
+ ---
2
+ description: "Classify task complexity and right-size reasoning depth, context gathering, and response detail to reduce wasted tokens."
3
+ skillMetadata:
4
+ author: "skilly-hand"
5
+ last-edit: "2026-04-03"
6
+ license: "Apache-2.0"
7
+ version: "1.0.3"
8
+ changelog: "Migrated token-optimizer into portable catalog format with curated model-agnostic guidance; improves default reasoning and token-efficiency behavior across installs; affects skill discovery, auto-invoke routing, and install baseline"
9
+ auto-invoke: "Classifying task complexity and choosing reasoning depth/token budget"
10
+ allowed-tools:
11
+ - "Read"
12
+ - "Edit"
13
+ - "Write"
14
+ - "Glob"
15
+ - "Grep"
16
+ - "Bash"
17
+ - "Task"
18
+ ---
1
19
  # Token Optimizer Guide
2
20
 
3
21
  ## When to Use
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skilly-hand/skilly-hand",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "license": "CC-BY-NC-4.0",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -26,7 +26,7 @@
26
26
  "scripts": {
27
27
  "build": "node ./scripts/build-catalog-index.mjs",
28
28
  "catalog:check": "node ./scripts/check-catalog.mjs",
29
- "catalog:sync": "node ./scripts/sync-catalog-readme.mjs",
29
+ "catalog:sync": "node ./scripts/sync-catalog.mjs",
30
30
  "agentic:self:sync": "node ./scripts/sync-self-agentic.mjs",
31
31
  "test": "node --test tests/*.test.js && node ./scripts/test-in-sandbox.mjs",
32
32
  "security:check": "node ./scripts/security-check.mjs",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skilly-hand/catalog",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -1,4 +1,4 @@
1
- import { cp, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
1
+ import { cp, mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
 
@@ -21,6 +21,16 @@ const REQUIRED_FIELDS = [
21
21
  "dependencies"
22
22
  ];
23
23
 
24
+ const MIRRORED_SKILL_METADATA_KEYS = [
25
+ "author",
26
+ "last-edit",
27
+ "license",
28
+ "version",
29
+ "changelog",
30
+ "auto-invoke",
31
+ "allowed-tools"
32
+ ];
33
+
24
34
  export function getCatalogRoot() {
25
35
  return catalogDir;
26
36
  }
@@ -70,9 +80,372 @@ export function validateSkillManifest(manifest) {
70
80
  throw new Error(`Skill "${manifest.id}" must declare files`);
71
81
  }
72
82
 
83
+ const hasSkillInstruction = manifest.files.some((file) => file.path === "SKILL.md" && file.kind === "instruction");
84
+ if (!hasSkillInstruction) {
85
+ throw new Error(`Skill "${manifest.id}" must include files entry for SKILL.md as instruction`);
86
+ }
87
+
88
+ assertFrontmatterFields(manifest);
89
+
73
90
  return true;
74
91
  }
75
92
 
93
+ function toLf(text) {
94
+ return text.replaceAll("\r\n", "\n");
95
+ }
96
+
97
+ function splitLinesWithOffsets(text) {
98
+ const lines = [];
99
+ let start = 0;
100
+ for (let index = 0; index < text.length; index += 1) {
101
+ if (text[index] === "\n") {
102
+ lines.push({
103
+ text: text.slice(start, index),
104
+ start,
105
+ end: index + 1
106
+ });
107
+ start = index + 1;
108
+ }
109
+ }
110
+ if (start < text.length) {
111
+ lines.push({
112
+ text: text.slice(start),
113
+ start,
114
+ end: text.length
115
+ });
116
+ } else if (text.length === 0) {
117
+ lines.push({ text: "", start: 0, end: 0 });
118
+ }
119
+ return lines;
120
+ }
121
+
122
+ function yamlQuote(value) {
123
+ return JSON.stringify(String(value));
124
+ }
125
+
126
+ function assertFrontmatterFields(manifest) {
127
+ if (!manifest || typeof manifest !== "object") {
128
+ throw new Error("Invalid manifest while building SKILL.md frontmatter");
129
+ }
130
+
131
+ if (typeof manifest.description !== "string" || manifest.description.length === 0) {
132
+ throw new Error(`Skill "${manifest.id}" is missing required manifest.description for frontmatter mirroring`);
133
+ }
134
+
135
+ if (!manifest.skillMetadata || typeof manifest.skillMetadata !== "object") {
136
+ throw new Error(`Skill "${manifest.id}" is missing required manifest.skillMetadata for frontmatter mirroring`);
137
+ }
138
+
139
+ for (const key of MIRRORED_SKILL_METADATA_KEYS) {
140
+ if (!(key in manifest.skillMetadata)) {
141
+ throw new Error(`Skill "${manifest.id}" is missing required skillMetadata.${key} for frontmatter mirroring`);
142
+ }
143
+ }
144
+
145
+ const scalarKeys = ["author", "last-edit", "license", "version", "changelog", "auto-invoke"];
146
+ for (const key of scalarKeys) {
147
+ if (typeof manifest.skillMetadata[key] !== "string" || manifest.skillMetadata[key].trim().length === 0) {
148
+ throw new Error(`Skill "${manifest.id}" has invalid skillMetadata.${key}; expected a non-empty string`);
149
+ }
150
+ }
151
+
152
+ if (!Array.isArray(manifest.skillMetadata["allowed-tools"])) {
153
+ throw new Error(`Skill "${manifest.id}" must declare skillMetadata.allowed-tools as an array`);
154
+ }
155
+
156
+ for (const tool of manifest.skillMetadata["allowed-tools"]) {
157
+ if (typeof tool !== "string" || tool.trim().length === 0) {
158
+ throw new Error(`Skill "${manifest.id}" has invalid skillMetadata.allowed-tools; expected non-empty strings`);
159
+ }
160
+ }
161
+ }
162
+
163
+ export function buildSkillFrontmatterPayload(manifest) {
164
+ assertFrontmatterFields(manifest);
165
+ return {
166
+ description: manifest.description,
167
+ skillMetadata: {
168
+ author: manifest.skillMetadata.author,
169
+ "last-edit": manifest.skillMetadata["last-edit"],
170
+ license: manifest.skillMetadata.license,
171
+ version: manifest.skillMetadata.version,
172
+ changelog: manifest.skillMetadata.changelog,
173
+ "auto-invoke": manifest.skillMetadata["auto-invoke"],
174
+ "allowed-tools": [...manifest.skillMetadata["allowed-tools"]]
175
+ }
176
+ };
177
+ }
178
+
179
+ function renderSkillFrontmatterInner(payload) {
180
+ const lines = [
181
+ `description: ${yamlQuote(payload.description)}`,
182
+ "skillMetadata:",
183
+ ` author: ${yamlQuote(payload.skillMetadata.author)}`,
184
+ ` last-edit: ${yamlQuote(payload.skillMetadata["last-edit"])}`,
185
+ ` license: ${yamlQuote(payload.skillMetadata.license)}`,
186
+ ` version: ${yamlQuote(payload.skillMetadata.version)}`,
187
+ ` changelog: ${yamlQuote(payload.skillMetadata.changelog)}`,
188
+ ` auto-invoke: ${yamlQuote(payload.skillMetadata["auto-invoke"])}`,
189
+ " allowed-tools:"
190
+ ];
191
+
192
+ for (const tool of payload.skillMetadata["allowed-tools"]) {
193
+ lines.push(` - ${yamlQuote(tool)}`);
194
+ }
195
+
196
+ return lines.join("\n");
197
+ }
198
+
199
+ export function renderSkillFrontmatter(manifest) {
200
+ const payload = buildSkillFrontmatterPayload(manifest);
201
+ return `---\n${renderSkillFrontmatterInner(payload)}\n---\n`;
202
+ }
203
+
204
+ export function splitSkillMarkdown(content) {
205
+ const normalized = toLf(content);
206
+ const source = normalized.startsWith("\uFEFF") ? normalized.slice(1) : normalized;
207
+ const lines = splitLinesWithOffsets(source);
208
+ const mirroredKeys = new Set([
209
+ "description",
210
+ "skillMetadata",
211
+ "author",
212
+ "last-edit",
213
+ "license",
214
+ "version",
215
+ "changelog",
216
+ "auto-invoke",
217
+ "allowed-tools"
218
+ ]);
219
+ const isYamlLike = (line) => (
220
+ line.trim().length === 0 ||
221
+ /^\s*[A-Za-z0-9_-]+:(?:\s.*)?$/.test(line) ||
222
+ /^\s*-\s+.*$/.test(line)
223
+ );
224
+
225
+ let firstNonBlankIndex = 0;
226
+ while (firstNonBlankIndex < lines.length && lines[firstNonBlankIndex].text.trim().length === 0) {
227
+ firstNonBlankIndex += 1;
228
+ }
229
+
230
+ if (firstNonBlankIndex >= lines.length || lines[firstNonBlankIndex].text !== "---") {
231
+ return {
232
+ hasFrontmatter: false,
233
+ malformedFrontmatter: false,
234
+ frontmatter: null,
235
+ body: source
236
+ };
237
+ }
238
+
239
+ const openLine = lines[firstNonBlankIndex];
240
+ let sawKeyValue = false;
241
+ let sawMirroredKey = false;
242
+ const detectedKeys = new Set();
243
+
244
+ for (let index = firstNonBlankIndex + 1; index < lines.length; index += 1) {
245
+ const line = lines[index].text;
246
+ const nextLine = index + 1 < lines.length ? lines[index + 1].text : null;
247
+
248
+ if (
249
+ sawMirroredKey &&
250
+ line.trim().length === 0 &&
251
+ nextLine &&
252
+ /^(?:[-*+]\s+|\d+\.\s+|#{1,6}\s+|>\s+|```)/.test(nextLine)
253
+ ) {
254
+ return {
255
+ hasFrontmatter: true,
256
+ malformedFrontmatter: true,
257
+ frontmatter: null,
258
+ body: source.slice(lines[index + 1].start)
259
+ };
260
+ }
261
+
262
+ if (line === "---") {
263
+ if (!sawKeyValue || !sawMirroredKey) {
264
+ return {
265
+ hasFrontmatter: false,
266
+ malformedFrontmatter: false,
267
+ frontmatter: null,
268
+ body: source
269
+ };
270
+ }
271
+ const end = lines[index].end;
272
+ const frontmatter = source.slice(openLine.start, end);
273
+ return {
274
+ hasFrontmatter: true,
275
+ malformedFrontmatter: false,
276
+ frontmatter: frontmatter.endsWith("\n") ? frontmatter : `${frontmatter}\n`,
277
+ body: source.slice(end),
278
+ detectedKeys: Array.from(detectedKeys)
279
+ };
280
+ }
281
+
282
+ if (/^\s*[A-Za-z0-9_-]+:(?:\s.*)?$/.test(line)) {
283
+ sawKeyValue = true;
284
+ const key = line.split(":", 1)[0].trim();
285
+ detectedKeys.add(key);
286
+ if (mirroredKeys.has(key)) {
287
+ sawMirroredKey = true;
288
+ }
289
+ }
290
+
291
+ if (!isYamlLike(line)) {
292
+ if (!sawKeyValue || !sawMirroredKey) {
293
+ return {
294
+ hasFrontmatter: false,
295
+ malformedFrontmatter: false,
296
+ frontmatter: null,
297
+ body: source
298
+ };
299
+ }
300
+ return {
301
+ hasFrontmatter: true,
302
+ malformedFrontmatter: true,
303
+ frontmatter: null,
304
+ body: source.slice(lines[index].start),
305
+ detectedKeys: Array.from(detectedKeys)
306
+ };
307
+ }
308
+ }
309
+
310
+ if (!sawKeyValue) {
311
+ return {
312
+ hasFrontmatter: false,
313
+ malformedFrontmatter: false,
314
+ frontmatter: null,
315
+ body: source
316
+ };
317
+ }
318
+
319
+ if (!sawMirroredKey) {
320
+ return {
321
+ hasFrontmatter: false,
322
+ malformedFrontmatter: false,
323
+ frontmatter: null,
324
+ body: source
325
+ };
326
+ }
327
+
328
+ return {
329
+ hasFrontmatter: true,
330
+ malformedFrontmatter: true,
331
+ frontmatter: null,
332
+ body: "",
333
+ detectedKeys: Array.from(detectedKeys)
334
+ };
335
+ }
336
+
337
+ export function applyManifestFrontmatterToSkill(content, manifest) {
338
+ const expectedFrontmatter = renderSkillFrontmatter(manifest);
339
+ const parts = splitSkillMarkdown(content);
340
+ return `${expectedFrontmatter}${parts.body}`;
341
+ }
342
+
343
+ export function verifySkillFrontmatterContent(content, manifest) {
344
+ const expectedFrontmatter = renderSkillFrontmatter(manifest);
345
+ const parts = splitSkillMarkdown(content);
346
+ if (!parts.hasFrontmatter) {
347
+ return { ok: false, reason: "missing" };
348
+ }
349
+ if (parts.malformedFrontmatter || !parts.frontmatter) {
350
+ return { ok: false, reason: "malformed" };
351
+ }
352
+ if (parts.frontmatter !== expectedFrontmatter) {
353
+ return { ok: false, reason: "mismatch" };
354
+ }
355
+ const residual = splitSkillMarkdown(parts.body);
356
+ if (residual.hasFrontmatter && !residual.malformedFrontmatter && residual.frontmatter === expectedFrontmatter) {
357
+ return { ok: false, reason: "residual-frontmatter" };
358
+ }
359
+ return { ok: true, reason: null };
360
+ }
361
+
362
+ export async function syncSkillFrontmatter({ skillId, dryRun = false } = {}) {
363
+ const plan = await planSkillFrontmatterSync({ skillId });
364
+ if (!dryRun) {
365
+ await applyTextUpdatesAtomically(plan.updates);
366
+ }
367
+ return {
368
+ skillCount: plan.skillCount,
369
+ updatedSkillIds: plan.updatedSkillIds
370
+ };
371
+ }
372
+
373
+ export async function planSkillFrontmatterSync({ skillId } = {}) {
374
+ const allIds = await listSkillIds();
375
+ if (skillId && !allIds.includes(skillId)) {
376
+ throw new Error(`Unknown skill id: ${skillId}`);
377
+ }
378
+
379
+ const ids = skillId ? [skillId] : allIds;
380
+ const updatedSkillIds = [];
381
+ const updates = [];
382
+
383
+ for (const id of ids) {
384
+ const manifest = await loadSkillManifest(id);
385
+ const skillPath = path.join(skillsDir, id, "SKILL.md");
386
+ const current = await readFile(skillPath, "utf8");
387
+ const next = applyManifestFrontmatterToSkill(current, manifest);
388
+ if (next !== toLf(current)) {
389
+ updatedSkillIds.push(id);
390
+ updates.push({
391
+ skillId: id,
392
+ path: skillPath,
393
+ content: next
394
+ });
395
+ }
396
+ }
397
+
398
+ return {
399
+ skillCount: ids.length,
400
+ updatedSkillIds,
401
+ updates
402
+ };
403
+ }
404
+
405
+ export async function applyTextUpdatesAtomically(updates) {
406
+ const deduped = [];
407
+ const seenPaths = new Set();
408
+ for (let index = updates.length - 1; index >= 0; index -= 1) {
409
+ const update = updates[index];
410
+ if (!seenPaths.has(update.path)) {
411
+ seenPaths.add(update.path);
412
+ deduped.push(update);
413
+ }
414
+ }
415
+ deduped.reverse();
416
+
417
+ const originals = new Map();
418
+ for (const update of deduped) {
419
+ try {
420
+ originals.set(update.path, await readFile(update.path, "utf8"));
421
+ } catch (error) {
422
+ if (error?.code === "ENOENT") {
423
+ originals.set(update.path, null);
424
+ } else {
425
+ throw error;
426
+ }
427
+ }
428
+ }
429
+
430
+ const writtenPaths = [];
431
+ try {
432
+ for (const update of deduped) {
433
+ await writeFile(update.path, update.content, "utf8");
434
+ writtenPaths.push(update.path);
435
+ }
436
+ } catch (error) {
437
+ for (const targetPath of writtenPaths.reverse()) {
438
+ const original = originals.get(targetPath);
439
+ if (original === null) {
440
+ await rm(targetPath, { force: true });
441
+ } else {
442
+ await writeFile(targetPath, original, "utf8");
443
+ }
444
+ }
445
+ throw error;
446
+ }
447
+ }
448
+
76
449
  export async function copySkillTo(targetCatalogDir, skillId) {
77
450
  const source = path.join(skillsDir, skillId);
78
451
  const destination = path.join(targetCatalogDir, skillId);
@@ -88,6 +461,7 @@ export async function readTemplate(templateName) {
88
461
  }
89
462
 
90
463
  export function renderAgentsMarkdown({ skills, detections, generatedAt, projectName }) {
464
+ const escapeTableCell = (value) => String(value).replaceAll("|", "\\|").replaceAll("\n", "<br>");
91
465
  const sortedSkills = [...skills].sort((a, b) => a.id.localeCompare(b.id));
92
466
  const sortedDetections = [...detections].sort((a, b) => a.technology.localeCompare(b.technology));
93
467
  const autoInvokeSkills = sortedSkills.filter((skill) => skill.skillMetadata?.["auto-invoke"]);
@@ -114,7 +488,7 @@ export function renderAgentsMarkdown({ skills, detections, generatedAt, projectN
114
488
  ];
115
489
 
116
490
  for (const skill of sortedSkills) {
117
- lines.push(`| \`${skill.id}\` | ${skill.description} | ${skill.tags.join(", ")} |`);
491
+ lines.push(`| \`${skill.id}\` | ${escapeTableCell(skill.description)} | ${escapeTableCell(skill.tags.join(", "))} |`);
118
492
  }
119
493
 
120
494
  lines.push(
@@ -126,7 +500,7 @@ export function renderAgentsMarkdown({ skills, detections, generatedAt, projectN
126
500
  "1. Always run `token-optimizer` first to classify complexity and set the minimum viable reasoning depth.",
127
501
  "2. Always run `output-optimizer` immediately after `token-optimizer` for response-shape control.",
128
502
  "3. `output-optimizer` mode policy:",
129
- " - Default: select a random canonical mode for each new interaction.",
503
+ " - Default: use `step-brief` when there is no explicit mode or strong phrasing signal.",
130
504
  " - Override: if user explicitly requests a mode (for example `mode: step-brief`), that explicit mode wins.",
131
505
  " - Persistence: keep the explicitly requested mode active until the user asks for a different mode.",
132
506
  "",
@@ -154,7 +528,7 @@ export function renderAgentsMarkdown({ skills, detections, generatedAt, projectN
154
528
  } else {
155
529
  lines.push("| Action | Skill |", "| ------ | ----- |");
156
530
  for (const skill of autoInvokeSkills) {
157
- lines.push(`| ${skill.skillMetadata["auto-invoke"]} | \`${skill.id}\` |`);
531
+ lines.push(`| ${escapeTableCell(skill.skillMetadata["auto-invoke"])} | \`${skill.id}\` |`);
158
532
  }
159
533
  }
160
534
 
@@ -230,6 +604,28 @@ export async function verifyCatalogFiles() {
230
604
  issues.push(`Missing file for ${skillId}: ${file.path}`);
231
605
  }
232
606
  }
607
+
608
+ const skillDocPath = path.join(skillPath, "SKILL.md");
609
+ let skillDocContent;
610
+ try {
611
+ skillDocContent = await readFile(skillDocPath, "utf8");
612
+ } catch (error) {
613
+ if (error?.code === "ENOENT") {
614
+ // Missing file is already surfaced above via manifest file verification.
615
+ continue;
616
+ }
617
+ issues.push(`Cannot read ${skillId}/SKILL.md: ${error.message}`);
618
+ continue;
619
+ }
620
+
621
+ try {
622
+ const status = verifySkillFrontmatterContent(skillDocContent, manifest);
623
+ if (!status.ok) {
624
+ issues.push(`Frontmatter ${status.reason} for ${skillId}: SKILL.md`);
625
+ }
626
+ } catch (error) {
627
+ issues.push(`Frontmatter validation failed for ${skillId}: ${error.message}`);
628
+ }
233
629
  }
234
630
 
235
631
  return issues;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skilly-hand/cli",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skilly-hand/core",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skilly-hand/detectors",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "private": true,
5
5
  "type": "module"
6
6
  }