@gluecharm-lab/easyspecs-cli 0.0.10 → 0.0.11

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/main.cjs CHANGED
@@ -2251,8 +2251,8 @@ var require_resolve = __commonJS({
2251
2251
  }
2252
2252
  return count;
2253
2253
  }
2254
- function getFullPath(resolver, id = "", normalize4) {
2255
- if (normalize4 !== false)
2254
+ function getFullPath(resolver, id = "", normalize5) {
2255
+ if (normalize5 !== false)
2256
2256
  id = normalizeId(id);
2257
2257
  const p = resolver.parse(id);
2258
2258
  return _getFullPath(resolver, p);
@@ -3592,7 +3592,7 @@ var require_fast_uri = __commonJS({
3592
3592
  "use strict";
3593
3593
  var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizeComponentEncoding, isIPv4, nonSimpleDomain } = require_utils();
3594
3594
  var { SCHEMES, getSchemeHandler } = require_schemes();
3595
- function normalize4(uri, options) {
3595
+ function normalize5(uri, options) {
3596
3596
  if (typeof uri === "string") {
3597
3597
  uri = /** @type {T} */
3598
3598
  serialize(parse(uri, options), options);
@@ -3828,7 +3828,7 @@ var require_fast_uri = __commonJS({
3828
3828
  }
3829
3829
  var fastUri = {
3830
3830
  SCHEMES,
3831
- normalize: normalize4,
3831
+ normalize: normalize5,
3832
3832
  resolve: resolve15,
3833
3833
  resolveComponent,
3834
3834
  equal,
@@ -7395,30 +7395,35 @@ function resolveRepoRoot(flags) {
7395
7395
  const git = findGitRepoRoot(abs);
7396
7396
  return git ?? abs;
7397
7397
  }
7398
- function resolveExtensionRoot() {
7399
- const env = process.env.EASYSPECS_EXTENSION_ROOT?.trim();
7400
- if (env) {
7401
- return path2.resolve(env);
7398
+ function resolveExtensionRootForCli(repoRoot, config) {
7399
+ const raw = config.easyspecs?.cli?.bundledResourcesRoot?.trim();
7400
+ if (raw) {
7401
+ const resourcesAbs = path2.isAbsolute(raw) ? path2.normalize(raw) : path2.resolve(repoRoot, raw);
7402
+ return path2.resolve(resourcesAbs, "..");
7402
7403
  }
7404
+ return resolveExtensionRootHeuristic();
7405
+ }
7406
+ function resolveExtensionRootHeuristic() {
7403
7407
  const here = thisDir();
7404
7408
  if (here.includes(`${path2.sep}packages${path2.sep}cli${path2.sep}dist`)) {
7405
7409
  return path2.resolve(here, "..", "..", "..");
7406
7410
  }
7407
7411
  return path2.resolve(here, "..", "..");
7408
7412
  }
7409
- function resolveOpenCodeAgentsDir() {
7410
- const resEnv = process.env.EASYSPECS_CLI_RESOURCES?.trim();
7411
- if (resEnv) {
7412
- return path2.join(path2.resolve(resEnv), "opencode-agents");
7413
+ function resolveOpenCodeAgentsDir(repoRoot, config) {
7414
+ const raw = config.easyspecs?.cli?.bundledResourcesRoot?.trim();
7415
+ if (raw) {
7416
+ const resourcesAbs = path2.isAbsolute(raw) ? path2.normalize(raw) : path2.resolve(repoRoot, raw);
7417
+ return path2.join(resourcesAbs, "opencode-agents");
7413
7418
  }
7414
7419
  const here = thisDir();
7415
7420
  if (here.includes(`${path2.sep}packages${path2.sep}cli${path2.sep}dist`)) {
7416
7421
  return path2.join(here, "..", "resources", "opencode-agents");
7417
7422
  }
7418
- return path2.join(resolveExtensionRoot(), "resources", "opencode-agents");
7423
+ return path2.join(resolveExtensionRootHeuristic(), "resources", "opencode-agents");
7419
7424
  }
7420
7425
  function initApiBaseUrlForCli(repoRoot, flags, config) {
7421
- const extensionRoot2 = resolveExtensionRoot();
7426
+ const extensionRoot2 = resolveExtensionRootForCli(repoRoot, config);
7422
7427
  setApiBaseUrlExtensionRoot(extensionRoot2);
7423
7428
  return resolveEasyspecsApiBaseUrlForCli({
7424
7429
  flagApiBaseUrl: flags.apiBaseUrl,
@@ -7429,7 +7434,7 @@ function initApiBaseUrlForCli(repoRoot, flags, config) {
7429
7434
  function assertAgentsDirExists(agentsDir) {
7430
7435
  if (!fs2.existsSync(agentsDir)) {
7431
7436
  throw new Error(
7432
- `OpenCode agents directory missing: ${agentsDir}. Set EASYSPECS_EXTENSION_ROOT or EASYSPECS_CLI_RESOURCES, or run \`npm run build:cli\` to copy resources.`
7437
+ `OpenCode agents directory missing: ${agentsDir}. Set easyspecs.cli.bundledResourcesRoot in .easyspecs/config.json to your EasySpecs resources directory (folder containing opencode-agents/), or run "npm run build:cli" so packages/cli ships resources.`
7433
7438
  );
7434
7439
  }
7435
7440
  }
@@ -7501,7 +7506,8 @@ function mergeEasyspecsCliSettings(cfg, overrides = {}) {
7501
7506
  maxDelayMs: orch.maxDelayMs ?? 48e4,
7502
7507
  maxOuterIterationsPerPhase: maxOuter,
7503
7508
  maxMacroPingPongCycles: orch.maxMacroPingPongCycles ?? 5,
7504
- synthesisRemediationShareBackoff: orch.synthesisRemediationShareBackoff !== false
7509
+ synthesisRemediationShareBackoff: orch.synthesisRemediationShareBackoff !== false,
7510
+ debugPhases: es.macro?.debug === true
7505
7511
  };
7506
7512
  const openCodeChildEnv = buildOpenCodeProviderEnvFromConfig(cfg, overrides.repoRoot);
7507
7513
  const projectConfigOverlay = cfg.easyspecs?.openCodeRuntime?.projectConfigOverlay;
@@ -7640,13 +7646,43 @@ function mergeEasyspecsConfigDefaults(defaults, partial) {
7640
7646
  esIn.analysis
7641
7647
  ),
7642
7648
  orchestration: { ...defEs.orchestration, ...esIn.orchestration },
7643
- openCodeRuntime: mergeOpenCodeRuntime(defEs.openCodeRuntime, esIn.openCodeRuntime)
7649
+ openCodeRuntime: mergeOpenCodeRuntime(defEs.openCodeRuntime, esIn.openCodeRuntime),
7650
+ diagnose: mergeDiagnoseBlock(defEs.diagnose, esIn.diagnose),
7651
+ upload: { ...defEs.upload, ...esIn.upload },
7652
+ macro: { ...defEs.macro, ...esIn.macro },
7653
+ cli: { ...defEs.cli, ...esIn.cli },
7654
+ auth: mergeAuthBlock(defEs.auth, esIn.auth)
7644
7655
  };
7645
7656
  return {
7646
7657
  schemaVersion: partial.schemaVersion ?? defaults.schemaVersion,
7647
7658
  easyspecs: mergedEs
7648
7659
  };
7649
7660
  }
7661
+ function mergeDiagnoseBlock(base, over) {
7662
+ if (!over) {
7663
+ return { ...base };
7664
+ }
7665
+ const zBase = base.zeroReference ?? { maxPercentNonReferenced: null };
7666
+ const zOver = over.zeroReference;
7667
+ const cBase = base.coordinationDuplicates ?? { strict: true };
7668
+ const cOver = over.coordinationDuplicates;
7669
+ return {
7670
+ zeroReference: {
7671
+ maxPercentNonReferenced: zOver?.maxPercentNonReferenced !== void 0 ? zOver.maxPercentNonReferenced : zBase.maxPercentNonReferenced ?? null
7672
+ },
7673
+ coordinationDuplicates: {
7674
+ strict: cOver?.strict !== void 0 ? cOver.strict : cBase.strict !== false
7675
+ }
7676
+ };
7677
+ }
7678
+ function mergeAuthBlock(base, over) {
7679
+ if (!over) {
7680
+ return { ...base };
7681
+ }
7682
+ return {
7683
+ ciLogin: { ...base.ciLogin, ...over.ciLogin }
7684
+ };
7685
+ }
7650
7686
  function mergeAnalysisBlock(base, over) {
7651
7687
  if (!over || Object.keys(over).length === 0) {
7652
7688
  return { ...base };
@@ -7707,7 +7743,7 @@ function getDefaultEasyspecsConfig() {
7707
7743
  projectConfigOverlay: {}
7708
7744
  };
7709
7745
  return {
7710
- schemaVersion: 1,
7746
+ schemaVersion: 2,
7711
7747
  easyspecs: {
7712
7748
  deploymentEnvironment: "production",
7713
7749
  apiBaseUrl: PRODUCTION_SYSTEM_MANAGER_URL,
@@ -7722,7 +7758,15 @@ function getDefaultEasyspecsConfig() {
7722
7758
  promoteContextToWorkspace: true
7723
7759
  },
7724
7760
  orchestration: {},
7725
- openCodeRuntime
7761
+ openCodeRuntime,
7762
+ diagnose: {
7763
+ zeroReference: { maxPercentNonReferenced: null },
7764
+ coordinationDuplicates: { strict: true }
7765
+ },
7766
+ upload: { contextDirectory: "" },
7767
+ macro: { debug: false },
7768
+ cli: { bundledResourcesRoot: "" },
7769
+ auth: { ciLogin: {} }
7726
7770
  }
7727
7771
  };
7728
7772
  }
@@ -7910,6 +7954,11 @@ function redactEasyspecsConfigRootForDump(cfg) {
7910
7954
  }
7911
7955
  }
7912
7956
  }
7957
+ const authCi = es?.auth;
7958
+ const ciLogin = authCi?.ciLogin;
7959
+ if (ciLogin && typeof ciLogin === "object" && "password" in ciLogin) {
7960
+ ciLogin.password = "(redacted)";
7961
+ }
7913
7962
  return o;
7914
7963
  }
7915
7964
 
@@ -12687,13 +12736,6 @@ function hasMarker(resourcesRoot) {
12687
12736
  return fs22.existsSync(path20.join(resourcesRoot, CONTEXT_LIST_MARKER));
12688
12737
  }
12689
12738
  function resolveRepoResourcesRoot() {
12690
- const env = process.env.EASYSPECS_EXTENSION_ROOT?.trim();
12691
- if (env) {
12692
- const r = path20.join(path20.resolve(env), "resources");
12693
- if (hasMarker(r)) {
12694
- return r;
12695
- }
12696
- }
12697
12739
  const candidates = [path20.join(__dirname, "..", "resources"), path20.join(__dirname, "..", "..", "resources")];
12698
12740
  for (const c of candidates) {
12699
12741
  const abs = path20.resolve(c);
@@ -12702,7 +12744,7 @@ function resolveRepoResourcesRoot() {
12702
12744
  }
12703
12745
  }
12704
12746
  throw new Error(
12705
- `Could not find EasySpecs bundled resources (e.g. ${CONTEXT_LIST_MARKER}). Set EASYSPECS_EXTENSION_ROOT to your easyspecs.ai-vsc-extension repo root, or reinstall @gluecharm-lab/easyspecs-cli after \`npm run build:cli\` so packages/cli/resources contains the full resources tree.`
12747
+ `Could not find EasySpecs bundled resources (e.g. ${CONTEXT_LIST_MARKER}). Set easyspecs.cli.bundledResourcesRoot in .easyspecs/config.json to your resources directory, or reinstall @gluecharm-lab/easyspecs-cli after \`npm run build:cli\` so packages/cli/resources contains the full resources tree.`
12706
12748
  );
12707
12749
  }
12708
12750
  function resolveContextListSchemasDir() {
@@ -18232,7 +18274,7 @@ function buildMacroOrchestrationDepsHeadless(input) {
18232
18274
  config: { ...macroConfig, ...input.synthesisOnly ? { synthesisOnly: true } : {} },
18233
18275
  sleep: (ms) => macroSleep(ms, signal),
18234
18276
  post: (payload) => {
18235
- if (process.env.EASYSPECS_CLI_MACRO_DEBUG === "1") {
18277
+ if (macroConfig.debugPhases === true) {
18236
18278
  log(`[macro] phases=${payload.phases.map((p) => `${p.key}:${p.status}`).join(",")} elapsed=${String(payload.totalElapsedMs)}ms`);
18237
18279
  }
18238
18280
  },
@@ -19448,16 +19490,16 @@ function parseAuthLoginTail(tail) {
19448
19490
  return { ok: false, error: `Unexpected argument: ${a}` };
19449
19491
  }
19450
19492
  if (!email?.trim()) {
19451
- return { ok: false, error: "Missing --email (or use --ci with EASYSPECS_EMAIL / EASYSPECS_PASSWORD)" };
19493
+ return { ok: false, error: "Missing --email (or use --ci with easyspecs.auth.ciLogin in .easyspecs/config.json)" };
19452
19494
  }
19453
19495
  if (password === void 0) {
19454
- return { ok: false, error: "Missing --password (or use --ci with EASYSPECS_EMAIL / EASYSPECS_PASSWORD)" };
19496
+ return { ok: false, error: "Missing --password (or use --ci with easyspecs.auth.ciLogin in .easyspecs/config.json)" };
19455
19497
  }
19456
19498
  return { ok: true, creds: { email: email.trim(), password } };
19457
19499
  }
19458
19500
 
19459
19501
  // ../../src/cli/main.ts
19460
- var PKG_VERSION = "0.0.9";
19502
+ var PKG_VERSION = "0.0.11";
19461
19503
  function logErr(flags, ...a) {
19462
19504
  if (!flags.json) {
19463
19505
  console.error(...a);
@@ -19564,9 +19606,9 @@ function requireOpenCode(merged, flags) {
19564
19606
  }
19565
19607
  return readiness;
19566
19608
  }
19567
- async function runResumeRemediationPool(storage, repoRoot, analysisRoot, merged, flags) {
19609
+ async function runResumeRemediationPool(storage, repoRoot, analysisRoot, merged, flags, repoConfig) {
19568
19610
  requireOpenCode(merged, flags);
19569
- const agentsDir = resolveOpenCodeAgentsDir();
19611
+ const agentsDir = resolveOpenCodeAgentsDir(repoRoot, repoConfig);
19570
19612
  assertAgentsDirExists(agentsDir);
19571
19613
  const ctxDir = path43.join(analysisRoot, ".gluecharm", "context");
19572
19614
  const snap = readArtefactRunSnapshot(storage);
@@ -19775,7 +19817,7 @@ async function main() {
19775
19817
  logErr(flags, "Usage: easyspecs-cli doctor [--readiness] [--inspect-config]");
19776
19818
  process.exit(ExitCode.usage);
19777
19819
  }
19778
- const agentsDir = resolveOpenCodeAgentsDir();
19820
+ const agentsDir = resolveOpenCodeAgentsDir(repoRoot, repoConfig);
19779
19821
  const agentsOk = fs43.existsSync(agentsDir);
19780
19822
  const oc = getOpenCodeReadiness({
19781
19823
  executable: merged.openCodeExecutable,
@@ -19840,17 +19882,18 @@ async function main() {
19840
19882
  process.exit(ExitCode.usage);
19841
19883
  }
19842
19884
  const parsedCli = parseAuthLoginTail(tailForCreds);
19843
- const envEmail = process.env.EASYSPECS_EMAIL?.trim();
19844
- const envPassword = process.env.EASYSPECS_PASSWORD?.trim();
19845
- const useLegacyCiEnv = !parsedCli.ok && (flags.ci || tailFull.includes("--ci")) && Boolean(envEmail) && Boolean(envPassword);
19885
+ const ciLogin = repoConfig.easyspecs?.auth?.ciLogin;
19886
+ const cfgEmail = ciLogin?.email?.trim();
19887
+ const cfgPassword = ciLogin?.password;
19888
+ const useCiConfig = !parsedCli.ok && (flags.ci || tailFull.includes("--ci")) && Boolean(cfgEmail) && typeof cfgPassword === "string" && cfgPassword.length > 0;
19846
19889
  let email;
19847
19890
  let password;
19848
19891
  if (parsedCli.ok) {
19849
19892
  email = parsedCli.creds.email;
19850
19893
  password = parsedCli.creds.password;
19851
- } else if (useLegacyCiEnv) {
19852
- email = envEmail;
19853
- password = envPassword;
19894
+ } else if (useCiConfig) {
19895
+ email = cfgEmail;
19896
+ password = cfgPassword;
19854
19897
  } else {
19855
19898
  logErr(flags, parsedCli.error);
19856
19899
  logErr(
@@ -19859,7 +19902,7 @@ async function main() {
19859
19902
  );
19860
19903
  logErr(
19861
19904
  flags,
19862
- "Legacy: easyspecs-cli --ci auth login (requires EASYSPECS_EMAIL and EASYSPECS_PASSWORD)"
19905
+ "Non-interactive (--ci): set easyspecs.auth.ciLogin.email and easyspecs.auth.ciLogin.password in .easyspecs/config.json"
19863
19906
  );
19864
19907
  process.exit(ExitCode.usage);
19865
19908
  }
@@ -19912,7 +19955,7 @@ async function main() {
19912
19955
  const storage = createFileBackedWorkspaceState(repoRoot);
19913
19956
  const { worktree } = parseWorktreeFlag(pos.slice(3));
19914
19957
  const analysisRoot = resolveAdHocCheckoutRoot(repoRoot, storage, worktree);
19915
- const poolRes = await runResumeRemediationPool(storage, repoRoot, analysisRoot, merged, flags);
19958
+ const poolRes = await runResumeRemediationPool(storage, repoRoot, analysisRoot, merged, flags, repoConfig);
19916
19959
  if (poolRes.cancelled) {
19917
19960
  finish(ExitCode.cancelled, { ok: false, error: "Remediation cancelled.", analysisWorktreePath: analysisRoot });
19918
19961
  }
@@ -19929,7 +19972,7 @@ async function main() {
19929
19972
  const storage = createFileBackedWorkspaceState(repoRoot);
19930
19973
  const { worktree } = parseWorktreeFlag(pos.slice(3));
19931
19974
  const analysisRoot = resolveAdHocCheckoutRoot(repoRoot, storage, worktree);
19932
- const poolRes = await runResumeRemediationPool(storage, repoRoot, analysisRoot, merged, flags);
19975
+ const poolRes = await runResumeRemediationPool(storage, repoRoot, analysisRoot, merged, flags, repoConfig);
19933
19976
  if (poolRes.cancelled) {
19934
19977
  finish(ExitCode.cancelled, { ok: false, error: "Remediation cancelled.", analysisWorktreePath: analysisRoot });
19935
19978
  }
@@ -19944,7 +19987,7 @@ async function main() {
19944
19987
  }
19945
19988
  if (pos[0] === "run" && pos[1] === "synthesis" && pos.length === 2) {
19946
19989
  requireOpenCode(merged, flags);
19947
- const agentsDir = resolveOpenCodeAgentsDir();
19990
+ const agentsDir = resolveOpenCodeAgentsDir(repoRoot, repoConfig);
19948
19991
  assertAgentsDirExists(agentsDir);
19949
19992
  const storage = createFileBackedWorkspaceState(repoRoot);
19950
19993
  const result = await runContextArtefactPipelineAsync(
@@ -20012,8 +20055,8 @@ async function main() {
20012
20055
  });
20013
20056
  if (res.ok) {
20014
20057
  const d = res.document;
20015
- const maxPct = Number(process.env.EASYSPECS_MAX_PERCENT_NON_REFERENCED ?? "");
20016
- if (Number.isFinite(maxPct) && d.metrics.percentNonReferenced > maxPct) {
20058
+ const maxPct = repoConfig.easyspecs?.diagnose?.zeroReference?.maxPercentNonReferenced;
20059
+ if (maxPct !== null && maxPct !== void 0 && Number.isFinite(maxPct) && d.metrics.percentNonReferenced > maxPct) {
20017
20060
  finish(ExitCode.validation, {
20018
20061
  ok: false,
20019
20062
  error: `percentNonReferenced ${String(d.metrics.percentNonReferenced)} > ${String(maxPct)}`
@@ -20032,7 +20075,7 @@ async function main() {
20032
20075
  sourceRoot: rootKind === "worktree" ? "worktree" : "workspace"
20033
20076
  });
20034
20077
  if (res.ok) {
20035
- const strict = process.env.EASYSPECS_DUPLICATES_STRICT !== "0";
20078
+ const strict = repoConfig.easyspecs?.diagnose?.coordinationDuplicates?.strict !== false;
20036
20079
  const bad = strict && ((res.groupCount ?? 0) > 0 || (res.orphanMarkdownCount ?? 0) > 0);
20037
20080
  finish(bad ? ExitCode.validation : ExitCode.ok, {
20038
20081
  ok: !bad,
@@ -20076,7 +20119,7 @@ async function main() {
20076
20119
  "[SRS-30] zero-reference remediation runs OpenCode agents per file; expect cost and runtime similar to the extension macro phase."
20077
20120
  );
20078
20121
  requireOpenCode(merged, flags);
20079
- const agentsDir = resolveOpenCodeAgentsDir();
20122
+ const agentsDir = resolveOpenCodeAgentsDir(repoRoot, repoConfig);
20080
20123
  assertAgentsDirExists(agentsDir);
20081
20124
  const storage = createFileBackedWorkspaceState(repoRoot);
20082
20125
  const { worktree: wtAce } = parseWorktreeFlag(pos.slice(2));
@@ -20126,11 +20169,11 @@ async function main() {
20126
20169
  if (wantsUpload && !uploadSession) {
20127
20170
  finish(ExitCode.auth, {
20128
20171
  ok: false,
20129
- error: "analysis --upload requires `easyspecs-cli auth login --email \u2026 --password \u2026` (or legacy `--ci` + EASYSPECS_EMAIL/PASSWORD)."
20172
+ error: "analysis --upload requires `easyspecs-cli auth login --email \u2026 --password \u2026` or `--ci` with easyspecs.auth.ciLogin in .easyspecs/config.json."
20130
20173
  });
20131
20174
  }
20132
20175
  const skipBackendSync = !wantsUpload;
20133
- const agentsDir = resolveOpenCodeAgentsDir();
20176
+ const agentsDir = resolveOpenCodeAgentsDir(repoRoot, repoConfig);
20134
20177
  assertAgentsDirExists(agentsDir);
20135
20178
  const storage = createFileBackedWorkspaceState(repoRoot);
20136
20179
  const runBackendSyncImpl = wantsUpload && uploadSession ? async () => {
@@ -20208,15 +20251,16 @@ async function main() {
20208
20251
  if (sessRaw === void 0) {
20209
20252
  finish(ExitCode.auth, {
20210
20253
  ok: false,
20211
- error: "auth login first (`auth login --email \u2026 --password \u2026`, or legacy `--ci` + env)"
20254
+ error: "auth login first (`easyspecs-cli auth login --email \u2026 --password \u2026`, or `--ci` with easyspecs.auth.ciLogin in .easyspecs/config.json)"
20212
20255
  });
20213
20256
  }
20214
20257
  const sess = sessRaw;
20215
20258
  let ctxDir = path43.join(repoRoot, ".gluecharm", "context");
20216
20259
  if (pos[1] === "republish") {
20217
- const fromEnv = process.env.EASYSPECS_UPLOAD_CONTEXT_DIR?.trim();
20218
- if (fromEnv && fs43.existsSync(path43.join(fromEnv, ".."))) {
20219
- ctxDir = path43.resolve(fromEnv);
20260
+ const fromCfg = repoConfig.easyspecs?.upload?.contextDirectory?.trim();
20261
+ const resolvedOverride = fromCfg && fromCfg.length > 0 ? path43.isAbsolute(fromCfg) ? path43.normalize(fromCfg) : path43.resolve(repoRoot, fromCfg) : "";
20262
+ if (resolvedOverride && fs43.existsSync(path43.join(resolvedOverride, ".."))) {
20263
+ ctxDir = resolvedOverride;
20220
20264
  } else {
20221
20265
  const storage = createFileBackedWorkspaceState(repoRoot);
20222
20266
  const snap = readAnalysisWorkspaceSnapshot(storage);
@@ -20224,7 +20268,7 @@ async function main() {
20224
20268
  if (!wt) {
20225
20269
  finish(ExitCode.misconfiguration, {
20226
20270
  ok: false,
20227
- error: "upload republish requires an analysis worktree with .gluecharm/context (`easyspecs-cli run synthesis` first), or set EASYSPECS_UPLOAD_CONTEXT_DIR."
20271
+ error: "upload republish requires an analysis worktree with .gluecharm/context (`easyspecs-cli run synthesis` first), or set easyspecs.upload.contextDirectory in .easyspecs/config.json."
20228
20272
  });
20229
20273
  }
20230
20274
  ctxDir = wt;