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
|
@@ -1,32 +1,41 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { detectCIStack } from './ci.js';
|
|
6
|
-
const execFileAsync = promisify(execFile);
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import { execFileAsync } from "../lib/exec.js";
|
|
4
|
+
import { detectCIStack } from "./ci.js";
|
|
7
5
|
// =============================================================================
|
|
8
6
|
// Constants
|
|
9
7
|
// =============================================================================
|
|
10
|
-
const BASELINE_DIR =
|
|
11
|
-
const BASELINE_FILE =
|
|
12
|
-
const BASELINE_VERSION =
|
|
8
|
+
const BASELINE_DIR = ".javi-forge";
|
|
9
|
+
const BASELINE_FILE = "security-baseline.json";
|
|
10
|
+
const BASELINE_VERSION = "2.0.0";
|
|
11
|
+
const SEVERITY_ORDER = {
|
|
12
|
+
critical: 5,
|
|
13
|
+
high: 4,
|
|
14
|
+
moderate: 3,
|
|
15
|
+
low: 2,
|
|
16
|
+
info: 1,
|
|
17
|
+
};
|
|
18
|
+
const DEFAULT_STALE_DAYS = 30;
|
|
13
19
|
// =============================================================================
|
|
14
20
|
// Audit command resolution
|
|
15
21
|
// =============================================================================
|
|
16
22
|
export function getAuditCommand(stack, buildTool) {
|
|
17
23
|
switch (stack) {
|
|
18
|
-
case
|
|
24
|
+
case "node":
|
|
19
25
|
switch (buildTool) {
|
|
20
|
-
case
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
case "pnpm":
|
|
27
|
+
return { cmd: "pnpm", args: ["audit", "--json"] };
|
|
28
|
+
case "yarn":
|
|
29
|
+
return { cmd: "yarn", args: ["npm", "audit", "--json"] };
|
|
30
|
+
default:
|
|
31
|
+
return { cmd: "npm", args: ["audit", "--json"] };
|
|
23
32
|
}
|
|
24
|
-
case
|
|
25
|
-
return { cmd:
|
|
26
|
-
case
|
|
27
|
-
return { cmd:
|
|
28
|
-
case
|
|
29
|
-
return { cmd:
|
|
33
|
+
case "python":
|
|
34
|
+
return { cmd: "pip-audit", args: ["--format=json", "--output=-"] };
|
|
35
|
+
case "go":
|
|
36
|
+
return { cmd: "govulncheck", args: ["-json", "./..."] };
|
|
37
|
+
case "rust":
|
|
38
|
+
return { cmd: "cargo", args: ["audit", "--json"] };
|
|
30
39
|
default:
|
|
31
40
|
return null;
|
|
32
41
|
}
|
|
@@ -46,7 +55,7 @@ export function parseNpmAudit(raw) {
|
|
|
46
55
|
for (const [pkgName, info] of Object.entries(vulns)) {
|
|
47
56
|
const v = info;
|
|
48
57
|
// via can contain objects (direct vulns) or strings (transitive refs)
|
|
49
|
-
const directVias = (v.via ?? []).filter((x) => typeof x ===
|
|
58
|
+
const directVias = (v.via ?? []).filter((x) => typeof x === "object");
|
|
50
59
|
if (directVias.length === 0) {
|
|
51
60
|
findings.push({
|
|
52
61
|
id: `npm-${pkgName}`,
|
|
@@ -80,10 +89,10 @@ export function parsePipAudit(raw) {
|
|
|
80
89
|
// pip-audit JSON: array of { name, version, vulns: [{ id, fix_versions, description }] }
|
|
81
90
|
const deps = Array.isArray(data) ? data : (data.dependencies ?? []);
|
|
82
91
|
for (const dep of deps) {
|
|
83
|
-
for (const vuln of
|
|
92
|
+
for (const vuln of dep.vulns ?? []) {
|
|
84
93
|
findings.push({
|
|
85
94
|
id: vuln.id ?? `pip-${dep.name}`,
|
|
86
|
-
severity: normalizeSeverity(vuln.fix_versions?.length ?
|
|
95
|
+
severity: normalizeSeverity(vuln.fix_versions?.length ? "high" : "moderate"),
|
|
87
96
|
package: dep.name,
|
|
88
97
|
title: vuln.description ?? `Vulnerability in ${dep.name}`,
|
|
89
98
|
});
|
|
@@ -103,10 +112,10 @@ export function parseCargoAudit(raw) {
|
|
|
103
112
|
for (const v of vulns) {
|
|
104
113
|
const advisory = v.advisory ?? {};
|
|
105
114
|
findings.push({
|
|
106
|
-
id: advisory.id ?? `cargo-${v.package?.name ??
|
|
115
|
+
id: advisory.id ?? `cargo-${v.package?.name ?? "unknown"}`,
|
|
107
116
|
severity: normalizeSeverity(advisory.cvss?.severity),
|
|
108
|
-
package: v.package?.name ??
|
|
109
|
-
title: advisory.title ?? `Vulnerability in ${v.package?.name ??
|
|
117
|
+
package: v.package?.name ?? "unknown",
|
|
118
|
+
title: advisory.title ?? `Vulnerability in ${v.package?.name ?? "unknown"}`,
|
|
110
119
|
url: advisory.url,
|
|
111
120
|
});
|
|
112
121
|
}
|
|
@@ -120,16 +129,16 @@ export function parseGovulncheck(raw) {
|
|
|
120
129
|
const findings = [];
|
|
121
130
|
try {
|
|
122
131
|
// govulncheck JSON outputs one JSON object per line (NDJSON)
|
|
123
|
-
const lines = raw.split(
|
|
132
|
+
const lines = raw.split("\n").filter((l) => l.trim());
|
|
124
133
|
for (const line of lines) {
|
|
125
134
|
try {
|
|
126
135
|
const entry = JSON.parse(line);
|
|
127
136
|
if (entry.osv) {
|
|
128
137
|
findings.push({
|
|
129
|
-
id: entry.osv.id ??
|
|
138
|
+
id: entry.osv.id ?? "unknown",
|
|
130
139
|
severity: normalizeSeverity(entry.osv.database_specific?.severity),
|
|
131
|
-
package: entry.osv.affected?.[0]?.package?.name ??
|
|
132
|
-
title: entry.osv.summary ?? entry.osv.id ??
|
|
140
|
+
package: entry.osv.affected?.[0]?.package?.name ?? "unknown",
|
|
141
|
+
title: entry.osv.summary ?? entry.osv.id ?? "Go vulnerability",
|
|
133
142
|
url: entry.osv.references?.[0]?.url,
|
|
134
143
|
});
|
|
135
144
|
}
|
|
@@ -146,39 +155,120 @@ export function parseGovulncheck(raw) {
|
|
|
146
155
|
}
|
|
147
156
|
function normalizeSeverity(raw) {
|
|
148
157
|
if (!raw)
|
|
149
|
-
return
|
|
158
|
+
return "moderate";
|
|
150
159
|
const lower = raw.toLowerCase();
|
|
151
|
-
if (lower ===
|
|
152
|
-
return
|
|
153
|
-
if (lower ===
|
|
154
|
-
return
|
|
155
|
-
if (lower ===
|
|
156
|
-
return
|
|
157
|
-
if (lower ===
|
|
158
|
-
return
|
|
159
|
-
if (lower ===
|
|
160
|
-
return
|
|
161
|
-
return
|
|
160
|
+
if (lower === "critical")
|
|
161
|
+
return "critical";
|
|
162
|
+
if (lower === "high")
|
|
163
|
+
return "high";
|
|
164
|
+
if (lower === "moderate" || lower === "medium")
|
|
165
|
+
return "moderate";
|
|
166
|
+
if (lower === "low")
|
|
167
|
+
return "low";
|
|
168
|
+
if (lower === "info" || lower === "none")
|
|
169
|
+
return "info";
|
|
170
|
+
return "moderate";
|
|
162
171
|
}
|
|
163
172
|
export function parseAuditOutput(stack, raw) {
|
|
164
173
|
switch (stack) {
|
|
165
|
-
case
|
|
166
|
-
|
|
167
|
-
case
|
|
168
|
-
|
|
169
|
-
|
|
174
|
+
case "node":
|
|
175
|
+
return parseNpmAudit(raw);
|
|
176
|
+
case "python":
|
|
177
|
+
return parsePipAudit(raw);
|
|
178
|
+
case "rust":
|
|
179
|
+
return parseCargoAudit(raw);
|
|
180
|
+
case "go":
|
|
181
|
+
return parseGovulncheck(raw);
|
|
182
|
+
default:
|
|
183
|
+
return [];
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// =============================================================================
|
|
187
|
+
// Severity helpers
|
|
188
|
+
// =============================================================================
|
|
189
|
+
export function severityAtOrAbove(severity, threshold) {
|
|
190
|
+
return SEVERITY_ORDER[severity] >= SEVERITY_ORDER[threshold];
|
|
191
|
+
}
|
|
192
|
+
export function filterBySeverity(findings, minSeverity) {
|
|
193
|
+
return findings.filter((f) => severityAtOrAbove(f.severity, minSeverity));
|
|
194
|
+
}
|
|
195
|
+
// =============================================================================
|
|
196
|
+
// Allowlist filtering
|
|
197
|
+
// =============================================================================
|
|
198
|
+
export function filterAllowlisted(findings, allowlist) {
|
|
199
|
+
if (allowlist.length === 0)
|
|
200
|
+
return findings;
|
|
201
|
+
const allowSet = new Set(allowlist);
|
|
202
|
+
return findings.filter((f) => !allowSet.has(makeFindingKey(f)));
|
|
203
|
+
}
|
|
204
|
+
// =============================================================================
|
|
205
|
+
// Staleness detection
|
|
206
|
+
// =============================================================================
|
|
207
|
+
export function checkStaleness(baseline, staleDays) {
|
|
208
|
+
const refDate = baseline.updatedAt ?? baseline.createdAt;
|
|
209
|
+
const ageMs = Date.now() - new Date(refDate).getTime();
|
|
210
|
+
const ageDays = Math.floor(ageMs / (1000 * 60 * 60 * 24));
|
|
211
|
+
if (ageDays > staleDays) {
|
|
212
|
+
return `Baseline is ${ageDays} days old (threshold: ${staleDays}). Consider running \`javi-forge security update\`.`;
|
|
213
|
+
}
|
|
214
|
+
return undefined;
|
|
215
|
+
}
|
|
216
|
+
export function baselineAgeDays(baseline) {
|
|
217
|
+
const refDate = baseline.updatedAt ?? baseline.createdAt;
|
|
218
|
+
const ageMs = Date.now() - new Date(refDate).getTime();
|
|
219
|
+
return Math.floor(ageMs / (1000 * 60 * 60 * 24));
|
|
220
|
+
}
|
|
221
|
+
// =============================================================================
|
|
222
|
+
// Summary computation
|
|
223
|
+
// =============================================================================
|
|
224
|
+
export function computeSummary(current, regressions, resolved, filteredRegressions, baseline) {
|
|
225
|
+
const bySeverity = {
|
|
226
|
+
critical: 0,
|
|
227
|
+
high: 0,
|
|
228
|
+
moderate: 0,
|
|
229
|
+
low: 0,
|
|
230
|
+
info: 0,
|
|
231
|
+
};
|
|
232
|
+
for (const f of current) {
|
|
233
|
+
bySeverity[f.severity]++;
|
|
170
234
|
}
|
|
235
|
+
return {
|
|
236
|
+
total: current.length,
|
|
237
|
+
bySeverity,
|
|
238
|
+
regressionCount: regressions.length,
|
|
239
|
+
resolvedCount: resolved.length,
|
|
240
|
+
filteredCount: filteredRegressions.length,
|
|
241
|
+
baselineAge: baselineAgeDays(baseline),
|
|
242
|
+
};
|
|
171
243
|
}
|
|
172
244
|
// =============================================================================
|
|
173
245
|
// Regression detection
|
|
174
246
|
// =============================================================================
|
|
175
|
-
export function detectRegressions(baseline, current) {
|
|
247
|
+
export function detectRegressions(baseline, current, options = {}) {
|
|
176
248
|
const baselineKeySet = new Set(baseline.findingKeys);
|
|
177
249
|
const currentKeys = current.map(makeFindingKey);
|
|
178
250
|
const currentKeySet = new Set(currentKeys);
|
|
179
|
-
|
|
180
|
-
const resolved = baseline.findings.filter(f => !currentKeySet.has(makeFindingKey(f)));
|
|
181
|
-
|
|
251
|
+
let regressions = current.filter((f) => !baselineKeySet.has(makeFindingKey(f)));
|
|
252
|
+
const resolved = baseline.findings.filter((f) => !currentKeySet.has(makeFindingKey(f)));
|
|
253
|
+
// Apply allowlist filtering
|
|
254
|
+
const allowlist = baseline.allowlist ?? [];
|
|
255
|
+
regressions = filterAllowlisted(regressions, allowlist);
|
|
256
|
+
// Apply severity threshold
|
|
257
|
+
const minSeverity = options.minSeverity ?? "low";
|
|
258
|
+
const filteredRegressions = filterBySeverity(regressions, minSeverity);
|
|
259
|
+
// Check staleness
|
|
260
|
+
const staleDays = options.staleDays ?? DEFAULT_STALE_DAYS;
|
|
261
|
+
const staleWarning = checkStaleness(baseline, staleDays);
|
|
262
|
+
const summary = computeSummary(current, regressions, resolved, filteredRegressions, baseline);
|
|
263
|
+
return {
|
|
264
|
+
baseline,
|
|
265
|
+
current,
|
|
266
|
+
regressions,
|
|
267
|
+
resolved,
|
|
268
|
+
filteredRegressions,
|
|
269
|
+
staleWarning,
|
|
270
|
+
summary,
|
|
271
|
+
};
|
|
182
272
|
}
|
|
183
273
|
// =============================================================================
|
|
184
274
|
// Baseline file I/O
|
|
@@ -188,7 +278,7 @@ function baselinePath(projectDir) {
|
|
|
188
278
|
}
|
|
189
279
|
export async function readBaseline(projectDir) {
|
|
190
280
|
const bp = baselinePath(projectDir);
|
|
191
|
-
if (!await fs.pathExists(bp))
|
|
281
|
+
if (!(await fs.pathExists(bp)))
|
|
192
282
|
return null;
|
|
193
283
|
try {
|
|
194
284
|
return await fs.readJson(bp);
|
|
@@ -200,6 +290,27 @@ export async function readBaseline(projectDir) {
|
|
|
200
290
|
export async function writeBaseline(projectDir, baseline) {
|
|
201
291
|
const bp = baselinePath(projectDir);
|
|
202
292
|
await fs.ensureDir(path.dirname(bp));
|
|
293
|
+
// Refuse to follow symlinks: if .javi-forge/security-baseline.json has been
|
|
294
|
+
// replaced with a symlink (to /etc/passwd, /home/user/.ssh/authorized_keys,
|
|
295
|
+
// or any other sensitive file owned by the user) the default fs.writeJson
|
|
296
|
+
// would clobber the target. lstat reports the link, not what it points to.
|
|
297
|
+
let isSymlink = false;
|
|
298
|
+
try {
|
|
299
|
+
const stat = await fs.lstat(bp);
|
|
300
|
+
isSymlink = stat.isSymbolicLink();
|
|
301
|
+
}
|
|
302
|
+
catch (e) {
|
|
303
|
+
const code = e && typeof e === "object" && "code" in e
|
|
304
|
+
? e.code
|
|
305
|
+
: "";
|
|
306
|
+
if (code !== "ENOENT") {
|
|
307
|
+
throw e;
|
|
308
|
+
}
|
|
309
|
+
// ENOENT is expected on first run; continue.
|
|
310
|
+
}
|
|
311
|
+
if (isSymlink) {
|
|
312
|
+
throw new Error(`Refusing to write baseline through a symlink: ${bp}. Remove the symlink first.`);
|
|
313
|
+
}
|
|
203
314
|
await fs.writeJson(bp, baseline, { spaces: 2 });
|
|
204
315
|
}
|
|
205
316
|
// =============================================================================
|
|
@@ -217,7 +328,7 @@ async function runAuditTool(projectDir, auditCmd) {
|
|
|
217
328
|
catch (err) {
|
|
218
329
|
// npm audit exits non-zero when vulns are found — that's expected
|
|
219
330
|
// We still want the stdout (JSON output)
|
|
220
|
-
if (err && typeof err ===
|
|
331
|
+
if (err && typeof err === "object" && "stdout" in err) {
|
|
221
332
|
const stdout = err.stdout;
|
|
222
333
|
if (stdout && stdout.trim().length > 0)
|
|
223
334
|
return stdout;
|
|
@@ -231,75 +342,123 @@ async function runAuditTool(projectDir, auditCmd) {
|
|
|
231
342
|
function report(onStep, id, label, status, detail) {
|
|
232
343
|
onStep({ id, label, status, detail });
|
|
233
344
|
}
|
|
234
|
-
export async function runSecurity(mode, projectDir, onStep) {
|
|
345
|
+
export async function runSecurity(mode, projectDir, onStep, options = {}) {
|
|
235
346
|
// ── Detect stack ────────────────────────────────────────────────────────
|
|
236
|
-
report(onStep,
|
|
347
|
+
report(onStep, "detect", "Detecting stack", "running");
|
|
237
348
|
let stackInfo;
|
|
238
349
|
try {
|
|
239
350
|
stackInfo = await detectCIStack(projectDir);
|
|
240
|
-
report(onStep,
|
|
351
|
+
report(onStep, "detect", `Stack: ${stackInfo.stackType} (${stackInfo.buildTool})`, "done");
|
|
241
352
|
}
|
|
242
353
|
catch (e) {
|
|
243
|
-
report(onStep,
|
|
354
|
+
report(onStep, "detect", "Detecting stack", "error", String(e));
|
|
244
355
|
throw e;
|
|
245
356
|
}
|
|
246
357
|
// ── Resolve audit command ──────────────────────────────────────────────
|
|
247
358
|
const auditCmd = getAuditCommand(stackInfo.stackType, stackInfo.buildTool);
|
|
248
359
|
if (!auditCmd) {
|
|
249
|
-
report(onStep,
|
|
360
|
+
report(onStep, "audit", "Security audit", "error", `No audit tool for stack "${stackInfo.stackType}". Supported: node, python, go, rust`);
|
|
250
361
|
throw new Error(`Unsupported stack for security audit: ${stackInfo.stackType}`);
|
|
251
362
|
}
|
|
252
363
|
// ── Run audit ──────────────────────────────────────────────────────────
|
|
253
|
-
report(onStep,
|
|
364
|
+
report(onStep, "audit", `Running ${auditCmd.cmd} audit`, "running");
|
|
254
365
|
let raw;
|
|
255
366
|
try {
|
|
256
367
|
raw = await runAuditTool(projectDir, auditCmd);
|
|
257
|
-
report(onStep,
|
|
368
|
+
report(onStep, "audit", `Audit complete`, "done");
|
|
258
369
|
}
|
|
259
370
|
catch (e) {
|
|
260
|
-
report(onStep,
|
|
371
|
+
report(onStep, "audit", `Audit failed`, "error", `${auditCmd.cmd} not found or failed. Install it first.`);
|
|
261
372
|
throw e;
|
|
262
373
|
}
|
|
263
374
|
// ── Parse findings ─────────────────────────────────────────────────────
|
|
264
375
|
const findings = parseAuditOutput(stackInfo.stackType, raw);
|
|
265
376
|
switch (mode) {
|
|
266
|
-
case
|
|
267
|
-
case
|
|
268
|
-
report(onStep,
|
|
377
|
+
case "baseline":
|
|
378
|
+
case "update": {
|
|
379
|
+
report(onStep, "save", mode === "update" ? "Updating baseline" : "Creating baseline", "running");
|
|
380
|
+
// Preserve createdAt and allowlist on update
|
|
381
|
+
let createdAt = new Date().toISOString();
|
|
382
|
+
let allowlist = [];
|
|
383
|
+
if (mode === "update") {
|
|
384
|
+
const existing = await readBaseline(projectDir);
|
|
385
|
+
if (existing) {
|
|
386
|
+
createdAt = existing.createdAt;
|
|
387
|
+
allowlist = existing.allowlist ?? [];
|
|
388
|
+
}
|
|
389
|
+
}
|
|
269
390
|
const baseline = {
|
|
270
391
|
version: BASELINE_VERSION,
|
|
271
|
-
createdAt
|
|
392
|
+
createdAt,
|
|
393
|
+
updatedAt: mode === "update" ? new Date().toISOString() : undefined,
|
|
272
394
|
stack: stackInfo.stackType,
|
|
273
395
|
buildTool: stackInfo.buildTool,
|
|
274
396
|
findings,
|
|
275
397
|
findingKeys: findings.map(makeFindingKey),
|
|
398
|
+
allowlist,
|
|
276
399
|
};
|
|
277
400
|
await writeBaseline(projectDir, baseline);
|
|
278
|
-
report(onStep,
|
|
401
|
+
report(onStep, "save", `Baseline saved with ${findings.length} finding(s)`, "done", baselinePath(projectDir));
|
|
279
402
|
return null;
|
|
280
403
|
}
|
|
281
|
-
case
|
|
282
|
-
report(onStep,
|
|
404
|
+
case "check": {
|
|
405
|
+
report(onStep, "check", "Checking for regressions", "running");
|
|
283
406
|
const existing = await readBaseline(projectDir);
|
|
284
407
|
if (!existing) {
|
|
285
|
-
report(onStep,
|
|
286
|
-
throw new Error(
|
|
408
|
+
report(onStep, "check", "No baseline found", "error", "Run `javi-forge security baseline` first to create a baseline");
|
|
409
|
+
throw new Error("No security baseline found. Run `javi-forge security baseline` first.");
|
|
287
410
|
}
|
|
288
|
-
const result = detectRegressions(existing, findings);
|
|
289
|
-
|
|
411
|
+
const result = detectRegressions(existing, findings, options);
|
|
412
|
+
// Staleness warning
|
|
413
|
+
if (result.staleWarning) {
|
|
414
|
+
report(onStep, "stale", "Baseline staleness", "skipped", result.staleWarning);
|
|
415
|
+
}
|
|
416
|
+
// Summary line
|
|
417
|
+
const { summary } = result;
|
|
418
|
+
const sevBreakdown = Object.entries(summary.bySeverity)
|
|
419
|
+
.filter(([, count]) => count > 0)
|
|
420
|
+
.map(([sev, count]) => `${count} ${sev}`)
|
|
421
|
+
.join(", ");
|
|
422
|
+
if (sevBreakdown) {
|
|
423
|
+
report(onStep, "summary", `Current findings: ${summary.total} (${sevBreakdown})`, "done");
|
|
424
|
+
}
|
|
425
|
+
// Use filteredRegressions (severity-filtered + allowlist-filtered) for pass/fail
|
|
426
|
+
if (result.filteredRegressions.length === 0) {
|
|
290
427
|
const resolvedMsg = result.resolved.length > 0
|
|
291
428
|
? ` (${result.resolved.length} resolved)`
|
|
292
|
-
:
|
|
293
|
-
|
|
429
|
+
: "";
|
|
430
|
+
const belowThreshold = result.regressions.length > result.filteredRegressions.length
|
|
431
|
+
? ` (${result.regressions.length - result.filteredRegressions.length} below threshold)`
|
|
432
|
+
: "";
|
|
433
|
+
report(onStep, "check", `No actionable regressions${resolvedMsg}${belowThreshold}`, "done");
|
|
294
434
|
}
|
|
295
435
|
else {
|
|
296
|
-
const details = result.
|
|
297
|
-
.map(r => ` ${r.severity.toUpperCase()} ${r.package}: ${r.title}`)
|
|
298
|
-
.join(
|
|
299
|
-
report(onStep,
|
|
436
|
+
const details = result.filteredRegressions
|
|
437
|
+
.map((r) => ` ${r.severity.toUpperCase()} ${r.package}: ${r.title}`)
|
|
438
|
+
.join("\n");
|
|
439
|
+
report(onStep, "check", `${result.filteredRegressions.length} regression(s) found`, "error", details);
|
|
300
440
|
}
|
|
301
441
|
return result;
|
|
302
442
|
}
|
|
443
|
+
case "allowlist": {
|
|
444
|
+
report(onStep, "allowlist", "Updating allowlist", "running");
|
|
445
|
+
const existing = await readBaseline(projectDir);
|
|
446
|
+
if (!existing) {
|
|
447
|
+
report(onStep, "allowlist", "No baseline found", "error", "Run `javi-forge security baseline` first to create a baseline");
|
|
448
|
+
throw new Error("No security baseline found. Run `javi-forge security baseline` first.");
|
|
449
|
+
}
|
|
450
|
+
// Add all current findings to the allowlist
|
|
451
|
+
const currentKeys = findings.map(makeFindingKey);
|
|
452
|
+
const existingAllowlist = new Set(existing.allowlist ?? []);
|
|
453
|
+
for (const key of currentKeys) {
|
|
454
|
+
existingAllowlist.add(key);
|
|
455
|
+
}
|
|
456
|
+
existing.allowlist = [...existingAllowlist];
|
|
457
|
+
existing.updatedAt = new Date().toISOString();
|
|
458
|
+
await writeBaseline(projectDir, existing);
|
|
459
|
+
report(onStep, "allowlist", `Allowlist updated: ${existingAllowlist.size} finding(s) allowed`, "done", baselinePath(projectDir));
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
303
462
|
}
|
|
304
463
|
}
|
|
305
464
|
//# sourceMappingURL=security.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ConflictKind, SkillBudgetEntry, SkillBudgetResult, SkillBudgetSuggestion, SkillConflict, SkillDuplicate } from "../../types/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Generate minimal disable sets to bring total tokens under budget.
|
|
4
|
+
* Uses a greedy approach: disable largest skills first until under budget.
|
|
5
|
+
* Returns up to 3 alternative optimization suggestions.
|
|
6
|
+
*/
|
|
7
|
+
export declare function generateBudgetOptimizations(entries: SkillBudgetEntry[], totalTokens: number, budget: number): SkillBudgetSuggestion[];
|
|
8
|
+
/** Check if two rules contradict each other (regex pairs + directive clash) */
|
|
9
|
+
export declare function detectRuleConflict(ruleA: string, ruleB: string): {
|
|
10
|
+
reason: string;
|
|
11
|
+
kind: ConflictKind;
|
|
12
|
+
} | null;
|
|
13
|
+
/** Scan all skills for conflicting critical rules */
|
|
14
|
+
export declare function findConflicts(skillsDir: string): Promise<SkillConflict[]>;
|
|
15
|
+
/** Calculate token budget for all installed skills */
|
|
16
|
+
export declare function calculateBudget(skillsDir: string, budget?: number): Promise<SkillBudgetResult>;
|
|
17
|
+
/** Find skills that overlap in scope/triggers */
|
|
18
|
+
export declare function findDuplicates(skillsDir: string): Promise<SkillDuplicate[]>;
|
|
19
|
+
//# sourceMappingURL=analysis.d.ts.map
|