javi-forge 1.5.0 → 1.6.1
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 +191 -3
- package/ci-local/hooks/pre-push +17 -13
- package/dist/commands/analyze.d.ts +1 -1
- package/dist/commands/analyze.js +15 -15
- package/dist/commands/atlassian-mcp.d.ts +42 -0
- package/dist/commands/atlassian-mcp.js +98 -0
- package/dist/commands/ci.d.ts +3 -3
- package/dist/commands/ci.js +185 -147
- package/dist/commands/crash-recovery.d.ts +34 -0
- package/dist/commands/crash-recovery.js +123 -0
- package/dist/commands/doctor.d.ts +2 -2
- package/dist/commands/doctor.js +113 -61
- package/dist/commands/harness-audit.d.ts +35 -0
- package/dist/commands/harness-audit.js +277 -0
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +415 -118
- package/dist/commands/llmstxt.d.ts +1 -1
- package/dist/commands/llmstxt.js +36 -34
- package/dist/commands/parallel-batch.d.ts +42 -0
- package/dist/commands/parallel-batch.js +90 -0
- package/dist/commands/plugin.d.ts +26 -1
- package/dist/commands/plugin.js +138 -24
- package/dist/commands/secret-scanner.d.ts +30 -0
- package/dist/commands/secret-scanner.js +272 -0
- package/dist/commands/security-analysis.d.ts +74 -0
- package/dist/commands/security-analysis.js +487 -0
- package/dist/commands/security.d.ts +31 -0
- package/dist/commands/security.js +445 -0
- package/dist/commands/skill-scanner.d.ts +63 -0
- package/dist/commands/skill-scanner.js +383 -0
- package/dist/commands/skills.d.ts +139 -0
- package/dist/commands/skills.js +895 -0
- package/dist/commands/supply-chain.d.ts +23 -0
- package/dist/commands/supply-chain.js +126 -0
- package/dist/commands/tdd-pipeline.d.ts +17 -0
- package/dist/commands/tdd-pipeline.js +144 -0
- package/dist/commands/tdd.d.ts +21 -0
- package/dist/commands/tdd.js +120 -0
- package/dist/commands/team-presets.d.ts +53 -0
- package/dist/commands/team-presets.js +201 -0
- package/dist/commands/workflow.d.ts +23 -0
- package/dist/commands/workflow.js +114 -0
- package/dist/constants.d.ts +21 -0
- package/dist/constants.js +208 -37
- package/dist/index.js +400 -54
- package/dist/lib/agent-skills.d.ts +73 -0
- package/dist/lib/agent-skills.js +260 -0
- package/dist/lib/auto-skill-install.d.ts +37 -0
- package/dist/lib/auto-skill-install.js +92 -0
- package/dist/lib/auto-wire.d.ts +20 -0
- package/dist/lib/auto-wire.js +240 -0
- package/dist/lib/claudemd.d.ts +20 -0
- package/dist/lib/claudemd.js +222 -0
- package/dist/lib/codex-export.d.ts +16 -0
- package/dist/lib/codex-export.js +109 -0
- package/dist/lib/common.d.ts +1 -1
- package/dist/lib/common.js +52 -44
- package/dist/lib/context.d.ts +27 -0
- package/dist/lib/context.js +204 -0
- package/dist/lib/docker.d.ts +1 -1
- package/dist/lib/docker.js +141 -112
- package/dist/lib/frontmatter.d.ts +1 -1
- package/dist/lib/frontmatter.js +29 -15
- package/dist/lib/plugin.d.ts +19 -1
- package/dist/lib/plugin.js +174 -47
- package/dist/lib/skill-publish.d.ts +40 -0
- package/dist/lib/skill-publish.js +146 -0
- package/dist/lib/stack-detector.d.ts +38 -0
- package/dist/lib/stack-detector.js +207 -0
- package/dist/lib/template.d.ts +16 -1
- package/dist/lib/template.js +46 -17
- package/dist/lib/workflow/discovery.d.ts +19 -0
- package/dist/lib/workflow/discovery.js +68 -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 +55 -0
- package/dist/tasks/task-tracker.d.ts +15 -0
- package/dist/tasks/task-tracker.js +81 -0
- package/dist/types/index.d.ts +252 -5
- 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 +92 -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 +3 -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 +8 -2
- package/dist/ui/OptionSelector.js +83 -26
- package/dist/ui/Plugin.d.ts +4 -3
- package/dist/ui/Plugin.js +89 -29
- package/dist/ui/Progress.d.ts +3 -3
- package/dist/ui/Progress.js +23 -22
- package/dist/ui/Skills.d.ts +11 -0
- package/dist/ui/Skills.js +148 -0
- package/dist/ui/StackSelector.d.ts +2 -2
- package/dist/ui/StackSelector.js +26 -16
- 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/package.json +9 -6
- 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/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/common.d.ts.map +0 -1
- package/dist/lib/common.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/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/commands/ci.js
CHANGED
|
@@ -1,73 +1,74 @@
|
|
|
1
|
-
import { execFile, spawn } from
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
1
|
+
import { execFile, spawn } from "child_process";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { promisify } from "util";
|
|
5
|
+
import { refreshContextDir } from "../lib/context.js";
|
|
6
|
+
import { ensureImage, isDockerAvailable, openShell, runInContainer, } from "../lib/docker.js";
|
|
6
7
|
const execFileAsync = promisify(execFile);
|
|
7
8
|
// =============================================================================
|
|
8
9
|
// Stack detection
|
|
9
10
|
// =============================================================================
|
|
10
11
|
export async function detectCIStack(projectDir) {
|
|
11
|
-
let stackType =
|
|
12
|
-
let buildTool =
|
|
13
|
-
let javaVersion =
|
|
12
|
+
let stackType = "node";
|
|
13
|
+
let buildTool = "npm";
|
|
14
|
+
let javaVersion = "21";
|
|
14
15
|
// Java Gradle
|
|
15
|
-
if (await fs.pathExists(path.join(projectDir,
|
|
16
|
-
await fs.pathExists(path.join(projectDir,
|
|
17
|
-
stackType =
|
|
18
|
-
buildTool =
|
|
16
|
+
if ((await fs.pathExists(path.join(projectDir, "build.gradle.kts"))) ||
|
|
17
|
+
(await fs.pathExists(path.join(projectDir, "build.gradle")))) {
|
|
18
|
+
stackType = "java-gradle";
|
|
19
|
+
buildTool = "gradle";
|
|
19
20
|
// Try to read java version from build files
|
|
20
|
-
const ktsPath = path.join(projectDir,
|
|
21
|
-
const gradlePath = path.join(projectDir,
|
|
21
|
+
const ktsPath = path.join(projectDir, "build.gradle.kts");
|
|
22
|
+
const gradlePath = path.join(projectDir, "build.gradle");
|
|
22
23
|
if (await fs.pathExists(ktsPath)) {
|
|
23
|
-
const content = await fs.readFile(ktsPath,
|
|
24
|
+
const content = await fs.readFile(ktsPath, "utf-8");
|
|
24
25
|
const match = content.match(/JavaLanguageVersion\.of\((\d+)\)/);
|
|
25
26
|
if (match?.[1])
|
|
26
27
|
javaVersion = match[1];
|
|
27
28
|
}
|
|
28
29
|
else if (await fs.pathExists(gradlePath)) {
|
|
29
|
-
const content = await fs.readFile(gradlePath,
|
|
30
|
+
const content = await fs.readFile(gradlePath, "utf-8");
|
|
30
31
|
const match = content.match(/sourceCompatibility\s*=\s*['"]*(\d+)/);
|
|
31
32
|
if (match?.[1])
|
|
32
33
|
javaVersion = match[1];
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
// Java Maven
|
|
36
|
-
else if (await fs.pathExists(path.join(projectDir,
|
|
37
|
-
stackType =
|
|
38
|
-
buildTool =
|
|
37
|
+
else if (await fs.pathExists(path.join(projectDir, "pom.xml"))) {
|
|
38
|
+
stackType = "java-maven";
|
|
39
|
+
buildTool = "mvn";
|
|
39
40
|
}
|
|
40
41
|
// Node
|
|
41
|
-
else if (await fs.pathExists(path.join(projectDir,
|
|
42
|
-
stackType =
|
|
43
|
-
if (await fs.pathExists(path.join(projectDir,
|
|
44
|
-
buildTool =
|
|
45
|
-
else if (await fs.pathExists(path.join(projectDir,
|
|
46
|
-
buildTool =
|
|
42
|
+
else if (await fs.pathExists(path.join(projectDir, "package.json"))) {
|
|
43
|
+
stackType = "node";
|
|
44
|
+
if (await fs.pathExists(path.join(projectDir, "pnpm-lock.yaml")))
|
|
45
|
+
buildTool = "pnpm";
|
|
46
|
+
else if (await fs.pathExists(path.join(projectDir, "yarn.lock")))
|
|
47
|
+
buildTool = "yarn";
|
|
47
48
|
else
|
|
48
|
-
buildTool =
|
|
49
|
+
buildTool = "npm";
|
|
49
50
|
}
|
|
50
51
|
// Go
|
|
51
|
-
else if (await fs.pathExists(path.join(projectDir,
|
|
52
|
-
stackType =
|
|
53
|
-
buildTool =
|
|
52
|
+
else if (await fs.pathExists(path.join(projectDir, "go.mod"))) {
|
|
53
|
+
stackType = "go";
|
|
54
|
+
buildTool = "go";
|
|
54
55
|
}
|
|
55
56
|
// Rust
|
|
56
|
-
else if (await fs.pathExists(path.join(projectDir,
|
|
57
|
-
stackType =
|
|
58
|
-
buildTool =
|
|
57
|
+
else if (await fs.pathExists(path.join(projectDir, "Cargo.toml"))) {
|
|
58
|
+
stackType = "rust";
|
|
59
|
+
buildTool = "cargo";
|
|
59
60
|
}
|
|
60
61
|
// Python
|
|
61
|
-
else if (await fs.pathExists(path.join(projectDir,
|
|
62
|
-
await fs.pathExists(path.join(projectDir,
|
|
63
|
-
await fs.pathExists(path.join(projectDir,
|
|
64
|
-
stackType =
|
|
65
|
-
if (await fs.pathExists(path.join(projectDir,
|
|
66
|
-
buildTool =
|
|
67
|
-
else if (await fs.pathExists(path.join(projectDir,
|
|
68
|
-
buildTool =
|
|
62
|
+
else if ((await fs.pathExists(path.join(projectDir, "pyproject.toml"))) ||
|
|
63
|
+
(await fs.pathExists(path.join(projectDir, "requirements.txt"))) ||
|
|
64
|
+
(await fs.pathExists(path.join(projectDir, "setup.py")))) {
|
|
65
|
+
stackType = "python";
|
|
66
|
+
if (await fs.pathExists(path.join(projectDir, "uv.lock")))
|
|
67
|
+
buildTool = "uv";
|
|
68
|
+
else if (await fs.pathExists(path.join(projectDir, "poetry.lock")))
|
|
69
|
+
buildTool = "poetry";
|
|
69
70
|
else
|
|
70
|
-
buildTool =
|
|
71
|
+
buildTool = "pip";
|
|
71
72
|
}
|
|
72
73
|
// Build CI commands per stack
|
|
73
74
|
const { lintCmd, compileCmd, testCmd } = await buildCICommands(stackType, buildTool, projectDir);
|
|
@@ -75,52 +76,58 @@ export async function detectCIStack(projectDir) {
|
|
|
75
76
|
}
|
|
76
77
|
async function buildCICommands(stack, buildTool, projectDir) {
|
|
77
78
|
switch (stack) {
|
|
78
|
-
case
|
|
79
|
+
case "java-gradle":
|
|
79
80
|
return {
|
|
80
|
-
lintCmd:
|
|
81
|
-
compileCmd:
|
|
82
|
-
testCmd:
|
|
81
|
+
lintCmd: "./gradlew spotlessCheck --no-daemon",
|
|
82
|
+
compileCmd: "./gradlew clean classes testClasses --no-daemon && chown -R runner:runner build/ .gradle/ 2>/dev/null || true",
|
|
83
|
+
testCmd: "./gradlew test --no-daemon",
|
|
83
84
|
};
|
|
84
|
-
case
|
|
85
|
+
case "java-maven":
|
|
85
86
|
return {
|
|
86
|
-
lintCmd:
|
|
87
|
-
compileCmd:
|
|
88
|
-
testCmd:
|
|
87
|
+
lintCmd: "./mvnw spotless:check",
|
|
88
|
+
compileCmd: "./mvnw clean compile test-compile && chown -R runner:runner target/ .mvn/ 2>/dev/null || true",
|
|
89
|
+
testCmd: "./mvnw test",
|
|
89
90
|
};
|
|
90
|
-
case
|
|
91
|
-
const pkgPath = path.join(projectDir,
|
|
92
|
-
let pkgContent =
|
|
91
|
+
case "node": {
|
|
92
|
+
const pkgPath = path.join(projectDir, "package.json");
|
|
93
|
+
let pkgContent = "";
|
|
93
94
|
try {
|
|
94
|
-
pkgContent = await fs.readFile(pkgPath,
|
|
95
|
+
pkgContent = await fs.readFile(pkgPath, "utf-8");
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
/* no package.json */
|
|
95
99
|
}
|
|
96
|
-
catch { /* no package.json */ }
|
|
97
100
|
// Clean dist/ before build and chown after so tests (as runner) can access output.
|
|
98
101
|
// Runs as root inside the container to handle host-owned output dirs.
|
|
99
|
-
const buildPrefix =
|
|
100
|
-
const buildSuffix =
|
|
102
|
+
const buildPrefix = "rm -rf dist/ && ";
|
|
103
|
+
const buildSuffix = " && chown -R runner:runner dist/ 2>/dev/null || true";
|
|
101
104
|
return {
|
|
102
105
|
lintCmd: pkgContent.includes('"lint"') ? `${buildTool} run lint` : null,
|
|
103
|
-
compileCmd: pkgContent.includes('"build"')
|
|
104
|
-
|
|
106
|
+
compileCmd: pkgContent.includes('"build"')
|
|
107
|
+
? `${buildPrefix}${buildTool} run build${buildSuffix}`
|
|
108
|
+
: null,
|
|
109
|
+
testCmd: pkgContent.includes('"test"')
|
|
110
|
+
? `${buildTool} ${buildTool === "npm" ? "test" : "run test"}`
|
|
111
|
+
: null,
|
|
105
112
|
};
|
|
106
113
|
}
|
|
107
|
-
case
|
|
114
|
+
case "python":
|
|
108
115
|
return {
|
|
109
|
-
lintCmd:
|
|
116
|
+
lintCmd: "ruff check . && { pylint **/*.py 2>/dev/null || true; }",
|
|
110
117
|
compileCmd: null,
|
|
111
|
-
testCmd:
|
|
118
|
+
testCmd: "pytest",
|
|
112
119
|
};
|
|
113
|
-
case
|
|
120
|
+
case "go":
|
|
114
121
|
return {
|
|
115
|
-
lintCmd:
|
|
116
|
-
compileCmd:
|
|
117
|
-
testCmd:
|
|
122
|
+
lintCmd: "golangci-lint run",
|
|
123
|
+
compileCmd: "go clean -cache && go build ./... && chown -R runner:runner . 2>/dev/null || true",
|
|
124
|
+
testCmd: "go test ./...",
|
|
118
125
|
};
|
|
119
|
-
case
|
|
126
|
+
case "rust":
|
|
120
127
|
return {
|
|
121
|
-
lintCmd:
|
|
122
|
-
compileCmd:
|
|
123
|
-
testCmd:
|
|
128
|
+
lintCmd: "cargo clippy -- -D warnings",
|
|
129
|
+
compileCmd: "cargo clean && cargo build && chown -R runner:runner target/ 2>/dev/null || true",
|
|
130
|
+
testCmd: "cargo test",
|
|
124
131
|
};
|
|
125
132
|
default:
|
|
126
133
|
return { lintCmd: null, compileCmd: null, testCmd: null };
|
|
@@ -131,7 +138,7 @@ async function buildCICommands(stack, buildTool, projectDir) {
|
|
|
131
138
|
// =============================================================================
|
|
132
139
|
async function isGhaggaAvailable() {
|
|
133
140
|
try {
|
|
134
|
-
await execFileAsync(
|
|
141
|
+
await execFileAsync("ghagga", ["--version"], { timeout: 3000 });
|
|
135
142
|
return true;
|
|
136
143
|
}
|
|
137
144
|
catch {
|
|
@@ -145,35 +152,38 @@ function report(onStep, id, label, status, detail) {
|
|
|
145
152
|
onStep({ id, label, status, detail });
|
|
146
153
|
}
|
|
147
154
|
export async function runCI(options, onStep) {
|
|
148
|
-
const { projectDir = process.cwd(), mode =
|
|
155
|
+
const { projectDir = process.cwd(), mode = "full", noDocker = false, noGhagga = false, noSecurity = false, timeout = 600, } = options;
|
|
149
156
|
// ── Detect stack ────────────────────────────────────────────────────────────
|
|
150
|
-
const stepDetect =
|
|
151
|
-
report(onStep, stepDetect,
|
|
157
|
+
const stepDetect = "detect";
|
|
158
|
+
report(onStep, stepDetect, "Detecting stack", "running");
|
|
152
159
|
let stackInfo;
|
|
153
160
|
try {
|
|
154
161
|
stackInfo = await detectCIStack(projectDir);
|
|
155
|
-
report(onStep, stepDetect, `Stack: ${stackInfo.stackType} (${stackInfo.buildTool})`,
|
|
162
|
+
report(onStep, stepDetect, `Stack: ${stackInfo.stackType} (${stackInfo.buildTool})`, "done");
|
|
156
163
|
}
|
|
157
164
|
catch (e) {
|
|
158
|
-
report(onStep, stepDetect,
|
|
165
|
+
report(onStep, stepDetect, "Detecting stack", "error", String(e));
|
|
159
166
|
throw e;
|
|
160
167
|
}
|
|
161
168
|
// ── Detect mode ─────────────────────────────────────────────────────────────
|
|
162
|
-
if (mode ===
|
|
169
|
+
if (mode === "detect")
|
|
163
170
|
return;
|
|
164
171
|
// ── Shell mode ──────────────────────────────────────────────────────────────
|
|
165
|
-
if (mode ===
|
|
172
|
+
if (mode === "shell") {
|
|
166
173
|
if (noDocker) {
|
|
167
|
-
report(onStep,
|
|
168
|
-
throw new Error(
|
|
174
|
+
report(onStep, "shell", "Shell", "error", "--shell requires Docker");
|
|
175
|
+
throw new Error("--shell requires Docker");
|
|
169
176
|
}
|
|
170
|
-
report(onStep,
|
|
177
|
+
report(onStep, "docker-image", "Building Docker image", "running");
|
|
171
178
|
try {
|
|
172
|
-
await ensureImage({
|
|
173
|
-
|
|
179
|
+
await ensureImage({
|
|
180
|
+
stack: stackInfo.stackType,
|
|
181
|
+
javaVersion: stackInfo.javaVersion,
|
|
182
|
+
});
|
|
183
|
+
report(onStep, "docker-image", "Docker image ready", "done");
|
|
174
184
|
}
|
|
175
185
|
catch (e) {
|
|
176
|
-
report(onStep,
|
|
186
|
+
report(onStep, "docker-image", "Building Docker image", "error", String(e));
|
|
177
187
|
throw e;
|
|
178
188
|
}
|
|
179
189
|
await openShell(projectDir);
|
|
@@ -181,103 +191,122 @@ export async function runCI(options, onStep) {
|
|
|
181
191
|
}
|
|
182
192
|
// ── Check Docker ─────────────────────────────────────────────────────────────
|
|
183
193
|
if (!noDocker) {
|
|
184
|
-
const stepDocker =
|
|
185
|
-
report(onStep, stepDocker,
|
|
194
|
+
const stepDocker = "docker-check";
|
|
195
|
+
report(onStep, stepDocker, "Checking Docker", "running");
|
|
186
196
|
const dockerOk = await isDockerAvailable();
|
|
187
197
|
if (!dockerOk) {
|
|
188
|
-
report(onStep, stepDocker,
|
|
189
|
-
throw new Error(
|
|
198
|
+
report(onStep, stepDocker, "Docker not available", "error", "Start Docker or use --no-docker");
|
|
199
|
+
throw new Error("Docker is not available");
|
|
190
200
|
}
|
|
191
|
-
report(onStep, stepDocker,
|
|
201
|
+
report(onStep, stepDocker, "Docker available", "done");
|
|
192
202
|
// Build image
|
|
193
|
-
const stepImage =
|
|
194
|
-
report(onStep, stepImage, `Building image for ${stackInfo.stackType}`,
|
|
203
|
+
const stepImage = "docker-image";
|
|
204
|
+
report(onStep, stepImage, `Building image for ${stackInfo.stackType}`, "running");
|
|
195
205
|
try {
|
|
196
|
-
await ensureImage({
|
|
197
|
-
|
|
206
|
+
await ensureImage({
|
|
207
|
+
stack: stackInfo.stackType,
|
|
208
|
+
javaVersion: stackInfo.javaVersion,
|
|
209
|
+
});
|
|
210
|
+
report(onStep, stepImage, "Docker image ready", "done");
|
|
198
211
|
}
|
|
199
212
|
catch (e) {
|
|
200
|
-
report(onStep, stepImage,
|
|
213
|
+
report(onStep, stepImage, "Building Docker image", "error", String(e));
|
|
201
214
|
throw e;
|
|
202
215
|
}
|
|
203
216
|
}
|
|
217
|
+
// ── Refresh .context/ ────────────────────────────────────────────────────────
|
|
218
|
+
const stepContext = "context-refresh";
|
|
219
|
+
report(onStep, stepContext, "Refresh .context/ directory", "running");
|
|
220
|
+
try {
|
|
221
|
+
const ctxResult = await refreshContextDir(projectDir);
|
|
222
|
+
if (ctxResult) {
|
|
223
|
+
report(onStep, stepContext, "Refresh .context/ directory", "done", "INDEX.md + summary.md updated");
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
report(onStep, stepContext, "Refresh .context/ directory", "skipped", "no .context/ or no manifest");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
catch (e) {
|
|
230
|
+
// Non-fatal: context refresh failure should not block CI
|
|
231
|
+
report(onStep, stepContext, "Refresh .context/ directory", "error", String(e));
|
|
232
|
+
}
|
|
204
233
|
// ── Lint ─────────────────────────────────────────────────────────────────────
|
|
205
234
|
if (stackInfo.lintCmd) {
|
|
206
|
-
const stepLint =
|
|
207
|
-
report(onStep, stepLint, `Lint: ${stackInfo.lintCmd}`,
|
|
235
|
+
const stepLint = "lint";
|
|
236
|
+
report(onStep, stepLint, `Lint: ${stackInfo.lintCmd}`, "running");
|
|
208
237
|
try {
|
|
209
238
|
await runStep(stackInfo.lintCmd, projectDir, noDocker, timeout);
|
|
210
|
-
report(onStep, stepLint,
|
|
239
|
+
report(onStep, stepLint, "Lint passed", "done");
|
|
211
240
|
}
|
|
212
241
|
catch (e) {
|
|
213
|
-
report(onStep, stepLint,
|
|
242
|
+
report(onStep, stepLint, "Lint failed", "error", String(e));
|
|
214
243
|
throw e;
|
|
215
244
|
}
|
|
216
245
|
}
|
|
217
246
|
// ── Compile ──────────────────────────────────────────────────────────────────
|
|
218
247
|
if (stackInfo.compileCmd) {
|
|
219
|
-
const stepCompile =
|
|
220
|
-
report(onStep, stepCompile, `Compile: ${stackInfo.compileCmd}`,
|
|
248
|
+
const stepCompile = "compile";
|
|
249
|
+
report(onStep, stepCompile, `Compile: ${stackInfo.compileCmd}`, "running");
|
|
221
250
|
try {
|
|
222
251
|
// Run as root inside the container to rm/build output dirs owned by any host user,
|
|
223
252
|
// then chown back to runner so subsequent test steps can read the output.
|
|
224
|
-
await runStep(stackInfo.compileCmd, projectDir, noDocker, timeout,
|
|
225
|
-
report(onStep, stepCompile,
|
|
253
|
+
await runStep(stackInfo.compileCmd, projectDir, noDocker, timeout, "root");
|
|
254
|
+
report(onStep, stepCompile, "Compile passed", "done");
|
|
226
255
|
}
|
|
227
256
|
catch (e) {
|
|
228
|
-
report(onStep, stepCompile,
|
|
257
|
+
report(onStep, stepCompile, "Compile failed", "error", String(e));
|
|
229
258
|
throw e;
|
|
230
259
|
}
|
|
231
260
|
}
|
|
232
261
|
// ── Test (full mode only) ────────────────────────────────────────────────────
|
|
233
|
-
if (mode ===
|
|
234
|
-
const stepTest =
|
|
235
|
-
report(onStep, stepTest, `Test: ${stackInfo.testCmd}`,
|
|
262
|
+
if (mode === "full" && stackInfo.testCmd) {
|
|
263
|
+
const stepTest = "test";
|
|
264
|
+
report(onStep, stepTest, `Test: ${stackInfo.testCmd}`, "running");
|
|
236
265
|
try {
|
|
237
266
|
await runStep(stackInfo.testCmd, projectDir, noDocker, timeout);
|
|
238
|
-
report(onStep, stepTest,
|
|
267
|
+
report(onStep, stepTest, "Tests passed", "done");
|
|
239
268
|
}
|
|
240
269
|
catch (e) {
|
|
241
|
-
report(onStep, stepTest,
|
|
270
|
+
report(onStep, stepTest, "Tests failed", "error", String(e));
|
|
242
271
|
throw e;
|
|
243
272
|
}
|
|
244
273
|
}
|
|
245
274
|
// ── Security scan (full mode only) ──────────────────────────────────────────
|
|
246
|
-
if (mode ===
|
|
247
|
-
const stepSecurity =
|
|
275
|
+
if (mode === "full" && !noSecurity) {
|
|
276
|
+
const stepSecurity = "security";
|
|
248
277
|
const semgrepAvailable = await isSemgrepAvailable();
|
|
249
278
|
if (semgrepAvailable) {
|
|
250
|
-
report(onStep, stepSecurity,
|
|
279
|
+
report(onStep, stepSecurity, "Security scan (Semgrep)", "running");
|
|
251
280
|
try {
|
|
252
281
|
await runSemgrep(projectDir);
|
|
253
|
-
report(onStep, stepSecurity,
|
|
282
|
+
report(onStep, stepSecurity, "Security scan passed", "done");
|
|
254
283
|
}
|
|
255
284
|
catch (e) {
|
|
256
|
-
report(onStep, stepSecurity,
|
|
285
|
+
report(onStep, stepSecurity, "Security scan failed", "error", String(e));
|
|
257
286
|
throw e;
|
|
258
287
|
}
|
|
259
288
|
}
|
|
260
289
|
else {
|
|
261
|
-
report(onStep, stepSecurity,
|
|
290
|
+
report(onStep, stepSecurity, "Security scan", "skipped", "Semgrep not available — install semgrep or Docker");
|
|
262
291
|
}
|
|
263
292
|
}
|
|
264
293
|
// ── GHAGGA review (full mode only) ──────────────────────────────────────────
|
|
265
|
-
if (mode ===
|
|
266
|
-
const stepGhagga =
|
|
294
|
+
if (mode === "full" && !noGhagga) {
|
|
295
|
+
const stepGhagga = "ghagga";
|
|
267
296
|
const ghagga = await isGhaggaAvailable();
|
|
268
297
|
if (ghagga) {
|
|
269
|
-
report(onStep, stepGhagga,
|
|
298
|
+
report(onStep, stepGhagga, "GHAGGA review", "running");
|
|
270
299
|
try {
|
|
271
300
|
await runGhagga(projectDir);
|
|
272
|
-
report(onStep, stepGhagga,
|
|
301
|
+
report(onStep, stepGhagga, "GHAGGA review passed", "done");
|
|
273
302
|
}
|
|
274
303
|
catch (e) {
|
|
275
|
-
report(onStep, stepGhagga,
|
|
304
|
+
report(onStep, stepGhagga, "GHAGGA review failed", "error", String(e));
|
|
276
305
|
throw e;
|
|
277
306
|
}
|
|
278
307
|
}
|
|
279
308
|
else {
|
|
280
|
-
report(onStep, stepGhagga,
|
|
309
|
+
report(onStep, stepGhagga, "GHAGGA review", "skipped", "ghagga not installed");
|
|
281
310
|
}
|
|
282
311
|
}
|
|
283
312
|
}
|
|
@@ -288,13 +317,15 @@ async function runStep(command, projectDir, noDocker, timeout, user) {
|
|
|
288
317
|
if (noDocker) {
|
|
289
318
|
// Run natively
|
|
290
319
|
await new Promise((resolve, reject) => {
|
|
291
|
-
const proc = spawn(
|
|
320
|
+
const proc = spawn("bash", ["-c", command], {
|
|
292
321
|
cwd: projectDir,
|
|
293
|
-
stdio:
|
|
294
|
-
env: { ...process.env, CI:
|
|
322
|
+
stdio: "inherit",
|
|
323
|
+
env: { ...process.env, CI: "true" },
|
|
295
324
|
});
|
|
296
|
-
proc.on(
|
|
297
|
-
|
|
325
|
+
proc.on("close", (code) => code === 0
|
|
326
|
+
? resolve()
|
|
327
|
+
: reject(new Error(`Command failed with code ${code}`)));
|
|
328
|
+
proc.on("error", reject);
|
|
298
329
|
});
|
|
299
330
|
}
|
|
300
331
|
else {
|
|
@@ -312,7 +343,7 @@ async function runStep(command, projectDir, noDocker, timeout, user) {
|
|
|
312
343
|
}
|
|
313
344
|
async function isSemgrepAvailable() {
|
|
314
345
|
try {
|
|
315
|
-
await execFileAsync(
|
|
346
|
+
await execFileAsync("semgrep", ["--version"], { timeout: 3000 });
|
|
316
347
|
return true;
|
|
317
348
|
}
|
|
318
349
|
catch {
|
|
@@ -321,26 +352,30 @@ async function isSemgrepAvailable() {
|
|
|
321
352
|
}
|
|
322
353
|
async function runSemgrep(projectDir) {
|
|
323
354
|
// Look for semgrep config in project or use auto
|
|
324
|
-
const semgrepConfig = await fs.pathExists(path.join(projectDir,
|
|
325
|
-
? path.join(projectDir,
|
|
326
|
-
:
|
|
355
|
+
const semgrepConfig = (await fs.pathExists(path.join(projectDir, ".semgrep.yml")))
|
|
356
|
+
? path.join(projectDir, ".semgrep.yml")
|
|
357
|
+
: "auto";
|
|
327
358
|
await new Promise((resolve, reject) => {
|
|
328
|
-
const proc = spawn(
|
|
359
|
+
const proc = spawn("semgrep", ["--config", semgrepConfig, "--severity", "ERROR", "--quiet", "."], {
|
|
329
360
|
cwd: projectDir,
|
|
330
|
-
stdio:
|
|
361
|
+
stdio: "inherit",
|
|
331
362
|
});
|
|
332
|
-
proc.on(
|
|
333
|
-
|
|
363
|
+
proc.on("close", (code) => code === 0
|
|
364
|
+
? resolve()
|
|
365
|
+
: reject(new Error(`Semgrep found issues (exit ${code})`)));
|
|
366
|
+
proc.on("error", reject);
|
|
334
367
|
});
|
|
335
368
|
}
|
|
336
369
|
async function runGhagga(projectDir) {
|
|
337
370
|
await new Promise((resolve, reject) => {
|
|
338
|
-
const proc = spawn(
|
|
371
|
+
const proc = spawn("ghagga", ["review", "--plain", "--exit-on-issues"], {
|
|
339
372
|
cwd: projectDir,
|
|
340
|
-
stdio:
|
|
373
|
+
stdio: "inherit",
|
|
341
374
|
});
|
|
342
|
-
proc.on(
|
|
343
|
-
|
|
375
|
+
proc.on("close", (code) => code === 0
|
|
376
|
+
? resolve()
|
|
377
|
+
: reject(new Error(`GHAGGA review found issues (exit ${code})`)));
|
|
378
|
+
proc.on("error", reject);
|
|
344
379
|
});
|
|
345
380
|
}
|
|
346
381
|
// =============================================================================
|
|
@@ -386,8 +421,8 @@ fi || {
|
|
|
386
421
|
const COMMIT_MSG_HOOK = `#!/bin/bash
|
|
387
422
|
# Commit-msg: block AI attribution in commit messages
|
|
388
423
|
set -e
|
|
389
|
-
COMMIT_MSG_FILE="
|
|
390
|
-
COMMIT_MSG
|
|
424
|
+
COMMIT_MSG_FILE="$1"
|
|
425
|
+
COMMIT_MSG=$(cat "$COMMIT_MSG_FILE")
|
|
391
426
|
|
|
392
427
|
AI_PATTERNS=(
|
|
393
428
|
"co-authored-by:.*claude" "co-authored-by:.*anthropic"
|
|
@@ -403,10 +438,10 @@ AI_PATTERNS=(
|
|
|
403
438
|
)
|
|
404
439
|
|
|
405
440
|
for pattern in "\${AI_PATTERNS[@]}"; do
|
|
406
|
-
if echo "
|
|
441
|
+
if echo "$COMMIT_MSG" | grep -iqE "$pattern"; then
|
|
407
442
|
echo ""
|
|
408
443
|
echo "COMMIT BLOCKED: AI Attribution Detected"
|
|
409
|
-
echo " Pattern:
|
|
444
|
+
echo " Pattern: $pattern"
|
|
410
445
|
echo " Remove AI attribution. You are the sole author."
|
|
411
446
|
echo ""
|
|
412
447
|
exit 1
|
|
@@ -415,16 +450,19 @@ done
|
|
|
415
450
|
exit 0
|
|
416
451
|
`;
|
|
417
452
|
export async function installCIHooks(projectDir) {
|
|
418
|
-
const gitDir = path.join(projectDir,
|
|
419
|
-
if (!await fs.pathExists(gitDir)) {
|
|
420
|
-
return {
|
|
453
|
+
const gitDir = path.join(projectDir, ".git");
|
|
454
|
+
if (!(await fs.pathExists(gitDir))) {
|
|
455
|
+
return {
|
|
456
|
+
installed: [],
|
|
457
|
+
errors: ["Not a git repository. Run git init first."],
|
|
458
|
+
};
|
|
421
459
|
}
|
|
422
|
-
const hooksDir = path.join(gitDir,
|
|
460
|
+
const hooksDir = path.join(gitDir, "hooks");
|
|
423
461
|
await fs.ensureDir(hooksDir);
|
|
424
462
|
const hooks = [
|
|
425
|
-
{ name:
|
|
426
|
-
{ name:
|
|
427
|
-
{ name:
|
|
463
|
+
{ name: "pre-commit", content: PRE_COMMIT_HOOK },
|
|
464
|
+
{ name: "pre-push", content: PRE_PUSH_HOOK },
|
|
465
|
+
{ name: "commit-msg", content: COMMIT_MSG_HOOK },
|
|
428
466
|
];
|
|
429
467
|
const installed = [];
|
|
430
468
|
const errors = [];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crash recovery — reconstruct pipeline state from git commit history
|
|
3
|
+
* when checkpoint files are corrupted or lost. Git is the single
|
|
4
|
+
* source of truth for what was actually done.
|
|
5
|
+
*/
|
|
6
|
+
export interface RecoveredTask {
|
|
7
|
+
commitHash: string;
|
|
8
|
+
message: string;
|
|
9
|
+
timestamp: string;
|
|
10
|
+
filesChanged: string[];
|
|
11
|
+
taskId: string | null;
|
|
12
|
+
phase: string | null;
|
|
13
|
+
}
|
|
14
|
+
export interface RecoveryReport {
|
|
15
|
+
branch: string;
|
|
16
|
+
totalCommits: number;
|
|
17
|
+
tasks: RecoveredTask[];
|
|
18
|
+
phases: Record<string, number>;
|
|
19
|
+
lastActivity: string | null;
|
|
20
|
+
}
|
|
21
|
+
export declare function getGitLog(projectDir: string, options?: {
|
|
22
|
+
maxCommits?: number;
|
|
23
|
+
since?: string;
|
|
24
|
+
}): Promise<string>;
|
|
25
|
+
export declare function getCurrentBranch(projectDir: string): Promise<string>;
|
|
26
|
+
export declare function parseCommitPhase(message: string): string | null;
|
|
27
|
+
export declare function extractTaskId(message: string): string | null;
|
|
28
|
+
export declare function parseGitLog(raw: string): RecoveredTask[];
|
|
29
|
+
export declare function recoverFromGit(projectDir: string, options?: {
|
|
30
|
+
maxCommits?: number;
|
|
31
|
+
since?: string;
|
|
32
|
+
}): Promise<RecoveryReport>;
|
|
33
|
+
export declare function formatRecovery(report: RecoveryReport): string;
|
|
34
|
+
//# sourceMappingURL=crash-recovery.d.ts.map
|