mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
fix: PR #118 round 2 — null-safe parser, docs, consistency
- Null-safe extraction for description, license, and compatibility in skill_parser.py — YAML bare keys (e.g. `description:`) no longer produce the literal string "None" - Log warning on skill catalog storage failure instead of silent swallow - Use `enabled == 1` in list_skills_by_activation for consistency with other prompt_templates queries in both storage backends - Add parser tests for YAML null description, license, and compatibility - Update governance.md: document runtime config editing on installed skills, two-column modal layout, SPDX license dropdown, origin badge
This commit is contained in:
committed by
Patrick Buckley
parent
341d2f604f
commit
c76a61841e
@@ -104,6 +104,20 @@ etc.) since workstream templates were merged into the skills system in v0.8.0.
|
||||
Discovery view has search bar, result cards, and "Import from GitHub" modal.
|
||||
- SDK: `discover_skills(q)` and `install_skill(source, skill_id=..., url=...)`
|
||||
on both Python and TypeScript console clients.
|
||||
- **Runtime config on installed skills**: Installed (readonly) skills can have
|
||||
their runtime configuration edited — model, temperature, reasoning effort,
|
||||
token budget, max tokens, agent max turns, auto-approve, allowed tools,
|
||||
and enabled flag. The server restricts updates to these fields only via
|
||||
`_SKILL_RUNTIME_CONFIG_FIELDS` filtering; spec/content fields (name,
|
||||
description, tags, license, compatibility, content, activation) remain
|
||||
immutable. The admin UI shows "Save Config" instead of "Save" for these
|
||||
skills. Audit action: `skill.update.config`.
|
||||
- **Admin UI**: Create/Edit skill modals use a two-column spec manifest layout
|
||||
(left: Identity / Manifest / Deployment; right: Skill Content editor with
|
||||
monospace font). Runtime Config is a collapsible 3-column grid below.
|
||||
License uses an SPDX identifier dropdown (MIT, Apache-2.0, GPL-3.0, etc.).
|
||||
Installed skills show a cyan origin badge with source URL, spec fields are
|
||||
disabled, and all collapsible sections auto-expand in view mode.
|
||||
|
||||
### Usage Tracking
|
||||
|
||||
|
||||
@@ -394,6 +394,36 @@ Content.
|
||||
result = parse_skill_md(raw)
|
||||
assert result.version == "1.0.0"
|
||||
|
||||
def test_null_description_falls_back_to_body(self) -> None:
|
||||
"""YAML null description must not produce 'None' string."""
|
||||
raw = """\
|
||||
---
|
||||
name: null-desc
|
||||
description:
|
||||
---
|
||||
|
||||
First paragraph here.
|
||||
"""
|
||||
result = parse_skill_md(raw)
|
||||
assert result.description == "First paragraph here."
|
||||
assert "None" not in result.description
|
||||
|
||||
def test_null_license_and_compatibility(self) -> None:
|
||||
"""YAML null license/compatibility must not produce 'None' string."""
|
||||
raw = """\
|
||||
---
|
||||
name: null-fields
|
||||
description: Test
|
||||
license:
|
||||
compatibility:
|
||||
---
|
||||
|
||||
Content.
|
||||
"""
|
||||
result = parse_skill_md(raw)
|
||||
assert result.license == ""
|
||||
assert result.compatibility == ""
|
||||
|
||||
|
||||
class TestStandardFieldLengths:
|
||||
"""Spec caps: description <= 1024, compatibility <= 500."""
|
||||
|
||||
@@ -862,6 +862,7 @@ class ChatSession:
|
||||
try:
|
||||
search_skills = list_skills_by_activation("search", enabled_only=True, limit=30)
|
||||
except Exception:
|
||||
log.warning("session.skill_catalog_failed", exc_info=True)
|
||||
search_skills = []
|
||||
if search_skills:
|
||||
catalog_lines = ["<available-skills>"]
|
||||
|
||||
@@ -199,7 +199,8 @@ def parse_skill_md(raw: str, *, lenient: bool = False) -> ParsedSkill | None:
|
||||
raise ValueError(name_err)
|
||||
|
||||
# Description — frontmatter or first paragraph of body
|
||||
description = str(meta.get("description", "")).strip()
|
||||
raw_desc = meta.get("description")
|
||||
description = str(raw_desc).strip() if raw_desc is not None else ""
|
||||
if not description and body:
|
||||
first_line = body.split("\n")[0].strip()
|
||||
# Skip markdown headings
|
||||
@@ -220,7 +221,8 @@ def parse_skill_md(raw: str, *, lenient: bool = False) -> ParsedSkill | None:
|
||||
)
|
||||
description = description[:_MAX_DESCRIPTION_LEN]
|
||||
|
||||
compatibility = str(meta.get("compatibility", "")).strip()
|
||||
raw_compat = meta.get("compatibility")
|
||||
compatibility = str(raw_compat).strip() if raw_compat is not None else ""
|
||||
if len(compatibility) > _MAX_COMPATIBILITY_LEN:
|
||||
log.warning(
|
||||
"skill_parser.compatibility_truncated",
|
||||
@@ -238,7 +240,7 @@ def parse_skill_md(raw: str, *, lenient: bool = False) -> ParsedSkill | None:
|
||||
version=_extract_str(meta, "version", default="1.0.0"),
|
||||
# Standard uses "allowed-tools" (hyphenated); stored internally as allowed_tools
|
||||
allowed_tools=_extract_list(meta, "allowed-tools"),
|
||||
license=str(meta.get("license", "")).strip(),
|
||||
license=_extract_str(meta, "license"),
|
||||
compatibility=compatibility,
|
||||
raw_frontmatter=meta,
|
||||
)
|
||||
|
||||
@@ -1697,7 +1697,7 @@ class PostgreSQLBackend:
|
||||
.order_by(prompt_templates.c.name)
|
||||
)
|
||||
if enabled_only:
|
||||
q = q.where(prompt_templates.c.enabled == True) # noqa: E712
|
||||
q = q.where(prompt_templates.c.enabled == 1)
|
||||
if limit > 0:
|
||||
q = q.limit(limit)
|
||||
rows = conn.execute(q).fetchall()
|
||||
|
||||
@@ -1721,7 +1721,7 @@ class SQLiteBackend:
|
||||
.order_by(prompt_templates.c.name)
|
||||
)
|
||||
if enabled_only:
|
||||
q = q.where(prompt_templates.c.enabled == True) # noqa: E712
|
||||
q = q.where(prompt_templates.c.enabled == 1)
|
||||
if limit > 0:
|
||||
q = q.limit(limit)
|
||||
rows = conn.execute(q).fetchall()
|
||||
|
||||
Reference in New Issue
Block a user