contextos-agents 1.7.0 → 2.0.0-beta.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.
- package/.agents/AGENTS.md +1 -1
- package/.agents/adapters/aider/export.js +117 -99
- package/.agents/adapters/claude/export.js +68 -26
- package/.agents/adapters/copilot/export.js +90 -53
- package/.agents/adapters/cursor/export.js +80 -101
- package/.agents/adapters/drift-detector.js +196 -0
- package/.agents/adapters/gemini/export.js +76 -45
- package/.agents/adapters/pure-compiler.js +443 -0
- package/.agents/adapters/zed/export.js +104 -96
- package/.agents/compiled/registry.v2.json +504 -0
- package/.agents/compiled/registry.v2.sha256 +1 -0
- package/.agents/compiler/manifest-compiler.js +963 -0
- package/.agents/compiler/vendor/yaml.LICENSE.txt +13 -0
- package/.agents/compiler/vendor/yaml.SBOM.json +6 -0
- package/.agents/compiler/vendor/yaml.js +139 -0
- package/.agents/core/profiles/init.yaml +25 -0
- package/.agents/core/skills/context-manager/skill.yaml +3 -5
- package/.agents/core/skills/context-os/SKILL.md +3 -6
- package/.agents/core/skills/context-os/skill.yaml +3 -8
- package/.agents/core/skills/engineering-workflow/skill.yaml +1 -7
- package/.agents/core/skills/gemini-precision/skill.yaml +1 -6
- package/.agents/core/skills/gstack-roles/skill.yaml +3 -6
- package/.agents/core/skills/ponytail-mindset/skill.yaml +1 -7
- package/.agents/core/skills/security/skill.yaml +15 -3
- package/.agents/ctx.js +574 -111
- package/.agents/customization-dx.js +282 -0
- package/.agents/doctor.js +719 -64
- package/.agents/filesystem/index.js +71 -0
- package/.agents/filesystem/journaled-transaction.js +451 -0
- package/.agents/filesystem/lockfile-v2.js +275 -0
- package/.agents/filesystem/platform-hardening.js +222 -0
- package/.agents/filesystem/project-lock.js +218 -0
- package/.agents/filesystem/safe-path.js +256 -0
- package/.agents/generated/claude/skills/context-os/SKILL.md +1 -1
- package/.agents/generated/gemini/skills/context-os/SKILL.md +2 -2
- package/.agents/plugins/contextos/plugin.json +1 -1
- package/.agents/plugins.js +259 -60
- package/.agents/profiles.js +486 -51
- package/.agents/resolver.js +50 -534
- package/.agents/schemas/attestation.review.v1.json +111 -0
- package/.agents/schemas/attestation.verification.v1.json +85 -0
- package/.agents/schemas/lockfile.v2.schema.json +134 -0
- package/.agents/schemas/profile.v2.schema.json +114 -0
- package/.agents/schemas/runtime.thread.v1.json +192 -0
- package/.agents/schemas/skill.manifest.v2.json +177 -0
- package/.agents/schemas/verification.spec.v1.json +39 -0
- package/.agents/schemas/workspace.graph.schema.json +106 -0
- package/.agents/transaction-core/event-store.js +288 -0
- package/.agents/transaction-core/idempotency.js +129 -0
- package/.agents/transaction-core/ipc-lock.js +311 -0
- package/.agents/transaction-core/plugin-supply-chain-bundle.js +436 -0
- package/.agents/validate.js +44 -1
- package/.agents/watch.js +354 -102
- package/.agents/workspace/workspace-graph.js +778 -0
- package/README.md +59 -387
- package/benchmarks/v2/analysis/statistics.js +140 -0
- package/benchmarks/v2/analysis/stats.js +69 -0
- package/benchmarks/v2/arms/arm-definitions.js +79 -0
- package/benchmarks/v2/dataset.schema.json +34 -0
- package/benchmarks/v2/evaluators/index.js +25 -0
- package/benchmarks/v2/evaluators/verified-success.js +116 -0
- package/benchmarks/v2/harness/runner.js +88 -0
- package/benchmarks/v2/pilot-tasks.json +392 -0
- package/bin/commands/recover.js +88 -0
- package/bin/commands/update.js +80 -17
- package/bin/commands.js +62 -25
- package/bin/index.js +138 -81
- package/bin/lib/lockfile.js +5 -3
- package/bin/lib/safe-writer.js +34 -3
- package/package.json +85 -72
- package/registry.json +2 -2
- package/registry.v2.schema.json +86 -0
- package/.agents/core/profiles/backend.yaml +0 -47
- package/.agents/core/profiles/enterprise.yaml +0 -46
- package/.agents/core/profiles/frontend.yaml +0 -46
- package/.agents/core/profiles/hackathon.yaml +0 -45
- package/.agents/core/profiles/mvp.yaml +0 -44
- package/.agents/core/profiles/startup.yaml +0 -48
- package/.agents/core/skills/adapters/EXAMPLES.md +0 -19
- package/.agents/core/skills/adapters/SKILL.md +0 -105
- package/.agents/core/skills/adapters/TROUBLESHOOTING.md +0 -7
- package/.agents/core/skills/adapters/VALIDATION.json +0 -12
- package/.agents/core/skills/adapters/skill.yaml +0 -16
- package/.agents/core/skills/architecture-diagrams/SKILL.md +0 -108
- package/.agents/core/skills/architecture-diagrams/VALIDATION.json +0 -12
- package/.agents/core/skills/architecture-diagrams/skill.yaml +0 -12
- package/.agents/core/skills/brutalist-design/SKILL.md +0 -150
- package/.agents/core/skills/brutalist-design/VALIDATION.json +0 -12
- package/.agents/core/skills/brutalist-design/skill.yaml +0 -12
- package/.agents/core/skills/database/EXAMPLES.md +0 -74
- package/.agents/core/skills/database/SKILL.md +0 -101
- package/.agents/core/skills/database/TROUBLESHOOTING.md +0 -18
- package/.agents/core/skills/database/VALIDATION.json +0 -11
- package/.agents/core/skills/database/skill.yaml +0 -31
- package/.agents/core/skills/ddd/EXAMPLES.md +0 -42
- package/.agents/core/skills/ddd/SKILL.md +0 -247
- package/.agents/core/skills/ddd/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/ddd/VALIDATION.json +0 -12
- package/.agents/core/skills/ddd/ddd.md +0 -178
- package/.agents/core/skills/ddd/skill.yaml +0 -17
- package/.agents/core/skills/decisions/EXAMPLES.md +0 -35
- package/.agents/core/skills/decisions/SKILL.md +0 -90
- package/.agents/core/skills/decisions/TROUBLESHOOTING.md +0 -13
- package/.agents/core/skills/decisions/VALIDATION.json +0 -12
- package/.agents/core/skills/decisions/skill.yaml +0 -16
- package/.agents/core/skills/docker/EXAMPLES.md +0 -56
- package/.agents/core/skills/docker/SKILL.md +0 -63
- package/.agents/core/skills/docker/TROUBLESHOOTING.md +0 -18
- package/.agents/core/skills/docker/VALIDATION.json +0 -11
- package/.agents/core/skills/docker/skill.yaml +0 -29
- package/.agents/core/skills/fastapi/EXAMPLES.md +0 -36
- package/.agents/core/skills/fastapi/SKILL.md +0 -148
- package/.agents/core/skills/fastapi/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/fastapi/VALIDATION.json +0 -12
- package/.agents/core/skills/fastapi/fastapi.md +0 -112
- package/.agents/core/skills/fastapi/skill.yaml +0 -17
- package/.agents/core/skills/generators/EXAMPLES.md +0 -19
- package/.agents/core/skills/generators/SKILL.md +0 -112
- package/.agents/core/skills/generators/TROUBLESHOOTING.md +0 -7
- package/.agents/core/skills/generators/VALIDATION.json +0 -12
- package/.agents/core/skills/generators/skill.yaml +0 -25
- package/.agents/core/skills/generators/templates/API.md +0 -77
- package/.agents/core/skills/generators/templates/ARCHITECTURE.md +0 -70
- package/.agents/core/skills/generators/templates/DATABASE.md +0 -42
- package/.agents/core/skills/generators/templates/DECISION.md +0 -46
- package/.agents/core/skills/generators/templates/PRD.md +0 -67
- package/.agents/core/skills/generators/templates/PROJECT_GRAPH.md +0 -56
- package/.agents/core/skills/generators/templates/ROADMAP.md +0 -51
- package/.agents/core/skills/generators/templates/TASKS.md +0 -43
- package/.agents/core/skills/generators/templates/UI.md +0 -73
- package/.agents/core/skills/graphify/EXAMPLES.md +0 -73
- package/.agents/core/skills/graphify/SKILL.md +0 -130
- package/.agents/core/skills/graphify/VALIDATION.json +0 -12
- package/.agents/core/skills/graphify/skill.yaml +0 -18
- package/.agents/core/skills/impeccable-design/EXAMPLES.md +0 -26
- package/.agents/core/skills/impeccable-design/SKILL.md +0 -201
- package/.agents/core/skills/impeccable-design/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/impeccable-design/VALIDATION.json +0 -12
- package/.agents/core/skills/impeccable-design/skill.yaml +0 -20
- package/.agents/core/skills/interview-me/SKILL.md +0 -97
- package/.agents/core/skills/interview-me/VALIDATION.json +0 -12
- package/.agents/core/skills/interview-me/skill.yaml +0 -12
- package/.agents/core/skills/microservices/EXAMPLES.md +0 -38
- package/.agents/core/skills/microservices/SKILL.md +0 -164
- package/.agents/core/skills/microservices/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/microservices/VALIDATION.json +0 -12
- package/.agents/core/skills/microservices/microservices.md +0 -119
- package/.agents/core/skills/microservices/skill.yaml +0 -17
- package/.agents/core/skills/minimalist-design/SKILL.md +0 -113
- package/.agents/core/skills/minimalist-design/VALIDATION.json +0 -12
- package/.agents/core/skills/minimalist-design/skill.yaml +0 -12
- package/.agents/core/skills/nestjs/EXAMPLES.md +0 -40
- package/.agents/core/skills/nestjs/SKILL.md +0 -139
- package/.agents/core/skills/nestjs/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/nestjs/VALIDATION.json +0 -12
- package/.agents/core/skills/nestjs/nestjs.md +0 -103
- package/.agents/core/skills/nestjs/skill.yaml +0 -17
- package/.agents/core/skills/nextjs/EXAMPLES.md +0 -40
- package/.agents/core/skills/nextjs/SKILL.md +0 -163
- package/.agents/core/skills/nextjs/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/nextjs/VALIDATION.json +0 -12
- package/.agents/core/skills/nextjs/nextjs.md +0 -67
- package/.agents/core/skills/nextjs/skill.yaml +0 -17
- package/.agents/core/skills/node/EXAMPLES.md +0 -80
- package/.agents/core/skills/node/SKILL.md +0 -128
- package/.agents/core/skills/node/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/node/VALIDATION.json +0 -12
- package/.agents/core/skills/node/node.md +0 -87
- package/.agents/core/skills/node/skill.yaml +0 -17
- package/.agents/core/skills/performance/EXAMPLES.md +0 -30
- package/.agents/core/skills/performance/SKILL.md +0 -75
- package/.agents/core/skills/performance/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/performance/VALIDATION.json +0 -12
- package/.agents/core/skills/performance/performance.md +0 -52
- package/.agents/core/skills/performance/skill.yaml +0 -17
- package/.agents/core/skills/react/EXAMPLES.md +0 -79
- package/.agents/core/skills/react/SKILL.md +0 -132
- package/.agents/core/skills/react/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/react/VALIDATION.json +0 -12
- package/.agents/core/skills/react/react.md +0 -93
- package/.agents/core/skills/react/skill.yaml +0 -17
- package/.agents/core/skills/react-best-practices/SKILL.md +0 -155
- package/.agents/core/skills/react-best-practices/VALIDATION.json +0 -12
- package/.agents/core/skills/react-best-practices/skill.yaml +0 -14
- package/.agents/core/skills/redesign-audit/SKILL.md +0 -117
- package/.agents/core/skills/redesign-audit/VALIDATION.json +0 -12
- package/.agents/core/skills/redesign-audit/skill.yaml +0 -12
- package/.agents/core/skills/soft-design/SKILL.md +0 -108
- package/.agents/core/skills/soft-design/VALIDATION.json +0 -12
- package/.agents/core/skills/soft-design/skill.yaml +0 -12
- package/.agents/core/skills/state-management/EXAMPLES.md +0 -56
- package/.agents/core/skills/state-management/SKILL.md +0 -48
- package/.agents/core/skills/state-management/TROUBLESHOOTING.md +0 -18
- package/.agents/core/skills/state-management/VALIDATION.json +0 -11
- package/.agents/core/skills/state-management/skill.yaml +0 -28
- package/.agents/core/skills/subagent-orchestrator/SKILL.md +0 -117
- package/.agents/core/skills/subagent-orchestrator/VALIDATION.json +0 -12
- package/.agents/core/skills/subagent-orchestrator/skill.yaml +0 -12
- package/.agents/core/skills/system-design/EXAMPLES.md +0 -75
- package/.agents/core/skills/system-design/SKILL.md +0 -419
- package/.agents/core/skills/system-design/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/system-design/VALIDATION.json +0 -12
- package/.agents/core/skills/system-design/skill.yaml +0 -20
- package/.agents/core/skills/system-design/system-design.md +0 -112
- package/.agents/core/skills/testing/EXAMPLES.md +0 -71
- package/.agents/core/skills/testing/SKILL.md +0 -70
- package/.agents/core/skills/testing/TROUBLESHOOTING.md +0 -18
- package/.agents/core/skills/testing/VALIDATION.json +0 -11
- package/.agents/core/skills/testing/skill.yaml +0 -32
- package/.agents/core/skills/typescript/EXAMPLES.md +0 -64
- package/.agents/core/skills/typescript/SKILL.md +0 -112
- package/.agents/core/skills/typescript/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/typescript/VALIDATION.json +0 -12
- package/.agents/core/skills/typescript/skill.yaml +0 -17
- package/.agents/core/skills/typescript/typescript.md +0 -71
- package/.agents/core/skills/ui-design/EXAMPLES.md +0 -21
- package/.agents/core/skills/ui-design/SKILL.md +0 -124
- package/.agents/core/skills/ui-design/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/ui-design/VALIDATION.json +0 -12
- package/.agents/core/skills/ui-design/skill.yaml +0 -17
- package/.agents/core/skills/ui-design/ui.md +0 -88
- package/.agents/core/skills/ui-ux-pro/EXAMPLES.md +0 -62
- package/.agents/core/skills/ui-ux-pro/SKILL.md +0 -375
- package/.agents/core/skills/ui-ux-pro/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/ui-ux-pro/VALIDATION.json +0 -12
- package/.agents/core/skills/ui-ux-pro/skill.yaml +0 -19
- package/.agents/core/skills/ux-design/EXAMPLES.md +0 -36
- package/.agents/core/skills/ux-design/SKILL.md +0 -116
- package/.agents/core/skills/ux-design/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/ux-design/VALIDATION.json +0 -12
- package/.agents/core/skills/ux-design/skill.yaml +0 -17
- package/.agents/core/skills/ux-design/ux.md +0 -80
- package/.agents/core/skills/vercel-optimize/SKILL.md +0 -83
- package/.agents/core/skills/vercel-optimize/VALIDATION.json +0 -12
- package/.agents/core/skills/vercel-optimize/scripts/collect-signals.mjs +0 -131
- package/.agents/core/skills/vercel-optimize/scripts/gate-investigations.mjs +0 -142
- package/.agents/core/skills/vercel-optimize/scripts/merge-signals.mjs +0 -143
- package/.agents/core/skills/vercel-optimize/scripts/scan-codebase.mjs +0 -174
- package/.agents/core/skills/vercel-optimize/skill.yaml +0 -18
- package/.agents/core/skills/web-accessibility/EXAMPLES.md +0 -39
- package/.agents/core/skills/web-accessibility/SKILL.md +0 -170
- package/.agents/core/skills/web-accessibility/TROUBLESHOOTING.md +0 -19
- package/.agents/core/skills/web-accessibility/VALIDATION.json +0 -12
- package/.agents/core/skills/web-accessibility/accessibility.md +0 -63
- package/.agents/core/skills/web-accessibility/skill.yaml +0 -17
- package/.agents/generated/claude/skills/adapters/SKILL.md +0 -126
- package/.agents/generated/claude/skills/architecture-diagrams/SKILL.md +0 -101
- package/.agents/generated/claude/skills/brutalist-design/SKILL.md +0 -145
- package/.agents/generated/claude/skills/database/SKILL.md +0 -191
- package/.agents/generated/claude/skills/ddd/SKILL.md +0 -305
- package/.agents/generated/claude/skills/decisions/SKILL.md +0 -134
- package/.agents/generated/claude/skills/docker/SKILL.md +0 -135
- package/.agents/generated/claude/skills/fastapi/SKILL.md +0 -200
- package/.agents/generated/claude/skills/generators/SKILL.md +0 -133
- package/.agents/generated/claude/skills/graphify/SKILL.md +0 -198
- package/.agents/generated/claude/skills/impeccable-design/SKILL.md +0 -241
- package/.agents/generated/claude/skills/interview-me/SKILL.md +0 -90
- package/.agents/generated/claude/skills/microservices/SKILL.md +0 -218
- package/.agents/generated/claude/skills/minimalist-design/SKILL.md +0 -108
- package/.agents/generated/claude/skills/nestjs/SKILL.md +0 -195
- package/.agents/generated/claude/skills/nextjs/SKILL.md +0 -219
- package/.agents/generated/claude/skills/node/SKILL.md +0 -224
- package/.agents/generated/claude/skills/performance/SKILL.md +0 -121
- package/.agents/generated/claude/skills/react/SKILL.md +0 -227
- package/.agents/generated/claude/skills/react-best-practices/SKILL.md +0 -146
- package/.agents/generated/claude/skills/redesign-audit/SKILL.md +0 -112
- package/.agents/generated/claude/skills/soft-design/SKILL.md +0 -103
- package/.agents/generated/claude/skills/state-management/SKILL.md +0 -120
- package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +0 -110
- package/.agents/generated/claude/skills/system-design/SKILL.md +0 -507
- package/.agents/generated/claude/skills/testing/SKILL.md +0 -157
- package/.agents/generated/claude/skills/typescript/SKILL.md +0 -192
- package/.agents/generated/claude/skills/ui-design/SKILL.md +0 -161
- package/.agents/generated/claude/skills/ui-ux-pro/SKILL.md +0 -451
- package/.agents/generated/claude/skills/ux-design/SKILL.md +0 -168
- package/.agents/generated/claude/skills/vercel-optimize/SKILL.md +0 -76
- package/.agents/generated/claude/skills/web-accessibility/SKILL.md +0 -225
- package/.agents/generated/gemini/skills/adapters/SKILL.md +0 -135
- package/.agents/generated/gemini/skills/architecture-diagrams/SKILL.md +0 -107
- package/.agents/generated/gemini/skills/brutalist-design/SKILL.md +0 -151
- package/.agents/generated/gemini/skills/database/SKILL.md +0 -200
- package/.agents/generated/gemini/skills/ddd/SKILL.md +0 -314
- package/.agents/generated/gemini/skills/decisions/SKILL.md +0 -143
- package/.agents/generated/gemini/skills/docker/SKILL.md +0 -144
- package/.agents/generated/gemini/skills/fastapi/SKILL.md +0 -209
- package/.agents/generated/gemini/skills/generators/SKILL.md +0 -142
- package/.agents/generated/gemini/skills/graphify/SKILL.md +0 -205
- package/.agents/generated/gemini/skills/impeccable-design/SKILL.md +0 -250
- package/.agents/generated/gemini/skills/interview-me/SKILL.md +0 -96
- package/.agents/generated/gemini/skills/microservices/SKILL.md +0 -227
- package/.agents/generated/gemini/skills/minimalist-design/SKILL.md +0 -114
- package/.agents/generated/gemini/skills/nestjs/SKILL.md +0 -204
- package/.agents/generated/gemini/skills/nextjs/SKILL.md +0 -298
- package/.agents/generated/gemini/skills/node/SKILL.md +0 -323
- package/.agents/generated/gemini/skills/performance/SKILL.md +0 -185
- package/.agents/generated/gemini/skills/react/SKILL.md +0 -332
- package/.agents/generated/gemini/skills/react-best-practices/SKILL.md +0 -152
- package/.agents/generated/gemini/skills/redesign-audit/SKILL.md +0 -118
- package/.agents/generated/gemini/skills/soft-design/SKILL.md +0 -109
- package/.agents/generated/gemini/skills/state-management/SKILL.md +0 -129
- package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +0 -116
- package/.agents/generated/gemini/skills/system-design/SKILL.md +0 -631
- package/.agents/generated/gemini/skills/testing/SKILL.md +0 -166
- package/.agents/generated/gemini/skills/typescript/SKILL.md +0 -275
- package/.agents/generated/gemini/skills/ui-design/SKILL.md +0 -170
- package/.agents/generated/gemini/skills/ui-ux-pro/SKILL.md +0 -460
- package/.agents/generated/gemini/skills/ux-design/SKILL.md +0 -177
- package/.agents/generated/gemini/skills/vercel-optimize/SKILL.md +0 -82
- package/.agents/generated/gemini/skills/web-accessibility/SKILL.md +0 -300
|
@@ -0,0 +1,963 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* .agents/compiler/manifest-compiler.js
|
|
3
|
+
* ContextOS — Manifest Compiler & Registry v2 Generator
|
|
4
|
+
*
|
|
5
|
+
* Compiles skill manifests (v1 and v2) into a unified, deterministic,
|
|
6
|
+
* cryptographically verified registry (registry.v2.json) with:
|
|
7
|
+
* - Strict schema validation (fail-closed, additionalProperties: false)
|
|
8
|
+
* - Frontmatter parity enforcement between SKILL.md and manifest
|
|
9
|
+
* - Dependency closure, cycle detection, self-dependency and conflict validation
|
|
10
|
+
* - Resource tree verification & path traversal / symlink escape guards
|
|
11
|
+
* - SHA-256 content hashing (entrypointHash, resourceTreeHash, sourceGraphHash)
|
|
12
|
+
* - Standardized error contracts & SARIF 2.1.0 reporting
|
|
13
|
+
* - Zero install-time runtime dependencies (pure Node.js)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const crypto = require('crypto');
|
|
21
|
+
|
|
22
|
+
// Standard error codes for manifest diagnostics
|
|
23
|
+
const CODES = {
|
|
24
|
+
PARSE_ERROR: 'CTX_MANIFEST_PARSE_ERROR',
|
|
25
|
+
SCHEMA_ERROR: 'CTX_MANIFEST_SCHEMA_ERROR',
|
|
26
|
+
UNKNOWN_DEPENDENCY: 'CTX_MANIFEST_UNKNOWN_DEPENDENCY',
|
|
27
|
+
CYCLE_DETECTED: 'CTX_MANIFEST_CYCLE_DETECTED',
|
|
28
|
+
SELF_DEPENDENCY: 'CTX_MANIFEST_SELF_DEPENDENCY',
|
|
29
|
+
CONFLICT_VIOLATION: 'CTX_MANIFEST_CONFLICT_VIOLATION',
|
|
30
|
+
FRONTMATTER_MISMATCH: 'CTX_MANIFEST_FRONTMATTER_MISMATCH',
|
|
31
|
+
MISSING_ENTRYPOINT: 'CTX_MANIFEST_MISSING_ENTRYPOINT',
|
|
32
|
+
MISSING_RESOURCE: 'CTX_MANIFEST_MISSING_RESOURCE',
|
|
33
|
+
INVALID_PATH: 'CTX_MANIFEST_INVALID_PATH',
|
|
34
|
+
DUPLICATE_ID: 'CTX_MANIFEST_DUPLICATE_ID',
|
|
35
|
+
DUPLICATE_ALIAS: 'CTX_MANIFEST_DUPLICATE_ALIAS',
|
|
36
|
+
MISSING_RULE_CHECKER: 'CTX_MANIFEST_MISSING_RULE_CHECKER',
|
|
37
|
+
INVALID_RULE: 'CTX_MANIFEST_INVALID_RULE',
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Default signals by skill ID (extracted from canonical resolver patterns for v1 backward compatibility)
|
|
41
|
+
const DEFAULT_SIGNALS = {
|
|
42
|
+
nextjs: {
|
|
43
|
+
aliases: ['next', 'next.js', 'nextjs'],
|
|
44
|
+
keywords: [
|
|
45
|
+
{ value: 'next', weight: 40 },
|
|
46
|
+
{ value: 'next.js', weight: 40 },
|
|
47
|
+
{ value: 'app router', weight: 35 },
|
|
48
|
+
{ value: 'server actions', weight: 30 },
|
|
49
|
+
{ value: 'rsc', weight: 30 },
|
|
50
|
+
{ value: 'некст', locale: 'ru', weight: 35 },
|
|
51
|
+
],
|
|
52
|
+
fileGlobs: [
|
|
53
|
+
{ value: 'app/**/*.{tsx,jsx,ts,js}', weight: 30 },
|
|
54
|
+
{ value: '**/next.config.{js,mjs,ts}', weight: 35 },
|
|
55
|
+
],
|
|
56
|
+
packages: [{ ecosystem: 'npm', name: 'next', weight: 35 }],
|
|
57
|
+
},
|
|
58
|
+
react: {
|
|
59
|
+
aliases: ['react', 'reactjs'],
|
|
60
|
+
keywords: [
|
|
61
|
+
{ value: 'react', weight: 40 },
|
|
62
|
+
{ value: 'useOptimistic', weight: 30 },
|
|
63
|
+
{ value: 'component', weight: 20 },
|
|
64
|
+
{ value: 'hooks', weight: 25 },
|
|
65
|
+
{ value: 'компонент', locale: 'ru', weight: 30 },
|
|
66
|
+
{ value: 'хук', locale: 'ru', weight: 30 },
|
|
67
|
+
],
|
|
68
|
+
fileGlobs: [{ value: '**/*.{tsx,jsx}', weight: 25 }],
|
|
69
|
+
packages: [{ ecosystem: 'npm', name: 'react', weight: 35 }],
|
|
70
|
+
},
|
|
71
|
+
typescript: {
|
|
72
|
+
aliases: ['ts', 'typescript'],
|
|
73
|
+
keywords: [
|
|
74
|
+
{ value: 'typescript', weight: 40 },
|
|
75
|
+
{ value: 'type-safe', weight: 30 },
|
|
76
|
+
{ value: 'generics', weight: 25 },
|
|
77
|
+
{ value: 'tsconfig', weight: 30 },
|
|
78
|
+
{ value: 'тайпскрипт', locale: 'ru', weight: 35 },
|
|
79
|
+
{ value: 'типизац', locale: 'ru', weight: 30 },
|
|
80
|
+
],
|
|
81
|
+
fileGlobs: [
|
|
82
|
+
{ value: '**/*.tsx?', weight: 20 },
|
|
83
|
+
{ value: '**/tsconfig.json', weight: 35 },
|
|
84
|
+
],
|
|
85
|
+
packages: [{ ecosystem: 'npm', name: 'typescript', weight: 35 }],
|
|
86
|
+
},
|
|
87
|
+
security: {
|
|
88
|
+
aliases: ['sec', 'auth', 'security'],
|
|
89
|
+
keywords: [
|
|
90
|
+
{ value: 'security', weight: 40 },
|
|
91
|
+
{ value: 'auth', weight: 35 },
|
|
92
|
+
{ value: 'jwt', weight: 30 },
|
|
93
|
+
{ value: 'csrf', weight: 30 },
|
|
94
|
+
{ value: 'xss', weight: 30 },
|
|
95
|
+
{ value: 'безопасност', locale: 'ru', weight: 35 },
|
|
96
|
+
{ value: 'авториз', locale: 'ru', weight: 30 },
|
|
97
|
+
{ value: 'парол', locale: 'ru', weight: 30 },
|
|
98
|
+
],
|
|
99
|
+
fileGlobs: [
|
|
100
|
+
{ value: '**/*auth*', weight: 25 },
|
|
101
|
+
{ value: '**/*security*', weight: 25 },
|
|
102
|
+
],
|
|
103
|
+
packages: [],
|
|
104
|
+
},
|
|
105
|
+
database: {
|
|
106
|
+
aliases: ['db', 'database', 'sql'],
|
|
107
|
+
keywords: [
|
|
108
|
+
{ value: 'database', weight: 40 },
|
|
109
|
+
{ value: 'sql', weight: 35 },
|
|
110
|
+
{ value: 'postgres', weight: 35 },
|
|
111
|
+
{ value: 'prisma', weight: 35 },
|
|
112
|
+
{ value: 'drizzle', weight: 35 },
|
|
113
|
+
{ value: 'migration', weight: 30 },
|
|
114
|
+
{ value: 'баз.*данн', locale: 'ru', weight: 35 },
|
|
115
|
+
{ value: 'миграц', locale: 'ru', weight: 30 },
|
|
116
|
+
],
|
|
117
|
+
fileGlobs: [
|
|
118
|
+
{ value: '**/*.prisma', weight: 35 },
|
|
119
|
+
{ value: '**/drizzle.config.*', weight: 35 },
|
|
120
|
+
{ value: '**/migrations/**/*.sql', weight: 30 },
|
|
121
|
+
],
|
|
122
|
+
packages: [
|
|
123
|
+
{ ecosystem: 'npm', name: 'prisma', weight: 30 },
|
|
124
|
+
{ ecosystem: 'npm', name: 'drizzle-orm', weight: 30 },
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
testing: {
|
|
128
|
+
aliases: ['test', 'tests', 'testing'],
|
|
129
|
+
keywords: [
|
|
130
|
+
{ value: 'vitest', weight: 40 },
|
|
131
|
+
{ value: 'jest', weight: 40 },
|
|
132
|
+
{ value: 'playwright', weight: 40 },
|
|
133
|
+
{ value: 'tdd', weight: 35 },
|
|
134
|
+
{ value: 'bdd', weight: 30 },
|
|
135
|
+
{ value: 'тестирован', locale: 'ru', weight: 35 },
|
|
136
|
+
{ value: 'юнит', locale: 'ru', weight: 30 },
|
|
137
|
+
],
|
|
138
|
+
fileGlobs: [
|
|
139
|
+
{ value: '**/*.{test,spec}.{ts,js,tsx,jsx}', weight: 35 },
|
|
140
|
+
{ value: '**/vitest.config.*', weight: 35 },
|
|
141
|
+
{ value: '**/playwright.config.*', weight: 35 },
|
|
142
|
+
],
|
|
143
|
+
packages: [],
|
|
144
|
+
},
|
|
145
|
+
'ui-ux-pro': {
|
|
146
|
+
aliases: ['ui', 'ux', 'ui-ux'],
|
|
147
|
+
keywords: [
|
|
148
|
+
{ value: 'ui', weight: 35 },
|
|
149
|
+
{ value: 'ux', weight: 35 },
|
|
150
|
+
{ value: 'tailwind', weight: 35 },
|
|
151
|
+
{ value: 'styling', weight: 30 },
|
|
152
|
+
{ value: 'дизайн', locale: 'ru', weight: 35 },
|
|
153
|
+
{ value: 'интерфейс', locale: 'ru', weight: 30 },
|
|
154
|
+
],
|
|
155
|
+
fileGlobs: [
|
|
156
|
+
{ value: '**/*.{css,scss,sass}', weight: 25 },
|
|
157
|
+
{ value: '**/tailwind.config.*', weight: 35 },
|
|
158
|
+
],
|
|
159
|
+
packages: [{ ecosystem: 'npm', name: 'tailwindcss', weight: 30 }],
|
|
160
|
+
},
|
|
161
|
+
node: {
|
|
162
|
+
aliases: ['nodejs', 'node'],
|
|
163
|
+
keywords: [
|
|
164
|
+
{ value: 'node', weight: 35 },
|
|
165
|
+
{ value: 'nodejs', weight: 35 },
|
|
166
|
+
{ value: 'express', weight: 30 },
|
|
167
|
+
{ value: 'fastify', weight: 30 },
|
|
168
|
+
],
|
|
169
|
+
fileGlobs: [{ value: '**/package.json', weight: 15 }],
|
|
170
|
+
packages: [],
|
|
171
|
+
},
|
|
172
|
+
fastapi: {
|
|
173
|
+
aliases: ['fastapi'],
|
|
174
|
+
keywords: [
|
|
175
|
+
{ value: 'fastapi', weight: 45 },
|
|
176
|
+
{ value: 'pydantic', weight: 35 },
|
|
177
|
+
{ value: 'uvicorn', weight: 30 },
|
|
178
|
+
],
|
|
179
|
+
fileGlobs: [
|
|
180
|
+
{ value: '**/main.py', weight: 20 },
|
|
181
|
+
{ value: '**/requirements.txt', weight: 15 },
|
|
182
|
+
],
|
|
183
|
+
packages: [{ ecosystem: 'pypi', name: 'fastapi', weight: 40 }],
|
|
184
|
+
},
|
|
185
|
+
nestjs: {
|
|
186
|
+
aliases: ['nestjs', 'nest'],
|
|
187
|
+
keywords: [
|
|
188
|
+
{ value: 'nestjs', weight: 45 },
|
|
189
|
+
{ value: 'nest.js', weight: 40 },
|
|
190
|
+
{ value: 'injectable', weight: 25 },
|
|
191
|
+
],
|
|
192
|
+
fileGlobs: [{ value: '**/nest-cli.json', weight: 40 }],
|
|
193
|
+
packages: [{ ecosystem: 'npm', name: '@nestjs/core', weight: 40 }],
|
|
194
|
+
},
|
|
195
|
+
docker: {
|
|
196
|
+
aliases: ['docker'],
|
|
197
|
+
keywords: [
|
|
198
|
+
{ value: 'docker', weight: 45 },
|
|
199
|
+
{ value: 'dockerfile', weight: 45 },
|
|
200
|
+
{ value: 'compose', weight: 30 },
|
|
201
|
+
{ value: 'докер', locale: 'ru', weight: 40 },
|
|
202
|
+
{ value: 'container', weight: 5 },
|
|
203
|
+
{ value: 'контейнер', locale: 'ru', weight: 5 },
|
|
204
|
+
],
|
|
205
|
+
fileGlobs: [
|
|
206
|
+
{ value: '**/Dockerfile*', weight: 40 },
|
|
207
|
+
{ value: '**/docker-compose*.yml', weight: 40 },
|
|
208
|
+
],
|
|
209
|
+
packages: [],
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Diagnostic error object.
|
|
215
|
+
*/
|
|
216
|
+
class DiagnosticError extends Error {
|
|
217
|
+
/**
|
|
218
|
+
* @param {Object} diag - Diagnostic payload
|
|
219
|
+
*/
|
|
220
|
+
constructor(diag) {
|
|
221
|
+
super(diag.message);
|
|
222
|
+
this.name = 'DiagnosticError';
|
|
223
|
+
this.diag = diag;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const yaml = require('./vendor/yaml.js');
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Robust zero-dep YAML parser for skill manifests.
|
|
231
|
+
* Handles scalars, inline arrays, block lists, nested maps, and quoted multiline strings.
|
|
232
|
+
*/
|
|
233
|
+
function parseYaml(yamlText) {
|
|
234
|
+
if (typeof yamlText !== 'string' || !yamlText.trim()) return {};
|
|
235
|
+
const doc = yaml.parseDocument(yamlText, { merge: true, strict: true, uniqueKeys: true });
|
|
236
|
+
if (doc.errors && doc.errors.length > 0) {
|
|
237
|
+
throw new DiagnosticError({
|
|
238
|
+
message: `YAML Parse Error: ${doc.errors[0].message}`,
|
|
239
|
+
code: CODES.PARSE_ERROR
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return doc.toJS() || {};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* ManifestCompiler class.
|
|
247
|
+
*/
|
|
248
|
+
class ManifestCompiler {
|
|
249
|
+
constructor(options = {}) {
|
|
250
|
+
this.rootDir = options.rootDir || process.cwd();
|
|
251
|
+
this.agentsDir = options.agentsDir || path.join(this.rootDir, '.agents');
|
|
252
|
+
this.coreSkillsDir = options.coreSkillsDir || path.join(this.agentsDir, 'core', 'skills');
|
|
253
|
+
this.compiledDir = options.compiledDir || path.join(this.agentsDir, 'compiled');
|
|
254
|
+
this.diagnostics = [];
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
addDiagnostic(code, message, options = {}) {
|
|
258
|
+
const diag = {
|
|
259
|
+
code,
|
|
260
|
+
severity: options.severity || 'error',
|
|
261
|
+
component: 'manifest-compiler',
|
|
262
|
+
file: options.file || '',
|
|
263
|
+
path: options.path || '',
|
|
264
|
+
line: options.line,
|
|
265
|
+
column: options.column,
|
|
266
|
+
message,
|
|
267
|
+
remediation: options.remediation || 'Correct the manifest according to the schema specification.',
|
|
268
|
+
};
|
|
269
|
+
this.diagnostics.push(diag);
|
|
270
|
+
return diag;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Reads and parses frontmatter from a markdown file.
|
|
275
|
+
*/
|
|
276
|
+
readFrontmatter(filePath) {
|
|
277
|
+
if (!fs.existsSync(filePath)) return null;
|
|
278
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
279
|
+
if (!content.startsWith('---')) return null;
|
|
280
|
+
const endIdx = content.indexOf('---', 3);
|
|
281
|
+
if (endIdx === -1) return { malformed: true };
|
|
282
|
+
const rawYaml = content.slice(3, endIdx).trim();
|
|
283
|
+
const parsed = parseYaml(rawYaml);
|
|
284
|
+
return {
|
|
285
|
+
raw: rawYaml,
|
|
286
|
+
parsed,
|
|
287
|
+
contentWithoutFrontmatter: content.slice(endIdx + 3).trim(),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Normalizes a v1 or v2 manifest into canonical SkillManifestV2 format.
|
|
293
|
+
*/
|
|
294
|
+
normalizeManifest(skillDir, rawYamlText) {
|
|
295
|
+
const parsed = parseYaml(rawYamlText);
|
|
296
|
+
const skillId = parsed.id || parsed.name || path.basename(skillDir);
|
|
297
|
+
const displayName = parsed.displayName || parsed.name || skillId;
|
|
298
|
+
const isV2 = parsed.schemaVersion === 2;
|
|
299
|
+
|
|
300
|
+
const entrypoint = parsed.entrypoint || 'SKILL.md';
|
|
301
|
+
const version = parsed.version ? String(parsed.version) : '1.0.0';
|
|
302
|
+
const description = parsed.description || `ContextOS skill for ${displayName}`;
|
|
303
|
+
const type = parsed.type || 'instruction-only';
|
|
304
|
+
const category = parsed.category || 'general';
|
|
305
|
+
|
|
306
|
+
// Normalizing dependencies
|
|
307
|
+
const requires = Array.isArray(parsed.dependencies?.requires)
|
|
308
|
+
? parsed.dependencies.requires
|
|
309
|
+
: (Array.isArray(parsed.requires) ? parsed.requires : []);
|
|
310
|
+
const optional = Array.isArray(parsed.dependencies?.optional)
|
|
311
|
+
? parsed.dependencies.optional
|
|
312
|
+
: (Array.isArray(parsed.optional) ? parsed.optional : []);
|
|
313
|
+
const conflicts = Array.isArray(parsed.dependencies?.conflicts)
|
|
314
|
+
? parsed.dependencies.conflicts
|
|
315
|
+
: (Array.isArray(parsed.conflicts) ? parsed.conflicts : []);
|
|
316
|
+
|
|
317
|
+
// Read SKILL.md frontmatter name if present to ensure alias parity
|
|
318
|
+
const discoveredAliases = [];
|
|
319
|
+
const entrypointFile = path.join(skillDir, entrypoint);
|
|
320
|
+
if (fs.existsSync(entrypointFile)) {
|
|
321
|
+
const fm = this.readFrontmatter(entrypointFile);
|
|
322
|
+
if (fm && fm.parsed && fm.parsed.name) {
|
|
323
|
+
const fmSlug = String(fm.parsed.name).trim().toLowerCase().replace(/\s+/g, '-');
|
|
324
|
+
discoveredAliases.push(fmSlug);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Normalizing signals (merge provided signals with default signals for known skills)
|
|
329
|
+
const defaults = DEFAULT_SIGNALS[skillId] || {};
|
|
330
|
+
const signals = {
|
|
331
|
+
aliases: Array.from(new Set([
|
|
332
|
+
...(parsed.signals?.aliases || []),
|
|
333
|
+
...(defaults.aliases || []),
|
|
334
|
+
...discoveredAliases,
|
|
335
|
+
skillId
|
|
336
|
+
])),
|
|
337
|
+
keywords: parsed.signals?.keywords || defaults.keywords || [],
|
|
338
|
+
fileGlobs: parsed.signals?.fileGlobs || defaults.fileGlobs || [],
|
|
339
|
+
packages: parsed.signals?.packages || defaults.packages || [],
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// Normalizing context
|
|
343
|
+
const priority = parsed.context?.priority !== undefined
|
|
344
|
+
? parsed.context.priority
|
|
345
|
+
: (parsed.weight !== undefined ? parsed.weight * 10 : 50);
|
|
346
|
+
|
|
347
|
+
// Normalizing resources
|
|
348
|
+
let resources = [];
|
|
349
|
+
if (Array.isArray(parsed.resources)) {
|
|
350
|
+
resources = parsed.resources.map(r => {
|
|
351
|
+
if (typeof r === 'string') return { path: r, mode: 'on-demand' };
|
|
352
|
+
return { path: r.path, mode: r.mode || 'on-demand' };
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const deprecated = Boolean(parsed.deprecated);
|
|
357
|
+
const canonical = typeof parsed.canonical === 'string' ? parsed.canonical : null;
|
|
358
|
+
const rules = Array.isArray(parsed.rules) ? parsed.rules : [];
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
schemaVersion: 2,
|
|
362
|
+
id: skillId,
|
|
363
|
+
displayName,
|
|
364
|
+
version,
|
|
365
|
+
description,
|
|
366
|
+
type,
|
|
367
|
+
category,
|
|
368
|
+
entrypoint,
|
|
369
|
+
signals,
|
|
370
|
+
dependencies: {
|
|
371
|
+
requires: Array.from(new Set(requires)),
|
|
372
|
+
optional: Array.from(new Set(optional)),
|
|
373
|
+
conflicts: Array.from(new Set(conflicts)),
|
|
374
|
+
},
|
|
375
|
+
context: {
|
|
376
|
+
priority,
|
|
377
|
+
estimatedTokens: 0, // Calculated during compilation
|
|
378
|
+
},
|
|
379
|
+
resources,
|
|
380
|
+
deprecated,
|
|
381
|
+
canonical,
|
|
382
|
+
rules,
|
|
383
|
+
_raw: parsed,
|
|
384
|
+
_isV2: isV2,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Validates a single manifest against strict security, schema and filesystem rules.
|
|
390
|
+
*/
|
|
391
|
+
validateSingleManifest(skillDir, manifest, manifestFile) {
|
|
392
|
+
const relManifestPath = path.relative(this.rootDir, manifestFile);
|
|
393
|
+
|
|
394
|
+
// 0. Strict Schema Validation (additionalProperties: false)
|
|
395
|
+
const allowedKeys = new Set([
|
|
396
|
+
'schemaVersion', 'id', 'name', 'displayName', 'entrypoint', 'version',
|
|
397
|
+
'description', 'type', 'category', 'dependencies', 'requires', 'optional',
|
|
398
|
+
'conflicts', 'signals', 'context', 'weight', 'resources', 'deprecated',
|
|
399
|
+
'canonical', 'rules'
|
|
400
|
+
]);
|
|
401
|
+
if (manifest._raw) {
|
|
402
|
+
for (const key of Object.keys(manifest._raw)) {
|
|
403
|
+
if (!allowedKeys.has(key)) {
|
|
404
|
+
this.addDiagnostic(
|
|
405
|
+
CODES.SCHEMA_ERROR,
|
|
406
|
+
`Strict Schema Validation Failed: Unknown property '${key}' is not allowed in schemaVersion 2.`,
|
|
407
|
+
{ file: relManifestPath, path: `/${key}` }
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// 1. Check ID format
|
|
414
|
+
if (!manifest.id || !/^[a-z0-9-]+$/.test(manifest.id)) {
|
|
415
|
+
this.addDiagnostic(
|
|
416
|
+
CODES.SCHEMA_ERROR,
|
|
417
|
+
`Invalid skill id '${manifest.id}'. Must contain only lowercase alphanumeric characters and hyphens.`,
|
|
418
|
+
{ file: relManifestPath, path: '/id' }
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// 2. Check entrypoint on disk
|
|
423
|
+
if (manifest.entrypoint.includes('..') || manifest.entrypoint.startsWith('/') || manifest.entrypoint.startsWith('\\') || manifest.entrypoint.includes('\0')) {
|
|
424
|
+
this.addDiagnostic(
|
|
425
|
+
CODES.INVALID_PATH,
|
|
426
|
+
`Entrypoint path '${manifest.entrypoint}' contains invalid characters or directory traversal.`,
|
|
427
|
+
{ file: relManifestPath, path: '/entrypoint' }
|
|
428
|
+
);
|
|
429
|
+
} else {
|
|
430
|
+
const fullEntrypoint = path.join(skillDir, manifest.entrypoint);
|
|
431
|
+
if (!fs.existsSync(fullEntrypoint)) {
|
|
432
|
+
this.addDiagnostic(
|
|
433
|
+
CODES.MISSING_ENTRYPOINT,
|
|
434
|
+
`Required entrypoint file '${manifest.entrypoint}' does not exist on disk.`,
|
|
435
|
+
{ file: relManifestPath, path: '/entrypoint', remediation: `Create ${manifest.entrypoint} in ${path.relative(this.rootDir, skillDir)}` }
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// 3. Frontmatter parity check with SKILL.md
|
|
441
|
+
const skillMdPath = path.join(skillDir, manifest.entrypoint);
|
|
442
|
+
if (fs.existsSync(skillMdPath)) {
|
|
443
|
+
const fm = this.readFrontmatter(skillMdPath);
|
|
444
|
+
if (fm && fm.parsed && fm.parsed.name) {
|
|
445
|
+
const fmName = String(fm.parsed.name).trim().toLowerCase();
|
|
446
|
+
const idMatches = manifest.id.toLowerCase() === fmName;
|
|
447
|
+
const displayNameMatches = manifest.displayName.toLowerCase() === fmName;
|
|
448
|
+
const aliasMatches = manifest.signals && Array.isArray(manifest.signals.aliases)
|
|
449
|
+
? manifest.signals.aliases.some(a => a.toLowerCase() === fmName || a.toLowerCase() === fmName.replace(/\s+/g, '-'))
|
|
450
|
+
: false;
|
|
451
|
+
|
|
452
|
+
if (!idMatches && !displayNameMatches && !aliasMatches) {
|
|
453
|
+
this.addDiagnostic(
|
|
454
|
+
CODES.FRONTMATTER_MISMATCH,
|
|
455
|
+
`Frontmatter name '${fm.parsed.name}' in ${manifest.entrypoint} does not match manifest id '${manifest.id}', displayName '${manifest.displayName}', or registered aliases.`,
|
|
456
|
+
{ file: relManifestPath, path: '/displayName', remediation: `Update ${manifest.entrypoint} frontmatter name to match manifest displayName '${manifest.displayName}' or add '${fm.parsed.name}' to aliases.` }
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// 4. Resource paths validation
|
|
463
|
+
const realSkillDir = fs.realpathSync(skillDir);
|
|
464
|
+
for (let i = 0; i < manifest.resources.length; i++) {
|
|
465
|
+
const res = manifest.resources[i];
|
|
466
|
+
const resPath = res.path;
|
|
467
|
+
if (!resPath || resPath.includes('..') || path.isAbsolute(resPath) || resPath.includes('\0')) {
|
|
468
|
+
this.addDiagnostic(
|
|
469
|
+
CODES.INVALID_PATH,
|
|
470
|
+
`Resource path '${resPath}' is invalid or attempts path traversal.`,
|
|
471
|
+
{ file: relManifestPath, path: `/resources/${i}/path` }
|
|
472
|
+
);
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const fullResPath = path.join(skillDir, resPath);
|
|
477
|
+
if (!fs.existsSync(fullResPath)) {
|
|
478
|
+
this.addDiagnostic(
|
|
479
|
+
CODES.MISSING_RESOURCE,
|
|
480
|
+
`Resource file '${resPath}' listed in manifest does not exist.`,
|
|
481
|
+
{ file: relManifestPath, path: `/resources/${i}/path`, remediation: `Create the file or remove it from resources.` }
|
|
482
|
+
);
|
|
483
|
+
} else {
|
|
484
|
+
// Symlink escape check
|
|
485
|
+
const realResPath = fs.realpathSync(fullResPath);
|
|
486
|
+
if (!realResPath.startsWith(realSkillDir)) {
|
|
487
|
+
this.addDiagnostic(
|
|
488
|
+
CODES.INVALID_PATH,
|
|
489
|
+
`Resource file '${resPath}' resolves outside skill directory (symlink escape).`,
|
|
490
|
+
{ file: relManifestPath, path: `/resources/${i}/path` }
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// 5. Check self-dependency
|
|
497
|
+
if (manifest.dependencies.requires.includes(manifest.id)) {
|
|
498
|
+
this.addDiagnostic(
|
|
499
|
+
CODES.SELF_DEPENDENCY,
|
|
500
|
+
`Skill '${manifest.id}' cannot require itself.`,
|
|
501
|
+
{ file: relManifestPath, path: '/dependencies/requires' }
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// 6. Validate declared rules and checkers
|
|
506
|
+
if (Array.isArray(manifest.rules)) {
|
|
507
|
+
const { AUTOMATED_CHECKERS } = require('../rules/rule-catalog.js');
|
|
508
|
+
for (let i = 0; i < manifest.rules.length; i++) {
|
|
509
|
+
const rule = manifest.rules[i];
|
|
510
|
+
if (!rule.id || !/^[A-Z0-9_-]+$/.test(rule.id)) {
|
|
511
|
+
this.addDiagnostic(
|
|
512
|
+
CODES.SCHEMA_ERROR,
|
|
513
|
+
`Rule [${i}] in '${manifest.id}' has invalid ID '${rule.id}'. Must match pattern ^[A-Z0-9_-]+$.`,
|
|
514
|
+
{ file: relManifestPath, path: `/rules/${i}/id` }
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
if (!['must', 'should', 'may'].includes(rule.level)) {
|
|
518
|
+
this.addDiagnostic(
|
|
519
|
+
CODES.SCHEMA_ERROR,
|
|
520
|
+
`Rule '${rule.id}' has invalid level '${rule.level}'. Must be 'must', 'should', or 'may'.`,
|
|
521
|
+
{ file: relManifestPath, path: `/rules/${i}/level` }
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
if (!['runtime', 'linter', 'prompt-guidance', 'reference', 'example'].includes(rule.enforcement)) {
|
|
525
|
+
this.addDiagnostic(
|
|
526
|
+
CODES.SCHEMA_ERROR,
|
|
527
|
+
`Rule '${rule.id}' has invalid enforcement '${rule.enforcement}'.`,
|
|
528
|
+
{ file: relManifestPath, path: `/rules/${i}/enforcement` }
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
if (['runtime', 'linter'].includes(rule.enforcement)) {
|
|
532
|
+
if (!rule.checker) {
|
|
533
|
+
this.addDiagnostic(
|
|
534
|
+
CODES.MISSING_RULE_CHECKER,
|
|
535
|
+
`Rule '${rule.id}' marked enforcement '${rule.enforcement}' requires an automated checker, but none was provided.`,
|
|
536
|
+
{ file: relManifestPath, path: `/rules/${i}/checker` }
|
|
537
|
+
);
|
|
538
|
+
} else if (!AUTOMATED_CHECKERS[rule.checker]) {
|
|
539
|
+
this.addDiagnostic(
|
|
540
|
+
CODES.MISSING_RULE_CHECKER,
|
|
541
|
+
`Rule '${rule.id}' references unknown automated checker '${rule.checker}'.`,
|
|
542
|
+
{ file: relManifestPath, path: `/rules/${i}/checker` }
|
|
543
|
+
);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* Validates cross-skill dependency graph (closure, cycles, conflicts).
|
|
552
|
+
*/
|
|
553
|
+
validateDependencyGraph(manifestsById) {
|
|
554
|
+
const knownIds = new Set(Object.keys(manifestsById));
|
|
555
|
+
|
|
556
|
+
// Well-known external frameworks (not internal skills)
|
|
557
|
+
const EXTERNAL_NAMES = new Set([
|
|
558
|
+
'vue', 'angular', 'svelte', 'remix', 'express', 'koa', 'hapi', 'sails',
|
|
559
|
+
'tailwind', 'prisma', 'drizzle', 'mongoose', 'sequelize', 'typeorm',
|
|
560
|
+
'next-auth', 'react-query', 'zustand', 'redux', 'mobx', 'jotai', 'valtio',
|
|
561
|
+
'vitest', 'jest', 'mocha', 'cypress', 'playwright', 'storybook',
|
|
562
|
+
'graphql', 'trpc', 'apollo', 'relay', 'urql',
|
|
563
|
+
'sqlite', 'simple-auth', 'kubernetes', 'monitoring', 'cicd', 'cqrs',
|
|
564
|
+
'redis', 'postgres', 'postgresql', 'jwt', 'auth'
|
|
565
|
+
]);
|
|
566
|
+
|
|
567
|
+
// 1. Unknown dependencies & direct conflict violations
|
|
568
|
+
for (const [id, m] of Object.entries(manifestsById)) {
|
|
569
|
+
for (const req of m.dependencies.requires) {
|
|
570
|
+
if (!knownIds.has(req)) {
|
|
571
|
+
this.addDiagnostic(
|
|
572
|
+
CODES.UNKNOWN_DEPENDENCY,
|
|
573
|
+
`Skill '${id}' requires unknown skill '${req}'.`,
|
|
574
|
+
{ file: m.source, path: '/dependencies/requires', remediation: `Ensure skill '${req}' is present in .agents/core/skills.` }
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Direct conflict violation
|
|
579
|
+
if (m.dependencies.conflicts.includes(req)) {
|
|
580
|
+
this.addDiagnostic(
|
|
581
|
+
CODES.CONFLICT_VIOLATION,
|
|
582
|
+
`Skill '${id}' both requires and conflicts with '${req}'.`,
|
|
583
|
+
{ file: m.source, path: '/dependencies/conflicts' }
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
const reqManifest = manifestsById[req];
|
|
588
|
+
if (reqManifest && reqManifest.dependencies.conflicts.includes(id)) {
|
|
589
|
+
this.addDiagnostic(
|
|
590
|
+
CODES.CONFLICT_VIOLATION,
|
|
591
|
+
`Skill '${id}' requires '${req}', but '${req}' conflicts with '${id}'.`,
|
|
592
|
+
{ file: m.source, path: '/dependencies/requires' }
|
|
593
|
+
);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// 2. Cycle detection via DFS
|
|
599
|
+
const visited = new Map(); // id -> 'unvisited' | 'visiting' | 'visited'
|
|
600
|
+
for (const id of knownIds) visited.set(id, 'unvisited');
|
|
601
|
+
|
|
602
|
+
const checkCycle = (curr, stack) => {
|
|
603
|
+
visited.set(curr, 'visiting');
|
|
604
|
+
stack.push(curr);
|
|
605
|
+
|
|
606
|
+
const m = manifestsById[curr];
|
|
607
|
+
if (m) {
|
|
608
|
+
for (const dep of m.dependencies.requires) {
|
|
609
|
+
if (!knownIds.has(dep)) continue;
|
|
610
|
+
const status = visited.get(dep);
|
|
611
|
+
if (status === 'visiting') {
|
|
612
|
+
const cyclePath = [...stack.slice(stack.indexOf(dep)), dep].join(' -> ');
|
|
613
|
+
this.addDiagnostic(
|
|
614
|
+
CODES.CYCLE_DETECTED,
|
|
615
|
+
`Dependency cycle detected: ${cyclePath}`,
|
|
616
|
+
{ file: m.source, path: '/dependencies/requires' }
|
|
617
|
+
);
|
|
618
|
+
} else if (status === 'unvisited') {
|
|
619
|
+
checkCycle(dep, stack);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
stack.pop();
|
|
625
|
+
visited.set(curr, 'visited');
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
for (const id of knownIds) {
|
|
629
|
+
if (visited.get(id) === 'unvisited') {
|
|
630
|
+
checkCycle(id, []);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Computes sha256 hash for a file.
|
|
637
|
+
*/
|
|
638
|
+
hashFile(filePath) {
|
|
639
|
+
if (!fs.existsSync(filePath)) return null;
|
|
640
|
+
const content = fs.readFileSync(filePath);
|
|
641
|
+
return 'sha256:' + crypto.createHash('sha256').update(content).digest('hex');
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Computes sha256 hash for a string.
|
|
646
|
+
*/
|
|
647
|
+
hashString(str) {
|
|
648
|
+
return 'sha256:' + crypto.createHash('sha256').update(str, 'utf8').digest('hex');
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Compiles all skills in coreSkillsDir and plugins.
|
|
653
|
+
* Generates deterministic CompiledRegistryV2 structure.
|
|
654
|
+
*/
|
|
655
|
+
compile() {
|
|
656
|
+
this.diagnostics = [];
|
|
657
|
+
const manifestsById = {};
|
|
658
|
+
const skillDirs = [];
|
|
659
|
+
|
|
660
|
+
// 1. Discover skill directories
|
|
661
|
+
if (fs.existsSync(this.coreSkillsDir)) {
|
|
662
|
+
const entries = fs.readdirSync(this.coreSkillsDir, { withFileTypes: true });
|
|
663
|
+
for (const entry of entries) {
|
|
664
|
+
if (entry.isDirectory()) {
|
|
665
|
+
skillDirs.push(path.join(this.coreSkillsDir, entry.name));
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Also check plugins
|
|
671
|
+
const pluginsDir = path.join(this.agentsDir, 'plugins');
|
|
672
|
+
if (fs.existsSync(pluginsDir)) {
|
|
673
|
+
const pluginEntries = fs.readdirSync(pluginsDir, { withFileTypes: true });
|
|
674
|
+
for (const pe of pluginEntries) {
|
|
675
|
+
if (pe.isDirectory()) {
|
|
676
|
+
const pSkills = path.join(pluginsDir, pe.name, 'skills');
|
|
677
|
+
if (fs.existsSync(pSkills)) {
|
|
678
|
+
const subs = fs.readdirSync(pSkills, { withFileTypes: true });
|
|
679
|
+
for (const sub of subs) {
|
|
680
|
+
if (sub.isDirectory()) skillDirs.push(path.join(pSkills, sub.name));
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Sort skill directories for determinism
|
|
688
|
+
skillDirs.sort();
|
|
689
|
+
|
|
690
|
+
// 2. Parse and normalize manifests
|
|
691
|
+
for (const dir of skillDirs) {
|
|
692
|
+
const v2File = path.join(dir, 'skill.v2.yaml');
|
|
693
|
+
const v1File = path.join(dir, 'skill.yaml');
|
|
694
|
+
const manifestFile = fs.existsSync(v2File) ? v2File : (fs.existsSync(v1File) ? v1File : null);
|
|
695
|
+
|
|
696
|
+
if (!manifestFile) {
|
|
697
|
+
// Synthesize minimal manifest from directory name & SKILL.md
|
|
698
|
+
const id = path.basename(dir);
|
|
699
|
+
const norm = this.normalizeManifest(dir, `id: ${id}\nname: ${id}\n`);
|
|
700
|
+
norm.source = path.relative(this.rootDir, dir).replace(/\\/g, '/');
|
|
701
|
+
manifestsById[id] = norm;
|
|
702
|
+
continue;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
try {
|
|
706
|
+
const rawYaml = fs.readFileSync(manifestFile, 'utf8');
|
|
707
|
+
const norm = this.normalizeManifest(dir, rawYaml);
|
|
708
|
+
norm.source = path.relative(this.rootDir, manifestFile).replace(/\\/g, '/');
|
|
709
|
+
|
|
710
|
+
if (manifestsById[norm.id]) {
|
|
711
|
+
this.addDiagnostic(
|
|
712
|
+
CODES.DUPLICATE_ID,
|
|
713
|
+
`Duplicate skill id '${norm.id}' found in ${norm.source}`,
|
|
714
|
+
{ file: norm.source, path: '/id' }
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
manifestsById[norm.id] = norm;
|
|
719
|
+
this.validateSingleManifest(dir, norm, manifestFile);
|
|
720
|
+
} catch (err) {
|
|
721
|
+
this.addDiagnostic(
|
|
722
|
+
err.diag ? err.diag.code : CODES.PARSE_ERROR,
|
|
723
|
+
`Failed to parse manifest at ${path.relative(this.rootDir, manifestFile)}: ${err.message}`,
|
|
724
|
+
{
|
|
725
|
+
file: path.relative(this.rootDir, manifestFile),
|
|
726
|
+
line: err.diag ? err.diag.line : undefined,
|
|
727
|
+
column: err.diag ? err.diag.column : undefined
|
|
728
|
+
}
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// 3. Cross-skill dependency graph validation
|
|
734
|
+
this.validateDependencyGraph(manifestsById);
|
|
735
|
+
|
|
736
|
+
// If any errors exist, stop and return diagnostics
|
|
737
|
+
const hasErrors = this.diagnostics.some(d => d.severity === 'error');
|
|
738
|
+
if (hasErrors) {
|
|
739
|
+
return {
|
|
740
|
+
success: false,
|
|
741
|
+
diagnostics: this.diagnostics,
|
|
742
|
+
registry: null,
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// 4. Compute hashes, estimated tokens, aliases and packageMap
|
|
747
|
+
const compiledSkills = {};
|
|
748
|
+
const aliases = {};
|
|
749
|
+
const deprecatedAliases = {};
|
|
750
|
+
const packageMap = {};
|
|
751
|
+
const dependencyGraph = {};
|
|
752
|
+
const rules = {};
|
|
753
|
+
|
|
754
|
+
const sortedIds = Object.keys(manifestsById).sort();
|
|
755
|
+
|
|
756
|
+
for (const id of sortedIds) {
|
|
757
|
+
const m = manifestsById[id];
|
|
758
|
+
const dir = path.join(this.rootDir, path.dirname(m.source));
|
|
759
|
+
const entrypointFile = path.join(dir, m.entrypoint);
|
|
760
|
+
|
|
761
|
+
// Entrypoint hash
|
|
762
|
+
const entrypointHash = this.hashFile(entrypointFile) || 'sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
|
|
763
|
+
|
|
764
|
+
// Estimated tokens: entrypoint character length / 3.8
|
|
765
|
+
let estimatedTokens = 1000;
|
|
766
|
+
if (fs.existsSync(entrypointFile)) {
|
|
767
|
+
const chars = fs.readFileSync(entrypointFile, 'utf8').length;
|
|
768
|
+
estimatedTokens = Math.max(100, Math.ceil(chars / 3.8));
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// Resource tree hash
|
|
772
|
+
const resHashes = [];
|
|
773
|
+
for (const res of m.resources) {
|
|
774
|
+
const rFile = path.join(dir, res.path);
|
|
775
|
+
const rHash = this.hashFile(rFile);
|
|
776
|
+
if (rHash) resHashes.push(`${res.path}:${rHash}`);
|
|
777
|
+
}
|
|
778
|
+
resHashes.sort();
|
|
779
|
+
const resourceTreeHash = this.hashString(resHashes.join('\n'));
|
|
780
|
+
|
|
781
|
+
compiledSkills[id] = {
|
|
782
|
+
id: m.id,
|
|
783
|
+
displayName: m.displayName,
|
|
784
|
+
version: m.version,
|
|
785
|
+
description: m.description,
|
|
786
|
+
category: m.category,
|
|
787
|
+
type: m.type,
|
|
788
|
+
entrypoint: m.entrypoint,
|
|
789
|
+
entrypointHash,
|
|
790
|
+
resourceTreeHash,
|
|
791
|
+
estimatedTokens,
|
|
792
|
+
source: m.source,
|
|
793
|
+
signals: {
|
|
794
|
+
aliases: m.signals.aliases.sort(),
|
|
795
|
+
keywords: m.signals.keywords,
|
|
796
|
+
fileGlobs: m.signals.fileGlobs,
|
|
797
|
+
packages: m.signals.packages,
|
|
798
|
+
},
|
|
799
|
+
dependencies: {
|
|
800
|
+
requires: m.dependencies.requires.sort(),
|
|
801
|
+
optional: m.dependencies.optional.sort(),
|
|
802
|
+
conflicts: m.dependencies.conflicts.sort(),
|
|
803
|
+
},
|
|
804
|
+
context: {
|
|
805
|
+
priority: m.context.priority,
|
|
806
|
+
estimatedTokens,
|
|
807
|
+
},
|
|
808
|
+
resources: m.resources,
|
|
809
|
+
deprecated: m.deprecated || false,
|
|
810
|
+
canonical: m.canonical || null,
|
|
811
|
+
rules: m.rules || [],
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
// Register deprecated alias mapping
|
|
815
|
+
if (m.deprecated && m.canonical) {
|
|
816
|
+
deprecatedAliases[id] = m.canonical;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// Register rules
|
|
820
|
+
if (Array.isArray(m.rules)) {
|
|
821
|
+
for (const r of m.rules) {
|
|
822
|
+
rules[r.id] = {
|
|
823
|
+
...r,
|
|
824
|
+
sourceSkill: id,
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Register aliases
|
|
830
|
+
for (const alias of m.signals.aliases) {
|
|
831
|
+
if (!aliases[alias]) {
|
|
832
|
+
aliases[alias] = id;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Register packageMap
|
|
837
|
+
for (const pkg of m.signals.packages) {
|
|
838
|
+
packageMap[`${pkg.ecosystem}:${pkg.name}`] = id;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// Register dependencyGraph
|
|
842
|
+
dependencyGraph[id] = m.dependencies.requires.sort();
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Overall sourceGraphHash
|
|
846
|
+
const sourceGraphHash = this.hashString(JSON.stringify({
|
|
847
|
+
skills: compiledSkills,
|
|
848
|
+
dependencyGraph,
|
|
849
|
+
}));
|
|
850
|
+
|
|
851
|
+
const registry = {
|
|
852
|
+
schemaVersion: 2,
|
|
853
|
+
compilerVersion: '2.0.0',
|
|
854
|
+
sourceGraphHash,
|
|
855
|
+
skills: compiledSkills,
|
|
856
|
+
aliases: sortObjectKeys(aliases),
|
|
857
|
+
deprecatedAliases: sortObjectKeys(deprecatedAliases),
|
|
858
|
+
packageMap: sortObjectKeys(packageMap),
|
|
859
|
+
dependencyGraph: sortObjectKeys(dependencyGraph),
|
|
860
|
+
rules: sortObjectKeys(rules),
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
return {
|
|
864
|
+
success: true,
|
|
865
|
+
diagnostics: this.diagnostics,
|
|
866
|
+
registry,
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
/**
|
|
871
|
+
* Compiles and writes .agents/compiled/registry.v2.json and .agents/compiled/registry.v2.sha256.
|
|
872
|
+
*/
|
|
873
|
+
compileAndWrite() {
|
|
874
|
+
const result = this.compile();
|
|
875
|
+
if (!result.success) {
|
|
876
|
+
return result;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
if (!fs.existsSync(this.compiledDir)) {
|
|
880
|
+
fs.mkdirSync(this.compiledDir, { recursive: true });
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
const registryJsonPath = path.join(this.compiledDir, 'registry.v2.json');
|
|
884
|
+
const shaPath = path.join(this.compiledDir, 'registry.v2.sha256');
|
|
885
|
+
|
|
886
|
+
const jsonString = JSON.stringify(result.registry, null, 2) + '\n';
|
|
887
|
+
fs.writeFileSync(registryJsonPath, jsonString, 'utf8');
|
|
888
|
+
|
|
889
|
+
const shaContent = result.registry.sourceGraphHash + ' registry.v2.json\n';
|
|
890
|
+
fs.writeFileSync(shaPath, shaContent, 'utf8');
|
|
891
|
+
|
|
892
|
+
return {
|
|
893
|
+
...result,
|
|
894
|
+
registryJsonPath,
|
|
895
|
+
shaPath,
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* Formats diagnostics as SARIF 2.1.0 output.
|
|
901
|
+
*/
|
|
902
|
+
formatSarif() {
|
|
903
|
+
const rules = [];
|
|
904
|
+
const results = [];
|
|
905
|
+
|
|
906
|
+
const ruleIds = new Set(this.diagnostics.map(d => d.code));
|
|
907
|
+
for (const code of ruleIds) {
|
|
908
|
+
rules.push({
|
|
909
|
+
id: code,
|
|
910
|
+
name: code,
|
|
911
|
+
shortDescription: { text: `ContextOS Manifest Rule ${code}` },
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
for (const d of this.diagnostics) {
|
|
916
|
+
results.push({
|
|
917
|
+
ruleId: d.code,
|
|
918
|
+
level: d.severity === 'error' ? 'error' : 'warning',
|
|
919
|
+
message: { text: d.message },
|
|
920
|
+
locations: [
|
|
921
|
+
{
|
|
922
|
+
physicalLocation: {
|
|
923
|
+
artifactLocation: { uri: d.file.replace(/\\/g, '/') },
|
|
924
|
+
...(d.line ? { region: { startLine: d.line, ...(d.column ? { startColumn: d.column } : {}) } } : {})
|
|
925
|
+
},
|
|
926
|
+
},
|
|
927
|
+
],
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
return {
|
|
932
|
+
$schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
|
|
933
|
+
version: '2.1.0',
|
|
934
|
+
runs: [
|
|
935
|
+
{
|
|
936
|
+
tool: {
|
|
937
|
+
driver: {
|
|
938
|
+
name: 'contextos-manifest-compiler',
|
|
939
|
+
version: '2.0.0',
|
|
940
|
+
rules,
|
|
941
|
+
},
|
|
942
|
+
},
|
|
943
|
+
results,
|
|
944
|
+
},
|
|
945
|
+
],
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
function sortObjectKeys(obj) {
|
|
951
|
+
const sorted = {};
|
|
952
|
+
for (const k of Object.keys(obj).sort()) {
|
|
953
|
+
sorted[k] = obj[k];
|
|
954
|
+
}
|
|
955
|
+
return sorted;
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
module.exports = {
|
|
959
|
+
ManifestCompiler,
|
|
960
|
+
DiagnosticError,
|
|
961
|
+
CODES,
|
|
962
|
+
parseYaml,
|
|
963
|
+
};
|