mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-20 00:11:37 +00:00
* feat(cli): add .gitnexusrc config and --default-branch for analyze (#243) Let a repo preconfigure recurring `gitnexus analyze` options via a project-local `.gitnexusrc` (JSON) plus a new `--default-branch` flag, so projects on `develop`/`master` no longer get the generated regression example rewritten to `base_ref: "main"` on every analyze run. - New `cli/analyze-config.ts`: locate/parse/validate `.gitnexusrc` (flat + nested `analyze` form, alias mapping, fail-closed on unknown keys / bad types / hidden chars), merge with CLI (CLI overrides config), and resolve the default branch (CLI > config defaultBranch/branch > auto-detected origin/HEAD > "main"). - `getDefaultBranch()` in storage/git.ts (best-effort, local-only, no network). - Thread `defaultBranch` through analyze -> run-analyze -> ai-context so the generated regression-compare example uses the configured branch, JSON-escaped; the --skills re-generation path uses the same branch. - `skipContextFiles`/`skipAiContext` alias `skipAgentsMd` (block only, does not imply skipSkills); `indexOnly` stays the stronger "skip all injection". - README + CLI help; unit tests for the config module and end-to-end wiring tests that fail if config is parsed but not threaded into analyze/context. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cli): harden .gitnexusrc against Markdown injection and stale base_ref (#243) Addresses the tri-review findings on PR #1996. - P1 (Markdown injection into generated AGENTS.md/CLAUDE.md): reject the backtick in validateBranchName (covers --default-branch, .gitnexusrc, and the origin/HEAD auto-detect via sanitizeDetectedBranch) and strip it at the ai-context sink (markdownSafeBranch); reject Markdown-significant chars (` * [ ] < >) in the config `name` (it lands in generated bold/code-spans), while still allowing `_ . - /`. Corrected the false "can't break the code span" comment. - P2 (configured defaultBranch silently no-ops on an up-to-date repo): on the alreadyUpToDate fast path, surgically refresh only the `base_ref:` line in AGENTS.md/CLAUDE.md (refreshBaseRefLine), preserving the rest of the block incl. --skills community rows; no-op when unchanged. - P3: gate the .gitnexusrc key lookup with Object.hasOwn so inherited keys (__proto__, constructor, …) hit the actionable "Unknown key" error. - Cleanups: strip a leading UTF-8 BOM before JSON.parse; give --default-branch CLI validation its own `default-branch-invalid` recovery hint; drop the dead `options.defaultBranch` write and the now-redundant `options?.` chaining. - Tests: backtick rejection + even-backtick generated output, 255-char branch bound, config `name` Markdown rejection, __proto__ → Unknown key, BOM, mergeAnalyzeOptions omits defaultBranch, willGenerateContext suppression, and the fast-path base_ref refresh. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
129 lines
4.4 KiB
TypeScript
129 lines
4.4 KiB
TypeScript
/**
|
|
* CLI message helpers — for user-facing banners, error guidance, and
|
|
* recovery hints emitted by `gitnexus` subcommands.
|
|
*
|
|
* These functions write **plain text** directly to `process.stderr` AND
|
|
* tee a structured pino record through the singleton `logger`. Plain text
|
|
* preserves the human-readable contract for users running `gitnexus`
|
|
* interactively, redirecting to a file, or piping to `cat`/`grep`. The
|
|
* structured tee keeps log aggregators happy.
|
|
*
|
|
* **Use these for:**
|
|
* - User-facing banners ("Server listening on http://...:N")
|
|
* - Validation errors ("--worker-timeout must be at least 1 second")
|
|
* - Recovery hints ("Suggestions: 1. Clear the npm cache, 2. ...")
|
|
* - One-line user notices ("No indexed repositories found.")
|
|
*
|
|
* **Do NOT use these for:**
|
|
* - Internal diagnostics (worker progress, retry counts, telemetry)
|
|
* — use `logger.info`/`warn`/`error` directly. Internal logs only
|
|
* need structured fields, not double-output to stderr.
|
|
* - High-volume hot paths — every `cliMessage` call writes twice (raw
|
|
* + structured). Acceptable for user-facing messages, wasteful for
|
|
* ingestion pipeline events.
|
|
*
|
|
* Design note: stderr is the right channel even for non-error messages
|
|
* because GitNexus CLI tools (`query`, `cypher`, `impact`) emit JSON
|
|
* data on stdout for piping (`gitnexus query | jq`). User banners on
|
|
* stdout would corrupt that pipeline.
|
|
*/
|
|
import { logger } from '../core/logger.js';
|
|
import { t, type CliMessageKey, type CliMessageVars } from './i18n/index.js';
|
|
|
|
/**
|
|
* String-literal union of all `recoveryHint` tags emitted by the CLI.
|
|
*
|
|
* Centralized so a new recovery branch added in `analyze.ts` cannot land
|
|
* without updating this union — TypeScript will reject the unknown literal
|
|
* passed via `cliError({ recoveryHint: '...' })`. To add a new hint:
|
|
* 1. Add the tag string to this union.
|
|
* 2. Pass it as the `recoveryHint` field at the relevant `cliError`
|
|
* call site.
|
|
*
|
|
* Consumers can import this type to narrow log-record `recoveryHint`
|
|
* fields without restating the literal list.
|
|
*/
|
|
export type RecoveryHint =
|
|
| 'wal-corruption'
|
|
| 'wal-checkpoint-threshold'
|
|
| 'heap-oom-respawn'
|
|
| 'native-worker-abort'
|
|
| 'hf-endpoint-unreachable'
|
|
| 'local-embedding-unsupported'
|
|
| 'large-repo'
|
|
| 'npm-resolution'
|
|
| 'module-not-found'
|
|
| 'gitnexusrc-invalid'
|
|
| 'default-branch-invalid';
|
|
|
|
/**
|
|
* Common shape for the optional structured-field bag passed to
|
|
* `cliError`/`cliWarn`/`cliInfo`. Typed so the `recoveryHint` slot is
|
|
* checked against the {@link RecoveryHint} union.
|
|
*/
|
|
export interface CliMessageFields extends Record<string, unknown> {
|
|
recoveryHint?: RecoveryHint;
|
|
}
|
|
|
|
function writeStderr(msg: string): void {
|
|
// Direct write — bypassing `console.*` so it cannot be intercepted by
|
|
// progress-bar redirection (see `cli/analyze.ts:barLog`) or other
|
|
// routing. The structured tee below still goes through the logger so
|
|
// log aggregation works either way.
|
|
process.stderr.write(msg.endsWith('\n') ? msg : msg + '\n');
|
|
}
|
|
|
|
/**
|
|
* User-facing informational message. Use for banners, listening URLs,
|
|
* and any message the user expects to read in plain text.
|
|
*/
|
|
export function cliInfo(msg: string, fields?: CliMessageFields): void {
|
|
writeStderr(msg);
|
|
logger.info(fields ?? {}, msg);
|
|
}
|
|
|
|
/**
|
|
* Key-based informational message. Keeps the legacy string API intact while
|
|
* allowing commands to opt into localized user-facing stderr output.
|
|
*/
|
|
export function cliInfoKey(
|
|
key: CliMessageKey,
|
|
vars?: CliMessageVars,
|
|
fields?: Record<string, unknown>,
|
|
): void {
|
|
cliInfo(t(key, vars), fields);
|
|
}
|
|
|
|
/**
|
|
* User-facing warning. Operator-actionable but non-fatal — `cliWarn`
|
|
* indicates the command can still proceed in some form.
|
|
*/
|
|
export function cliWarn(msg: string, fields?: CliMessageFields): void {
|
|
writeStderr(msg);
|
|
logger.warn(fields ?? {}, msg);
|
|
}
|
|
|
|
export function cliWarnKey(
|
|
key: CliMessageKey,
|
|
vars?: CliMessageVars,
|
|
fields?: Record<string, unknown>,
|
|
): void {
|
|
cliWarn(t(key, vars), fields);
|
|
}
|
|
|
|
/**
|
|
* User-facing error. Indicates the command cannot proceed; usually
|
|
* paired with a non-zero exit code at the call site.
|
|
*/
|
|
export function cliError(msg: string, fields?: CliMessageFields): void {
|
|
writeStderr(msg);
|
|
logger.error(fields ?? {}, msg);
|
|
}
|
|
|
|
export function cliErrorKey(
|
|
key: CliMessageKey,
|
|
vars?: CliMessageVars,
|
|
fields?: Record<string, unknown>,
|
|
): void {
|
|
cliError(t(key, vars), fields);
|
|
}
|