fix(ci-setup): resolve all PR CI failures and correctness bugs

- console.error → console.log (ESLint no-console)
- Add i18n keys for ci-setup command + all 9 options in en.ts, zh-CN.ts,
  help-i18n.ts; add ['ci-setup'] to allHelpCommands in cli-index-help test
- Container port hardcoded to 4747 (image CMD is not overridable); --port
  now controls host binding only; fixes dead deployments with non-4747 ports
- Guard non-TTY overwrite prompt with process.stdin.isTTY check
- Add port range validation (1–65534) with clear error message
- Fix ACA --target-port to 4747 (container port, not host)
- Add permissions/concurrency/timeout-minutes to generated GHA workflow
- Fix GITNEXUS.md false claim about skills being auto-committed
- Strip // comments from MCP snippet (invalid JSON); use _comment/_note keys
- All 48 unit tests passing; prettier check clean

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
MCKRUZ 2026-06-09 12:54:31 -04:00
parent 084510f488
commit 8bc4ce1859
6 changed files with 78 additions and 28 deletions

View file

@ -35,9 +35,7 @@ export const ciSetupCommand = async (options?: {
const detect = await detectEnvironment(cwd);
if (!detect.gitRoot) {
console.error(
'✗ Not a git repository. Run `gitnexus ci-setup` from inside a git repo root.',
);
console.log('✗ Not a git repository. Run `gitnexus ci-setup` from inside a git repo root.');
process.exit(1);
}
@ -51,14 +49,23 @@ export const ciSetupCommand = async (options?: {
if (detect.hasDocker) {
console.log(' ✓ Docker: docker-compose or Dockerfile found');
}
console.log(` ${detect.portAvailable ? '✓' : '⚠'} Port 4747: ${detect.portAvailable ? 'available' : 'in use (serve may already be running)'}`);
console.log(
` ${detect.portAvailable ? '✓' : '⚠'} Port 4747: ${detect.portAvailable ? 'available' : 'in use (serve may already be running)'}`,
);
console.log(' ⚠ License: PolyForm-Noncommercial — confirm non-commercial use\n');
// Parse and validate options from commander flags
const partial: Partial<CiSetupOptions> = {};
if (options?.ci) partial.ci = options.ci as CiSystem;
if (options?.deploy) partial.deploy = options.deploy as DeployTarget;
if (options?.port) partial.port = parseInt(options.port, 10);
if (options?.port) {
const portNum = parseInt(options.port as string, 10);
if (isNaN(portNum) || portNum < 1 || portNum > 65534) {
console.log(`✗ Invalid port: "${options.port}". Must be an integer between 1 and 65534.`);
process.exit(1);
}
partial.port = portNum;
}
if (options?.auth) partial.auth = options.auth as AuthMode;
if (options?.branchStrategy) partial.branchStrategy = options.branchStrategy as BranchStrategy;
if (options?.dryRun !== undefined) partial.dryRun = options.dryRun;
@ -120,13 +127,15 @@ async function applyFiles(
if (existingContent !== null && !opts.yes) {
console.log(`\n⚠ ${file.relativePath} already exists and differs.`);
if (!opts.yes) {
const { confirm } = await import('@inquirer/prompts');
const ok = await confirm({ message: `Overwrite ${file.relativePath}?`, default: false });
if (!ok) {
result.skipped.push(`${file.relativePath} (skipped by user)`);
continue;
}
if (!process.stdin.isTTY) {
result.skipped.push(`${file.relativePath} (differs, non-TTY — use --yes to overwrite)`);
continue;
}
const { confirm } = await import('@inquirer/prompts');
const ok = await confirm({ message: `Overwrite ${file.relativePath}?`, default: false });
if (!ok) {
result.skipped.push(`${file.relativePath} (skipped by user)`);
continue;
}
}
@ -175,12 +184,16 @@ function printResult(result: CiSetupResult, opts: CiSetupOptions): void {
}
if (opts.ci === 'github-actions' || opts.ci === 'both') {
console.log(` ${step++}. Commit .github/workflows/gitnexus-ci.yml and push to trigger the first index.`);
console.log(
` ${step++}. Commit .github/workflows/gitnexus-ci.yml and push to trigger the first index.`,
);
}
if (opts.ci === 'azure-devops' || opts.ci === 'both') {
console.log(` ${step++}. Import azure-pipelines-gitnexus.yml into Azure DevOps and run it.`);
}
console.log(` ${step++}. Follow GITNEXUS.md to connect Claude Code / Cursor to the shared server.`);
console.log(
` ${step++}. Follow GITNEXUS.md to connect Claude Code / Cursor to the shared server.`,
);
console.log('');
}

View file

@ -24,10 +24,18 @@ name: GitNexus Index
${onBlock}
permissions:
contents: read
concurrency:
group: gitnexus-$\{{ github.ref }}
cancel-in-progress: true
jobs:
gitnexus-index:
name: Index repository
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
@ -136,7 +144,7 @@ services:
GITNEXUS_HOME: /data/gitnexus
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:${opts.port}/api/health"]
test: ["CMD", "curl", "-f", "http://localhost:4747/api/health"]
interval: 30s
timeout: 5s
retries: 3
@ -172,7 +180,7 @@ services:
gitnexus:
image: ghcr.io/abhigyanpatwari/gitnexus:latest
ports:
- "\${GITNEXUS_PORT:-${opts.port}}:${opts.port}"
- "\${GITNEXUS_PORT:-${opts.port}}:4747"
volumes:
- gitnexus-data:/data/gitnexus
- \${WORKSPACE_DIR:-./workspace}:/workspace:ro
@ -180,7 +188,7 @@ services:
GITNEXUS_HOME: /data/gitnexus
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:${opts.port}/api/health"]
test: ["CMD", "curl", "-f", "http://localhost:4747/api/health"]
interval: 30s
timeout: 5s
retries: 3
@ -201,7 +209,7 @@ function buildCaddyfile(opts: CiSetupOptions): string {
@authorized header Authorization "Bearer {env.GITNEXUS_TOKEN}"
handle @authorized {
reverse_proxy gitnexus:${opts.port}
reverse_proxy gitnexus:4747
}
respond "Unauthorized" 401
@ -277,7 +285,7 @@ az containerapp create \\
--resource-group "\$RESOURCE_GROUP" \\
--environment "\$ENVIRONMENT" \\
--image ghcr.io/abhigyanpatwari/gitnexus:latest \\
--target-port ${opts.port} \\
--target-port 4747 \\
--ingress internal \\
--env-vars "GITNEXUS_HOME=/data/gitnexus" \\
--volume-name gitnexus-data \\
@ -308,15 +316,10 @@ function buildMcpSnippet(opts: CiSetupOptions): string {
}`
: '';
const commentLine = urlComment.replace(/^\s*\/\/\s*/, '').trim();
return `{
// GitNexus shared MCP server snippet — merge into ~/.claude/settings.json mcpServers block.
// Do NOT auto-apply: use 'gitnexus setup' for personal stdio MCP; this snippet is for the
// shared HTTP server variant.
//
// NOTE: Verify the exact Claude Code HTTP MCP entry format against current docs
// (https://github.com/anthropics/claude-code) before applying — field names may evolve.
//
${urlComment}
"_comment": "${commentLine}",
"_note": "Merge into ~/.claude/settings.json mcpServers block. Verify the exact Claude Code HTTP MCP entry format against current docs before applying.",
"mcpServers": {
"gitnexus": {
"type": "http",
@ -449,7 +452,7 @@ When the CI workflow runs \`analyze --skills\`, GitNexus generates repo-specific
- \`.claude/skills/gitnexus/\` — standard GitNexus MCP skills (query, impact, context, detect-changes)
- \`.claude/skills/generated/\` — community-detected area skills (one file per functional cluster)
These are committed to the repository so every developer's Claude Code session has them available automatically.
These are written into the repository by the CI workflow. To share them with your team, add a \`git add .claude/skills/ && git commit\` step after \`analyze --skills\` in your workflow, or commit them manually after the first run.
---

View file

@ -12,6 +12,7 @@ const TITLE_KEYS = {
const COMMAND_DESCRIPTION_KEYS = {
'': 'help.description.root',
setup: 'help.command.setup.description',
'ci-setup': 'help.command.ciSetup.description',
analyze: 'help.command.analyze.description',
index: 'help.command.index.description',
serve: 'help.command.serve.description',
@ -44,6 +45,15 @@ const COMMAND_DESCRIPTION_KEYS = {
const OPTION_DESCRIPTION_KEYS = {
'|-V, --version': 'help.option.version',
'ci-setup|--ci <system>': 'help.option.ciSetup.ci',
'ci-setup|--deploy <target>': 'help.option.ciSetup.deploy',
'ci-setup|--port <port>': 'help.option.ciSetup.port',
'ci-setup|--auth <mode>': 'help.option.ciSetup.auth',
'ci-setup|--branch-strategy <strategy>': 'help.option.ciSetup.branchStrategy',
'ci-setup|--dry-run': 'help.option.ciSetup.dryRun',
'ci-setup|--apply': 'help.option.ciSetup.apply',
'ci-setup|--yes': 'help.option.ciSetup.yes',
'ci-setup|--output-dir <path>': 'help.option.ciSetup.outputDir',
'analyze|-f, --force': 'help.option.analyze.force',
'analyze|--repair-fts': 'help.option.analyze.repairFts',
'analyze|--embeddings [limit]': 'help.option.analyze.embeddings',

View file

@ -106,6 +106,8 @@ export const en = {
'help.option.version': 'output the version number',
'help.command.setup.description':
'One-time setup: configure MCP for Cursor, Claude Code, OpenCode, Codex',
'help.command.ciSetup.description':
'Generate CI/CD workflows, Docker Compose, and MCP config for a shared team GitNexus server',
'help.command.analyze.description': 'Index a repository (full analysis)',
'help.command.index.description':
'Register an existing .gitnexus/ folder into the global registry (no re-analysis needed)',
@ -146,6 +148,16 @@ export const en = {
'Cross-repo impact for a symbol in one member repo of a group',
'help.command.group.query.description': 'Search execution flows across all repos in a group',
'help.command.group.contracts.description': 'Inspect Contract Registry',
'help.option.ciSetup.ci': 'CI/CD system: github-actions, azure-devops, or both',
'help.option.ciSetup.deploy': 'Deploy target: docker, azure-container-app, or both',
'help.option.ciSetup.port': 'Host port to bind (container always runs on 4747)',
'help.option.ciSetup.auth': 'Auth mode: token (Caddy proxy) or none',
'help.option.ciSetup.branchStrategy': 'Index strategy: pr-scoped or main-only',
'help.option.ciSetup.dryRun':
'Print generated files without writing (default when no mode flag given)',
'help.option.ciSetup.apply': 'Write files with per-file confirmation gates',
'help.option.ciSetup.yes': 'Skip per-file confirmation prompts (use with --apply)',
'help.option.ciSetup.outputDir': 'Directory to write generated files (default: git root)',
'help.option.analyze.force': 'Force full re-index even if up to date',
'help.option.analyze.repairFts': 'Repair/rebuild search FTS indexes without full re-analysis',
'help.option.analyze.embeddings':

View file

@ -108,6 +108,8 @@ export const zhCN = {
'help.option.help': '显示命令帮助',
'help.option.version': '输出版本号',
'help.command.setup.description': '一次性设置:为 Cursor、Claude Code、OpenCode、Codex 配置 MCP',
'help.command.ciSetup.description':
'为团队共享 GitNexus 服务器生成 CI/CD 工作流、Docker Compose 和 MCP 配置',
'help.command.analyze.description': '索引仓库(完整分析)',
'help.command.index.description': '将现有 .gitnexus/ 文件夹注册到全局注册表(无需重新分析)',
'help.command.serve.description': '启动供 Web UI 连接的本地 HTTP 服务器',
@ -139,6 +141,15 @@ export const zhCN = {
'help.command.group.impact.description': '分析仓库组中某个成员仓库符号的跨仓库影响',
'help.command.group.query.description': '跨仓库组所有仓库搜索执行流程',
'help.command.group.contracts.description': '查看 Contract Registry',
'help.option.ciSetup.ci': 'CI/CD 系统:github-actions、azure-devops 或 both',
'help.option.ciSetup.deploy': '部署目标:docker、azure-container-app 或 both',
'help.option.ciSetup.port': '绑定的主机端口(容器始终运行在 4747)',
'help.option.ciSetup.auth': '认证模式:token(Caddy 代理)或 none',
'help.option.ciSetup.branchStrategy': '索引策略:pr-scoped 或 main-only',
'help.option.ciSetup.dryRun': '打印生成的文件而不写入(未指定模式标志时的默认行为)',
'help.option.ciSetup.apply': '写入文件,每个文件有独立确认步骤',
'help.option.ciSetup.yes': '跳过逐文件确认提示(与 --apply 配合使用)',
'help.option.ciSetup.outputDir': '生成文件的写入目录(默认:git 根目录)',
'help.option.analyze.force': '即使已是最新也强制完整重建索引',
'help.option.analyze.repairFts': '修复/重建搜索 FTS 索引,不执行完整重新分析',
'help.option.analyze.embeddings':

View file

@ -31,6 +31,7 @@ function runRootHelp(env: NodeJS.ProcessEnv = {}) {
const allHelpCommands = [
[],
['setup'],
['ci-setup'],
['analyze'],
['index'],
['serve'],