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.
Files changed (88) hide show
  1. package/.agents/AGENTS.md +5 -0
  2. package/.agents/adapters/aider/export.js +4 -2
  3. package/.agents/adapters/copilot/export.js +4 -2
  4. package/.agents/adapters/cursor/export.js +48 -12
  5. package/.agents/adapters/zed/export.js +53 -14
  6. package/.agents/core/profiles/mvp.yaml +1 -0
  7. package/.agents/core/skills/adapters/skill.yaml +6 -0
  8. package/.agents/core/skills/architecture-diagrams/skill.yaml +4 -0
  9. package/.agents/core/skills/brutalist-design/skill.yaml +4 -0
  10. package/.agents/core/skills/context-manager/references/context-rules.md +59 -0
  11. package/.agents/core/skills/context-manager/skill.yaml +7 -0
  12. package/.agents/core/skills/context-os/skill.yaml +11 -0
  13. package/.agents/core/skills/database/skill.yaml +6 -0
  14. package/.agents/core/skills/ddd/skill.yaml +7 -0
  15. package/.agents/core/skills/decisions/skill.yaml +6 -0
  16. package/.agents/core/skills/docker/skill.yaml +6 -0
  17. package/.agents/core/skills/engineering-workflow/SKILL.md +1 -1
  18. package/.agents/core/skills/engineering-workflow/skill.yaml +6 -0
  19. package/.agents/core/skills/fastapi/skill.yaml +7 -0
  20. package/.agents/core/skills/gemini-precision/SKILL.md +4 -0
  21. package/.agents/core/skills/gemini-precision/skill.yaml +4 -0
  22. package/.agents/core/skills/generators/skill.yaml +15 -0
  23. package/.agents/core/skills/graphify/SKILL.md +2 -2
  24. package/.agents/core/skills/graphify/skill.yaml +6 -1
  25. package/.agents/core/skills/gstack-roles/SKILL.md +3 -1
  26. package/.agents/core/skills/gstack-roles/skill.yaml +6 -0
  27. package/.agents/core/skills/impeccable-design/skill.yaml +6 -0
  28. package/.agents/core/skills/interview-me/skill.yaml +4 -0
  29. package/.agents/core/skills/microservices/skill.yaml +7 -0
  30. package/.agents/core/skills/minimalist-design/skill.yaml +4 -0
  31. package/.agents/core/skills/nestjs/skill.yaml +7 -0
  32. package/.agents/core/skills/nextjs/skill.yaml +7 -0
  33. package/.agents/core/skills/node/skill.yaml +7 -0
  34. package/.agents/core/skills/performance/skill.yaml +7 -0
  35. package/.agents/core/skills/ponytail-mindset/skill.yaml +6 -0
  36. package/.agents/core/skills/react/skill.yaml +7 -0
  37. package/.agents/core/skills/react-best-practices/skill.yaml +4 -0
  38. package/.agents/core/skills/redesign-audit/skill.yaml +4 -0
  39. package/.agents/core/skills/security/skill.yaml +7 -0
  40. package/.agents/core/skills/soft-design/skill.yaml +4 -0
  41. package/.agents/core/skills/state-management/skill.yaml +6 -0
  42. package/.agents/core/skills/subagent-orchestrator/SKILL.md +17 -0
  43. package/.agents/core/skills/subagent-orchestrator/skill.yaml +4 -0
  44. package/.agents/core/skills/system-design/skill.yaml +7 -0
  45. package/.agents/core/skills/testing/skill.yaml +6 -0
  46. package/.agents/core/skills/typescript/skill.yaml +7 -0
  47. package/.agents/core/skills/ui-design/skill.yaml +7 -0
  48. package/.agents/core/skills/ui-ux-pro/skill.yaml +6 -0
  49. package/.agents/core/skills/ux-design/skill.yaml +7 -0
  50. package/.agents/core/skills/vercel-optimize/scripts/collect-signals.mjs +131 -0
  51. package/.agents/core/skills/vercel-optimize/scripts/gate-investigations.mjs +142 -0
  52. package/.agents/core/skills/vercel-optimize/scripts/merge-signals.mjs +143 -0
  53. package/.agents/core/skills/vercel-optimize/scripts/scan-codebase.mjs +174 -0
  54. package/.agents/core/skills/vercel-optimize/skill.yaml +8 -0
  55. package/.agents/core/skills/web-accessibility/skill.yaml +7 -0
  56. package/.agents/ctx.js +44 -0
  57. package/.agents/doctor.js +193 -4
  58. package/.agents/generated/claude/skills/engineering-workflow/SKILL.md +1 -1
  59. package/.agents/generated/claude/skills/gemini-precision/SKILL.md +4 -0
  60. package/.agents/generated/claude/skills/graphify/SKILL.md +1 -1
  61. package/.agents/generated/claude/skills/gstack-roles/SKILL.md +3 -1
  62. package/.agents/generated/claude/skills/subagent-orchestrator/SKILL.md +17 -0
  63. package/.agents/generated/gemini/skills/engineering-workflow/SKILL.md +1 -1
  64. package/.agents/generated/gemini/skills/gemini-precision/SKILL.md +4 -0
  65. package/.agents/generated/gemini/skills/graphify/SKILL.md +2 -2
  66. package/.agents/generated/gemini/skills/gstack-roles/SKILL.md +3 -1
  67. package/.agents/generated/gemini/skills/subagent-orchestrator/SKILL.md +17 -0
  68. package/.agents/plugins/contextos/hooks.json +25 -0
  69. package/.agents/plugins/contextos/plugin.json +19 -0
  70. package/.agents/plugins.js +180 -20
  71. package/.agents/profiles.js +26 -0
  72. package/.agents/resolver.js +150 -30
  73. package/.agents/stats.js +22 -9
  74. package/.agents/validate.js +152 -13
  75. package/README.md +77 -47
  76. package/bin/commands/uninstall.js +207 -0
  77. package/bin/commands/update.js +262 -0
  78. package/bin/commands.js +305 -0
  79. package/bin/index.js +275 -119
  80. package/bin/lib/detector.js +106 -0
  81. package/bin/lib/lockfile.js +251 -0
  82. package/bin/lib/safe-writer.js +259 -0
  83. package/package.json +6 -9
  84. package/registry.json +13 -5
  85. package/registry.schema.json +3 -1
  86. package/.agents/mcp/runtime.py +0 -470
  87. package/.agents/mcp/server.mjs +0 -189386
  88. 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 a codebase mapping and context optimization engine. Instead of feeding raw directory trees or entire source files into an agent's context window, Graphify leverages local **Tree-sitter** AST parsing to construct a deterministic, queryable knowledge graph (`graph.json`, `GRAPH_REPORT.md`, `graph.html`).
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, or declares `[PHASE: Build]`, execute the BUILD phase directly and deliver the complete, self-contained production code without conversational pauses.
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 via Tree-sitter AST parsing and semantic indexing. Minimizes token consumption and maps dependency blast radius.
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 a codebase mapping and context optimization engine. Instead of feeding raw directory trees or entire source files into an agent's context window, Graphify leverages local **Tree-sitter** AST parsing to construct a deterministic, queryable knowledge graph (`graph.json`, `GRAPH_REPORT.md`, `graph.html`).
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
+ }
@@ -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
- // ── ANSI helpers ──────────────────────────────────────────────────────────────
66
- const NO_COLOR = process.env.NO_COLOR || !process.stdout.isTTY;
67
- const c = {
68
- green: (s) => NO_COLOR ? s : `\x1b[32m${s}\x1b[0m`,
69
- red: (s) => NO_COLOR ? s : `\x1b[31m${s}\x1b[0m`,
70
- yellow: (s) => NO_COLOR ? s : `\x1b[33m${s}\x1b[0m`,
71
- cyan: (s) => NO_COLOR ? s : `\x1b[36m${s}\x1b[0m`,
72
- bold: (s) => NO_COLOR ? s : `\x1b[1m${s}\x1b[0m`,
73
- dim: (s) => NO_COLOR ? s : `\x1b[2m${s}\x1b[0m`,
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
- scanForPromptInjection(skillMdContent, { throwOnMatch: !forceUnsafe });
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(CORE_SKILLS)) {
554
- for (const name of fs.readdirSync(CORE_SKILLS)) {
555
- const d = path.join(CORE_SKILLS, name);
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(PLUGINS_DIR)) {
562
- for (const name of fs.readdirSync(PLUGINS_DIR)) {
563
- const d = path.join(PLUGINS_DIR, name);
564
- if (fs.statSync(d).isDirectory()) dirs.push(d);
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
  };
@@ -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