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
package/dist/lib/plugin.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
const execFileAsync = promisify(execFile);
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import { PLUGIN_ASSET_DIRS, PLUGIN_MANIFEST_FILE, PLUGIN_REGISTRY_URL, PLUGINS_DIR, } from "../constants.js";
|
|
4
|
+
import { generateAgentSkillsManifest } from "./agent-skills.js";
|
|
5
|
+
import { autoWirePlugins } from "./auto-wire.js";
|
|
6
|
+
import { execFileAsync } from "./exec.js";
|
|
8
7
|
const KEBAB_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
9
8
|
const SEMVER_RE = /^\d+\.\d+\.\d+$/;
|
|
10
9
|
// ── Validation ──────────────────────────────────────────────────────────────
|
|
@@ -15,49 +14,60 @@ export async function validatePlugin(pluginDir) {
|
|
|
15
14
|
const errors = [];
|
|
16
15
|
// Check plugin.json exists
|
|
17
16
|
const manifestPath = path.join(pluginDir, PLUGIN_MANIFEST_FILE);
|
|
18
|
-
if (!await fs.pathExists(manifestPath)) {
|
|
17
|
+
if (!(await fs.pathExists(manifestPath))) {
|
|
19
18
|
return {
|
|
20
19
|
valid: false,
|
|
21
|
-
errors: [
|
|
20
|
+
errors: [
|
|
21
|
+
{ path: PLUGIN_MANIFEST_FILE, message: "plugin.json not found" },
|
|
22
|
+
],
|
|
22
23
|
manifest: null,
|
|
23
24
|
};
|
|
24
25
|
}
|
|
25
26
|
// Parse manifest
|
|
26
27
|
let manifest;
|
|
27
28
|
try {
|
|
28
|
-
manifest = await fs.readJson(manifestPath);
|
|
29
|
+
manifest = (await fs.readJson(manifestPath));
|
|
29
30
|
}
|
|
30
31
|
catch {
|
|
31
32
|
return {
|
|
32
33
|
valid: false,
|
|
33
|
-
errors: [{ path: PLUGIN_MANIFEST_FILE, message:
|
|
34
|
+
errors: [{ path: PLUGIN_MANIFEST_FILE, message: "invalid JSON" }],
|
|
34
35
|
manifest: null,
|
|
35
36
|
};
|
|
36
37
|
}
|
|
37
38
|
// Validate required fields
|
|
38
|
-
if (typeof manifest.name !==
|
|
39
|
-
errors.push({ path:
|
|
39
|
+
if (typeof manifest.name !== "string" || !manifest.name) {
|
|
40
|
+
errors.push({ path: "name", message: "name is required" });
|
|
40
41
|
}
|
|
41
42
|
else if (!KEBAB_RE.test(manifest.name)) {
|
|
42
|
-
errors.push({ path:
|
|
43
|
+
errors.push({ path: "name", message: "name must be kebab-case" });
|
|
43
44
|
}
|
|
44
45
|
else if (manifest.name.length < 2 || manifest.name.length > 60) {
|
|
45
|
-
errors.push({ path:
|
|
46
|
+
errors.push({ path: "name", message: "name must be 2-60 characters" });
|
|
46
47
|
}
|
|
47
|
-
if (typeof manifest.version !==
|
|
48
|
-
errors.push({ path:
|
|
48
|
+
if (typeof manifest.version !== "string" || !manifest.version) {
|
|
49
|
+
errors.push({ path: "version", message: "version is required" });
|
|
49
50
|
}
|
|
50
51
|
else if (!SEMVER_RE.test(manifest.version)) {
|
|
51
|
-
errors.push({
|
|
52
|
+
errors.push({
|
|
53
|
+
path: "version",
|
|
54
|
+
message: "version must be semver (e.g. 1.0.0)",
|
|
55
|
+
});
|
|
52
56
|
}
|
|
53
|
-
if (typeof manifest.description !==
|
|
54
|
-
errors.push({ path:
|
|
57
|
+
if (typeof manifest.description !== "string" || !manifest.description) {
|
|
58
|
+
errors.push({ path: "description", message: "description is required" });
|
|
55
59
|
}
|
|
56
60
|
else if (manifest.description.length < 10) {
|
|
57
|
-
errors.push({
|
|
61
|
+
errors.push({
|
|
62
|
+
path: "description",
|
|
63
|
+
message: "description must be at least 10 characters",
|
|
64
|
+
});
|
|
58
65
|
}
|
|
59
66
|
else if (manifest.description.length > 200) {
|
|
60
|
-
errors.push({
|
|
67
|
+
errors.push({
|
|
68
|
+
path: "description",
|
|
69
|
+
message: "description must be at most 200 characters",
|
|
70
|
+
});
|
|
61
71
|
}
|
|
62
72
|
// Validate asset directories actually exist when declared
|
|
63
73
|
for (const assetType of PLUGIN_ASSET_DIRS) {
|
|
@@ -65,26 +75,36 @@ export async function validatePlugin(pluginDir) {
|
|
|
65
75
|
if (!Array.isArray(declared) || declared.length === 0)
|
|
66
76
|
continue;
|
|
67
77
|
const assetDir = path.join(pluginDir, assetType);
|
|
68
|
-
if (!await fs.pathExists(assetDir)) {
|
|
69
|
-
errors.push({
|
|
78
|
+
if (!(await fs.pathExists(assetDir))) {
|
|
79
|
+
errors.push({
|
|
80
|
+
path: assetType,
|
|
81
|
+
message: `declared ${assetType}/ directory not found`,
|
|
82
|
+
});
|
|
70
83
|
continue;
|
|
71
84
|
}
|
|
72
85
|
// Check each declared asset exists
|
|
73
86
|
for (const entry of declared) {
|
|
74
87
|
const entryPath = path.join(assetDir, entry);
|
|
75
|
-
if (!await fs.pathExists(entryPath)) {
|
|
76
|
-
errors.push({
|
|
88
|
+
if (!(await fs.pathExists(entryPath))) {
|
|
89
|
+
errors.push({
|
|
90
|
+
path: `${assetType}/${entry}`,
|
|
91
|
+
message: `declared entry not found`,
|
|
92
|
+
});
|
|
77
93
|
}
|
|
78
94
|
}
|
|
79
95
|
}
|
|
80
96
|
// Validate tags
|
|
81
97
|
if (manifest.tags && !Array.isArray(manifest.tags)) {
|
|
82
|
-
errors.push({ path:
|
|
98
|
+
errors.push({ path: "tags", message: "tags must be an array" });
|
|
83
99
|
}
|
|
84
100
|
else if (manifest.tags && manifest.tags.length > 10) {
|
|
85
|
-
errors.push({ path:
|
|
101
|
+
errors.push({ path: "tags", message: "max 10 tags allowed" });
|
|
86
102
|
}
|
|
87
|
-
return {
|
|
103
|
+
return {
|
|
104
|
+
valid: errors.length === 0,
|
|
105
|
+
errors,
|
|
106
|
+
manifest: errors.length === 0 ? manifest : manifest,
|
|
107
|
+
};
|
|
88
108
|
}
|
|
89
109
|
// ── Installation ────────────────────────────────────────────────────────────
|
|
90
110
|
/**
|
|
@@ -96,23 +116,36 @@ export async function installPlugin(source, options = {}) {
|
|
|
96
116
|
// Normalize source to a git URL
|
|
97
117
|
const gitUrl = normalizeGitUrl(source);
|
|
98
118
|
if (!gitUrl) {
|
|
99
|
-
return {
|
|
119
|
+
return {
|
|
120
|
+
success: false,
|
|
121
|
+
error: `invalid source: ${source}. Use org/repo or a GitHub URL`,
|
|
122
|
+
};
|
|
100
123
|
}
|
|
101
124
|
// Clone to temp
|
|
102
|
-
const tmpDir = path.join(PLUGINS_DIR,
|
|
125
|
+
const tmpDir = path.join(PLUGINS_DIR, ".tmp", `install-${Date.now()}`);
|
|
103
126
|
try {
|
|
104
127
|
if (!dryRun) {
|
|
105
128
|
await fs.ensureDir(tmpDir);
|
|
106
|
-
await execFileAsync(
|
|
129
|
+
await execFileAsync("git", ["clone", "--depth", "1", gitUrl, tmpDir], {
|
|
107
130
|
timeout: 60_000,
|
|
108
131
|
});
|
|
109
132
|
}
|
|
110
133
|
// Validate
|
|
111
134
|
const validation = dryRun
|
|
112
|
-
? {
|
|
135
|
+
? {
|
|
136
|
+
valid: true,
|
|
137
|
+
errors: [],
|
|
138
|
+
manifest: {
|
|
139
|
+
name: source.split("/").pop() ?? "unknown",
|
|
140
|
+
version: "0.0.0",
|
|
141
|
+
description: "dry-run placeholder",
|
|
142
|
+
},
|
|
143
|
+
}
|
|
113
144
|
: await validatePlugin(tmpDir);
|
|
114
145
|
if (!validation.valid || !validation.manifest) {
|
|
115
|
-
const msgs = validation.errors
|
|
146
|
+
const msgs = validation.errors
|
|
147
|
+
.map((e) => ` ${e.path}: ${e.message}`)
|
|
148
|
+
.join("\n");
|
|
116
149
|
return { success: false, error: `validation failed:\n${msgs}` };
|
|
117
150
|
}
|
|
118
151
|
const pluginName = validation.manifest.name;
|
|
@@ -131,7 +164,7 @@ export async function installPlugin(source, options = {}) {
|
|
|
131
164
|
source,
|
|
132
165
|
manifest: validation.manifest,
|
|
133
166
|
};
|
|
134
|
-
await fs.writeJson(path.join(destDir,
|
|
167
|
+
await fs.writeJson(path.join(destDir, ".installed.json"), installedPlugin, { spaces: 2 });
|
|
135
168
|
// Generate Agent Skills spec manifest for cross-agent compatibility
|
|
136
169
|
await generateAgentSkillsManifest(destDir, source).catch(() => { });
|
|
137
170
|
}
|
|
@@ -143,7 +176,7 @@ export async function installPlugin(source, options = {}) {
|
|
|
143
176
|
}
|
|
144
177
|
finally {
|
|
145
178
|
// Clean up tmp if it still exists (error path)
|
|
146
|
-
if (!dryRun && await fs.pathExists(tmpDir)) {
|
|
179
|
+
if (!dryRun && (await fs.pathExists(tmpDir))) {
|
|
147
180
|
await fs.remove(tmpDir).catch(() => { });
|
|
148
181
|
}
|
|
149
182
|
}
|
|
@@ -153,7 +186,7 @@ export async function installPlugin(source, options = {}) {
|
|
|
153
186
|
*/
|
|
154
187
|
export async function removePlugin(name, options = {}) {
|
|
155
188
|
const pluginDir = path.join(PLUGINS_DIR, name);
|
|
156
|
-
if (!await fs.pathExists(pluginDir)) {
|
|
189
|
+
if (!(await fs.pathExists(pluginDir))) {
|
|
157
190
|
return { success: false, error: `plugin "${name}" is not installed` };
|
|
158
191
|
}
|
|
159
192
|
if (!options.dryRun) {
|
|
@@ -166,20 +199,22 @@ export async function removePlugin(name, options = {}) {
|
|
|
166
199
|
* List all installed plugins.
|
|
167
200
|
*/
|
|
168
201
|
export async function listInstalledPlugins() {
|
|
169
|
-
if (!await fs.pathExists(PLUGINS_DIR))
|
|
202
|
+
if (!(await fs.pathExists(PLUGINS_DIR)))
|
|
170
203
|
return [];
|
|
171
204
|
const entries = await fs.readdir(PLUGINS_DIR);
|
|
172
205
|
const plugins = [];
|
|
173
206
|
for (const entry of entries) {
|
|
174
|
-
if (entry.startsWith(
|
|
207
|
+
if (entry.startsWith("."))
|
|
175
208
|
continue;
|
|
176
|
-
const metaPath = path.join(PLUGINS_DIR, entry,
|
|
209
|
+
const metaPath = path.join(PLUGINS_DIR, entry, ".installed.json");
|
|
177
210
|
if (await fs.pathExists(metaPath)) {
|
|
178
211
|
try {
|
|
179
|
-
const meta = await fs.readJson(metaPath);
|
|
212
|
+
const meta = (await fs.readJson(metaPath));
|
|
180
213
|
plugins.push(meta);
|
|
181
214
|
}
|
|
182
|
-
catch {
|
|
215
|
+
catch {
|
|
216
|
+
/* skip corrupt entries */
|
|
217
|
+
}
|
|
183
218
|
}
|
|
184
219
|
}
|
|
185
220
|
return plugins;
|
|
@@ -193,13 +228,13 @@ export async function searchRegistry(query) {
|
|
|
193
228
|
if (!response.ok) {
|
|
194
229
|
return [];
|
|
195
230
|
}
|
|
196
|
-
const registry = await response.json();
|
|
231
|
+
const registry = (await response.json());
|
|
197
232
|
let plugins = registry.plugins ?? [];
|
|
198
233
|
if (query) {
|
|
199
234
|
const q = query.toLowerCase();
|
|
200
|
-
plugins = plugins.filter(p => p.id.toLowerCase().includes(q) ||
|
|
235
|
+
plugins = plugins.filter((p) => p.id.toLowerCase().includes(q) ||
|
|
201
236
|
p.description.toLowerCase().includes(q) ||
|
|
202
|
-
p.tags.some(t => t.toLowerCase().includes(q)));
|
|
237
|
+
p.tags.some((t) => t.toLowerCase().includes(q)));
|
|
203
238
|
}
|
|
204
239
|
return plugins;
|
|
205
240
|
}
|
|
@@ -213,47 +248,59 @@ export async function searchRegistry(query) {
|
|
|
213
248
|
* Returns an array of plugin names (sorted alphabetically).
|
|
214
249
|
*/
|
|
215
250
|
export async function detectProjectPlugins(projectDir) {
|
|
216
|
-
const
|
|
217
|
-
|
|
251
|
+
const full = await detectProjectPluginsFull(projectDir);
|
|
252
|
+
return full.map((p) => p.name).sort();
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Detect installed plugins with full metadata (including manifest).
|
|
256
|
+
* Used by auto-wiring to read plugin capabilities.
|
|
257
|
+
*/
|
|
258
|
+
export async function detectProjectPluginsFull(projectDir) {
|
|
259
|
+
const pluginsDir = path.join(projectDir, ".javi-forge", "plugins");
|
|
260
|
+
if (!(await fs.pathExists(pluginsDir)))
|
|
218
261
|
return [];
|
|
219
262
|
const entries = await fs.readdir(pluginsDir);
|
|
220
263
|
const plugins = [];
|
|
221
264
|
for (const entry of entries) {
|
|
222
|
-
if (entry.startsWith(
|
|
265
|
+
if (entry.startsWith("."))
|
|
223
266
|
continue;
|
|
224
|
-
const metaPath = path.join(pluginsDir, entry,
|
|
267
|
+
const metaPath = path.join(pluginsDir, entry, ".installed.json");
|
|
225
268
|
if (await fs.pathExists(metaPath)) {
|
|
226
269
|
try {
|
|
227
|
-
const meta = await fs.readJson(metaPath);
|
|
270
|
+
const meta = (await fs.readJson(metaPath));
|
|
228
271
|
if (meta.name) {
|
|
229
|
-
plugins.push(meta
|
|
272
|
+
plugins.push(meta);
|
|
230
273
|
}
|
|
231
274
|
}
|
|
232
|
-
catch {
|
|
275
|
+
catch {
|
|
276
|
+
/* skip corrupt entries */
|
|
277
|
+
}
|
|
233
278
|
}
|
|
234
279
|
}
|
|
235
|
-
return plugins.sort();
|
|
280
|
+
return plugins.sort((a, b) => a.name.localeCompare(b.name));
|
|
236
281
|
}
|
|
237
282
|
/**
|
|
238
|
-
* Sync detected plugins into the project manifest
|
|
239
|
-
*
|
|
283
|
+
* Sync detected plugins into the project manifest and auto-wire
|
|
284
|
+
* their capabilities into CLAUDE.md and .claude/settings.json.
|
|
285
|
+
* Returns a report of added, removed, unchanged, wired, and unwired plugins.
|
|
240
286
|
*/
|
|
241
287
|
export async function syncPlugins(projectDir, options = {}) {
|
|
242
288
|
const { dryRun = false } = options;
|
|
243
|
-
const
|
|
244
|
-
const
|
|
289
|
+
const detectedFull = await detectProjectPluginsFull(projectDir);
|
|
290
|
+
const detected = detectedFull.map((p) => p.name).sort();
|
|
291
|
+
const manifestPath = path.join(projectDir, ".javi-forge", "manifest.json");
|
|
245
292
|
let manifest;
|
|
246
293
|
if (await fs.pathExists(manifestPath)) {
|
|
247
|
-
manifest = await fs.readJson(manifestPath);
|
|
294
|
+
manifest = (await fs.readJson(manifestPath));
|
|
248
295
|
}
|
|
249
296
|
else {
|
|
250
297
|
// No manifest yet — treat current plugins as empty
|
|
251
298
|
manifest = {
|
|
252
|
-
version:
|
|
299
|
+
version: "0.1.0",
|
|
253
300
|
projectName: path.basename(projectDir),
|
|
254
|
-
stack:
|
|
255
|
-
ciProvider:
|
|
256
|
-
memory:
|
|
301
|
+
stack: "node",
|
|
302
|
+
ciProvider: "github",
|
|
303
|
+
memory: "none",
|
|
257
304
|
createdAt: new Date().toISOString(),
|
|
258
305
|
updatedAt: new Date().toISOString(),
|
|
259
306
|
modules: [],
|
|
@@ -261,16 +308,26 @@ export async function syncPlugins(projectDir, options = {}) {
|
|
|
261
308
|
}
|
|
262
309
|
const previous = new Set(manifest.plugins ?? []);
|
|
263
310
|
const current = new Set(detected);
|
|
264
|
-
const added = detected.filter(p => !previous.has(p));
|
|
265
|
-
const removed = [...previous].filter(p => !current.has(p));
|
|
266
|
-
const unchanged = detected.filter(p => previous.has(p));
|
|
311
|
+
const added = detected.filter((p) => !previous.has(p));
|
|
312
|
+
const removed = [...previous].filter((p) => !current.has(p));
|
|
313
|
+
const unchanged = detected.filter((p) => previous.has(p));
|
|
267
314
|
if (!dryRun && (added.length > 0 || removed.length > 0)) {
|
|
268
315
|
manifest.plugins = detected;
|
|
269
316
|
manifest.updatedAt = new Date().toISOString();
|
|
270
317
|
await fs.ensureDir(path.dirname(manifestPath));
|
|
271
318
|
await fs.writeJson(manifestPath, manifest, { spaces: 2 });
|
|
272
319
|
}
|
|
273
|
-
|
|
320
|
+
// ── Auto-wire plugin capabilities ──────────────────────────────────
|
|
321
|
+
const wireResult = await autoWirePlugins(projectDir, detectedFull, {
|
|
322
|
+
dryRun,
|
|
323
|
+
});
|
|
324
|
+
return {
|
|
325
|
+
added,
|
|
326
|
+
removed,
|
|
327
|
+
unchanged,
|
|
328
|
+
wired: wireResult.wired,
|
|
329
|
+
unwired: wireResult.unwired,
|
|
330
|
+
};
|
|
274
331
|
}
|
|
275
332
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
276
333
|
/**
|
|
@@ -279,15 +336,15 @@ export async function syncPlugins(projectDir, options = {}) {
|
|
|
279
336
|
*/
|
|
280
337
|
export function normalizeGitUrl(source) {
|
|
281
338
|
// Already a full URL
|
|
282
|
-
if (source.startsWith(
|
|
283
|
-
return source.endsWith(
|
|
339
|
+
if (source.startsWith("https://github.com/")) {
|
|
340
|
+
return source.endsWith(".git") ? source : `${source}.git`;
|
|
284
341
|
}
|
|
285
342
|
// github.com/org/repo
|
|
286
|
-
if (source.startsWith(
|
|
343
|
+
if (source.startsWith("github.com/")) {
|
|
287
344
|
return `https://${source}.git`;
|
|
288
345
|
}
|
|
289
346
|
// org/repo shorthand
|
|
290
|
-
const parts = source.split(
|
|
347
|
+
const parts = source.split("/");
|
|
291
348
|
if (parts.length === 2 && parts[0] && parts[1]) {
|
|
292
349
|
return `https://github.com/${parts[0]}/${parts[1]}.git`;
|
|
293
350
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret scanner — detects leaked credentials in source files using
|
|
3
|
+
* a curated regex bundle. Designed for pre-commit hooks and CI gates.
|
|
4
|
+
*/
|
|
5
|
+
export interface SecretPattern {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
pattern: RegExp;
|
|
9
|
+
severity: "critical" | "high" | "medium";
|
|
10
|
+
}
|
|
11
|
+
export interface SecretFinding {
|
|
12
|
+
patternId: string;
|
|
13
|
+
label: string;
|
|
14
|
+
severity: SecretPattern["severity"];
|
|
15
|
+
file: string;
|
|
16
|
+
line: number;
|
|
17
|
+
match: string;
|
|
18
|
+
}
|
|
19
|
+
export interface ScanResult {
|
|
20
|
+
findings: SecretFinding[];
|
|
21
|
+
filesScanned: number;
|
|
22
|
+
patternsUsed: number;
|
|
23
|
+
}
|
|
24
|
+
export declare const SECRET_PATTERNS: SecretPattern[];
|
|
25
|
+
export declare function maskSecret(value: string): string;
|
|
26
|
+
export declare function shouldScanFile(filePath: string): boolean;
|
|
27
|
+
export declare function shouldSkipDir(dirName: string): boolean;
|
|
28
|
+
export declare function scanContent(content: string, filePath: string, patterns?: SecretPattern[]): SecretFinding[];
|
|
29
|
+
export declare function scanDirectory(dir: string, patterns?: SecretPattern[]): Promise<ScanResult>;
|
|
30
|
+
//# sourceMappingURL=secret-scanner.d.ts.map
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret scanner — detects leaked credentials in source files using
|
|
3
|
+
* a curated regex bundle. Designed for pre-commit hooks and CI gates.
|
|
4
|
+
*/
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import fs from "fs-extra";
|
|
7
|
+
// ── Regex Bundle ──
|
|
8
|
+
export const SECRET_PATTERNS = [
|
|
9
|
+
// AWS
|
|
10
|
+
{
|
|
11
|
+
id: "aws-access-key",
|
|
12
|
+
label: "AWS Access Key ID",
|
|
13
|
+
pattern: /\bAKIA[0-9A-Z]{16}\b/,
|
|
14
|
+
severity: "critical",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: "aws-secret-key",
|
|
18
|
+
label: "AWS Secret Access Key",
|
|
19
|
+
pattern: /(?<![A-Za-z0-9/+=])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9/+=])/,
|
|
20
|
+
severity: "critical",
|
|
21
|
+
},
|
|
22
|
+
// Google
|
|
23
|
+
{
|
|
24
|
+
id: "gcp-api-key",
|
|
25
|
+
label: "Google API Key",
|
|
26
|
+
pattern: /\bAIza[0-9A-Za-z_-]{35}\b/,
|
|
27
|
+
severity: "high",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "gcp-oauth",
|
|
31
|
+
label: "Google OAuth Client Secret",
|
|
32
|
+
pattern: /\bGOCSPX-[A-Za-z0-9_-]{28}\b/,
|
|
33
|
+
severity: "critical",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "gcp-sa-key",
|
|
37
|
+
label: "GCP Service Account Key",
|
|
38
|
+
pattern: /"type":\s*"service_account"/,
|
|
39
|
+
severity: "critical",
|
|
40
|
+
},
|
|
41
|
+
// GitHub
|
|
42
|
+
{
|
|
43
|
+
id: "github-pat",
|
|
44
|
+
label: "GitHub Personal Access Token",
|
|
45
|
+
pattern: /\bghp_[A-Za-z0-9]{36}\b/,
|
|
46
|
+
severity: "critical",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "github-oauth",
|
|
50
|
+
label: "GitHub OAuth Token",
|
|
51
|
+
pattern: /\bgho_[A-Za-z0-9]{36}\b/,
|
|
52
|
+
severity: "critical",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "github-app-token",
|
|
56
|
+
label: "GitHub App Token",
|
|
57
|
+
pattern: /\bghu_[A-Za-z0-9]{36}\b/,
|
|
58
|
+
severity: "high",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "github-fine-pat",
|
|
62
|
+
label: "GitHub Fine-Grained PAT",
|
|
63
|
+
pattern: /\bgithub_pat_[A-Za-z0-9]{22}_[A-Za-z0-9]{59}\b/,
|
|
64
|
+
severity: "critical",
|
|
65
|
+
},
|
|
66
|
+
// Stripe
|
|
67
|
+
{
|
|
68
|
+
id: "stripe-secret",
|
|
69
|
+
label: "Stripe Secret Key",
|
|
70
|
+
pattern: /\bsk_live_[A-Za-z0-9]{24,}\b/,
|
|
71
|
+
severity: "critical",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "stripe-restricted",
|
|
75
|
+
label: "Stripe Restricted Key",
|
|
76
|
+
pattern: /\brk_live_[A-Za-z0-9]{24,}\b/,
|
|
77
|
+
severity: "critical",
|
|
78
|
+
},
|
|
79
|
+
// Slack
|
|
80
|
+
{
|
|
81
|
+
id: "slack-bot-token",
|
|
82
|
+
label: "Slack Bot Token",
|
|
83
|
+
pattern: /\bxoxb-[0-9]{10,}-[0-9]{10,}-[A-Za-z0-9]{24,}\b/,
|
|
84
|
+
severity: "high",
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "slack-webhook",
|
|
88
|
+
label: "Slack Webhook URL",
|
|
89
|
+
pattern: /https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]+/,
|
|
90
|
+
severity: "high",
|
|
91
|
+
},
|
|
92
|
+
// Generic
|
|
93
|
+
{
|
|
94
|
+
id: "private-key",
|
|
95
|
+
label: "Private Key",
|
|
96
|
+
pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/,
|
|
97
|
+
severity: "critical",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: "jwt-token",
|
|
101
|
+
label: "JSON Web Token",
|
|
102
|
+
pattern: /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\b/,
|
|
103
|
+
severity: "high",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: "bearer-token",
|
|
107
|
+
label: "Bearer Token",
|
|
108
|
+
pattern: /[Bb]earer\s+[A-Za-z0-9_-]{20,}/,
|
|
109
|
+
severity: "medium",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "password-assign",
|
|
113
|
+
label: "Password Assignment",
|
|
114
|
+
pattern: /(?:password|passwd|pwd|secret)\s*[:=]\s*['"][^'"]{8,}['"]/,
|
|
115
|
+
severity: "high",
|
|
116
|
+
},
|
|
117
|
+
// Cloud providers
|
|
118
|
+
{
|
|
119
|
+
id: "azure-connection",
|
|
120
|
+
label: "Azure Connection String",
|
|
121
|
+
pattern: /DefaultEndpointsProtocol=https;AccountName=[^;]+;AccountKey=[A-Za-z0-9+/=]+/,
|
|
122
|
+
severity: "critical",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: "sendgrid-key",
|
|
126
|
+
label: "SendGrid API Key",
|
|
127
|
+
pattern: /\bSG\.[A-Za-z0-9_-]{22}\.[A-Za-z0-9_-]{43}\b/,
|
|
128
|
+
severity: "high",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: "twilio-key",
|
|
132
|
+
label: "Twilio API Key",
|
|
133
|
+
pattern: /\bSK[0-9a-fA-F]{32}\b/,
|
|
134
|
+
severity: "high",
|
|
135
|
+
},
|
|
136
|
+
// Database
|
|
137
|
+
{
|
|
138
|
+
id: "db-connection",
|
|
139
|
+
label: "Database Connection String",
|
|
140
|
+
pattern: /(?:mongodb|postgres|mysql|redis):\/\/[^:\s]+:[^@\s]+@[^\s]+/,
|
|
141
|
+
severity: "critical",
|
|
142
|
+
},
|
|
143
|
+
// Anthropic / OpenAI
|
|
144
|
+
{
|
|
145
|
+
id: "anthropic-key",
|
|
146
|
+
label: "Anthropic API Key",
|
|
147
|
+
pattern: /\bsk-ant-api03-[A-Za-z0-9_-]{93}\b/,
|
|
148
|
+
severity: "critical",
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: "openai-key",
|
|
152
|
+
label: "OpenAI API Key",
|
|
153
|
+
pattern: /\bsk-[A-Za-z0-9]{48,}\b/,
|
|
154
|
+
severity: "critical",
|
|
155
|
+
},
|
|
156
|
+
];
|
|
157
|
+
// ── Masking ──
|
|
158
|
+
export function maskSecret(value) {
|
|
159
|
+
if (value.length <= 8)
|
|
160
|
+
return "****";
|
|
161
|
+
return `${value.slice(0, 4)}...${value.slice(-2)}`;
|
|
162
|
+
}
|
|
163
|
+
// ── File filtering ──
|
|
164
|
+
const BINARY_EXTENSIONS = new Set([
|
|
165
|
+
".png",
|
|
166
|
+
".jpg",
|
|
167
|
+
".jpeg",
|
|
168
|
+
".gif",
|
|
169
|
+
".ico",
|
|
170
|
+
".svg",
|
|
171
|
+
".woff",
|
|
172
|
+
".woff2",
|
|
173
|
+
".ttf",
|
|
174
|
+
".eot",
|
|
175
|
+
".mp3",
|
|
176
|
+
".mp4",
|
|
177
|
+
".zip",
|
|
178
|
+
".gz",
|
|
179
|
+
".tar",
|
|
180
|
+
".pdf",
|
|
181
|
+
".exe",
|
|
182
|
+
".dll",
|
|
183
|
+
".so",
|
|
184
|
+
".dylib",
|
|
185
|
+
".o",
|
|
186
|
+
".a",
|
|
187
|
+
]);
|
|
188
|
+
const SKIP_DIRS = new Set([
|
|
189
|
+
"node_modules",
|
|
190
|
+
".git",
|
|
191
|
+
"dist",
|
|
192
|
+
"build",
|
|
193
|
+
".next",
|
|
194
|
+
"__pycache__",
|
|
195
|
+
"target",
|
|
196
|
+
"vendor",
|
|
197
|
+
".venv",
|
|
198
|
+
"venv",
|
|
199
|
+
".tox",
|
|
200
|
+
".mypy_cache",
|
|
201
|
+
"coverage",
|
|
202
|
+
".nyc_output",
|
|
203
|
+
".cache",
|
|
204
|
+
]);
|
|
205
|
+
export function shouldScanFile(filePath) {
|
|
206
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
207
|
+
if (BINARY_EXTENSIONS.has(ext))
|
|
208
|
+
return false;
|
|
209
|
+
if (filePath.includes("lock") && (ext === ".json" || ext === ".yaml"))
|
|
210
|
+
return false;
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
export function shouldSkipDir(dirName) {
|
|
214
|
+
return SKIP_DIRS.has(dirName);
|
|
215
|
+
}
|
|
216
|
+
// ── Scanner ──
|
|
217
|
+
export function scanContent(content, filePath, patterns = SECRET_PATTERNS) {
|
|
218
|
+
const findings = [];
|
|
219
|
+
const lines = content.split("\n");
|
|
220
|
+
for (let i = 0; i < lines.length; i++) {
|
|
221
|
+
const line = lines[i];
|
|
222
|
+
if (line === undefined)
|
|
223
|
+
continue;
|
|
224
|
+
for (const pat of patterns) {
|
|
225
|
+
const match = pat.pattern.exec(line);
|
|
226
|
+
if (match) {
|
|
227
|
+
findings.push({
|
|
228
|
+
patternId: pat.id,
|
|
229
|
+
label: pat.label,
|
|
230
|
+
severity: pat.severity,
|
|
231
|
+
file: filePath,
|
|
232
|
+
line: i + 1,
|
|
233
|
+
match: maskSecret(match[0]),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return findings;
|
|
239
|
+
}
|
|
240
|
+
async function collectFiles(dir) {
|
|
241
|
+
const results = [];
|
|
242
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
243
|
+
for (const entry of entries) {
|
|
244
|
+
if (entry.isDirectory()) {
|
|
245
|
+
if (!shouldSkipDir(entry.name)) {
|
|
246
|
+
results.push(...(await collectFiles(path.join(dir, entry.name))));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
else if (entry.isFile() && shouldScanFile(entry.name)) {
|
|
250
|
+
results.push(path.join(dir, entry.name));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return results;
|
|
254
|
+
}
|
|
255
|
+
export async function scanDirectory(dir, patterns = SECRET_PATTERNS) {
|
|
256
|
+
const files = await collectFiles(dir);
|
|
257
|
+
const allFindings = [];
|
|
258
|
+
for (const file of files) {
|
|
259
|
+
try {
|
|
260
|
+
const content = await fs.readFile(file, "utf-8");
|
|
261
|
+
const rel = path.relative(dir, file);
|
|
262
|
+
allFindings.push(...scanContent(content, rel, patterns));
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
// Skip unreadable files
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
findings: allFindings,
|
|
270
|
+
filesScanned: files.length,
|
|
271
|
+
patternsUsed: patterns.length,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
//# sourceMappingURL=secret-scanner.js.map
|