javi-forge 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/README.md +45 -1
- package/ci-local/README.md +56 -11
- package/ci-local/ci-local.ps1 +445 -0
- package/ci-local/ci-local.sh +52 -9
- package/ci-local/docker/python.Dockerfile +7 -0
- package/ci-local/hooks/commit-msg +120 -44
- package/ci-local/hooks/commit-msg.test.sh +286 -0
- package/ci-local/hooks/pre-commit +9 -1
- package/ci-local/hooks/pre-push +13 -2
- package/ci-local/install.ps1 +367 -0
- package/ci-local/install.sh +151 -5
- package/ci-local/uninstall.ps1 +85 -0
- package/ci-local/uninstall.sh +95 -0
- package/dist/cli/dispatch/ci.d.ts +12 -0
- package/dist/cli/dispatch/ci.js +38 -0
- package/dist/cli/dispatch/security.d.ts +11 -0
- package/dist/cli/dispatch/security.js +68 -0
- package/dist/cli/dispatch/simple-renderers.d.ts +16 -0
- package/dist/cli/dispatch/simple-renderers.js +65 -0
- package/dist/cli/dispatch/skill-publish.d.ts +15 -0
- package/dist/cli/dispatch/skill-publish.js +44 -0
- package/dist/cli/dispatch/skills-cmd.d.ts +13 -0
- package/dist/cli/dispatch/skills-cmd.js +93 -0
- package/dist/cli/dispatch/tdd.d.ts +11 -0
- package/dist/cli/dispatch/tdd.js +45 -0
- package/dist/cli/dispatch/types.d.ts +29 -0
- package/dist/cli/dispatch/types.js +2 -0
- package/dist/cli/dispatch/workflow.d.ts +11 -0
- package/dist/cli/dispatch/workflow.js +67 -0
- package/dist/cli/help.d.ts +127 -0
- package/dist/cli/help.js +138 -0
- package/dist/cli/runtime.d.ts +16 -0
- package/dist/cli/runtime.js +38 -0
- package/dist/cli/validators.d.ts +17 -0
- package/dist/cli/validators.js +35 -0
- package/dist/commands/analyze.d.ts +1 -1
- package/dist/commands/analyze.js +14 -16
- package/dist/commands/ci.d.ts +50 -3
- package/dist/commands/ci.js +537 -182
- package/dist/commands/doctor.d.ts +2 -2
- package/dist/commands/doctor.js +112 -62
- package/dist/commands/init/report.d.ts +13 -0
- package/dist/commands/init/report.js +13 -0
- package/dist/commands/init/steps/agent-skills.d.ts +13 -0
- package/dist/commands/init/steps/agent-skills.js +45 -0
- package/dist/commands/init/steps/ai-sync.d.ts +15 -0
- package/dist/commands/init/steps/ai-sync.js +62 -0
- package/dist/commands/init/steps/ci.d.ts +24 -0
- package/dist/commands/init/steps/ci.js +74 -0
- package/dist/commands/init/steps/claude-md.d.ts +14 -0
- package/dist/commands/init/steps/claude-md.js +54 -0
- package/dist/commands/init/steps/code-graph.d.ts +16 -0
- package/dist/commands/init/steps/code-graph.js +68 -0
- package/dist/commands/init/steps/context-dir.d.ts +13 -0
- package/dist/commands/init/steps/context-dir.js +47 -0
- package/dist/commands/init/steps/docker-deploy.d.ts +16 -0
- package/dist/commands/init/steps/docker-deploy.js +60 -0
- package/dist/commands/init/steps/ghagga.d.ts +15 -0
- package/dist/commands/init/steps/ghagga.js +58 -0
- package/dist/commands/init/steps/git.d.ts +25 -0
- package/dist/commands/init/steps/git.js +81 -0
- package/dist/commands/init/steps/gitignore.d.ts +13 -0
- package/dist/commands/init/steps/gitignore.js +39 -0
- package/dist/commands/init/steps/local-ai.d.ts +14 -0
- package/dist/commands/init/steps/local-ai.js +53 -0
- package/dist/commands/init/steps/manifest.d.ts +13 -0
- package/dist/commands/init/steps/manifest.js +58 -0
- package/dist/commands/init/steps/memory.d.ts +15 -0
- package/dist/commands/init/steps/memory.js +60 -0
- package/dist/commands/init/steps/mock.d.ts +13 -0
- package/dist/commands/init/steps/mock.js +64 -0
- package/dist/commands/init/steps/sdd.d.ts +12 -0
- package/dist/commands/init/steps/sdd.js +40 -0
- package/dist/commands/init/steps/security.d.ts +28 -0
- package/dist/commands/init/steps/security.js +103 -0
- package/dist/commands/init/types.d.ts +30 -0
- package/dist/commands/init/types.js +2 -0
- package/dist/commands/init.d.ts +5 -4
- package/dist/commands/init.js +42 -387
- package/dist/commands/llmstxt.d.ts +1 -1
- package/dist/commands/llmstxt.js +36 -34
- package/dist/commands/plugin.d.ts +10 -1
- package/dist/commands/plugin.js +92 -47
- package/dist/commands/security.d.ts +11 -5
- package/dist/commands/security.js +236 -77
- package/dist/commands/skills/analysis.d.ts +19 -0
- package/dist/commands/skills/analysis.js +211 -0
- package/dist/commands/skills/benchmark.d.ts +6 -0
- package/dist/commands/skills/benchmark.js +82 -0
- package/dist/commands/skills/constants.d.ts +10 -0
- package/dist/commands/skills/constants.js +27 -0
- package/dist/commands/skills/directives.d.ts +21 -0
- package/dist/commands/skills/directives.js +101 -0
- package/dist/commands/skills/parsing.d.ts +16 -0
- package/dist/commands/skills/parsing.js +108 -0
- package/dist/commands/skills/rules.d.ts +5 -0
- package/dist/commands/skills/rules.js +32 -0
- package/dist/commands/skills/scoring.d.ts +47 -0
- package/dist/commands/skills/scoring.js +306 -0
- package/dist/commands/skills.d.ts +8 -69
- package/dist/commands/skills.js +9 -490
- package/dist/commands/tdd-pipeline.d.ts +17 -0
- package/dist/commands/tdd-pipeline.js +144 -0
- package/dist/commands/tdd.d.ts +1 -1
- package/dist/commands/tdd.js +21 -18
- package/dist/commands/workflow.d.ts +23 -0
- package/dist/commands/workflow.js +116 -0
- package/dist/constants.d.ts +15 -1
- package/dist/constants.js +161 -122
- package/dist/index.js +43 -280
- package/dist/lib/agent-skills.d.ts +36 -1
- package/dist/lib/agent-skills.js +168 -19
- package/dist/lib/atlassian-mcp.d.ts +42 -0
- package/dist/lib/atlassian-mcp.js +98 -0
- package/dist/lib/auto-skill-install.d.ts +37 -0
- package/dist/lib/auto-skill-install.js +91 -0
- package/dist/lib/auto-wire.d.ts +20 -0
- package/dist/lib/auto-wire.js +240 -0
- package/dist/lib/ci-config.d.ts +65 -0
- package/dist/lib/ci-config.js +297 -0
- package/dist/lib/claudemd.d.ts +13 -1
- package/dist/lib/claudemd.js +174 -24
- package/dist/lib/codex-export.d.ts +1 -1
- package/dist/lib/codex-export.js +26 -32
- package/dist/lib/common.d.ts +1 -1
- package/dist/lib/common.js +52 -44
- package/dist/lib/context.d.ts +17 -2
- package/dist/lib/context.js +142 -13
- package/dist/lib/crash-recovery.d.ts +34 -0
- package/dist/lib/crash-recovery.js +123 -0
- package/dist/lib/docker.d.ts +23 -2
- package/dist/lib/docker.js +168 -131
- package/dist/lib/exec.d.ts +10 -0
- package/dist/lib/exec.js +11 -0
- package/dist/lib/frontmatter.d.ts +1 -1
- package/dist/lib/frontmatter.js +29 -15
- package/dist/lib/harness-audit.d.ts +35 -0
- package/dist/lib/harness-audit.js +277 -0
- package/dist/lib/parallel-batch.d.ts +42 -0
- package/dist/lib/parallel-batch.js +88 -0
- package/dist/lib/plugin.d.ts +9 -3
- package/dist/lib/plugin.js +127 -70
- package/dist/lib/secret-scanner.d.ts +30 -0
- package/dist/lib/secret-scanner.js +274 -0
- package/dist/lib/security-analysis.d.ts +74 -0
- package/dist/lib/security-analysis.js +489 -0
- package/dist/lib/skill-publish.d.ts +40 -0
- package/dist/lib/skill-publish.js +164 -0
- package/dist/lib/skill-scanner.d.ts +63 -0
- package/dist/lib/skill-scanner.js +383 -0
- package/dist/lib/stack-detector.d.ts +38 -0
- package/dist/lib/stack-detector.js +207 -0
- package/dist/lib/supply-chain.d.ts +23 -0
- package/dist/lib/supply-chain.js +126 -0
- package/dist/lib/team-presets.d.ts +53 -0
- package/dist/lib/team-presets.js +201 -0
- package/dist/lib/template.d.ts +16 -1
- package/dist/lib/template.js +46 -17
- package/dist/lib/workflow/discovery.d.ts +27 -0
- package/dist/lib/workflow/discovery.js +85 -0
- package/dist/lib/workflow/index.d.ts +5 -0
- package/dist/lib/workflow/index.js +5 -0
- package/dist/lib/workflow/parser.d.ts +16 -0
- package/dist/lib/workflow/parser.js +198 -0
- package/dist/lib/workflow/renderer.d.ts +9 -0
- package/dist/lib/workflow/renderer.js +152 -0
- package/dist/lib/workflow/validator.d.ts +10 -0
- package/dist/lib/workflow/validator.js +189 -0
- package/dist/tasks/index.d.ts +4 -0
- package/dist/tasks/index.js +4 -0
- package/dist/tasks/scaffold-tasks.d.ts +3 -0
- package/dist/tasks/scaffold-tasks.js +14 -0
- package/dist/tasks/task-id.d.ts +30 -0
- package/dist/tasks/task-id.js +59 -0
- package/dist/tasks/task-tracker.d.ts +15 -0
- package/dist/tasks/task-tracker.js +81 -0
- package/dist/types/index.d.ts +134 -6
- package/dist/types/index.js +11 -1
- package/dist/ui/AnalyzeUI.d.ts +1 -1
- package/dist/ui/AnalyzeUI.js +38 -39
- package/dist/ui/App.d.ts +5 -3
- package/dist/ui/App.js +86 -46
- package/dist/ui/AutoSkills.d.ts +9 -0
- package/dist/ui/AutoSkills.js +124 -0
- package/dist/ui/CI.d.ts +2 -2
- package/dist/ui/CI.js +24 -26
- package/dist/ui/CIContext.d.ts +1 -1
- package/dist/ui/CIContext.js +2 -2
- package/dist/ui/CISelector.d.ts +2 -2
- package/dist/ui/CISelector.js +23 -15
- package/dist/ui/Doctor.d.ts +1 -1
- package/dist/ui/Doctor.js +35 -29
- package/dist/ui/Header.d.ts +1 -1
- package/dist/ui/Header.js +14 -14
- package/dist/ui/HookProfileSelector.d.ts +9 -0
- package/dist/ui/HookProfileSelector.js +54 -0
- package/dist/ui/LlmsTxt.d.ts +1 -1
- package/dist/ui/LlmsTxt.js +31 -22
- package/dist/ui/MemorySelector.d.ts +2 -2
- package/dist/ui/MemorySelector.js +28 -16
- package/dist/ui/NameInput.d.ts +1 -1
- package/dist/ui/NameInput.js +21 -21
- package/dist/ui/OptionSelector.d.ts +6 -2
- package/dist/ui/OptionSelector.js +83 -32
- package/dist/ui/Plugin.d.ts +4 -3
- package/dist/ui/Plugin.js +78 -35
- package/dist/ui/Progress.d.ts +3 -3
- package/dist/ui/Progress.js +23 -22
- package/dist/ui/Skills.d.ts +2 -2
- package/dist/ui/Skills.js +61 -32
- package/dist/ui/StackSelector.d.ts +2 -2
- package/dist/ui/StackSelector.js +27 -17
- package/dist/ui/Summary.d.ts +3 -3
- package/dist/ui/Summary.js +60 -50
- package/dist/ui/Welcome.d.ts +1 -1
- package/dist/ui/Welcome.js +15 -16
- package/dist/ui/theme.d.ts +1 -1
- package/dist/ui/theme.js +6 -6
- package/lib/common.psm1 +167 -0
- package/lib/common.sh +30 -0
- package/package.json +46 -24
- package/templates/common/atlassian/mcp-atlassian-snippet.json +16 -0
- package/templates/common/repoforge/mcp-repoforge-snippet.json +11 -0
- package/templates/common/repoforge/repoforge.yaml +34 -0
- package/templates/github/deploy-docker-zero-downtime.yml +140 -0
- package/templates/github/repoforge-graph.yml +45 -0
- package/templates/gitlab/deploy-docker-zero-downtime.yml +57 -0
- package/templates/local-ai/.env.example +17 -0
- package/templates/local-ai/docker-compose.yml +95 -0
- package/templates/security-hooks/claude-settings-security.json +30 -0
- package/templates/security-hooks/commit-msg-signing +29 -0
- package/templates/security-hooks/pre-commit-permissions +74 -0
- package/templates/security-hooks/pre-commit-secrets +74 -0
- package/templates/security-hooks/pre-push-branch-protection +62 -0
- package/templates/security-hooks/pre-push-deps +83 -0
- package/templates/security-hooks/pre-push-signing +67 -0
- package/templates/woodpecker/deploy-docker-zero-downtime.yml +50 -0
- package/templates/workflows/ci-pipeline.dot +15 -0
- package/templates/workflows/feature-flow.dot +21 -0
- package/templates/workflows/release.dot +16 -0
- package/dist/__integration__/helpers.d.ts +0 -20
- package/dist/__integration__/helpers.d.ts.map +0 -1
- package/dist/__integration__/helpers.js +0 -31
- package/dist/__integration__/helpers.js.map +0 -1
- package/dist/commands/analyze.d.ts.map +0 -1
- package/dist/commands/analyze.js.map +0 -1
- package/dist/commands/ci.d.ts.map +0 -1
- package/dist/commands/ci.js.map +0 -1
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/llmstxt.d.ts.map +0 -1
- package/dist/commands/llmstxt.js.map +0 -1
- package/dist/commands/plugin.d.ts.map +0 -1
- package/dist/commands/plugin.js.map +0 -1
- package/dist/commands/security.d.ts.map +0 -1
- package/dist/commands/security.js.map +0 -1
- package/dist/commands/skills.d.ts.map +0 -1
- package/dist/commands/skills.js.map +0 -1
- package/dist/commands/tdd.d.ts.map +0 -1
- package/dist/commands/tdd.js.map +0 -1
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/agent-skills.d.ts.map +0 -1
- package/dist/lib/agent-skills.js.map +0 -1
- package/dist/lib/claudemd.d.ts.map +0 -1
- package/dist/lib/claudemd.js.map +0 -1
- package/dist/lib/codex-export.d.ts.map +0 -1
- package/dist/lib/codex-export.js.map +0 -1
- package/dist/lib/common.d.ts.map +0 -1
- package/dist/lib/common.js.map +0 -1
- package/dist/lib/context.d.ts.map +0 -1
- package/dist/lib/context.js.map +0 -1
- package/dist/lib/docker.d.ts.map +0 -1
- package/dist/lib/docker.js.map +0 -1
- package/dist/lib/frontmatter.d.ts.map +0 -1
- package/dist/lib/frontmatter.js.map +0 -1
- package/dist/lib/plugin.d.ts.map +0 -1
- package/dist/lib/plugin.js.map +0 -1
- package/dist/lib/template.d.ts.map +0 -1
- package/dist/lib/template.js.map +0 -1
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js.map +0 -1
- package/dist/ui/AnalyzeUI.d.ts.map +0 -1
- package/dist/ui/AnalyzeUI.js.map +0 -1
- package/dist/ui/App.d.ts.map +0 -1
- package/dist/ui/App.js.map +0 -1
- package/dist/ui/CI.d.ts.map +0 -1
- package/dist/ui/CI.js.map +0 -1
- package/dist/ui/CIContext.d.ts.map +0 -1
- package/dist/ui/CIContext.js.map +0 -1
- package/dist/ui/CISelector.d.ts.map +0 -1
- package/dist/ui/CISelector.js.map +0 -1
- package/dist/ui/Doctor.d.ts.map +0 -1
- package/dist/ui/Doctor.js.map +0 -1
- package/dist/ui/Header.d.ts.map +0 -1
- package/dist/ui/Header.js.map +0 -1
- package/dist/ui/LlmsTxt.d.ts.map +0 -1
- package/dist/ui/LlmsTxt.js.map +0 -1
- package/dist/ui/MemorySelector.d.ts.map +0 -1
- package/dist/ui/MemorySelector.js.map +0 -1
- package/dist/ui/NameInput.d.ts.map +0 -1
- package/dist/ui/NameInput.js.map +0 -1
- package/dist/ui/OptionSelector.d.ts.map +0 -1
- package/dist/ui/OptionSelector.js.map +0 -1
- package/dist/ui/Plugin.d.ts.map +0 -1
- package/dist/ui/Plugin.js.map +0 -1
- package/dist/ui/Progress.d.ts.map +0 -1
- package/dist/ui/Progress.js.map +0 -1
- package/dist/ui/Skills.d.ts.map +0 -1
- package/dist/ui/Skills.js.map +0 -1
- package/dist/ui/StackSelector.d.ts.map +0 -1
- package/dist/ui/StackSelector.js.map +0 -1
- package/dist/ui/Summary.d.ts.map +0 -1
- package/dist/ui/Summary.js.map +0 -1
- package/dist/ui/Welcome.d.ts.map +0 -1
- package/dist/ui/Welcome.js.map +0 -1
- package/dist/ui/theme.d.ts.map +0 -1
- package/dist/ui/theme.js.map +0 -1
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import { PLUGIN_MANIFEST_FILE } from "../constants.js";
|
|
4
|
+
import { parseFrontmatter } from "./frontmatter.js";
|
|
5
|
+
// ── Core ───────────────────────────────────────────────────────────────────
|
|
6
|
+
/**
|
|
7
|
+
* Package a skill directory for marketplace distribution.
|
|
8
|
+
*
|
|
9
|
+
* Reads the SKILL.md, extracts metadata from frontmatter, and generates
|
|
10
|
+
* a plugin.json compatible with both `javi-forge plugin install` and
|
|
11
|
+
* `claude plugin install` (Anthropic's plugin format).
|
|
12
|
+
*
|
|
13
|
+
* Expected input structure:
|
|
14
|
+
* skill-name/
|
|
15
|
+
* SKILL.md
|
|
16
|
+
* (optional other files)
|
|
17
|
+
*
|
|
18
|
+
* Output:
|
|
19
|
+
* skill-name/
|
|
20
|
+
* SKILL.md
|
|
21
|
+
* plugin.json ← generated
|
|
22
|
+
*/
|
|
23
|
+
export async function publishSkill(options) {
|
|
24
|
+
const { skillDir, author, repository, tags = [], dryRun = false } = options;
|
|
25
|
+
// Validate skill directory
|
|
26
|
+
const skillMdPath = path.join(skillDir, "SKILL.md");
|
|
27
|
+
if (!(await fs.pathExists(skillMdPath))) {
|
|
28
|
+
return { success: false, error: `SKILL.md not found in ${skillDir}` };
|
|
29
|
+
}
|
|
30
|
+
// Refuse to follow a symlink. If SKILL.md is a symlink to a file outside
|
|
31
|
+
// the skill directory (e.g., /etc/passwd), readFile + the later fs.copy
|
|
32
|
+
// would expose the target's contents in the published manifest bundle.
|
|
33
|
+
// Mirrors the symlink-reject pattern in security.ts writeBaseline /
|
|
34
|
+
// ci.ts installCIHooks.
|
|
35
|
+
try {
|
|
36
|
+
const stat = await fs.lstat(skillMdPath);
|
|
37
|
+
if (stat.isSymbolicLink()) {
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
error: `SKILL.md is a symlink; refusing to publish. Use a regular file.`,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// pathExists already returned true above; lstat failures here are
|
|
46
|
+
// unexpected. Fall through to readFile which will surface the error.
|
|
47
|
+
}
|
|
48
|
+
// Read and parse SKILL.md
|
|
49
|
+
let raw;
|
|
50
|
+
try {
|
|
51
|
+
raw = await fs.readFile(skillMdPath, "utf-8");
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return { success: false, error: "Failed to read SKILL.md" };
|
|
55
|
+
}
|
|
56
|
+
const parsed = parseFrontmatter(raw);
|
|
57
|
+
const frontmatter = parsed?.data ?? {};
|
|
58
|
+
// Extract metadata
|
|
59
|
+
const name = frontmatter.name ?? path.basename(skillDir);
|
|
60
|
+
const version = frontmatter.version ?? "1.0.0";
|
|
61
|
+
const description = frontmatter.description ?? `${name} AI skill`;
|
|
62
|
+
// Validate name format (kebab-case)
|
|
63
|
+
if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
error: `Skill name "${name}" must be kebab-case (e.g., "react-19", "tailwind-4")`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// Validate description length
|
|
70
|
+
if (description.length < 10) {
|
|
71
|
+
return {
|
|
72
|
+
success: false,
|
|
73
|
+
error: "Description must be at least 10 characters",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Build plugin.json manifest
|
|
77
|
+
const manifest = {
|
|
78
|
+
name,
|
|
79
|
+
version,
|
|
80
|
+
description: description.length > 200
|
|
81
|
+
? `${description.slice(0, 197)}...`
|
|
82
|
+
: description,
|
|
83
|
+
skills: [name],
|
|
84
|
+
tags: tags.length > 0 ? tags : extractTagsFromDescription(description),
|
|
85
|
+
...(author ? { author } : {}),
|
|
86
|
+
...(repository ? { repository } : {}),
|
|
87
|
+
};
|
|
88
|
+
if (!dryRun) {
|
|
89
|
+
const pluginJsonPath = path.join(skillDir, PLUGIN_MANIFEST_FILE);
|
|
90
|
+
await fs.writeJson(pluginJsonPath, manifest, { spaces: 2 });
|
|
91
|
+
// Also create a skills/ subdirectory structure for plugin compatibility
|
|
92
|
+
const skillsSubdir = path.join(skillDir, "skills", name);
|
|
93
|
+
if (!(await fs.pathExists(skillsSubdir))) {
|
|
94
|
+
await fs.ensureDir(skillsSubdir);
|
|
95
|
+
// Symlink or copy SKILL.md into skills/name/
|
|
96
|
+
const targetSkillMd = path.join(skillsSubdir, "SKILL.md");
|
|
97
|
+
if (!(await fs.pathExists(targetSkillMd))) {
|
|
98
|
+
await fs.copy(skillMdPath, targetSkillMd);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { success: true, pluginJsonPath, manifest };
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
success: true,
|
|
105
|
+
pluginJsonPath: path.join(skillDir, PLUGIN_MANIFEST_FILE),
|
|
106
|
+
manifest,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
110
|
+
/**
|
|
111
|
+
* Extract reasonable tags from a skill description.
|
|
112
|
+
* Looks for common tech keywords.
|
|
113
|
+
*/
|
|
114
|
+
function extractTagsFromDescription(description) {
|
|
115
|
+
const keywords = [
|
|
116
|
+
"react",
|
|
117
|
+
"next",
|
|
118
|
+
"nextjs",
|
|
119
|
+
"angular",
|
|
120
|
+
"vue",
|
|
121
|
+
"svelte",
|
|
122
|
+
"typescript",
|
|
123
|
+
"javascript",
|
|
124
|
+
"python",
|
|
125
|
+
"go",
|
|
126
|
+
"rust",
|
|
127
|
+
"java",
|
|
128
|
+
"elixir",
|
|
129
|
+
"tailwind",
|
|
130
|
+
"css",
|
|
131
|
+
"styling",
|
|
132
|
+
"testing",
|
|
133
|
+
"test",
|
|
134
|
+
"e2e",
|
|
135
|
+
"unit",
|
|
136
|
+
"api",
|
|
137
|
+
"rest",
|
|
138
|
+
"graphql",
|
|
139
|
+
"ai",
|
|
140
|
+
"llm",
|
|
141
|
+
"ml",
|
|
142
|
+
"agent",
|
|
143
|
+
"security",
|
|
144
|
+
"auth",
|
|
145
|
+
"database",
|
|
146
|
+
"sql",
|
|
147
|
+
"nosql",
|
|
148
|
+
"docker",
|
|
149
|
+
"kubernetes",
|
|
150
|
+
"devops",
|
|
151
|
+
"ci",
|
|
152
|
+
"cd",
|
|
153
|
+
"state",
|
|
154
|
+
"management",
|
|
155
|
+
"store",
|
|
156
|
+
"validation",
|
|
157
|
+
"schema",
|
|
158
|
+
"zod",
|
|
159
|
+
];
|
|
160
|
+
const lower = description.toLowerCase();
|
|
161
|
+
const found = keywords.filter((kw) => lower.includes(kw));
|
|
162
|
+
return found.slice(0, 10); // Max 10 tags per plugin spec
|
|
163
|
+
}
|
|
164
|
+
//# sourceMappingURL=skill-publish.js.map
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CI-level skill security scanner — pre-install analysis of SKILL.md files.
|
|
3
|
+
*
|
|
4
|
+
* Detects credential theft, code injection, data exfiltration, and scope escape
|
|
5
|
+
* patterns before skills are installed. Inspired by the SkillGuard skill
|
|
6
|
+
* (javi-ai) but implemented as a programmatic module with structured output.
|
|
7
|
+
*/
|
|
8
|
+
import type { SecuritySeverity } from "../types/index.js";
|
|
9
|
+
export type SkillThreatCategory = "credential-theft" | "code-injection" | "data-exfiltration" | "scope-escape" | "privilege-escalation" | "destructive-command" | "self-modification" | "hook-tampering" | "obfuscation" | "missing-provenance" | "excessive-permissions" | "file-traversal";
|
|
10
|
+
export interface SkillThreat {
|
|
11
|
+
category: SkillThreatCategory;
|
|
12
|
+
severity: SecuritySeverity;
|
|
13
|
+
pattern: string;
|
|
14
|
+
line: number;
|
|
15
|
+
context: string;
|
|
16
|
+
message: string;
|
|
17
|
+
}
|
|
18
|
+
export type SkillScanVerdict = "pass" | "warn" | "block";
|
|
19
|
+
export interface SkillScanResult {
|
|
20
|
+
skillPath: string;
|
|
21
|
+
skillName: string;
|
|
22
|
+
verdict: SkillScanVerdict;
|
|
23
|
+
threats: SkillThreat[];
|
|
24
|
+
summary: SkillScanSummary;
|
|
25
|
+
}
|
|
26
|
+
export interface SkillScanSummary {
|
|
27
|
+
total: number;
|
|
28
|
+
critical: number;
|
|
29
|
+
high: number;
|
|
30
|
+
moderate: number;
|
|
31
|
+
low: number;
|
|
32
|
+
}
|
|
33
|
+
interface ThreatPattern {
|
|
34
|
+
category: SkillThreatCategory;
|
|
35
|
+
severity: SecuritySeverity;
|
|
36
|
+
pattern: RegExp;
|
|
37
|
+
message: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Ordered by severity (critical first). Each pattern is tested against
|
|
41
|
+
* every non-comment line in the skill file.
|
|
42
|
+
*/
|
|
43
|
+
export declare const THREAT_PATTERNS: ThreatPattern[];
|
|
44
|
+
interface ProvenanceInfo {
|
|
45
|
+
hasAuthor: boolean;
|
|
46
|
+
hasVersion: boolean;
|
|
47
|
+
hasDescription: boolean;
|
|
48
|
+
}
|
|
49
|
+
export declare function checkProvenance(content: string): ProvenanceInfo;
|
|
50
|
+
export declare function scanSkillContent(content: string, _filePath: string): SkillThreat[];
|
|
51
|
+
export declare function computeVerdict(threats: SkillThreat[]): SkillScanVerdict;
|
|
52
|
+
export declare function computeScanSummary(threats: SkillThreat[]): SkillScanSummary;
|
|
53
|
+
export declare function extractSkillName(content: string, filePath: string): string;
|
|
54
|
+
export declare function scanSkillFile(filePath: string): Promise<SkillScanResult>;
|
|
55
|
+
/**
|
|
56
|
+
* Scan all SKILL.md files in a directory (recursive).
|
|
57
|
+
* Useful for scanning a plugin's skills directory before installation.
|
|
58
|
+
*/
|
|
59
|
+
export declare function scanSkillsDirectory(dir: string): Promise<SkillScanResult[]>;
|
|
60
|
+
export declare function formatScanReport(result: SkillScanResult): string;
|
|
61
|
+
export declare function formatBatchReport(results: SkillScanResult[]): string;
|
|
62
|
+
export {};
|
|
63
|
+
//# sourceMappingURL=skill-scanner.d.ts.map
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CI-level skill security scanner — pre-install analysis of SKILL.md files.
|
|
3
|
+
*
|
|
4
|
+
* Detects credential theft, code injection, data exfiltration, and scope escape
|
|
5
|
+
* patterns before skills are installed. Inspired by the SkillGuard skill
|
|
6
|
+
* (javi-ai) but implemented as a programmatic module with structured output.
|
|
7
|
+
*/
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import fs from "fs-extra";
|
|
10
|
+
/**
|
|
11
|
+
* Ordered by severity (critical first). Each pattern is tested against
|
|
12
|
+
* every non-comment line in the skill file.
|
|
13
|
+
*/
|
|
14
|
+
export const THREAT_PATTERNS = [
|
|
15
|
+
// ── Critical: Credential Theft ──
|
|
16
|
+
{
|
|
17
|
+
category: "credential-theft",
|
|
18
|
+
severity: "critical",
|
|
19
|
+
pattern: /(?:~\/\.ssh|~\/\.aws|~\/\.config\/gh|~\/\.gnupg|~\/\.netrc|~\/\.npmrc|\/etc\/shadow|\/etc\/passwd|id_rsa|id_ed25519)/i,
|
|
20
|
+
message: "References sensitive credential paths — potential data theft",
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
category: "credential-theft",
|
|
24
|
+
severity: "critical",
|
|
25
|
+
pattern: /(?:AWS_SECRET_ACCESS_KEY|GITHUB_TOKEN|NPM_TOKEN|PRIVATE_KEY|API_SECRET|DATABASE_URL|MONGO_URI|REDIS_URL)\s*[=:]/i,
|
|
26
|
+
message: "References environment variable containing secrets — potential exfiltration",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
category: "credential-theft",
|
|
30
|
+
severity: "critical",
|
|
31
|
+
pattern: /(?:read|cat|type|get-content|less|more|head|tail)\s+.*(?:\.env|credentials|secrets?\.(json|yaml|yml|toml))/i,
|
|
32
|
+
message: "Reads secret/credential files directly — potential credential theft",
|
|
33
|
+
},
|
|
34
|
+
// ── Critical: Code Injection ──
|
|
35
|
+
{
|
|
36
|
+
category: "code-injection",
|
|
37
|
+
severity: "critical",
|
|
38
|
+
pattern: /\beval\s*\(\s*(?:user|input|req|params|args|data|body)/i,
|
|
39
|
+
message: "eval() with user-controlled input — enables arbitrary code execution",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
category: "code-injection",
|
|
43
|
+
severity: "critical",
|
|
44
|
+
pattern: /\b(?:exec|execSync|spawn|spawnSync)\s*\(\s*(?:user|input|req|params|args|data)/i,
|
|
45
|
+
message: "Process execution with user input — enables command injection",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
category: "code-injection",
|
|
49
|
+
severity: "critical",
|
|
50
|
+
pattern: /\b(?:subprocess\.(?:call|run|Popen)|os\.system|os\.popen)\s*\(\s*(?:f['"]|user|input|req)/i,
|
|
51
|
+
message: "Python subprocess with user input — enables command injection",
|
|
52
|
+
},
|
|
53
|
+
// ── Critical: Data Exfiltration ──
|
|
54
|
+
{
|
|
55
|
+
category: "data-exfiltration",
|
|
56
|
+
severity: "critical",
|
|
57
|
+
pattern: /(?:curl|wget|fetch|axios|got|request)\s+.*(?:--data|--upload|-d\s|-F\s|\.post\(|\.put\()\s*.*(?:\/etc\/|~\/\.|\.env|secret|credential|token|key)/i,
|
|
58
|
+
message: "Sending sensitive data to external endpoint — data exfiltration",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
category: "data-exfiltration",
|
|
62
|
+
severity: "high",
|
|
63
|
+
pattern: /(?:curl|wget)\s+(?:-[sSfLkO]*\s+)*(?:https?:\/\/)?(?!localhost|127\.0\.0\.1|0\.0\.0\.0|::1)[\w.-]+\.\w{2,}/i,
|
|
64
|
+
message: "Outbound HTTP request to external URL — verify the destination is trusted",
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
category: "data-exfiltration",
|
|
68
|
+
severity: "high",
|
|
69
|
+
pattern: /fetch\s*\(\s*['"`]https?:\/\/(?!localhost|127\.0\.0\.1)/i,
|
|
70
|
+
message: "fetch() to external URL — verify the destination is trusted",
|
|
71
|
+
},
|
|
72
|
+
// ── Critical: Scope Escape ──
|
|
73
|
+
{
|
|
74
|
+
category: "scope-escape",
|
|
75
|
+
severity: "critical",
|
|
76
|
+
pattern: /(?:ignore\s+(?:all\s+)?previous|disregard\s+(?:all\s+)?(?:prior|above)|override\s+(?:safety|security|rules)|bypass\s+(?:safety|security|restrictions))/i,
|
|
77
|
+
message: "Prompt injection attempt — tries to override safety instructions",
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
category: "scope-escape",
|
|
81
|
+
severity: "critical",
|
|
82
|
+
pattern: /(?:you\s+are\s+now|from\s+now\s+on|new\s+instructions?:?\s+)/i,
|
|
83
|
+
message: "Attempts to redefine agent identity — prompt injection risk",
|
|
84
|
+
},
|
|
85
|
+
// ── Critical: Self-Modification ──
|
|
86
|
+
{
|
|
87
|
+
category: "self-modification",
|
|
88
|
+
severity: "critical",
|
|
89
|
+
pattern: /(?:write|append|modify|edit|overwrite|patch)\s+.*(?:CLAUDE\.md|AGENTS\.md|settings\.json|\.claude\/)/i,
|
|
90
|
+
message: "Attempts to modify agent config files — persistence/privilege escalation",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
category: "hook-tampering",
|
|
94
|
+
severity: "critical",
|
|
95
|
+
pattern: /(?:rm|remove|delete|disable)\s+.*(?:pre-commit|pre-push|commit-msg|\.git\/hooks)/i,
|
|
96
|
+
message: "Attempts to disable or remove git hooks — bypasses safety guardrails",
|
|
97
|
+
},
|
|
98
|
+
// ── High: Privilege Escalation ──
|
|
99
|
+
{
|
|
100
|
+
category: "privilege-escalation",
|
|
101
|
+
severity: "high",
|
|
102
|
+
pattern: /\bsudo\s+/i,
|
|
103
|
+
message: "Uses sudo — may escalate to root privileges",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
category: "privilege-escalation",
|
|
107
|
+
severity: "high",
|
|
108
|
+
pattern: /chmod\s+(?:777|666|a\+[rwx])/i,
|
|
109
|
+
message: "Sets overly permissive file permissions — security risk",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
category: "privilege-escalation",
|
|
113
|
+
severity: "high",
|
|
114
|
+
pattern: /chown\s+root/i,
|
|
115
|
+
message: "Changes file ownership to root — privilege escalation",
|
|
116
|
+
},
|
|
117
|
+
// ── High: Destructive Commands ──
|
|
118
|
+
{
|
|
119
|
+
category: "destructive-command",
|
|
120
|
+
severity: "high",
|
|
121
|
+
pattern: /\brm\s+-rf?\s+(?:\/|~|\$HOME|\.\.)/i,
|
|
122
|
+
message: "Destructive file deletion targeting root, home, or parent directories",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
category: "destructive-command",
|
|
126
|
+
severity: "high",
|
|
127
|
+
pattern: /git\s+push\s+--force\b/i,
|
|
128
|
+
message: "Force push can destroy remote history",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
category: "destructive-command",
|
|
132
|
+
severity: "high",
|
|
133
|
+
pattern: /DROP\s+(?:TABLE|DATABASE|INDEX)/i,
|
|
134
|
+
message: "SQL DROP statement — potential data loss",
|
|
135
|
+
},
|
|
136
|
+
// ── High: File Traversal ──
|
|
137
|
+
{
|
|
138
|
+
category: "file-traversal",
|
|
139
|
+
severity: "high",
|
|
140
|
+
pattern: /(?:\.\.\/){2,}/,
|
|
141
|
+
message: "Multiple path traversal sequences — may access files outside project",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
category: "file-traversal",
|
|
145
|
+
severity: "high",
|
|
146
|
+
pattern: /(?:readFile|writeFile|open|fs\.)\s*\(\s*['"`]\/(?:etc|usr|var|tmp|root|home)\//i,
|
|
147
|
+
message: "Absolute path to system directory — scope escape risk",
|
|
148
|
+
},
|
|
149
|
+
// ── Moderate: Obfuscation ──
|
|
150
|
+
{
|
|
151
|
+
category: "obfuscation",
|
|
152
|
+
severity: "moderate",
|
|
153
|
+
pattern: /(?:atob|btoa|Buffer\.from)\s*\(\s*['"`][A-Za-z0-9+/]{40,}/,
|
|
154
|
+
message: "Base64 encoded content — may hide malicious payloads",
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
category: "obfuscation",
|
|
158
|
+
severity: "moderate",
|
|
159
|
+
pattern: /\\x[0-9a-fA-F]{2}(?:\\x[0-9a-fA-F]{2}){4,}/,
|
|
160
|
+
message: "Hex-encoded string sequence — may hide malicious payloads",
|
|
161
|
+
},
|
|
162
|
+
// ── Moderate: Excessive Permissions ──
|
|
163
|
+
{
|
|
164
|
+
category: "excessive-permissions",
|
|
165
|
+
severity: "moderate",
|
|
166
|
+
pattern: /allowed-tools:\s*.*(?:Bash|Edit|Write|Read|Glob|Grep|WebFetch|WebSearch).*(?:Bash|Edit|Write|Read|Glob|Grep|WebFetch|WebSearch).*(?:Bash|Edit|Write|Read|Glob|Grep|WebFetch|WebSearch).*(?:Bash|Edit|Write|Read|Glob|Grep|WebFetch|WebSearch)/i,
|
|
167
|
+
message: "Requests many tools — verify skill actually needs all of them",
|
|
168
|
+
},
|
|
169
|
+
];
|
|
170
|
+
export function checkProvenance(content) {
|
|
171
|
+
// Check YAML frontmatter
|
|
172
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
173
|
+
if (!frontmatterMatch) {
|
|
174
|
+
return { hasAuthor: false, hasVersion: false, hasDescription: false };
|
|
175
|
+
}
|
|
176
|
+
const fm = frontmatterMatch[1];
|
|
177
|
+
return {
|
|
178
|
+
hasAuthor: /\bauthor\s*:/i.test(fm),
|
|
179
|
+
hasVersion: /\bversion\s*:/i.test(fm),
|
|
180
|
+
hasDescription: /\bdescription\s*:/i.test(fm),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
// =============================================================================
|
|
184
|
+
// Core scanner
|
|
185
|
+
// =============================================================================
|
|
186
|
+
export function scanSkillContent(content, _filePath) {
|
|
187
|
+
const threats = [];
|
|
188
|
+
const lines = content.split("\n");
|
|
189
|
+
for (let i = 0; i < lines.length; i++) {
|
|
190
|
+
const line = lines[i];
|
|
191
|
+
const trimmed = line.trim();
|
|
192
|
+
// Skip empty lines
|
|
193
|
+
if (!trimmed)
|
|
194
|
+
continue;
|
|
195
|
+
for (const tp of THREAT_PATTERNS) {
|
|
196
|
+
if (tp.pattern.test(trimmed)) {
|
|
197
|
+
threats.push({
|
|
198
|
+
category: tp.category,
|
|
199
|
+
severity: tp.severity,
|
|
200
|
+
pattern: tp.pattern.source.slice(0, 80),
|
|
201
|
+
line: i + 1,
|
|
202
|
+
context: trimmed.slice(0, 120),
|
|
203
|
+
message: tp.message,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Check provenance
|
|
209
|
+
const prov = checkProvenance(content);
|
|
210
|
+
if (!prov.hasAuthor) {
|
|
211
|
+
threats.push({
|
|
212
|
+
category: "missing-provenance",
|
|
213
|
+
severity: "moderate",
|
|
214
|
+
pattern: "missing author",
|
|
215
|
+
line: 1,
|
|
216
|
+
context: "YAML frontmatter",
|
|
217
|
+
message: "No author metadata — skill origin is unknown",
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
if (!prov.hasVersion) {
|
|
221
|
+
threats.push({
|
|
222
|
+
category: "missing-provenance",
|
|
223
|
+
severity: "moderate",
|
|
224
|
+
pattern: "missing version",
|
|
225
|
+
line: 1,
|
|
226
|
+
context: "YAML frontmatter",
|
|
227
|
+
message: "No version metadata — cannot track updates",
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
return threats;
|
|
231
|
+
}
|
|
232
|
+
// =============================================================================
|
|
233
|
+
// Verdict computation
|
|
234
|
+
// =============================================================================
|
|
235
|
+
export function computeVerdict(threats) {
|
|
236
|
+
if (threats.some((t) => t.severity === "critical"))
|
|
237
|
+
return "block";
|
|
238
|
+
if (threats.some((t) => t.severity === "high"))
|
|
239
|
+
return "warn";
|
|
240
|
+
return "pass";
|
|
241
|
+
}
|
|
242
|
+
export function computeScanSummary(threats) {
|
|
243
|
+
const summary = {
|
|
244
|
+
total: threats.length,
|
|
245
|
+
critical: 0,
|
|
246
|
+
high: 0,
|
|
247
|
+
moderate: 0,
|
|
248
|
+
low: 0,
|
|
249
|
+
};
|
|
250
|
+
for (const t of threats) {
|
|
251
|
+
switch (t.severity) {
|
|
252
|
+
case "critical":
|
|
253
|
+
summary.critical++;
|
|
254
|
+
break;
|
|
255
|
+
case "high":
|
|
256
|
+
summary.high++;
|
|
257
|
+
break;
|
|
258
|
+
case "moderate":
|
|
259
|
+
summary.moderate++;
|
|
260
|
+
break;
|
|
261
|
+
case "low":
|
|
262
|
+
summary.low++;
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return summary;
|
|
267
|
+
}
|
|
268
|
+
// =============================================================================
|
|
269
|
+
// Skill name extraction
|
|
270
|
+
// =============================================================================
|
|
271
|
+
export function extractSkillName(content, filePath) {
|
|
272
|
+
// Try frontmatter name
|
|
273
|
+
const fmMatch = content.match(/^---\n[\s\S]*?\bname:\s*(.+)/m);
|
|
274
|
+
if (fmMatch?.[1])
|
|
275
|
+
return fmMatch[1].trim();
|
|
276
|
+
// Fall back to directory name
|
|
277
|
+
const dirName = path.basename(path.dirname(filePath));
|
|
278
|
+
if (dirName && dirName !== ".")
|
|
279
|
+
return dirName;
|
|
280
|
+
return path.basename(filePath, path.extname(filePath));
|
|
281
|
+
}
|
|
282
|
+
// =============================================================================
|
|
283
|
+
// Main scan function
|
|
284
|
+
// =============================================================================
|
|
285
|
+
export async function scanSkillFile(filePath) {
|
|
286
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
287
|
+
const skillName = extractSkillName(content, filePath);
|
|
288
|
+
const threats = scanSkillContent(content, filePath);
|
|
289
|
+
const verdict = computeVerdict(threats);
|
|
290
|
+
const summary = computeScanSummary(threats);
|
|
291
|
+
return {
|
|
292
|
+
skillPath: filePath,
|
|
293
|
+
skillName,
|
|
294
|
+
verdict,
|
|
295
|
+
threats,
|
|
296
|
+
summary,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Scan all SKILL.md files in a directory (recursive).
|
|
301
|
+
* Useful for scanning a plugin's skills directory before installation.
|
|
302
|
+
*/
|
|
303
|
+
export async function scanSkillsDirectory(dir) {
|
|
304
|
+
const results = [];
|
|
305
|
+
async function walk(currentDir) {
|
|
306
|
+
let entries;
|
|
307
|
+
try {
|
|
308
|
+
entries = await fs.readdir(currentDir);
|
|
309
|
+
}
|
|
310
|
+
catch {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
for (const entry of entries) {
|
|
314
|
+
if (entry === "node_modules" || entry === ".git")
|
|
315
|
+
continue;
|
|
316
|
+
const fullPath = path.join(currentDir, entry);
|
|
317
|
+
let stat;
|
|
318
|
+
try {
|
|
319
|
+
stat = await fs.stat(fullPath);
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (stat.isDirectory()) {
|
|
325
|
+
await walk(fullPath);
|
|
326
|
+
}
|
|
327
|
+
else if (entry === "SKILL.md" ||
|
|
328
|
+
entry === "PLUGIN.md" ||
|
|
329
|
+
entry.toLowerCase() === "skill.md") {
|
|
330
|
+
const result = await scanSkillFile(fullPath);
|
|
331
|
+
results.push(result);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
await walk(dir);
|
|
336
|
+
return results;
|
|
337
|
+
}
|
|
338
|
+
// =============================================================================
|
|
339
|
+
// Report formatting
|
|
340
|
+
// =============================================================================
|
|
341
|
+
export function formatScanReport(result) {
|
|
342
|
+
const lines = [];
|
|
343
|
+
const { summary, threats, verdict } = result;
|
|
344
|
+
lines.push(`=== SkillGuard Scan: ${result.skillName} ===`);
|
|
345
|
+
lines.push(`Path: ${result.skillPath}`);
|
|
346
|
+
lines.push(`Verdict: ${verdict.toUpperCase()}`);
|
|
347
|
+
lines.push(`Findings: ${summary.total} (${summary.critical} critical, ${summary.high} high, ${summary.moderate} moderate, ${summary.low} low)`);
|
|
348
|
+
lines.push("");
|
|
349
|
+
if (threats.length > 0) {
|
|
350
|
+
lines.push("--- Threats ---");
|
|
351
|
+
for (const t of threats) {
|
|
352
|
+
lines.push(`[${t.severity.toUpperCase()}] ${t.category} (line ${t.line})`);
|
|
353
|
+
lines.push(` ${t.message}`);
|
|
354
|
+
lines.push(` Context: ${t.context}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (verdict === "block") {
|
|
358
|
+
lines.push("");
|
|
359
|
+
lines.push("BLOCKED: Critical threats detected. Review and remove before installing.");
|
|
360
|
+
}
|
|
361
|
+
else if (verdict === "warn") {
|
|
362
|
+
lines.push("");
|
|
363
|
+
lines.push("WARNING: High-severity threats detected. Confirm you trust this skill.");
|
|
364
|
+
}
|
|
365
|
+
return lines.join("\n");
|
|
366
|
+
}
|
|
367
|
+
export function formatBatchReport(results) {
|
|
368
|
+
const lines = [];
|
|
369
|
+
const blocked = results.filter((r) => r.verdict === "block");
|
|
370
|
+
const warned = results.filter((r) => r.verdict === "warn");
|
|
371
|
+
const passed = results.filter((r) => r.verdict === "pass");
|
|
372
|
+
lines.push(`=== SkillGuard Batch Scan ===`);
|
|
373
|
+
lines.push(`Scanned: ${results.length} skill(s)`);
|
|
374
|
+
lines.push(`Blocked: ${blocked.length}`);
|
|
375
|
+
lines.push(`Warned: ${warned.length}`);
|
|
376
|
+
lines.push(`Passed: ${passed.length}`);
|
|
377
|
+
lines.push("");
|
|
378
|
+
for (const result of results) {
|
|
379
|
+
lines.push(`[${result.verdict.toUpperCase()}] ${result.skillName} (${result.summary.total} finding(s))`);
|
|
380
|
+
}
|
|
381
|
+
return lines.join("\n");
|
|
382
|
+
}
|
|
383
|
+
//# sourceMappingURL=skill-scanner.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Stack } from "../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Maps detected project signals to recommended AI skills.
|
|
4
|
+
* Each key is a detection signal (file/dependency/pattern),
|
|
5
|
+
* and the value is the list of skills it implies.
|
|
6
|
+
*/
|
|
7
|
+
export declare const SIGNAL_SKILL_MAP: Record<string, string[]>;
|
|
8
|
+
/**
|
|
9
|
+
* Docker-related files that indicate a containerized project.
|
|
10
|
+
* Used by the init command to suggest Docker zero-downtime deploy scaffolding.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DOCKER_FILES: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Detect whether the project uses Docker (Dockerfile or compose file present).
|
|
15
|
+
*/
|
|
16
|
+
export declare function detectDockerPresence(projectDir: string): Promise<boolean>;
|
|
17
|
+
export interface StackDetectionResult {
|
|
18
|
+
/** Primary stack type (node, python, etc.) */
|
|
19
|
+
stack: Stack | null;
|
|
20
|
+
/** All detected signals with their source */
|
|
21
|
+
signals: DetectedSignal[];
|
|
22
|
+
/** De-duplicated list of recommended skill names */
|
|
23
|
+
recommendedSkills: string[];
|
|
24
|
+
}
|
|
25
|
+
export interface DetectedSignal {
|
|
26
|
+
/** What was detected (e.g., "react", "tailwindcss", "tsconfig.json") */
|
|
27
|
+
signal: string;
|
|
28
|
+
/** Where it was found (e.g., "package.json dependencies", "file exists") */
|
|
29
|
+
source: string;
|
|
30
|
+
/** Skills this signal maps to */
|
|
31
|
+
skills: string[];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Scan a project directory and detect its tech stack + recommended skills.
|
|
35
|
+
* Reads package.json deps, pyproject.toml, and well-known config files.
|
|
36
|
+
*/
|
|
37
|
+
export declare function detectProjectStack(projectDir: string): Promise<StackDetectionResult>;
|
|
38
|
+
//# sourceMappingURL=stack-detector.d.ts.map
|