contextos-agents 2.0.0 → 2.1.1
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/.agents/AGENTS.md +53 -33
- package/.agents/adapters/aider/export.js +41 -14
- package/.agents/adapters/claude/export.js +54 -3
- package/.agents/adapters/copilot/export.js +1 -1
- package/.agents/adapters/cursor/export.js +1 -1
- package/.agents/adapters/drift-detector.js +86 -10
- package/.agents/adapters/gemini/export.js +1 -1
- package/.agents/adapters/pure-compiler.js +28 -6
- package/.agents/adapters/shared.js +13 -4
- package/.agents/adapters/zed/export.js +1 -1
- package/.agents/compiled/registry.v2.json +29 -25
- package/.agents/compiled/registry.v2.sha256 +1 -1
- package/.agents/core/skills/context-os/SKILL.md +34 -37
- package/.agents/core/skills/engineering-workflow/SKILL.md +24 -24
- package/.agents/core/skills/gemini-precision/EXAMPLES.md +72 -0
- package/.agents/core/skills/gemini-precision/SKILL.md +2 -1
- package/.agents/core/skills/gemini-precision/TROUBLESHOOTING.md +25 -0
- package/.agents/core/skills/gemini-precision/skill.yaml +2 -0
- package/.agents/core/skills/gstack-roles/SKILL.md +7 -6
- package/.agents/core/skills/security/SKILL.md +44 -16
- package/.agents/core/skills/security/skill.yaml +0 -1
- package/.agents/ctx.js +20 -14
- package/.agents/generated/claude/skills/context-os/SKILL.md +34 -37
- package/.agents/generated/claude/skills/engineering-workflow/SKILL.md +24 -24
- package/.agents/generated/claude/skills/gemini-precision/SKILL.md +102 -1
- package/.agents/generated/claude/skills/gstack-roles/SKILL.md +7 -6
- package/.agents/generated/claude/skills/security/SKILL.md +44 -16
- package/.agents/generated/gemini/skills/context-os/SKILL.md +34 -37
- package/.agents/generated/gemini/skills/engineering-workflow/SKILL.md +24 -24
- package/.agents/generated/gemini/skills/gemini-precision/SKILL.md +105 -1
- package/.agents/generated/gemini/skills/gstack-roles/SKILL.md +7 -6
- package/.agents/generated/gemini/skills/security/SKILL.md +44 -125
- package/.agents/plugins.js +105 -8
- package/.agents/profiles.js +32 -11
- package/.agents/resolver/canonical-resolver.js +7 -7
- package/.agents/validate.js +69 -1
- package/README.md +81 -24
- package/bin/commands/hook.js +167 -0
- package/bin/commands/scan.js +77 -0
- package/bin/commands.js +39 -1
- package/bin/index.js +151 -34
- package/bin/lib/gate.js +171 -0
- package/bin/lib/git-snapshot.js +214 -0
- package/bin/lib/scan.js +461 -0
- package/catalog/skills/adapters/EXAMPLES.md +19 -0
- package/catalog/skills/adapters/SKILL.md +101 -0
- package/catalog/skills/adapters/TROUBLESHOOTING.md +7 -0
- package/catalog/skills/adapters/VALIDATION.json +12 -0
- package/catalog/skills/adapters/skill.yaml +13 -0
- package/catalog/skills/api-design/EXAMPLES.md +91 -0
- package/catalog/skills/api-design/SKILL.md +63 -0
- package/catalog/skills/api-design/TROUBLESHOOTING.md +54 -0
- package/catalog/skills/api-design/VALIDATION.json +11 -0
- package/catalog/skills/api-design/skill.yaml +14 -0
- package/catalog/skills/architecture-diagrams/SKILL.md +108 -0
- package/catalog/skills/architecture-diagrams/VALIDATION.json +12 -0
- package/catalog/skills/architecture-diagrams/skill.yaml +9 -0
- package/catalog/skills/brutalist-design/EXAMPLES.md +59 -0
- package/catalog/skills/brutalist-design/SKILL.md +150 -0
- package/catalog/skills/brutalist-design/VALIDATION.json +12 -0
- package/catalog/skills/brutalist-design/skill.yaml +10 -0
- package/catalog/skills/ci-cd/EXAMPLES.md +79 -0
- package/catalog/skills/ci-cd/SKILL.md +69 -0
- package/catalog/skills/ci-cd/TROUBLESHOOTING.md +52 -0
- package/catalog/skills/ci-cd/VALIDATION.json +11 -0
- package/catalog/skills/ci-cd/skill.yaml +13 -0
- package/catalog/skills/database/EXAMPLES.md +74 -0
- package/catalog/skills/database/SKILL.md +101 -0
- package/catalog/skills/database/TROUBLESHOOTING.md +18 -0
- package/catalog/skills/database/VALIDATION.json +11 -0
- package/catalog/skills/database/skill.yaml +14 -0
- package/catalog/skills/ddd/EXAMPLES.md +42 -0
- package/catalog/skills/ddd/SKILL.md +247 -0
- package/catalog/skills/ddd/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/ddd/VALIDATION.json +12 -0
- package/catalog/skills/ddd/skill.yaml +14 -0
- package/catalog/skills/decisions/EXAMPLES.md +35 -0
- package/catalog/skills/decisions/SKILL.md +90 -0
- package/catalog/skills/decisions/TROUBLESHOOTING.md +13 -0
- package/catalog/skills/decisions/VALIDATION.json +12 -0
- package/catalog/skills/decisions/skill.yaml +13 -0
- package/catalog/skills/docker/EXAMPLES.md +56 -0
- package/catalog/skills/docker/SKILL.md +169 -0
- package/catalog/skills/docker/TROUBLESHOOTING.md +18 -0
- package/catalog/skills/docker/VALIDATION.json +11 -0
- package/catalog/skills/docker/skill.yaml +13 -0
- package/catalog/skills/fastapi/EXAMPLES.md +36 -0
- package/catalog/skills/fastapi/SKILL.md +171 -0
- package/catalog/skills/fastapi/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/fastapi/VALIDATION.json +12 -0
- package/catalog/skills/fastapi/skill.yaml +14 -0
- package/catalog/skills/generators/EXAMPLES.md +19 -0
- package/catalog/skills/generators/SKILL.md +110 -0
- package/catalog/skills/generators/TROUBLESHOOTING.md +7 -0
- package/catalog/skills/generators/VALIDATION.json +12 -0
- package/catalog/skills/generators/skill.yaml +22 -0
- package/catalog/skills/generators/templates/API.md +77 -0
- package/catalog/skills/generators/templates/ARCHITECTURE.md +70 -0
- package/catalog/skills/generators/templates/DATABASE.md +42 -0
- package/catalog/skills/generators/templates/DECISION.md +46 -0
- package/catalog/skills/generators/templates/PRD.md +67 -0
- package/catalog/skills/generators/templates/PROJECT_GRAPH.md +56 -0
- package/catalog/skills/generators/templates/ROADMAP.md +51 -0
- package/catalog/skills/generators/templates/TASKS.md +43 -0
- package/catalog/skills/generators/templates/UI.md +73 -0
- package/catalog/skills/graphify/EXAMPLES.md +73 -0
- package/catalog/skills/graphify/SKILL.md +130 -0
- package/catalog/skills/graphify/VALIDATION.json +12 -0
- package/catalog/skills/graphify/skill.yaml +13 -0
- package/catalog/skills/impeccable-design/EXAMPLES.md +26 -0
- package/catalog/skills/impeccable-design/SKILL.md +201 -0
- package/catalog/skills/impeccable-design/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/impeccable-design/VALIDATION.json +12 -0
- package/catalog/skills/impeccable-design/skill.yaml +15 -0
- package/catalog/skills/interview-me/SKILL.md +97 -0
- package/catalog/skills/interview-me/VALIDATION.json +12 -0
- package/catalog/skills/interview-me/skill.yaml +9 -0
- package/catalog/skills/microservices/EXAMPLES.md +38 -0
- package/catalog/skills/microservices/SKILL.md +164 -0
- package/catalog/skills/microservices/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/microservices/VALIDATION.json +12 -0
- package/catalog/skills/microservices/skill.yaml +14 -0
- package/catalog/skills/minimalist-design/EXAMPLES.md +58 -0
- package/catalog/skills/minimalist-design/SKILL.md +113 -0
- package/catalog/skills/minimalist-design/VALIDATION.json +12 -0
- package/catalog/skills/minimalist-design/skill.yaml +10 -0
- package/catalog/skills/nestjs/EXAMPLES.md +40 -0
- package/catalog/skills/nestjs/SKILL.md +139 -0
- package/catalog/skills/nestjs/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/nestjs/VALIDATION.json +12 -0
- package/catalog/skills/nestjs/skill.yaml +14 -0
- package/catalog/skills/nextjs/EXAMPLES.md +40 -0
- package/catalog/skills/nextjs/SKILL.md +163 -0
- package/catalog/skills/nextjs/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/nextjs/VALIDATION.json +12 -0
- package/catalog/skills/nextjs/skill.yaml +14 -0
- package/catalog/skills/node/EXAMPLES.md +80 -0
- package/catalog/skills/node/SKILL.md +128 -0
- package/catalog/skills/node/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/node/VALIDATION.json +12 -0
- package/catalog/skills/node/skill.yaml +14 -0
- package/catalog/skills/performance/EXAMPLES.md +30 -0
- package/catalog/skills/performance/SKILL.md +75 -0
- package/catalog/skills/performance/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/performance/VALIDATION.json +12 -0
- package/catalog/skills/performance/skill.yaml +14 -0
- package/catalog/skills/react/EXAMPLES.md +79 -0
- package/catalog/skills/react/SKILL.md +132 -0
- package/catalog/skills/react/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/react/VALIDATION.json +12 -0
- package/catalog/skills/react/skill.yaml +14 -0
- package/catalog/skills/react-best-practices/SKILL.md +158 -0
- package/catalog/skills/react-best-practices/VALIDATION.json +12 -0
- package/catalog/skills/react-best-practices/skill.yaml +13 -0
- package/catalog/skills/redesign-audit/SKILL.md +117 -0
- package/catalog/skills/redesign-audit/VALIDATION.json +12 -0
- package/catalog/skills/redesign-audit/skill.yaml +9 -0
- package/catalog/skills/security-audit/EXAMPLES.md +79 -0
- package/catalog/skills/security-audit/SKILL.md +91 -0
- package/catalog/skills/security-audit/TROUBLESHOOTING.md +46 -0
- package/catalog/skills/security-audit/VALIDATION.json +11 -0
- package/catalog/skills/security-audit/skill.yaml +14 -0
- package/catalog/skills/soft-design/EXAMPLES.md +51 -0
- package/catalog/skills/soft-design/SKILL.md +108 -0
- package/catalog/skills/soft-design/VALIDATION.json +12 -0
- package/catalog/skills/soft-design/skill.yaml +10 -0
- package/catalog/skills/state-management/EXAMPLES.md +56 -0
- package/catalog/skills/state-management/SKILL.md +168 -0
- package/catalog/skills/state-management/TROUBLESHOOTING.md +18 -0
- package/catalog/skills/state-management/VALIDATION.json +11 -0
- package/catalog/skills/state-management/skill.yaml +14 -0
- package/catalog/skills/subagent-orchestrator/SKILL.md +117 -0
- package/catalog/skills/subagent-orchestrator/VALIDATION.json +12 -0
- package/catalog/skills/subagent-orchestrator/skill.yaml +9 -0
- package/catalog/skills/system-design/EXAMPLES.md +75 -0
- package/catalog/skills/system-design/SKILL.md +419 -0
- package/catalog/skills/system-design/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/system-design/VALIDATION.json +12 -0
- package/catalog/skills/system-design/skill.yaml +14 -0
- package/catalog/skills/terraform/EXAMPLES.md +74 -0
- package/catalog/skills/terraform/SKILL.md +55 -0
- package/catalog/skills/terraform/TROUBLESHOOTING.md +53 -0
- package/catalog/skills/terraform/VALIDATION.json +11 -0
- package/catalog/skills/terraform/skill.yaml +14 -0
- package/catalog/skills/testing/EXAMPLES.md +122 -0
- package/catalog/skills/testing/SKILL.md +70 -0
- package/catalog/skills/testing/TROUBLESHOOTING.md +18 -0
- package/catalog/skills/testing/VALIDATION.json +11 -0
- package/catalog/skills/testing/skill.yaml +14 -0
- package/catalog/skills/typescript/EXAMPLES.md +64 -0
- package/catalog/skills/typescript/SKILL.md +112 -0
- package/catalog/skills/typescript/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/typescript/VALIDATION.json +12 -0
- package/catalog/skills/typescript/skill.yaml +14 -0
- package/catalog/skills/ui-design/EXAMPLES.md +21 -0
- package/catalog/skills/ui-design/SKILL.md +124 -0
- package/catalog/skills/ui-design/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/ui-design/VALIDATION.json +12 -0
- package/catalog/skills/ui-design/skill.yaml +16 -0
- package/catalog/skills/ui-ux-pro/EXAMPLES.md +62 -0
- package/catalog/skills/ui-ux-pro/SKILL.md +418 -0
- package/catalog/skills/ui-ux-pro/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/ui-ux-pro/VALIDATION.json +12 -0
- package/catalog/skills/ui-ux-pro/skill.yaml +14 -0
- package/catalog/skills/ux-design/EXAMPLES.md +36 -0
- package/catalog/skills/ux-design/SKILL.md +116 -0
- package/catalog/skills/ux-design/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/ux-design/VALIDATION.json +12 -0
- package/catalog/skills/ux-design/skill.yaml +16 -0
- package/catalog/skills/vercel-optimize/SKILL.md +83 -0
- package/catalog/skills/vercel-optimize/VALIDATION.json +12 -0
- package/catalog/skills/vercel-optimize/scripts/collect-signals.mjs +131 -0
- package/catalog/skills/vercel-optimize/scripts/gate-investigations.mjs +142 -0
- package/catalog/skills/vercel-optimize/scripts/merge-signals.mjs +143 -0
- package/catalog/skills/vercel-optimize/scripts/scan-codebase.mjs +174 -0
- package/catalog/skills/vercel-optimize/skill.yaml +15 -0
- package/catalog/skills/web-accessibility/EXAMPLES.md +39 -0
- package/catalog/skills/web-accessibility/SKILL.md +151 -0
- package/catalog/skills/web-accessibility/TROUBLESHOOTING.md +19 -0
- package/catalog/skills/web-accessibility/VALIDATION.json +12 -0
- package/catalog/skills/web-accessibility/skill.yaml +14 -0
- package/package.json +5 -2
- package/.agents/core/skills/security/security.md +0 -106
|
@@ -30,7 +30,7 @@ Activate whenever writing authentication, authorization, session management, dat
|
|
|
30
30
|
|
|
31
31
|
#### 1. Injection (SQL, NoSQL, Command)
|
|
32
32
|
|
|
33
|
-
- Always use parameterized queries
|
|
33
|
+
- Always use parameterized queries - never concatenate user input into SQL or shell commands.
|
|
34
34
|
- Use ORMs (Prisma, Drizzle, SQLAlchemy) with strict schema validation.
|
|
35
35
|
- Validate and sanitize all user input before processing.
|
|
36
36
|
|
|
@@ -79,14 +79,20 @@ When building AI workflows, tools, or MCP servers:
|
|
|
79
79
|
- Never allow untrusted content to override system instructions or tool execution permissions.
|
|
80
80
|
2. **Tool Execution Boundaries**:
|
|
81
81
|
- Destructive operations (database drops, file deletions, payment triggers) MUST require explicit user confirmation.
|
|
82
|
-
- Restrict file system tools to the workspace root
|
|
82
|
+
- Restrict file system tools to the workspace root - block directory traversal (`../`).
|
|
83
83
|
3. **Secret Masking & Output Sanitization**:
|
|
84
84
|
- Scrub API keys (`sk-...`, `Bearer ...`), tokens, and credentials before writing to agent logs or step summaries.
|
|
85
|
+
4. **Sandbox Execution & Write Isolation (Supply-Chain Defense)**:
|
|
86
|
+
- Target code is inspected strictly read-only; never execute target-controlled builds or tests with write access to the repository root.
|
|
87
|
+
- Restrict process write boundaries strictly to an isolated temporary `scratch/` directory.
|
|
88
|
+
- Enforce zero outbound external network access during security audits to prevent secret exfiltration via malicious scripts or dependencies.
|
|
89
|
+
- Promote verified non-secret results to retained `artifacts/` only via trusted parent-side inspection code.
|
|
85
90
|
|
|
86
91
|
---
|
|
87
92
|
|
|
88
93
|
## Code Examples
|
|
89
94
|
|
|
95
|
+
|
|
90
96
|
### Timing-Safe Secret Verification
|
|
91
97
|
|
|
92
98
|
```javascript
|
|
@@ -102,28 +108,50 @@ export function verifyWebhookSignature(payload, signature, secret) {
|
|
|
102
108
|
}
|
|
103
109
|
```
|
|
104
110
|
|
|
105
|
-
###
|
|
111
|
+
### SSRF Prevention Requirements (OWASP Compliant)
|
|
106
112
|
|
|
107
|
-
|
|
108
|
-
|
|
113
|
+
Per [OWASP SSRF Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html), naive application-level DNS pre-checks followed by standard `fetch(url)` are fundamentally flawed due to DNS rebinding (TOCTOU) and unvalidated HTTP 3xx redirects.
|
|
114
|
+
|
|
115
|
+
#### Mandatory Architectural Controls
|
|
116
|
+
|
|
117
|
+
1. **Network-Layer Defense (Primary)**: For user-supplied arbitrary webhooks or URLs, route all outbound traffic through an isolated egress forward proxy (e.g., Smokescreen, Envoy, Squid) configured with firewall-level IP filters blocking RFC 1918, RFC 6598, link-local (`169.254.169.254`), loopback, and IPv6 local addresses at the socket handshake level.
|
|
118
|
+
2. **Positive Destination Allowlist**: If fetching from known external partners, validate destination hostname against a strict positive allowlist.
|
|
119
|
+
3. **Disable Automatic Redirects**: Always set `redirect: 'error'` or `'manual'`. Never follow HTTP redirects automatically without re-validating the target URL against allowlist rules.
|
|
120
|
+
4. **Protocol & Credential Restrictions**: Enforce `https:` exclusively; reject embedded credentials (`user:pass@host`) and non-standard ports.
|
|
109
121
|
|
|
110
|
-
|
|
122
|
+
```typescript
|
|
123
|
+
/**
|
|
124
|
+
* Verified Allowlist-based HTTP Client (OWASP SSRF Prevention)
|
|
125
|
+
* Enforces HTTPS, strict destination allowlist, and rejects HTTP redirects.
|
|
126
|
+
*/
|
|
127
|
+
export async function fetchFromAllowlist(
|
|
128
|
+
urlString: string,
|
|
129
|
+
allowedHostnames: ReadonlySet<string>,
|
|
130
|
+
options: RequestInit = {}
|
|
131
|
+
): Promise<Response> {
|
|
111
132
|
const parsed = new URL(urlString);
|
|
133
|
+
|
|
134
|
+
// 1. Enforce HTTPS only
|
|
112
135
|
if (parsed.protocol !== 'https:') {
|
|
113
|
-
throw new Error(
|
|
136
|
+
throw new Error(`SSRF blocked: protocol "${parsed.protocol}" is not permitted; HTTPS required`);
|
|
114
137
|
}
|
|
115
138
|
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
address.startsWith('10.') ||
|
|
120
|
-
address.startsWith('192.168.') ||
|
|
121
|
-
address === '169.254.169.254'
|
|
122
|
-
) {
|
|
123
|
-
throw new Error('Access to private/metadata IP addresses is blocked');
|
|
139
|
+
// 2. Reject credentials in URL
|
|
140
|
+
if (parsed.username || parsed.password) {
|
|
141
|
+
throw new Error('SSRF blocked: URL credentials (user:password@host) are prohibited');
|
|
124
142
|
}
|
|
125
143
|
|
|
126
|
-
|
|
144
|
+
// 3. Strict positive destination allowlist (prevents internal network probing)
|
|
145
|
+
const normalizedHost = parsed.hostname.toLowerCase();
|
|
146
|
+
if (!allowedHostnames.has(normalizedHost)) {
|
|
147
|
+
throw new Error(`SSRF blocked: destination host "${normalizedHost}" is not in the approved allowlist`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 4. Disable automatic redirects to prevent redirection to private IPs or metadata endpoints
|
|
151
|
+
return fetch(urlString, {
|
|
152
|
+
...options,
|
|
153
|
+
redirect: 'error'
|
|
154
|
+
});
|
|
127
155
|
}
|
|
128
156
|
```
|
|
129
157
|
|
|
@@ -156,115 +184,6 @@ export async function validateSafeUrl(urlString: string): Promise<URL> {
|
|
|
156
184
|
- Pairs with `system-design` to mandate secure network boundaries and authorization layers.
|
|
157
185
|
|
|
158
186
|
|
|
159
|
-
<!-- Source: security.md -->
|
|
160
|
-
|
|
161
|
-
# Application Security — Best Practices
|
|
162
|
-
|
|
163
|
-
## OWASP Top 10
|
|
164
|
-
|
|
165
|
-
### 1. Injection (SQL, NoSQL, Command)
|
|
166
|
-
|
|
167
|
-
- **Always use parameterized queries** — never concatenate user input into SQL
|
|
168
|
-
- Use ORM (Prisma, SQLAlchemy, TypeORM) — they parameterize by default
|
|
169
|
-
- Validate and sanitize all user input
|
|
170
|
-
|
|
171
|
-
### 2. Broken Authentication
|
|
172
|
-
|
|
173
|
-
- Use bcrypt/argon2 for password hashing (cost factor ≥ 12)
|
|
174
|
-
- JWT: short-lived access tokens (15min), refresh tokens (7 days)
|
|
175
|
-
- Rate limit login attempts
|
|
176
|
-
- Implement account lockout after N failed attempts
|
|
177
|
-
- MFA for sensitive operations
|
|
178
|
-
|
|
179
|
-
### 3. Sensitive Data Exposure
|
|
180
|
-
|
|
181
|
-
- HTTPS everywhere — redirect HTTP to HTTPS
|
|
182
|
-
- Encrypt sensitive data at rest (AES-256)
|
|
183
|
-
- Never log passwords, tokens, or PII
|
|
184
|
-
- Use environment variables for secrets
|
|
185
|
-
|
|
186
|
-
### 4. XML/XXE
|
|
187
|
-
|
|
188
|
-
- Disable external entity processing
|
|
189
|
-
- Use JSON instead of XML where possible
|
|
190
|
-
|
|
191
|
-
### 5. Broken Access Control
|
|
192
|
-
|
|
193
|
-
- Default deny — explicitly grant access
|
|
194
|
-
- RBAC (Role-Based Access Control) or ABAC (Attribute-Based)
|
|
195
|
-
- Check authorization on every request, not just UI
|
|
196
|
-
- Don't rely on client-side validation for security
|
|
197
|
-
|
|
198
|
-
### 6. Security Misconfiguration
|
|
199
|
-
|
|
200
|
-
- Remove default credentials
|
|
201
|
-
- Disable debug mode in production
|
|
202
|
-
- Security headers (see below)
|
|
203
|
-
- Keep dependencies updated
|
|
204
|
-
|
|
205
|
-
### 7. XSS (Cross-Site Scripting)
|
|
206
|
-
|
|
207
|
-
- Escape all output by default
|
|
208
|
-
- Content-Security-Policy header
|
|
209
|
-
- HttpOnly + Secure + SameSite cookies
|
|
210
|
-
- Use framework's built-in XSS protection
|
|
211
|
-
|
|
212
|
-
### 8. Insecure Deserialization
|
|
213
|
-
|
|
214
|
-
- Validate and schema-check all input (Zod, Pydantic, class-validator)
|
|
215
|
-
- Don't deserialize untrusted data
|
|
216
|
-
|
|
217
|
-
### 9. Insufficient Logging
|
|
218
|
-
|
|
219
|
-
- Log all authentication events
|
|
220
|
-
- Log authorization failures
|
|
221
|
-
- Log input validation failures
|
|
222
|
-
- Include request ID for tracing
|
|
223
|
-
|
|
224
|
-
### 10. SSRF (Server-Side Request Forgery)
|
|
225
|
-
|
|
226
|
-
- Validate and allowlist URLs
|
|
227
|
-
- Don't let users control server-side HTTP requests
|
|
228
|
-
|
|
229
|
-
## Security Headers
|
|
230
|
-
|
|
231
|
-
```
|
|
232
|
-
Content-Security-Policy: default-src 'self'
|
|
233
|
-
X-Content-Type-Options: nosniff
|
|
234
|
-
X-Frame-Options: DENY
|
|
235
|
-
Strict-Transport-Security: max-age=31536000; includeSubDomains
|
|
236
|
-
Referrer-Policy: strict-origin-when-cross-origin
|
|
237
|
-
Permissions-Policy: camera=(), microphone=(), geolocation=()
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
## Authentication Patterns
|
|
241
|
-
|
|
242
|
-
### JWT Flow
|
|
243
|
-
|
|
244
|
-
```
|
|
245
|
-
Login → Access Token (15min) + Refresh Token (7d, HttpOnly cookie)
|
|
246
|
-
Request → Authorization: Bearer <access_token>
|
|
247
|
-
Expired → POST /auth/refresh (sends refresh cookie) → new access token
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
### OAuth2 Flow
|
|
251
|
-
|
|
252
|
-
```
|
|
253
|
-
Redirect → Provider (Google, GitHub) → Callback → Create/link user → JWT
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
## Checklist Before Deploy
|
|
257
|
-
|
|
258
|
-
- [ ] All secrets in environment variables
|
|
259
|
-
- [ ] HTTPS enabled
|
|
260
|
-
- [ ] Security headers configured
|
|
261
|
-
- [ ] Input validation on all endpoints
|
|
262
|
-
- [ ] Rate limiting enabled
|
|
263
|
-
- [ ] CORS configured (not `*`)
|
|
264
|
-
- [ ] Error messages don't leak internals
|
|
265
|
-
- [ ] Dependency audit (`npm audit`, `pip audit`)
|
|
266
|
-
- [ ] Logging for security events
|
|
267
|
-
|
|
268
187
|
<!-- Source: EXAMPLES.md -->
|
|
269
188
|
|
|
270
189
|
# Application Security Examples — Anti-patterns vs ContextOS Standard
|
package/.agents/plugins.js
CHANGED
|
@@ -26,7 +26,7 @@ const https = require('https');
|
|
|
26
26
|
const crypto = require('crypto');
|
|
27
27
|
const { execFileSync } = require('child_process');
|
|
28
28
|
const { ProjectMutationLock } = require('./filesystem/project-lock.js');
|
|
29
|
-
const JournaledTransaction = require('./filesystem/journaled-transaction.js');
|
|
29
|
+
const { JournaledTransaction } = require('./filesystem/journaled-transaction.js');
|
|
30
30
|
const {
|
|
31
31
|
calculateTreeDigest,
|
|
32
32
|
validateArchiveEntry,
|
|
@@ -284,11 +284,49 @@ function isValidNpmPackage(name) {
|
|
|
284
284
|
return /^(?:@[a-z0-9_.-]+\/)?[a-z0-9_.-]+$/.test(name);
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
-
function parseRef(ref) {
|
|
287
|
+
function parseRef(ref, projectRoot = null) {
|
|
288
288
|
if (typeof ref !== 'string' || !ref.trim()) {
|
|
289
289
|
throw new Error('A plugin reference is required');
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
// 1. Built-in package catalog skills take strict precedence for catalog names.
|
|
293
|
+
// Consumer project local files can never spoof or hijack package catalog skills.
|
|
294
|
+
const pkgCatalogPath = path.join(path.resolve(__dirname, '..'), 'catalog', 'skills', ref);
|
|
295
|
+
if (fs.existsSync(pkgCatalogPath) && fs.existsSync(path.join(pkgCatalogPath, 'SKILL.md'))) {
|
|
296
|
+
return {
|
|
297
|
+
type: 'catalog',
|
|
298
|
+
name: ref,
|
|
299
|
+
path: pkgCatalogPath,
|
|
300
|
+
isPinned: true,
|
|
301
|
+
raw: ref,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// 2. Explicit local references (must start with 'local:', './', '../', or 'file:')
|
|
306
|
+
if (ref.startsWith('local:') || ref.startsWith('./') || ref.startsWith('../') || ref.startsWith('file:')) {
|
|
307
|
+
const rawTarget = ref.startsWith('local:')
|
|
308
|
+
? ref.slice(6)
|
|
309
|
+
: (ref.startsWith('file:') ? ref.slice(5) : ref);
|
|
310
|
+
|
|
311
|
+
const baseRoot = projectRoot || process.cwd();
|
|
312
|
+
let localDir = path.resolve(baseRoot, rawTarget);
|
|
313
|
+
if (!fs.existsSync(localDir)) {
|
|
314
|
+
localDir = path.join(baseRoot, 'catalog', 'skills', rawTarget);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (fs.existsSync(localDir) && fs.existsSync(path.join(localDir, 'SKILL.md'))) {
|
|
318
|
+
const skillName = path.basename(localDir);
|
|
319
|
+
return {
|
|
320
|
+
type: 'catalog',
|
|
321
|
+
name: skillName,
|
|
322
|
+
path: localDir,
|
|
323
|
+
isPinned: true,
|
|
324
|
+
raw: ref,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
throw new Error(`Local skill not found at: '${ref}'`);
|
|
328
|
+
}
|
|
329
|
+
|
|
292
330
|
// Scoped packages contain a slash but are npm packages, not GitHub refs.
|
|
293
331
|
if (ref.startsWith('@')) {
|
|
294
332
|
if (!isValidNpmPackage(ref)) {
|
|
@@ -529,6 +567,62 @@ function installFromNpm(descriptor, skillName, dryRun, checksum, forceUnsafe = f
|
|
|
529
567
|
return sha256;
|
|
530
568
|
}
|
|
531
569
|
|
|
570
|
+
// ── Catalog installer ─────────────────────────────────────────────────────────
|
|
571
|
+
function installFromCatalog(descriptor, skillName, dryRun, checksum, forceUnsafe = false, tx = null) {
|
|
572
|
+
const sourceDir = descriptor.path;
|
|
573
|
+
const targetDir = path.join(PLUGINS_DIR, skillName);
|
|
574
|
+
const relTargetDir = toPosix(path.relative(ROOT, targetDir));
|
|
575
|
+
|
|
576
|
+
if (dryRun) {
|
|
577
|
+
console.log(c.dim(` [DRY-RUN] Would copy catalog skill from: ${sourceDir}`));
|
|
578
|
+
return 'sha256:0000000000000000000000000000000000000000000000000000000000000000';
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function copyRecursive(src, dest, relDest) {
|
|
582
|
+
if (!fs.existsSync(dest)) {
|
|
583
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
584
|
+
}
|
|
585
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
586
|
+
for (const entry of entries) {
|
|
587
|
+
const srcPath = path.join(src, entry.name);
|
|
588
|
+
const destPath = path.join(dest, entry.name);
|
|
589
|
+
const relPath = toPosix(path.join(relDest, entry.name));
|
|
590
|
+
|
|
591
|
+
if (entry.isDirectory()) {
|
|
592
|
+
copyRecursive(srcPath, destPath, relPath);
|
|
593
|
+
} else {
|
|
594
|
+
const content = fs.readFileSync(srcPath, 'utf8');
|
|
595
|
+
if (tx) {
|
|
596
|
+
tx.stageWrite(relPath, content);
|
|
597
|
+
} else {
|
|
598
|
+
fs.writeFileSync(destPath, content, 'utf8');
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
copyRecursive(sourceDir, targetDir, relTargetDir);
|
|
605
|
+
|
|
606
|
+
const primarySkillMd = path.join(sourceDir, 'SKILL.md');
|
|
607
|
+
const sha256 = fs.existsSync(primarySkillMd) ? crypto.createHash('sha256').update(fs.readFileSync(primarySkillMd)).digest('hex') : null;
|
|
608
|
+
|
|
609
|
+
const sourceMeta = {
|
|
610
|
+
source: 'catalog',
|
|
611
|
+
name: skillName,
|
|
612
|
+
path: toPosix(path.relative(ROOT, sourceDir)),
|
|
613
|
+
sha256,
|
|
614
|
+
installedAt: new Date().toISOString(),
|
|
615
|
+
};
|
|
616
|
+
if (tx) {
|
|
617
|
+
tx.stageWrite(`${relTargetDir}/.source`, JSON.stringify(sourceMeta, null, 2) + '\n');
|
|
618
|
+
} else {
|
|
619
|
+
fs.writeFileSync(path.join(targetDir, '.source'), JSON.stringify(sourceMeta, null, 2) + '\n');
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
console.log(c.green(` ✓ Installed from built-in catalog: ${skillName}`));
|
|
623
|
+
return sha256;
|
|
624
|
+
}
|
|
625
|
+
|
|
532
626
|
// ── Derive skill name from ref ────────────────────────────────────────────────
|
|
533
627
|
function deriveSkillName(ref) {
|
|
534
628
|
// username/repo/path/to/my-skill → my-skill
|
|
@@ -570,7 +664,7 @@ async function add(ref, options = {}) {
|
|
|
570
664
|
}
|
|
571
665
|
|
|
572
666
|
// Supply-chain source pinning check (Wave 6)
|
|
573
|
-
if (!dryRun) {
|
|
667
|
+
if (!dryRun && descriptor.type !== 'catalog') {
|
|
574
668
|
const pinning = validatePluginPinning(
|
|
575
669
|
{
|
|
576
670
|
type: descriptor.type,
|
|
@@ -637,7 +731,9 @@ async function add(ref, options = {}) {
|
|
|
637
731
|
lockToken = projectLock.acquire({ command: 'skill:add' });
|
|
638
732
|
|
|
639
733
|
let installedSha256 = null;
|
|
640
|
-
if (descriptor.type === '
|
|
734
|
+
if (descriptor.type === 'catalog') {
|
|
735
|
+
installedSha256 = installFromCatalog(descriptor, skillName, dryRun, checksum, forceUnsafe, tx);
|
|
736
|
+
} else if (descriptor.type === 'github') {
|
|
641
737
|
installedSha256 = await installFromGitHub(descriptor, skillName, dryRun, checksum, forceUnsafe, tx);
|
|
642
738
|
} else {
|
|
643
739
|
installedSha256 = installFromNpm(descriptor, skillName, dryRun, checksum, forceUnsafe, tx);
|
|
@@ -901,17 +997,18 @@ async function search(query) {
|
|
|
901
997
|
* Adapters call this instead of reading CORE_SKILLS directly.
|
|
902
998
|
*/
|
|
903
999
|
function collectAllSkillDirs(targetRoot) {
|
|
1000
|
+
const isTargetExplicit = Boolean(targetRoot);
|
|
904
1001
|
const root = targetRoot || process.cwd();
|
|
905
1002
|
const agentsDir = path.join(root, '.agents');
|
|
906
1003
|
const localCore = path.join(agentsDir, 'core', 'skills');
|
|
907
1004
|
const localPlugins = path.join(agentsDir, 'plugins');
|
|
908
1005
|
|
|
909
|
-
const coreDir = fs.existsSync(localCore) ? localCore : CORE_SKILLS;
|
|
910
|
-
const pluginsDir = fs.existsSync(localPlugins) ? localPlugins : PLUGINS_DIR;
|
|
1006
|
+
const coreDir = fs.existsSync(localCore) ? localCore : (isTargetExplicit ? null : CORE_SKILLS);
|
|
1007
|
+
const pluginsDir = fs.existsSync(localPlugins) ? localPlugins : (isTargetExplicit ? null : PLUGINS_DIR);
|
|
911
1008
|
const dirs = [];
|
|
912
1009
|
|
|
913
1010
|
// Core skills
|
|
914
|
-
if (fs.existsSync(coreDir)) {
|
|
1011
|
+
if (coreDir && fs.existsSync(coreDir)) {
|
|
915
1012
|
for (const name of fs.readdirSync(coreDir)) {
|
|
916
1013
|
const d = path.join(coreDir, name);
|
|
917
1014
|
if (fs.statSync(d).isDirectory()) dirs.push(d);
|
|
@@ -919,7 +1016,7 @@ function collectAllSkillDirs(targetRoot) {
|
|
|
919
1016
|
}
|
|
920
1017
|
|
|
921
1018
|
// Plugin skills (supports both standalone skill dirs and plugin bundles with skills/)
|
|
922
|
-
if (fs.existsSync(pluginsDir)) {
|
|
1019
|
+
if (pluginsDir && fs.existsSync(pluginsDir)) {
|
|
923
1020
|
for (const name of fs.readdirSync(pluginsDir)) {
|
|
924
1021
|
const d = path.join(pluginsDir, name);
|
|
925
1022
|
if (!fs.statSync(d).isDirectory()) continue;
|
package/.agents/profiles.js
CHANGED
|
@@ -298,14 +298,26 @@ function validateProfile(profile, registry = null) {
|
|
|
298
298
|
}
|
|
299
299
|
|
|
300
300
|
/**
|
|
301
|
-
* Lists all available project profiles defined in core/profiles.
|
|
301
|
+
* Lists all available project profiles defined in core/profiles and optionally consumer project.
|
|
302
|
+
* Searches built-in profiles in packageRoot, and if projectRoot is supplied, searches
|
|
303
|
+
* consumer project .agents/profiles, .agents/core/profiles, and profiles directories.
|
|
302
304
|
*
|
|
305
|
+
* @param {string|null} [projectRoot=null] - Optional consumer project root
|
|
303
306
|
* @returns {ProfileConfig[]} Array of profile configurations
|
|
304
307
|
*/
|
|
305
|
-
function listProfiles() {
|
|
308
|
+
function listProfiles(projectRoot = null) {
|
|
306
309
|
const profiles = [];
|
|
307
310
|
const searchDirs = [PROFILES_DIR, path.join(AGENTS_DIR, 'catalog', 'presets')];
|
|
308
311
|
|
|
312
|
+
if (projectRoot) {
|
|
313
|
+
const absConsumer = path.resolve(projectRoot);
|
|
314
|
+
searchDirs.push(
|
|
315
|
+
path.join(absConsumer, '.agents', 'profiles'),
|
|
316
|
+
path.join(absConsumer, '.agents', 'core', 'profiles'),
|
|
317
|
+
path.join(absConsumer, 'profiles')
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
|
|
309
321
|
for (const dir of searchDirs) {
|
|
310
322
|
if (!fs.existsSync(dir)) continue;
|
|
311
323
|
const files = fs.readdirSync(dir).filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
|
|
@@ -314,22 +326,31 @@ function listProfiles() {
|
|
|
314
326
|
const parsed = parseYamlProfile(content);
|
|
315
327
|
if (!parsed.id) parsed.id = path.basename(file, path.extname(file));
|
|
316
328
|
if (!parsed.name) parsed.name = parsed.id;
|
|
317
|
-
|
|
329
|
+
|
|
330
|
+
// Consumer profiles can override or extend built-in profiles; deduplicate by id
|
|
331
|
+
const normId = parsed.id.toLowerCase();
|
|
332
|
+
const existingIdx = profiles.findIndex(p => p.id.toLowerCase() === normId);
|
|
333
|
+
if (existingIdx !== -1) {
|
|
334
|
+
profiles[existingIdx] = parsed;
|
|
335
|
+
} else {
|
|
336
|
+
profiles.push(parsed);
|
|
337
|
+
}
|
|
318
338
|
}
|
|
319
339
|
}
|
|
320
340
|
return profiles;
|
|
321
341
|
}
|
|
322
342
|
|
|
323
343
|
/**
|
|
324
|
-
* Finds a profile by its slug ID or title.
|
|
344
|
+
* Finds a profile by its slug ID or title across package and consumer roots.
|
|
325
345
|
*
|
|
326
346
|
* @param {string} name - Profile identifier or name
|
|
347
|
+
* @param {string|null} [projectRoot=null] - Optional consumer project root
|
|
327
348
|
* @returns {ProfileConfig|null} Found profile or null
|
|
328
349
|
*/
|
|
329
|
-
function getProfile(name) {
|
|
350
|
+
function getProfile(name, projectRoot = null) {
|
|
330
351
|
if (!name) return null;
|
|
331
352
|
const clean = name.toLowerCase().trim();
|
|
332
|
-
const all = listProfiles();
|
|
353
|
+
const all = listProfiles(projectRoot);
|
|
333
354
|
return all.find(p => p.id.toLowerCase() === clean || p.name.toLowerCase() === clean) || null;
|
|
334
355
|
}
|
|
335
356
|
|
|
@@ -398,7 +419,7 @@ function getActiveProfile(projectDir = process.cwd(), targetFileOrScope = null)
|
|
|
398
419
|
}
|
|
399
420
|
|
|
400
421
|
if (matchedProfileName) {
|
|
401
|
-
const overrideProf = getProfile(matchedProfileName);
|
|
422
|
+
const overrideProf = getProfile(matchedProfileName, projectDir);
|
|
402
423
|
if (overrideProf) {
|
|
403
424
|
return {
|
|
404
425
|
...overrideProf,
|
|
@@ -424,9 +445,9 @@ function getActiveProfile(projectDir = process.cwd(), targetFileOrScope = null)
|
|
|
424
445
|
* @returns {Object} Updated profile lock data
|
|
425
446
|
*/
|
|
426
447
|
function applyProfile(profileName, projectDir = process.cwd(), options = {}) {
|
|
427
|
-
const profile = getProfile(profileName);
|
|
448
|
+
const profile = getProfile(profileName, projectDir);
|
|
428
449
|
if (!profile) {
|
|
429
|
-
throw new Error(`Profile '${profileName}' not found. Available profiles: ${listProfiles().map(p => p.id).join(', ')}`);
|
|
450
|
+
throw new Error(`Profile '${profileName}' not found. Available profiles: ${listProfiles(projectDir).map(p => p.id).join(', ')}`);
|
|
430
451
|
}
|
|
431
452
|
|
|
432
453
|
validateProfile(profile);
|
|
@@ -563,9 +584,9 @@ function removeActiveProfile(projectDir = process.cwd(), options = {}) {
|
|
|
563
584
|
* @returns {Object} Explanation structure
|
|
564
585
|
*/
|
|
565
586
|
function explainProfile(profileName, projectDir = process.cwd()) {
|
|
566
|
-
const profile = getProfile(profileName);
|
|
587
|
+
const profile = getProfile(profileName, projectDir);
|
|
567
588
|
if (!profile) {
|
|
568
|
-
throw new Error(`Profile '${profileName}' not found. Available profiles: ${listProfiles().map(p => p.id).join(', ')}`);
|
|
589
|
+
throw new Error(`Profile '${profileName}' not found. Available profiles: ${listProfiles(projectDir).map(p => p.id).join(', ')}`);
|
|
569
590
|
}
|
|
570
591
|
|
|
571
592
|
return {
|
|
@@ -23,15 +23,15 @@ const { WorkspaceGraphBuilder } = require('../workspace/workspace-graph');
|
|
|
23
23
|
// Prompt budget limits (Section 14.4)
|
|
24
24
|
const BUDGET_TIERS = {
|
|
25
25
|
BOOTSTRAP: 1200, // always-on bootstrap budget
|
|
26
|
-
SKILL_SUMMARY:
|
|
27
|
-
SKILL_BODY:
|
|
28
|
-
ROUTINE:
|
|
29
|
-
STANDARD:
|
|
30
|
-
HIGH:
|
|
31
|
-
DESTRUCTIVE:
|
|
26
|
+
SKILL_SUMMARY: 300, // single skill summary limit
|
|
27
|
+
SKILL_BODY: 10000, // single skill body limit (supports rich code examples and rules)
|
|
28
|
+
ROUTINE: 16000, // routine context limit
|
|
29
|
+
STANDARD: 32000, // standard normal compiled context limit (supports 8-10 full skills)
|
|
30
|
+
HIGH: 64000, // high-risk compiled context limit
|
|
31
|
+
DESTRUCTIVE: 128000, // destructive compiled context limit
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
const DEFAULT_CONTEXT_BUDGET_TOKENS =
|
|
34
|
+
const DEFAULT_CONTEXT_BUDGET_TOKENS = 64000;
|
|
35
35
|
|
|
36
36
|
// Risk-based workflows (Section 14.3)
|
|
37
37
|
const WORKFLOW_TEMPLATES = {
|
package/.agents/validate.js
CHANGED
|
@@ -641,10 +641,74 @@ function checkRegistryV2() {
|
|
|
641
641
|
}
|
|
642
642
|
}
|
|
643
643
|
|
|
644
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
645
|
+
// CHECK 13 — Catalog Skills Validation (Optional, gated by --catalog)
|
|
646
|
+
// ═════════════════════════════════════════════════════════════════════════════
|
|
647
|
+
function checkCatalogSkills() {
|
|
648
|
+
const catalogSkillsDir = path.join(ROOT, 'catalog', 'skills');
|
|
649
|
+
if (!fs.existsSync(catalogSkillsDir)) {
|
|
650
|
+
error(`[catalog] Catalog skills directory not found: ${catalogSkillsDir}`);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
let validated = 0;
|
|
655
|
+
const entries = fs.readdirSync(catalogSkillsDir);
|
|
656
|
+
|
|
657
|
+
for (const name of entries) {
|
|
658
|
+
const dir = path.join(catalogSkillsDir, name);
|
|
659
|
+
if (!fs.statSync(dir).isDirectory()) continue;
|
|
660
|
+
|
|
661
|
+
const skillMd = path.join(dir, 'SKILL.md');
|
|
662
|
+
const yaml = path.join(dir, 'skill.yaml');
|
|
663
|
+
const validationJson = path.join(dir, 'VALIDATION.json');
|
|
664
|
+
|
|
665
|
+
if (!fs.existsSync(skillMd)) {
|
|
666
|
+
error(`[catalog] ${name}: missing SKILL.md`);
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const content = fs.readFileSync(skillMd, 'utf8');
|
|
671
|
+
const fm = parseFrontmatter(content);
|
|
672
|
+
if (!fm || fm.malformed) {
|
|
673
|
+
error(`[catalog] ${name}: malformed YAML frontmatter in SKILL.md`);
|
|
674
|
+
} else {
|
|
675
|
+
if (!yamlField(fm.raw, 'name')) {
|
|
676
|
+
error(`[catalog] ${name}: missing 'name' in SKILL.md frontmatter`);
|
|
677
|
+
}
|
|
678
|
+
if (!yamlField(fm.raw, 'description')) {
|
|
679
|
+
error(`[catalog] ${name}: missing 'description' in SKILL.md frontmatter`);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
if (!fs.existsSync(yaml)) {
|
|
684
|
+
error(`[catalog] ${name}: missing skill.yaml`);
|
|
685
|
+
} else {
|
|
686
|
+
const yamlText = fs.readFileSync(yaml, 'utf8');
|
|
687
|
+
if (!yamlField(yamlText, 'name') && !yamlField(yamlText, 'id')) {
|
|
688
|
+
error(`[catalog] ${name}: missing 'name' or 'id' in skill.yaml`);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (fs.existsSync(validationJson)) {
|
|
693
|
+
try {
|
|
694
|
+
JSON.parse(fs.readFileSync(validationJson, 'utf8'));
|
|
695
|
+
} catch (err) {
|
|
696
|
+
error(`[catalog] ${name}: invalid JSON in VALIDATION.json: ${err.message}`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
validated++;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
info(`[catalog] ${validated} catalog skills validated (frontmatter, skill.yaml, validation metadata)`);
|
|
704
|
+
}
|
|
705
|
+
|
|
644
706
|
// ═════════════════════════════════════════════════════════════════════════════
|
|
645
707
|
// MAIN
|
|
646
708
|
// ═════════════════════════════════════════════════════════════════════════════
|
|
647
|
-
function run() {
|
|
709
|
+
function run(options = {}) {
|
|
710
|
+
const checkCatalog = Boolean(options.checkCatalog || process.argv.includes('--catalog'));
|
|
711
|
+
|
|
648
712
|
console.log(c.cyan('\nContextOS Validator — scanning skills...\n'));
|
|
649
713
|
|
|
650
714
|
if (!fs.existsSync(CORE_SKILLS)) {
|
|
@@ -670,6 +734,10 @@ function run() {
|
|
|
670
734
|
checkProfilesIntegrity(sourceSkills);
|
|
671
735
|
checkRegistryV2();
|
|
672
736
|
|
|
737
|
+
if (checkCatalog) {
|
|
738
|
+
checkCatalogSkills();
|
|
739
|
+
}
|
|
740
|
+
|
|
673
741
|
const passed = printReport();
|
|
674
742
|
process.exit(passed ? 0 : 1);
|
|
675
743
|
}
|