javi-forge 1.1.0 → 1.3.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/ci-local/ci-local.sh +38 -10
- package/ci-local/hooks/pre-commit +10 -155
- package/ci-local/hooks/pre-push +12 -29
- package/dist/commands/ci.d.ts +33 -0
- package/dist/commands/ci.js +341 -0
- package/dist/commands/init.js +5 -0
- package/dist/index.js +39 -5
- package/dist/lib/docker.d.ts +43 -0
- package/dist/lib/docker.js +223 -0
- package/dist/ui/CI.d.ts +9 -0
- package/dist/ui/CI.js +91 -0
- package/package.json +9 -1
- package/ai-config/.skillignore +0 -15
- package/ai-config/AUTO_INVOKE.md +0 -300
- package/ai-config/agents/_TEMPLATE.md +0 -93
- package/ai-config/agents/business/api-designer.md +0 -1657
- package/ai-config/agents/business/business-analyst.md +0 -1331
- package/ai-config/agents/business/product-strategist.md +0 -206
- package/ai-config/agents/business/project-manager.md +0 -178
- package/ai-config/agents/business/requirements-analyst.md +0 -1277
- package/ai-config/agents/business/technical-writer.md +0 -1679
- package/ai-config/agents/creative/ux-designer.md +0 -205
- package/ai-config/agents/data-ai/ai-engineer.md +0 -487
- package/ai-config/agents/data-ai/analytics-engineer.md +0 -953
- package/ai-config/agents/data-ai/data-engineer.md +0 -173
- package/ai-config/agents/data-ai/data-scientist.md +0 -672
- package/ai-config/agents/data-ai/mlops-engineer.md +0 -814
- package/ai-config/agents/data-ai/prompt-engineer.md +0 -772
- package/ai-config/agents/development/angular-expert.md +0 -620
- package/ai-config/agents/development/backend-architect.md +0 -795
- package/ai-config/agents/development/database-specialist.md +0 -212
- package/ai-config/agents/development/frontend-specialist.md +0 -686
- package/ai-config/agents/development/fullstack-engineer.md +0 -668
- package/ai-config/agents/development/golang-pro.md +0 -338
- package/ai-config/agents/development/java-enterprise.md +0 -400
- package/ai-config/agents/development/javascript-pro.md +0 -422
- package/ai-config/agents/development/nextjs-pro.md +0 -474
- package/ai-config/agents/development/python-pro.md +0 -570
- package/ai-config/agents/development/react-pro.md +0 -487
- package/ai-config/agents/development/rust-pro.md +0 -246
- package/ai-config/agents/development/spring-boot-4-expert.md +0 -326
- package/ai-config/agents/development/typescript-pro.md +0 -336
- package/ai-config/agents/development/vue-specialist.md +0 -605
- package/ai-config/agents/infrastructure/cloud-architect.md +0 -472
- package/ai-config/agents/infrastructure/deployment-manager.md +0 -358
- package/ai-config/agents/infrastructure/devops-engineer.md +0 -455
- package/ai-config/agents/infrastructure/incident-responder.md +0 -519
- package/ai-config/agents/infrastructure/kubernetes-expert.md +0 -705
- package/ai-config/agents/infrastructure/monitoring-specialist.md +0 -674
- package/ai-config/agents/infrastructure/performance-engineer.md +0 -658
- package/ai-config/agents/orchestrator.md +0 -241
- package/ai-config/agents/quality/accessibility-auditor.md +0 -1204
- package/ai-config/agents/quality/code-reviewer-compact.md +0 -123
- package/ai-config/agents/quality/code-reviewer.md +0 -363
- package/ai-config/agents/quality/dependency-manager.md +0 -743
- package/ai-config/agents/quality/e2e-test-specialist.md +0 -1005
- package/ai-config/agents/quality/performance-tester.md +0 -1086
- package/ai-config/agents/quality/security-auditor.md +0 -133
- package/ai-config/agents/quality/test-engineer.md +0 -453
- package/ai-config/agents/specialists/api-designer.md +0 -87
- package/ai-config/agents/specialists/backend-architect.md +0 -73
- package/ai-config/agents/specialists/code-reviewer.md +0 -77
- package/ai-config/agents/specialists/db-optimizer.md +0 -75
- package/ai-config/agents/specialists/devops-engineer.md +0 -83
- package/ai-config/agents/specialists/documentation-writer.md +0 -78
- package/ai-config/agents/specialists/frontend-developer.md +0 -75
- package/ai-config/agents/specialists/performance-analyst.md +0 -82
- package/ai-config/agents/specialists/refactor-specialist.md +0 -74
- package/ai-config/agents/specialists/security-auditor.md +0 -74
- package/ai-config/agents/specialists/test-engineer.md +0 -81
- package/ai-config/agents/specialists/ux-consultant.md +0 -76
- package/ai-config/agents/specialized/agent-generator.md +0 -1190
- package/ai-config/agents/specialized/blockchain-developer.md +0 -149
- package/ai-config/agents/specialized/code-migrator.md +0 -892
- package/ai-config/agents/specialized/context-manager.md +0 -978
- package/ai-config/agents/specialized/documentation-writer.md +0 -1078
- package/ai-config/agents/specialized/ecommerce-expert.md +0 -1756
- package/ai-config/agents/specialized/embedded-engineer.md +0 -1714
- package/ai-config/agents/specialized/error-detective.md +0 -1034
- package/ai-config/agents/specialized/fintech-specialist.md +0 -1659
- package/ai-config/agents/specialized/freelance-project-planner-v2.md +0 -1988
- package/ai-config/agents/specialized/freelance-project-planner-v3.md +0 -2136
- package/ai-config/agents/specialized/freelance-project-planner-v4.md +0 -4503
- package/ai-config/agents/specialized/freelance-project-planner.md +0 -722
- package/ai-config/agents/specialized/game-developer.md +0 -1963
- package/ai-config/agents/specialized/healthcare-dev.md +0 -1620
- package/ai-config/agents/specialized/mobile-developer.md +0 -188
- package/ai-config/agents/specialized/parallel-plan-executor.md +0 -506
- package/ai-config/agents/specialized/plan-executor.md +0 -485
- package/ai-config/agents/specialized/solo-dev-planner-modular/00-INDEX.md +0 -485
- package/ai-config/agents/specialized/solo-dev-planner-modular/01-CORE.md +0 -3493
- package/ai-config/agents/specialized/solo-dev-planner-modular/02-SELF-CORRECTION.md +0 -778
- package/ai-config/agents/specialized/solo-dev-planner-modular/03-PROGRESSIVE-SETUP.md +0 -918
- package/ai-config/agents/specialized/solo-dev-planner-modular/04-DEPLOYMENT.md +0 -1537
- package/ai-config/agents/specialized/solo-dev-planner-modular/05-TESTING.md +0 -2633
- package/ai-config/agents/specialized/solo-dev-planner-modular/06-OPERATIONS.md +0 -5610
- package/ai-config/agents/specialized/solo-dev-planner-modular/INSTALL.md +0 -335
- package/ai-config/agents/specialized/solo-dev-planner-modular/QUICK-REFERENCE.txt +0 -215
- package/ai-config/agents/specialized/solo-dev-planner-modular/README.md +0 -260
- package/ai-config/agents/specialized/solo-dev-planner-modular/START-HERE.md +0 -379
- package/ai-config/agents/specialized/solo-dev-planner-modular/WORKFLOW-DIAGRAM.md +0 -355
- package/ai-config/agents/specialized/solo-dev-planner-modular/solo-dev-planner.md +0 -279
- package/ai-config/agents/specialized/template-writer.md +0 -347
- package/ai-config/agents/specialized/test-runner.md +0 -99
- package/ai-config/agents/specialized/vibekanban-smart-worker.md +0 -244
- package/ai-config/agents/specialized/wave-executor.md +0 -138
- package/ai-config/agents/specialized/workflow-optimizer.md +0 -1114
- package/ai-config/commands/git/changelog.md +0 -32
- package/ai-config/commands/git/ci-local.md +0 -70
- package/ai-config/commands/git/commit.md +0 -35
- package/ai-config/commands/git/fix-issue.md +0 -23
- package/ai-config/commands/git/pr-create.md +0 -42
- package/ai-config/commands/git/pr-review.md +0 -50
- package/ai-config/commands/git/worktree.md +0 -39
- package/ai-config/commands/refactoring/cleanup.md +0 -24
- package/ai-config/commands/refactoring/dead-code.md +0 -40
- package/ai-config/commands/refactoring/extract.md +0 -31
- package/ai-config/commands/testing/e2e.md +0 -30
- package/ai-config/commands/testing/tdd.md +0 -36
- package/ai-config/commands/testing/test-coverage.md +0 -30
- package/ai-config/commands/testing/test-fix.md +0 -24
- package/ai-config/commands/workflow/generate-agents-md.md +0 -85
- package/ai-config/commands/workflow/planning.md +0 -47
- package/ai-config/commands/workflows/compound.md +0 -89
- package/ai-config/commands/workflows/diagnose.md +0 -70
- package/ai-config/commands/workflows/discover.md +0 -86
- package/ai-config/commands/workflows/plan.md +0 -77
- package/ai-config/commands/workflows/review.md +0 -78
- package/ai-config/commands/workflows/work.md +0 -75
- package/ai-config/config.yaml +0 -18
- package/ai-config/hooks/_TEMPLATE.md +0 -96
- package/ai-config/hooks/block-dangerous-commands.md +0 -75
- package/ai-config/hooks/commit-guard.md +0 -90
- package/ai-config/hooks/context-loader.md +0 -73
- package/ai-config/hooks/improve-prompt.md +0 -91
- package/ai-config/hooks/learning-log.md +0 -72
- package/ai-config/hooks/model-router.md +0 -86
- package/ai-config/hooks/secret-scanner.md +0 -64
- package/ai-config/hooks/skill-validator.md +0 -102
- package/ai-config/hooks/task-artifact.md +0 -114
- package/ai-config/hooks/validate-workflow.md +0 -100
- package/ai-config/prompts/base.md +0 -71
- package/ai-config/prompts/modes/debug.md +0 -34
- package/ai-config/prompts/modes/deploy.md +0 -40
- package/ai-config/prompts/modes/research.md +0 -32
- package/ai-config/prompts/modes/review.md +0 -33
- package/ai-config/prompts/review-policy.md +0 -79
- package/ai-config/skills/_TEMPLATE.md +0 -157
- package/ai-config/skills/backend/api-gateway/SKILL.md +0 -254
- package/ai-config/skills/backend/bff-concepts/SKILL.md +0 -239
- package/ai-config/skills/backend/bff-spring/SKILL.md +0 -364
- package/ai-config/skills/backend/chi-router/SKILL.md +0 -396
- package/ai-config/skills/backend/error-handling/SKILL.md +0 -255
- package/ai-config/skills/backend/exceptions-spring/SKILL.md +0 -323
- package/ai-config/skills/backend/fastapi/SKILL.md +0 -302
- package/ai-config/skills/backend/gateway-spring/SKILL.md +0 -390
- package/ai-config/skills/backend/go-backend/SKILL.md +0 -457
- package/ai-config/skills/backend/gradle-multimodule/SKILL.md +0 -274
- package/ai-config/skills/backend/graphql-concepts/SKILL.md +0 -352
- package/ai-config/skills/backend/graphql-spring/SKILL.md +0 -398
- package/ai-config/skills/backend/grpc-concepts/SKILL.md +0 -283
- package/ai-config/skills/backend/grpc-spring/SKILL.md +0 -445
- package/ai-config/skills/backend/jwt-auth/SKILL.md +0 -412
- package/ai-config/skills/backend/notifications-concepts/SKILL.md +0 -259
- package/ai-config/skills/backend/recommendations-concepts/SKILL.md +0 -261
- package/ai-config/skills/backend/search-concepts/SKILL.md +0 -263
- package/ai-config/skills/backend/search-spring/SKILL.md +0 -375
- package/ai-config/skills/backend/spring-boot-4/SKILL.md +0 -172
- package/ai-config/skills/backend/websockets/SKILL.md +0 -532
- package/ai-config/skills/data-ai/ai-ml/SKILL.md +0 -423
- package/ai-config/skills/data-ai/analytics-concepts/SKILL.md +0 -195
- package/ai-config/skills/data-ai/analytics-spring/SKILL.md +0 -340
- package/ai-config/skills/data-ai/duckdb-analytics/SKILL.md +0 -440
- package/ai-config/skills/data-ai/langchain/SKILL.md +0 -238
- package/ai-config/skills/data-ai/mlflow/SKILL.md +0 -302
- package/ai-config/skills/data-ai/onnx-inference/SKILL.md +0 -290
- package/ai-config/skills/data-ai/powerbi/SKILL.md +0 -352
- package/ai-config/skills/data-ai/pytorch/SKILL.md +0 -274
- package/ai-config/skills/data-ai/scikit-learn/SKILL.md +0 -321
- package/ai-config/skills/data-ai/vector-db/SKILL.md +0 -301
- package/ai-config/skills/database/graph-databases/SKILL.md +0 -218
- package/ai-config/skills/database/graph-spring/SKILL.md +0 -361
- package/ai-config/skills/database/pgx-postgres/SKILL.md +0 -512
- package/ai-config/skills/database/redis-cache/SKILL.md +0 -343
- package/ai-config/skills/database/sqlite-embedded/SKILL.md +0 -388
- package/ai-config/skills/database/timescaledb/SKILL.md +0 -320
- package/ai-config/skills/docs/api-documentation/SKILL.md +0 -293
- package/ai-config/skills/docs/docs-spring/SKILL.md +0 -377
- package/ai-config/skills/docs/mustache-templates/SKILL.md +0 -190
- package/ai-config/skills/docs/technical-docs/SKILL.md +0 -447
- package/ai-config/skills/frontend/astro-ssr/SKILL.md +0 -441
- package/ai-config/skills/frontend/frontend-design/SKILL.md +0 -54
- package/ai-config/skills/frontend/frontend-web/SKILL.md +0 -368
- package/ai-config/skills/frontend/mantine-ui/SKILL.md +0 -396
- package/ai-config/skills/frontend/tanstack-query/SKILL.md +0 -439
- package/ai-config/skills/frontend/zod-validation/SKILL.md +0 -417
- package/ai-config/skills/frontend/zustand-state/SKILL.md +0 -350
- package/ai-config/skills/infrastructure/chaos-engineering/SKILL.md +0 -244
- package/ai-config/skills/infrastructure/chaos-spring/SKILL.md +0 -378
- package/ai-config/skills/infrastructure/devops-infra/SKILL.md +0 -435
- package/ai-config/skills/infrastructure/docker-containers/SKILL.md +0 -420
- package/ai-config/skills/infrastructure/kubernetes/SKILL.md +0 -456
- package/ai-config/skills/infrastructure/opentelemetry/SKILL.md +0 -546
- package/ai-config/skills/infrastructure/traefik-proxy/SKILL.md +0 -474
- package/ai-config/skills/infrastructure/woodpecker-ci/SKILL.md +0 -315
- package/ai-config/skills/mobile/ionic-capacitor/SKILL.md +0 -504
- package/ai-config/skills/mobile/mobile-ionic/SKILL.md +0 -448
- package/ai-config/skills/prompt-improver/SKILL.md +0 -125
- package/ai-config/skills/quality/ghagga-review/SKILL.md +0 -216
- package/ai-config/skills/references/hooks-patterns/SKILL.md +0 -238
- package/ai-config/skills/references/mcp-servers/SKILL.md +0 -275
- package/ai-config/skills/references/plugins-reference/SKILL.md +0 -110
- package/ai-config/skills/references/skills-reference/SKILL.md +0 -420
- package/ai-config/skills/references/subagent-templates/SKILL.md +0 -193
- package/ai-config/skills/systems-iot/modbus-protocol/SKILL.md +0 -410
- package/ai-config/skills/systems-iot/mqtt-rumqttc/SKILL.md +0 -408
- package/ai-config/skills/systems-iot/rust-systems/SKILL.md +0 -386
- package/ai-config/skills/systems-iot/tokio-async/SKILL.md +0 -324
- package/ai-config/skills/testing/playwright-e2e/SKILL.md +0 -289
- package/ai-config/skills/testing/testcontainers/SKILL.md +0 -299
- package/ai-config/skills/testing/vitest-testing/SKILL.md +0 -381
- package/ai-config/skills/workflow/ci-local-guide/SKILL.md +0 -118
- package/ai-config/skills/workflow/claude-automation-recommender/SKILL.md +0 -299
- package/ai-config/skills/workflow/claude-md-improver/SKILL.md +0 -158
- package/ai-config/skills/workflow/finishing-a-development-branch/SKILL.md +0 -117
- package/ai-config/skills/workflow/git-github/SKILL.md +0 -334
- package/ai-config/skills/workflow/git-github/references/examples.md +0 -160
- package/ai-config/skills/workflow/git-workflow/SKILL.md +0 -214
- package/ai-config/skills/workflow/ide-plugins/SKILL.md +0 -277
- package/ai-config/skills/workflow/ide-plugins-intellij/SKILL.md +0 -401
- package/ai-config/skills/workflow/obsidian-brain-workflow/SKILL.md +0 -199
- package/ai-config/skills/workflow/using-git-worktrees/SKILL.md +0 -100
- package/ai-config/skills/workflow/verification-before-completion/SKILL.md +0 -73
- package/ai-config/skills/workflow/wave-workflow/SKILL.md +0 -178
- package/schemas/agent.schema.json +0 -34
- package/schemas/ai-config.schema.json +0 -28
- package/schemas/plugin.schema.json +0 -62
- package/schemas/skill.schema.json +0 -44
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { execFile, spawn } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { isDockerAvailable, ensureImage, runInContainer, openShell } from '../lib/docker.js';
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
|
+
// =============================================================================
|
|
8
|
+
// Stack detection
|
|
9
|
+
// =============================================================================
|
|
10
|
+
export async function detectCIStack(projectDir) {
|
|
11
|
+
let stackType = 'node';
|
|
12
|
+
let buildTool = 'npm';
|
|
13
|
+
let javaVersion = '21';
|
|
14
|
+
// Java Gradle
|
|
15
|
+
if (await fs.pathExists(path.join(projectDir, 'build.gradle.kts')) ||
|
|
16
|
+
await fs.pathExists(path.join(projectDir, 'build.gradle'))) {
|
|
17
|
+
stackType = 'java-gradle';
|
|
18
|
+
buildTool = 'gradle';
|
|
19
|
+
// Try to read java version from build files
|
|
20
|
+
const ktsPath = path.join(projectDir, 'build.gradle.kts');
|
|
21
|
+
const gradlePath = path.join(projectDir, 'build.gradle');
|
|
22
|
+
if (await fs.pathExists(ktsPath)) {
|
|
23
|
+
const content = await fs.readFile(ktsPath, 'utf-8');
|
|
24
|
+
const match = content.match(/JavaLanguageVersion\.of\((\d+)\)/);
|
|
25
|
+
if (match?.[1])
|
|
26
|
+
javaVersion = match[1];
|
|
27
|
+
}
|
|
28
|
+
else if (await fs.pathExists(gradlePath)) {
|
|
29
|
+
const content = await fs.readFile(gradlePath, 'utf-8');
|
|
30
|
+
const match = content.match(/sourceCompatibility\s*=\s*['"]*(\d+)/);
|
|
31
|
+
if (match?.[1])
|
|
32
|
+
javaVersion = match[1];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// Java Maven
|
|
36
|
+
else if (await fs.pathExists(path.join(projectDir, 'pom.xml'))) {
|
|
37
|
+
stackType = 'java-maven';
|
|
38
|
+
buildTool = 'mvn';
|
|
39
|
+
}
|
|
40
|
+
// Node
|
|
41
|
+
else if (await fs.pathExists(path.join(projectDir, 'package.json'))) {
|
|
42
|
+
stackType = 'node';
|
|
43
|
+
if (await fs.pathExists(path.join(projectDir, 'pnpm-lock.yaml')))
|
|
44
|
+
buildTool = 'pnpm';
|
|
45
|
+
else if (await fs.pathExists(path.join(projectDir, 'yarn.lock')))
|
|
46
|
+
buildTool = 'yarn';
|
|
47
|
+
else
|
|
48
|
+
buildTool = 'npm';
|
|
49
|
+
}
|
|
50
|
+
// Go
|
|
51
|
+
else if (await fs.pathExists(path.join(projectDir, 'go.mod'))) {
|
|
52
|
+
stackType = 'go';
|
|
53
|
+
buildTool = 'go';
|
|
54
|
+
}
|
|
55
|
+
// Rust
|
|
56
|
+
else if (await fs.pathExists(path.join(projectDir, 'Cargo.toml'))) {
|
|
57
|
+
stackType = 'rust';
|
|
58
|
+
buildTool = 'cargo';
|
|
59
|
+
}
|
|
60
|
+
// Python
|
|
61
|
+
else if (await fs.pathExists(path.join(projectDir, 'pyproject.toml')) ||
|
|
62
|
+
await fs.pathExists(path.join(projectDir, 'requirements.txt')) ||
|
|
63
|
+
await fs.pathExists(path.join(projectDir, 'setup.py'))) {
|
|
64
|
+
stackType = 'python';
|
|
65
|
+
if (await fs.pathExists(path.join(projectDir, 'uv.lock')))
|
|
66
|
+
buildTool = 'uv';
|
|
67
|
+
else if (await fs.pathExists(path.join(projectDir, 'poetry.lock')))
|
|
68
|
+
buildTool = 'poetry';
|
|
69
|
+
else
|
|
70
|
+
buildTool = 'pip';
|
|
71
|
+
}
|
|
72
|
+
// Build CI commands per stack
|
|
73
|
+
const { lintCmd, compileCmd, testCmd } = await buildCICommands(stackType, buildTool, projectDir);
|
|
74
|
+
return { stackType, buildTool, javaVersion, lintCmd, compileCmd, testCmd };
|
|
75
|
+
}
|
|
76
|
+
async function buildCICommands(stack, buildTool, projectDir) {
|
|
77
|
+
switch (stack) {
|
|
78
|
+
case 'java-gradle':
|
|
79
|
+
return {
|
|
80
|
+
lintCmd: './gradlew spotlessCheck --no-daemon',
|
|
81
|
+
compileCmd: './gradlew classes testClasses --no-daemon',
|
|
82
|
+
testCmd: './gradlew test --no-daemon',
|
|
83
|
+
};
|
|
84
|
+
case 'java-maven':
|
|
85
|
+
return {
|
|
86
|
+
lintCmd: './mvnw spotless:check',
|
|
87
|
+
compileCmd: './mvnw compile test-compile',
|
|
88
|
+
testCmd: './mvnw test',
|
|
89
|
+
};
|
|
90
|
+
case 'node': {
|
|
91
|
+
const pkgPath = path.join(projectDir, 'package.json');
|
|
92
|
+
let pkgContent = '';
|
|
93
|
+
try {
|
|
94
|
+
pkgContent = await fs.readFile(pkgPath, 'utf-8');
|
|
95
|
+
}
|
|
96
|
+
catch { /* no package.json */ }
|
|
97
|
+
// Clean dist/ before build to avoid permission issues from prior Docker builds
|
|
98
|
+
const buildPrefix = 'rm -rf dist/ && ';
|
|
99
|
+
return {
|
|
100
|
+
lintCmd: pkgContent.includes('"lint"') ? `${buildTool} run lint` : null,
|
|
101
|
+
compileCmd: pkgContent.includes('"build"') ? `${buildPrefix}${buildTool} run build` : null,
|
|
102
|
+
testCmd: pkgContent.includes('"test"') ? `${buildTool} ${buildTool === 'npm' ? 'test' : 'run test'}` : null,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
case 'python':
|
|
106
|
+
return {
|
|
107
|
+
lintCmd: 'ruff check . && { pylint **/*.py 2>/dev/null || true; }',
|
|
108
|
+
compileCmd: null,
|
|
109
|
+
testCmd: 'pytest',
|
|
110
|
+
};
|
|
111
|
+
case 'go':
|
|
112
|
+
return {
|
|
113
|
+
lintCmd: 'golangci-lint run',
|
|
114
|
+
compileCmd: 'go build ./...',
|
|
115
|
+
testCmd: 'go test ./...',
|
|
116
|
+
};
|
|
117
|
+
case 'rust':
|
|
118
|
+
return {
|
|
119
|
+
lintCmd: 'cargo clippy -- -D warnings',
|
|
120
|
+
compileCmd: 'cargo build',
|
|
121
|
+
testCmd: 'cargo test',
|
|
122
|
+
};
|
|
123
|
+
default:
|
|
124
|
+
return { lintCmd: null, compileCmd: null, testCmd: null };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// =============================================================================
|
|
128
|
+
// GHAGGA check
|
|
129
|
+
// =============================================================================
|
|
130
|
+
async function isGhaggaAvailable() {
|
|
131
|
+
try {
|
|
132
|
+
await execFileAsync('ghagga', ['--version'], { timeout: 3000 });
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// =============================================================================
|
|
140
|
+
// Main CI runner
|
|
141
|
+
// =============================================================================
|
|
142
|
+
function report(onStep, id, label, status, detail) {
|
|
143
|
+
onStep({ id, label, status, detail });
|
|
144
|
+
}
|
|
145
|
+
export async function runCI(options, onStep) {
|
|
146
|
+
const { projectDir = process.cwd(), mode = 'full', noDocker = false, noGhagga = false, noSecurity = false, timeout = 600, } = options;
|
|
147
|
+
// ── Detect stack ────────────────────────────────────────────────────────────
|
|
148
|
+
const stepDetect = 'detect';
|
|
149
|
+
report(onStep, stepDetect, 'Detecting stack', 'running');
|
|
150
|
+
let stackInfo;
|
|
151
|
+
try {
|
|
152
|
+
stackInfo = await detectCIStack(projectDir);
|
|
153
|
+
report(onStep, stepDetect, `Stack: ${stackInfo.stackType} (${stackInfo.buildTool})`, 'done');
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
report(onStep, stepDetect, 'Detecting stack', 'error', String(e));
|
|
157
|
+
throw e;
|
|
158
|
+
}
|
|
159
|
+
// ── Detect mode ─────────────────────────────────────────────────────────────
|
|
160
|
+
if (mode === 'detect')
|
|
161
|
+
return;
|
|
162
|
+
// ── Shell mode ──────────────────────────────────────────────────────────────
|
|
163
|
+
if (mode === 'shell') {
|
|
164
|
+
if (noDocker) {
|
|
165
|
+
report(onStep, 'shell', 'Shell', 'error', '--shell requires Docker');
|
|
166
|
+
throw new Error('--shell requires Docker');
|
|
167
|
+
}
|
|
168
|
+
report(onStep, 'docker-image', 'Building Docker image', 'running');
|
|
169
|
+
try {
|
|
170
|
+
await ensureImage({ stack: stackInfo.stackType, javaVersion: stackInfo.javaVersion });
|
|
171
|
+
report(onStep, 'docker-image', 'Docker image ready', 'done');
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
report(onStep, 'docker-image', 'Building Docker image', 'error', String(e));
|
|
175
|
+
throw e;
|
|
176
|
+
}
|
|
177
|
+
await openShell(projectDir);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
// ── Check Docker ─────────────────────────────────────────────────────────────
|
|
181
|
+
if (!noDocker) {
|
|
182
|
+
const stepDocker = 'docker-check';
|
|
183
|
+
report(onStep, stepDocker, 'Checking Docker', 'running');
|
|
184
|
+
const dockerOk = await isDockerAvailable();
|
|
185
|
+
if (!dockerOk) {
|
|
186
|
+
report(onStep, stepDocker, 'Docker not available', 'error', 'Start Docker or use --no-docker');
|
|
187
|
+
throw new Error('Docker is not available');
|
|
188
|
+
}
|
|
189
|
+
report(onStep, stepDocker, 'Docker available', 'done');
|
|
190
|
+
// Build image
|
|
191
|
+
const stepImage = 'docker-image';
|
|
192
|
+
report(onStep, stepImage, `Building image for ${stackInfo.stackType}`, 'running');
|
|
193
|
+
try {
|
|
194
|
+
await ensureImage({ stack: stackInfo.stackType, javaVersion: stackInfo.javaVersion });
|
|
195
|
+
report(onStep, stepImage, 'Docker image ready', 'done');
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
report(onStep, stepImage, 'Building Docker image', 'error', String(e));
|
|
199
|
+
throw e;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// ── Lint ─────────────────────────────────────────────────────────────────────
|
|
203
|
+
if (stackInfo.lintCmd) {
|
|
204
|
+
const stepLint = 'lint';
|
|
205
|
+
report(onStep, stepLint, `Lint: ${stackInfo.lintCmd}`, 'running');
|
|
206
|
+
try {
|
|
207
|
+
await runStep(stackInfo.lintCmd, projectDir, noDocker, timeout);
|
|
208
|
+
report(onStep, stepLint, 'Lint passed', 'done');
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
report(onStep, stepLint, 'Lint failed', 'error', String(e));
|
|
212
|
+
throw e;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
// ── Compile ──────────────────────────────────────────────────────────────────
|
|
216
|
+
if (stackInfo.compileCmd) {
|
|
217
|
+
const stepCompile = 'compile';
|
|
218
|
+
report(onStep, stepCompile, `Compile: ${stackInfo.compileCmd}`, 'running');
|
|
219
|
+
try {
|
|
220
|
+
await runStep(stackInfo.compileCmd, projectDir, noDocker, timeout);
|
|
221
|
+
report(onStep, stepCompile, 'Compile passed', 'done');
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
report(onStep, stepCompile, 'Compile failed', 'error', String(e));
|
|
225
|
+
throw e;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// ── Test (full mode only) ────────────────────────────────────────────────────
|
|
229
|
+
if (mode === 'full' && stackInfo.testCmd) {
|
|
230
|
+
const stepTest = 'test';
|
|
231
|
+
report(onStep, stepTest, `Test: ${stackInfo.testCmd}`, 'running');
|
|
232
|
+
try {
|
|
233
|
+
await runStep(stackInfo.testCmd, projectDir, noDocker, timeout);
|
|
234
|
+
report(onStep, stepTest, 'Tests passed', 'done');
|
|
235
|
+
}
|
|
236
|
+
catch (e) {
|
|
237
|
+
report(onStep, stepTest, 'Tests failed', 'error', String(e));
|
|
238
|
+
throw e;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
// ── Security scan (full mode only) ──────────────────────────────────────────
|
|
242
|
+
if (mode === 'full' && !noSecurity) {
|
|
243
|
+
const stepSecurity = 'security';
|
|
244
|
+
const semgrepAvailable = await isSemgrepAvailable();
|
|
245
|
+
if (semgrepAvailable) {
|
|
246
|
+
report(onStep, stepSecurity, 'Security scan (Semgrep)', 'running');
|
|
247
|
+
try {
|
|
248
|
+
await runSemgrep(projectDir);
|
|
249
|
+
report(onStep, stepSecurity, 'Security scan passed', 'done');
|
|
250
|
+
}
|
|
251
|
+
catch (e) {
|
|
252
|
+
report(onStep, stepSecurity, 'Security scan failed', 'error', String(e));
|
|
253
|
+
throw e;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
report(onStep, stepSecurity, 'Security scan', 'skipped', 'Semgrep not available — install semgrep or Docker');
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// ── GHAGGA review (full mode only) ──────────────────────────────────────────
|
|
261
|
+
if (mode === 'full' && !noGhagga) {
|
|
262
|
+
const stepGhagga = 'ghagga';
|
|
263
|
+
const ghagga = await isGhaggaAvailable();
|
|
264
|
+
if (ghagga) {
|
|
265
|
+
report(onStep, stepGhagga, 'GHAGGA review', 'running');
|
|
266
|
+
try {
|
|
267
|
+
await runGhagga(projectDir);
|
|
268
|
+
report(onStep, stepGhagga, 'GHAGGA review passed', 'done');
|
|
269
|
+
}
|
|
270
|
+
catch (e) {
|
|
271
|
+
report(onStep, stepGhagga, 'GHAGGA review failed', 'error', String(e));
|
|
272
|
+
throw e;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
report(onStep, stepGhagga, 'GHAGGA review', 'skipped', 'ghagga not installed');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// =============================================================================
|
|
281
|
+
// Step runners
|
|
282
|
+
// =============================================================================
|
|
283
|
+
async function runStep(command, projectDir, noDocker, timeout) {
|
|
284
|
+
if (noDocker) {
|
|
285
|
+
// Run natively
|
|
286
|
+
await new Promise((resolve, reject) => {
|
|
287
|
+
const proc = spawn('bash', ['-c', command], {
|
|
288
|
+
cwd: projectDir,
|
|
289
|
+
stdio: 'inherit',
|
|
290
|
+
env: { ...process.env, CI: 'true' },
|
|
291
|
+
});
|
|
292
|
+
proc.on('close', code => code === 0 ? resolve() : reject(new Error(`Command failed with code ${code}`)));
|
|
293
|
+
proc.on('error', reject);
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
const result = await runInContainer({
|
|
298
|
+
projectDir,
|
|
299
|
+
command: `cd /home/runner/work && ${command}`,
|
|
300
|
+
timeout,
|
|
301
|
+
stream: true,
|
|
302
|
+
});
|
|
303
|
+
if (result.exitCode !== 0) {
|
|
304
|
+
throw new Error(`Command failed with exit code ${result.exitCode}`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
async function isSemgrepAvailable() {
|
|
309
|
+
try {
|
|
310
|
+
await execFileAsync('semgrep', ['--version'], { timeout: 3000 });
|
|
311
|
+
return true;
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
async function runSemgrep(projectDir) {
|
|
318
|
+
// Look for semgrep config in project or use auto
|
|
319
|
+
const semgrepConfig = await fs.pathExists(path.join(projectDir, '.semgrep.yml'))
|
|
320
|
+
? path.join(projectDir, '.semgrep.yml')
|
|
321
|
+
: 'auto';
|
|
322
|
+
await new Promise((resolve, reject) => {
|
|
323
|
+
const proc = spawn('semgrep', ['--config', semgrepConfig, '--severity', 'ERROR', '--quiet', '.'], {
|
|
324
|
+
cwd: projectDir,
|
|
325
|
+
stdio: 'inherit',
|
|
326
|
+
});
|
|
327
|
+
proc.on('close', code => code === 0 ? resolve() : reject(new Error(`Semgrep found issues (exit ${code})`)));
|
|
328
|
+
proc.on('error', reject);
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
async function runGhagga(projectDir) {
|
|
332
|
+
await new Promise((resolve, reject) => {
|
|
333
|
+
const proc = spawn('ghagga', ['review', '--plain', '--exit-on-issues'], {
|
|
334
|
+
cwd: projectDir,
|
|
335
|
+
stdio: 'inherit',
|
|
336
|
+
});
|
|
337
|
+
proc.on('close', code => code === 0 ? resolve() : reject(new Error(`GHAGGA review found issues (exit ${code})`)));
|
|
338
|
+
proc.on('error', reject);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
//# sourceMappingURL=ci.js.map
|
package/dist/commands/init.js
CHANGED
|
@@ -49,6 +49,11 @@ export async function initProject(options, onStep) {
|
|
|
49
49
|
// Set core.hooksPath to ci-local/hooks
|
|
50
50
|
const hooksDir = path.join(ciLocalDest, 'hooks');
|
|
51
51
|
if (await fs.pathExists(hooksDir)) {
|
|
52
|
+
// Ensure hooks are executable
|
|
53
|
+
const hookFiles = await fs.readdir(hooksDir);
|
|
54
|
+
for (const hook of hookFiles) {
|
|
55
|
+
await fs.chmod(path.join(hooksDir, hook), 0o755);
|
|
56
|
+
}
|
|
52
57
|
await execFileAsync('git', ['config', 'core.hooksPath', 'ci-local/hooks'], { cwd: projectDir });
|
|
53
58
|
}
|
|
54
59
|
}
|
package/dist/index.js
CHANGED
|
@@ -2,18 +2,26 @@
|
|
|
2
2
|
import React from 'react';
|
|
3
3
|
import { render } from 'ink';
|
|
4
4
|
import meow from 'meow';
|
|
5
|
+
import updateNotifier from 'update-notifier';
|
|
6
|
+
import { createRequire } from 'module';
|
|
5
7
|
import App from './ui/App.js';
|
|
6
8
|
import Doctor from './ui/Doctor.js';
|
|
7
9
|
import AnalyzeUI from './ui/AnalyzeUI.js';
|
|
8
10
|
import Plugin from './ui/Plugin.js';
|
|
9
11
|
import LlmsTxt from './ui/LlmsTxt.js';
|
|
12
|
+
import CI from './ui/CI.js';
|
|
10
13
|
import { CIProvider as CIContextProvider } from './ui/CIContext.js';
|
|
14
|
+
// Check for updates in background (non-blocking, cached 24h)
|
|
15
|
+
const _require = createRequire(import.meta.url);
|
|
16
|
+
const pkg = _require('../package.json');
|
|
17
|
+
updateNotifier({ pkg, updateCheckInterval: 1000 * 60 * 60 * 24 }).notify();
|
|
11
18
|
const cli = meow(`
|
|
12
19
|
Usage
|
|
13
20
|
$ javi-forge [command] [options]
|
|
14
21
|
|
|
15
22
|
Commands
|
|
16
23
|
init Bootstrap a new project (default)
|
|
24
|
+
ci Run CI simulation (lint + compile + test + security + ghagga)
|
|
17
25
|
analyze Run repoforge skills analysis
|
|
18
26
|
doctor Show health report
|
|
19
27
|
plugin add Install a plugin from GitHub (org/repo)
|
|
@@ -35,19 +43,28 @@ const cli = meow(`
|
|
|
35
43
|
--version Show version
|
|
36
44
|
--help Show this help
|
|
37
45
|
|
|
46
|
+
CI options (javi-forge ci)
|
|
47
|
+
--quick Lint + compile only (fast, for pre-commit)
|
|
48
|
+
--shell Open interactive shell in CI container
|
|
49
|
+
--detect Show detected stack and exit
|
|
50
|
+
--no-docker Run commands natively (no Docker)
|
|
51
|
+
--no-ghagga Skip GHAGGA review
|
|
52
|
+
--no-security Skip Semgrep security scan
|
|
53
|
+
--timeout N Per-step timeout in seconds (default: 600)
|
|
54
|
+
|
|
38
55
|
Examples
|
|
39
56
|
$ javi-forge
|
|
40
57
|
$ javi-forge init --dry-run
|
|
41
58
|
$ javi-forge init --stack node --ci github
|
|
42
|
-
$ javi-forge
|
|
59
|
+
$ javi-forge ci
|
|
60
|
+
$ javi-forge ci --quick
|
|
61
|
+
$ javi-forge ci --no-ghagga --no-security
|
|
62
|
+
$ javi-forge ci --no-docker
|
|
63
|
+
$ javi-forge ci --shell
|
|
43
64
|
$ javi-forge analyze
|
|
44
|
-
$ javi-forge analyze --dry-run
|
|
45
65
|
$ javi-forge doctor
|
|
46
66
|
$ javi-forge plugin add mapbox/agent-skills
|
|
47
67
|
$ javi-forge plugin list
|
|
48
|
-
$ javi-forge plugin search ai
|
|
49
|
-
$ javi-forge plugin validate ./my-plugin
|
|
50
|
-
$ javi-forge plugin remove my-plugin
|
|
51
68
|
`, {
|
|
52
69
|
importMeta: import.meta,
|
|
53
70
|
flags: {
|
|
@@ -59,6 +76,14 @@ const cli = meow(`
|
|
|
59
76
|
ghagga: { type: 'boolean', default: false },
|
|
60
77
|
mock: { type: 'boolean', default: false },
|
|
61
78
|
batch: { type: 'boolean', default: false },
|
|
79
|
+
// CI flags
|
|
80
|
+
quick: { type: 'boolean', default: false },
|
|
81
|
+
shell: { type: 'boolean', default: false },
|
|
82
|
+
detect: { type: 'boolean', default: false },
|
|
83
|
+
noDocker: { type: 'boolean', default: false },
|
|
84
|
+
noGhagga: { type: 'boolean', default: false },
|
|
85
|
+
noSecurity: { type: 'boolean', default: false },
|
|
86
|
+
timeout: { type: 'number', default: 600 },
|
|
62
87
|
}
|
|
63
88
|
});
|
|
64
89
|
const subcommand = cli.input[0] ?? 'init';
|
|
@@ -67,6 +92,15 @@ const VALID_CI = ['github', 'gitlab', 'woodpecker'];
|
|
|
67
92
|
const VALID_MEMORY = ['engram', 'obsidian-brain', 'memory-simple', 'none'];
|
|
68
93
|
const isCI = cli.flags.batch || process.env['CI'] === '1' || process.env['CI'] === 'true';
|
|
69
94
|
switch (subcommand) {
|
|
95
|
+
case 'ci': {
|
|
96
|
+
const ciMode = cli.flags.detect ? 'detect'
|
|
97
|
+
: cli.flags.shell ? 'shell'
|
|
98
|
+
: cli.flags.quick ? 'quick'
|
|
99
|
+
: 'full';
|
|
100
|
+
render(React.createElement(CIContextProvider, { isCI: true },
|
|
101
|
+
React.createElement(CI, { projectDir: process.cwd(), mode: ciMode, noDocker: cli.flags.noDocker, noGhagga: cli.flags.noGhagga, noSecurity: cli.flags.noSecurity, timeout: cli.flags.timeout })));
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
70
104
|
case 'doctor': {
|
|
71
105
|
render(React.createElement(CIContextProvider, { isCI: isCI },
|
|
72
106
|
React.createElement(Doctor, null)));
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Stack } from '../types/index.js';
|
|
2
|
+
export interface DockerRunOptions {
|
|
3
|
+
/** Absolute path to mount as /home/runner/work */
|
|
4
|
+
projectDir: string;
|
|
5
|
+
/** Command to run inside the container */
|
|
6
|
+
command: string;
|
|
7
|
+
/** Timeout in seconds (default: 600) */
|
|
8
|
+
timeout?: number;
|
|
9
|
+
/** Stream output to stdout/stderr (default: true) */
|
|
10
|
+
stream?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface DockerRunResult {
|
|
13
|
+
exitCode: number;
|
|
14
|
+
stdout: string;
|
|
15
|
+
stderr: string;
|
|
16
|
+
}
|
|
17
|
+
export interface DockerImageOptions {
|
|
18
|
+
stack: Stack;
|
|
19
|
+
/** Java version override (only for java-* stacks) */
|
|
20
|
+
javaVersion?: string;
|
|
21
|
+
/** Directory where Dockerfiles are stored (defaults to package-bundled dir) */
|
|
22
|
+
dockerfilesDir?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function getImageName(stack: Stack): string;
|
|
25
|
+
export declare function getDockerfileContent(stack: Stack): string;
|
|
26
|
+
export declare function isDockerAvailable(): Promise<boolean>;
|
|
27
|
+
/**
|
|
28
|
+
* Ensure a CI Docker image exists and is up-to-date.
|
|
29
|
+
* Rebuilds only if the Dockerfile content has changed (hash-based staleness check).
|
|
30
|
+
* Returns the image name.
|
|
31
|
+
*/
|
|
32
|
+
export declare function ensureImage(options: DockerImageOptions): Promise<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Run a shell command inside the CI Docker container.
|
|
35
|
+
* Mounts projectDir as /home/runner/work.
|
|
36
|
+
* Streams output to process.stdout/stderr by default.
|
|
37
|
+
*/
|
|
38
|
+
export declare function runInContainer(options: DockerRunOptions): Promise<DockerRunResult>;
|
|
39
|
+
/**
|
|
40
|
+
* Open an interactive shell inside the CI container.
|
|
41
|
+
*/
|
|
42
|
+
export declare function openShell(projectDir: string): Promise<void>;
|
|
43
|
+
//# sourceMappingURL=docker.d.ts.map
|