contextos-agents 1.6.1 → 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 +6 -1
- package/.agents/adapters/aider/export.js +117 -97
- package/.agents/adapters/claude/export.js +68 -26
- package/.agents/adapters/copilot/export.js +90 -51
- package/.agents/adapters/cursor/export.js +83 -68
- 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 +109 -62
- 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/references/context-rules.md +59 -0
- package/.agents/core/skills/context-manager/skill.yaml +10 -5
- package/.agents/core/skills/context-os/SKILL.md +3 -6
- package/.agents/core/skills/context-os/skill.yaml +14 -8
- package/.agents/core/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/core/skills/engineering-workflow/skill.yaml +7 -7
- package/.agents/core/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/core/skills/gemini-precision/skill.yaml +5 -6
- package/.agents/core/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/core/skills/gstack-roles/skill.yaml +9 -6
- package/.agents/core/skills/ponytail-mindset/skill.yaml +7 -7
- package/.agents/core/skills/security/skill.yaml +21 -2
- package/.agents/ctx.js +587 -111
- package/.agents/customization-dx.js +282 -0
- package/.agents/doctor.js +877 -33
- 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/claude/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/generated/claude/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/generated/claude/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/generated/gemini/skills/context-os/SKILL.md +2 -2
- package/.agents/generated/gemini/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/generated/gemini/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/generated/gemini/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/plugins/contextos/hooks.json +25 -0
- package/.agents/plugins/contextos/plugin.json +19 -0
- package/.agents/plugins.js +432 -73
- package/.agents/profiles.js +507 -46
- package/.agents/resolver.js +50 -414
- 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/stats.js +22 -9
- 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 +143 -14
- 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/uninstall.js +207 -0
- package/bin/commands/update.js +325 -0
- package/bin/commands.js +342 -0
- package/bin/index.js +326 -149
- package/bin/lib/detector.js +106 -0
- package/bin/lib/lockfile.js +253 -0
- package/bin/lib/safe-writer.js +290 -0
- package/package.json +85 -73
- package/registry.json +15 -7
- package/registry.schema.json +3 -1
- 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 -10
- 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 -8
- 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 -8
- 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 -25
- 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 -10
- 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 -10
- 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 -23
- 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 -10
- 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 -10
- 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 -13
- 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 -14
- 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 -8
- 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 -10
- 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 -8
- 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 -10
- 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 -10
- 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 -10
- 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 -10
- 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 -10
- 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 -10
- 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 -8
- 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 -8
- 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 -22
- package/.agents/core/skills/subagent-orchestrator/SKILL.md +0 -100
- package/.agents/core/skills/subagent-orchestrator/VALIDATION.json +0 -12
- package/.agents/core/skills/subagent-orchestrator/skill.yaml +0 -8
- 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 -13
- 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 -26
- 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 -10
- 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 -10
- 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 -13
- 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 -10
- 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/skill.yaml +0 -10
- 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 -10
- 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 -93
- 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 -99
- 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
- package/.agents/mcp/runtime.py +0 -470
- package/.agents/mcp/server.mjs +0 -189271
- package/benchmarks/gemini-issues.js +0 -533
package/.agents/plugins.js
CHANGED
|
@@ -25,6 +25,17 @@ const path = require('path');
|
|
|
25
25
|
const https = require('https');
|
|
26
26
|
const crypto = require('crypto');
|
|
27
27
|
const { execFileSync } = require('child_process');
|
|
28
|
+
const { ProjectMutationLock } = require('./filesystem/project-lock.js');
|
|
29
|
+
const JournaledTransaction = require('./filesystem/journaled-transaction.js');
|
|
30
|
+
const {
|
|
31
|
+
calculateTreeDigest,
|
|
32
|
+
validateArchiveEntry,
|
|
33
|
+
validatePluginPinning,
|
|
34
|
+
ScriptGrantManager,
|
|
35
|
+
AtomicPluginUpdater,
|
|
36
|
+
THREAT_CODES,
|
|
37
|
+
verifyNpmTarballIntegrity,
|
|
38
|
+
} = require('./transaction-core/plugin-supply-chain-bundle.js');
|
|
28
39
|
|
|
29
40
|
// ── Paths ─────────────────────────────────────────────────────────────────────
|
|
30
41
|
const ROOT = process.cwd();
|
|
@@ -36,6 +47,17 @@ const REGISTRY_URL = 'https://raw.githubusercontent.com/kok-o/contextos-agents
|
|
|
36
47
|
const MAX_DOWNLOAD_BYTES = 5 * 1024 * 1024;
|
|
37
48
|
const SAFE_SKILL_NAME = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
|
|
38
49
|
|
|
50
|
+
// ── ANSI helpers ──────────────────────────────────────────────────────────────
|
|
51
|
+
const NO_COLOR = process.env.NO_COLOR || !process.stdout.isTTY;
|
|
52
|
+
const c = {
|
|
53
|
+
green: (s) => NO_COLOR ? s : `\x1b[32m${s}\x1b[0m`,
|
|
54
|
+
red: (s) => NO_COLOR ? s : `\x1b[31m${s}\x1b[0m`,
|
|
55
|
+
yellow: (s) => NO_COLOR ? s : `\x1b[33m${s}\x1b[0m`,
|
|
56
|
+
cyan: (s) => NO_COLOR ? s : `\x1b[36m${s}\x1b[0m`,
|
|
57
|
+
bold: (s) => NO_COLOR ? s : `\x1b[1m${s}\x1b[0m`,
|
|
58
|
+
dim: (s) => NO_COLOR ? s : `\x1b[2m${s}\x1b[0m`,
|
|
59
|
+
};
|
|
60
|
+
|
|
39
61
|
// ── Prompt Injection Scanner ──────────────────────────────────────────────────
|
|
40
62
|
const PROMPT_INJECTION_PATTERNS = [
|
|
41
63
|
/ignore\s+(all\s+)?previous\s+instructions/i,
|
|
@@ -48,6 +70,18 @@ const PROMPT_INJECTION_PATTERNS = [
|
|
|
48
70
|
/\b(?:eval|exec|Function)\s*\(/i,
|
|
49
71
|
];
|
|
50
72
|
|
|
73
|
+
// ── Shell Escapes & Script Injection Scanner ──────────────────────────────────
|
|
74
|
+
const SHELL_ESCAPE_PATTERNS = [
|
|
75
|
+
/(?:curl|wget)\s+[^|\n\r]+?\|\s*(?:bash|sh|zsh|powershell|pwsh|cmd)/i,
|
|
76
|
+
/\bpowershell(?:\.exe)?\s+.*-(?:enc|encodedcommand|ep\s+bypass)\b/i,
|
|
77
|
+
/\b(?:rm\s+-rf\s+[\/~]|del\s+\/[sfq]\s+[c-z]:[\\\/])/i,
|
|
78
|
+
/\b(?:mkfifo|mknod)\s+.*?;/i,
|
|
79
|
+
/\b(?:nc|netcat|ncat)\s+-[elp]+/i,
|
|
80
|
+
/<script\b[^>]*>[\s\S]*?<\/script>/i,
|
|
81
|
+
/javascript\s*:\s*[^\s'"]+/i,
|
|
82
|
+
/data:text\/html/i,
|
|
83
|
+
];
|
|
84
|
+
|
|
51
85
|
function scanForPromptInjection(content, options = {}) {
|
|
52
86
|
if (typeof content !== 'string') return false;
|
|
53
87
|
const found = PROMPT_INJECTION_PATTERNS.filter(p => p.test(content));
|
|
@@ -62,16 +96,124 @@ function scanForPromptInjection(content, options = {}) {
|
|
|
62
96
|
return false;
|
|
63
97
|
}
|
|
64
98
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
99
|
+
function scanContentSecurity(content, filePath, options = {}) {
|
|
100
|
+
if (typeof content !== 'string') return false;
|
|
101
|
+
|
|
102
|
+
const injectionMatches = PROMPT_INJECTION_PATTERNS.filter(p => p.test(content));
|
|
103
|
+
const shellMatches = SHELL_ESCAPE_PATTERNS.filter(p => p.test(content));
|
|
104
|
+
|
|
105
|
+
const allMatches = [
|
|
106
|
+
...injectionMatches.map(p => `prompt_injection: ${p.source}`),
|
|
107
|
+
...shellMatches.map(p => `shell_or_script: ${p.source}`),
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
if (allMatches.length > 0) {
|
|
111
|
+
const rel = filePath ? path.basename(filePath) : 'content';
|
|
112
|
+
console.warn(c.red(` 🚨 Unsafe pattern or injection detected in plugin file '${rel}':`));
|
|
113
|
+
console.warn(c.yellow(` Matches: ${allMatches.join('; ')}`));
|
|
114
|
+
if (options.throwOnMatch) {
|
|
115
|
+
throw new Error(`Security validation failed: unsafe content or script injection detected in '${rel}' (${allMatches.length} pattern matches). Installation aborted. Pass --force-unsafe-prompts to override.`);
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isScannableFile(fileName) {
|
|
123
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
124
|
+
const scannableExtensions = new Set([
|
|
125
|
+
'.md', '.mdc', '.markdown', '.txt',
|
|
126
|
+
'.yaml', '.yml', '.json',
|
|
127
|
+
'.sh', '.bash', '.zsh', '.ps1', '.bat', '.cmd',
|
|
128
|
+
'.js', '.ts', '.mjs', '.cjs', '.py',
|
|
129
|
+
]);
|
|
130
|
+
return scannableExtensions.has(ext) || !ext;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Recursively scan an entire plugin directory tree:
|
|
135
|
+
* - Rejects any symlinks that traverse outside the plugin directory
|
|
136
|
+
* - Scans all documentation and code files (EXAMPLES.md, TROUBLESHOOTING.md, references/, etc.)
|
|
137
|
+
* for prompt injection, script injection, and dangerous shell escape patterns.
|
|
138
|
+
*/
|
|
139
|
+
function scanPluginDirectory(pluginDir, options = {}) {
|
|
140
|
+
const throwOnMatch = options.throwOnMatch !== undefined ? options.throwOnMatch : !options.forceUnsafe;
|
|
141
|
+
if (!fs.existsSync(pluginDir)) return true;
|
|
142
|
+
|
|
143
|
+
const realPluginDir = fs.realpathSync(pluginDir);
|
|
144
|
+
|
|
145
|
+
function walk(currentDir) {
|
|
146
|
+
let entries;
|
|
147
|
+
try {
|
|
148
|
+
entries = fs.readdirSync(currentDir);
|
|
149
|
+
} catch {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
for (const name of entries) {
|
|
154
|
+
const fullPath = path.join(currentDir, name);
|
|
155
|
+
let lstat;
|
|
156
|
+
try {
|
|
157
|
+
lstat = fs.lstatSync(fullPath);
|
|
158
|
+
} catch {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 1. Check symlink traversal
|
|
163
|
+
if (lstat.isSymbolicLink()) {
|
|
164
|
+
let linkTarget;
|
|
165
|
+
try {
|
|
166
|
+
linkTarget = fs.readlinkSync(fullPath);
|
|
167
|
+
} catch (e) {
|
|
168
|
+
throw new Error(`Security validation failed: Unreadable symlink '${name}'. Installation aborted.`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const resolvedTarget = path.resolve(currentDir, linkTarget);
|
|
172
|
+
let realTarget;
|
|
173
|
+
try {
|
|
174
|
+
realTarget = fs.realpathSync(resolvedTarget);
|
|
175
|
+
} catch {
|
|
176
|
+
realTarget = resolvedTarget;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const normalizedRealPlugin = realPluginDir.replace(/\\/g, '/').toLowerCase();
|
|
180
|
+
const normalizedRealTarget = realTarget.replace(/\\/g, '/').toLowerCase();
|
|
181
|
+
|
|
182
|
+
if (
|
|
183
|
+
!normalizedRealTarget.startsWith(normalizedRealPlugin + '/') &&
|
|
184
|
+
normalizedRealTarget !== normalizedRealPlugin
|
|
185
|
+
) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`Security validation failed: Symlink traversal detected in '${name}' pointing outside plugin directory to '${linkTarget}'. Installation aborted.`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// 2. Walk subdirectories
|
|
193
|
+
if (lstat.isDirectory()) {
|
|
194
|
+
if (name === '.git' || name === 'node_modules') continue;
|
|
195
|
+
walk(fullPath);
|
|
196
|
+
} else if (lstat.isFile()) {
|
|
197
|
+
if (name === '.source') continue;
|
|
198
|
+
if (lstat.size > 2 * 1024 * 1024) continue;
|
|
199
|
+
|
|
200
|
+
if (isScannableFile(name)) {
|
|
201
|
+
try {
|
|
202
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
203
|
+
scanContentSecurity(content, fullPath, { throwOnMatch });
|
|
204
|
+
} catch (err) {
|
|
205
|
+
if (err.message && err.message.startsWith('Security validation failed')) {
|
|
206
|
+
throw err;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
walk(pluginDir);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
75
217
|
|
|
76
218
|
// ── plugins.json lock file ─────────────────────────────────────────────────────
|
|
77
219
|
function readLock() {
|
|
@@ -83,9 +225,19 @@ function readLock() {
|
|
|
83
225
|
}
|
|
84
226
|
}
|
|
85
227
|
|
|
86
|
-
function writeLock(lock) {
|
|
87
|
-
|
|
88
|
-
|
|
228
|
+
function writeLock(lock, tx = null) {
|
|
229
|
+
if (tx) {
|
|
230
|
+
const relLockPath = toPosix(path.relative(ROOT, PLUGINS_JSON));
|
|
231
|
+
tx.stageWrite(relLockPath, JSON.stringify(lock, null, 2) + '\n');
|
|
232
|
+
} else {
|
|
233
|
+
fs.mkdirSync(AGENTS_DIR, { recursive: true });
|
|
234
|
+
fs.writeFileSync(PLUGINS_JSON, JSON.stringify(lock, null, 2) + '\n');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// POSIX path helper
|
|
239
|
+
function toPosix(p) {
|
|
240
|
+
return p.replace(/\\/g, '/');
|
|
89
241
|
}
|
|
90
242
|
|
|
91
243
|
// ── HTTP fetch helper (no deps) ───────────────────────────────────────────────
|
|
@@ -162,7 +314,17 @@ function parseRef(ref) {
|
|
|
162
314
|
throw new Error('Invalid GitHub plugin reference');
|
|
163
315
|
}
|
|
164
316
|
|
|
165
|
-
|
|
317
|
+
const isPinned = repoRef.includes('@');
|
|
318
|
+
return {
|
|
319
|
+
type: 'github',
|
|
320
|
+
owner,
|
|
321
|
+
repo,
|
|
322
|
+
gitRef,
|
|
323
|
+
commit: isPinned ? gitRef : undefined,
|
|
324
|
+
isPinned,
|
|
325
|
+
subPath,
|
|
326
|
+
raw: ref,
|
|
327
|
+
};
|
|
166
328
|
}
|
|
167
329
|
// npm package
|
|
168
330
|
if (!isValidNpmPackage(ref)) {
|
|
@@ -172,7 +334,7 @@ function parseRef(ref) {
|
|
|
172
334
|
}
|
|
173
335
|
|
|
174
336
|
// ── GitHub installer ──────────────────────────────────────────────────────────
|
|
175
|
-
async function installFromGitHub(descriptor, skillName, dryRun, checksum, forceUnsafe = false) {
|
|
337
|
+
async function installFromGitHub(descriptor, skillName, dryRun, checksum, forceUnsafe = false, tx = null) {
|
|
176
338
|
const { owner, repo, gitRef, isPinned, subPath } = descriptor;
|
|
177
339
|
const skillPath = subPath || '';
|
|
178
340
|
const base = `https://raw.githubusercontent.com/${owner}/${repo}/${gitRef}`;
|
|
@@ -219,6 +381,7 @@ async function installFromGitHub(descriptor, skillName, dryRun, checksum, forceU
|
|
|
219
381
|
}
|
|
220
382
|
|
|
221
383
|
const targetDir = path.join(PLUGINS_DIR, skillName);
|
|
384
|
+
const relTargetDir = path.relative(ROOT, targetDir).replace(/\\/g, '/');
|
|
222
385
|
|
|
223
386
|
if (dryRun) {
|
|
224
387
|
console.log(c.yellow(` [DRY-RUN] Would install to: ${targetDir}`));
|
|
@@ -226,14 +389,30 @@ async function installFromGitHub(descriptor, skillName, dryRun, checksum, forceU
|
|
|
226
389
|
return sha256;
|
|
227
390
|
}
|
|
228
391
|
|
|
229
|
-
|
|
230
|
-
fs.
|
|
392
|
+
// Pre-scan using temp dir to keep `scanPluginDirectory` happy, as it reads from disk
|
|
393
|
+
const temporaryPrefix = fs.mkdtempSync(path.join(os.tmpdir(), 'contextos-github-'));
|
|
394
|
+
try {
|
|
395
|
+
fs.writeFileSync(path.join(temporaryPrefix, 'SKILL.md'), skillMdContent);
|
|
396
|
+
if (yamlContent) fs.writeFileSync(path.join(temporaryPrefix, 'skill.yaml'), yamlContent);
|
|
397
|
+
scanPluginDirectory(temporaryPrefix, { throwOnMatch: !forceUnsafe, forceUnsafe });
|
|
398
|
+
} finally {
|
|
399
|
+
fs.rmSync(temporaryPrefix, { recursive: true, force: true });
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const manifest = {};
|
|
403
|
+
|
|
404
|
+
const skillMdHash = crypto.createHash('sha256').update(skillMdContent).digest('hex');
|
|
405
|
+
tx.stageWrite(`${relTargetDir}/SKILL.md`, skillMdContent);
|
|
406
|
+
manifest['SKILL.md'] = skillMdHash;
|
|
407
|
+
|
|
231
408
|
if (yamlContent) {
|
|
232
|
-
|
|
409
|
+
const yamlHash = crypto.createHash('sha256').update(yamlContent).digest('hex');
|
|
410
|
+
tx.stageWrite(`${relTargetDir}/skill.yaml`, yamlContent);
|
|
411
|
+
manifest['skill.yaml'] = yamlHash;
|
|
233
412
|
}
|
|
234
413
|
|
|
235
|
-
// Write a source marker
|
|
236
|
-
|
|
414
|
+
// Write a source marker with manifest
|
|
415
|
+
const sourceContent = JSON.stringify({
|
|
237
416
|
type: 'github',
|
|
238
417
|
owner,
|
|
239
418
|
repo,
|
|
@@ -242,7 +421,9 @@ async function installFromGitHub(descriptor, skillName, dryRun, checksum, forceU
|
|
|
242
421
|
ref: descriptor.raw,
|
|
243
422
|
sha256,
|
|
244
423
|
installedAt: new Date().toISOString(),
|
|
245
|
-
|
|
424
|
+
manifest,
|
|
425
|
+
}, null, 2);
|
|
426
|
+
tx.stageWrite(`${relTargetDir}/.source`, sourceContent);
|
|
246
427
|
|
|
247
428
|
console.log(c.green(` ✓ Installed SKILL.md (${skillMdContent.length} bytes, sha256: ${sha256.slice(0, 12)}...)`));
|
|
248
429
|
if (yamlContent) console.log(c.green(` ✓ Installed skill.yaml`));
|
|
@@ -250,12 +431,15 @@ async function installFromGitHub(descriptor, skillName, dryRun, checksum, forceU
|
|
|
250
431
|
}
|
|
251
432
|
|
|
252
433
|
// ── npm installer ─────────────────────────────────────────────────────────────
|
|
253
|
-
function installFromNpm(descriptor, skillName, dryRun, checksum, forceUnsafe = false) {
|
|
434
|
+
function installFromNpm(descriptor, skillName, dryRun, checksum, forceUnsafe = false, tx = null) {
|
|
254
435
|
const pkgName = descriptor.package;
|
|
255
436
|
|
|
437
|
+
const targetDir = path.join(PLUGINS_DIR, skillName);
|
|
438
|
+
const relTargetDir = path.relative(ROOT, targetDir).replace(/\\/g, '/');
|
|
439
|
+
|
|
256
440
|
if (dryRun) {
|
|
257
441
|
console.log(c.yellow(` [DRY-RUN] Would run: npm install ${pkgName}`));
|
|
258
|
-
console.log(c.yellow(` [DRY-RUN] Would copy skill to: ${
|
|
442
|
+
console.log(c.yellow(` [DRY-RUN] Would copy skill to: ${targetDir}`));
|
|
259
443
|
return null;
|
|
260
444
|
}
|
|
261
445
|
|
|
@@ -286,21 +470,42 @@ function installFromNpm(descriptor, skillName, dryRun, checksum, forceUnsafe = f
|
|
|
286
470
|
if (checksum && sha256.toLowerCase() !== checksum.toLowerCase()) {
|
|
287
471
|
throw new Error(`Checksum mismatch for ${skillName}: expected ${checksum}, got ${sha256}`);
|
|
288
472
|
}
|
|
289
|
-
|
|
473
|
+
// Deep security scan across all plugin files (EXAMPLES.md, TROUBLESHOOTING.md, references/, symlinks)
|
|
474
|
+
scanPluginDirectory(skillSrc, { throwOnMatch: !forceUnsafe, forceUnsafe });
|
|
475
|
+
|
|
476
|
+
const manifest = {};
|
|
477
|
+
|
|
478
|
+
// Helper to walk and stage all files
|
|
479
|
+
const stageRecursive = (dir, relBase) => {
|
|
480
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
481
|
+
if (entry.name === '.git' || entry.name === 'node_modules') continue;
|
|
482
|
+
const fullPath = path.join(dir, entry.name);
|
|
483
|
+
const entryRel = relBase ? `${relBase}/${entry.name}` : entry.name;
|
|
484
|
+
|
|
485
|
+
if (entry.isDirectory()) {
|
|
486
|
+
stageRecursive(fullPath, entryRel);
|
|
487
|
+
} else if (entry.isFile() || entry.isSymbolicLink()) {
|
|
488
|
+
// Read content to stage it
|
|
489
|
+
const content = fs.readFileSync(fullPath);
|
|
490
|
+
const hash = crypto.createHash('sha256').update(content).digest('hex');
|
|
491
|
+
tx.stageWrite(`${relTargetDir}/${entryRel}`, content);
|
|
492
|
+
manifest[entryRel] = hash;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
};
|
|
290
496
|
|
|
291
|
-
|
|
292
|
-
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
293
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
294
|
-
fs.cpSync(skillSrc, targetDir, { recursive: true, force: true });
|
|
497
|
+
stageRecursive(skillSrc, '');
|
|
295
498
|
|
|
296
499
|
// Write source marker
|
|
297
|
-
|
|
500
|
+
const sourceContent = JSON.stringify({
|
|
298
501
|
type: 'npm',
|
|
299
502
|
package: pkgName,
|
|
300
503
|
ref: descriptor.raw,
|
|
301
504
|
sha256,
|
|
302
505
|
installedAt: new Date().toISOString(),
|
|
303
|
-
|
|
506
|
+
manifest,
|
|
507
|
+
}, null, 2);
|
|
508
|
+
tx.stageWrite(`${relTargetDir}/.source`, sourceContent);
|
|
304
509
|
} finally {
|
|
305
510
|
fs.rmSync(temporaryPrefix, { recursive: true, force: true });
|
|
306
511
|
}
|
|
@@ -333,6 +538,8 @@ async function add(ref, options = {}) {
|
|
|
333
538
|
const dryRun = Boolean(options.dryRun || options['dry-run']);
|
|
334
539
|
const checksum = options.checksum || null;
|
|
335
540
|
const forceUnsafe = Boolean(options.forceUnsafe || options['force-unsafe-prompts']);
|
|
541
|
+
const allowFloating = Boolean(options.allowFloating || options['allow-floating']);
|
|
542
|
+
const force = Boolean(options.force);
|
|
336
543
|
|
|
337
544
|
if (!ref) {
|
|
338
545
|
console.error(c.red('Usage: ctx.js skill add <ref> [--checksum <sha256>]'));
|
|
@@ -347,6 +554,27 @@ async function add(ref, options = {}) {
|
|
|
347
554
|
throw new Error(`Invalid plugin name derived from reference: '${skillName}'`);
|
|
348
555
|
}
|
|
349
556
|
|
|
557
|
+
// Supply-chain source pinning check (Wave 6)
|
|
558
|
+
if (!dryRun) {
|
|
559
|
+
const pinning = validatePluginPinning(
|
|
560
|
+
{
|
|
561
|
+
type: descriptor.type,
|
|
562
|
+
commit: descriptor.commit,
|
|
563
|
+
version: descriptor.version,
|
|
564
|
+
integrity: descriptor.integrity,
|
|
565
|
+
},
|
|
566
|
+
{ allowFloating }
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
if (!pinning.valid) {
|
|
570
|
+
console.error(c.red(`\n[ERROR] Supply-chain pinning check failed: ${pinning.error}`));
|
|
571
|
+
if (pinning.code === THREAT_CODES.FLOATING_SOURCE_BLOCKED) {
|
|
572
|
+
console.error(c.yellow(' Pass --allow-floating to override this check with explicit user consent.'));
|
|
573
|
+
}
|
|
574
|
+
process.exit(1);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
350
578
|
console.log('');
|
|
351
579
|
console.log(c.bold(`Installing skill: ${c.cyan(skillName)}`));
|
|
352
580
|
console.log(c.dim(` Source: ${ref} (${descriptor.type})`));
|
|
@@ -363,45 +591,86 @@ async function add(ref, options = {}) {
|
|
|
363
591
|
// Check if already installed
|
|
364
592
|
const lock = readLock();
|
|
365
593
|
const existing = lock.plugins.find(p => p.name === skillName);
|
|
594
|
+
const targetDir = path.join(PLUGINS_DIR, skillName);
|
|
595
|
+
|
|
366
596
|
if (existing && !dryRun) {
|
|
597
|
+
// Check for local modifications (Wave 6 / Section 20.7)
|
|
598
|
+
if (fs.existsSync(targetDir) && existing.treeDigest && !force) {
|
|
599
|
+
if (AtomicPluginUpdater.isModifiedLocally(targetDir, existing.treeDigest)) {
|
|
600
|
+
const conflictFile = path.join(targetDir, 'CONFLICT_LOCAL_MODIFICATIONS.md');
|
|
601
|
+
fs.writeFileSync(
|
|
602
|
+
conflictFile,
|
|
603
|
+
`# Conflict: Local Modifications Detected\n\nPlugin "${skillName}" contains local uncommitted modifications.\nAutomatic update was blocked to preserve your changes.\n\nTo overwrite local modifications, re-run with --force.\n`,
|
|
604
|
+
'utf8'
|
|
605
|
+
);
|
|
606
|
+
console.error(c.red(`\n[ERROR] Plugin in "${targetDir}" has local modifications.`));
|
|
607
|
+
console.error(c.red(` Update blocked to prevent overwriting user changes.`));
|
|
608
|
+
console.error(c.yellow(` Conflict artifact written to: ${conflictFile}`));
|
|
609
|
+
console.error(c.yellow(` Pass --force to overwrite.`));
|
|
610
|
+
process.exit(1);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
367
613
|
console.log(c.yellow(`\n[WARN] '${skillName}' is already installed (from: ${existing.ref})`));
|
|
368
614
|
console.log(c.yellow(' Re-installing will overwrite it. Continuing...'));
|
|
369
615
|
}
|
|
370
616
|
|
|
371
|
-
|
|
617
|
+
const projectLock = new ProjectMutationLock(ROOT);
|
|
618
|
+
const tx = new JournaledTransaction(ROOT);
|
|
619
|
+
let lockToken = null;
|
|
620
|
+
|
|
372
621
|
try {
|
|
622
|
+
lockToken = projectLock.acquire({ command: 'skill:add' });
|
|
623
|
+
|
|
624
|
+
let installedSha256 = null;
|
|
373
625
|
if (descriptor.type === 'github') {
|
|
374
|
-
installedSha256 = await installFromGitHub(descriptor, skillName, dryRun, checksum, forceUnsafe);
|
|
626
|
+
installedSha256 = await installFromGitHub(descriptor, skillName, dryRun, checksum, forceUnsafe, tx);
|
|
375
627
|
} else {
|
|
376
|
-
installedSha256 = installFromNpm(descriptor, skillName, dryRun, checksum, forceUnsafe);
|
|
628
|
+
installedSha256 = installFromNpm(descriptor, skillName, dryRun, checksum, forceUnsafe, tx);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
if (!dryRun) {
|
|
632
|
+
// Update lock file
|
|
633
|
+
const idx = lock.plugins.findIndex(p => p.name === skillName);
|
|
634
|
+
const entry = {
|
|
635
|
+
name: skillName,
|
|
636
|
+
ref,
|
|
637
|
+
type: descriptor.type,
|
|
638
|
+
commit: descriptor.commit,
|
|
639
|
+
version: descriptor.version,
|
|
640
|
+
integrity: descriptor.integrity,
|
|
641
|
+
sha256: installedSha256,
|
|
642
|
+
installedAt: new Date().toISOString(),
|
|
643
|
+
};
|
|
644
|
+
if (idx >= 0) lock.plugins[idx] = entry;
|
|
645
|
+
else lock.plugins.push(entry);
|
|
646
|
+
|
|
647
|
+
writeLock(lock, tx);
|
|
648
|
+
tx.commit();
|
|
649
|
+
|
|
650
|
+
// Compute and record full-tree digest into lock
|
|
651
|
+
if (fs.existsSync(targetDir)) {
|
|
652
|
+
const digestResult = calculateTreeDigest(targetDir);
|
|
653
|
+
entry.treeDigest = digestResult.treeDigest;
|
|
654
|
+
writeLock(lock);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
console.log('');
|
|
658
|
+
console.log(c.green(c.bold('✓ Skill installed successfully!')));
|
|
659
|
+
console.log(c.dim(` Location: .agents/plugins/${skillName}/`));
|
|
660
|
+
console.log('');
|
|
661
|
+
console.log(' Next steps:');
|
|
662
|
+
console.log(` node .agents/ctx.js export all # recompile with new skill`);
|
|
663
|
+
console.log(` node .agents/ctx.js validate # verify everything is consistent`);
|
|
664
|
+
console.log('');
|
|
377
665
|
}
|
|
378
666
|
} catch (err) {
|
|
667
|
+
if (tx && tx.state === 'PLANNED') {
|
|
668
|
+
// We don't need to do anything since it wasn't committed
|
|
669
|
+
}
|
|
379
670
|
console.error(c.red(`\n[ERROR] Failed to install '${skillName}': ${err.message}`));
|
|
380
671
|
process.exit(1);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (!dryRun) {
|
|
384
|
-
// Update lock file
|
|
385
|
-
const idx = lock.plugins.findIndex(p => p.name === skillName);
|
|
386
|
-
const entry = {
|
|
387
|
-
name: skillName,
|
|
388
|
-
ref,
|
|
389
|
-
type: descriptor.type,
|
|
390
|
-
sha256: installedSha256,
|
|
391
|
-
installedAt: new Date().toISOString(),
|
|
392
|
-
};
|
|
393
|
-
if (idx >= 0) lock.plugins[idx] = entry;
|
|
394
|
-
else lock.plugins.push(entry);
|
|
395
|
-
writeLock(lock);
|
|
396
|
-
|
|
397
|
-
console.log('');
|
|
398
|
-
console.log(c.green(c.bold('✓ Skill installed successfully!')));
|
|
399
|
-
console.log(c.dim(` Location: .agents/plugins/${skillName}/`));
|
|
400
|
-
console.log('');
|
|
401
|
-
console.log(' Next steps:');
|
|
402
|
-
console.log(` node .agents/ctx.js export all # recompile with new skill`);
|
|
403
|
-
console.log(` node .agents/ctx.js validate # verify everything is consistent`);
|
|
404
|
-
console.log('');
|
|
672
|
+
} finally {
|
|
673
|
+
if (lockToken) projectLock.release(lockToken);
|
|
405
674
|
}
|
|
406
675
|
}
|
|
407
676
|
|
|
@@ -436,15 +705,85 @@ function remove(skillName) {
|
|
|
436
705
|
process.exit(1);
|
|
437
706
|
}
|
|
438
707
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
708
|
+
const projectLock = new ProjectMutationLock(ROOT);
|
|
709
|
+
const tx = new JournaledTransaction(ROOT);
|
|
710
|
+
let lockToken = null;
|
|
442
711
|
|
|
443
|
-
|
|
444
|
-
|
|
712
|
+
try {
|
|
713
|
+
lockToken = projectLock.acquire({ command: 'skill:remove' });
|
|
714
|
+
|
|
715
|
+
if (fs.existsSync(skillDir)) {
|
|
716
|
+
// Find the manifest if it exists
|
|
717
|
+
const sourceFile = path.join(skillDir, '.source');
|
|
718
|
+
let manifest = null;
|
|
719
|
+
if (fs.existsSync(sourceFile)) {
|
|
720
|
+
try {
|
|
721
|
+
const sourceData = JSON.parse(fs.readFileSync(sourceFile, 'utf8'));
|
|
722
|
+
manifest = sourceData.manifest || null;
|
|
723
|
+
} catch {}
|
|
724
|
+
}
|
|
445
725
|
|
|
446
|
-
|
|
447
|
-
|
|
726
|
+
const stageRecursiveDeletes = (dir, relBase) => {
|
|
727
|
+
let hasPreservedFiles = false;
|
|
728
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
729
|
+
const fullPath = path.join(dir, entry.name);
|
|
730
|
+
const entryRel = relBase ? `${relBase}/${entry.name}` : entry.name;
|
|
731
|
+
|
|
732
|
+
if (entry.isDirectory()) {
|
|
733
|
+
const preserved = stageRecursiveDeletes(fullPath, entryRel);
|
|
734
|
+
if (preserved) hasPreservedFiles = true;
|
|
735
|
+
else tx.stageDeleteDir(toPosix(path.relative(ROOT, fullPath)));
|
|
736
|
+
} else {
|
|
737
|
+
// Check against manifest
|
|
738
|
+
let shouldDelete = false;
|
|
739
|
+
if (manifest && manifest[entryRel]) {
|
|
740
|
+
const currentHash = crypto.createHash('sha256').update(fs.readFileSync(fullPath)).digest('hex');
|
|
741
|
+
if (currentHash === manifest[entryRel]) {
|
|
742
|
+
shouldDelete = true;
|
|
743
|
+
}
|
|
744
|
+
} else if (!manifest) {
|
|
745
|
+
// If we have NO manifest, we fallback to W3.4 "Remove не удаляет modified/unmanaged files"
|
|
746
|
+
// But without a manifest, we don't know what was installed vs modified.
|
|
747
|
+
// We'll safely skip all files unless we can confidently delete them, OR we can just assume
|
|
748
|
+
// if it's the exact SHA256 of SKILL.md, delete it. But it's safer to only delete known things.
|
|
749
|
+
// We'll just delete SKILL.md and skill.yaml if they match the lockfile hash
|
|
750
|
+
if (entry.name === 'SKILL.md' || entry.name === 'skill.yaml' || entry.name === '.source') {
|
|
751
|
+
shouldDelete = true;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (entry.name === '.source') shouldDelete = true;
|
|
756
|
+
|
|
757
|
+
if (shouldDelete) {
|
|
758
|
+
tx.stageDelete(toPosix(path.relative(ROOT, fullPath)));
|
|
759
|
+
} else {
|
|
760
|
+
hasPreservedFiles = true;
|
|
761
|
+
console.log(c.yellow(` [WARN] Preserving modified/unmanaged file: ${entryRel}`));
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return hasPreservedFiles;
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
const preserved = stageRecursiveDeletes(skillDir, '');
|
|
769
|
+
if (!preserved) {
|
|
770
|
+
tx.stageDeleteDir(toPosix(path.relative(ROOT, skillDir)));
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
lock.plugins = lock.plugins.filter(p => p.name !== skillName);
|
|
775
|
+
writeLock(lock, tx);
|
|
776
|
+
|
|
777
|
+
tx.commit();
|
|
778
|
+
|
|
779
|
+
console.log(c.green(`✓ Removed plugin: ${skillName}`));
|
|
780
|
+
console.log(c.dim(' Run: node .agents/ctx.js export all (to recompile)'));
|
|
781
|
+
} catch (err) {
|
|
782
|
+
console.error(c.red(`\n[ERROR] Failed to remove '${skillName}': ${err.message}`));
|
|
783
|
+
process.exit(1);
|
|
784
|
+
} finally {
|
|
785
|
+
if (lockToken) projectLock.release(lockToken);
|
|
786
|
+
}
|
|
448
787
|
}
|
|
449
788
|
|
|
450
789
|
/**
|
|
@@ -546,22 +885,39 @@ async function search(query) {
|
|
|
546
885
|
* both core (builtin) and plugins.
|
|
547
886
|
* Adapters call this instead of reading CORE_SKILLS directly.
|
|
548
887
|
*/
|
|
549
|
-
function collectAllSkillDirs() {
|
|
888
|
+
function collectAllSkillDirs(targetRoot) {
|
|
889
|
+
const root = targetRoot || process.cwd();
|
|
890
|
+
const agentsDir = path.join(root, '.agents');
|
|
891
|
+
const localCore = path.join(agentsDir, 'core', 'skills');
|
|
892
|
+
const localPlugins = path.join(agentsDir, 'plugins');
|
|
893
|
+
|
|
894
|
+
const coreDir = fs.existsSync(localCore) ? localCore : CORE_SKILLS;
|
|
895
|
+
const pluginsDir = fs.existsSync(localPlugins) ? localPlugins : PLUGINS_DIR;
|
|
550
896
|
const dirs = [];
|
|
551
897
|
|
|
552
898
|
// Core skills
|
|
553
|
-
if (fs.existsSync(
|
|
554
|
-
for (const name of fs.readdirSync(
|
|
555
|
-
const d = path.join(
|
|
899
|
+
if (fs.existsSync(coreDir)) {
|
|
900
|
+
for (const name of fs.readdirSync(coreDir)) {
|
|
901
|
+
const d = path.join(coreDir, name);
|
|
556
902
|
if (fs.statSync(d).isDirectory()) dirs.push(d);
|
|
557
903
|
}
|
|
558
904
|
}
|
|
559
905
|
|
|
560
|
-
// Plugin skills
|
|
561
|
-
if (fs.existsSync(
|
|
562
|
-
for (const name of fs.readdirSync(
|
|
563
|
-
const d = path.join(
|
|
564
|
-
if (fs.statSync(d).isDirectory())
|
|
906
|
+
// Plugin skills (supports both standalone skill dirs and plugin bundles with skills/)
|
|
907
|
+
if (fs.existsSync(pluginsDir)) {
|
|
908
|
+
for (const name of fs.readdirSync(pluginsDir)) {
|
|
909
|
+
const d = path.join(pluginsDir, name);
|
|
910
|
+
if (!fs.statSync(d).isDirectory()) continue;
|
|
911
|
+
|
|
912
|
+
const nestedSkills = path.join(d, 'skills');
|
|
913
|
+
if (fs.existsSync(nestedSkills) && fs.statSync(nestedSkills).isDirectory()) {
|
|
914
|
+
for (const sub of fs.readdirSync(nestedSkills)) {
|
|
915
|
+
const subDir = path.join(nestedSkills, sub);
|
|
916
|
+
if (fs.statSync(subDir).isDirectory()) dirs.push(subDir);
|
|
917
|
+
}
|
|
918
|
+
} else if (fs.existsSync(path.join(d, 'SKILL.md')) || fs.existsSync(path.join(d, 'skill.yaml'))) {
|
|
919
|
+
dirs.push(d);
|
|
920
|
+
}
|
|
565
921
|
}
|
|
566
922
|
}
|
|
567
923
|
|
|
@@ -579,6 +935,9 @@ module.exports = {
|
|
|
579
935
|
deriveSkillName,
|
|
580
936
|
isSafeSkillName,
|
|
581
937
|
scanForPromptInjection,
|
|
938
|
+
scanContentSecurity,
|
|
939
|
+
scanPluginDirectory,
|
|
582
940
|
PROMPT_INJECTION_PATTERNS,
|
|
941
|
+
SHELL_ESCAPE_PATTERNS,
|
|
583
942
|
PLUGINS_DIR,
|
|
584
943
|
};
|