@chrisdudek/yg 5.4.0 → 5.4.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.
Files changed (2) hide show
  1. package/dist/bin.js +114 -13
  2. package/package.json +1 -1
package/dist/bin.js CHANGED
@@ -17,6 +17,17 @@ var DEFAULT_CONFIG = `version: "5.1.0"
17
17
  quality:
18
18
  max_direct_relations: 10
19
19
 
20
+ # Coverage \u2014 which files must belong to a node.
21
+ # Fresh projects start in "require nothing" mode: every unmapped file surfaces as
22
+ # a NON-blocking warning (not a blocking error), so your very first \`yg check\` is
23
+ # green while you adopt incrementally. Add a path prefix to \`required\` (e.g.
24
+ # "src/") to make that area blocking once you start mapping it. NOTE: an ABSENT
25
+ # coverage block defaults to requiring the WHOLE repo \u2014 this explicit empty list
26
+ # is what opts a fresh project into require-nothing.
27
+ coverage:
28
+ required: []
29
+ excluded: []
30
+
20
31
  # Reviewer configuration added by: yg init
21
32
  # (see yg schemas read config + yg knowledge read configuration)
22
33
 
@@ -3890,6 +3901,13 @@ async function ensureYggdrasilGitignore(yggRoot) {
3890
3901
  }
3891
3902
  var API_PROVIDERS = ["anthropic", "openai", "google", "openai-compatible", "ollama"];
3892
3903
  var CLI_PROVIDERS = ["claude-code", "codex", "gemini-cli"];
3904
+ var ALL_PROVIDERS = [...CLI_PROVIDERS, ...API_PROVIDERS];
3905
+ var API_KEY_ENV = {
3906
+ anthropic: "ANTHROPIC_API_KEY",
3907
+ openai: "OPENAI_API_KEY",
3908
+ google: "GOOGLE_API_KEY",
3909
+ "openai-compatible": "OPENAI_API_KEY"
3910
+ };
3893
3911
  var CLAUDE_CODE_ALIASES = [
3894
3912
  { value: "haiku", label: "haiku" },
3895
3913
  { value: "sonnet", label: "sonnet" },
@@ -3984,15 +4002,16 @@ async function promptModelText(provider) {
3984
4002
  async function runReviewerConfigFlow() {
3985
4003
  const provider = await p.select({
3986
4004
  message: "Which provider should verify your code?",
4005
+ initialValue: "claude-code",
3987
4006
  options: [
3988
- { value: "anthropic", label: "Anthropic", hint: "API \u2014 Claude models" },
3989
- { value: "openai", label: "OpenAI", hint: "API \u2014 GPT models" },
3990
- { value: "google", label: "Google", hint: "API \u2014 Gemini models" },
3991
- { value: "ollama", label: "Ollama", hint: "Local \u2014 no API costs" },
3992
- { value: "openai-compatible", label: "OpenAI-compatible", hint: "API \u2014 custom endpoint" },
3993
- { value: "claude-code", label: "Claude Code", hint: "CLI \u2014 uses installed claude" },
3994
- { value: "codex", label: "Codex", hint: "CLI \u2014 uses installed codex" },
3995
- { value: "gemini-cli", label: "Gemini CLI", hint: "CLI \u2014 uses installed gemini" }
4007
+ { value: "claude-code", label: "Claude Code", hint: "CLI \u2014 free, no API key; uses installed claude" },
4008
+ { value: "codex", label: "Codex", hint: "CLI \u2014 free, no API key; uses installed codex" },
4009
+ { value: "gemini-cli", label: "Gemini CLI", hint: "CLI \u2014 free, no API key; uses installed gemini" },
4010
+ { value: "ollama", label: "Ollama", hint: "Local \u2014 no API costs; needs a local install" },
4011
+ { value: "anthropic", label: "Anthropic", hint: "API key \u2014 Claude models" },
4012
+ { value: "openai", label: "OpenAI", hint: "API key \u2014 GPT models" },
4013
+ { value: "google", label: "Google", hint: "API key \u2014 Gemini models" },
4014
+ { value: "openai-compatible", label: "OpenAI-compatible", hint: "API key \u2014 custom endpoint" }
3996
4015
  ]
3997
4016
  });
3998
4017
  assertNotCancelled(provider);
@@ -4123,9 +4142,9 @@ async function freshInit(projectRoot) {
4123
4142
  const yggRoot = path14.join(projectRoot, ".yggdrasil");
4124
4143
  if (!isTTY()) {
4125
4144
  process.stderr.write(chalk2.red(`Error: ${buildIssueMessage({
4126
- what: "yg init requires an interactive terminal.",
4127
- why: "Setup requires interactive prompts to configure platform and reviewer.",
4128
- next: "Run yg init in an interactive terminal session."
4145
+ what: "yg init requires an interactive terminal (no --provider given).",
4146
+ why: "The interactive wizard needs prompts to configure platform and reviewer. Docker, devcontainer, and CI runs have no TTY.",
4147
+ next: `Run yg init in an interactive terminal, OR bootstrap non-interactively: yg init --platform <name> --provider <name> [--model <m>] [--endpoint <url>]. Supported providers: ${ALL_PROVIDERS.join(", ")}.`
4129
4148
  })}
4130
4149
  `));
4131
4150
  process.exit(1);
@@ -4139,7 +4158,7 @@ async function freshInit(projectRoot) {
4139
4158
  const platform = await promptPlatform();
4140
4159
  p.log.step("Step 2: Reviewer provider");
4141
4160
  p.log.info(
4142
- "The reviewer checks your source code against aspect rules during yg check --approve.\n API providers make HTTP calls. CLI providers delegate to an installed agent.\n For local review without API costs, use Ollama."
4161
+ "The reviewer checks your source code against aspect rules during yg check --approve.\n If you already run an agent CLI (Claude Code, Codex, Gemini), pick it \u2014 the\n reviewer then needs no API key and adds no separate API bill. Ollama runs\n locally with no API cost. API providers (Anthropic, OpenAI, Google) need a key."
4143
4162
  );
4144
4163
  const reviewerConfig = await runReviewerConfigFlow();
4145
4164
  await createYggdrasilStructure(projectRoot, yggRoot, platform);
@@ -4159,6 +4178,54 @@ async function createYggdrasilStructure(projectRoot, yggRoot, platform) {
4159
4178
  await ensureYggdrasilGitignore(yggRoot);
4160
4179
  await installRulesForPlatform(projectRoot, platform);
4161
4180
  }
4181
+ async function freshInitNonInteractive(projectRoot, yggRoot, opts) {
4182
+ const { platform, provider } = opts;
4183
+ const model = opts.model?.trim();
4184
+ if (!model) {
4185
+ process.stderr.write(chalk2.red(`Error: ${buildIssueMessage({
4186
+ what: "--model is required for non-interactive init.",
4187
+ why: "Non-interactive init records the reviewer model verbatim and applies no default \u2014 the model must be named explicitly.",
4188
+ next: `Re-run naming a model: yg init --platform ${platform} --provider ${provider} --model <name>.`
4189
+ })}
4190
+ `));
4191
+ process.exit(1);
4192
+ }
4193
+ let endpoint = opts.endpoint?.trim() || void 0;
4194
+ if (needsEndpoint(provider) && !endpoint) {
4195
+ if (provider === "ollama") {
4196
+ endpoint = "http://localhost:11434";
4197
+ } else {
4198
+ process.stderr.write(chalk2.red(`Error: ${buildIssueMessage({
4199
+ what: `--endpoint is required for provider '${provider}'.`,
4200
+ why: "An OpenAI-compatible provider has no default base URL \u2014 the reviewer needs an endpoint to call.",
4201
+ next: `Re-run naming an endpoint: yg init --platform ${platform} --provider ${provider} --model ${model} --endpoint <url>.`
4202
+ })}
4203
+ `));
4204
+ process.exit(1);
4205
+ }
4206
+ }
4207
+ await createYggdrasilStructure(projectRoot, yggRoot, platform);
4208
+ await writeReviewerConfig(yggRoot, { provider, model, endpoint });
4209
+ if (needsApiKey(provider)) {
4210
+ const envVar = API_KEY_ENV[provider];
4211
+ const apiKey = (envVar ? process.env[envVar] : void 0)?.trim();
4212
+ if (apiKey) {
4213
+ await writeSecretsFile(yggRoot, apiKey);
4214
+ } else {
4215
+ process.stdout.write(chalk2.yellow(`${buildIssueMessage({
4216
+ what: `No API key found${envVar ? ` in $${envVar}` : ""}; wrote the config without one.`,
4217
+ why: "An API provider needs a key before the reviewer can run; init records the config anyway so setup is not blocked.",
4218
+ next: `Set ${envVar ?? "the provider API key environment variable"} (or add the key to .yggdrasil/yg-secrets.yaml) before running yg check --approve.`
4219
+ })}
4220
+ `));
4221
+ }
4222
+ }
4223
+ await ensureGitattributes(projectRoot);
4224
+ process.stdout.write(chalk2.green(
4225
+ `Yggdrasil initialized (platform: ${platform}, provider: ${provider}, model: ${model}). Run yg check to get started.
4226
+ `
4227
+ ));
4228
+ }
4162
4229
  async function runVersionUpgrade2(projectRoot, yggRoot, platform) {
4163
4230
  const { migrationActions, migrationWarnings, withheld } = await runVersionUpgrade({
4164
4231
  yggRoot,
@@ -4249,7 +4316,7 @@ async function existingInit(projectRoot) {
4249
4316
  }
4250
4317
  }
4251
4318
  function registerInitCommand(program2) {
4252
- program2.command("init").description("Initialize Yggdrasil graph in current project").option("--upgrade", "Non-interactive: refresh rules").option("--platform <name>", `Platform for rules file (${PLATFORMS.join(", ")})`).action(async (options) => {
4319
+ program2.command("init").description("Initialize Yggdrasil graph in current project").option("--upgrade", "Non-interactive: refresh rules").option("--platform <name>", `Platform for rules file (${PLATFORMS.join(", ")})`).option("--provider <name>", `Non-interactive fresh init: reviewer provider (${ALL_PROVIDERS.join(", ")})`).option("--model <name>", "Non-interactive fresh init: reviewer model (required)").option("--endpoint <url>", "Non-interactive fresh init: reviewer endpoint (ollama / openai-compatible)").action(async (options) => {
4253
4320
  try {
4254
4321
  const projectRoot = process.cwd();
4255
4322
  const yggRoot = path14.join(projectRoot, ".yggdrasil");
@@ -4359,6 +4426,40 @@ function registerInitCommand(program2) {
4359
4426
  }
4360
4427
  if (exists) {
4361
4428
  await existingInit(projectRoot);
4429
+ } else if (options.provider) {
4430
+ if (!options.platform) {
4431
+ process.stderr.write(chalk2.red(`Error: ${buildIssueMessage({
4432
+ what: "--provider requires --platform for non-interactive init.",
4433
+ why: "A fresh graph must install the rules file for a specific agent platform; there is no prompt to ask in non-interactive mode.",
4434
+ next: `Pass --platform <name>. Supported: ${PLATFORMS.join(", ")}.`
4435
+ })}
4436
+ `));
4437
+ process.exit(1);
4438
+ }
4439
+ if (!PLATFORMS.includes(options.platform)) {
4440
+ process.stderr.write(chalk2.red(`Error: ${buildIssueMessage({
4441
+ what: `Unknown platform '${options.platform}'.`,
4442
+ why: "The --platform value must match one of the supported agent platforms.",
4443
+ next: `Use one of: ${PLATFORMS.join(", ")}`
4444
+ })}
4445
+ `));
4446
+ process.exit(1);
4447
+ }
4448
+ if (!ALL_PROVIDERS.includes(options.provider)) {
4449
+ process.stderr.write(chalk2.red(`Error: ${buildIssueMessage({
4450
+ what: `Unknown provider '${options.provider}'.`,
4451
+ why: "The --provider value must match one of the supported reviewer providers.",
4452
+ next: `Use one of: ${ALL_PROVIDERS.join(", ")}`
4453
+ })}
4454
+ `));
4455
+ process.exit(1);
4456
+ }
4457
+ await freshInitNonInteractive(projectRoot, yggRoot, {
4458
+ platform: options.platform,
4459
+ provider: options.provider,
4460
+ model: options.model,
4461
+ endpoint: options.endpoint
4462
+ });
4362
4463
  } else {
4363
4464
  await freshInit(projectRoot);
4364
4465
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrisdudek/yg",
3
- "version": "5.4.0",
3
+ "version": "5.4.1",
4
4
  "description": "Architecture rules your AI coding agent can't ignore. It gets the rules for a file before it edits, and every change is checked — by a free local script or an LLM reviewer — before it moves on. Works with Claude Code, Cursor, Copilot, Codex, Cline.",
5
5
  "type": "module",
6
6
  "bin": {