contextos-agents 1.6.0 → 1.7.0
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 +5 -0
- package/.agents/adapters/aider/export.js +4 -2
- package/.agents/adapters/copilot/export.js +4 -2
- package/.agents/adapters/cursor/export.js +48 -12
- package/.agents/adapters/zed/export.js +53 -14
- package/.agents/core/profiles/mvp.yaml +1 -0
- package/.agents/core/skills/adapters/skill.yaml +6 -0
- package/.agents/core/skills/architecture-diagrams/skill.yaml +4 -0
- package/.agents/core/skills/brutalist-design/skill.yaml +4 -0
- package/.agents/core/skills/context-manager/references/context-rules.md +59 -0
- package/.agents/core/skills/context-manager/skill.yaml +7 -0
- package/.agents/core/skills/context-os/skill.yaml +11 -0
- package/.agents/core/skills/database/skill.yaml +6 -0
- package/.agents/core/skills/ddd/skill.yaml +7 -0
- package/.agents/core/skills/decisions/skill.yaml +6 -0
- package/.agents/core/skills/docker/skill.yaml +6 -0
- package/.agents/core/skills/engineering-workflow/SKILL.md +1 -1
- package/.agents/core/skills/engineering-workflow/skill.yaml +6 -0
- package/.agents/core/skills/fastapi/skill.yaml +7 -0
- package/.agents/core/skills/gemini-precision/SKILL.md +4 -0
- package/.agents/core/skills/gemini-precision/skill.yaml +4 -0
- package/.agents/core/skills/generators/skill.yaml +15 -0
- package/.agents/core/skills/graphify/SKILL.md +2 -2
- package/.agents/core/skills/graphify/skill.yaml +6 -1
- package/.agents/core/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/core/skills/gstack-roles/skill.yaml +6 -0
- package/.agents/core/skills/impeccable-design/skill.yaml +6 -0
- package/.agents/core/skills/interview-me/skill.yaml +4 -0
- package/.agents/core/skills/microservices/skill.yaml +7 -0
- package/.agents/core/skills/minimalist-design/skill.yaml +4 -0
- package/.agents/core/skills/nestjs/skill.yaml +7 -0
- package/.agents/core/skills/nextjs/skill.yaml +7 -0
- package/.agents/core/skills/node/skill.yaml +7 -0
- package/.agents/core/skills/performance/skill.yaml +7 -0
- package/.agents/core/skills/ponytail-mindset/skill.yaml +6 -0
- package/.agents/core/skills/react/skill.yaml +7 -0
- package/.agents/core/skills/react-best-practices/skill.yaml +4 -0
- package/.agents/core/skills/redesign-audit/skill.yaml +4 -0
- package/.agents/core/skills/security/skill.yaml +7 -0
- package/.agents/core/skills/soft-design/skill.yaml +4 -0
- package/.agents/core/skills/state-management/skill.yaml +6 -0
- package/.agents/core/skills/subagent-orchestrator/SKILL.md +17 -0
- package/.agents/core/skills/subagent-orchestrator/skill.yaml +4 -0
- package/.agents/core/skills/system-design/skill.yaml +7 -0
- package/.agents/core/skills/testing/skill.yaml +6 -0
- package/.agents/core/skills/typescript/skill.yaml +7 -0
- package/.agents/core/skills/ui-design/skill.yaml +7 -0
- package/.agents/core/skills/ui-ux-pro/skill.yaml +6 -0
- package/.agents/core/skills/ux-design/skill.yaml +7 -0
- package/.agents/core/skills/vercel-optimize/scripts/collect-signals.mjs +131 -0
- package/.agents/core/skills/vercel-optimize/scripts/gate-investigations.mjs +142 -0
- package/.agents/core/skills/vercel-optimize/scripts/merge-signals.mjs +143 -0
- package/.agents/core/skills/vercel-optimize/scripts/scan-codebase.mjs +174 -0
- package/.agents/core/skills/vercel-optimize/skill.yaml +8 -0
- package/.agents/core/skills/web-accessibility/skill.yaml +7 -0
- package/.agents/ctx.js +44 -0
- package/.agents/doctor.js +193 -4
- 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/graphify/SKILL.md +1 -1
- package/.agents/generated/claude/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +17 -0
- 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/graphify/SKILL.md +2 -2
- package/.agents/generated/gemini/skills/gstack-roles/SKILL.md +3 -1
- package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +17 -0
- package/.agents/plugins/contextos/hooks.json +25 -0
- package/.agents/plugins/contextos/plugin.json +19 -0
- package/.agents/plugins.js +180 -20
- package/.agents/profiles.js +26 -0
- package/.agents/resolver.js +150 -30
- package/.agents/stats.js +22 -9
- package/.agents/validate.js +152 -13
- package/README.md +77 -47
- package/bin/commands/uninstall.js +207 -0
- package/bin/commands/update.js +262 -0
- package/bin/commands.js +305 -0
- package/bin/index.js +275 -119
- package/bin/lib/detector.js +106 -0
- package/bin/lib/lockfile.js +251 -0
- package/bin/lib/safe-writer.js +259 -0
- package/package.json +6 -9
- package/registry.json +13 -5
- package/registry.schema.json +3 -1
- package/.agents/mcp/runtime.py +0 -470
- package/.agents/mcp/server.mjs +0 -189386
- package/benchmarks/gemini-issues.js +0 -533
|
@@ -82,6 +82,10 @@ Activate whenever:
|
|
|
82
82
|
- State what was inspected or verified from the code.
|
|
83
83
|
- State the architectural decision made and the immediate next action.
|
|
84
84
|
- Keep narration crisp and actionable without excessive verbosity.
|
|
85
|
+
- **Zero-Spam Constraint**:
|
|
86
|
+
- ❌ **Forbidden**: Starting every intermediate step, tool call, or status update with domain/phase/role tags (e.g. `[DOMAIN: ...] [PHASE: ...] [ROLE: ...]`).
|
|
87
|
+
- ✅ **Mandatory**: Declare role and phase strictly once at the start of a phase. Intermediate step updates must be clean, natural language sentences describing technical actions directly.
|
|
88
|
+
- Do not narrate routine micro-inspections (single line reads or basic greps). Announce only meaningful task phases and decisions.
|
|
85
89
|
|
|
86
90
|
---
|
|
87
91
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Overview
|
|
4
4
|
|
|
5
|
-
**Graphify** is
|
|
5
|
+
**Graphify** is an instruction-only codebase mapping and context optimization guide. Instead of feeding raw directory trees or entire source files into an agent's context window, Graphify instructs agents on how to construct a deterministic, queryable knowledge graph (`graph.json`, `GRAPH_REPORT.md`, `graph.html`) using external companion analyzers (such as the TypeScript AST analyzer in `contextos-mcp` or the external `graphifyy` CLI), keeping the core package 100% zero-dependency without bundled native Tree-sitter binaries.
|
|
6
6
|
|
|
7
7
|
This skill instructs agents how to build, query, and maintain codebase graphs to navigate complex architectures with near-zero token overhead.
|
|
8
8
|
|
|
@@ -18,12 +18,14 @@ Inspired by [Garry Tan's gstack](https://github.com/garrytan/gstack) — shippin
|
|
|
18
18
|
|
|
19
19
|
## Role Identification Protocol
|
|
20
20
|
|
|
21
|
-
At the start of each task, declare your role:
|
|
21
|
+
At the start of each task or major phase switch, declare your role:
|
|
22
22
|
|
|
23
23
|
```
|
|
24
24
|
[ROLE: <Role Name>] — <One-line description of your mandate for this task>
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
> **Anti-Spam Invariant**: Declare this role **strictly once per phase**. Never prefix intermediate tool calls, file operations, or step updates with role tags.
|
|
28
|
+
|
|
27
29
|
Then execute ONLY within the constraints of that role.
|
|
28
30
|
|
|
29
31
|
---
|
|
@@ -37,6 +37,22 @@ Before delegating any subagent task:
|
|
|
37
37
|
|
|
38
38
|
### 3. Context Hand-off Specification
|
|
39
39
|
|
|
40
|
+
Every subagent dispatch must be backed by an immutable `TaskBrief`:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
type TaskBrief = Readonly<{
|
|
44
|
+
taskId: string;
|
|
45
|
+
baseSha: string;
|
|
46
|
+
objective: string;
|
|
47
|
+
writeScope: readonly string[];
|
|
48
|
+
testCommand: string;
|
|
49
|
+
expectedResult: string;
|
|
50
|
+
maxAttempts: number;
|
|
51
|
+
}>;
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Create the brief once before dispatch. Agents and retry loops must reuse it without
|
|
55
|
+
changing the base commit, scope, verification command, or acceptance result.
|
|
40
56
|
Every subagent dispatch prompt must contain:
|
|
41
57
|
|
|
42
58
|
1. **Target Objective**: Single, verifiable deliverable.
|
|
@@ -72,6 +88,7 @@ Every subagent dispatch prompt must contain:
|
|
|
72
88
|
## Validation Checklist
|
|
73
89
|
|
|
74
90
|
- [ ] All delegated tasks have disjoint, non-overlapping file sets.
|
|
91
|
+
- [ ] Every worker received an immutable `TaskBrief` tied to the original `baseSha`.
|
|
75
92
|
- [ ] Every subagent prompt has explicit read vs write boundaries.
|
|
76
93
|
- [ ] Subagent results verified individually before merging.
|
|
77
94
|
- [ ] Global regression suite executed across the entire repository after all subagents finish.
|
|
@@ -38,7 +38,7 @@ Inspired by [addyosmani/agent-skills](https://github.com/addyosmani/agent-skills
|
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
**IRON RULE**: In interactive development, no phase can be skipped and no code is written before `/plan` is approved.
|
|
41
|
-
**Direct Build Exception**: When the prompt/caller explicitly requests a standalone implementation,
|
|
41
|
+
**Direct Build & Fast-Track Exception**: When the prompt/caller explicitly requests a standalone implementation, declares `[PHASE: Build]`, or requests routine operational/maintenance tasks (git operations, version bumps, typo fixes, small config tweaks, diagnostic checks), proceed directly to execution without conversational approval pauses.
|
|
42
42
|
|
|
43
43
|
---
|
|
44
44
|
|
|
@@ -87,6 +87,10 @@ Activate whenever:
|
|
|
87
87
|
- State what was inspected or verified from the code.
|
|
88
88
|
- State the architectural decision made and the immediate next action.
|
|
89
89
|
- Keep narration crisp and actionable without excessive verbosity.
|
|
90
|
+
- **Zero-Spam Constraint**:
|
|
91
|
+
- ❌ **Forbidden**: Starting every intermediate step, tool call, or status update with domain/phase/role tags (e.g. `[DOMAIN: ...] [PHASE: ...] [ROLE: ...]`).
|
|
92
|
+
- ✅ **Mandatory**: Declare role and phase strictly once at the start of a phase. Intermediate step updates must be clean, natural language sentences describing technical actions directly.
|
|
93
|
+
- Do not narrate routine micro-inspections (single line reads or basic greps). Announce only meaningful task phases and decisions.
|
|
90
94
|
|
|
91
95
|
---
|
|
92
96
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: graphify
|
|
3
3
|
description: >
|
|
4
|
-
Codebase knowledge graph generator
|
|
4
|
+
Codebase knowledge graph generator and architecture mapping guide. Instructs agents on AST dependency extraction and blast radius analysis.
|
|
5
5
|
---
|
|
6
6
|
# graphify
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
**Graphify** is
|
|
10
|
+
**Graphify** is an instruction-only codebase mapping and context optimization guide. Instead of feeding raw directory trees or entire source files into an agent's context window, Graphify instructs agents on how to construct a deterministic, queryable knowledge graph (`graph.json`, `GRAPH_REPORT.md`, `graph.html`) using external companion analyzers (such as the TypeScript AST analyzer in `contextos-mcp` or the external `graphifyy` CLI), keeping the core package 100% zero-dependency without bundled native Tree-sitter binaries.
|
|
11
11
|
|
|
12
12
|
This skill instructs agents how to build, query, and maintain codebase graphs to navigate complex architectures with near-zero token overhead.
|
|
13
13
|
|
|
@@ -23,12 +23,14 @@ Inspired by [Garry Tan's gstack](https://github.com/garrytan/gstack) — shippin
|
|
|
23
23
|
|
|
24
24
|
## Role Identification Protocol
|
|
25
25
|
|
|
26
|
-
At the start of each task, declare your role:
|
|
26
|
+
At the start of each task or major phase switch, declare your role:
|
|
27
27
|
|
|
28
28
|
```
|
|
29
29
|
[ROLE: <Role Name>] — <One-line description of your mandate for this task>
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
+
> **Anti-Spam Invariant**: Declare this role **strictly once per phase**. Never prefix intermediate tool calls, file operations, or step updates with role tags.
|
|
33
|
+
|
|
32
34
|
Then execute ONLY within the constraints of that role.
|
|
33
35
|
|
|
34
36
|
---
|
|
@@ -42,6 +42,22 @@ Before delegating any subagent task:
|
|
|
42
42
|
|
|
43
43
|
### 3. Context Hand-off Specification
|
|
44
44
|
|
|
45
|
+
Every subagent dispatch must be backed by an immutable `TaskBrief`:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
type TaskBrief = Readonly<{
|
|
49
|
+
taskId: string;
|
|
50
|
+
baseSha: string;
|
|
51
|
+
objective: string;
|
|
52
|
+
writeScope: readonly string[];
|
|
53
|
+
testCommand: string;
|
|
54
|
+
expectedResult: string;
|
|
55
|
+
maxAttempts: number;
|
|
56
|
+
}>;
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Create the brief once before dispatch. Agents and retry loops must reuse it without
|
|
60
|
+
changing the base commit, scope, verification command, or acceptance result.
|
|
45
61
|
Every subagent dispatch prompt must contain:
|
|
46
62
|
|
|
47
63
|
1. **Target Objective**: Single, verifiable deliverable.
|
|
@@ -77,6 +93,7 @@ Every subagent dispatch prompt must contain:
|
|
|
77
93
|
## Validation Checklist
|
|
78
94
|
|
|
79
95
|
- [ ] All delegated tasks have disjoint, non-overlapping file sets.
|
|
96
|
+
- [ ] Every worker received an immutable `TaskBrief` tied to the original `baseSha`.
|
|
80
97
|
- [ ] Every subagent prompt has explicit read vs write boundaries.
|
|
81
98
|
- [ ] Subagent results verified individually before merging.
|
|
82
99
|
- [ ] Global regression suite executed across the entire repository after all subagents finish.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contextos-session-guard": {
|
|
3
|
+
"enabled": true,
|
|
4
|
+
"PreInvocation": [
|
|
5
|
+
{
|
|
6
|
+
"type": "command",
|
|
7
|
+
"command": "node .agents/ctx.js doctor"
|
|
8
|
+
}
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"contextos-integrity-check": {
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"PostToolUse": [
|
|
14
|
+
{
|
|
15
|
+
"matcher": "run_command",
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "node .agents/ctx.js validate"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "contextos",
|
|
3
|
+
"version": "1.7.0",
|
|
4
|
+
"description": "Deterministic AI Project Operating System context compiler, MCP runtime, and high-precision engineering skills for Antigravity.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "ContextOS Team",
|
|
7
|
+
"url": "https://github.com/kok-o/contextos-agents"
|
|
8
|
+
},
|
|
9
|
+
"repository": "https://github.com/kok-o/contextos-agents",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"contextos",
|
|
13
|
+
"antigravity",
|
|
14
|
+
"agent-skills",
|
|
15
|
+
"orchestration",
|
|
16
|
+
"gemini",
|
|
17
|
+
"engineering-workflow"
|
|
18
|
+
]
|
|
19
|
+
}
|
package/.agents/plugins.js
CHANGED
|
@@ -36,6 +36,17 @@ const REGISTRY_URL = 'https://raw.githubusercontent.com/kok-o/contextos-agents
|
|
|
36
36
|
const MAX_DOWNLOAD_BYTES = 5 * 1024 * 1024;
|
|
37
37
|
const SAFE_SKILL_NAME = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
|
|
38
38
|
|
|
39
|
+
// ── ANSI helpers ──────────────────────────────────────────────────────────────
|
|
40
|
+
const NO_COLOR = process.env.NO_COLOR || !process.stdout.isTTY;
|
|
41
|
+
const c = {
|
|
42
|
+
green: (s) => NO_COLOR ? s : `\x1b[32m${s}\x1b[0m`,
|
|
43
|
+
red: (s) => NO_COLOR ? s : `\x1b[31m${s}\x1b[0m`,
|
|
44
|
+
yellow: (s) => NO_COLOR ? s : `\x1b[33m${s}\x1b[0m`,
|
|
45
|
+
cyan: (s) => NO_COLOR ? s : `\x1b[36m${s}\x1b[0m`,
|
|
46
|
+
bold: (s) => NO_COLOR ? s : `\x1b[1m${s}\x1b[0m`,
|
|
47
|
+
dim: (s) => NO_COLOR ? s : `\x1b[2m${s}\x1b[0m`,
|
|
48
|
+
};
|
|
49
|
+
|
|
39
50
|
// ── Prompt Injection Scanner ──────────────────────────────────────────────────
|
|
40
51
|
const PROMPT_INJECTION_PATTERNS = [
|
|
41
52
|
/ignore\s+(all\s+)?previous\s+instructions/i,
|
|
@@ -48,6 +59,18 @@ const PROMPT_INJECTION_PATTERNS = [
|
|
|
48
59
|
/\b(?:eval|exec|Function)\s*\(/i,
|
|
49
60
|
];
|
|
50
61
|
|
|
62
|
+
// ── Shell Escapes & Script Injection Scanner ──────────────────────────────────
|
|
63
|
+
const SHELL_ESCAPE_PATTERNS = [
|
|
64
|
+
/(?:curl|wget)\s+[^|\n\r]+?\|\s*(?:bash|sh|zsh|powershell|pwsh|cmd)/i,
|
|
65
|
+
/\bpowershell(?:\.exe)?\s+.*-(?:enc|encodedcommand|ep\s+bypass)\b/i,
|
|
66
|
+
/\b(?:rm\s+-rf\s+[\/~]|del\s+\/[sfq]\s+[c-z]:[\\\/])/i,
|
|
67
|
+
/\b(?:mkfifo|mknod)\s+.*?;/i,
|
|
68
|
+
/\b(?:nc|netcat|ncat)\s+-[elp]+/i,
|
|
69
|
+
/<script\b[^>]*>[\s\S]*?<\/script>/i,
|
|
70
|
+
/javascript\s*:\s*[^\s'"]+/i,
|
|
71
|
+
/data:text\/html/i,
|
|
72
|
+
];
|
|
73
|
+
|
|
51
74
|
function scanForPromptInjection(content, options = {}) {
|
|
52
75
|
if (typeof content !== 'string') return false;
|
|
53
76
|
const found = PROMPT_INJECTION_PATTERNS.filter(p => p.test(content));
|
|
@@ -62,16 +85,124 @@ function scanForPromptInjection(content, options = {}) {
|
|
|
62
85
|
return false;
|
|
63
86
|
}
|
|
64
87
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
88
|
+
function scanContentSecurity(content, filePath, options = {}) {
|
|
89
|
+
if (typeof content !== 'string') return false;
|
|
90
|
+
|
|
91
|
+
const injectionMatches = PROMPT_INJECTION_PATTERNS.filter(p => p.test(content));
|
|
92
|
+
const shellMatches = SHELL_ESCAPE_PATTERNS.filter(p => p.test(content));
|
|
93
|
+
|
|
94
|
+
const allMatches = [
|
|
95
|
+
...injectionMatches.map(p => `prompt_injection: ${p.source}`),
|
|
96
|
+
...shellMatches.map(p => `shell_or_script: ${p.source}`),
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
if (allMatches.length > 0) {
|
|
100
|
+
const rel = filePath ? path.basename(filePath) : 'content';
|
|
101
|
+
console.warn(c.red(` 🚨 Unsafe pattern or injection detected in plugin file '${rel}':`));
|
|
102
|
+
console.warn(c.yellow(` Matches: ${allMatches.join('; ')}`));
|
|
103
|
+
if (options.throwOnMatch) {
|
|
104
|
+
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.`);
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isScannableFile(fileName) {
|
|
112
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
113
|
+
const scannableExtensions = new Set([
|
|
114
|
+
'.md', '.mdc', '.markdown', '.txt',
|
|
115
|
+
'.yaml', '.yml', '.json',
|
|
116
|
+
'.sh', '.bash', '.zsh', '.ps1', '.bat', '.cmd',
|
|
117
|
+
'.js', '.ts', '.mjs', '.cjs', '.py',
|
|
118
|
+
]);
|
|
119
|
+
return scannableExtensions.has(ext) || !ext;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Recursively scan an entire plugin directory tree:
|
|
124
|
+
* - Rejects any symlinks that traverse outside the plugin directory
|
|
125
|
+
* - Scans all documentation and code files (EXAMPLES.md, TROUBLESHOOTING.md, references/, etc.)
|
|
126
|
+
* for prompt injection, script injection, and dangerous shell escape patterns.
|
|
127
|
+
*/
|
|
128
|
+
function scanPluginDirectory(pluginDir, options = {}) {
|
|
129
|
+
const throwOnMatch = options.throwOnMatch !== undefined ? options.throwOnMatch : !options.forceUnsafe;
|
|
130
|
+
if (!fs.existsSync(pluginDir)) return true;
|
|
131
|
+
|
|
132
|
+
const realPluginDir = fs.realpathSync(pluginDir);
|
|
133
|
+
|
|
134
|
+
function walk(currentDir) {
|
|
135
|
+
let entries;
|
|
136
|
+
try {
|
|
137
|
+
entries = fs.readdirSync(currentDir);
|
|
138
|
+
} catch {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const name of entries) {
|
|
143
|
+
const fullPath = path.join(currentDir, name);
|
|
144
|
+
let lstat;
|
|
145
|
+
try {
|
|
146
|
+
lstat = fs.lstatSync(fullPath);
|
|
147
|
+
} catch {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 1. Check symlink traversal
|
|
152
|
+
if (lstat.isSymbolicLink()) {
|
|
153
|
+
let linkTarget;
|
|
154
|
+
try {
|
|
155
|
+
linkTarget = fs.readlinkSync(fullPath);
|
|
156
|
+
} catch (e) {
|
|
157
|
+
throw new Error(`Security validation failed: Unreadable symlink '${name}'. Installation aborted.`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const resolvedTarget = path.resolve(currentDir, linkTarget);
|
|
161
|
+
let realTarget;
|
|
162
|
+
try {
|
|
163
|
+
realTarget = fs.realpathSync(resolvedTarget);
|
|
164
|
+
} catch {
|
|
165
|
+
realTarget = resolvedTarget;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const normalizedRealPlugin = realPluginDir.replace(/\\/g, '/').toLowerCase();
|
|
169
|
+
const normalizedRealTarget = realTarget.replace(/\\/g, '/').toLowerCase();
|
|
170
|
+
|
|
171
|
+
if (
|
|
172
|
+
!normalizedRealTarget.startsWith(normalizedRealPlugin + '/') &&
|
|
173
|
+
normalizedRealTarget !== normalizedRealPlugin
|
|
174
|
+
) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`Security validation failed: Symlink traversal detected in '${name}' pointing outside plugin directory to '${linkTarget}'. Installation aborted.`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// 2. Walk subdirectories
|
|
182
|
+
if (lstat.isDirectory()) {
|
|
183
|
+
if (name === '.git' || name === 'node_modules') continue;
|
|
184
|
+
walk(fullPath);
|
|
185
|
+
} else if (lstat.isFile()) {
|
|
186
|
+
if (name === '.source') continue;
|
|
187
|
+
if (lstat.size > 2 * 1024 * 1024) continue;
|
|
188
|
+
|
|
189
|
+
if (isScannableFile(name)) {
|
|
190
|
+
try {
|
|
191
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
192
|
+
scanContentSecurity(content, fullPath, { throwOnMatch });
|
|
193
|
+
} catch (err) {
|
|
194
|
+
if (err.message && err.message.startsWith('Security validation failed')) {
|
|
195
|
+
throw err;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
walk(pluginDir);
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
75
206
|
|
|
76
207
|
// ── plugins.json lock file ─────────────────────────────────────────────────────
|
|
77
208
|
function readLock() {
|
|
@@ -232,6 +363,14 @@ async function installFromGitHub(descriptor, skillName, dryRun, checksum, forceU
|
|
|
232
363
|
fs.writeFileSync(path.join(targetDir, 'skill.yaml'), yamlContent);
|
|
233
364
|
}
|
|
234
365
|
|
|
366
|
+
// Deep security scan before writing source marker
|
|
367
|
+
try {
|
|
368
|
+
scanPluginDirectory(targetDir, { throwOnMatch: !forceUnsafe, forceUnsafe });
|
|
369
|
+
} catch (err) {
|
|
370
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
371
|
+
throw err;
|
|
372
|
+
}
|
|
373
|
+
|
|
235
374
|
// Write a source marker so we know where to update from
|
|
236
375
|
fs.writeFileSync(path.join(targetDir, '.source'), JSON.stringify({
|
|
237
376
|
type: 'github',
|
|
@@ -286,7 +425,8 @@ function installFromNpm(descriptor, skillName, dryRun, checksum, forceUnsafe = f
|
|
|
286
425
|
if (checksum && sha256.toLowerCase() !== checksum.toLowerCase()) {
|
|
287
426
|
throw new Error(`Checksum mismatch for ${skillName}: expected ${checksum}, got ${sha256}`);
|
|
288
427
|
}
|
|
289
|
-
|
|
428
|
+
// Deep security scan across all plugin files (EXAMPLES.md, TROUBLESHOOTING.md, references/, symlinks)
|
|
429
|
+
scanPluginDirectory(skillSrc, { throwOnMatch: !forceUnsafe, forceUnsafe });
|
|
290
430
|
|
|
291
431
|
const targetDir = path.join(PLUGINS_DIR, skillName);
|
|
292
432
|
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
@@ -546,22 +686,39 @@ async function search(query) {
|
|
|
546
686
|
* both core (builtin) and plugins.
|
|
547
687
|
* Adapters call this instead of reading CORE_SKILLS directly.
|
|
548
688
|
*/
|
|
549
|
-
function collectAllSkillDirs() {
|
|
689
|
+
function collectAllSkillDirs(targetRoot) {
|
|
690
|
+
const root = targetRoot || process.cwd();
|
|
691
|
+
const agentsDir = path.join(root, '.agents');
|
|
692
|
+
const localCore = path.join(agentsDir, 'core', 'skills');
|
|
693
|
+
const localPlugins = path.join(agentsDir, 'plugins');
|
|
694
|
+
|
|
695
|
+
const coreDir = fs.existsSync(localCore) ? localCore : CORE_SKILLS;
|
|
696
|
+
const pluginsDir = fs.existsSync(localPlugins) ? localPlugins : PLUGINS_DIR;
|
|
550
697
|
const dirs = [];
|
|
551
698
|
|
|
552
699
|
// Core skills
|
|
553
|
-
if (fs.existsSync(
|
|
554
|
-
for (const name of fs.readdirSync(
|
|
555
|
-
const d = path.join(
|
|
700
|
+
if (fs.existsSync(coreDir)) {
|
|
701
|
+
for (const name of fs.readdirSync(coreDir)) {
|
|
702
|
+
const d = path.join(coreDir, name);
|
|
556
703
|
if (fs.statSync(d).isDirectory()) dirs.push(d);
|
|
557
704
|
}
|
|
558
705
|
}
|
|
559
706
|
|
|
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())
|
|
707
|
+
// Plugin skills (supports both standalone skill dirs and plugin bundles with skills/)
|
|
708
|
+
if (fs.existsSync(pluginsDir)) {
|
|
709
|
+
for (const name of fs.readdirSync(pluginsDir)) {
|
|
710
|
+
const d = path.join(pluginsDir, name);
|
|
711
|
+
if (!fs.statSync(d).isDirectory()) continue;
|
|
712
|
+
|
|
713
|
+
const nestedSkills = path.join(d, 'skills');
|
|
714
|
+
if (fs.existsSync(nestedSkills) && fs.statSync(nestedSkills).isDirectory()) {
|
|
715
|
+
for (const sub of fs.readdirSync(nestedSkills)) {
|
|
716
|
+
const subDir = path.join(nestedSkills, sub);
|
|
717
|
+
if (fs.statSync(subDir).isDirectory()) dirs.push(subDir);
|
|
718
|
+
}
|
|
719
|
+
} else if (fs.existsSync(path.join(d, 'SKILL.md')) || fs.existsSync(path.join(d, 'skill.yaml'))) {
|
|
720
|
+
dirs.push(d);
|
|
721
|
+
}
|
|
565
722
|
}
|
|
566
723
|
}
|
|
567
724
|
|
|
@@ -579,6 +736,9 @@ module.exports = {
|
|
|
579
736
|
deriveSkillName,
|
|
580
737
|
isSafeSkillName,
|
|
581
738
|
scanForPromptInjection,
|
|
739
|
+
scanContentSecurity,
|
|
740
|
+
scanPluginDirectory,
|
|
582
741
|
PROMPT_INJECTION_PATTERNS,
|
|
742
|
+
SHELL_ESCAPE_PATTERNS,
|
|
583
743
|
PLUGINS_DIR,
|
|
584
744
|
};
|
package/.agents/profiles.js
CHANGED
|
@@ -72,10 +72,34 @@ function parseYamlProfile(text) {
|
|
|
72
72
|
.filter(Boolean);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
function parseMap(field) {
|
|
76
|
+
const blockRegex = new RegExp(`^${field}:\\s*\\n((?:[ \\t]+[a-zA-Z0-9_-]+:[^\\n]*\\n?)+)`, 'm');
|
|
77
|
+
const match = text.match(blockRegex);
|
|
78
|
+
if (!match) return {};
|
|
79
|
+
const map = {};
|
|
80
|
+
const lines = match[1].split('\n');
|
|
81
|
+
for (const line of lines) {
|
|
82
|
+
const lineClean = line.replace(/#.*$/, '').trim();
|
|
83
|
+
if (!lineClean) continue;
|
|
84
|
+
const kv = lineClean.match(/^([a-zA-Z0-9_-]+):\s*(.*)$/);
|
|
85
|
+
if (kv) {
|
|
86
|
+
const key = kv[1].trim();
|
|
87
|
+
let val = kv[2].trim().replace(/^['"]|['"]$/g, '');
|
|
88
|
+
if (val === 'true') val = true;
|
|
89
|
+
else if (val === 'false') val = false;
|
|
90
|
+
else if (/^\d+$/.test(val)) val = parseInt(val, 10);
|
|
91
|
+
map[key] = val;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return map;
|
|
95
|
+
}
|
|
96
|
+
|
|
75
97
|
result.prefer_skills = parseList('prefer_skills');
|
|
76
98
|
result.exclude_skills = parseList('exclude_skills');
|
|
77
99
|
result.generate_docs = parseList('generate_docs');
|
|
78
100
|
result.skip_docs = parseList('skip_docs');
|
|
101
|
+
result.enforce = parseMap('enforce');
|
|
102
|
+
result.defaults = parseMap('defaults');
|
|
79
103
|
|
|
80
104
|
return result;
|
|
81
105
|
}
|
|
@@ -140,6 +164,8 @@ function applyProfile(profileName, projectDir = process.cwd()) {
|
|
|
140
164
|
description: profile.description,
|
|
141
165
|
exclude_skills: profile.exclude_skills || [],
|
|
142
166
|
prefer_skills: profile.prefer_skills || [],
|
|
167
|
+
enforce: profile.enforce || {},
|
|
168
|
+
defaults: profile.defaults || {},
|
|
143
169
|
appliedAt: new Date().toISOString(),
|
|
144
170
|
};
|
|
145
171
|
|