@pasko70/pibo 1.4.0 → 1.4.2

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.
Files changed (129) hide show
  1. package/README.md +183 -183
  2. package/context/codex-base-prompt.md +148 -148
  3. package/context/compute-worker.md +23 -23
  4. package/context/pibo-compaction-prompt.md +100 -100
  5. package/context/pibo-native-tooling.md +18 -18
  6. package/context/pibo-system-prompt.md +77 -77
  7. package/dist/apps/chat/agent-store.js +82 -82
  8. package/dist/apps/chat/data/project-service.js +166 -166
  9. package/dist/apps/chat/data/read-state-service.js +18 -18
  10. package/dist/apps/chat/data/timeline-query-service.js +8 -8
  11. package/dist/apps/chat/model-catalog.js +5 -1
  12. package/dist/apps/chat/static-assets.js +853 -853
  13. package/dist/apps/chat/workflow-persistence.js +255 -255
  14. package/dist/apps/chat-ui/assets/{dist-7YaJd19a.js → dist-B2BEpL7n.js} +1 -1
  15. package/dist/apps/chat-ui/assets/{dist-CiYeO8nN.js → dist-BAGS_xkV.js} +1 -1
  16. package/dist/apps/chat-ui/assets/{dist-BIvPnn_C.js → dist-BAXNalar.js} +1 -1
  17. package/dist/apps/chat-ui/assets/{dist-CjSI6y5z.js → dist-BwUvs6Ph.js} +1 -1
  18. package/dist/apps/chat-ui/assets/{dist-Ubstha8t.js → dist-C0zsJ8II.js} +1 -1
  19. package/dist/apps/chat-ui/assets/{dist-KXCMNKIL.js → dist-C3PnEkhb.js} +1 -1
  20. package/dist/apps/chat-ui/assets/{dist-CVQU42Fn.js → dist-CYPL-B2Z.js} +1 -1
  21. package/dist/apps/chat-ui/assets/{dist-BbNE72h8.js → dist-CiDSXgtg.js} +1 -1
  22. package/dist/apps/chat-ui/assets/{dist-D7TCkoFT.js → dist-DnACFKyO.js} +1 -1
  23. package/dist/apps/chat-ui/assets/{dist-ChSZNqKE.js → dist-ZB1-ui2y.js} +1 -1
  24. package/dist/apps/chat-ui/assets/{dist-8Noo5eCN.js → dist-vKlxFkTa.js} +1 -1
  25. package/dist/apps/chat-ui/assets/{index-CmxtUVG1.js → index-0x7tuTNX.js} +3 -3
  26. package/dist/apps/chat-ui/assets/{index-C25VYnyb.css → index-B-qaya1G.css} +1 -1
  27. package/dist/apps/chat-ui/index.html +18 -18
  28. package/dist/apps/chat-ui/manifest.webmanifest +25 -25
  29. package/dist/apps/chat-ui/sw.js +41 -41
  30. package/dist/apps/chat-vscode-web/index.html +12 -12
  31. package/dist/apps/cli-ui/cliSessionsCommand.js +23 -23
  32. package/dist/apps/context-files-ui/index.html +11 -11
  33. package/dist/apps/vscode-artifacts/latest.vsix +0 -0
  34. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.4.2.vsix +0 -0
  35. package/dist/bin/pibo.js +0 -0
  36. package/dist/bin/rg.js +0 -0
  37. package/dist/cli.js +35 -35
  38. package/dist/compute/cli.js +54 -54
  39. package/dist/core/runtime.js +2 -0
  40. package/dist/core/session-router.js +1 -0
  41. package/dist/cron/cli.js +15 -15
  42. package/dist/cron/store.js +49 -49
  43. package/dist/data/cli.js +23 -23
  44. package/dist/data/event-log.js +23 -23
  45. package/dist/data/message-store.js +20 -20
  46. package/dist/data/navigation-store.js +9 -9
  47. package/dist/data/observation-store.js +4 -4
  48. package/dist/data/payload-store.js +17 -17
  49. package/dist/data/schema.js +429 -429
  50. package/dist/data/session-store.js +4 -4
  51. package/dist/data/telemetry-queries.js +54 -54
  52. package/dist/data/telemetry.js +156 -156
  53. package/dist/debug/events.js +12 -12
  54. package/dist/debug/failures.js +6 -6
  55. package/dist/debug/index.js +207 -207
  56. package/dist/debug/messages.js +6 -6
  57. package/dist/debug/pty.js +124 -124
  58. package/dist/debug/session.js +29 -29
  59. package/dist/debug/tools.js +5 -5
  60. package/dist/debug/web-snapshot-browser-scripts.js +294 -294
  61. package/dist/debug/web-streaming-browser-library.js +925 -925
  62. package/dist/debug/web-streaming-browser-scripts.js +232 -232
  63. package/dist/debug/web-streaming-provider-telemetry.js +4 -4
  64. package/dist/debug/web.js +93 -93
  65. package/dist/gateway/cli.js +19 -19
  66. package/dist/mcp/config-command.js +53 -53
  67. package/dist/mcp/index.js +21 -21
  68. package/dist/mcp/registry.js +11 -11
  69. package/dist/pi-packages/cli.js +11 -11
  70. package/dist/plugins/context-files-store.js +110 -110
  71. package/dist/plugins/context-files.js +4 -4
  72. package/dist/providers/glm.js +59 -0
  73. package/dist/ralph/cli.js +18 -18
  74. package/dist/ralph/templates.js +140 -140
  75. package/dist/reliability/store.js +226 -226
  76. package/dist/sessions/pibo-data-store.js +16 -16
  77. package/dist/sessions/sqlite-store.js +53 -53
  78. package/dist/setup/cli.js +58 -58
  79. package/dist/tools/agent-browser-wrapper.js +80 -80
  80. package/dist/tools/browser-use-cdp.js +12 -12
  81. package/dist/tools/browser-use-wrapper.js +762 -762
  82. package/dist/tools/guides.js +455 -455
  83. package/dist/tools/index.js +99 -99
  84. package/dist/tools/runtime/node-worker-source.js +205 -205
  85. package/dist/tools/runtime/python-worker-source.js +177 -177
  86. package/dist/vscode/cli.js +9 -9
  87. package/dist/web-annotations/cdp.js +900 -900
  88. package/dist/web-annotations/store.js +96 -96
  89. package/docs/README.md +23 -23
  90. package/docs/ops/install-developer-host.md +112 -112
  91. package/docs/ops/install-user-host.md +96 -96
  92. package/docs/ops/upgrade-user-to-developer-host.md +69 -69
  93. package/docs/ops/vscode-extension-release.md +160 -160
  94. package/package.json +1 -1
  95. package/skills/builtin/pi-agent-harness/SKILL.md +319 -319
  96. package/skills/builtin/pi-agent-harness/agents/openai.yaml +4 -4
  97. package/skills/builtin/pibo-docker-system/SKILL.md +170 -170
  98. package/skills/builtin/pibo-spec-writing/SKILL.md +330 -330
  99. package/skills/builtin/prd/SKILL.md +143 -143
  100. package/skills/builtin/ralph-loop/SKILL.md +359 -359
  101. package/skills/builtin/ralph-prd-json/SKILL.md +123 -123
  102. package/skills/builtin/skill-creator/LICENSE.txt +201 -201
  103. package/skills/builtin/skill-creator/SKILL.md +513 -513
  104. package/skills/builtin/skill-creator/agents/analyzer.md +274 -274
  105. package/skills/builtin/skill-creator/agents/comparator.md +202 -202
  106. package/skills/builtin/skill-creator/agents/grader.md +223 -223
  107. package/skills/builtin/skill-creator/assets/eval_review.html +146 -146
  108. package/skills/builtin/skill-creator/eval-viewer/generate_review.py +471 -471
  109. package/skills/builtin/skill-creator/eval-viewer/viewer.html +1325 -1325
  110. package/skills/builtin/skill-creator/references/schemas.md +430 -430
  111. package/skills/builtin/skill-creator/scripts/aggregate_benchmark.py +401 -401
  112. package/skills/builtin/skill-creator/scripts/generate_report.py +326 -326
  113. package/skills/builtin/skill-creator/scripts/improve_description.py +247 -247
  114. package/skills/builtin/skill-creator/scripts/package_skill.py +136 -136
  115. package/skills/builtin/skill-creator/scripts/quick_validate.py +102 -102
  116. package/skills/builtin/skill-creator/scripts/run_eval.py +310 -310
  117. package/skills/builtin/skill-creator/scripts/run_loop.py +328 -328
  118. package/skills/builtin/skill-creator/scripts/utils.py +47 -47
  119. package/skills/builtin/web-annotations/SKILL.md +93 -93
  120. package/src/mcp/LICENSE.mcp-cli +21 -21
  121. package/dist/apps/vscode-artifacts/pibo-vscode-1.3.0.vsix +0 -0
  122. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.3.3.vsix +0 -0
  123. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.3.4.vsix +0 -0
  124. package/dist/apps/vscode-artifacts/pibo-vscode-ext-1.3.5.vsix +0 -0
  125. package/dist/core/shared-app.js +0 -17
  126. package/dist/data/final-app-space-cutover-migration.js +0 -728
  127. package/dist/data/shared-app-migration.js +0 -757
  128. package/dist/session-ui/ownerViewModel.js +0 -27
  129. package/dist/shared-app.js +0 -4
@@ -1,136 +1,136 @@
1
- #!/usr/bin/env python3
2
- """
3
- Skill Packager - Creates a distributable .skill file of a skill folder
4
-
5
- Usage:
6
- python utils/package_skill.py <path/to/skill-folder> [output-directory]
7
-
8
- Example:
9
- python utils/package_skill.py skills/public/my-skill
10
- python utils/package_skill.py skills/public/my-skill ./dist
11
- """
12
-
13
- import fnmatch
14
- import sys
15
- import zipfile
16
- from pathlib import Path
17
- from scripts.quick_validate import validate_skill
18
-
19
- # Patterns to exclude when packaging skills.
20
- EXCLUDE_DIRS = {"__pycache__", "node_modules"}
21
- EXCLUDE_GLOBS = {"*.pyc"}
22
- EXCLUDE_FILES = {".DS_Store"}
23
- # Directories excluded only at the skill root (not when nested deeper).
24
- ROOT_EXCLUDE_DIRS = {"evals"}
25
-
26
-
27
- def should_exclude(rel_path: Path) -> bool:
28
- """Check if a path should be excluded from packaging."""
29
- parts = rel_path.parts
30
- if any(part in EXCLUDE_DIRS for part in parts):
31
- return True
32
- # rel_path is relative to skill_path.parent, so parts[0] is the skill
33
- # folder name and parts[1] (if present) is the first subdir.
34
- if len(parts) > 1 and parts[1] in ROOT_EXCLUDE_DIRS:
35
- return True
36
- name = rel_path.name
37
- if name in EXCLUDE_FILES:
38
- return True
39
- return any(fnmatch.fnmatch(name, pat) for pat in EXCLUDE_GLOBS)
40
-
41
-
42
- def package_skill(skill_path, output_dir=None):
43
- """
44
- Package a skill folder into a .skill file.
45
-
46
- Args:
47
- skill_path: Path to the skill folder
48
- output_dir: Optional output directory for the .skill file (defaults to current directory)
49
-
50
- Returns:
51
- Path to the created .skill file, or None if error
52
- """
53
- skill_path = Path(skill_path).resolve()
54
-
55
- # Validate skill folder exists
56
- if not skill_path.exists():
57
- print(f"❌ Error: Skill folder not found: {skill_path}")
58
- return None
59
-
60
- if not skill_path.is_dir():
61
- print(f"❌ Error: Path is not a directory: {skill_path}")
62
- return None
63
-
64
- # Validate SKILL.md exists
65
- skill_md = skill_path / "SKILL.md"
66
- if not skill_md.exists():
67
- print(f"❌ Error: SKILL.md not found in {skill_path}")
68
- return None
69
-
70
- # Run validation before packaging
71
- print("🔍 Validating skill...")
72
- valid, message = validate_skill(skill_path)
73
- if not valid:
74
- print(f"❌ Validation failed: {message}")
75
- print(" Please fix the validation errors before packaging.")
76
- return None
77
- print(f"✅ {message}\n")
78
-
79
- # Determine output location
80
- skill_name = skill_path.name
81
- if output_dir:
82
- output_path = Path(output_dir).resolve()
83
- output_path.mkdir(parents=True, exist_ok=True)
84
- else:
85
- output_path = Path.cwd()
86
-
87
- skill_filename = output_path / f"{skill_name}.skill"
88
-
89
- # Create the .skill file (zip format)
90
- try:
91
- with zipfile.ZipFile(skill_filename, 'w', zipfile.ZIP_DEFLATED) as zipf:
92
- # Walk through the skill directory, excluding build artifacts
93
- for file_path in skill_path.rglob('*'):
94
- if not file_path.is_file():
95
- continue
96
- arcname = file_path.relative_to(skill_path.parent)
97
- if should_exclude(arcname):
98
- print(f" Skipped: {arcname}")
99
- continue
100
- zipf.write(file_path, arcname)
101
- print(f" Added: {arcname}")
102
-
103
- print(f"\n✅ Successfully packaged skill to: {skill_filename}")
104
- return skill_filename
105
-
106
- except Exception as e:
107
- print(f"❌ Error creating .skill file: {e}")
108
- return None
109
-
110
-
111
- def main():
112
- if len(sys.argv) < 2:
113
- print("Usage: python utils/package_skill.py <path/to/skill-folder> [output-directory]")
114
- print("\nExample:")
115
- print(" python utils/package_skill.py skills/public/my-skill")
116
- print(" python utils/package_skill.py skills/public/my-skill ./dist")
117
- sys.exit(1)
118
-
119
- skill_path = sys.argv[1]
120
- output_dir = sys.argv[2] if len(sys.argv) > 2 else None
121
-
122
- print(f"📦 Packaging skill: {skill_path}")
123
- if output_dir:
124
- print(f" Output directory: {output_dir}")
125
- print()
126
-
127
- result = package_skill(skill_path, output_dir)
128
-
129
- if result:
130
- sys.exit(0)
131
- else:
132
- sys.exit(1)
133
-
134
-
135
- if __name__ == "__main__":
136
- main()
1
+ #!/usr/bin/env python3
2
+ """
3
+ Skill Packager - Creates a distributable .skill file of a skill folder
4
+
5
+ Usage:
6
+ python utils/package_skill.py <path/to/skill-folder> [output-directory]
7
+
8
+ Example:
9
+ python utils/package_skill.py skills/public/my-skill
10
+ python utils/package_skill.py skills/public/my-skill ./dist
11
+ """
12
+
13
+ import fnmatch
14
+ import sys
15
+ import zipfile
16
+ from pathlib import Path
17
+ from scripts.quick_validate import validate_skill
18
+
19
+ # Patterns to exclude when packaging skills.
20
+ EXCLUDE_DIRS = {"__pycache__", "node_modules"}
21
+ EXCLUDE_GLOBS = {"*.pyc"}
22
+ EXCLUDE_FILES = {".DS_Store"}
23
+ # Directories excluded only at the skill root (not when nested deeper).
24
+ ROOT_EXCLUDE_DIRS = {"evals"}
25
+
26
+
27
+ def should_exclude(rel_path: Path) -> bool:
28
+ """Check if a path should be excluded from packaging."""
29
+ parts = rel_path.parts
30
+ if any(part in EXCLUDE_DIRS for part in parts):
31
+ return True
32
+ # rel_path is relative to skill_path.parent, so parts[0] is the skill
33
+ # folder name and parts[1] (if present) is the first subdir.
34
+ if len(parts) > 1 and parts[1] in ROOT_EXCLUDE_DIRS:
35
+ return True
36
+ name = rel_path.name
37
+ if name in EXCLUDE_FILES:
38
+ return True
39
+ return any(fnmatch.fnmatch(name, pat) for pat in EXCLUDE_GLOBS)
40
+
41
+
42
+ def package_skill(skill_path, output_dir=None):
43
+ """
44
+ Package a skill folder into a .skill file.
45
+
46
+ Args:
47
+ skill_path: Path to the skill folder
48
+ output_dir: Optional output directory for the .skill file (defaults to current directory)
49
+
50
+ Returns:
51
+ Path to the created .skill file, or None if error
52
+ """
53
+ skill_path = Path(skill_path).resolve()
54
+
55
+ # Validate skill folder exists
56
+ if not skill_path.exists():
57
+ print(f"❌ Error: Skill folder not found: {skill_path}")
58
+ return None
59
+
60
+ if not skill_path.is_dir():
61
+ print(f"❌ Error: Path is not a directory: {skill_path}")
62
+ return None
63
+
64
+ # Validate SKILL.md exists
65
+ skill_md = skill_path / "SKILL.md"
66
+ if not skill_md.exists():
67
+ print(f"❌ Error: SKILL.md not found in {skill_path}")
68
+ return None
69
+
70
+ # Run validation before packaging
71
+ print("🔍 Validating skill...")
72
+ valid, message = validate_skill(skill_path)
73
+ if not valid:
74
+ print(f"❌ Validation failed: {message}")
75
+ print(" Please fix the validation errors before packaging.")
76
+ return None
77
+ print(f"✅ {message}\n")
78
+
79
+ # Determine output location
80
+ skill_name = skill_path.name
81
+ if output_dir:
82
+ output_path = Path(output_dir).resolve()
83
+ output_path.mkdir(parents=True, exist_ok=True)
84
+ else:
85
+ output_path = Path.cwd()
86
+
87
+ skill_filename = output_path / f"{skill_name}.skill"
88
+
89
+ # Create the .skill file (zip format)
90
+ try:
91
+ with zipfile.ZipFile(skill_filename, 'w', zipfile.ZIP_DEFLATED) as zipf:
92
+ # Walk through the skill directory, excluding build artifacts
93
+ for file_path in skill_path.rglob('*'):
94
+ if not file_path.is_file():
95
+ continue
96
+ arcname = file_path.relative_to(skill_path.parent)
97
+ if should_exclude(arcname):
98
+ print(f" Skipped: {arcname}")
99
+ continue
100
+ zipf.write(file_path, arcname)
101
+ print(f" Added: {arcname}")
102
+
103
+ print(f"\n✅ Successfully packaged skill to: {skill_filename}")
104
+ return skill_filename
105
+
106
+ except Exception as e:
107
+ print(f"❌ Error creating .skill file: {e}")
108
+ return None
109
+
110
+
111
+ def main():
112
+ if len(sys.argv) < 2:
113
+ print("Usage: python utils/package_skill.py <path/to/skill-folder> [output-directory]")
114
+ print("\nExample:")
115
+ print(" python utils/package_skill.py skills/public/my-skill")
116
+ print(" python utils/package_skill.py skills/public/my-skill ./dist")
117
+ sys.exit(1)
118
+
119
+ skill_path = sys.argv[1]
120
+ output_dir = sys.argv[2] if len(sys.argv) > 2 else None
121
+
122
+ print(f"📦 Packaging skill: {skill_path}")
123
+ if output_dir:
124
+ print(f" Output directory: {output_dir}")
125
+ print()
126
+
127
+ result = package_skill(skill_path, output_dir)
128
+
129
+ if result:
130
+ sys.exit(0)
131
+ else:
132
+ sys.exit(1)
133
+
134
+
135
+ if __name__ == "__main__":
136
+ main()
@@ -1,103 +1,103 @@
1
- #!/usr/bin/env python3
2
- """
3
- Quick validation script for skills - minimal version
4
- """
5
-
6
- import sys
7
- import os
8
- import re
9
- import yaml
10
- from pathlib import Path
11
-
12
- def validate_skill(skill_path):
13
- """Basic validation of a skill"""
14
- skill_path = Path(skill_path)
15
-
16
- # Check SKILL.md exists
17
- skill_md = skill_path / 'SKILL.md'
18
- if not skill_md.exists():
19
- return False, "SKILL.md not found"
20
-
21
- # Read and validate frontmatter
22
- content = skill_md.read_text()
23
- if not content.startswith('---'):
24
- return False, "No YAML frontmatter found"
25
-
26
- # Extract frontmatter
27
- match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
28
- if not match:
29
- return False, "Invalid frontmatter format"
30
-
31
- frontmatter_text = match.group(1)
32
-
33
- # Parse YAML frontmatter
34
- try:
35
- frontmatter = yaml.safe_load(frontmatter_text)
36
- if not isinstance(frontmatter, dict):
37
- return False, "Frontmatter must be a YAML dictionary"
38
- except yaml.YAMLError as e:
39
- return False, f"Invalid YAML in frontmatter: {e}"
40
-
41
- # Define allowed properties
42
- ALLOWED_PROPERTIES = {'name', 'description', 'license', 'allowed-tools', 'metadata', 'compatibility'}
43
-
44
- # Check for unexpected properties (excluding nested keys under metadata)
45
- unexpected_keys = set(frontmatter.keys()) - ALLOWED_PROPERTIES
46
- if unexpected_keys:
47
- return False, (
48
- f"Unexpected key(s) in SKILL.md frontmatter: {', '.join(sorted(unexpected_keys))}. "
49
- f"Allowed properties are: {', '.join(sorted(ALLOWED_PROPERTIES))}"
50
- )
51
-
52
- # Check required fields
53
- if 'name' not in frontmatter:
54
- return False, "Missing 'name' in frontmatter"
55
- if 'description' not in frontmatter:
56
- return False, "Missing 'description' in frontmatter"
57
-
58
- # Extract name for validation
59
- name = frontmatter.get('name', '')
60
- if not isinstance(name, str):
61
- return False, f"Name must be a string, got {type(name).__name__}"
62
- name = name.strip()
63
- if name:
64
- # Check naming convention (kebab-case: lowercase with hyphens)
65
- if not re.match(r'^[a-z0-9-]+$', name):
66
- return False, f"Name '{name}' should be kebab-case (lowercase letters, digits, and hyphens only)"
67
- if name.startswith('-') or name.endswith('-') or '--' in name:
68
- return False, f"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens"
69
- # Check name length (max 64 characters per spec)
70
- if len(name) > 64:
71
- return False, f"Name is too long ({len(name)} characters). Maximum is 64 characters."
72
-
73
- # Extract and validate description
74
- description = frontmatter.get('description', '')
75
- if not isinstance(description, str):
76
- return False, f"Description must be a string, got {type(description).__name__}"
77
- description = description.strip()
78
- if description:
79
- # Check for angle brackets
80
- if '<' in description or '>' in description:
81
- return False, "Description cannot contain angle brackets (< or >)"
82
- # Check description length (max 1024 characters per spec)
83
- if len(description) > 1024:
84
- return False, f"Description is too long ({len(description)} characters). Maximum is 1024 characters."
85
-
86
- # Validate compatibility field if present (optional)
87
- compatibility = frontmatter.get('compatibility', '')
88
- if compatibility:
89
- if not isinstance(compatibility, str):
90
- return False, f"Compatibility must be a string, got {type(compatibility).__name__}"
91
- if len(compatibility) > 500:
92
- return False, f"Compatibility is too long ({len(compatibility)} characters). Maximum is 500 characters."
93
-
94
- return True, "Skill is valid!"
95
-
96
- if __name__ == "__main__":
97
- if len(sys.argv) != 2:
98
- print("Usage: python quick_validate.py <skill_directory>")
99
- sys.exit(1)
100
-
101
- valid, message = validate_skill(sys.argv[1])
102
- print(message)
1
+ #!/usr/bin/env python3
2
+ """
3
+ Quick validation script for skills - minimal version
4
+ """
5
+
6
+ import sys
7
+ import os
8
+ import re
9
+ import yaml
10
+ from pathlib import Path
11
+
12
+ def validate_skill(skill_path):
13
+ """Basic validation of a skill"""
14
+ skill_path = Path(skill_path)
15
+
16
+ # Check SKILL.md exists
17
+ skill_md = skill_path / 'SKILL.md'
18
+ if not skill_md.exists():
19
+ return False, "SKILL.md not found"
20
+
21
+ # Read and validate frontmatter
22
+ content = skill_md.read_text()
23
+ if not content.startswith('---'):
24
+ return False, "No YAML frontmatter found"
25
+
26
+ # Extract frontmatter
27
+ match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
28
+ if not match:
29
+ return False, "Invalid frontmatter format"
30
+
31
+ frontmatter_text = match.group(1)
32
+
33
+ # Parse YAML frontmatter
34
+ try:
35
+ frontmatter = yaml.safe_load(frontmatter_text)
36
+ if not isinstance(frontmatter, dict):
37
+ return False, "Frontmatter must be a YAML dictionary"
38
+ except yaml.YAMLError as e:
39
+ return False, f"Invalid YAML in frontmatter: {e}"
40
+
41
+ # Define allowed properties
42
+ ALLOWED_PROPERTIES = {'name', 'description', 'license', 'allowed-tools', 'metadata', 'compatibility'}
43
+
44
+ # Check for unexpected properties (excluding nested keys under metadata)
45
+ unexpected_keys = set(frontmatter.keys()) - ALLOWED_PROPERTIES
46
+ if unexpected_keys:
47
+ return False, (
48
+ f"Unexpected key(s) in SKILL.md frontmatter: {', '.join(sorted(unexpected_keys))}. "
49
+ f"Allowed properties are: {', '.join(sorted(ALLOWED_PROPERTIES))}"
50
+ )
51
+
52
+ # Check required fields
53
+ if 'name' not in frontmatter:
54
+ return False, "Missing 'name' in frontmatter"
55
+ if 'description' not in frontmatter:
56
+ return False, "Missing 'description' in frontmatter"
57
+
58
+ # Extract name for validation
59
+ name = frontmatter.get('name', '')
60
+ if not isinstance(name, str):
61
+ return False, f"Name must be a string, got {type(name).__name__}"
62
+ name = name.strip()
63
+ if name:
64
+ # Check naming convention (kebab-case: lowercase with hyphens)
65
+ if not re.match(r'^[a-z0-9-]+$', name):
66
+ return False, f"Name '{name}' should be kebab-case (lowercase letters, digits, and hyphens only)"
67
+ if name.startswith('-') or name.endswith('-') or '--' in name:
68
+ return False, f"Name '{name}' cannot start/end with hyphen or contain consecutive hyphens"
69
+ # Check name length (max 64 characters per spec)
70
+ if len(name) > 64:
71
+ return False, f"Name is too long ({len(name)} characters). Maximum is 64 characters."
72
+
73
+ # Extract and validate description
74
+ description = frontmatter.get('description', '')
75
+ if not isinstance(description, str):
76
+ return False, f"Description must be a string, got {type(description).__name__}"
77
+ description = description.strip()
78
+ if description:
79
+ # Check for angle brackets
80
+ if '<' in description or '>' in description:
81
+ return False, "Description cannot contain angle brackets (< or >)"
82
+ # Check description length (max 1024 characters per spec)
83
+ if len(description) > 1024:
84
+ return False, f"Description is too long ({len(description)} characters). Maximum is 1024 characters."
85
+
86
+ # Validate compatibility field if present (optional)
87
+ compatibility = frontmatter.get('compatibility', '')
88
+ if compatibility:
89
+ if not isinstance(compatibility, str):
90
+ return False, f"Compatibility must be a string, got {type(compatibility).__name__}"
91
+ if len(compatibility) > 500:
92
+ return False, f"Compatibility is too long ({len(compatibility)} characters). Maximum is 500 characters."
93
+
94
+ return True, "Skill is valid!"
95
+
96
+ if __name__ == "__main__":
97
+ if len(sys.argv) != 2:
98
+ print("Usage: python quick_validate.py <skill_directory>")
99
+ sys.exit(1)
100
+
101
+ valid, message = validate_skill(sys.argv[1])
102
+ print(message)
103
103
  sys.exit(0 if valid else 1)