@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.
- package/dist/bin.js +114 -13
- 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: "
|
|
3989
|
-
{ value: "
|
|
3990
|
-
{ value: "
|
|
3991
|
-
{ value: "ollama", label: "Ollama", hint: "Local \u2014 no API costs" },
|
|
3992
|
-
{ value: "
|
|
3993
|
-
{ value: "
|
|
3994
|
-
{ value: "
|
|
3995
|
-
{ value: "
|
|
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: "
|
|
4128
|
-
next:
|
|
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
|
|
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.
|
|
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": {
|