@gluecharm-lab/easyspecs-cli 0.0.3 → 0.0.5

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 = "", normalize) {
2255
- if (normalize !== false)
2254
+ function getFullPath(resolver, id = "", normalize4) {
2255
+ if (normalize4 !== false)
2256
2256
  id = normalizeId(id);
2257
2257
  const p = resolver.parse(id);
2258
2258
  return _getFullPath(resolver, p);
@@ -3000,7 +3000,7 @@ var require_compile = __commonJS({
3000
3000
  const schOrFunc = root.refs[ref];
3001
3001
  if (schOrFunc)
3002
3002
  return schOrFunc;
3003
- let _sch = resolve14.call(this, root, ref);
3003
+ let _sch = resolve15.call(this, root, ref);
3004
3004
  if (_sch === void 0) {
3005
3005
  const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
3006
3006
  const { schemaId } = this.opts;
@@ -3027,7 +3027,7 @@ var require_compile = __commonJS({
3027
3027
  function sameSchemaEnv(s1, s2) {
3028
3028
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
3029
3029
  }
3030
- function resolve14(root, ref) {
3030
+ function resolve15(root, ref) {
3031
3031
  let sch;
3032
3032
  while (typeof (sch = this.refs[ref]) == "string")
3033
3033
  ref = sch;
@@ -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 normalize(uri, options) {
3595
+ function normalize4(uri, options) {
3596
3596
  if (typeof uri === "string") {
3597
3597
  uri = /** @type {T} */
3598
3598
  serialize(parse(uri, options), options);
@@ -3602,55 +3602,55 @@ var require_fast_uri = __commonJS({
3602
3602
  }
3603
3603
  return uri;
3604
3604
  }
3605
- function resolve14(baseURI, relativeURI, options) {
3605
+ function resolve15(baseURI, relativeURI, options) {
3606
3606
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3607
3607
  const resolved = resolveComponent(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true);
3608
3608
  schemelessOptions.skipEscape = true;
3609
3609
  return serialize(resolved, schemelessOptions);
3610
3610
  }
3611
- function resolveComponent(base, relative11, options, skipNormalization) {
3611
+ function resolveComponent(base, relative12, options, skipNormalization) {
3612
3612
  const target = {};
3613
3613
  if (!skipNormalization) {
3614
3614
  base = parse(serialize(base, options), options);
3615
- relative11 = parse(serialize(relative11, options), options);
3615
+ relative12 = parse(serialize(relative12, options), options);
3616
3616
  }
3617
3617
  options = options || {};
3618
- if (!options.tolerant && relative11.scheme) {
3619
- target.scheme = relative11.scheme;
3620
- target.userinfo = relative11.userinfo;
3621
- target.host = relative11.host;
3622
- target.port = relative11.port;
3623
- target.path = removeDotSegments(relative11.path || "");
3624
- target.query = relative11.query;
3618
+ if (!options.tolerant && relative12.scheme) {
3619
+ target.scheme = relative12.scheme;
3620
+ target.userinfo = relative12.userinfo;
3621
+ target.host = relative12.host;
3622
+ target.port = relative12.port;
3623
+ target.path = removeDotSegments(relative12.path || "");
3624
+ target.query = relative12.query;
3625
3625
  } else {
3626
- if (relative11.userinfo !== void 0 || relative11.host !== void 0 || relative11.port !== void 0) {
3627
- target.userinfo = relative11.userinfo;
3628
- target.host = relative11.host;
3629
- target.port = relative11.port;
3630
- target.path = removeDotSegments(relative11.path || "");
3631
- target.query = relative11.query;
3626
+ if (relative12.userinfo !== void 0 || relative12.host !== void 0 || relative12.port !== void 0) {
3627
+ target.userinfo = relative12.userinfo;
3628
+ target.host = relative12.host;
3629
+ target.port = relative12.port;
3630
+ target.path = removeDotSegments(relative12.path || "");
3631
+ target.query = relative12.query;
3632
3632
  } else {
3633
- if (!relative11.path) {
3633
+ if (!relative12.path) {
3634
3634
  target.path = base.path;
3635
- if (relative11.query !== void 0) {
3636
- target.query = relative11.query;
3635
+ if (relative12.query !== void 0) {
3636
+ target.query = relative12.query;
3637
3637
  } else {
3638
3638
  target.query = base.query;
3639
3639
  }
3640
3640
  } else {
3641
- if (relative11.path[0] === "/") {
3642
- target.path = removeDotSegments(relative11.path);
3641
+ if (relative12.path[0] === "/") {
3642
+ target.path = removeDotSegments(relative12.path);
3643
3643
  } else {
3644
3644
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
3645
- target.path = "/" + relative11.path;
3645
+ target.path = "/" + relative12.path;
3646
3646
  } else if (!base.path) {
3647
- target.path = relative11.path;
3647
+ target.path = relative12.path;
3648
3648
  } else {
3649
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative11.path;
3649
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative12.path;
3650
3650
  }
3651
3651
  target.path = removeDotSegments(target.path);
3652
3652
  }
3653
- target.query = relative11.query;
3653
+ target.query = relative12.query;
3654
3654
  }
3655
3655
  target.userinfo = base.userinfo;
3656
3656
  target.host = base.host;
@@ -3658,7 +3658,7 @@ var require_fast_uri = __commonJS({
3658
3658
  }
3659
3659
  target.scheme = base.scheme;
3660
3660
  }
3661
- target.fragment = relative11.fragment;
3661
+ target.fragment = relative12.fragment;
3662
3662
  return target;
3663
3663
  }
3664
3664
  function equal(uriA, uriB, options) {
@@ -3828,8 +3828,8 @@ var require_fast_uri = __commonJS({
3828
3828
  }
3829
3829
  var fastUri = {
3830
3830
  SCHEMES,
3831
- normalize,
3832
- resolve: resolve14,
3831
+ normalize: normalize4,
3832
+ resolve: resolve15,
3833
3833
  resolveComponent,
3834
3834
  equal,
3835
3835
  serialize,
@@ -7082,7 +7082,14 @@ var path43 = __toESM(require("node:path"));
7082
7082
 
7083
7083
  // src/cli/argv.ts
7084
7084
  function isGlobalFlag(s) {
7085
- return s === "--cwd" || s === "--ci" || s === "--json" || s === "--verbose" || s === "--config" || s === "--api-base-url" || s === "--promote" || s === "--no-promote" || s === "--help" || s === "-h" || s === "--version" || s === "-V";
7085
+ return s === "--cwd" || s === "--ci" || s === "--json" || s === "--verbose" || s === "--config" || s === "--api-base-url" || s === "--session-path" || s === "--environment" || s === "--env" || s === "--promote" || s === "--no-promote" || s === "--help" || s === "-h" || s === "--version" || s === "-V";
7086
+ }
7087
+ function parseEnvironmentToken(raw) {
7088
+ const t = raw?.trim().toLowerCase();
7089
+ if (t === "production" || t === "staging") {
7090
+ return t;
7091
+ }
7092
+ throw new Error(`Invalid --environment: ${raw ?? "(missing)"} (expected production or staging)`);
7086
7093
  }
7087
7094
  function parseArgv(argv) {
7088
7095
  const flags = {
@@ -7115,6 +7122,16 @@ function parseArgv(argv) {
7115
7122
  i += 1;
7116
7123
  continue;
7117
7124
  }
7125
+ if (a === "--session-path" && argv[i + 1]) {
7126
+ flags.sessionPath = argv[i + 1];
7127
+ i += 1;
7128
+ continue;
7129
+ }
7130
+ if ((a === "--environment" || a === "--env") && argv[i + 1]) {
7131
+ flags.environment = parseEnvironmentToken(argv[i + 1]);
7132
+ i += 1;
7133
+ continue;
7134
+ }
7118
7135
  if (a === "--ci") {
7119
7136
  flags.ci = true;
7120
7137
  continue;
@@ -7147,9 +7164,6 @@ function parseArgv(argv) {
7147
7164
  throw new Error(`Unknown flag: ${a}`);
7148
7165
  }
7149
7166
  }
7150
- if (process.env.EASYSPECS_CI === "1" || process.env.EASYSPECS_CI === "true") {
7151
- flags.ci = true;
7152
- }
7153
7167
  return { flags, positionals };
7154
7168
  }
7155
7169
 
@@ -7173,8 +7187,8 @@ function printJsonLine(envelope) {
7173
7187
  }
7174
7188
 
7175
7189
  // src/cli/cliContext.ts
7176
- var fs4 = __toESM(require("node:fs"));
7177
- var path4 = __toESM(require("path"));
7190
+ var fs2 = __toESM(require("node:fs"));
7191
+ var path2 = __toESM(require("path"));
7178
7192
 
7179
7193
  // src/repositoryProvenance.ts
7180
7194
  var import_node_child_process = require("node:child_process");
@@ -7343,98 +7357,115 @@ function attachRepositoryProvenance(doc, contextDir2, opts) {
7343
7357
  }
7344
7358
 
7345
7359
  // src/apiBaseUrlResolve.ts
7346
- var fs2 = __toESM(require("fs"));
7347
- var path2 = __toESM(require("path"));
7348
7360
  var vscode = __toESM(require_vscode_stub());
7349
-
7350
- // src/envDotEnvParse.ts
7351
- function parseDotEnvContent(content) {
7352
- const out = {};
7353
- for (const line of content.split(/\r?\n/)) {
7354
- const t = line.trim();
7355
- if (t.length === 0 || t.startsWith("#")) {
7356
- continue;
7357
- }
7358
- const eq = t.indexOf("=");
7359
- if (eq <= 0) {
7360
- continue;
7361
- }
7362
- const key = t.slice(0, eq).trim();
7363
- let val = t.slice(eq + 1).trim();
7364
- if (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'")) {
7365
- val = val.slice(1, -1);
7366
- }
7367
- out[key] = val;
7368
- }
7369
- return out;
7370
- }
7371
-
7372
- // src/apiBaseUrlResolve.ts
7373
- var ENV_KEYS = ["VITE_SYSTEM_MANAGER_API_URL", "VITE_API_BASE_URL"];
7374
7361
  var extensionRoot;
7375
7362
  var cachedExtensionDotEnv = null;
7376
7363
  function setApiBaseUrlExtensionRoot(extensionFsPath) {
7377
7364
  extensionRoot = extensionFsPath;
7378
7365
  cachedExtensionDotEnv = null;
7379
7366
  }
7367
+
7368
+ // src/easyspecsBuiltInApiUrls.ts
7369
+ var PRODUCTION_SYSTEM_MANAGER_URL = "https://api.easyspecs.ai";
7370
+ var STAGING_SYSTEM_MANAGER_URL = "https://system-manager-api.staging.gluecharm.info:8092";
7371
+
7372
+ // src/easyspecsApiBaseUrlCli.ts
7380
7373
  function stripTrailingSlash(url) {
7381
7374
  return url.replace(/\/$/, "");
7382
7375
  }
7383
- function pickFromMap(map) {
7384
- if (!map) {
7385
- return "";
7376
+ function resolveEasyspecsApiBaseUrlForCli(input) {
7377
+ const fromFlag = input.flagApiBaseUrl?.trim();
7378
+ if (fromFlag) {
7379
+ return stripTrailingSlash(fromFlag);
7386
7380
  }
7387
- for (const key of ENV_KEYS) {
7388
- const v = map[key]?.trim();
7389
- if (v) {
7390
- return stripTrailingSlash(v);
7391
- }
7381
+ const fromFile = input.config.easyspecs?.apiBaseUrl?.trim();
7382
+ if (fromFile) {
7383
+ return stripTrailingSlash(fromFile);
7392
7384
  }
7393
- return "";
7385
+ const eff = input.flagEnvironment ?? input.config.easyspecs?.deploymentEnvironment ?? "production";
7386
+ return eff === "staging" ? STAGING_SYSTEM_MANAGER_URL : PRODUCTION_SYSTEM_MANAGER_URL;
7394
7387
  }
7395
- function readDotEnvFile(filePath) {
7396
- try {
7397
- if (!fs2.existsSync(filePath)) {
7398
- return void 0;
7399
- }
7400
- const content = fs2.readFileSync(filePath, "utf8");
7401
- return parseDotEnvContent(content);
7402
- } catch {
7403
- return void 0;
7388
+
7389
+ // src/cli/cliContext.ts
7390
+ function thisDir() {
7391
+ return __dirname;
7392
+ }
7393
+ function resolveRepoRoot(flags) {
7394
+ const abs = path2.resolve(flags.cwd);
7395
+ const git = findGitRepoRoot(abs);
7396
+ return git ?? abs;
7397
+ }
7398
+ function resolveExtensionRoot() {
7399
+ const env = process.env.EASYSPECS_EXTENSION_ROOT?.trim();
7400
+ if (env) {
7401
+ return path2.resolve(env);
7402
+ }
7403
+ const here = thisDir();
7404
+ if (here.includes(`${path2.sep}packages${path2.sep}cli${path2.sep}dist`)) {
7405
+ return path2.resolve(here, "..", "..", "..");
7404
7406
  }
7407
+ return path2.resolve(here, "..", "..");
7405
7408
  }
7406
- function resolveApiBaseUrlHeadless(input) {
7407
- const fromSettings = input.settingsApiBaseUrl?.trim() ?? "";
7408
- if (fromSettings.length > 0) {
7409
- return stripTrailingSlash(fromSettings);
7409
+ function resolveOpenCodeAgentsDir() {
7410
+ const resEnv = process.env.EASYSPECS_CLI_RESOURCES?.trim();
7411
+ if (resEnv) {
7412
+ return path2.join(path2.resolve(resEnv), "opencode-agents");
7410
7413
  }
7411
- const easyspecsEnv = process.env.EASYSPECS_API_BASE_URL?.trim();
7412
- if (easyspecsEnv) {
7413
- return stripTrailingSlash(easyspecsEnv);
7414
+ const here = thisDir();
7415
+ if (here.includes(`${path2.sep}packages${path2.sep}cli${path2.sep}dist`)) {
7416
+ return path2.join(here, "..", "resources", "opencode-agents");
7414
7417
  }
7415
- for (const key of ENV_KEYS) {
7416
- const v = process.env[key]?.trim();
7417
- if (v) {
7418
- return stripTrailingSlash(v);
7419
- }
7418
+ return path2.join(resolveExtensionRoot(), "resources", "opencode-agents");
7419
+ }
7420
+ function initApiBaseUrlForCli(repoRoot, flags, config) {
7421
+ const extensionRoot2 = resolveExtensionRoot();
7422
+ setApiBaseUrlExtensionRoot(extensionRoot2);
7423
+ return resolveEasyspecsApiBaseUrlForCli({
7424
+ flagApiBaseUrl: flags.apiBaseUrl,
7425
+ flagEnvironment: flags.environment,
7426
+ config
7427
+ });
7428
+ }
7429
+ function assertAgentsDirExists(agentsDir) {
7430
+ if (!fs2.existsSync(agentsDir)) {
7431
+ 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.`
7433
+ );
7420
7434
  }
7421
- const extMap = input.extensionDotEnv ?? (input.extensionRoot ? readDotEnvFile(path2.join(input.extensionRoot, ".env")) : void 0);
7422
- const fromExt = pickFromMap(extMap);
7423
- if (fromExt) {
7424
- return fromExt;
7435
+ }
7436
+
7437
+ // src/config/openCodeProviderEnv.ts
7438
+ var path3 = __toESM(require("node:path"));
7439
+ var PROVIDER_TO_ENV = {
7440
+ anthropic: "ANTHROPIC_API_KEY",
7441
+ openai: "OPENAI_API_KEY",
7442
+ openrouter: "OPENROUTER_API_KEY",
7443
+ google: "GOOGLE_API_KEY",
7444
+ opencode: "OPENCODE_API_KEY"
7445
+ };
7446
+ function buildOpenCodeProviderEnvFromConfig(cfg, repoRoot) {
7447
+ const out = {};
7448
+ const prov = cfg.easyspecs?.openCodeRuntime?.providers;
7449
+ if (prov) {
7450
+ for (const id of Object.keys(PROVIDER_TO_ENV)) {
7451
+ const envName = PROVIDER_TO_ENV[id];
7452
+ const key = prov[id]?.apiKey?.trim();
7453
+ if (key) {
7454
+ out[envName] = key;
7455
+ }
7456
+ }
7425
7457
  }
7426
- if (input.workspaceFolderPath) {
7427
- const fromWs = pickFromMap(readDotEnvFile(path2.join(input.workspaceFolderPath, ".env")));
7428
- if (fromWs) {
7429
- return fromWs;
7458
+ const raw = cfg.easyspecs?.openCodeRuntime?.credentialsPath;
7459
+ if (repoRoot && typeof raw === "string") {
7460
+ const t = raw.trim();
7461
+ if (t.length > 0) {
7462
+ out.OPENCODE_AUTH_PATH = path3.isAbsolute(t) ? path3.normalize(t) : path3.resolve(repoRoot, t);
7430
7463
  }
7431
7464
  }
7432
- return "";
7465
+ return out;
7433
7466
  }
7434
7467
 
7435
7468
  // src/cli/cliSettings.ts
7436
- var fs3 = __toESM(require("node:fs"));
7437
- var path3 = __toESM(require("node:path"));
7438
7469
  var DEFAULT_OPEN_CODE_TEST_ARGV = [
7439
7470
  "run",
7440
7471
  "--agent",
@@ -7444,72 +7475,23 @@ var DEFAULT_OPEN_CODE_TEST_ARGV = [
7444
7475
  "{promptFile}"
7445
7476
  ];
7446
7477
  var CI_DEFAULT_MAX_OUTER = 3;
7447
- function readCliJson(repoRoot) {
7448
- const f = path3.join(repoRoot, ".easyspecs", "cli.json");
7449
- try {
7450
- if (!fs3.existsSync(f)) {
7451
- return void 0;
7452
- }
7453
- const raw = fs3.readFileSync(f, "utf8");
7454
- return JSON.parse(raw);
7455
- } catch {
7456
- return void 0;
7457
- }
7458
- }
7459
- function numEnv(name, fallback) {
7460
- const v = process.env[name]?.trim();
7461
- if (!v) {
7462
- return fallback;
7463
- }
7464
- const n = Number(v);
7465
- return Number.isFinite(n) ? n : fallback;
7466
- }
7467
- function boolEnv(name, fallback) {
7468
- const v = process.env[name]?.trim().toLowerCase();
7469
- if (v === "1" || v === "true" || v === "yes") {
7470
- return true;
7471
- }
7472
- if (v === "0" || v === "false" || v === "no") {
7473
- return false;
7474
- }
7475
- return fallback;
7476
- }
7477
- function mergeCliSettings(repoRoot, overrides = {}) {
7478
- const file = readCliJson(repoRoot);
7479
- const es = file?.easyspecs ?? {};
7478
+ function mergeEasyspecsCliSettings(cfg, overrides = {}) {
7479
+ const es = cfg.easyspecs ?? {};
7480
7480
  const analysis = es.analysis ?? {};
7481
7481
  const orch = es.orchestration ?? {};
7482
- const ci = overrides.ci === true || boolEnv("EASYSPECS_CI", false);
7483
- const apiBaseUrl = overrides.apiBaseUrl?.trim() || process.env.EASYSPECS_API_BASE_URL?.trim() || file?.apiBaseUrl?.trim() || es.apiBaseUrl?.trim() || "";
7484
- const openCodeExecutable = overrides.openCodeExecutable?.trim() || es.openCode?.executable?.trim() || process.env.EASYSPECS_OPENCODE_EXECUTABLE?.trim() || "opencode";
7485
- const openCodeSkipCredentialsCheck = es.openCode?.skipCredentialsCheck === true || boolEnv("EASYSPECS_OPENCODE_SKIP_CREDENTIALS_CHECK", false);
7486
- let argvTemplate = overrides.argvTemplate ?? (Array.isArray(analysis.openCodeTestArgv) && analysis.openCodeTestArgv.length > 0 && analysis.openCodeTestArgv.every((x) => typeof x === "string") ? analysis.openCodeTestArgv : [...DEFAULT_OPEN_CODE_TEST_ARGV]);
7487
- const argvJson = process.env.EASYSPECS_OPEN_CODE_TEST_ARGV_JSON?.trim();
7488
- if (argvJson) {
7489
- try {
7490
- const parsed = JSON.parse(argvJson);
7491
- if (Array.isArray(parsed) && parsed.length > 0 && parsed.every((x) => typeof x === "string")) {
7492
- argvTemplate = parsed;
7493
- }
7494
- } catch {
7495
- }
7496
- }
7497
- const timeoutMs = overrides.timeoutMs ?? numEnv("EASYSPECS_ANALYSIS_TIMEOUT_MS", analysis.openCodeTestTimeoutMs ?? 6e5);
7498
- const listJsonSchemaRepairAttempts = numEnv(
7499
- "EASYSPECS_LIST_JSON_SCHEMA_REPAIR_ATTEMPTS",
7500
- analysis.listJsonSchemaRepairAttempts ?? 1
7501
- );
7502
- const markdownEvidenceRepairAttempts = numEnv(
7503
- "EASYSPECS_MARKDOWN_EVIDENCE_REPAIR_ATTEMPTS",
7504
- analysis.markdownEvidenceRepairAttempts ?? 2
7505
- );
7506
- const markdownOpenQuestionIterations = numEnv(
7507
- "EASYSPECS_MARKDOWN_OPEN_QUESTION_ITERATIONS",
7508
- analysis.markdownOpenQuestionIterations ?? 5
7509
- );
7510
- const maxConcurrentOpenCodeAgents = overrides.maxConcurrentOpenCodeAgents ?? numEnv("EASYSPECS_MAX_CONCURRENT_AGENTS", analysis.maxConcurrentOpenCodeAgents ?? 30);
7482
+ const rt = es.openCodeRuntime;
7483
+ const ci = overrides.ci === true;
7484
+ const apiBaseUrl = overrides.apiBaseUrl?.trim() || (typeof es.apiBaseUrl === "string" ? es.apiBaseUrl.trim() : "") || "";
7485
+ const openCodeExecutable = overrides.openCodeExecutable?.trim() || es.openCode?.executable?.trim() || rt?.executable?.trim() || "opencode";
7486
+ const openCodeSkipCredentialsCheck = es.openCode?.skipCredentialsCheck === true || rt?.skipCredentialsCheck === true;
7487
+ let argvTemplate = overrides.argvTemplate ?? (rt?.run?.argvTemplate && rt.run.argvTemplate.length > 0 && rt.run.argvTemplate.every((x) => typeof x === "string") ? [...rt.run.argvTemplate] : void 0) ?? (Array.isArray(analysis.openCodeTestArgv) && analysis.openCodeTestArgv.length > 0 && analysis.openCodeTestArgv.every((x) => typeof x === "string") ? analysis.openCodeTestArgv : [...DEFAULT_OPEN_CODE_TEST_ARGV]);
7488
+ const timeoutMs = overrides.timeoutMs ?? rt?.run?.timeoutMs ?? analysis.openCodeTestTimeoutMs ?? 6e5;
7489
+ const listJsonSchemaRepairAttempts = rt?.coordinationRepairs?.listJsonSchemaRepairAttempts ?? analysis.listJsonSchemaRepairAttempts ?? 1;
7490
+ const markdownEvidenceRepairAttempts = rt?.coordinationRepairs?.markdownEvidenceRepairAttempts ?? analysis.markdownEvidenceRepairAttempts ?? 2;
7491
+ const markdownOpenQuestionIterations = rt?.coordinationRepairs?.markdownOpenQuestionIterations ?? analysis.markdownOpenQuestionIterations ?? 5;
7492
+ const maxConcurrentOpenCodeAgents = overrides.maxConcurrentOpenCodeAgents ?? rt?.pool?.maxConcurrentAgents ?? analysis.maxConcurrentOpenCodeAgents ?? 30;
7511
7493
  const promoteContextToWorkspace = overrides.promote === false ? false : overrides.promote === true ? true : analysis.promoteContextToWorkspace !== false;
7512
- let maxOuter = overrides.maxOuterIterationsPerPhase ?? orch.maxOuterIterationsPerPhase ?? numEnv("EASYSPECS_MAX_OUTER_ITERATIONS_PER_PHASE", 0);
7494
+ let maxOuter = overrides.maxOuterIterationsPerPhase ?? orch.maxOuterIterationsPerPhase ?? 0;
7513
7495
  if (ci && maxOuter === 0) {
7514
7496
  maxOuter = CI_DEFAULT_MAX_OUTER;
7515
7497
  }
@@ -7521,12 +7503,15 @@ function mergeCliSettings(repoRoot, overrides = {}) {
7521
7503
  maxMacroPingPongCycles: orch.maxMacroPingPongCycles ?? 5,
7522
7504
  synthesisRemediationShareBackoff: orch.synthesisRemediationShareBackoff !== false
7523
7505
  };
7506
+ const openCodeChildEnv = buildOpenCodeProviderEnvFromConfig(cfg, overrides.repoRoot);
7507
+ const projectConfigOverlay = cfg.easyspecs?.openCodeRuntime?.projectConfigOverlay;
7524
7508
  return {
7525
7509
  apiBaseUrl,
7526
7510
  openCodeExecutable,
7527
7511
  openCodeSkipCredentialsCheck,
7528
7512
  promoteContextToWorkspace,
7529
7513
  ci,
7514
+ openCodeChildEnv,
7530
7515
  pipelineOpenCode: {
7531
7516
  executable: openCodeExecutable,
7532
7517
  argvTemplate,
@@ -7534,85 +7519,420 @@ function mergeCliSettings(repoRoot, overrides = {}) {
7534
7519
  listJsonSchemaRepairAttempts,
7535
7520
  markdownEvidenceRepairAttempts,
7536
7521
  markdownOpenQuestionIterations,
7537
- maxConcurrentOpenCodeAgents
7522
+ maxConcurrentOpenCodeAgents,
7523
+ openCodeChildEnv,
7524
+ projectConfigOverlay
7538
7525
  },
7539
7526
  macroOrchestration
7540
7527
  };
7541
7528
  }
7542
7529
 
7543
- // src/cli/cliContext.ts
7544
- function thisDir() {
7545
- return __dirname;
7530
+ // src/cli/cliSettingsDump.ts
7531
+ function redactMergedCliSettingsForDump(merged) {
7532
+ const child = merged.openCodeChildEnv;
7533
+ const childRedacted = child && Object.keys(child).length > 0 ? Object.fromEntries(Object.keys(child).map((k) => [k, "(redacted)"])) : {};
7534
+ return {
7535
+ ...merged,
7536
+ apiBaseUrl: merged.apiBaseUrl ? "(set)" : "",
7537
+ openCodeChildEnv: childRedacted,
7538
+ pipelineOpenCode: {
7539
+ ...merged.pipelineOpenCode,
7540
+ openCodeChildEnv: childRedacted
7541
+ }
7542
+ };
7546
7543
  }
7547
- function resolveRepoRoot(flags) {
7548
- const abs = path4.resolve(flags.cwd);
7549
- const git = findGitRepoRoot(abs);
7550
- return git ?? abs;
7544
+
7545
+ // src/config/easyspecsConfigFile.ts
7546
+ var fs5 = __toESM(require("node:fs"));
7547
+ var path6 = __toESM(require("node:path"));
7548
+
7549
+ // src/easySpecsWorkspaceSettingsCore.ts
7550
+ var fs4 = __toESM(require("node:fs"));
7551
+ var path5 = __toESM(require("node:path"));
7552
+
7553
+ // src/analysis/easySpecsWorktreeMarker.ts
7554
+ var fs3 = __toESM(require("fs"));
7555
+ var path4 = __toESM(require("path"));
7556
+ var EASYSPECS_LOCAL_DIR = ".easyspecs";
7557
+ var EASYSPECS_WORKTREE_MARKER_REL = path4.join(EASYSPECS_LOCAL_DIR, "analysis-worktree.json");
7558
+ var MARKER_KIND = "easyspecs-analysis-worktree";
7559
+ function writeAnalysisWorktreeMarker(worktreeRoot, repositoryRoot) {
7560
+ const dir = path4.join(worktreeRoot, EASYSPECS_LOCAL_DIR);
7561
+ fs3.mkdirSync(dir, { recursive: true });
7562
+ const payload = {
7563
+ kind: MARKER_KIND,
7564
+ repositoryRoot: path4.resolve(repositoryRoot)
7565
+ };
7566
+ fs3.writeFileSync(path4.join(dir, "analysis-worktree.json"), `${JSON.stringify(payload)}
7567
+ `, "utf-8");
7551
7568
  }
7552
- function resolveExtensionRoot() {
7553
- const env = process.env.EASYSPECS_EXTENSION_ROOT?.trim();
7554
- if (env) {
7555
- return path4.resolve(env);
7569
+
7570
+ // src/easySpecsWorkspaceSettingsCore.ts
7571
+ var EASYSPECS_WORKSPACE_DIR = EASYSPECS_LOCAL_DIR;
7572
+ var EASYSPECS_SETTINGS_JSON = "settings.json";
7573
+ var EASYSPECS_SETTINGS_ACE_KEY = "easyspecs.analysis.ace.enabled";
7574
+ var EASYSPECS_SETTINGS_ACE_OFFLINE_AFTER_TRACE_KEY = "easyspecs.analysis.ace.offlineLearnAfterSameSessionTrace";
7575
+ function isRecord(v) {
7576
+ return typeof v === "object" && v !== null && !Array.isArray(v);
7577
+ }
7578
+ function readAceEnabledFromEasySpecsSettingsFile(workspaceRootFsPath) {
7579
+ const filePath = path5.join(workspaceRootFsPath, EASYSPECS_WORKSPACE_DIR, EASYSPECS_SETTINGS_JSON);
7580
+ let raw;
7581
+ try {
7582
+ raw = fs4.readFileSync(filePath, "utf8");
7583
+ } catch {
7584
+ return void 0;
7556
7585
  }
7557
- const here = thisDir();
7558
- if (here.includes(`${path4.sep}packages${path4.sep}cli${path4.sep}dist`)) {
7559
- return path4.resolve(here, "..", "..", "..");
7586
+ let parsed;
7587
+ try {
7588
+ parsed = JSON.parse(raw);
7589
+ } catch {
7590
+ return void 0;
7591
+ }
7592
+ if (!isRecord(parsed) || !Object.prototype.hasOwnProperty.call(parsed, EASYSPECS_SETTINGS_ACE_KEY)) {
7593
+ return void 0;
7560
7594
  }
7561
- return path4.resolve(here, "..", "..");
7595
+ const v = parsed[EASYSPECS_SETTINGS_ACE_KEY];
7596
+ return typeof v === "boolean" ? v : void 0;
7562
7597
  }
7563
- function resolveOpenCodeAgentsDir() {
7564
- const resEnv = process.env.EASYSPECS_CLI_RESOURCES?.trim();
7565
- if (resEnv) {
7566
- return path4.join(path4.resolve(resEnv), "opencode-agents");
7598
+ function readAceOfflineLearnAfterSameSessionTraceFromEasySpecsSettingsFile(workspaceRootFsPath) {
7599
+ const filePath = path5.join(workspaceRootFsPath, EASYSPECS_WORKSPACE_DIR, EASYSPECS_SETTINGS_JSON);
7600
+ let raw;
7601
+ try {
7602
+ raw = fs4.readFileSync(filePath, "utf8");
7603
+ } catch {
7604
+ return void 0;
7567
7605
  }
7568
- const here = thisDir();
7569
- if (here.includes(`${path4.sep}packages${path4.sep}cli${path4.sep}dist`)) {
7570
- return path4.join(here, "..", "resources", "opencode-agents");
7606
+ let parsed;
7607
+ try {
7608
+ parsed = JSON.parse(raw);
7609
+ } catch {
7610
+ return void 0;
7571
7611
  }
7572
- return path4.join(resolveExtensionRoot(), "resources", "opencode-agents");
7612
+ if (!isRecord(parsed) || !Object.prototype.hasOwnProperty.call(parsed, EASYSPECS_SETTINGS_ACE_OFFLINE_AFTER_TRACE_KEY)) {
7613
+ return void 0;
7614
+ }
7615
+ const v = parsed[EASYSPECS_SETTINGS_ACE_OFFLINE_AFTER_TRACE_KEY];
7616
+ return typeof v === "boolean" ? v : void 0;
7573
7617
  }
7574
- function initApiBaseUrlForCli(repoRoot, flags) {
7575
- const extensionRoot2 = resolveExtensionRoot();
7576
- setApiBaseUrlExtensionRoot(extensionRoot2);
7577
- const merged = mergeCliSettings(repoRoot, {
7578
- ci: flags.ci,
7579
- apiBaseUrl: flags.apiBaseUrl,
7580
- promote: flags.promote
7581
- });
7582
- return resolveApiBaseUrlHeadless({
7583
- settingsApiBaseUrl: merged.apiBaseUrl,
7584
- extensionRoot: extensionRoot2,
7585
- workspaceFolderPath: repoRoot
7586
- });
7618
+
7619
+ // src/config/easyspecsConfigJson.ts
7620
+ function mergeEasyspecsConfigDefaults(defaults, partial) {
7621
+ if (!partial) {
7622
+ return defaults;
7623
+ }
7624
+ const esInRaw = partial.easyspecs ?? {};
7625
+ const legacyProjectId = esInRaw.applicationId;
7626
+ const { applicationId: _omitLegacyApplicationIdKey, ...esIn } = esInRaw;
7627
+ void _omitLegacyApplicationIdKey;
7628
+ const defEs = defaults.easyspecs;
7629
+ const mergedEs = {
7630
+ ...defEs,
7631
+ ...esIn,
7632
+ deploymentEnvironment: esIn.deploymentEnvironment ?? defEs.deploymentEnvironment,
7633
+ apiBaseUrl: esIn.apiBaseUrl ?? defEs.apiBaseUrl,
7634
+ easyspecsProjectId: esIn.easyspecsProjectId !== void 0 ? esIn.easyspecsProjectId : legacyProjectId !== void 0 ? legacyProjectId : defEs.easyspecsProjectId,
7635
+ defaultGitRemoteUrl: esIn.defaultGitRemoteUrl !== void 0 ? esIn.defaultGitRemoteUrl : defEs.defaultGitRemoteUrl,
7636
+ cliSessionPath: esIn.cliSessionPath !== void 0 ? esIn.cliSessionPath : defEs.cliSessionPath,
7637
+ openCode: { ...defEs.openCode, ...esIn.openCode },
7638
+ analysis: mergeAnalysisBlock(
7639
+ defEs.analysis ?? { promoteContextToWorkspace: true },
7640
+ esIn.analysis
7641
+ ),
7642
+ orchestration: { ...defEs.orchestration, ...esIn.orchestration },
7643
+ openCodeRuntime: mergeOpenCodeRuntime(defEs.openCodeRuntime, esIn.openCodeRuntime)
7644
+ };
7645
+ return {
7646
+ schemaVersion: partial.schemaVersion ?? defaults.schemaVersion,
7647
+ easyspecs: mergedEs
7648
+ };
7587
7649
  }
7588
- function assertAgentsDirExists(agentsDir) {
7589
- if (!fs4.existsSync(agentsDir)) {
7590
- throw new Error(
7591
- `OpenCode agents directory missing: ${agentsDir}. Set EASYSPECS_EXTENSION_ROOT or EASYSPECS_CLI_RESOURCES, or run \`npm run build:cli\` to copy resources.`
7592
- );
7650
+ function mergeAnalysisBlock(base, over) {
7651
+ if (!over || Object.keys(over).length === 0) {
7652
+ return { ...base };
7653
+ }
7654
+ const merged = {
7655
+ ...base,
7656
+ ...over
7657
+ };
7658
+ if (base.ace || over.ace) {
7659
+ merged.ace = { ...base.ace, ...over.ace };
7660
+ }
7661
+ return merged;
7662
+ }
7663
+ function mergeOpenCodeRuntime(base, over) {
7664
+ if (!over && !base) {
7665
+ return void 0;
7593
7666
  }
7667
+ const b = base ?? {};
7668
+ const o = over ?? {};
7669
+ return {
7670
+ ...b,
7671
+ ...o,
7672
+ executable: o.executable ?? b.executable,
7673
+ skipCredentialsCheck: o.skipCredentialsCheck ?? b.skipCredentialsCheck,
7674
+ credentialsPath: o.credentialsPath !== void 0 ? o.credentialsPath : b.credentialsPath,
7675
+ providers: { ...b.providers, ...o.providers },
7676
+ run: { ...b.run, ...o.run },
7677
+ coordinationRepairs: { ...b.coordinationRepairs, ...o.coordinationRepairs },
7678
+ pool: { ...b.pool, ...o.pool },
7679
+ projectConfigOverlay: b.projectConfigOverlay || o.projectConfigOverlay ? { ...b.projectConfigOverlay ?? {}, ...o.projectConfigOverlay ?? {} } : void 0
7680
+ };
7681
+ }
7682
+ function getDefaultEasyspecsConfig() {
7683
+ const openCodeRuntime = {
7684
+ executable: "opencode",
7685
+ skipCredentialsCheck: false,
7686
+ credentialsPath: ".opencode/auth.json",
7687
+ providers: {},
7688
+ run: {
7689
+ argvTemplate: [
7690
+ "run",
7691
+ "--agent",
7692
+ "{agentId}",
7693
+ "Execute the task described in the attached EasySpecs prompt file.",
7694
+ "-f",
7695
+ "{promptFile}"
7696
+ ],
7697
+ timeoutMs: 6e5
7698
+ },
7699
+ coordinationRepairs: {
7700
+ listJsonSchemaRepairAttempts: 1,
7701
+ markdownEvidenceRepairAttempts: 2,
7702
+ markdownOpenQuestionIterations: 5
7703
+ },
7704
+ pool: {
7705
+ maxConcurrentAgents: 30
7706
+ },
7707
+ projectConfigOverlay: {}
7708
+ };
7709
+ return {
7710
+ schemaVersion: 1,
7711
+ easyspecs: {
7712
+ deploymentEnvironment: "production",
7713
+ apiBaseUrl: PRODUCTION_SYSTEM_MANAGER_URL,
7714
+ easyspecsProjectId: "",
7715
+ defaultGitRemoteUrl: "",
7716
+ cliSessionPath: "",
7717
+ openCode: {
7718
+ executable: "opencode",
7719
+ skipCredentialsCheck: false
7720
+ },
7721
+ analysis: {
7722
+ promoteContextToWorkspace: true
7723
+ },
7724
+ orchestration: {},
7725
+ openCodeRuntime
7726
+ }
7727
+ };
7728
+ }
7729
+
7730
+ // src/config/easyspecsConfigFile.ts
7731
+ var DIRNAME = ".easyspecs";
7732
+ var CONFIG_BASENAME = "config.json";
7733
+ var LEGACY_CLI_JSON = "cli.json";
7734
+ var LEGACY_SETTINGS_JSON = "settings.json";
7735
+ function easyspecsConfigPath(repoRoot) {
7736
+ return path6.join(repoRoot, DIRNAME, CONFIG_BASENAME);
7737
+ }
7738
+ function easyspecsLegacyCliJsonPath(repoRoot) {
7739
+ return path6.join(repoRoot, DIRNAME, LEGACY_CLI_JSON);
7740
+ }
7741
+ function easyspecsLegacySettingsJsonPath(repoRoot) {
7742
+ return path6.join(repoRoot, DIRNAME, LEGACY_SETTINGS_JSON);
7743
+ }
7744
+ var EasyspecsConfigInvalidJsonError = class extends Error {
7745
+ constructor(filePath, cause) {
7746
+ super(`Invalid JSON in ${filePath}${cause instanceof Error ? `: ${cause.message}` : ""}`);
7747
+ this.filePath = filePath;
7748
+ this.cause = cause;
7749
+ this.name = "EasyspecsConfigInvalidJsonError";
7750
+ }
7751
+ };
7752
+ function parseEasyspecsConfigJson(raw, filePath) {
7753
+ try {
7754
+ const parsed = JSON.parse(raw);
7755
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
7756
+ throw new EasyspecsConfigInvalidJsonError(filePath);
7757
+ }
7758
+ return mergeEasyspecsConfigDefaults(getDefaultEasyspecsConfig(), parsed);
7759
+ } catch (e) {
7760
+ if (e instanceof EasyspecsConfigInvalidJsonError) {
7761
+ throw e;
7762
+ }
7763
+ throw new EasyspecsConfigInvalidJsonError(filePath, e);
7764
+ }
7765
+ }
7766
+ function readEasyspecsConfig(repoRoot) {
7767
+ const p = easyspecsConfigPath(repoRoot);
7768
+ const raw = fs5.readFileSync(p, "utf8");
7769
+ return parseEasyspecsConfigJson(raw, p);
7770
+ }
7771
+ function cliJsonFileToConfigPartial(legacy) {
7772
+ const es = legacy.easyspecs ?? {};
7773
+ const analysis = es.analysis ?? {};
7774
+ const orch = es.orchestration ?? {};
7775
+ const partial = {
7776
+ easyspecs: {
7777
+ apiBaseUrl: legacy.apiBaseUrl ?? es.apiBaseUrl ?? "",
7778
+ openCode: es.openCode,
7779
+ analysis: {
7780
+ openCodeTestArgv: analysis.openCodeTestArgv,
7781
+ openCodeTestTimeoutMs: analysis.openCodeTestTimeoutMs,
7782
+ listJsonSchemaRepairAttempts: analysis.listJsonSchemaRepairAttempts,
7783
+ markdownEvidenceRepairAttempts: analysis.markdownEvidenceRepairAttempts,
7784
+ markdownOpenQuestionIterations: analysis.markdownOpenQuestionIterations,
7785
+ maxConcurrentOpenCodeAgents: analysis.maxConcurrentOpenCodeAgents,
7786
+ promoteContextToWorkspace: analysis.promoteContextToWorkspace
7787
+ },
7788
+ orchestration: {
7789
+ initialDelayMs: orch.initialDelayMs,
7790
+ backoffMultiplier: orch.backoffMultiplier,
7791
+ maxDelayMs: orch.maxDelayMs,
7792
+ maxOuterIterationsPerPhase: orch.maxOuterIterationsPerPhase,
7793
+ maxMacroPingPongCycles: orch.maxMacroPingPongCycles,
7794
+ synthesisRemediationShareBackoff: orch.synthesisRemediationShareBackoff
7795
+ }
7796
+ }
7797
+ };
7798
+ return partial;
7799
+ }
7800
+ function atomicWriteFile(targetPath, contents) {
7801
+ const dir = path6.dirname(targetPath);
7802
+ fs5.mkdirSync(dir, { recursive: true });
7803
+ const tmp = path6.join(dir, `.${path6.basename(targetPath)}.${process.pid}.${Date.now()}.tmp`);
7804
+ fs5.writeFileSync(tmp, contents, "utf8");
7805
+ fs5.renameSync(tmp, targetPath);
7806
+ }
7807
+ function buildFreshEasyspecsConfigFromLegacy(repoRoot, warnMigration) {
7808
+ let base = getDefaultEasyspecsConfig();
7809
+ const legacyPath = easyspecsLegacyCliJsonPath(repoRoot);
7810
+ if (fs5.existsSync(legacyPath)) {
7811
+ try {
7812
+ const rawLegacy = fs5.readFileSync(legacyPath, "utf8");
7813
+ const legacyParsed = JSON.parse(rawLegacy);
7814
+ base = mergeEasyspecsConfigDefaults(base, cliJsonFileToConfigPartial(legacyParsed));
7815
+ warnMigration?.(`Imported settings from ${LEGACY_CLI_JSON} into ${CONFIG_BASENAME} (SRS-43 migration).`);
7816
+ } catch (e) {
7817
+ warnMigration?.(`Could not import ${LEGACY_CLI_JSON}: ${e instanceof Error ? e.message : String(e)}`);
7818
+ }
7819
+ }
7820
+ const settingsLegacyPath = easyspecsLegacySettingsJsonPath(repoRoot);
7821
+ if (fs5.existsSync(settingsLegacyPath)) {
7822
+ try {
7823
+ const rawS = fs5.readFileSync(settingsLegacyPath, "utf8");
7824
+ const leg = JSON.parse(rawS);
7825
+ const ace = {};
7826
+ if (Object.prototype.hasOwnProperty.call(leg, EASYSPECS_SETTINGS_ACE_KEY)) {
7827
+ const v = leg[EASYSPECS_SETTINGS_ACE_KEY];
7828
+ if (typeof v === "boolean") {
7829
+ ace.enabled = v;
7830
+ }
7831
+ }
7832
+ if (Object.prototype.hasOwnProperty.call(leg, EASYSPECS_SETTINGS_ACE_OFFLINE_AFTER_TRACE_KEY)) {
7833
+ const v = leg[EASYSPECS_SETTINGS_ACE_OFFLINE_AFTER_TRACE_KEY];
7834
+ if (typeof v === "boolean") {
7835
+ ace.offlineLearnAfterSameSessionTrace = v;
7836
+ }
7837
+ }
7838
+ if (Object.keys(ace).length > 0) {
7839
+ base = mergeEasyspecsConfigDefaults(base, { easyspecs: { analysis: { ace } } });
7840
+ warnMigration?.(`Imported ACE toggles from ${LEGACY_SETTINGS_JSON} into ${CONFIG_BASENAME}.`);
7841
+ }
7842
+ } catch (e) {
7843
+ warnMigration?.(
7844
+ `Could not import ${LEGACY_SETTINGS_JSON}: ${e instanceof Error ? e.message : String(e)}`
7845
+ );
7846
+ }
7847
+ }
7848
+ return base;
7849
+ }
7850
+ function initEasyspecsConfigFile(repoRoot, opts) {
7851
+ const configPath = easyspecsConfigPath(repoRoot);
7852
+ const existed = fs5.existsSync(configPath);
7853
+ if (existed && !opts.overwrite) {
7854
+ return { outcome: "unchanged", path: configPath, config: readEasyspecsConfig(repoRoot) };
7855
+ }
7856
+ const base = buildFreshEasyspecsConfigFromLegacy(repoRoot, opts.warnMigration);
7857
+ atomicWriteFile(configPath, `${JSON.stringify(base, null, 2)}
7858
+ `);
7859
+ return {
7860
+ outcome: existed ? "overwritten" : "created",
7861
+ path: configPath,
7862
+ config: readEasyspecsConfig(repoRoot)
7863
+ };
7864
+ }
7865
+ function ensureEasyspecsConfig(repoRoot, opts) {
7866
+ const configPath = easyspecsConfigPath(repoRoot);
7867
+ const legacyPath = easyspecsLegacyCliJsonPath(repoRoot);
7868
+ if (fs5.existsSync(configPath)) {
7869
+ if (fs5.existsSync(legacyPath)) {
7870
+ opts.warnMigration?.(
7871
+ `${legacyPath} is deprecated alongside ${configPath}; prefer editing ${CONFIG_BASENAME} only.`
7872
+ );
7873
+ }
7874
+ const settingsLegacy = easyspecsLegacySettingsJsonPath(repoRoot);
7875
+ if (fs5.existsSync(settingsLegacy)) {
7876
+ opts.warnMigration?.(
7877
+ `${settingsLegacy} is deprecated; use easyspecs.analysis.ace in ${CONFIG_BASENAME} (see USER-MANUAL-SRS-35).`
7878
+ );
7879
+ }
7880
+ return readEasyspecsConfig(repoRoot);
7881
+ }
7882
+ const base = buildFreshEasyspecsConfigFromLegacy(repoRoot, opts.warnMigration);
7883
+ if (!opts.allowCreate) {
7884
+ return base;
7885
+ }
7886
+ const body = `${JSON.stringify(base, null, 2)}
7887
+ `;
7888
+ atomicWriteFile(configPath, body);
7889
+ return readEasyspecsConfig(repoRoot);
7890
+ }
7891
+ function updateEasyspecsConfig(repoRoot, patch, opts) {
7892
+ const configPath = easyspecsConfigPath(repoRoot);
7893
+ const base = fs5.existsSync(configPath) ? readEasyspecsConfig(repoRoot) : buildFreshEasyspecsConfigFromLegacy(repoRoot, opts?.warnMigration);
7894
+ const merged = mergeEasyspecsConfigDefaults(base, patch);
7895
+ atomicWriteFile(configPath, `${JSON.stringify(merged, null, 2)}
7896
+ `);
7897
+ return merged;
7898
+ }
7899
+
7900
+ // src/config/easyspecsConfigRedact.ts
7901
+ function redactEasyspecsConfigRootForDump(cfg) {
7902
+ const o = JSON.parse(JSON.stringify(cfg));
7903
+ const es = o.easyspecs;
7904
+ const rt = es?.openCodeRuntime;
7905
+ const prov = rt?.providers;
7906
+ if (prov && typeof prov === "object") {
7907
+ for (const p of Object.values(prov)) {
7908
+ if (p && typeof p === "object" && "apiKey" in p) {
7909
+ p.apiKey = "(redacted)";
7910
+ }
7911
+ }
7912
+ }
7913
+ return o;
7594
7914
  }
7595
7915
 
7596
7916
  // src/cli/cliFileWorkspaceState.ts
7597
- var fs5 = __toESM(require("node:fs"));
7598
- var path5 = __toESM(require("node:path"));
7917
+ var fs6 = __toESM(require("node:fs"));
7918
+ var path7 = __toESM(require("node:path"));
7599
7919
  function createFileBackedWorkspaceState(repoRoot) {
7600
- const dir = path5.join(repoRoot, ".gluecharm", "logs");
7601
- const file = path5.join(dir, "easyspecs-cli-workspace-state.json");
7602
- fs5.mkdirSync(dir, { recursive: true });
7920
+ const dir = path7.join(repoRoot, ".gluecharm", "logs");
7921
+ const file = path7.join(dir, "easyspecs-cli-workspace-state.json");
7922
+ fs6.mkdirSync(dir, { recursive: true });
7603
7923
  const load = () => {
7604
7924
  try {
7605
- if (!fs5.existsSync(file)) {
7925
+ if (!fs6.existsSync(file)) {
7606
7926
  return {};
7607
7927
  }
7608
- const j = JSON.parse(fs5.readFileSync(file, "utf8"));
7928
+ const j = JSON.parse(fs6.readFileSync(file, "utf8"));
7609
7929
  return typeof j === "object" && j !== null && !Array.isArray(j) ? j : {};
7610
7930
  } catch {
7611
7931
  return {};
7612
7932
  }
7613
7933
  };
7614
7934
  const save = (m) => {
7615
- fs5.writeFileSync(file, `${JSON.stringify(m, null, 2)}
7935
+ fs6.writeFileSync(file, `${JSON.stringify(m, null, 2)}
7616
7936
  `, "utf8");
7617
7937
  };
7618
7938
  const workspaceState = {
@@ -7638,8 +7958,8 @@ var fs27 = __toESM(require("fs"));
7638
7958
  var path25 = __toESM(require("path"));
7639
7959
 
7640
7960
  // src/analysis/analysisDynamicTestSteps.ts
7641
- var fs6 = __toESM(require("fs"));
7642
- var path6 = __toESM(require("path"));
7961
+ var fs7 = __toESM(require("fs"));
7962
+ var path8 = __toESM(require("path"));
7643
7963
  var FE_CODE = /^FE-\d+$/;
7644
7964
  var UC_CODE = /^UC-\d+$/;
7645
7965
  var DM_CODE = /^DM-\d+$/;
@@ -7648,7 +7968,7 @@ function stripUtf8Bom(s) {
7648
7968
  }
7649
7969
  function readJson(filePath) {
7650
7970
  try {
7651
- const raw = stripUtf8Bom(fs6.readFileSync(filePath, "utf-8"));
7971
+ const raw = stripUtf8Bom(fs7.readFileSync(filePath, "utf-8"));
7652
7972
  return JSON.parse(raw);
7653
7973
  } catch {
7654
7974
  return null;
@@ -7681,7 +8001,7 @@ function slugOrFallbackCoord(row2) {
7681
8001
  return null;
7682
8002
  }
7683
8003
  function discoverExperienceTreeRows(contextDir2) {
7684
- const expPath = path6.join(contextDir2, "experiences-list.json");
8004
+ const expPath = path8.join(contextDir2, "experiences-list.json");
7685
8005
  const exp = readJson(expPath);
7686
8006
  const views = Array.isArray(exp?.views) ? exp.views : [];
7687
8007
  const out = [];
@@ -7714,7 +8034,7 @@ function discoverExperienceTreeRows(contextDir2) {
7714
8034
  return out;
7715
8035
  }
7716
8036
  function discoverServiceTreeRows(contextDir2) {
7717
- const svcPath = path6.join(contextDir2, "services-list.json");
8037
+ const svcPath = path8.join(contextDir2, "services-list.json");
7718
8038
  const svcFile = readJson(svcPath);
7719
8039
  const svcs = Array.isArray(svcFile?.services) ? svcFile.services : [];
7720
8040
  const out = [];
@@ -7747,7 +8067,7 @@ function discoverServiceTreeRows(contextDir2) {
7747
8067
  return out;
7748
8068
  }
7749
8069
  function discoverTechStackToolRows(contextDir2) {
7750
- const tsPath = path6.join(contextDir2, "tech-stack-list.json");
8070
+ const tsPath = path8.join(contextDir2, "tech-stack-list.json");
7751
8071
  const tsFile = readJson(tsPath);
7752
8072
  const tools = Array.isArray(tsFile?.tools) ? tsFile.tools : [];
7753
8073
  const out = [];
@@ -7770,7 +8090,7 @@ function safeFeatureName(v) {
7770
8090
  }
7771
8091
  function nonEmptyContextFile(absolutePath) {
7772
8092
  try {
7773
- const st = fs6.statSync(absolutePath);
8093
+ const st = fs7.statSync(absolutePath);
7774
8094
  return st.isFile() && st.size > 0;
7775
8095
  } catch {
7776
8096
  return false;
@@ -7787,17 +8107,17 @@ var ANALYSIS_STATIC_CONTEXT_OUTPUTS = [
7787
8107
  "tech-stack-list.json"
7788
8108
  ];
7789
8109
  function discoverDynamicAnalysisTestSteps(contextDir2) {
7790
- const staticOutputs = ANALYSIS_STATIC_CONTEXT_OUTPUTS.map((basename11) => ({
7791
- basename: basename11,
7792
- exists: nonEmptyContextFile(path6.join(contextDir2, basename11))
8110
+ const staticOutputs = ANALYSIS_STATIC_CONTEXT_OUTPUTS.map((basename12) => ({
8111
+ basename: basename12,
8112
+ exists: nonEmptyContextFile(path8.join(contextDir2, basename12))
7793
8113
  }));
7794
- const featuresPath = path6.join(contextDir2, "features-list.json");
8114
+ const featuresPath = path8.join(contextDir2, "features-list.json");
7795
8115
  const featuresData = readJson(featuresPath);
7796
8116
  const rawFeatures = Array.isArray(featuresData?.features) ? featuresData.features : [];
7797
8117
  const entityFieldsRuns = [];
7798
8118
  const useCaseRuns = [];
7799
8119
  const scenarioRuns = [];
7800
- const dmPath = path6.join(contextDir2, "data-model-list.json");
8120
+ const dmPath = path8.join(contextDir2, "data-model-list.json");
7801
8121
  const dmData = readJson(dmPath);
7802
8122
  const rawEntities = Array.isArray(dmData?.entities) ? dmData.entities : [];
7803
8123
  for (const erow of rawEntities) {
@@ -7806,7 +8126,7 @@ function discoverDynamicAnalysisTestSteps(contextDir2) {
7806
8126
  continue;
7807
8127
  }
7808
8128
  const ename = safeFeatureName(erow.name);
7809
- const fieldsListPath = path6.join(contextDir2, `${dm}-fields-list.json`);
8129
+ const fieldsListPath = path8.join(contextDir2, `${dm}-fields-list.json`);
7810
8130
  entityFieldsRuns.push({
7811
8131
  entityCode: dm,
7812
8132
  entityName: ename,
@@ -7819,7 +8139,7 @@ function discoverDynamicAnalysisTestSteps(contextDir2) {
7819
8139
  continue;
7820
8140
  }
7821
8141
  const name = safeFeatureName(row2.name);
7822
- const useCasesListPath = path6.join(contextDir2, `${code}-use-cases-list.json`);
8142
+ const useCasesListPath = path8.join(contextDir2, `${code}-use-cases-list.json`);
7823
8143
  useCaseRuns.push({
7824
8144
  featureCode: code,
7825
8145
  featureName: name,
@@ -7839,54 +8159,54 @@ function discoverDynamicAnalysisTestSteps(contextDir2) {
7839
8159
  useCaseCode: ucc,
7840
8160
  useCaseName: ucName,
7841
8161
  featureName: name,
7842
- scenariosListExists: nonEmptyContextFile(path6.join(contextDir2, scenariosBasename))
8162
+ scenariosListExists: nonEmptyContextFile(path8.join(contextDir2, scenariosBasename))
7843
8163
  });
7844
8164
  }
7845
8165
  }
7846
- const entityFieldsEmptyHint = !fs6.existsSync(dmPath) || dmData === null ? "No data-model-list.json yet \u2014 run the data model list step after Materialize agents." : entityFieldsRuns.length === 0 ? "data-model-list.json has no valid DM-nn rows \u2014 run the data model step or edit that file." : "";
7847
- const expPath = path6.join(contextDir2, "experiences-list.json");
8166
+ const entityFieldsEmptyHint = !fs7.existsSync(dmPath) || dmData === null ? "No data-model-list.json yet \u2014 run the data model list step after Materialize agents." : entityFieldsRuns.length === 0 ? "data-model-list.json has no valid DM-nn rows \u2014 run the data model step or edit that file." : "";
8167
+ const expPath = path8.join(contextDir2, "experiences-list.json");
7848
8168
  const expFileData = readJson(expPath);
7849
8169
  const expTreeRows = discoverExperienceTreeRows(contextDir2);
7850
8170
  const experienceViewRuns = expTreeRows.map((row2) => ({
7851
8171
  code: row2.code,
7852
8172
  name: row2.name,
7853
8173
  slug: row2.slug,
7854
- viewDetailExists: nonEmptyContextFile(path6.join(contextDir2, `${row2.code}-${row2.slug}.md`)),
8174
+ viewDetailExists: nonEmptyContextFile(path8.join(contextDir2, `${row2.code}-${row2.slug}.md`)),
7855
8175
  interactions: row2.interactions.map((ix) => ({
7856
8176
  code: ix.code,
7857
8177
  name: ix.name,
7858
8178
  slug: ix.slug,
7859
- detailExists: nonEmptyContextFile(path6.join(contextDir2, `${row2.code}_${ix.code}-${ix.slug}.md`))
8179
+ detailExists: nonEmptyContextFile(path8.join(contextDir2, `${row2.code}_${ix.code}-${ix.slug}.md`))
7860
8180
  }))
7861
8181
  }));
7862
- const experienceEmptyHint = !fs6.existsSync(expPath) || expFileData === null ? "No experiences-list.json yet \u2014 run the experiences list step after Materialize agents." : experienceViewRuns.length === 0 ? "experiences-list.json has no valid XP-* views with resolvable URL-safe slugs (check code/slug fields and JSON syntax, including UTF-8 BOM)." : "";
7863
- const svcPath = path6.join(contextDir2, "services-list.json");
8182
+ const experienceEmptyHint = !fs7.existsSync(expPath) || expFileData === null ? "No experiences-list.json yet \u2014 run the experiences list step after Materialize agents." : experienceViewRuns.length === 0 ? "experiences-list.json has no valid XP-* views with resolvable URL-safe slugs (check code/slug fields and JSON syntax, including UTF-8 BOM)." : "";
8183
+ const svcPath = path8.join(contextDir2, "services-list.json");
7864
8184
  const svcFileData = readJson(svcPath);
7865
8185
  const svcTreeRows = discoverServiceTreeRows(contextDir2);
7866
8186
  const serviceRuns = svcTreeRows.map((row2) => ({
7867
8187
  code: row2.code,
7868
8188
  name: row2.name,
7869
8189
  slug: row2.slug,
7870
- serviceDetailExists: nonEmptyContextFile(path6.join(contextDir2, `${row2.code}-${row2.slug}.md`)),
8190
+ serviceDetailExists: nonEmptyContextFile(path8.join(contextDir2, `${row2.code}-${row2.slug}.md`)),
7871
8191
  methods: row2.methods.map((m) => ({
7872
8192
  code: m.code,
7873
8193
  name: m.name,
7874
8194
  slug: m.slug,
7875
- detailExists: nonEmptyContextFile(path6.join(contextDir2, `${row2.code}_${m.code}-${m.slug}.md`))
8195
+ detailExists: nonEmptyContextFile(path8.join(contextDir2, `${row2.code}_${m.code}-${m.slug}.md`))
7876
8196
  }))
7877
8197
  }));
7878
- const servicesEmptyHint = !fs6.existsSync(svcPath) || svcFileData === null ? "No services-list.json yet \u2014 run the services list step after Materialize agents." : serviceRuns.length === 0 ? "services-list.json has no valid SV-* services with resolvable URL-safe slugs (check code/slug fields and JSON syntax, including UTF-8 BOM)." : "";
7879
- const tsListPath = path6.join(contextDir2, "tech-stack-list.json");
8198
+ const servicesEmptyHint = !fs7.existsSync(svcPath) || svcFileData === null ? "No services-list.json yet \u2014 run the services list step after Materialize agents." : serviceRuns.length === 0 ? "services-list.json has no valid SV-* services with resolvable URL-safe slugs (check code/slug fields and JSON syntax, including UTF-8 BOM)." : "";
8199
+ const tsListPath = path8.join(contextDir2, "tech-stack-list.json");
7880
8200
  const tsListFileData = readJson(tsListPath);
7881
8201
  const toolTreeRows = discoverTechStackToolRows(contextDir2);
7882
8202
  const techStackToolRuns = toolTreeRows.map((row2) => ({
7883
8203
  code: row2.code,
7884
8204
  name: row2.name,
7885
8205
  slug: row2.slug,
7886
- toolDetailExists: nonEmptyContextFile(path6.join(contextDir2, `${row2.code}-${row2.slug}.md`))
8206
+ toolDetailExists: nonEmptyContextFile(path8.join(contextDir2, `${row2.code}-${row2.slug}.md`))
7887
8207
  }));
7888
- const techStackEmptyHint = !fs6.existsSync(tsListPath) || tsListFileData === null ? "No tech-stack-list.json yet \u2014 run the tech stack list step after Materialize agents." : techStackToolRuns.length === 0 ? "tech-stack-list.json has no valid TS-* tools with resolvable URL-safe slugs (check code/slug fields and JSON syntax, including UTF-8 BOM)." : "";
7889
- const useCaseEmptyHint = !fs6.existsSync(featuresPath) || featuresData === null ? "No features-list.json yet \u2014 run step 3 (features list) after Materialize agents." : useCaseRuns.length === 0 ? "features-list.json has no valid FE-nn rows \u2014 run step 3 or edit that file." : "";
8208
+ const techStackEmptyHint = !fs7.existsSync(tsListPath) || tsListFileData === null ? "No tech-stack-list.json yet \u2014 run the tech stack list step after Materialize agents." : techStackToolRuns.length === 0 ? "tech-stack-list.json has no valid TS-* tools with resolvable URL-safe slugs (check code/slug fields and JSON syntax, including UTF-8 BOM)." : "";
8209
+ const useCaseEmptyHint = !fs7.existsSync(featuresPath) || featuresData === null ? "No features-list.json yet \u2014 run step 3 (features list) after Materialize agents." : useCaseRuns.length === 0 ? "features-list.json has no valid FE-nn rows \u2014 run step 3 or edit that file." : "";
7890
8210
  const scenarioEmptyHint = scenarioRuns.length === 0 ? useCaseRuns.length === 0 ? "Scenario runs appear after features exist and per-feature use-case lists define UC rows." : "Run a per-feature use case list (above) or add UC rows to each FE-nn-use-cases-list.json." : "";
7891
8211
  return {
7892
8212
  staticOutputs,
@@ -7906,15 +8226,15 @@ function discoverDynamicAnalysisTestSteps(contextDir2) {
7906
8226
  }
7907
8227
 
7908
8228
  // src/analysis/materializeOpenCodeAgents.ts
7909
- var fs9 = __toESM(require("fs"));
7910
- var path9 = __toESM(require("path"));
8229
+ var fs10 = __toESM(require("fs"));
8230
+ var path11 = __toESM(require("path"));
7911
8231
 
7912
8232
  // src/analysis/applyAceMaterializedAgents.ts
7913
- var fs8 = __toESM(require("fs"));
7914
- var path8 = __toESM(require("path"));
8233
+ var fs9 = __toESM(require("fs"));
8234
+ var path10 = __toESM(require("path"));
7915
8235
 
7916
8236
  // src/analysis/acePaths.ts
7917
- var path7 = __toESM(require("path"));
8237
+ var path9 = __toESM(require("path"));
7918
8238
  var ACE_LEARNINGS_DIR = "learnings";
7919
8239
  var ACE_OVERLAYS_SUBDIR = "overlays";
7920
8240
  var ACE_TRACES_SUBDIR = "traces";
@@ -7927,10 +8247,10 @@ var ACE_SCHEMA_TRACE = "ace-generator-trace.schema.json";
7927
8247
  var ACE_SCHEMA_REFLECTOR = "ace-reflector-lessons.schema.json";
7928
8248
  var ACE_SCHEMA_CURATOR = "ace-curator-delta.schema.json";
7929
8249
  function gluecharmContextDir(worktreeRoot) {
7930
- return path7.join(worktreeRoot, ".gluecharm", "context");
8250
+ return path9.join(worktreeRoot, ".gluecharm", "context");
7931
8251
  }
7932
8252
  function aceLearningsRoot(contextDir2) {
7933
- return path7.join(contextDir2, ACE_LEARNINGS_DIR);
8253
+ return path9.join(contextDir2, ACE_LEARNINGS_DIR);
7934
8254
  }
7935
8255
  function sanitizeAcePathSegment(s) {
7936
8256
  return s.replace(/[^a-zA-Z0-9_.-]+/g, "_").slice(0, 200) || "id";
@@ -7943,29 +8263,29 @@ function aceTraceFileRunStem(artefactRunId, openCodeSessionId) {
7943
8263
  return joined.length <= 240 ? joined : joined.slice(0, 240);
7944
8264
  }
7945
8265
  function acePlaybookPath(contextDir2, agentStem) {
7946
- return path7.join(aceLearningsRoot(contextDir2), `${agentStem}.json`);
8266
+ return path9.join(aceLearningsRoot(contextDir2), `${agentStem}.json`);
7947
8267
  }
7948
8268
  function aceOverlayPath(contextDir2, agentStem) {
7949
- return path7.join(aceLearningsRoot(contextDir2), ACE_OVERLAYS_SUBDIR, `${agentStem}.json`);
8269
+ return path9.join(aceLearningsRoot(contextDir2), ACE_OVERLAYS_SUBDIR, `${agentStem}.json`);
7950
8270
  }
7951
8271
  function aceTracePath(contextDir2, agentStem, runId) {
7952
- return path7.join(aceLearningsRoot(contextDir2), ACE_TRACES_SUBDIR, agentStem, `${runId}-trace.json`);
8272
+ return path9.join(aceLearningsRoot(contextDir2), ACE_TRACES_SUBDIR, agentStem, `${runId}-trace.json`);
7953
8273
  }
7954
8274
  function aceReflectionPath(contextDir2, agentStem, runId) {
7955
- return path7.join(aceLearningsRoot(contextDir2), ACE_REFLECTIONS_SUBDIR, agentStem, `${runId}-lessons.json`);
8275
+ return path9.join(aceLearningsRoot(contextDir2), ACE_REFLECTIONS_SUBDIR, agentStem, `${runId}-lessons.json`);
7956
8276
  }
7957
8277
  function aceCurationPath(contextDir2, agentStem, runId) {
7958
- return path7.join(aceLearningsRoot(contextDir2), ACE_CURATIONS_SUBDIR, agentStem, `${runId}-delta.json`);
8278
+ return path9.join(aceLearningsRoot(contextDir2), ACE_CURATIONS_SUBDIR, agentStem, `${runId}-delta.json`);
7959
8279
  }
7960
8280
  function aceConsolidatedSessionsJsonlPath(contextDir2) {
7961
- return path7.join(aceLearningsRoot(contextDir2), ACE_CONSOLIDATED_SESSIONS_JSONL);
8281
+ return path9.join(aceLearningsRoot(contextDir2), ACE_CONSOLIDATED_SESSIONS_JSONL);
7962
8282
  }
7963
- function opencodeAceSchemaPath(worktreeRoot, basename11) {
7964
- return path7.join(worktreeRoot, ".opencode", "schemas", "ace", basename11);
8283
+ function opencodeAceSchemaPath(worktreeRoot, basename12) {
8284
+ return path9.join(worktreeRoot, ".opencode", "schemas", "ace", basename12);
7965
8285
  }
7966
8286
 
7967
8287
  // src/analysis/aceJsonValidate.ts
7968
- var fs7 = __toESM(require("fs"));
8288
+ var fs8 = __toESM(require("fs"));
7969
8289
  var import__ = __toESM(require__());
7970
8290
  function stripUtf8Bom2(s) {
7971
8291
  return s.length > 0 && s.charCodeAt(0) === 65279 ? s.slice(1) : s;
@@ -7983,7 +8303,7 @@ function formatAjvErrors(errors) {
7983
8303
  function validateAceJsonFile(jsonAbsolutePath, schemaAbsolutePath) {
7984
8304
  let raw;
7985
8305
  try {
7986
- raw = stripUtf8Bom2(fs7.readFileSync(jsonAbsolutePath, "utf-8"));
8306
+ raw = stripUtf8Bom2(fs8.readFileSync(jsonAbsolutePath, "utf-8"));
7987
8307
  } catch (e) {
7988
8308
  return { ok: false, kind: "read", message: e instanceof Error ? e.message : String(e) };
7989
8309
  }
@@ -8071,7 +8391,7 @@ function balancedJsonObjectFrom(s, start) {
8071
8391
  function validateAceJsonValue(data, schemaAbsolutePath) {
8072
8392
  let schemaRaw;
8073
8393
  try {
8074
- schemaRaw = stripUtf8Bom2(fs7.readFileSync(schemaAbsolutePath, "utf-8"));
8394
+ schemaRaw = stripUtf8Bom2(fs8.readFileSync(schemaAbsolutePath, "utf-8"));
8075
8395
  } catch (e) {
8076
8396
  return { ok: false, kind: "read", message: `Schema read failed: ${e instanceof Error ? e.message : String(e)}` };
8077
8397
  }
@@ -8165,26 +8485,26 @@ function applyAceToMaterializedAgents(worktreeRoot, options) {
8165
8485
  if (!options.enabled) {
8166
8486
  return;
8167
8487
  }
8168
- const agentsDir = path8.join(worktreeRoot, ".opencode", "agents");
8169
- if (!fs8.existsSync(agentsDir) || !fs8.statSync(agentsDir).isDirectory()) {
8488
+ const agentsDir = path10.join(worktreeRoot, ".opencode", "agents");
8489
+ if (!fs9.existsSync(agentsDir) || !fs9.statSync(agentsDir).isDirectory()) {
8170
8490
  log(`[ace] skip apply \u2014 missing agents dir: ${agentsDir}`);
8171
8491
  return;
8172
8492
  }
8173
8493
  const contextDir2 = gluecharmContextDir(worktreeRoot);
8174
8494
  const learningsRoot = aceLearningsRoot(contextDir2);
8175
- fs8.mkdirSync(learningsRoot, { recursive: true });
8495
+ fs9.mkdirSync(learningsRoot, { recursive: true });
8176
8496
  const playbookSchema = opencodeAceSchemaPath(worktreeRoot, ACE_SCHEMA_PLAYBOOK);
8177
8497
  const overlaySchema = opencodeAceSchemaPath(worktreeRoot, ACE_SCHEMA_OVERLAY);
8178
- const entries = fs8.readdirSync(agentsDir, { withFileTypes: true });
8498
+ const entries = fs9.readdirSync(agentsDir, { withFileTypes: true });
8179
8499
  for (const e of entries) {
8180
8500
  if (!e.isFile() || !e.name.endsWith(".md") || e.name === "README.md") {
8181
8501
  continue;
8182
8502
  }
8183
8503
  const stem = e.name.replace(/\.md$/i, "");
8184
- const agentPath = path8.join(agentsDir, e.name);
8504
+ const agentPath = path10.join(agentsDir, e.name);
8185
8505
  let raw;
8186
8506
  try {
8187
- raw = fs8.readFileSync(agentPath, "utf-8");
8507
+ raw = fs9.readFileSync(agentPath, "utf-8");
8188
8508
  } catch (err) {
8189
8509
  log(`[ace] skip ${stem} \u2014 read failed: ${err instanceof Error ? err.message : String(err)}`);
8190
8510
  continue;
@@ -8197,7 +8517,7 @@ function applyAceToMaterializedAgents(worktreeRoot, options) {
8197
8517
  const pbPath = acePlaybookPath(contextDir2, stem);
8198
8518
  const ovPath = aceOverlayPath(contextDir2, stem);
8199
8519
  let playbookDoc;
8200
- if (fs8.existsSync(pbPath)) {
8520
+ if (fs9.existsSync(pbPath)) {
8201
8521
  const v = validateAceJsonFile(pbPath, playbookSchema);
8202
8522
  if (v.ok) {
8203
8523
  const d = v.data;
@@ -8212,7 +8532,7 @@ ${v.errorsText.slice(0, 500)}` : ""}`
8212
8532
  }
8213
8533
  }
8214
8534
  let overlayDoc;
8215
- if (fs8.existsSync(ovPath)) {
8535
+ if (fs9.existsSync(ovPath)) {
8216
8536
  const v = validateAceJsonFile(ovPath, overlaySchema);
8217
8537
  if (v.ok) {
8218
8538
  overlayDoc = v.data;
@@ -8243,14 +8563,14 @@ ${extra}
8243
8563
 
8244
8564
  ${extra}
8245
8565
  `;
8246
- fs8.writeFileSync(agentPath, out, "utf-8");
8566
+ fs9.writeFileSync(agentPath, out, "utf-8");
8247
8567
  log(`[ace] merged playbook/overlay into materialized agent: ${e.name}`);
8248
8568
  }
8249
8569
  }
8250
8570
 
8251
8571
  // src/analysis/materializeOpenCodeAgents.ts
8252
8572
  function posixFsPath(absPath) {
8253
- return path9.resolve(absPath).split(path9.sep).join("/");
8573
+ return path11.resolve(absPath).split(path11.sep).join("/");
8254
8574
  }
8255
8575
  function mergeStringKeyedObject(permission, key, additions) {
8256
8576
  const cur = permission[key];
@@ -8270,10 +8590,22 @@ function ensurePathToolAllows(permission, tool, rules) {
8270
8590
  }
8271
8591
  mergeStringKeyedObject(permission, tool, rules);
8272
8592
  }
8273
- function ensureAnalysisOpenCodePermissionsConfig(analysisCheckoutRoot) {
8274
- const root = path9.resolve(analysisCheckoutRoot);
8593
+ function mergeDeepUnknown(base, over) {
8594
+ const out = { ...base };
8595
+ for (const [k, v] of Object.entries(over)) {
8596
+ const cur = out[k];
8597
+ if (v && typeof v === "object" && !Array.isArray(v) && cur && typeof cur === "object" && !Array.isArray(cur)) {
8598
+ out[k] = mergeDeepUnknown(cur, v);
8599
+ } else {
8600
+ out[k] = v;
8601
+ }
8602
+ }
8603
+ return out;
8604
+ }
8605
+ function ensureAnalysisOpenCodePermissionsConfig(analysisCheckoutRoot, projectConfigOverlay) {
8606
+ const root = path11.resolve(analysisCheckoutRoot);
8275
8607
  const r = posixFsPath(root);
8276
- const contextDir2 = posixFsPath(path9.join(root, ".gluecharm", "context"));
8608
+ const contextDir2 = posixFsPath(path11.join(root, ".gluecharm", "context"));
8277
8609
  const externalAllows = {
8278
8610
  [`${r}/**`]: "allow",
8279
8611
  [`${r}/.gluecharm/**`]: "allow",
@@ -8291,11 +8623,11 @@ function ensureAnalysisOpenCodePermissionsConfig(analysisCheckoutRoot) {
8291
8623
  [`${r}/**`]: "allow",
8292
8624
  [`${r}/*`]: "allow"
8293
8625
  };
8294
- const configPath = path9.join(root, "opencode.json");
8626
+ const configPath = path11.join(root, "opencode.json");
8295
8627
  let cfg = {};
8296
- if (fs9.existsSync(configPath)) {
8628
+ if (fs10.existsSync(configPath)) {
8297
8629
  try {
8298
- const raw = fs9.readFileSync(configPath, "utf-8");
8630
+ const raw = fs10.readFileSync(configPath, "utf-8");
8299
8631
  const parsed = JSON.parse(raw);
8300
8632
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
8301
8633
  cfg = { ...parsed };
@@ -8315,55 +8647,58 @@ function ensureAnalysisOpenCodePermissionsConfig(analysisCheckoutRoot) {
8315
8647
  if (cfg.$schema === void 0) {
8316
8648
  cfg.$schema = "https://opencode.ai/config.json";
8317
8649
  }
8318
- fs9.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
8650
+ if (projectConfigOverlay && Object.keys(projectConfigOverlay).length > 0) {
8651
+ cfg = mergeDeepUnknown(cfg, projectConfigOverlay);
8652
+ }
8653
+ fs10.writeFileSync(configPath, `${JSON.stringify(cfg, null, 2)}
8319
8654
  `, "utf-8");
8320
8655
  }
8321
8656
  function copyRecursive(src, dest) {
8322
- fs9.mkdirSync(dest, { recursive: true });
8323
- const entries = fs9.readdirSync(src, { withFileTypes: true });
8657
+ fs10.mkdirSync(dest, { recursive: true });
8658
+ const entries = fs10.readdirSync(src, { withFileTypes: true });
8324
8659
  for (const e of entries) {
8325
- const s = path9.join(src, e.name);
8326
- const d = path9.join(dest, e.name);
8660
+ const s = path11.join(src, e.name);
8661
+ const d = path11.join(dest, e.name);
8327
8662
  if (e.isDirectory()) {
8328
8663
  copyRecursive(s, d);
8329
8664
  } else {
8330
- fs9.copyFileSync(s, d);
8665
+ fs10.copyFileSync(s, d);
8331
8666
  }
8332
8667
  }
8333
8668
  }
8334
- function materializeOpenCodeAgents(extensionResourcesAgents, analysisCheckoutRoot) {
8335
- const dest = path9.join(analysisCheckoutRoot, ".opencode", "agents");
8336
- if (!fs9.existsSync(extensionResourcesAgents)) {
8337
- fs9.mkdirSync(dest, { recursive: true });
8338
- fs9.writeFileSync(
8339
- path9.join(dest, "README.md"),
8669
+ function materializeOpenCodeAgents(extensionResourcesAgents, analysisCheckoutRoot, projectConfigOverlay) {
8670
+ const dest = path11.join(analysisCheckoutRoot, ".opencode", "agents");
8671
+ if (!fs10.existsSync(extensionResourcesAgents)) {
8672
+ fs10.mkdirSync(dest, { recursive: true });
8673
+ fs10.writeFileSync(
8674
+ path11.join(dest, "README.md"),
8340
8675
  "# OpenCode agents\n\nPlaceholder: add agent configs here. Bundled folder was missing at materialize time.\n",
8341
8676
  "utf-8"
8342
8677
  );
8343
- ensureAnalysisOpenCodePermissionsConfig(analysisCheckoutRoot);
8678
+ ensureAnalysisOpenCodePermissionsConfig(analysisCheckoutRoot, projectConfigOverlay);
8344
8679
  return;
8345
8680
  }
8346
8681
  copyRecursive(extensionResourcesAgents, dest);
8347
- const schemasSrc = path9.join(path9.dirname(extensionResourcesAgents), "schemas", "context-lists");
8348
- if (fs9.existsSync(schemasSrc)) {
8349
- const schemasDest = path9.join(analysisCheckoutRoot, ".opencode", "schemas", "context-lists");
8682
+ const schemasSrc = path11.join(path11.dirname(extensionResourcesAgents), "schemas", "context-lists");
8683
+ if (fs10.existsSync(schemasSrc)) {
8684
+ const schemasDest = path11.join(analysisCheckoutRoot, ".opencode", "schemas", "context-lists");
8350
8685
  copyRecursive(schemasSrc, schemasDest);
8351
8686
  }
8352
- const aceSchemasSrc = path9.join(path9.dirname(extensionResourcesAgents), "schemas", "ace");
8353
- if (fs9.existsSync(aceSchemasSrc)) {
8354
- const aceSchemasDest = path9.join(analysisCheckoutRoot, ".opencode", "schemas", "ace");
8687
+ const aceSchemasSrc = path11.join(path11.dirname(extensionResourcesAgents), "schemas", "ace");
8688
+ if (fs10.existsSync(aceSchemasSrc)) {
8689
+ const aceSchemasDest = path11.join(analysisCheckoutRoot, ".opencode", "schemas", "ace");
8355
8690
  copyRecursive(aceSchemasSrc, aceSchemasDest);
8356
8691
  }
8357
- ensureAnalysisOpenCodePermissionsConfig(analysisCheckoutRoot);
8692
+ ensureAnalysisOpenCodePermissionsConfig(analysisCheckoutRoot, projectConfigOverlay);
8358
8693
  }
8359
8694
  function materializeOpenCodeAgentsWithAce(extensionResourcesAgents, analysisCheckoutRoot, ace) {
8360
- materializeOpenCodeAgents(extensionResourcesAgents, analysisCheckoutRoot);
8695
+ materializeOpenCodeAgents(extensionResourcesAgents, analysisCheckoutRoot, ace.projectConfigOverlay);
8361
8696
  applyAceToMaterializedAgents(analysisCheckoutRoot, ace);
8362
8697
  }
8363
8698
 
8364
8699
  // src/analysis/openCodeTestAgent.ts
8365
- var fs19 = __toESM(require("fs"));
8366
- var path16 = __toESM(require("path"));
8700
+ var fs20 = __toESM(require("fs"));
8701
+ var path18 = __toESM(require("path"));
8367
8702
 
8368
8703
  // src/analysis/promptTemplates.ts
8369
8704
  function fillMarkdownPrompt(p) {
@@ -8514,8 +8849,8 @@ function synthesisStepLabel(step, ctx) {
8514
8849
  }
8515
8850
 
8516
8851
  // src/analysis/aceTracePhase.ts
8517
- var fs14 = __toESM(require("fs"));
8518
- var path13 = __toESM(require("path"));
8852
+ var fs15 = __toESM(require("fs"));
8853
+ var path15 = __toESM(require("path"));
8519
8854
 
8520
8855
  // src/opencodeCli.ts
8521
8856
  var import_child_process = require("child_process");
@@ -8648,19 +8983,21 @@ function resolveExecutable(executable) {
8648
8983
  const t = executable?.trim();
8649
8984
  return t && t.length > 0 ? t : "opencode";
8650
8985
  }
8651
- function spawnCliSync(executable, args, cwd, timeoutMs) {
8986
+ function spawnCliSync(executable, args, cwd, timeoutMs, env) {
8987
+ const mergedEnv = env ? { ...process.env, ...env } : process.env;
8652
8988
  return (0, import_child_process.spawnSync)(executable, args, {
8653
8989
  encoding: "utf-8",
8654
8990
  cwd,
8655
8991
  shell: USE_SHELL,
8656
8992
  timeout: timeoutMs,
8657
- env: process.env
8993
+ env: mergedEnv
8658
8994
  });
8659
8995
  }
8660
- function isOpenCodeOnPath(executable = "opencode") {
8996
+ function isOpenCodeOnPath(executable = "opencode", probeEnv) {
8661
8997
  const cmd = resolveExecutable(executable);
8998
+ const extra = probeEnv ? { ...probeEnv } : void 0;
8662
8999
  for (const args of [["--version"], ["-h"], ["help"]]) {
8663
- const r = spawnCliSync(cmd, args, void 0, 12e3);
9000
+ const r = spawnCliSync(cmd, args, void 0, 12e3, extra);
8664
9001
  if (r.status === 0) {
8665
9002
  return true;
8666
9003
  }
@@ -8684,10 +9021,11 @@ function hasProviderEnvConfigured() {
8684
9021
  return false;
8685
9022
  }
8686
9023
  var CREDENTIAL_PROBE_ARG_SETS = [["auth", "status"], ["whoami"], ["auth", "whoami"]];
8687
- function isOpenCodeCredentialsReady(executable = "opencode") {
9024
+ function isOpenCodeCredentialsReady(executable = "opencode", options) {
8688
9025
  const cmd = resolveExecutable(executable);
9026
+ const mergedEnv = options?.mergedEnv ? { ...process.env, ...options.mergedEnv } : void 0;
8689
9027
  for (const args of CREDENTIAL_PROBE_ARG_SETS) {
8690
- const r = spawnCliSync(cmd, args, void 0, 15e3);
9028
+ const r = spawnCliSync(cmd, args, void 0, 15e3, mergedEnv);
8691
9029
  if (r.status === 0) {
8692
9030
  return true;
8693
9031
  }
@@ -8700,18 +9038,31 @@ ${r.stderr ?? ""}`.toLowerCase();
8700
9038
  if (hasProviderEnvConfigured()) {
8701
9039
  return true;
8702
9040
  }
9041
+ if (mergedEnv) {
9042
+ for (const k of PROVIDER_ENV_KEYS) {
9043
+ const v = mergedEnv[k];
9044
+ if (typeof v === "string" && v.trim().length > 0) {
9045
+ return true;
9046
+ }
9047
+ }
9048
+ }
8703
9049
  return false;
8704
9050
  }
8705
9051
  function getOpenCodeReadiness(options) {
8706
9052
  const exe = resolveExecutable(options?.executable);
8707
- const installed = isOpenCodeOnPath(exe);
9053
+ const probeEnv = options?.providerEnvFromConfig;
9054
+ const mergedProbe = probeEnv && Object.keys(probeEnv).length > 0 ? { ...probeEnv } : void 0;
9055
+ const installed = isOpenCodeOnPath(exe, probeEnv);
8708
9056
  if (!installed) {
8709
9057
  return { installed: false, credentialsReady: false };
8710
9058
  }
8711
9059
  if (options?.skipCredentialsCheck) {
8712
9060
  return { installed: true, credentialsReady: true };
8713
9061
  }
8714
- return { installed: true, credentialsReady: isOpenCodeCredentialsReady(exe) };
9062
+ return {
9063
+ installed: true,
9064
+ credentialsReady: isOpenCodeCredentialsReady(exe, { mergedEnv: mergedProbe })
9065
+ };
8715
9066
  }
8716
9067
  var DIAG_STDERR_MAX = 16384;
8717
9068
  var DIAG_STDOUT_MAX = 12288;
@@ -8744,15 +9095,16 @@ function runOpenCodeAgent(cwd, args, options) {
8744
9095
  log?.(`[OpenCode] command: ${formatCliCommandForLog(cmd, args)}`);
8745
9096
  log?.(`[OpenCode] cwd: ${JSON.stringify(cwd)}`);
8746
9097
  log?.(`[OpenCode] argv: ${JSON.stringify(args)}`);
8747
- return new Promise((resolve14) => {
9098
+ return new Promise((resolve15) => {
8748
9099
  if (sig?.aborted) {
8749
- resolve14({ ok: false, message: "Stopped by user.", cancelled: true });
9100
+ resolve15({ ok: false, message: "Stopped by user.", cancelled: true });
8750
9101
  return;
8751
9102
  }
9103
+ const spawnEnv = options?.childEnv && Object.keys(options.childEnv).length > 0 ? { ...process.env, ...options.childEnv } : process.env;
8752
9104
  const child = (0, import_child_process.spawn)(cmd, args, {
8753
9105
  cwd,
8754
9106
  shell: USE_SHELL,
8755
- env: process.env,
9107
+ env: spawnEnv,
8756
9108
  stdio: ["ignore", "pipe", "pipe"]
8757
9109
  });
8758
9110
  options?.onAgentLaunched?.();
@@ -8815,7 +9167,7 @@ ${truncateForDiag(outBody, DIAG_STDOUT_MAX)}`);
8815
9167
  if (diag) {
8816
9168
  finishDiag(diag.label, diag.code, diag.dumpStreams);
8817
9169
  }
8818
- resolve14(result);
9170
+ resolve15(result);
8819
9171
  };
8820
9172
  let onAbort;
8821
9173
  const clearAbortHandler = () => {
@@ -8890,7 +9242,7 @@ ${truncateForDiag(outBody, DIAG_STDOUT_MAX)}`);
8890
9242
  }
8891
9243
 
8892
9244
  // src/analysis/aceTraceNormalize.ts
8893
- var fs10 = __toESM(require("fs"));
9245
+ var fs11 = __toESM(require("fs"));
8894
9246
  var ACE_VERSION = "1.0.0-draft";
8895
9247
  var REASONING_PHASES = /* @__PURE__ */ new Set([
8896
9248
  "explore",
@@ -8900,7 +9252,7 @@ var REASONING_PHASES = /* @__PURE__ */ new Set([
8900
9252
  "repair",
8901
9253
  "finalize"
8902
9254
  ]);
8903
- function isRecord(v) {
9255
+ function isRecord2(v) {
8904
9256
  return v !== null && typeof v === "object" && !Array.isArray(v);
8905
9257
  }
8906
9258
  function sanitizeReasoningSteps(input, legacyTopLevelDecisions) {
@@ -8908,7 +9260,7 @@ function sanitizeReasoningSteps(input, legacyTopLevelDecisions) {
8908
9260
  input = [];
8909
9261
  }
8910
9262
  const steps = input.map((item, idx) => {
8911
- if (!isRecord(item)) {
9263
+ if (!isRecord2(item)) {
8912
9264
  return { stepId: `auto-${idx}`, phase: "explore", summary: "(invalid reasoningSteps entry)" };
8913
9265
  }
8914
9266
  const stepId = typeof item.stepId === "string" && item.stepId.trim() ? item.stepId : typeof item.step === "string" ? `legacy-${idx}` : `s${idx}`;
@@ -8955,7 +9307,7 @@ function sanitizeDecisions(input) {
8955
9307
  return [];
8956
9308
  }
8957
9309
  return input.map((item, idx) => {
8958
- if (!isRecord(item)) {
9310
+ if (!isRecord2(item)) {
8959
9311
  return {
8960
9312
  decisionId: `d${idx}`,
8961
9313
  topic: "",
@@ -8979,7 +9331,7 @@ function sanitizeFailures(input) {
8979
9331
  return [];
8980
9332
  }
8981
9333
  return input.map((item, idx) => {
8982
- if (!isRecord(item)) {
9334
+ if (!isRecord2(item)) {
8983
9335
  return { failureId: `f${idx}`, description: "(invalid failuresAndRetries entry)" };
8984
9336
  }
8985
9337
  const failureId = typeof item.failureId === "string" && item.failureId.trim() ? item.failureId : `f${idx}`;
@@ -8997,7 +9349,7 @@ function sanitizeEvidenceIndex(input) {
8997
9349
  }
8998
9350
  const out = [];
8999
9351
  for (const item of input) {
9000
- if (!isRecord(item)) {
9352
+ if (!isRecord2(item)) {
9001
9353
  continue;
9002
9354
  }
9003
9355
  const pathStr = typeof item.path === "string" ? item.path : typeof item.filePath === "string" ? item.filePath : "";
@@ -9020,7 +9372,7 @@ function sanitizePlaybookHooks(input) {
9020
9372
  const effects = /* @__PURE__ */ new Set(["applied", "violated", "absent"]);
9021
9373
  const out = [];
9022
9374
  for (const item of input) {
9023
- if (!isRecord(item)) {
9375
+ if (!isRecord2(item)) {
9024
9376
  continue;
9025
9377
  }
9026
9378
  const ruleRef = typeof item.ruleRef === "string" ? item.ruleRef : "";
@@ -9035,7 +9387,7 @@ function sanitizePlaybookHooks(input) {
9035
9387
  function rewriteAceTraceFileWithCanonicalEnvelope(traceAbsolutePath, canonical, diagnosticLog) {
9036
9388
  let raw;
9037
9389
  try {
9038
- raw = fs10.readFileSync(traceAbsolutePath, "utf-8");
9390
+ raw = fs11.readFileSync(traceAbsolutePath, "utf-8");
9039
9391
  } catch (e) {
9040
9392
  diagnosticLog?.(`[ace] trace normalize read failed: ${e instanceof Error ? e.message : String(e)}`);
9041
9393
  return false;
@@ -9047,7 +9399,7 @@ function rewriteAceTraceFileWithCanonicalEnvelope(traceAbsolutePath, canonical,
9047
9399
  diagnosticLog?.(`[ace] trace normalize JSON.parse failed: ${e instanceof Error ? e.message : String(e)}`);
9048
9400
  return false;
9049
9401
  }
9050
- const prev = isRecord(parsed) ? parsed : {};
9402
+ const prev = isRecord2(parsed) ? parsed : {};
9051
9403
  const sessionMeta = {
9052
9404
  openCodeSessionId: canonical.openCodeSessionId,
9053
9405
  runId: canonical.runId,
@@ -9055,7 +9407,7 @@ function rewriteAceTraceFileWithCanonicalEnvelope(traceAbsolutePath, canonical,
9055
9407
  startedAt: canonical.startedAt
9056
9408
  };
9057
9409
  const prevSm = prev.sessionMeta;
9058
- if (isRecord(prevSm)) {
9410
+ if (isRecord2(prevSm)) {
9059
9411
  if (typeof prevSm.sessionTitle === "string") {
9060
9412
  sessionMeta.sessionTitle = prevSm.sessionTitle;
9061
9413
  }
@@ -9080,7 +9432,7 @@ function rewriteAceTraceFileWithCanonicalEnvelope(traceAbsolutePath, canonical,
9080
9432
  doc.playbookHooks = hooks;
9081
9433
  }
9082
9434
  try {
9083
- fs10.writeFileSync(traceAbsolutePath, `${JSON.stringify(doc, null, 2)}
9435
+ fs11.writeFileSync(traceAbsolutePath, `${JSON.stringify(doc, null, 2)}
9084
9436
  `, "utf-8");
9085
9437
  } catch (e) {
9086
9438
  diagnosticLog?.(`[ace] trace normalize write failed: ${e instanceof Error ? e.message : String(e)}`);
@@ -9091,15 +9443,15 @@ function rewriteAceTraceFileWithCanonicalEnvelope(traceAbsolutePath, canonical,
9091
9443
  }
9092
9444
 
9093
9445
  // src/analysis/aceOfflineLearn.ts
9094
- var fs13 = __toESM(require("fs"));
9095
- var path12 = __toESM(require("path"));
9446
+ var fs14 = __toESM(require("fs"));
9447
+ var path14 = __toESM(require("path"));
9096
9448
 
9097
9449
  // src/analysis/aceCuratorApplier.ts
9098
- var fs11 = __toESM(require("fs"));
9099
- var path10 = __toESM(require("path"));
9450
+ var fs12 = __toESM(require("fs"));
9451
+ var path12 = __toESM(require("path"));
9100
9452
  function writeJson(pathAbs, obj) {
9101
- fs11.mkdirSync(path10.dirname(pathAbs), { recursive: true });
9102
- fs11.writeFileSync(pathAbs, `${JSON.stringify(obj, null, 2)}
9453
+ fs12.mkdirSync(path12.dirname(pathAbs), { recursive: true });
9454
+ fs12.writeFileSync(pathAbs, `${JSON.stringify(obj, null, 2)}
9103
9455
  `, "utf-8");
9104
9456
  }
9105
9457
  function emptyPlaybook(agentStem) {
@@ -9125,7 +9477,7 @@ function applyAceCuratorDeltaFile(worktreeRoot, deltaAbsolutePath, log) {
9125
9477
  const pbPath = acePlaybookPath(contextDir2, agentStem);
9126
9478
  const ovPath = aceOverlayPath(contextDir2, agentStem);
9127
9479
  let playbook;
9128
- if (fs11.existsSync(pbPath)) {
9480
+ if (fs12.existsSync(pbPath)) {
9129
9481
  const pv = validateAceJsonFile(pbPath, playbookSchema);
9130
9482
  if (!pv.ok) {
9131
9483
  return { ok: false, message: `Existing playbook invalid: ${pv.message}` };
@@ -9135,7 +9487,7 @@ function applyAceCuratorDeltaFile(worktreeRoot, deltaAbsolutePath, log) {
9135
9487
  playbook = emptyPlaybook(agentStem);
9136
9488
  }
9137
9489
  let overlay;
9138
- if (fs11.existsSync(ovPath)) {
9490
+ if (fs12.existsSync(ovPath)) {
9139
9491
  const ov = validateAceJsonFile(ovPath, overlaySchema);
9140
9492
  if (!ov.ok) {
9141
9493
  return { ok: false, message: `Existing overlay invalid: ${ov.message}` };
@@ -9208,13 +9560,13 @@ function applyAceCuratorDeltaFile(worktreeRoot, deltaAbsolutePath, log) {
9208
9560
  }
9209
9561
  writeJson(pbPath, playbook);
9210
9562
  writeJson(ovPath, overlay);
9211
- log?.(`[ace] curator applier wrote ${path10.relative(contextDir2, pbPath)} and overlay`);
9563
+ log?.(`[ace] curator applier wrote ${path12.relative(contextDir2, pbPath)} and overlay`);
9212
9564
  return { ok: true, message: "Applied curator delta" };
9213
9565
  }
9214
9566
 
9215
9567
  // src/analysis/aceOfflineLearnFallbacks.ts
9216
- var fs12 = __toESM(require("fs"));
9217
- var path11 = __toESM(require("path"));
9568
+ var fs13 = __toESM(require("fs"));
9569
+ var path13 = __toESM(require("path"));
9218
9570
  var ACE_VERSION2 = "1.0.0-draft";
9219
9571
  function buildEmptyReflectorLessons(traceRelativePath, sessionRunId) {
9220
9572
  return {
@@ -9237,9 +9589,9 @@ function buildEmptyCuratorDelta(agentStem, lessonsRelativePath) {
9237
9589
  }
9238
9590
  function appendAceConsolidatedSessionRecord(contextDir2, record, diagnosticLog) {
9239
9591
  const abs = aceConsolidatedSessionsJsonlPath(contextDir2);
9240
- const rel = path11.relative(contextDir2, abs).split(path11.sep).join("/");
9241
- fs12.mkdirSync(aceLearningsRoot(contextDir2), { recursive: true });
9242
- fs12.appendFileSync(abs, `${JSON.stringify(record)}
9592
+ const rel = path13.relative(contextDir2, abs).split(path13.sep).join("/");
9593
+ fs13.mkdirSync(aceLearningsRoot(contextDir2), { recursive: true });
9594
+ fs13.appendFileSync(abs, `${JSON.stringify(record)}
9243
9595
  `, "utf-8");
9244
9596
  diagnosticLog?.(`[ace] consolidated session record appended \u2014 ${rel}`);
9245
9597
  }
@@ -9261,7 +9613,7 @@ function safeRunIdSegment(runId) {
9261
9613
  }
9262
9614
  function readHead(p, max) {
9263
9615
  try {
9264
- const raw = fs13.readFileSync(p, "utf-8");
9616
+ const raw = fs14.readFileSync(p, "utf-8");
9265
9617
  return raw.length <= max ? raw : `${raw.slice(0, max)}
9266
9618
  \u2026 (truncated)`;
9267
9619
  } catch {
@@ -9269,14 +9621,14 @@ function readHead(p, max) {
9269
9621
  }
9270
9622
  }
9271
9623
  function listAceTraceFiles(contextDir2) {
9272
- const root = path12.join(aceLearningsRoot(contextDir2), "traces");
9273
- if (!fs13.existsSync(root)) {
9624
+ const root = path14.join(aceLearningsRoot(contextDir2), "traces");
9625
+ if (!fs14.existsSync(root)) {
9274
9626
  return [];
9275
9627
  }
9276
9628
  const out = [];
9277
9629
  const walk = (dir) => {
9278
- for (const e of fs13.readdirSync(dir, { withFileTypes: true })) {
9279
- const full = path12.join(dir, e.name);
9630
+ for (const e of fs14.readdirSync(dir, { withFileTypes: true })) {
9631
+ const full = path14.join(dir, e.name);
9280
9632
  if (e.isDirectory()) {
9281
9633
  walk(full);
9282
9634
  } else if (e.isFile() && e.name.endsWith(".json")) {
@@ -9297,20 +9649,20 @@ async function runAceOfflineLearnFromTrace(traceAbsolutePath, opts) {
9297
9649
  return { ok: false, message: `Trace invalid: ${tv.message}` };
9298
9650
  }
9299
9651
  const traceData = tv.data;
9300
- const runId = opts.artefactRunId ?? (typeof traceData.sessionMeta?.runId === "string" ? traceData.sessionMeta.runId : void 0) ?? path12.basename(traceAbsolutePath).replace(/-trace\.json$/i, "") ?? "unknown";
9301
- const agentStem = opts.agentStemOverride ?? (typeof traceData.sessionMeta?.agentStem === "string" ? traceData.sessionMeta.agentStem : path12.basename(path12.dirname(traceAbsolutePath)));
9652
+ const runId = opts.artefactRunId ?? (typeof traceData.sessionMeta?.runId === "string" ? traceData.sessionMeta.runId : void 0) ?? path14.basename(traceAbsolutePath).replace(/-trace\.json$/i, "") ?? "unknown";
9653
+ const agentStem = opts.agentStemOverride ?? (typeof traceData.sessionMeta?.agentStem === "string" ? traceData.sessionMeta.agentStem : path14.basename(path14.dirname(traceAbsolutePath)));
9302
9654
  const safeId = safeRunIdSegment(runId);
9303
- const traceRel = path12.relative(contextDir2, traceAbsolutePath).split(path12.sep).join("/");
9655
+ const traceRel = path14.relative(contextDir2, traceAbsolutePath).split(path14.sep).join("/");
9304
9656
  const lessonsAbs = aceReflectionPath(contextDir2, agentStem, safeId);
9305
9657
  const deltaAbs = aceCurationPath(contextDir2, agentStem, safeId);
9306
- fs13.mkdirSync(path12.dirname(lessonsAbs), { recursive: true });
9307
- fs13.mkdirSync(path12.dirname(deltaAbs), { recursive: true });
9308
- const pbPath = path12.join(aceLearningsRoot(contextDir2), `${agentStem}.json`);
9309
- const ovPath = path12.join(aceLearningsRoot(contextDir2), "overlays", `${agentStem}.json`);
9310
- const pbExcerpt = fs13.existsSync(pbPath) ? readHead(pbPath, 6e3) : "(no playbook yet)";
9311
- const ovExcerpt = fs13.existsSync(ovPath) ? readHead(ovPath, 4e3) : "(no overlay yet)";
9312
- const runDir = path12.join(opts.worktreeRoot, ".opencode", "_run");
9313
- fs13.mkdirSync(runDir, { recursive: true });
9658
+ fs14.mkdirSync(path14.dirname(lessonsAbs), { recursive: true });
9659
+ fs14.mkdirSync(path14.dirname(deltaAbs), { recursive: true });
9660
+ const pbPath = path14.join(aceLearningsRoot(contextDir2), `${agentStem}.json`);
9661
+ const ovPath = path14.join(aceLearningsRoot(contextDir2), "overlays", `${agentStem}.json`);
9662
+ const pbExcerpt = fs14.existsSync(pbPath) ? readHead(pbPath, 6e3) : "(no playbook yet)";
9663
+ const ovExcerpt = fs14.existsSync(ovPath) ? readHead(ovPath, 4e3) : "(no overlay yet)";
9664
+ const runDir = path14.join(opts.worktreeRoot, ".opencode", "_run");
9665
+ fs14.mkdirSync(runDir, { recursive: true });
9314
9666
  let reflectorUsedEmptyFallback = false;
9315
9667
  let curatorUsedEmptyFallback = false;
9316
9668
  const reflectPrompt = [
@@ -9328,8 +9680,8 @@ async function runAceOfflineLearnFromTrace(traceAbsolutePath, opts) {
9328
9680
  "",
9329
9681
  "Schema: `aceSchemaVersion` **1.0.0-draft**, `sourceTrace`, `lessons` (see ace-reflector-lessons schema)."
9330
9682
  ].join("\n");
9331
- const reflectPromptPath = path12.join(runDir, `ace-reflect-${safeId}-${Date.now()}.prompt.txt`);
9332
- fs13.writeFileSync(reflectPromptPath, reflectPrompt, "utf-8");
9683
+ const reflectPromptPath = path14.join(runDir, `ace-reflect-${safeId}-${Date.now()}.prompt.txt`);
9684
+ fs14.writeFileSync(reflectPromptPath, reflectPrompt, "utf-8");
9333
9685
  const reflectVars = {
9334
9686
  promptFile: reflectPromptPath,
9335
9687
  agentId: ACE_REFLECTOR_AGENT_STEM,
@@ -9351,7 +9703,8 @@ async function runAceOfflineLearnFromTrace(traceAbsolutePath, opts) {
9351
9703
  onAgentLaunched: opts.onAgentLaunched,
9352
9704
  signal: opts.abortSignal,
9353
9705
  /** Keep raw stdout in `message` so we can recover lessons JSON if the model prints it instead of writing the file. */
9354
- parseOpenCodeSessionFromJsonStdout: false
9706
+ parseOpenCodeSessionFromJsonStdout: false,
9707
+ ...opts.openCodeChildEnv ? { childEnv: opts.openCodeChildEnv } : {}
9355
9708
  });
9356
9709
  if (rCli.cancelled || opts.abortSignal?.aborted) {
9357
9710
  return { ok: false, message: "[ace] reflector cancelled" };
@@ -9359,11 +9712,11 @@ async function runAceOfflineLearnFromTrace(traceAbsolutePath, opts) {
9359
9712
  if (!rCli.ok) {
9360
9713
  return { ok: false, message: `[ace] reflector OpenCode failed: ${rCli.message}` };
9361
9714
  }
9362
- if (!fs13.existsSync(lessonsAbs)) {
9715
+ if (!fs14.existsSync(lessonsAbs)) {
9363
9716
  const recovered = extractJsonObjectWithTopLevelKeys(rCli.message, ["aceSchemaVersion", "sourceTrace", "lessons"]);
9364
9717
  if (recovered) {
9365
9718
  try {
9366
- fs13.writeFileSync(lessonsAbs, `${recovered}
9719
+ fs14.writeFileSync(lessonsAbs, `${recovered}
9367
9720
  `, "utf-8");
9368
9721
  opts.diagnosticLog?.(`[ace] reflector recovered from OpenCode stdout/stderr \u2192 ${lessonsAbs}`);
9369
9722
  } catch (e) {
@@ -9373,7 +9726,7 @@ async function runAceOfflineLearnFromTrace(traceAbsolutePath, opts) {
9373
9726
  }
9374
9727
  }
9375
9728
  }
9376
- if (!fs13.existsSync(lessonsAbs)) {
9729
+ if (!fs14.existsSync(lessonsAbs)) {
9377
9730
  return { ok: false, message: `[ace] reflector output missing: ${lessonsAbs}` };
9378
9731
  }
9379
9732
  let lv = validateAceJsonFile(lessonsAbs, reflectorSchema);
@@ -9385,7 +9738,7 @@ async function runAceOfflineLearnFromTrace(traceAbsolutePath, opts) {
9385
9738
  ]);
9386
9739
  if (recoveredAgain) {
9387
9740
  try {
9388
- fs13.writeFileSync(lessonsAbs, `${recoveredAgain}
9741
+ fs14.writeFileSync(lessonsAbs, `${recoveredAgain}
9389
9742
  `, "utf-8");
9390
9743
  opts.diagnosticLog?.(`[ace] reflector re-recovered from CLI output after schema failure \u2192 ${lessonsAbs}`);
9391
9744
  lv = validateAceJsonFile(lessonsAbs, reflectorSchema);
@@ -9399,7 +9752,7 @@ async function runAceOfflineLearnFromTrace(traceAbsolutePath, opts) {
9399
9752
  if (!lv.ok) {
9400
9753
  const hint = lv.message.length > 220 ? `${lv.message.slice(0, 220)}\u2026` : lv.message;
9401
9754
  opts.diagnosticLog?.(`[ace] reflector lessons failed schema \u2014 writing valid empty lessons \u2014 ${hint}`);
9402
- fs13.writeFileSync(
9755
+ fs14.writeFileSync(
9403
9756
  lessonsAbs,
9404
9757
  `${JSON.stringify(buildEmptyReflectorLessons(traceRel, runId), null, 2)}
9405
9758
  `,
@@ -9411,7 +9764,7 @@ async function runAceOfflineLearnFromTrace(traceAbsolutePath, opts) {
9411
9764
  if (!lv.ok) {
9412
9765
  return { ok: false, message: `[ace] lessons invalid after empty fallback: ${lv.message}` };
9413
9766
  }
9414
- const lessonsRel = path12.relative(contextDir2, lessonsAbs).split(path12.sep).join("/");
9767
+ const lessonsRel = path14.relative(contextDir2, lessonsAbs).split(path14.sep).join("/");
9415
9768
  const curPrompt = [
9416
9769
  "# ACE Curator (offline)",
9417
9770
  "",
@@ -9445,8 +9798,8 @@ async function runAceOfflineLearnFromTrace(traceAbsolutePath, opts) {
9445
9798
  "",
9446
9799
  "Schema: `aceSchemaVersion` **1.0.0-draft**, `agentStem`, `sourceLessons`, `operations` (see ace-curator-delta schema)."
9447
9800
  ].join("\n");
9448
- const curPromptPath = path12.join(runDir, `ace-curate-${safeId}-${Date.now()}.prompt.txt`);
9449
- fs13.writeFileSync(curPromptPath, curPrompt, "utf-8");
9801
+ const curPromptPath = path14.join(runDir, `ace-curate-${safeId}-${Date.now()}.prompt.txt`);
9802
+ fs14.writeFileSync(curPromptPath, curPrompt, "utf-8");
9450
9803
  const curVars = {
9451
9804
  promptFile: curPromptPath,
9452
9805
  agentId: ACE_CURATOR_AGENT_STEM,
@@ -9467,7 +9820,8 @@ async function runAceOfflineLearnFromTrace(traceAbsolutePath, opts) {
9467
9820
  diagnosticLog: opts.diagnosticLog,
9468
9821
  onAgentLaunched: opts.onAgentLaunched,
9469
9822
  signal: opts.abortSignal,
9470
- parseOpenCodeSessionFromJsonStdout: false
9823
+ parseOpenCodeSessionFromJsonStdout: false,
9824
+ ...opts.openCodeChildEnv ? { childEnv: opts.openCodeChildEnv } : {}
9471
9825
  });
9472
9826
  if (cCli.cancelled || opts.abortSignal?.aborted) {
9473
9827
  return { ok: false, message: "[ace] curator cancelled" };
@@ -9475,7 +9829,7 @@ async function runAceOfflineLearnFromTrace(traceAbsolutePath, opts) {
9475
9829
  if (!cCli.ok) {
9476
9830
  return { ok: false, message: `[ace] curator OpenCode failed: ${cCli.message}` };
9477
9831
  }
9478
- if (!fs13.existsSync(deltaAbs)) {
9832
+ if (!fs14.existsSync(deltaAbs)) {
9479
9833
  const recovered = extractJsonObjectWithTopLevelKeys(cCli.message, [
9480
9834
  "aceSchemaVersion",
9481
9835
  "agentStem",
@@ -9484,7 +9838,7 @@ async function runAceOfflineLearnFromTrace(traceAbsolutePath, opts) {
9484
9838
  ]);
9485
9839
  if (recovered) {
9486
9840
  try {
9487
- fs13.writeFileSync(deltaAbs, `${recovered}
9841
+ fs14.writeFileSync(deltaAbs, `${recovered}
9488
9842
  `, "utf-8");
9489
9843
  opts.diagnosticLog?.(`[ace] curator recovered from OpenCode stdout/stderr \u2192 ${deltaAbs}`);
9490
9844
  } catch (e) {
@@ -9494,7 +9848,7 @@ async function runAceOfflineLearnFromTrace(traceAbsolutePath, opts) {
9494
9848
  }
9495
9849
  }
9496
9850
  }
9497
- if (!fs13.existsSync(deltaAbs)) {
9851
+ if (!fs14.existsSync(deltaAbs)) {
9498
9852
  return { ok: false, message: `[ace] curator output missing: ${deltaAbs}` };
9499
9853
  }
9500
9854
  let dv = validateAceJsonFile(deltaAbs, curatorSchema);
@@ -9507,7 +9861,7 @@ async function runAceOfflineLearnFromTrace(traceAbsolutePath, opts) {
9507
9861
  ]);
9508
9862
  if (recoveredCur) {
9509
9863
  try {
9510
- fs13.writeFileSync(deltaAbs, `${recoveredCur}
9864
+ fs14.writeFileSync(deltaAbs, `${recoveredCur}
9511
9865
  `, "utf-8");
9512
9866
  opts.diagnosticLog?.(`[ace] curator re-recovered from CLI output after schema failure \u2192 ${deltaAbs}`);
9513
9867
  dv = validateAceJsonFile(deltaAbs, curatorSchema);
@@ -9521,7 +9875,7 @@ async function runAceOfflineLearnFromTrace(traceAbsolutePath, opts) {
9521
9875
  if (!dv.ok) {
9522
9876
  const hint = dv.message.length > 220 ? `${dv.message.slice(0, 220)}\u2026` : dv.message;
9523
9877
  opts.diagnosticLog?.(`[ace] curator delta failed schema \u2014 writing valid empty operations \u2014 ${hint}`);
9524
- fs13.writeFileSync(
9878
+ fs14.writeFileSync(
9525
9879
  deltaAbs,
9526
9880
  `${JSON.stringify(buildEmptyCuratorDelta(agentStem, lessonsRel), null, 2)}
9527
9881
  `,
@@ -9540,13 +9894,13 @@ async function runAceOfflineLearnFromTrace(traceAbsolutePath, opts) {
9540
9894
  let lessonCount = 0;
9541
9895
  let curatorOperationCount = 0;
9542
9896
  try {
9543
- const lessonsDoc = JSON.parse(fs13.readFileSync(lessonsAbs, "utf-8"));
9897
+ const lessonsDoc = JSON.parse(fs14.readFileSync(lessonsAbs, "utf-8"));
9544
9898
  lessonCount = Array.isArray(lessonsDoc.lessons) ? lessonsDoc.lessons.length : 0;
9545
- const deltaDoc = JSON.parse(fs13.readFileSync(deltaAbs, "utf-8"));
9899
+ const deltaDoc = JSON.parse(fs14.readFileSync(deltaAbs, "utf-8"));
9546
9900
  curatorOperationCount = Array.isArray(deltaDoc.operations) ? deltaDoc.operations.length : 0;
9547
9901
  } catch {
9548
9902
  }
9549
- const deltaRel = path12.relative(contextDir2, deltaAbs).split(path12.sep).join("/");
9903
+ const deltaRel = path14.relative(contextDir2, deltaAbs).split(path14.sep).join("/");
9550
9904
  appendAceConsolidatedSessionRecord(
9551
9905
  contextDir2,
9552
9906
  {
@@ -9601,15 +9955,15 @@ async function runAceTracePhase(params) {
9601
9955
  const contextDir2 = gluecharmContextDir(params.worktreeRoot);
9602
9956
  const traceStem = aceTraceFileRunStem(params.artefactRunId, params.followUpSessionId);
9603
9957
  const traceAbs = aceTracePath(contextDir2, params.producerAgentStem, traceStem);
9604
- fs14.mkdirSync(path13.dirname(traceAbs), { recursive: true });
9958
+ fs15.mkdirSync(path15.dirname(traceAbs), { recursive: true });
9605
9959
  const schemaAbs = opencodeAceSchemaPath(params.worktreeRoot, ACE_SCHEMA_TRACE);
9606
- if (!fs14.existsSync(schemaAbs)) {
9960
+ if (!fs15.existsSync(schemaAbs)) {
9607
9961
  const msg = `[ace] trace skipped \u2014 schema missing: ${schemaAbs}`;
9608
9962
  params.diagnosticLog?.(msg);
9609
9963
  return { ok: false, message: msg };
9610
9964
  }
9611
9965
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
9612
- const primaryAbs = path13.join(contextDir2, params.primaryRelativePathUnderContext.split("/").join(path13.sep));
9966
+ const primaryAbs = path15.join(contextDir2, params.primaryRelativePathUnderContext.split("/").join(path15.sep));
9613
9967
  const promptBody = [
9614
9968
  `# ACE trace (same session)`,
9615
9969
  ``,
@@ -9651,10 +10005,10 @@ async function runAceTracePhase(params) {
9651
10005
  `sessionMeta must include \`openCodeSessionId\` = the session id above, \`runId\` = **exactly** \`${traceStem}\`, \`agentStem\` = producer stem, \`startedAt\` = \`${startedAt}\`.`,
9652
10006
  `Use only evidence from this session's transcript and tool history.`
9653
10007
  ].join("\n");
9654
- const runDir = path13.join(params.worktreeRoot, ".opencode", "_run");
9655
- fs14.mkdirSync(runDir, { recursive: true });
9656
- const promptPath = path13.join(runDir, `ace-trace-${traceStem}-${Date.now()}.prompt.txt`);
9657
- fs14.writeFileSync(promptPath, promptBody, "utf-8");
10008
+ const runDir = path15.join(params.worktreeRoot, ".opencode", "_run");
10009
+ fs15.mkdirSync(runDir, { recursive: true });
10010
+ const promptPath = path15.join(runDir, `ace-trace-${traceStem}-${Date.now()}.prompt.txt`);
10011
+ fs15.writeFileSync(promptPath, promptBody, "utf-8");
9658
10012
  const vars = {
9659
10013
  promptFile: promptPath,
9660
10014
  agentId: ACE_TRACE_RECORDER_AGENT_STEM,
@@ -9677,7 +10031,7 @@ async function runAceTracePhase(params) {
9677
10031
  if (!cli.ok) {
9678
10032
  return { ok: false, message: `[ace] trace OpenCode failed: ${cli.message}` };
9679
10033
  }
9680
- if (!fs14.existsSync(traceAbs)) {
10034
+ if (!fs15.existsSync(traceAbs)) {
9681
10035
  const recovered = extractJsonObjectWithTopLevelKeys(cli.message, [
9682
10036
  "aceSchemaVersion",
9683
10037
  "sessionMeta",
@@ -9685,7 +10039,7 @@ async function runAceTracePhase(params) {
9685
10039
  ]);
9686
10040
  if (recovered) {
9687
10041
  try {
9688
- fs14.writeFileSync(traceAbs, `${recovered}
10042
+ fs15.writeFileSync(traceAbs, `${recovered}
9689
10043
  `, "utf-8");
9690
10044
  params.diagnosticLog?.(`[ace] trace recovered from OpenCode stdout/stderr \u2192 ${traceAbs}`);
9691
10045
  } catch (e) {
@@ -9695,7 +10049,7 @@ async function runAceTracePhase(params) {
9695
10049
  }
9696
10050
  }
9697
10051
  }
9698
- if (!fs14.existsSync(traceAbs)) {
10052
+ if (!fs15.existsSync(traceAbs)) {
9699
10053
  const msg = `[ace] trace file missing after run: ${traceAbs}`;
9700
10054
  params.diagnosticLog?.(msg);
9701
10055
  return { ok: false, message: msg };
@@ -9727,7 +10081,7 @@ async function runAceTracePhase(params) {
9727
10081
  return { ok: false, message: msg };
9728
10082
  }
9729
10083
  try {
9730
- const rawAlign = fs14.readFileSync(traceAbs, "utf-8");
10084
+ const rawAlign = fs15.readFileSync(traceAbs, "utf-8");
9731
10085
  const docAlign = JSON.parse(rawAlign);
9732
10086
  const smRun = docAlign.sessionMeta?.runId;
9733
10087
  if (typeof smRun !== "string" || smRun !== traceStem) {
@@ -9776,14 +10130,14 @@ async function runAceTracePhase(params) {
9776
10130
  }
9777
10131
  } else {
9778
10132
  params.diagnosticLog?.(
9779
- '[ace] Reflector/Curator not run automatically. Set easyspecs.analysis.ace.offlineLearnAfterSameSessionTrace=true (or the same key in .easyspecs/settings.json), or Command Palette: "EasySpecs: ACE \u2014 Learn from traces".'
10133
+ '[ace] Reflector/Curator not run automatically. Set easyspecs.analysis.ace.offlineLearnAfterSameSessionTrace in .easyspecs/config.json (or legacy .easyspecs/settings.json), or Command Palette: "EasySpecs: ACE \u2014 Learn from traces".'
9780
10134
  );
9781
10135
  }
9782
10136
  return { ok: true, message: "ACE trace OK", traceAbsolutePath: traceAbs };
9783
10137
  }
9784
10138
 
9785
10139
  // src/analysis/coordinationListJsonValidate.ts
9786
- var fs15 = __toESM(require("fs"));
10140
+ var fs16 = __toESM(require("fs"));
9787
10141
  var import__2 = __toESM(require__());
9788
10142
  function stripUtf8Bom3(s) {
9789
10143
  return s.length > 0 && s.charCodeAt(0) === 65279 ? s.slice(1) : s;
@@ -9847,7 +10201,7 @@ function formatAjvErrors2(errors) {
9847
10201
  function validateCoordinationListJson(jsonAbsolutePath, schemaAbsolutePath) {
9848
10202
  let raw;
9849
10203
  try {
9850
- raw = stripUtf8Bom3(fs15.readFileSync(jsonAbsolutePath, "utf-8"));
10204
+ raw = stripUtf8Bom3(fs16.readFileSync(jsonAbsolutePath, "utf-8"));
9851
10205
  } catch (e) {
9852
10206
  return {
9853
10207
  ok: false,
@@ -9863,7 +10217,7 @@ function validateCoordinationListJson(jsonAbsolutePath, schemaAbsolutePath) {
9863
10217
  }
9864
10218
  let schemaRaw;
9865
10219
  try {
9866
- schemaRaw = stripUtf8Bom3(fs15.readFileSync(schemaAbsolutePath, "utf-8"));
10220
+ schemaRaw = stripUtf8Bom3(fs16.readFileSync(schemaAbsolutePath, "utf-8"));
9867
10221
  } catch (e) {
9868
10222
  return {
9869
10223
  ok: false,
@@ -9924,7 +10278,7 @@ function validateCoordinationListJson(jsonAbsolutePath, schemaAbsolutePath) {
9924
10278
  var FILE_PREVIEW_MAX = 4e3;
9925
10279
  function readCoordinationJsonPreview(jsonAbsolutePath) {
9926
10280
  try {
9927
- const raw = stripUtf8Bom3(fs15.readFileSync(jsonAbsolutePath, "utf-8"));
10281
+ const raw = stripUtf8Bom3(fs16.readFileSync(jsonAbsolutePath, "utf-8"));
9928
10282
  return raw.length <= FILE_PREVIEW_MAX ? raw : `${raw.slice(0, FILE_PREVIEW_MAX)}
9929
10283
 
9930
10284
  \u2026 (truncated)`;
@@ -10000,7 +10354,7 @@ function formatCoordinationJsonRepairAppendix(outputBasename, failure, rawFilePr
10000
10354
  }
10001
10355
 
10002
10356
  // src/analysis/markdownEvidenceIndexValidate.ts
10003
- var fs16 = __toESM(require("fs"));
10357
+ var fs17 = __toESM(require("fs"));
10004
10358
  var EVIDENCE_HEADING = "## Evidence index";
10005
10359
  function evidenceIndexMentionsReadmeMd(body) {
10006
10360
  const re = /README\.md/gi;
@@ -10128,7 +10482,7 @@ function markdownEvidenceValidationPasses(content) {
10128
10482
  function validateMarkdownEvidenceIndexFile(absolutePath) {
10129
10483
  let text;
10130
10484
  try {
10131
- const buf = fs16.readFileSync(absolutePath);
10485
+ const buf = fs17.readFileSync(absolutePath);
10132
10486
  const decoder = new TextDecoder("utf-8", { fatal: true });
10133
10487
  text = decoder.decode(buf);
10134
10488
  } catch (e) {
@@ -10169,11 +10523,11 @@ function formatMarkdownEvidenceRepairAppendix(outputFileAbsolute, kind = "empty"
10169
10523
  }
10170
10524
 
10171
10525
  // src/analysis/openQuestionResolution.ts
10172
- var fs17 = __toESM(require("fs"));
10173
- var path14 = __toESM(require("path"));
10526
+ var fs18 = __toESM(require("fs"));
10527
+ var path16 = __toESM(require("path"));
10174
10528
  var OPEN_QUESTION_RESOLUTION_JSON_BASENAME = "open-question-resolution.json";
10175
10529
  function openQuestionResolutionJsonAbsolute(worktreeRoot) {
10176
- return path14.join(worktreeRoot, ".opencode", "_run", OPEN_QUESTION_RESOLUTION_JSON_BASENAME);
10530
+ return path16.join(worktreeRoot, ".opencode", "_run", OPEN_QUESTION_RESOLUTION_JSON_BASENAME);
10177
10531
  }
10178
10532
  function asTrimmedString(v) {
10179
10533
  if (typeof v !== "string") {
@@ -10208,10 +10562,10 @@ function parseOpenQuestionResolutionJsonText(raw) {
10208
10562
  function readOpenQuestionResolutionFile(worktreeRoot) {
10209
10563
  const p = openQuestionResolutionJsonAbsolute(worktreeRoot);
10210
10564
  try {
10211
- if (!fs17.existsSync(p)) {
10565
+ if (!fs18.existsSync(p)) {
10212
10566
  return { ok: false, error: `Missing resolution file: ${p}` };
10213
10567
  }
10214
- const raw = fs17.readFileSync(p, "utf-8");
10568
+ const raw = fs18.readFileSync(p, "utf-8");
10215
10569
  return parseOpenQuestionResolutionJsonText(raw);
10216
10570
  } catch (e) {
10217
10571
  const m = e instanceof Error ? e.message : String(e);
@@ -10221,16 +10575,16 @@ function readOpenQuestionResolutionFile(worktreeRoot) {
10221
10575
  function deleteOpenQuestionResolutionFile(worktreeRoot) {
10222
10576
  try {
10223
10577
  const p = openQuestionResolutionJsonAbsolute(worktreeRoot);
10224
- if (fs17.existsSync(p)) {
10225
- fs17.unlinkSync(p);
10578
+ if (fs18.existsSync(p)) {
10579
+ fs18.unlinkSync(p);
10226
10580
  }
10227
10581
  } catch {
10228
10582
  }
10229
10583
  }
10230
10584
 
10231
10585
  // src/analysis/openQuestionsSectionValidate.ts
10232
- var fs18 = __toESM(require("fs"));
10233
- var path15 = __toESM(require("path"));
10586
+ var fs19 = __toESM(require("fs"));
10587
+ var path17 = __toESM(require("path"));
10234
10588
  var CANONICAL_NORMALIZED = "open questions";
10235
10589
  function normalizeOpenQuestionsHeadingTitle(raw) {
10236
10590
  const t = raw.trim().replace(/\s+/gu, " ");
@@ -10347,9 +10701,9 @@ function parseOpenQuestionsSection(content) {
10347
10701
  return { kind: "hasResidualQuestions", questions, startLine1Based };
10348
10702
  }
10349
10703
  function isContextMarkdownPathForOpenQuestions(worktreeRoot, absolutePath) {
10350
- const ctx = path15.join(path15.resolve(worktreeRoot), ".gluecharm", "context");
10351
- const abs = path15.resolve(absolutePath);
10352
- const ctxNorm = ctx + path15.sep;
10704
+ const ctx = path17.join(path17.resolve(worktreeRoot), ".gluecharm", "context");
10705
+ const abs = path17.resolve(absolutePath);
10706
+ const ctxNorm = ctx + path17.sep;
10353
10707
  return abs === ctx || abs.startsWith(ctxNorm);
10354
10708
  }
10355
10709
  function validateOpenQuestionsSectionFile(absolutePath, scope) {
@@ -10362,7 +10716,7 @@ function validateOpenQuestionsSectionFile(absolutePath, scope) {
10362
10716
  }
10363
10717
  let text;
10364
10718
  try {
10365
- const buf = fs18.readFileSync(absolutePath);
10719
+ const buf = fs19.readFileSync(absolutePath);
10366
10720
  const decoder = new TextDecoder("utf-8", { fatal: true });
10367
10721
  text = decoder.decode(buf);
10368
10722
  } catch (e) {
@@ -10398,10 +10752,10 @@ function formatOpenQuestionsProducerRepairAppendix(outputFileAbsolute, stepLabel
10398
10752
  // src/analysis/openCodeTestAgent.ts
10399
10753
  var CITATION_EXAMPLE = "`src/example.ts:42` or `src/example.ts:10-25`";
10400
10754
  function contextDir(worktreeRoot) {
10401
- return path16.join(worktreeRoot, ".gluecharm", "context");
10755
+ return path18.join(worktreeRoot, ".gluecharm", "context");
10402
10756
  }
10403
10757
  function schemaRef(worktreeRoot, schemaBasename) {
10404
- return path16.join(worktreeRoot, ".opencode", "schemas", "context-lists", schemaBasename);
10758
+ return path18.join(worktreeRoot, ".opencode", "schemas", "context-lists", schemaBasename);
10405
10759
  }
10406
10760
  function expandArgvTemplate3(template, vars) {
10407
10761
  return template.map((part) => {
@@ -10829,7 +11183,7 @@ function clampMarkdownOpenQuestionIterations(raw) {
10829
11183
  }
10830
11184
  function getExpectedOutputArtefactStatus(absolutePath) {
10831
11185
  try {
10832
- const st = fs19.statSync(absolutePath);
11186
+ const st = fs20.statSync(absolutePath);
10833
11187
  if (!st.isFile()) {
10834
11188
  return { generated: false, reason: "not_a_file" };
10835
11189
  }
@@ -10845,46 +11199,46 @@ function outputPaths(step, worktreeRoot, listTarget) {
10845
11199
  const ctx = contextDir(worktreeRoot);
10846
11200
  switch (step) {
10847
11201
  case "docsProject":
10848
- return { absolute: path16.join(contextDir(worktreeRoot), "project.md"), basename: "project.md" };
11202
+ return { absolute: path18.join(contextDir(worktreeRoot), "project.md"), basename: "project.md" };
10849
11203
  case "architecture":
10850
- return { absolute: path16.join(ctx, "architecture.md"), basename: "architecture.md" };
11204
+ return { absolute: path18.join(ctx, "architecture.md"), basename: "architecture.md" };
10851
11205
  case "listFeatures":
10852
- return { absolute: path16.join(ctx, "features-list.json"), basename: "features-list.json" };
11206
+ return { absolute: path18.join(ctx, "features-list.json"), basename: "features-list.json" };
10853
11207
  case "reviewFeaturesList":
10854
- return { absolute: path16.join(ctx, "features-list.json"), basename: "features-list.json" };
11208
+ return { absolute: path18.join(ctx, "features-list.json"), basename: "features-list.json" };
10855
11209
  case "listRepoSurface":
10856
- return { absolute: path16.join(ctx, "repo-surface-scan.json"), basename: "repo-surface-scan.json" };
11210
+ return { absolute: path18.join(ctx, "repo-surface-scan.json"), basename: "repo-surface-scan.json" };
10857
11211
  case "listExperiences":
10858
- return { absolute: path16.join(ctx, "experiences-list.json"), basename: "experiences-list.json" };
11212
+ return { absolute: path18.join(ctx, "experiences-list.json"), basename: "experiences-list.json" };
10859
11213
  case "reviewExperiencesList":
10860
- return { absolute: path16.join(ctx, "experiences-list.json"), basename: "experiences-list.json" };
11214
+ return { absolute: path18.join(ctx, "experiences-list.json"), basename: "experiences-list.json" };
10861
11215
  case "listServices":
10862
- return { absolute: path16.join(ctx, "services-list.json"), basename: "services-list.json" };
11216
+ return { absolute: path18.join(ctx, "services-list.json"), basename: "services-list.json" };
10863
11217
  case "reviewServicesList":
10864
- return { absolute: path16.join(ctx, "services-list.json"), basename: "services-list.json" };
11218
+ return { absolute: path18.join(ctx, "services-list.json"), basename: "services-list.json" };
10865
11219
  case "listDataModel":
10866
- return { absolute: path16.join(ctx, "data-model-list.json"), basename: "data-model-list.json" };
11220
+ return { absolute: path18.join(ctx, "data-model-list.json"), basename: "data-model-list.json" };
10867
11221
  case "reviewDataModelList":
10868
- return { absolute: path16.join(ctx, "data-model-list.json"), basename: "data-model-list.json" };
11222
+ return { absolute: path18.join(ctx, "data-model-list.json"), basename: "data-model-list.json" };
10869
11223
  case "listTechStack":
10870
- return { absolute: path16.join(ctx, "tech-stack-list.json"), basename: "tech-stack-list.json" };
11224
+ return { absolute: path18.join(ctx, "tech-stack-list.json"), basename: "tech-stack-list.json" };
10871
11225
  case "reviewTechStackList":
10872
- return { absolute: path16.join(ctx, "tech-stack-list.json"), basename: "tech-stack-list.json" };
11226
+ return { absolute: path18.join(ctx, "tech-stack-list.json"), basename: "tech-stack-list.json" };
10873
11227
  case "listUseCases": {
10874
11228
  const fe = listTarget?.featureCode;
10875
11229
  if (!fe) {
10876
11230
  throw new Error("listUseCases requires listTarget.featureCode");
10877
11231
  }
10878
- const basename11 = `${fe}-use-cases-list.json`;
10879
- return { absolute: path16.join(ctx, basename11), basename: basename11 };
11232
+ const basename12 = `${fe}-use-cases-list.json`;
11233
+ return { absolute: path18.join(ctx, basename12), basename: basename12 };
10880
11234
  }
10881
11235
  case "reviewUseCasesList": {
10882
11236
  const fe = listTarget?.featureCode;
10883
11237
  if (!fe) {
10884
11238
  throw new Error("reviewUseCasesList requires listTarget.featureCode");
10885
11239
  }
10886
- const basename11 = `${fe}-use-cases-list.json`;
10887
- return { absolute: path16.join(ctx, basename11), basename: basename11 };
11240
+ const basename12 = `${fe}-use-cases-list.json`;
11241
+ return { absolute: path18.join(ctx, basename12), basename: basename12 };
10888
11242
  }
10889
11243
  case "listScenarios": {
10890
11244
  const fe = listTarget?.featureCode;
@@ -10892,8 +11246,8 @@ function outputPaths(step, worktreeRoot, listTarget) {
10892
11246
  if (!fe || !uc) {
10893
11247
  throw new Error("listScenarios requires listTarget.featureCode and useCaseCode");
10894
11248
  }
10895
- const basename11 = `${fe}_${uc}-scenarios-list.json`;
10896
- return { absolute: path16.join(ctx, basename11), basename: basename11 };
11249
+ const basename12 = `${fe}_${uc}-scenarios-list.json`;
11250
+ return { absolute: path18.join(ctx, basename12), basename: basename12 };
10897
11251
  }
10898
11252
  case "reviewScenariosList": {
10899
11253
  const fe = listTarget?.featureCode;
@@ -10901,24 +11255,24 @@ function outputPaths(step, worktreeRoot, listTarget) {
10901
11255
  if (!fe || !uc) {
10902
11256
  throw new Error("reviewScenariosList requires listTarget.featureCode and useCaseCode");
10903
11257
  }
10904
- const basename11 = `${fe}_${uc}-scenarios-list.json`;
10905
- return { absolute: path16.join(ctx, basename11), basename: basename11 };
11258
+ const basename12 = `${fe}_${uc}-scenarios-list.json`;
11259
+ return { absolute: path18.join(ctx, basename12), basename: basename12 };
10906
11260
  }
10907
11261
  case "listEntityFields": {
10908
11262
  const dm = listTarget?.entityCode;
10909
11263
  if (!dm) {
10910
11264
  throw new Error("listEntityFields requires listTarget.entityCode");
10911
11265
  }
10912
- const basename11 = `${dm}-fields-list.json`;
10913
- return { absolute: path16.join(ctx, basename11), basename: basename11 };
11266
+ const basename12 = `${dm}-fields-list.json`;
11267
+ return { absolute: path18.join(ctx, basename12), basename: basename12 };
10914
11268
  }
10915
11269
  case "reviewEntityFieldsList": {
10916
11270
  const dm = listTarget?.entityCode;
10917
11271
  if (!dm) {
10918
11272
  throw new Error("reviewEntityFieldsList requires listTarget.entityCode");
10919
11273
  }
10920
- const basename11 = `${dm}-fields-list.json`;
10921
- return { absolute: path16.join(ctx, basename11), basename: basename11 };
11274
+ const basename12 = `${dm}-fields-list.json`;
11275
+ return { absolute: path18.join(ctx, basename12), basename: basename12 };
10922
11276
  }
10923
11277
  default: {
10924
11278
  const _u = step;
@@ -10927,8 +11281,8 @@ function outputPaths(step, worktreeRoot, listTarget) {
10927
11281
  }
10928
11282
  }
10929
11283
  async function runOpenCodeResolveOpenQuestionStep(opts) {
10930
- const runDir = path16.join(opts.worktreeRoot, ".opencode", "_run");
10931
- fs19.mkdirSync(runDir, { recursive: true });
11284
+ const runDir = path18.join(opts.worktreeRoot, ".opencode", "_run");
11285
+ fs20.mkdirSync(runDir, { recursive: true });
10932
11286
  const resolutionJsonAbsolute = openQuestionResolutionJsonAbsolute(opts.worktreeRoot);
10933
11287
  const body = fillResolveOpenQuestionPrompt({
10934
11288
  worktreeRoot: opts.worktreeRoot,
@@ -10937,8 +11291,8 @@ async function runOpenCodeResolveOpenQuestionStep(opts) {
10937
11291
  questionText: opts.questionText,
10938
11292
  resolutionJsonAbsolute
10939
11293
  });
10940
- const promptPath = path16.join(runDir, `resolve-open-question-${Date.now()}.prompt.txt`);
10941
- fs19.writeFileSync(promptPath, body, "utf-8");
11294
+ const promptPath = path18.join(runDir, `resolve-open-question-${Date.now()}.prompt.txt`);
11295
+ fs20.writeFileSync(promptPath, body, "utf-8");
10942
11296
  const vars = {
10943
11297
  promptFile: promptPath,
10944
11298
  agentId: RESOLVE_OPEN_QUESTION_AGENT_STEM,
@@ -10965,7 +11319,8 @@ async function runOpenCodeResolveOpenQuestionStep(opts) {
10965
11319
  diagnosticLog: opts.diagnosticLog,
10966
11320
  onAgentLaunched: opts.onAgentLaunched,
10967
11321
  signal: opts.abortSignal,
10968
- parseOpenCodeSessionFromJsonStdout: opts.followUpOpenCodeSessionId === void 0
11322
+ parseOpenCodeSessionFromJsonStdout: opts.followUpOpenCodeSessionId === void 0,
11323
+ childEnv: opts.openCodeChildEnv
10969
11324
  });
10970
11325
  opts.onRemediationPhase?.("validating");
10971
11326
  if (cli.cancelled || opts.abortSignal?.aborted) {
@@ -11000,8 +11355,8 @@ ${cli.message}`,
11000
11355
  }
11001
11356
  async function runSynthesisMarkdownRemediationChain(common, step, workspaceLabel, initialMarkdownAppendix) {
11002
11357
  const totalMarkdownRuns = 1 + common.maxMarkdownEvidenceRepairs;
11003
- const runDir = path16.join(common.worktreeRoot, ".opencode", "_run");
11004
- fs19.mkdirSync(runDir, { recursive: true });
11358
+ const runDir = path18.join(common.worktreeRoot, ".opencode", "_run");
11359
+ fs20.mkdirSync(runDir, { recursive: true });
11005
11360
  let markdownEvidenceRepairAppendix = initialMarkdownAppendix;
11006
11361
  let lastPromptPath = "";
11007
11362
  let lastCliMessage = "";
@@ -11019,9 +11374,9 @@ async function runSynthesisMarkdownRemediationChain(common, step, workspaceLabel
11019
11374
  void 0,
11020
11375
  markdownEvidenceRepairAppendix
11021
11376
  );
11022
- const promptPath = path16.join(runDir, `${step}-oqrem-a${attempt}-${Date.now()}.prompt.txt`);
11377
+ const promptPath = path18.join(runDir, `${step}-oqrem-a${attempt}-${Date.now()}.prompt.txt`);
11023
11378
  lastPromptPath = promptPath;
11024
- fs19.writeFileSync(promptPath, body, "utf-8");
11379
+ fs20.writeFileSync(promptPath, body, "utf-8");
11025
11380
  const vars = {
11026
11381
  promptFile: promptPath,
11027
11382
  agentId: STEP_OPEN_CODE_AGENT_IDS[step],
@@ -11043,7 +11398,8 @@ async function runSynthesisMarkdownRemediationChain(common, step, workspaceLabel
11043
11398
  diagnosticLog: common.diagnosticLog,
11044
11399
  onAgentLaunched: common.onAgentLaunched,
11045
11400
  signal: common.abortSignal,
11046
- parseOpenCodeSessionFromJsonStdout: common.producerOpenCodeSessionId === void 0
11401
+ parseOpenCodeSessionFromJsonStdout: common.producerOpenCodeSessionId === void 0,
11402
+ childEnv: common.openCodeChildEnv
11047
11403
  });
11048
11404
  common.onRemediationPhase?.("validating");
11049
11405
  lastCliMessage = cli.message;
@@ -11119,8 +11475,8 @@ ${common.outputFileAbsolute}`,
11119
11475
  }
11120
11476
  async function runDetailMarkdownRemediationChain(common, target, initialMarkdownAppendix) {
11121
11477
  const totalMarkdownRuns = 1 + common.maxMarkdownEvidenceRepairs;
11122
- const runDir = path16.join(common.worktreeRoot, ".opencode", "_run");
11123
- fs19.mkdirSync(runDir, { recursive: true });
11478
+ const runDir = path18.join(common.worktreeRoot, ".opencode", "_run");
11479
+ fs20.mkdirSync(runDir, { recursive: true });
11124
11480
  const safeStem = target.outputBasename.replace(/[^A-Za-z0-9_.-]+/g, "_").slice(0, 120);
11125
11481
  let markdownEvidenceRepairAppendix = initialMarkdownAppendix;
11126
11482
  let lastPromptPath = "";
@@ -11139,12 +11495,12 @@ async function runDetailMarkdownRemediationChain(common, target, initialMarkdown
11139
11495
  citationFormatExample: CITATION_EXAMPLE,
11140
11496
  repairAppendix: markdownEvidenceRepairAppendix
11141
11497
  });
11142
- const promptPath = path16.join(
11498
+ const promptPath = path18.join(
11143
11499
  runDir,
11144
11500
  `markdownDetail-${target.openCodeAgentStem}-${safeStem}-oqrem-a${attempt}-${Date.now()}.prompt.txt`
11145
11501
  );
11146
11502
  lastPromptPath = promptPath;
11147
- fs19.writeFileSync(promptPath, body, "utf-8");
11503
+ fs20.writeFileSync(promptPath, body, "utf-8");
11148
11504
  const vars = {
11149
11505
  promptFile: promptPath,
11150
11506
  agentId: target.openCodeAgentStem,
@@ -11166,7 +11522,8 @@ async function runDetailMarkdownRemediationChain(common, target, initialMarkdown
11166
11522
  diagnosticLog: common.diagnosticLog,
11167
11523
  onAgentLaunched: common.onAgentLaunched,
11168
11524
  signal: common.abortSignal,
11169
- parseOpenCodeSessionFromJsonStdout: common.producerOpenCodeSessionId === void 0
11525
+ parseOpenCodeSessionFromJsonStdout: common.producerOpenCodeSessionId === void 0,
11526
+ childEnv: common.openCodeChildEnv
11170
11527
  });
11171
11528
  common.onRemediationPhase?.("validating");
11172
11529
  lastCliMessage = cli.message;
@@ -11240,7 +11597,7 @@ ${common.outputFileAbsolute}`,
11240
11597
  promptPath: lastPromptPath
11241
11598
  };
11242
11599
  }
11243
- async function remediateOpenQuestionsForContextMarkdown(mode, producerStepLabel, outputFileAbsolute, outputBasename, worktreeRoot, markdownOpenQuestionIterations, markdownEvidenceRepairAttempts, executable, argvTemplate, timeoutMs, diagnosticLog, abortSignal, onRemediationPhase, onAgentLaunched, stewardship) {
11600
+ async function remediateOpenQuestionsForContextMarkdown(mode, producerStepLabel, outputFileAbsolute, outputBasename, worktreeRoot, markdownOpenQuestionIterations, markdownEvidenceRepairAttempts, executable, argvTemplate, timeoutMs, diagnosticLog, abortSignal, onRemediationPhase, onAgentLaunched, stewardship, openCodeChildEnv) {
11244
11601
  const maxOq = clampMarkdownOpenQuestionIterations(markdownOpenQuestionIterations);
11245
11602
  let maxMdEv = markdownEvidenceRepairAttempts === void 0 ? 2 : Math.floor(Number(markdownEvidenceRepairAttempts));
11246
11603
  if (!Number.isFinite(maxMdEv)) {
@@ -11264,7 +11621,8 @@ async function remediateOpenQuestionsForContextMarkdown(mode, producerStepLabel,
11264
11621
  artefactRunId: runId,
11265
11622
  artefactWorkItemId: workItemId,
11266
11623
  stewardshipStepLabel: `${producerStepLabel}|open-questions-chain`,
11267
- producerOpenCodeSessionId: stewardship?.producerOpenCodeSessionId
11624
+ producerOpenCodeSessionId: stewardship?.producerOpenCodeSessionId,
11625
+ openCodeChildEnv
11268
11626
  };
11269
11627
  const first = validateOpenQuestionsSectionFile(outputFileAbsolute, { worktreeRoot });
11270
11628
  if (!first.ok) {
@@ -11320,7 +11678,8 @@ async function remediateOpenQuestionsForContextMarkdown(mode, producerStepLabel,
11320
11678
  followUpOpenCodeSessionId: stewardship?.producerOpenCodeSessionId,
11321
11679
  artefactRunId: runId,
11322
11680
  artefactWorkItemId: workItemId,
11323
- stewardshipStepLabel: `${producerStepLabel}|resolve-oq`
11681
+ stewardshipStepLabel: `${producerStepLabel}|resolve-oq`,
11682
+ openCodeChildEnv
11324
11683
  });
11325
11684
  if (resolveRes.cancelled || abortSignal?.aborted) {
11326
11685
  return { ok: false, cancelled: true, message: resolveRes.message };
@@ -11463,7 +11822,7 @@ async function runOpenCodeTestAgentStep(opts) {
11463
11822
  let schemaAbsoluteForList;
11464
11823
  if (effectiveListSchemaFile) {
11465
11824
  schemaAbsoluteForList = schemaRef(opts.worktreeRoot, effectiveListSchemaFile);
11466
- if (!fs19.existsSync(schemaAbsoluteForList)) {
11825
+ if (!fs20.existsSync(schemaAbsoluteForList)) {
11467
11826
  return {
11468
11827
  ok: false,
11469
11828
  message: `JSON Schema not found: ${schemaAbsoluteForList}
@@ -11476,11 +11835,11 @@ Run **Materialize agents** first (copies list schemas into this folder\u2019s \`
11476
11835
  }
11477
11836
  if (opts.step === "reviewExperiencesList" && effectiveListSchemaFile) {
11478
11837
  const schemaAbs = schemaRef(opts.worktreeRoot, effectiveListSchemaFile);
11479
- if (fs19.existsSync(outputFileAbsolute)) {
11838
+ if (fs20.existsSync(outputFileAbsolute)) {
11480
11839
  const v = validateCoordinationListJson(outputFileAbsolute, schemaAbs);
11481
11840
  if (v.ok) {
11482
11841
  try {
11483
- const raw = fs19.readFileSync(outputFileAbsolute, "utf-8");
11842
+ const raw = fs20.readFileSync(outputFileAbsolute, "utf-8");
11484
11843
  const data = JSON.parse(raw);
11485
11844
  if (Array.isArray(data.views) && data.views.length === 0) {
11486
11845
  return {
@@ -11496,9 +11855,9 @@ Run **Materialize agents** first (copies list schemas into this folder\u2019s \`
11496
11855
  }
11497
11856
  }
11498
11857
  }
11499
- fs19.mkdirSync(path16.dirname(outputFileAbsolute), { recursive: true });
11500
- const runDir = path16.join(opts.worktreeRoot, ".opencode", "_run");
11501
- fs19.mkdirSync(runDir, { recursive: true });
11858
+ fs20.mkdirSync(path18.dirname(outputFileAbsolute), { recursive: true });
11859
+ const runDir = path18.join(opts.worktreeRoot, ".opencode", "_run");
11860
+ fs20.mkdirSync(runDir, { recursive: true });
11502
11861
  const scopeSuffix = (opts.step === "listUseCases" || opts.step === "reviewUseCasesList") && opts.listTarget?.featureCode ? `-${opts.listTarget.featureCode}` : (opts.step === "listScenarios" || opts.step === "reviewScenariosList") && opts.listTarget?.featureCode && opts.listTarget?.useCaseCode ? `-${opts.listTarget.featureCode}_${opts.listTarget.useCaseCode}` : (opts.step === "listEntityFields" || opts.step === "reviewEntityFieldsList") && opts.listTarget?.entityCode ? `-${opts.listTarget.entityCode}` : "";
11503
11862
  const rawRepair = opts.listJsonSchemaRepairAttempts;
11504
11863
  let maxRepairAttempts = rawRepair === void 0 ? 1 : Math.floor(Number(rawRepair));
@@ -11549,9 +11908,9 @@ Run **Materialize agents** first (copies list schemas into this folder\u2019s \`
11549
11908
  effectiveListSchemaFile ? repairAppendix : void 0,
11550
11909
  effectiveListSchemaFile ? void 0 : markdownEvidenceRepairAppendix
11551
11910
  );
11552
- const promptPath = path16.join(runDir, `${opts.step}${scopeSuffix}-a${attempt}-${Date.now()}.prompt.txt`);
11911
+ const promptPath = path18.join(runDir, `${opts.step}${scopeSuffix}-a${attempt}-${Date.now()}.prompt.txt`);
11553
11912
  lastPromptPath = promptPath;
11554
- fs19.writeFileSync(promptPath, body, "utf-8");
11913
+ fs20.writeFileSync(promptPath, body, "utf-8");
11555
11914
  const vars = {
11556
11915
  promptFile: promptPath,
11557
11916
  agentId: STEP_OPEN_CODE_AGENT_IDS[opts.step],
@@ -11576,7 +11935,8 @@ Run **Materialize agents** first (copies list schemas into this folder\u2019s \`
11576
11935
  diagnosticLog: opts.diagnosticLog,
11577
11936
  onAgentLaunched: opts.onAgentLaunched,
11578
11937
  signal: opts.abortSignal,
11579
- parseOpenCodeSessionFromJsonStdout: !isFollowUpSpawn
11938
+ parseOpenCodeSessionFromJsonStdout: !isFollowUpSpawn,
11939
+ childEnv: opts.openCodeChildEnv
11580
11940
  });
11581
11941
  if (cli.cancelled || opts.abortSignal?.aborted) {
11582
11942
  return {
@@ -11660,7 +12020,8 @@ Run **Materialize agents** first (copies list schemas into this folder\u2019s \`
11660
12020
  artefactRunId: stewardshipRunId,
11661
12021
  artefactWorkItemId: stewardshipWorkItemId,
11662
12022
  producerOpenCodeSessionId: capturedOpenCodeSessionId
11663
- }
12023
+ },
12024
+ opts.openCodeChildEnv
11664
12025
  );
11665
12026
  if (oqRes.cancelled) {
11666
12027
  return {
@@ -11789,7 +12150,7 @@ Expected output: ${outputFileAbsolute}`;
11789
12150
  const orderSet = new Set(CONTEXT_SYNTHESIS_TEST_STEP_ORDER);
11790
12151
  if (orderSet.has(opts.step)) {
11791
12152
  const ctx = contextDir(opts.worktreeRoot);
11792
- const rel = path16.relative(ctx, outputFileAbsolute).split(path16.sep).join("/");
12153
+ const rel = path18.relative(ctx, outputFileAbsolute).split(path18.sep).join("/");
11793
12154
  opts.onRemediationPhase?.("aceTracing");
11794
12155
  try {
11795
12156
  await runAceTracePhase({
@@ -11836,7 +12197,7 @@ Expected output: ${outputFileAbsolute}`;
11836
12197
  var MARKDOWN_DETAIL_RUN_STEP = "markdownDetail";
11837
12198
  async function runOpenCodeMarkdownDetailStep(opts) {
11838
12199
  const ctx = contextDir(opts.worktreeRoot);
11839
- const outputFileAbsolute = path16.join(ctx, opts.target.outputBasename);
12200
+ const outputFileAbsolute = path18.join(ctx, opts.target.outputBasename);
11840
12201
  const outputBasename = opts.target.outputBasename;
11841
12202
  const rawMdEv = opts.markdownEvidenceRepairAttempts;
11842
12203
  let maxMarkdownEvidenceRepairs = rawMdEv === void 0 ? 2 : Math.floor(Number(rawMdEv));
@@ -11845,9 +12206,9 @@ async function runOpenCodeMarkdownDetailStep(opts) {
11845
12206
  }
11846
12207
  maxMarkdownEvidenceRepairs = Math.min(5, Math.max(0, maxMarkdownEvidenceRepairs));
11847
12208
  const totalMarkdownRuns = 1 + maxMarkdownEvidenceRepairs;
11848
- fs19.mkdirSync(path16.dirname(outputFileAbsolute), { recursive: true });
11849
- const runDir = path16.join(opts.worktreeRoot, ".opencode", "_run");
11850
- fs19.mkdirSync(runDir, { recursive: true });
12209
+ fs20.mkdirSync(path18.dirname(outputFileAbsolute), { recursive: true });
12210
+ const runDir = path18.join(opts.worktreeRoot, ".opencode", "_run");
12211
+ fs20.mkdirSync(runDir, { recursive: true });
11851
12212
  const safeStem = opts.target.outputBasename.replace(/[^A-Za-z0-9_.-]+/g, "_").slice(0, 120);
11852
12213
  let markdownEvidenceRepairAppendix;
11853
12214
  let lastPromptPath = "";
@@ -11882,12 +12243,12 @@ async function runOpenCodeMarkdownDetailStep(opts) {
11882
12243
  citationFormatExample: CITATION_EXAMPLE,
11883
12244
  repairAppendix: markdownEvidenceRepairAppendix
11884
12245
  });
11885
- const promptPath = path16.join(
12246
+ const promptPath = path18.join(
11886
12247
  runDir,
11887
12248
  `markdownDetail-${opts.target.openCodeAgentStem}-${safeStem}-a${attempt}-${Date.now()}.prompt.txt`
11888
12249
  );
11889
12250
  lastPromptPath = promptPath;
11890
- fs19.writeFileSync(promptPath, body, "utf-8");
12251
+ fs20.writeFileSync(promptPath, body, "utf-8");
11891
12252
  const vars = {
11892
12253
  promptFile: promptPath,
11893
12254
  agentId: opts.target.openCodeAgentStem,
@@ -11912,7 +12273,8 @@ async function runOpenCodeMarkdownDetailStep(opts) {
11912
12273
  diagnosticLog: opts.diagnosticLog,
11913
12274
  onAgentLaunched: opts.onAgentLaunched,
11914
12275
  signal: opts.abortSignal,
11915
- parseOpenCodeSessionFromJsonStdout: !isFollowUpSpawn
12276
+ parseOpenCodeSessionFromJsonStdout: !isFollowUpSpawn,
12277
+ childEnv: opts.openCodeChildEnv
11916
12278
  });
11917
12279
  if (cli.cancelled || opts.abortSignal?.aborted) {
11918
12280
  return {
@@ -11979,7 +12341,8 @@ async function runOpenCodeMarkdownDetailStep(opts) {
11979
12341
  artefactRunId: mdRunId,
11980
12342
  artefactWorkItemId: mdWorkItemId,
11981
12343
  producerOpenCodeSessionId: capturedOpenCodeSessionId
11982
- }
12344
+ },
12345
+ opts.openCodeChildEnv
11983
12346
  );
11984
12347
  if (oqRes.cancelled) {
11985
12348
  return {
@@ -12071,7 +12434,7 @@ Expected output: ${outputFileAbsolute}`;
12071
12434
  }
12072
12435
  const ok = cliProcessOk && artefactOk && validationOk;
12073
12436
  if (ok && opts.aceEnabled && sessionIdForAceTrace) {
12074
- const rel = path16.relative(ctx, outputFileAbsolute).split(path16.sep).join("/");
12437
+ const rel = path18.relative(ctx, outputFileAbsolute).split(path18.sep).join("/");
12075
12438
  opts.onRemediationPhase?.("aceTracing");
12076
12439
  try {
12077
12440
  await runAceTracePhase({
@@ -12106,39 +12469,20 @@ Expected output: ${outputFileAbsolute}`;
12106
12469
  message,
12107
12470
  outputPath: outputFileAbsolute,
12108
12471
  promptPath: lastPromptPath,
12109
- step: MARKDOWN_DETAIL_RUN_STEP,
12110
- ...capturedOpenCodeSessionId ? {
12111
- openCodeSessionId: capturedOpenCodeSessionId,
12112
- openCodeSessionTitle: mdTitleForStore,
12113
- openCodeSessionCapturedAtIso: (/* @__PURE__ */ new Date()).toISOString()
12114
- } : {}
12115
- };
12116
- }
12117
-
12118
- // src/analysis/worktreeManager.ts
12119
- var import_child_process2 = require("child_process");
12120
- var fs21 = __toESM(require("fs"));
12121
- var os = __toESM(require("os"));
12122
- var path18 = __toESM(require("path"));
12123
-
12124
- // src/analysis/easySpecsWorktreeMarker.ts
12125
- var fs20 = __toESM(require("fs"));
12126
- var path17 = __toESM(require("path"));
12127
- var EASYSPECS_LOCAL_DIR = ".easyspecs";
12128
- var EASYSPECS_WORKTREE_MARKER_REL = path17.join(EASYSPECS_LOCAL_DIR, "analysis-worktree.json");
12129
- var MARKER_KIND = "easyspecs-analysis-worktree";
12130
- function writeAnalysisWorktreeMarker(worktreeRoot, repositoryRoot) {
12131
- const dir = path17.join(worktreeRoot, EASYSPECS_LOCAL_DIR);
12132
- fs20.mkdirSync(dir, { recursive: true });
12133
- const payload = {
12134
- kind: MARKER_KIND,
12135
- repositoryRoot: path17.resolve(repositoryRoot)
12472
+ step: MARKDOWN_DETAIL_RUN_STEP,
12473
+ ...capturedOpenCodeSessionId ? {
12474
+ openCodeSessionId: capturedOpenCodeSessionId,
12475
+ openCodeSessionTitle: mdTitleForStore,
12476
+ openCodeSessionCapturedAtIso: (/* @__PURE__ */ new Date()).toISOString()
12477
+ } : {}
12136
12478
  };
12137
- fs20.writeFileSync(path17.join(dir, "analysis-worktree.json"), `${JSON.stringify(payload)}
12138
- `, "utf-8");
12139
12479
  }
12140
12480
 
12141
12481
  // src/analysis/worktreeManager.ts
12482
+ var import_child_process2 = require("child_process");
12483
+ var fs21 = __toESM(require("fs"));
12484
+ var os = __toESM(require("os"));
12485
+ var path19 = __toESM(require("path"));
12142
12486
  var EASYSPECS_WT_DIR_RE = /^easyspecs-(\d+)$/;
12143
12487
  function maxEasyspecsWorktreeIndex(parentDir) {
12144
12488
  let max = 0;
@@ -12171,7 +12515,7 @@ function allocateEasyspecsWorktreePath(parentDir) {
12171
12515
  const start = maxEasyspecsWorktreeIndex(parentDir);
12172
12516
  for (let i = start + 1; i < start + 1e4; i++) {
12173
12517
  const name = formatEasyspecsWorktreeFolderName(i);
12174
- const wt = path18.join(parentDir, name);
12518
+ const wt = path19.join(parentDir, name);
12175
12519
  if (!fs21.existsSync(wt)) {
12176
12520
  return wt;
12177
12521
  }
@@ -12191,8 +12535,8 @@ function readHeadBranchShort(repoRoot) {
12191
12535
  return b.length > 0 ? b : void 0;
12192
12536
  }
12193
12537
  function attachWorktreeHandle(worktreePath, repositoryRoot) {
12194
- const wt = path18.resolve(worktreePath);
12195
- const repo = path18.resolve(repositoryRoot);
12538
+ const wt = path19.resolve(worktreePath);
12539
+ const repo = path19.resolve(repositoryRoot);
12196
12540
  return {
12197
12541
  path: wt,
12198
12542
  repoRoot: repo,
@@ -12210,7 +12554,7 @@ function attachWorktreeHandle(worktreePath, repositoryRoot) {
12210
12554
  };
12211
12555
  }
12212
12556
  function createAnalysisWorktree(repoRoot) {
12213
- const parent = path18.join(os.tmpdir(), "easyspecs-analysis");
12557
+ const parent = path19.join(os.tmpdir(), "easyspecs-analysis");
12214
12558
  const wt = allocateEasyspecsWorktreePath(parent);
12215
12559
  if (!wt) {
12216
12560
  return {
@@ -12247,12 +12591,12 @@ var STORAGE_KEY = "easyspecs.analysis.artefactRun.v1";
12247
12591
  function randomRunId() {
12248
12592
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
12249
12593
  }
12250
- function isRecord2(v) {
12594
+ function isRecord3(v) {
12251
12595
  return v !== null && typeof v === "object" && !Array.isArray(v);
12252
12596
  }
12253
12597
  function readArtefactRunSnapshot(context) {
12254
12598
  const raw = context.workspaceState.get(STORAGE_KEY);
12255
- if (!raw || !isRecord2(raw)) {
12599
+ if (!raw || !isRecord3(raw)) {
12256
12600
  return void 0;
12257
12601
  }
12258
12602
  const runId = raw.runId;
@@ -12261,7 +12605,7 @@ function readArtefactRunSnapshot(context) {
12261
12605
  const startedAtIso = raw.startedAtIso;
12262
12606
  const overallStatus = raw.overallStatus;
12263
12607
  const items = raw.items;
12264
- if (typeof runId !== "string" || typeof repositoryRoot !== "string" || typeof analysisRoot !== "string" || typeof startedAtIso !== "string" || overallStatus !== "idle" && overallStatus !== "running" && overallStatus !== "complete" && overallStatus !== "failed" || !isRecord2(items)) {
12608
+ if (typeof runId !== "string" || typeof repositoryRoot !== "string" || typeof analysisRoot !== "string" || typeof startedAtIso !== "string" || overallStatus !== "idle" && overallStatus !== "running" && overallStatus !== "complete" && overallStatus !== "failed" || !isRecord3(items)) {
12265
12609
  return void 0;
12266
12610
  }
12267
12611
  const fin = raw.finishedAtIso;
@@ -12325,7 +12669,7 @@ function noteOpenCodeAgentLaunched() {
12325
12669
 
12326
12670
  // src/contextIndexAssembler.ts
12327
12671
  var fs24 = __toESM(require("fs"));
12328
- var path21 = __toESM(require("path"));
12672
+ var path22 = __toESM(require("path"));
12329
12673
 
12330
12674
  // src/srsModel.ts
12331
12675
  var APPLICATION_CONTEXT_KIND = "easyspecs.application-context";
@@ -12333,26 +12677,26 @@ var APPLICATION_CONTEXT_KIND = "easyspecs.application-context";
12333
12677
  // src/indexApplicationContextValidate.ts
12334
12678
  var import__3 = __toESM(require__());
12335
12679
  var fs23 = __toESM(require("fs"));
12336
- var path20 = __toESM(require("path"));
12680
+ var path21 = __toESM(require("path"));
12337
12681
 
12338
12682
  // src/shared/repoResourcesRoot.ts
12339
12683
  var fs22 = __toESM(require("node:fs"));
12340
- var path19 = __toESM(require("node:path"));
12341
- var CONTEXT_LIST_MARKER = path19.join("schemas", "context-lists", "zero-reference-classifier-record.schema.json");
12684
+ var path20 = __toESM(require("node:path"));
12685
+ var CONTEXT_LIST_MARKER = path20.join("schemas", "context-lists", "zero-reference-classifier-record.schema.json");
12342
12686
  function hasMarker(resourcesRoot) {
12343
- return fs22.existsSync(path19.join(resourcesRoot, CONTEXT_LIST_MARKER));
12687
+ return fs22.existsSync(path20.join(resourcesRoot, CONTEXT_LIST_MARKER));
12344
12688
  }
12345
12689
  function resolveRepoResourcesRoot() {
12346
12690
  const env = process.env.EASYSPECS_EXTENSION_ROOT?.trim();
12347
12691
  if (env) {
12348
- const r = path19.join(path19.resolve(env), "resources");
12692
+ const r = path20.join(path20.resolve(env), "resources");
12349
12693
  if (hasMarker(r)) {
12350
12694
  return r;
12351
12695
  }
12352
12696
  }
12353
- const candidates = [path19.join(__dirname, "..", "resources"), path19.join(__dirname, "..", "..", "resources")];
12697
+ const candidates = [path20.join(__dirname, "..", "resources"), path20.join(__dirname, "..", "..", "resources")];
12354
12698
  for (const c of candidates) {
12355
- const abs = path19.resolve(c);
12699
+ const abs = path20.resolve(c);
12356
12700
  if (hasMarker(abs)) {
12357
12701
  return abs;
12358
12702
  }
@@ -12362,10 +12706,10 @@ function resolveRepoResourcesRoot() {
12362
12706
  );
12363
12707
  }
12364
12708
  function resolveContextListSchemasDir() {
12365
- return path19.join(resolveRepoResourcesRoot(), "schemas", "context-lists");
12709
+ return path20.join(resolveRepoResourcesRoot(), "schemas", "context-lists");
12366
12710
  }
12367
12711
  function resolveIndexApplicationContextSchemaPath() {
12368
- return path19.join(resolveRepoResourcesRoot(), "schemas", "index-application-context.schema.json");
12712
+ return path20.join(resolveRepoResourcesRoot(), "schemas", "index-application-context.schema.json");
12369
12713
  }
12370
12714
 
12371
12715
  // src/indexApplicationContextValidate.ts
@@ -12374,7 +12718,7 @@ function getDefaultIndexSchemaPath() {
12374
12718
  return resolveIndexApplicationContextSchemaPath();
12375
12719
  }
12376
12720
  function getValidator(schemaPath) {
12377
- const abs = path20.resolve(schemaPath);
12721
+ const abs = path21.resolve(schemaPath);
12378
12722
  let v = validators.get(abs);
12379
12723
  if (!v) {
12380
12724
  const raw = fs23.readFileSync(abs, "utf-8");
@@ -12430,7 +12774,7 @@ function tryAttachMarkdown(node, contextDir2, relativePath) {
12430
12774
  if (!norm || norm.includes("..")) {
12431
12775
  return false;
12432
12776
  }
12433
- const full = path21.join(contextDir2, norm);
12777
+ const full = path22.join(contextDir2, norm);
12434
12778
  try {
12435
12779
  if (!fs24.existsSync(full) || !fs24.statSync(full).isFile()) {
12436
12780
  return false;
@@ -12488,7 +12832,7 @@ function mergePersistedIndexUploadMetadata(previous, next) {
12488
12832
  walkScopes3(next, apply);
12489
12833
  }
12490
12834
  function tryReadExistingIndexForMerge(contextDir2) {
12491
- const p = path21.join(contextDir2, "index-application-context.json");
12835
+ const p = path22.join(contextDir2, "index-application-context.json");
12492
12836
  try {
12493
12837
  if (!fs24.existsSync(p)) {
12494
12838
  return null;
@@ -12547,7 +12891,7 @@ function applyDetailMarkdownToDocument(doc, contextDir2) {
12547
12891
  }
12548
12892
  }
12549
12893
  }
12550
- const exp = readJson2(path21.join(contextDir2, "experiences-list.json"));
12894
+ const exp = readJson2(path22.join(contextDir2, "experiences-list.json"));
12551
12895
  if (doc.scopes.Experience?.nodes && exp?.views) {
12552
12896
  for (const vNode of doc.scopes.Experience.nodes) {
12553
12897
  if (vNode.type !== "View" || !vNode.code) {
@@ -12573,7 +12917,7 @@ function applyDetailMarkdownToDocument(doc, contextDir2) {
12573
12917
  }
12574
12918
  }
12575
12919
  }
12576
- const svcData = readJson2(path21.join(contextDir2, "services-list.json"));
12920
+ const svcData = readJson2(path22.join(contextDir2, "services-list.json"));
12577
12921
  if (doc.scopes.Service?.nodes && svcData?.services) {
12578
12922
  for (const sNode of doc.scopes.Service.nodes) {
12579
12923
  if (sNode.type !== "Service" || !sNode.code) {
@@ -12599,7 +12943,7 @@ function applyDetailMarkdownToDocument(doc, contextDir2) {
12599
12943
  }
12600
12944
  }
12601
12945
  }
12602
- const dmData = readJson2(path21.join(contextDir2, "data-model-list.json"));
12946
+ const dmData = readJson2(path22.join(contextDir2, "data-model-list.json"));
12603
12947
  if (doc.scopes.DataModel?.nodes && dmData?.entities) {
12604
12948
  for (const eNode of doc.scopes.DataModel.nodes) {
12605
12949
  if (eNode.type !== "Entity" || !eNode.code) {
@@ -12631,7 +12975,7 @@ function applyDetailMarkdownToDocument(doc, contextDir2) {
12631
12975
  }
12632
12976
  }
12633
12977
  }
12634
- const tsData = readJson2(path21.join(contextDir2, "tech-stack-list.json"));
12978
+ const tsData = readJson2(path22.join(contextDir2, "tech-stack-list.json"));
12635
12979
  if (doc.scopes.TechStack?.nodes && tsData?.tools) {
12636
12980
  for (const tNode of doc.scopes.TechStack.nodes) {
12637
12981
  if (tNode.type !== "Tool" || !tNode.code) {
@@ -12655,7 +12999,7 @@ function readJson2(filePath) {
12655
12999
  }
12656
13000
  }
12657
13001
  function safeReadFeaturesList(contextDir2) {
12658
- const p = path21.join(contextDir2, "features-list.json");
13002
+ const p = path22.join(contextDir2, "features-list.json");
12659
13003
  const data = readJson2(p);
12660
13004
  if (!data?.features?.length) {
12661
13005
  return null;
@@ -12663,7 +13007,7 @@ function safeReadFeaturesList(contextDir2) {
12663
13007
  return data;
12664
13008
  }
12665
13009
  function safeReadUseCasesList(contextDir2, feCode) {
12666
- const p = path21.join(contextDir2, `${feCode}-use-cases-list.json`);
13010
+ const p = path22.join(contextDir2, `${feCode}-use-cases-list.json`);
12667
13011
  const data = readJson2(p);
12668
13012
  if (!data?.useCases?.length) {
12669
13013
  return null;
@@ -12671,7 +13015,7 @@ function safeReadUseCasesList(contextDir2, feCode) {
12671
13015
  return data;
12672
13016
  }
12673
13017
  function scenariosListPath(contextDir2, feCode, ucCode) {
12674
- return path21.join(contextDir2, `${feCode}_${ucCode}-scenarios-list.json`);
13018
+ return path22.join(contextDir2, `${feCode}_${ucCode}-scenarios-list.json`);
12675
13019
  }
12676
13020
  function buildFeatureScope(contextDir2) {
12677
13021
  const fl = safeReadFeaturesList(contextDir2);
@@ -12708,7 +13052,7 @@ function buildFeatureScope(contextDir2) {
12708
13052
  return nodes;
12709
13053
  }
12710
13054
  function buildExperienceScope(contextDir2) {
12711
- const data = readJson2(path21.join(contextDir2, "experiences-list.json"));
13055
+ const data = readJson2(path22.join(contextDir2, "experiences-list.json"));
12712
13056
  if (!data?.views?.length) {
12713
13057
  return [];
12714
13058
  }
@@ -12720,7 +13064,7 @@ function buildExperienceScope(contextDir2) {
12720
13064
  }));
12721
13065
  }
12722
13066
  function buildServiceScope(contextDir2) {
12723
- const data = readJson2(path21.join(contextDir2, "services-list.json"));
13067
+ const data = readJson2(path22.join(contextDir2, "services-list.json"));
12724
13068
  if (!data?.services?.length) {
12725
13069
  return [];
12726
13070
  }
@@ -12732,13 +13076,13 @@ function buildServiceScope(contextDir2) {
12732
13076
  }));
12733
13077
  }
12734
13078
  function readEntityFieldsList(contextDir2, dmCode) {
12735
- const p = path21.join(contextDir2, `${dmCode}-fields-list.json`);
13079
+ const p = path22.join(contextDir2, `${dmCode}-fields-list.json`);
12736
13080
  const file = readJson2(p);
12737
13081
  const rows = Array.isArray(file?.fields) ? file.fields : [];
12738
13082
  return rows.filter((f) => Boolean(f?.code && f?.name));
12739
13083
  }
12740
13084
  function buildDataModelScope(contextDir2) {
12741
- const data = readJson2(path21.join(contextDir2, "data-model-list.json"));
13085
+ const data = readJson2(path22.join(contextDir2, "data-model-list.json"));
12742
13086
  if (!data?.entities?.length) {
12743
13087
  return [];
12744
13088
  }
@@ -12756,7 +13100,7 @@ function buildDataModelScope(contextDir2) {
12756
13100
  });
12757
13101
  }
12758
13102
  function buildTechStackScope(contextDir2) {
12759
- const data = readJson2(path21.join(contextDir2, "tech-stack-list.json"));
13103
+ const data = readJson2(path22.join(contextDir2, "tech-stack-list.json"));
12760
13104
  if (!data?.tools?.length) {
12761
13105
  return [];
12762
13106
  }
@@ -12825,7 +13169,7 @@ function writeIndexApplicationContext(contextDir2, title, options) {
12825
13169
  if (!validation.ok) {
12826
13170
  throw new Error(`index-application-context.json schema validation failed: ${validation.errors.join("; ")}`);
12827
13171
  }
12828
- const target = path21.join(contextDir2, "index-application-context.json");
13172
+ const target = path22.join(contextDir2, "index-application-context.json");
12829
13173
  const tmp = `${target}.${process.pid}.tmp`;
12830
13174
  try {
12831
13175
  fs24.writeFileSync(tmp, JSON.stringify(doc, null, 2), "utf-8");
@@ -12844,54 +13188,67 @@ function writeIndexApplicationContext(contextDir2, title, options) {
12844
13188
  var path23 = __toESM(require("node:path"));
12845
13189
  var vscode2 = __toESM(require_vscode_stub());
12846
13190
 
12847
- // src/easySpecsWorkspaceSettingsCore.ts
13191
+ // src/config/easyspecsAceConfigRead.ts
12848
13192
  var fs25 = __toESM(require("node:fs"));
12849
- var path22 = __toESM(require("node:path"));
12850
- var EASYSPECS_WORKSPACE_DIR = EASYSPECS_LOCAL_DIR;
12851
- var EASYSPECS_SETTINGS_JSON = "settings.json";
12852
- var EASYSPECS_SETTINGS_ACE_KEY = "easyspecs.analysis.ace.enabled";
12853
- var EASYSPECS_SETTINGS_ACE_OFFLINE_AFTER_TRACE_KEY = "easyspecs.analysis.ace.offlineLearnAfterSameSessionTrace";
12854
- function isRecord3(v) {
12855
- return typeof v === "object" && v !== null && !Array.isArray(v);
12856
- }
12857
- function readAceEnabledFromEasySpecsSettingsFile(workspaceRootFsPath) {
12858
- const filePath = path22.join(workspaceRootFsPath, EASYSPECS_WORKSPACE_DIR, EASYSPECS_SETTINGS_JSON);
13193
+ function readRawConfigJson(repoRoot) {
13194
+ const p = easyspecsConfigPath(repoRoot);
13195
+ if (!fs25.existsSync(p)) {
13196
+ return void 0;
13197
+ }
12859
13198
  let raw;
12860
13199
  try {
12861
- raw = fs25.readFileSync(filePath, "utf8");
13200
+ raw = fs25.readFileSync(p, "utf8");
12862
13201
  } catch {
12863
13202
  return void 0;
12864
13203
  }
12865
- let parsed;
12866
13204
  try {
12867
- parsed = JSON.parse(raw);
13205
+ const parsed = JSON.parse(raw);
13206
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
13207
+ return void 0;
13208
+ }
13209
+ return parsed;
12868
13210
  } catch {
12869
13211
  return void 0;
12870
13212
  }
12871
- if (!isRecord3(parsed) || !Object.prototype.hasOwnProperty.call(parsed, EASYSPECS_SETTINGS_ACE_KEY)) {
13213
+ }
13214
+ function readAceEnabledFromEasyspecsConfigFile(repoRoot) {
13215
+ const parsed = readRawConfigJson(repoRoot);
13216
+ const es = parsed?.easyspecs;
13217
+ if (!es || typeof es !== "object" || Array.isArray(es)) {
12872
13218
  return void 0;
12873
13219
  }
12874
- const v = parsed[EASYSPECS_SETTINGS_ACE_KEY];
13220
+ const analysis = es.analysis;
13221
+ if (!analysis || typeof analysis !== "object" || Array.isArray(analysis)) {
13222
+ return void 0;
13223
+ }
13224
+ const ace = analysis.ace;
13225
+ if (!ace || typeof ace !== "object" || Array.isArray(ace)) {
13226
+ return void 0;
13227
+ }
13228
+ if (!Object.prototype.hasOwnProperty.call(ace, "enabled")) {
13229
+ return void 0;
13230
+ }
13231
+ const v = ace.enabled;
12875
13232
  return typeof v === "boolean" ? v : void 0;
12876
13233
  }
12877
- function readAceOfflineLearnAfterSameSessionTraceFromEasySpecsSettingsFile(workspaceRootFsPath) {
12878
- const filePath = path22.join(workspaceRootFsPath, EASYSPECS_WORKSPACE_DIR, EASYSPECS_SETTINGS_JSON);
12879
- let raw;
12880
- try {
12881
- raw = fs25.readFileSync(filePath, "utf8");
12882
- } catch {
13234
+ function readAceOfflineLearnAfterSameSessionTraceFromEasyspecsConfigFile(repoRoot) {
13235
+ const parsed = readRawConfigJson(repoRoot);
13236
+ const es = parsed?.easyspecs;
13237
+ if (!es || typeof es !== "object" || Array.isArray(es)) {
12883
13238
  return void 0;
12884
13239
  }
12885
- let parsed;
12886
- try {
12887
- parsed = JSON.parse(raw);
12888
- } catch {
13240
+ const analysis = es.analysis;
13241
+ if (!analysis || typeof analysis !== "object" || Array.isArray(analysis)) {
12889
13242
  return void 0;
12890
13243
  }
12891
- if (!isRecord3(parsed) || !Object.prototype.hasOwnProperty.call(parsed, EASYSPECS_SETTINGS_ACE_OFFLINE_AFTER_TRACE_KEY)) {
13244
+ const ace = analysis.ace;
13245
+ if (!ace || typeof ace !== "object" || Array.isArray(ace)) {
12892
13246
  return void 0;
12893
13247
  }
12894
- const v = parsed[EASYSPECS_SETTINGS_ACE_OFFLINE_AFTER_TRACE_KEY];
13248
+ if (!Object.prototype.hasOwnProperty.call(ace, "offlineLearnAfterSameSessionTrace")) {
13249
+ return void 0;
13250
+ }
13251
+ const v = ace.offlineLearnAfterSameSessionTrace;
12895
13252
  return typeof v === "boolean" ? v : void 0;
12896
13253
  }
12897
13254
 
@@ -12927,9 +13284,13 @@ function getAceAnalysisEnabledForCheckout(analysisCheckoutRoot) {
12927
13284
  push(cliWorkspaceRootOverride);
12928
13285
  push(vscode2.workspace.workspaceFolders?.[0]?.uri.fsPath);
12929
13286
  for (const root of roots) {
12930
- const fromFile = readAceEnabledFromEasySpecsSettingsFile(root);
12931
- if (fromFile !== void 0) {
12932
- return fromFile;
13287
+ const fromConfig = readAceEnabledFromEasyspecsConfigFile(root);
13288
+ if (fromConfig !== void 0) {
13289
+ return fromConfig;
13290
+ }
13291
+ const fromLegacy = readAceEnabledFromEasySpecsSettingsFile(root);
13292
+ if (fromLegacy !== void 0) {
13293
+ return fromLegacy;
12933
13294
  }
12934
13295
  }
12935
13296
  if (cliHeadlessMode) {
@@ -12956,9 +13317,13 @@ function getAceOfflineLearnAfterSameSessionTrace(analysisCheckoutRoot) {
12956
13317
  push(cliWorkspaceRootOverride);
12957
13318
  push(vscode2.workspace.workspaceFolders?.[0]?.uri.fsPath);
12958
13319
  for (const root of roots) {
12959
- const fromFile = readAceOfflineLearnAfterSameSessionTraceFromEasySpecsSettingsFile(root);
12960
- if (fromFile !== void 0) {
12961
- return fromFile;
13320
+ const fromConfig = readAceOfflineLearnAfterSameSessionTraceFromEasyspecsConfigFile(root);
13321
+ if (fromConfig !== void 0) {
13322
+ return fromConfig;
13323
+ }
13324
+ const fromLegacy = readAceOfflineLearnAfterSameSessionTraceFromEasySpecsSettingsFile(root);
13325
+ if (fromLegacy !== void 0) {
13326
+ return fromLegacy;
12962
13327
  }
12963
13328
  }
12964
13329
  if (cliHeadlessMode) {
@@ -13115,91 +13480,91 @@ function mdItem(id, parentIds, t) {
13115
13480
  };
13116
13481
  }
13117
13482
  function featureDetailTarget(contextDir2, code, name, slug) {
13118
- const basename11 = `${code}-${slug}.md`;
13483
+ const basename12 = `${code}-${slug}.md`;
13119
13484
  return {
13120
13485
  openCodeAgentStem: "agent-md-feature-detail",
13121
13486
  agentId: "ctx-md-feature-detail",
13122
13487
  displayName: "Feature detail",
13123
- outputBasename: basename11,
13488
+ outputBasename: basename12,
13124
13489
  taskDescription: `Document feature **${code}** (**${name}**, slug **${slug}**) per \`.gluecharm/context/features-list.json\`: scope, behaviour, main dependencies, entry points, and links to code under the worktree. Write exactly one markdown file at the output path; cite substantive claims with file and line (or range).`,
13125
- exists: fs27.existsSync(path25.join(contextDir2, basename11)) && fs27.statSync(path25.join(contextDir2, basename11)).size > 0
13490
+ exists: fs27.existsSync(path25.join(contextDir2, basename12)) && fs27.statSync(path25.join(contextDir2, basename12)).size > 0
13126
13491
  };
13127
13492
  }
13128
13493
  function viewDetailTarget(row2, contextDir2) {
13129
- const basename11 = `${row2.code}-${row2.slug}.md`;
13494
+ const basename12 = `${row2.code}-${row2.slug}.md`;
13130
13495
  return {
13131
13496
  openCodeAgentStem: "agent-md-view-detail",
13132
13497
  agentId: "ctx-md-view-detail",
13133
13498
  displayName: "View detail",
13134
- outputBasename: basename11,
13499
+ outputBasename: basename12,
13135
13500
  taskDescription: `Describe view **${row2.code}** (**${row2.name}**, slug **${row2.slug}**) per \`.gluecharm/context/experiences-list.json\`: purpose, layout, controls, navigation, data shown; ground in components and routes.`,
13136
- exists: fs27.existsSync(path25.join(contextDir2, basename11)) && fs27.statSync(path25.join(contextDir2, basename11)).size > 0
13501
+ exists: fs27.existsSync(path25.join(contextDir2, basename12)) && fs27.statSync(path25.join(contextDir2, basename12)).size > 0
13137
13502
  };
13138
13503
  }
13139
13504
  function interactionDetailTarget(viewCode, ix, contextDir2) {
13140
- const basename11 = `${viewCode}_${ix.code}-${ix.slug}.md`;
13505
+ const basename12 = `${viewCode}_${ix.code}-${ix.slug}.md`;
13141
13506
  return {
13142
13507
  openCodeAgentStem: "agent-md-interaction-detail",
13143
13508
  agentId: "ctx-md-interaction-detail",
13144
13509
  displayName: "Interaction detail",
13145
- outputBasename: basename11,
13510
+ outputBasename: basename12,
13146
13511
  taskDescription: `Document interaction **${ix.code}** (**${ix.name}**) on view **${viewCode}** per \`.gluecharm/context/experiences-list.json\`.`,
13147
- exists: fs27.existsSync(path25.join(contextDir2, basename11)) && fs27.statSync(path25.join(contextDir2, basename11)).size > 0
13512
+ exists: fs27.existsSync(path25.join(contextDir2, basename12)) && fs27.statSync(path25.join(contextDir2, basename12)).size > 0
13148
13513
  };
13149
13514
  }
13150
13515
  function serviceDetailTarget(row2, contextDir2) {
13151
- const basename11 = `${row2.code}-${row2.slug}.md`;
13516
+ const basename12 = `${row2.code}-${row2.slug}.md`;
13152
13517
  return {
13153
13518
  openCodeAgentStem: "agent-md-service-detail",
13154
13519
  agentId: "ctx-md-service-detail",
13155
13520
  displayName: "Service detail",
13156
- outputBasename: basename11,
13521
+ outputBasename: basename12,
13157
13522
  taskDescription: `Describe service **${row2.code}** (**${row2.name}**, slug **${row2.slug}**) per \`.gluecharm/context/services-list.json\`: responsibilities, consumers, errors, integration points.`,
13158
- exists: fs27.existsSync(path25.join(contextDir2, basename11)) && fs27.statSync(path25.join(contextDir2, basename11)).size > 0
13523
+ exists: fs27.existsSync(path25.join(contextDir2, basename12)) && fs27.statSync(path25.join(contextDir2, basename12)).size > 0
13159
13524
  };
13160
13525
  }
13161
13526
  function methodDetailTarget(svc, m, contextDir2) {
13162
- const basename11 = `${svc.code}_${m.code}-${m.slug}.md`;
13527
+ const basename12 = `${svc.code}_${m.code}-${m.slug}.md`;
13163
13528
  return {
13164
13529
  openCodeAgentStem: "agent-md-method-detail",
13165
13530
  agentId: "ctx-md-method-detail",
13166
13531
  displayName: "Method detail",
13167
- outputBasename: basename11,
13532
+ outputBasename: basename12,
13168
13533
  taskDescription: `Document method **${m.code}** (**${m.name}**) on service **${svc.code}** per \`.gluecharm/context/services-list.json\`.`,
13169
- exists: fs27.existsSync(path25.join(contextDir2, basename11)) && fs27.statSync(path25.join(contextDir2, basename11)).size > 0
13534
+ exists: fs27.existsSync(path25.join(contextDir2, basename12)) && fs27.statSync(path25.join(contextDir2, basename12)).size > 0
13170
13535
  };
13171
13536
  }
13172
13537
  function entityDetailTarget(dmCode, ename, slug, contextDir2) {
13173
- const basename11 = `${dmCode}-${slug}.md`;
13538
+ const basename12 = `${dmCode}-${slug}.md`;
13174
13539
  return {
13175
13540
  openCodeAgentStem: "agent-md-entity-detail",
13176
13541
  agentId: "ctx-md-entity-detail",
13177
13542
  displayName: "Entity detail",
13178
- outputBasename: basename11,
13543
+ outputBasename: basename12,
13179
13544
  taskDescription: `Describe entity **${dmCode}** (**${ename}**, slug **${slug}**) per \`.gluecharm/context/data-model-list.json\`: lifecycle, invariants, storage, ORM/schema mapping.`,
13180
- exists: fs27.existsSync(path25.join(contextDir2, basename11)) && fs27.statSync(path25.join(contextDir2, basename11)).size > 0
13545
+ exists: fs27.existsSync(path25.join(contextDir2, basename12)) && fs27.statSync(path25.join(contextDir2, basename12)).size > 0
13181
13546
  };
13182
13547
  }
13183
13548
  function fieldDetailTarget(dmCode, fdCode, fname, fSlug, contextDir2) {
13184
- const basename11 = `${dmCode}_${fdCode}-${fSlug}.md`;
13549
+ const basename12 = `${dmCode}_${fdCode}-${fSlug}.md`;
13185
13550
  return {
13186
13551
  openCodeAgentStem: "agent-md-field-detail",
13187
13552
  agentId: "ctx-md-field-detail",
13188
13553
  displayName: "Field detail",
13189
- outputBasename: basename11,
13554
+ outputBasename: basename12,
13190
13555
  taskDescription: `Document field **${fdCode}** (**${fname}**) on entity **${dmCode}** per \`.gluecharm/context/${dmCode}-fields-list.json\`. Cite types and constraints with file and line.`,
13191
- exists: fs27.existsSync(path25.join(contextDir2, basename11)) && fs27.statSync(path25.join(contextDir2, basename11)).size > 0
13556
+ exists: fs27.existsSync(path25.join(contextDir2, basename12)) && fs27.statSync(path25.join(contextDir2, basename12)).size > 0
13192
13557
  };
13193
13558
  }
13194
13559
  function toolDetailTarget(row2, contextDir2) {
13195
- const basename11 = `${row2.code}-${row2.slug}.md`;
13560
+ const basename12 = `${row2.code}-${row2.slug}.md`;
13196
13561
  return {
13197
13562
  openCodeAgentStem: "agent-md-tool-detail",
13198
13563
  agentId: "ctx-md-tool-detail",
13199
13564
  displayName: "Tool detail",
13200
- outputBasename: basename11,
13565
+ outputBasename: basename12,
13201
13566
  taskDescription: `Describe tool **${row2.code}** (**${row2.name}**, slug **${row2.slug}**) per \`.gluecharm/context/tech-stack-list.json\`: role, version hints, configuration, boundaries.`,
13202
- exists: fs27.existsSync(path25.join(contextDir2, basename11)) && fs27.statSync(path25.join(contextDir2, basename11)).size > 0
13567
+ exists: fs27.existsSync(path25.join(contextDir2, basename12)) && fs27.statSync(path25.join(contextDir2, basename12)).size > 0
13203
13568
  };
13204
13569
  }
13205
13570
  function useCaseDetailTarget(feCode, ucCode, ucName, ucSlug, contextDir2) {
@@ -13216,14 +13581,14 @@ Follow bundled agent **agent-md-use-case-detail**: include **## Data inputs and
13216
13581
  };
13217
13582
  }
13218
13583
  function scenarioDetailTarget(feCode, ucCode, scCode, scName, contextDir2) {
13219
- const basename11 = `${feCode}_${ucCode}_${scCode}.md`;
13584
+ const basename12 = `${feCode}_${ucCode}_${scCode}.md`;
13220
13585
  return {
13221
13586
  openCodeAgentStem: "agent-md-scenario-detail",
13222
13587
  agentId: "ctx-md-scenario-detail",
13223
13588
  displayName: "Scenario detail",
13224
- outputBasename: basename11,
13589
+ outputBasename: basename12,
13225
13590
  taskDescription: `Document scenario **${scCode}** (**${scName}**) for **${feCode}** / **${ucCode}** per \`.gluecharm/context/${feCode}_${ucCode}-scenarios-list.json\`. Cite steps with file and line where possible.`,
13226
- exists: fs27.existsSync(path25.join(contextDir2, basename11)) && fs27.statSync(path25.join(contextDir2, basename11)).size > 0
13591
+ exists: fs27.existsSync(path25.join(contextDir2, basename12)) && fs27.statSync(path25.join(contextDir2, basename12)).size > 0
13227
13592
  };
13228
13593
  }
13229
13594
  function parentsDone(item, byId) {
@@ -13449,15 +13814,15 @@ function expandAfterSuccess(completed, contextDir2, byId, fifo) {
13449
13814
  return;
13450
13815
  }
13451
13816
  if (completed.kind === "markdown") {
13452
- const basename11 = completed.payload.outputBasename;
13817
+ const basename12 = completed.payload.outputBasename;
13453
13818
  const mdId = completed.id;
13454
- const feFromFeatureMd = basename11.match(/^(FE-\d+)-[^/]+\.md$/);
13819
+ const feFromFeatureMd = basename12.match(/^(FE-\d+)-[^/]+\.md$/);
13455
13820
  if (feFromFeatureMd) {
13456
13821
  const fe = feFromFeatureMd[1];
13457
13822
  add(coordItem(`coord:uc:${fe}`, [mdId], "listUseCases", { featureCode: fe }));
13458
13823
  return;
13459
13824
  }
13460
- const xpFromView = basename11.match(/^(XP-\d+)-[^/]+\.md$/);
13825
+ const xpFromView = basename12.match(/^(XP-\d+)-[^/]+\.md$/);
13461
13826
  if (xpFromView) {
13462
13827
  const xp = xpFromView[1];
13463
13828
  const row2 = discoverExperienceTreeRows(contextDir2).find((r) => r.code === xp);
@@ -13469,7 +13834,7 @@ function expandAfterSuccess(completed, contextDir2, byId, fifo) {
13469
13834
  }
13470
13835
  return;
13471
13836
  }
13472
- const svFromSvc = basename11.match(/^(SV-\d+)-[^/]+\.md$/);
13837
+ const svFromSvc = basename12.match(/^(SV-\d+)-[^/]+\.md$/);
13473
13838
  if (svFromSvc) {
13474
13839
  const sv = svFromSvc[1];
13475
13840
  const srow = discoverServiceTreeRows(contextDir2).find((r) => r.code === sv);
@@ -13481,7 +13846,7 @@ function expandAfterSuccess(completed, contextDir2, byId, fifo) {
13481
13846
  }
13482
13847
  return;
13483
13848
  }
13484
- const dmFromEnt = basename11.match(/^(DM-\d+)-[^/]+\.md$/);
13849
+ const dmFromEnt = basename12.match(/^(DM-\d+)-[^/]+\.md$/);
13485
13850
  if (dmFromEnt) {
13486
13851
  const dm = dmFromEnt[1];
13487
13852
  add(coordItem(`coord:ef:${dm}`, [mdId], "listEntityFields", { entityCode: dm }));
@@ -13520,7 +13885,8 @@ async function runArtefactWorkItemOpenCode(item, worktreeRoot, workspaceLabel, o
13520
13885
  artefactRunId,
13521
13886
  artefactWorkItemId: item.id,
13522
13887
  aceEnabled: oc.aceEnabled === true,
13523
- offlineLearnAfterSameSessionTrace: oc.offlineLearnAfterSameSessionTrace === true
13888
+ offlineLearnAfterSameSessionTrace: oc.offlineLearnAfterSameSessionTrace === true,
13889
+ openCodeChildEnv: oc.openCodeChildEnv
13524
13890
  };
13525
13891
  if (item.kind === "coord") {
13526
13892
  const { step, listTarget } = item.payload;
@@ -13577,6 +13943,7 @@ async function runArtefactWorkItemOpenCode(item, worktreeRoot, workspaceLabel, o
13577
13943
  artefactWorkItemId: item.id,
13578
13944
  aceEnabled: oc.aceEnabled === true,
13579
13945
  offlineLearnAfterSameSessionTrace: oc.offlineLearnAfterSameSessionTrace === true,
13946
+ openCodeChildEnv: oc.openCodeChildEnv,
13580
13947
  target: {
13581
13948
  openCodeAgentStem: p.openCodeAgentStem,
13582
13949
  agentId: p.agentId,
@@ -13645,8 +14012,8 @@ async function drainArtefactWorkPool(p) {
13645
14012
  const artefactRunId = readArtefactRunSnapshot(storageContext)?.runId ?? "unknown-run";
13646
14013
  let active = 0;
13647
14014
  let wake;
13648
- const waitTurn = () => new Promise((resolve14) => {
13649
- wake = resolve14;
14015
+ const waitTurn = () => new Promise((resolve15) => {
14016
+ wake = resolve15;
13650
14017
  });
13651
14018
  const persist = async () => {
13652
14019
  const items = {};
@@ -13886,7 +14253,8 @@ async function runContextArtefactPipelineAsync(storageContext, repoRoot, workspa
13886
14253
  try {
13887
14254
  materializeOpenCodeAgentsWithAce(extensionAgentsDirFs, handle.path, {
13888
14255
  enabled: aceEnabled,
13889
- diagnosticLog: log
14256
+ diagnosticLog: log,
14257
+ projectConfigOverlay: oc.projectConfigOverlay
13890
14258
  });
13891
14259
  } catch (e) {
13892
14260
  const msg = e instanceof Error ? e.message : String(e);
@@ -13973,7 +14341,7 @@ function macroSleep(ms, signal) {
13973
14341
  if (signal.aborted) {
13974
14342
  return Promise.reject(new DOMException("The operation was aborted.", "AbortError"));
13975
14343
  }
13976
- return new Promise((resolve14, reject) => {
14344
+ return new Promise((resolve15, reject) => {
13977
14345
  const onAbort = () => {
13978
14346
  clearTimeout(t);
13979
14347
  signal.removeEventListener("abort", onAbort);
@@ -13981,7 +14349,7 @@ function macroSleep(ms, signal) {
13981
14349
  };
13982
14350
  const t = setTimeout(() => {
13983
14351
  signal.removeEventListener("abort", onAbort);
13984
- resolve14();
14352
+ resolve15();
13985
14353
  }, ms);
13986
14354
  signal.addEventListener("abort", onAbort, { once: true });
13987
14355
  });
@@ -14174,7 +14542,7 @@ async function runMacroAnalysisOrchestration(deps) {
14174
14542
  setRowTimed("synthesis_convergence", "succeeded", "No missing artefacts.");
14175
14543
  await post();
14176
14544
  if (config.synthesisOnly) {
14177
- return { ok: true, message: "Synthesis-only macro complete.", totalElapsedMs: macroEnd() };
14545
+ return { ok: true, message: "Synthesis-only analysis complete.", totalElapsedMs: macroEnd() };
14178
14546
  }
14179
14547
  if (isAborted(signal)) {
14180
14548
  setRowTimed("reference_coverage", "cancelled");
@@ -14341,7 +14709,7 @@ async function runMacroAnalysisOrchestration(deps) {
14341
14709
  }
14342
14710
  setRowTimed("backend_context_sync", "succeeded", up.message);
14343
14711
  await post();
14344
- return { ok: true, message: "Full pipeline macro complete.", totalElapsedMs: macroEnd() };
14712
+ return { ok: true, message: "Analysis complete.", totalElapsedMs: macroEnd() };
14345
14713
  } catch (e) {
14346
14714
  const msg = e instanceof Error ? e.message : String(e);
14347
14715
  return fail(msg);
@@ -14516,8 +14884,8 @@ function expectedFeatureDetailBasenameFromRow(row2) {
14516
14884
  }
14517
14885
  return `${code}-${slug}.md`;
14518
14886
  }
14519
- function ctxPath(contextDir2, basename11) {
14520
- return path27.join(contextDir2, basename11);
14887
+ function ctxPath(contextDir2, basename12) {
14888
+ return path27.join(contextDir2, basename12);
14521
14889
  }
14522
14890
  function pushTarget(targets, stem, outputBasename, taskDescription, contextDir2) {
14523
14891
  const meta = STEM_TO_AGENT[stem];
@@ -14546,11 +14914,11 @@ function discoverDetailMarkdownGroups(contextDir2) {
14546
14914
  continue;
14547
14915
  }
14548
14916
  const name = safeStr3(row2.name) || code;
14549
- const basename11 = `${code}-${slug}.md`;
14917
+ const basename12 = `${code}-${slug}.md`;
14550
14918
  pushTarget(
14551
14919
  featureTargets,
14552
14920
  "agent-md-feature-detail",
14553
- basename11,
14921
+ basename12,
14554
14922
  `Document feature **${code}** (**${name}**, slug **${slug}**) per \`.gluecharm/context/features-list.json\`: scope, behaviour, main dependencies, entry points, and links to code under the worktree. Write exactly one markdown file at the output path; cite substantive claims with file and line (or range).`,
14555
14923
  contextDir2
14556
14924
  );
@@ -15738,8 +16106,8 @@ function formatAjvErrors5(errors) {
15738
16106
  });
15739
16107
  }
15740
16108
  var ajv = new import__5.default({ allErrors: true, strict: false });
15741
- function compileSchema(basename11) {
15742
- const schemaPath = path32.join(schemasDir(), basename11);
16109
+ function compileSchema(basename12) {
16110
+ const schemaPath = path32.join(schemasDir(), basename12);
15743
16111
  const schemaRaw = stripUtf8Bom5(fs33.readFileSync(schemaPath, "utf-8"));
15744
16112
  const schema = JSON.parse(schemaRaw);
15745
16113
  return ajv.compile(schema);
@@ -16346,7 +16714,8 @@ async function runClassifierAgent(common, contextDirAbs, targetFilePathPosix, wo
16346
16714
  executable: common.executable,
16347
16715
  diagnosticLog: common.diagnosticLog,
16348
16716
  onAgentLaunched: common.onAgentLaunched,
16349
- signal: common.abortSignal
16717
+ signal: common.abortSignal,
16718
+ ...common.openCodeChildEnv ? { childEnv: common.openCodeChildEnv } : {}
16350
16719
  });
16351
16720
  if (cli.cancelled || common.abortSignal?.aborted) {
16352
16721
  return { ok: false, message: "Stopped.", stagingPath: outAbs, cancelled: true };
@@ -16492,7 +16861,8 @@ async function runMarkdownReferenceAgent(common, contextDirAbs, targetFilePathPo
16492
16861
  executable: common.executable,
16493
16862
  diagnosticLog: common.diagnosticLog,
16494
16863
  onAgentLaunched: common.onAgentLaunched,
16495
- signal: common.abortSignal
16864
+ signal: common.abortSignal,
16865
+ ...common.openCodeChildEnv ? { childEnv: common.openCodeChildEnv } : {}
16496
16866
  });
16497
16867
  if (cli.cancelled || common.abortSignal?.aborted) {
16498
16868
  return { ok: false, message: "Stopped.", cancelled: true };
@@ -16553,7 +16923,8 @@ async function runCoordinationTriageAgent(common, contextDirAbs, targetFilePathP
16553
16923
  executable: common.executable,
16554
16924
  diagnosticLog: common.diagnosticLog,
16555
16925
  onAgentLaunched: common.onAgentLaunched,
16556
- signal: common.abortSignal
16926
+ signal: common.abortSignal,
16927
+ ...common.openCodeChildEnv ? { childEnv: common.openCodeChildEnv } : {}
16557
16928
  });
16558
16929
  if (cli.cancelled || common.abortSignal?.aborted) {
16559
16930
  return { ok: false, message: "Stopped.", stagingPath: outAbs, cancelled: true };
@@ -17596,18 +17967,18 @@ async function runParallelSinglesIntoAccum(requestJson, applicationId, paths, on
17596
17967
  const recentWaveMs = [];
17597
17968
  let idx = 0;
17598
17969
  const runOne = async (absPath) => {
17599
- const basename11 = path37.basename(absPath);
17970
+ const basename12 = path37.basename(absPath);
17600
17971
  let content;
17601
17972
  try {
17602
17973
  content = fs38.readFileSync(absPath, "utf8");
17603
17974
  } catch (e) {
17604
17975
  const msg = errorMessage(e);
17605
- logSrs13Failure(log, `read file file=${basename11}`, msg);
17976
+ logSrs13Failure(log, `read file file=${basename12}`, msg);
17606
17977
  accum.failed.push({ path: absPath, message: msg });
17607
17978
  return;
17608
17979
  }
17609
17980
  const existingId = resolveExistingId(absPath);
17610
- const payload = buildSrsDiscoverySavePayload(applicationId, basename11, content, existingId);
17981
+ const payload = buildSrsDiscoverySavePayload(applicationId, basename12, content, existingId);
17611
17982
  try {
17612
17983
  await postSingleCreate(requestJson, payload, log);
17613
17984
  accum.succeeded.push(absPath);
@@ -17621,7 +17992,7 @@ async function runParallelSinglesIntoAccum(requestJson, applicationId, paths, on
17621
17992
  throw e;
17622
17993
  }
17623
17994
  const msg = errorMessage(e);
17624
- logSrs13Failure(log, `POST /api/content/srs_discovery file=${basename11}`, msg);
17995
+ logSrs13Failure(log, `POST /api/content/srs_discovery file=${basename12}`, msg);
17625
17996
  accum.failed.push({ path: absPath, message: msg });
17626
17997
  }
17627
17998
  };
@@ -17703,18 +18074,18 @@ async function executeContextSrsDiscoveryUploadPhase(filePaths, phaseOpts, accum
17703
18074
  const items = [];
17704
18075
  const chunkPaths = [];
17705
18076
  for (const absPath of chunk) {
17706
- const basename11 = path37.basename(absPath);
18077
+ const basename12 = path37.basename(absPath);
17707
18078
  let content;
17708
18079
  try {
17709
18080
  content = fs38.readFileSync(absPath, "utf8");
17710
18081
  } catch (e) {
17711
18082
  const msg = errorMessage(e);
17712
- logSrs13Failure(log, `read file file=${basename11}`, msg);
18083
+ logSrs13Failure(log, `read file file=${basename12}`, msg);
17713
18084
  accum.failed.push({ path: absPath, message: msg });
17714
18085
  continue;
17715
18086
  }
17716
18087
  const existingId = resolveExistingId(absPath);
17717
- items.push(buildSrsDiscoverySavePayloadForBatch(applicationId, basename11, content, existingId));
18088
+ items.push(buildSrsDiscoverySavePayloadForBatch(applicationId, basename12, content, existingId));
17718
18089
  chunkPaths.push(absPath);
17719
18090
  }
17720
18091
  if (items.length === 0) {
@@ -18207,14 +18578,14 @@ var RE_MD_SV = /^SV-\d+-.+\.md$/i;
18207
18578
  var RE_MD_DM_FD = /^DM-\d+_FD-\d+-.+\.md$/i;
18208
18579
  var RE_MD_DM = /^DM-\d+-.+\.md$/i;
18209
18580
  var RE_MD_TS = /^TS-\d+-.+\.md$/i;
18210
- function looksLikeCoordinationDetailMarkdownBasename(basename11) {
18211
- if (!basename11 || basename11 !== path39.basename(basename11) || !/\.md$/i.test(basename11)) {
18581
+ function looksLikeCoordinationDetailMarkdownBasename(basename12) {
18582
+ if (!basename12 || basename12 !== path39.basename(basename12) || !/\.md$/i.test(basename12)) {
18212
18583
  return false;
18213
18584
  }
18214
- if (STAPLE_CONTEXT_MARKDOWN_BASENAMES.has(basename11)) {
18585
+ if (STAPLE_CONTEXT_MARKDOWN_BASENAMES.has(basename12)) {
18215
18586
  return false;
18216
18587
  }
18217
- return RE_MD_FE_UC_SC.test(basename11) || RE_MD_FE_UC_SLUG.test(basename11) || RE_MD_FE_UC_PLAIN.test(basename11) || RE_MD_FE_FEATURE.test(basename11) || RE_MD_XP_BH.test(basename11) || RE_MD_XP_VIEW.test(basename11) || RE_MD_SV_ME.test(basename11) || RE_MD_SV.test(basename11) || RE_MD_DM_FD.test(basename11) || RE_MD_DM.test(basename11) || RE_MD_TS.test(basename11);
18588
+ return RE_MD_FE_UC_SC.test(basename12) || RE_MD_FE_UC_SLUG.test(basename12) || RE_MD_FE_UC_PLAIN.test(basename12) || RE_MD_FE_FEATURE.test(basename12) || RE_MD_XP_BH.test(basename12) || RE_MD_XP_VIEW.test(basename12) || RE_MD_SV_ME.test(basename12) || RE_MD_SV.test(basename12) || RE_MD_DM_FD.test(basename12) || RE_MD_DM.test(basename12) || RE_MD_TS.test(basename12);
18218
18589
  }
18219
18590
  function loadRawFeatureRows(contextDirAbs) {
18220
18591
  const p = path39.join(contextDirAbs, "features-list.json");
@@ -18229,8 +18600,8 @@ function loadRawFeatureRows(contextDirAbs) {
18229
18600
  return [];
18230
18601
  }
18231
18602
  }
18232
- function hintForOrphanFeatureMarkdown(basename11, featureRows) {
18233
- const m = /^FE-(\d+)-(.+)\.md$/i.exec(basename11);
18603
+ function hintForOrphanFeatureMarkdown(basename12, featureRows) {
18604
+ const m = /^FE-(\d+)-(.+)\.md$/i.exec(basename12);
18234
18605
  if (!m) {
18235
18606
  return void 0;
18236
18607
  }
@@ -18242,8 +18613,8 @@ function hintForOrphanFeatureMarkdown(basename11, featureRows) {
18242
18613
  continue;
18243
18614
  }
18244
18615
  const expected = expectedFeatureDetailBasenameFromRow(row2);
18245
- if (expected && expected !== basename11) {
18246
- return `features-list row ${code} currently implies detail file ${expected} (slug/name changed \u2014 ${basename11} may be stale).`;
18616
+ if (expected && expected !== basename12) {
18617
+ return `features-list row ${code} currently implies detail file ${expected} (slug/name changed \u2014 ${basename12} may be stale).`;
18247
18618
  }
18248
18619
  if (!expected) {
18249
18620
  return `features-list row ${code} has no resolvable slug for a detail markdown basename.`;
@@ -18678,6 +19049,13 @@ async function refreshTokenWithApi(apiBaseUrl, fetchImpl, refreshToken) {
18678
19049
  const tokens = normalizeTokens(payload);
18679
19050
  return { accessToken: tokens.accessToken, refreshToken: tokens.refreshToken };
18680
19051
  }
19052
+ function toFetchErrorMessage(e) {
19053
+ if (e && typeof e === "object" && "status" in e && "message" in e) {
19054
+ return e;
19055
+ }
19056
+ const msg = e instanceof Error ? e.message : String(e);
19057
+ return { status: 0, message: msg };
19058
+ }
18681
19059
 
18682
19060
  // src/auth/gluecharmContentNegotiation.ts
18683
19061
  var GLUECHARM_WS_LEGACY_JSON = "application/vnd.gluecharm.v1.ws-legacy+json";
@@ -18783,9 +19161,41 @@ var path40 = __toESM(require("node:path"));
18783
19161
  function defaultSessionPath() {
18784
19162
  return path40.join(os2.homedir(), ".easyspecs", "cli-session.json");
18785
19163
  }
19164
+ var configSessionPath;
19165
+ function setCliSessionPathFromConfig(absPath) {
19166
+ configSessionPath = absPath?.trim() ? path40.resolve(absPath) : void 0;
19167
+ }
19168
+ function applyCliSessionPathFromRepoConfig(repoRoot, cfg) {
19169
+ const raw = cfg.easyspecs?.cliSessionPath?.trim();
19170
+ if (!raw) {
19171
+ setCliSessionPathFromConfig(void 0);
19172
+ return;
19173
+ }
19174
+ const abs = path40.isAbsolute(raw) ? raw : path40.join(repoRoot, raw);
19175
+ setCliSessionPathFromConfig(abs);
19176
+ }
19177
+ function normalizeCliSessionPathForConfig(repoRoot, raw) {
19178
+ const t = raw.trim();
19179
+ if (!t) {
19180
+ return "";
19181
+ }
19182
+ const resolvedRepo = path40.resolve(repoRoot);
19183
+ const abs = path40.isAbsolute(t) ? path40.normalize(t) : path40.resolve(resolvedRepo, t);
19184
+ const rel = path40.relative(resolvedRepo, abs);
19185
+ if (rel === "") {
19186
+ return abs;
19187
+ }
19188
+ const underRepo = !rel.startsWith("..") && !path40.isAbsolute(rel);
19189
+ if (underRepo) {
19190
+ return rel.split(path40.sep).join("/");
19191
+ }
19192
+ return abs;
19193
+ }
18786
19194
  function effectiveCliSessionPath() {
18787
- const f = process.env.EASYSPECS_CLI_SESSION_FILE?.trim();
18788
- return f ? path40.resolve(f) : defaultSessionPath();
19195
+ if (configSessionPath) {
19196
+ return configSessionPath;
19197
+ }
19198
+ return defaultSessionPath();
18789
19199
  }
18790
19200
  function readCliSession() {
18791
19201
  const p = effectiveCliSessionPath();
@@ -18900,8 +19310,8 @@ async function runAceAutoLearnPool(p) {
18900
19310
  const fifo = [...p.traceAbsolutePaths];
18901
19311
  let active = 0;
18902
19312
  let wake;
18903
- const waitTurn = () => new Promise((resolve14) => {
18904
- wake = resolve14;
19313
+ const waitTurn = () => new Promise((resolve15) => {
19314
+ wake = resolve15;
18905
19315
  });
18906
19316
  const pump = () => {
18907
19317
  wake?.();
@@ -18992,8 +19402,82 @@ function parseTailFlags(tail) {
18992
19402
  return { rootKind, worktree };
18993
19403
  }
18994
19404
 
19405
+ // src/cli/parseDoctorFlags.ts
19406
+ var ALLOWED = /* @__PURE__ */ new Set(["--readiness", "--inspect-config"]);
19407
+ function parseDoctorFlags(tail) {
19408
+ for (const t of tail) {
19409
+ if (!ALLOWED.has(t)) {
19410
+ throw new Error(`Unknown doctor flag: ${t}`);
19411
+ }
19412
+ }
19413
+ if (tail.length === 0) {
19414
+ return { readiness: true, inspectConfig: false };
19415
+ }
19416
+ return {
19417
+ readiness: tail.includes("--readiness"),
19418
+ inspectConfig: tail.includes("--inspect-config")
19419
+ };
19420
+ }
19421
+
19422
+ // src/cli/parseAuthLoginTail.ts
19423
+ function extractAuthLoginSessionPathFromTail(tail) {
19424
+ const rest = [];
19425
+ let sessionPath;
19426
+ for (let i = 0; i < tail.length; i += 1) {
19427
+ const a = tail[i] ?? "";
19428
+ if (a === "--session-path") {
19429
+ const v = tail[i + 1];
19430
+ if (v === void 0 || v.startsWith("-")) {
19431
+ throw new Error("--session-path requires a value");
19432
+ }
19433
+ sessionPath = v;
19434
+ i += 1;
19435
+ continue;
19436
+ }
19437
+ rest.push(a);
19438
+ }
19439
+ return { rest, sessionPath };
19440
+ }
19441
+ function stripCiFlag(tail) {
19442
+ return tail.filter((t) => t !== "--ci");
19443
+ }
19444
+ function parseAuthLoginTail(tail) {
19445
+ const tokens = stripCiFlag(tail);
19446
+ let email;
19447
+ let password;
19448
+ for (let i = 0; i < tokens.length; i += 1) {
19449
+ const a = tokens[i] ?? "";
19450
+ if (a === "--email") {
19451
+ const v = tokens[i + 1];
19452
+ if (v === void 0 || v.startsWith("-")) {
19453
+ return { ok: false, error: "--email requires a value" };
19454
+ }
19455
+ email = v;
19456
+ i += 1;
19457
+ continue;
19458
+ }
19459
+ if (a === "--password") {
19460
+ const v = tokens[i + 1];
19461
+ if (v === void 0) {
19462
+ return { ok: false, error: "--password requires a value" };
19463
+ }
19464
+ password = v;
19465
+ i += 1;
19466
+ continue;
19467
+ }
19468
+ return { ok: false, error: `Unexpected argument: ${a}` };
19469
+ }
19470
+ if (!email?.trim()) {
19471
+ return { ok: false, error: "Missing --email (or use --ci with EASYSPECS_EMAIL / EASYSPECS_PASSWORD)" };
19472
+ }
19473
+ if (password === void 0) {
19474
+ return { ok: false, error: "Missing --password (or use --ci with EASYSPECS_EMAIL / EASYSPECS_PASSWORD)" };
19475
+ }
19476
+ return { ok: true, creds: { email: email.trim(), password } };
19477
+ }
19478
+
18995
19479
  // src/cli/main.ts
18996
- var PKG_VERSION = "0.0.3";
19480
+ var PKG_VERSION = "0.0.5";
18997
19481
  function logErr(flags, ...a) {
18998
19482
  if (!flags.json) {
18999
19483
  console.error(...a);
@@ -19001,6 +19485,12 @@ function logErr(flags, ...a) {
19001
19485
  console.error(...a);
19002
19486
  }
19003
19487
  }
19488
+ function buildDoctorInspectPayload(merged, repoConfig) {
19489
+ return {
19490
+ merged: redactMergedCliSettingsForDump(merged),
19491
+ easyspecsConfig: redactEasyspecsConfigRootForDump(repoConfig)
19492
+ };
19493
+ }
19004
19494
  function formatCommand(positionals) {
19005
19495
  return positionals.join(" ").trim() || "help";
19006
19496
  }
@@ -19015,18 +19505,21 @@ Global options:
19015
19505
  --ci Non-interactive; fail fast if configuration is missing
19016
19506
  --json Print one JSON summary line on stdout
19017
19507
  --verbose Extra stderr logging
19018
- --api-base-url <url> System Manager API origin
19019
- --promote / --no-promote Copy generated context into workspace after analyze (default: promote)
19508
+ --api-base-url <url> System Manager API origin (overrides config)
19509
+ --session-path <file> Session JSON for this run (overrides easyspecs.cliSessionPath; e.g. extension delegate)
19510
+ --environment production|staging Effective deployment (alias: --env); overrides easyspecs.deploymentEnvironment
19511
+ --promote / --no-promote Copy generated context into workspace after run synthesis (default: promote)
19020
19512
 
19021
19513
  Commands:
19022
19514
  help | --help
19023
19515
  version
19024
- doctor
19025
- auth login | logout | status
19026
- analyze | analyze run
19027
- analyze resume-missing [--worktree <path>]
19028
- analyze resume-synthesis [--worktree <path>]
19029
- pipeline macro [--synthesis-only] [--upload] [--skip-upload]
19516
+ doctor [--readiness] [--inspect-config]
19517
+ auth login --email <email> --password <password> [--session-path <path>]
19518
+ auth logout | status
19519
+ run synthesis
19520
+ run synthesis resume-missing [--worktree <path>]
19521
+ run synthesis resume-synthesis [--worktree <path>]
19522
+ analysis [--synthesis-only] [--upload] [--skip-upload]
19030
19523
  diagnose reference-coverage --root workspace|worktree [--worktree <path>]
19031
19524
  diagnose coordination-duplicates --root workspace|worktree [--worktree <path>]
19032
19525
  diagnose coverage-report --root workspace|worktree [--worktree <path>]
@@ -19034,7 +19527,9 @@ Commands:
19034
19527
  diagnose zero-reference [--worktree <path>]
19035
19528
  upload context | upload republish
19036
19529
  ace clear | ace learn | ace auto-learn [--worktree <path>]
19037
- config dump
19530
+ config init [--overwrite]
19531
+ config set-project-id <easyspecsProjectId>
19532
+ config set-git-remote <url>
19038
19533
  `);
19039
19534
  }
19040
19535
  function parseWorktreeFlag(rest) {
@@ -19069,18 +19564,23 @@ function resolveAdHocCheckoutRoot(_repoRoot, storage, worktreeFlag) {
19069
19564
  if (p && fs43.existsSync(path43.join(p, ".git"))) {
19070
19565
  return p;
19071
19566
  }
19072
- throw new Error("No analysis checkout: run `easyspecs-cli analyze` first or pass `--worktree <path>`.");
19567
+ throw new Error("No analysis checkout: run `easyspecs-cli run synthesis` first or pass `--worktree <path>`.");
19073
19568
  }
19074
19569
  function requireOpenCode(merged, flags) {
19075
19570
  const readiness = getOpenCodeReadiness({
19076
19571
  executable: merged.openCodeExecutable,
19077
- skipCredentialsCheck: merged.openCodeSkipCredentialsCheck
19572
+ skipCredentialsCheck: merged.openCodeSkipCredentialsCheck,
19573
+ providerEnvFromConfig: merged.openCodeChildEnv
19078
19574
  });
19079
19575
  if (!readiness.installed) {
19080
- throw new Error("OpenCode not on PATH (set EASYSPECS_OPENCODE_EXECUTABLE or install opencode).");
19576
+ throw new Error(
19577
+ "OpenCode not on PATH (set `easyspecs.openCode.executable` or `easyspecs.openCodeRuntime.executable` in `.easyspecs/config.json`, or install `opencode`)."
19578
+ );
19081
19579
  }
19082
19580
  if (!readiness.credentialsReady) {
19083
- throw new Error("OpenCode credentials not ready.");
19581
+ throw new Error(
19582
+ "OpenCode credentials not ready: configure provider `apiKey` values under `easyspecs.openCodeRuntime.providers` in `.easyspecs/config.json`, or sign in with OpenCode using your usual shell."
19583
+ );
19084
19584
  }
19085
19585
  return readiness;
19086
19586
  }
@@ -19142,11 +19642,136 @@ async function main() {
19142
19642
  const repoRoot = resolveRepoRoot(flags);
19143
19643
  setAceCliHeadlessMode(true);
19144
19644
  setAceCliWorkspaceRoot(repoRoot);
19145
- const apiResolved = initApiBaseUrlForCli(repoRoot, flags);
19146
- const merged = mergeCliSettings(repoRoot, {
19645
+ if (positionals[0] === "config" && positionals[1] === "init") {
19646
+ const tail = positionals.slice(2);
19647
+ const overwrite = tail.includes("--overwrite");
19648
+ const unknown = tail.filter((t) => t !== "--overwrite");
19649
+ if (unknown.length > 0) {
19650
+ logErr(flags, `Unknown arguments for config init: ${unknown.join(", ")}`);
19651
+ process.exit(ExitCode.usage);
19652
+ }
19653
+ try {
19654
+ const r = initEasyspecsConfigFile(repoRoot, {
19655
+ overwrite,
19656
+ warnMigration: (m) => logErr(flags, `[EasySpecs] ${m}`)
19657
+ });
19658
+ if (flags.json) {
19659
+ printJsonLine({
19660
+ command: "config init",
19661
+ durationMs: Date.now() - t0,
19662
+ ok: true,
19663
+ outcome: r.outcome,
19664
+ path: r.path
19665
+ });
19666
+ } else {
19667
+ const msg = r.outcome === "created" ? `Wrote ${r.path}` : r.outcome === "overwritten" ? `Overwrote ${r.path} with defaults (merged legacy cli.json / settings.json when present).` : `Already exists \u2014 ${r.path} (use --overwrite to replace with defaults).`;
19668
+ console.log(msg);
19669
+ }
19670
+ process.exit(ExitCode.ok);
19671
+ } catch (e) {
19672
+ if (e instanceof EasyspecsConfigInvalidJsonError) {
19673
+ console.error(e.message);
19674
+ process.exit(ExitCode.misconfiguration);
19675
+ }
19676
+ throw e;
19677
+ }
19678
+ }
19679
+ if (positionals[0] === "config" && positionals[1] === "set-project-id") {
19680
+ const rest = positionals.slice(2);
19681
+ const id = rest[0]?.trim();
19682
+ const extra = rest.slice(1).filter((t) => t.length > 0);
19683
+ if (!id || extra.length > 0) {
19684
+ logErr(flags, "Usage: easyspecs-cli config set-project-id <easyspecsProjectId>");
19685
+ process.exit(ExitCode.usage);
19686
+ }
19687
+ try {
19688
+ const cfg = updateEasyspecsConfig(
19689
+ repoRoot,
19690
+ { easyspecs: { easyspecsProjectId: id } },
19691
+ { warnMigration: (m) => logErr(flags, `[EasySpecs] ${m}`) }
19692
+ );
19693
+ const path44 = easyspecsConfigPath(repoRoot);
19694
+ if (flags.json) {
19695
+ printJsonLine({
19696
+ command: "config set-project-id",
19697
+ durationMs: Date.now() - t0,
19698
+ ok: true,
19699
+ path: path44,
19700
+ easyspecsProjectId: cfg.easyspecs?.easyspecsProjectId ?? ""
19701
+ });
19702
+ } else {
19703
+ console.log(`Updated ${path44} \u2014 easyspecs.easyspecsProjectId`);
19704
+ }
19705
+ process.exit(ExitCode.ok);
19706
+ } catch (e) {
19707
+ if (e instanceof EasyspecsConfigInvalidJsonError) {
19708
+ console.error(e.message);
19709
+ process.exit(ExitCode.misconfiguration);
19710
+ }
19711
+ throw e;
19712
+ }
19713
+ }
19714
+ if (positionals[0] === "config" && positionals[1] === "set-git-remote") {
19715
+ const rest = positionals.slice(2);
19716
+ const url = rest[0]?.trim();
19717
+ const extra = rest.slice(1).filter((t) => t.length > 0);
19718
+ if (!url || extra.length > 0) {
19719
+ logErr(flags, "Usage: easyspecs-cli config set-git-remote <url>");
19720
+ process.exit(ExitCode.usage);
19721
+ }
19722
+ try {
19723
+ const cfg = updateEasyspecsConfig(
19724
+ repoRoot,
19725
+ { easyspecs: { defaultGitRemoteUrl: url } },
19726
+ { warnMigration: (m) => logErr(flags, `[EasySpecs] ${m}`) }
19727
+ );
19728
+ const path44 = easyspecsConfigPath(repoRoot);
19729
+ if (flags.json) {
19730
+ printJsonLine({
19731
+ command: "config set-git-remote",
19732
+ durationMs: Date.now() - t0,
19733
+ ok: true,
19734
+ path: path44,
19735
+ defaultGitRemoteUrl: cfg.easyspecs?.defaultGitRemoteUrl ?? ""
19736
+ });
19737
+ } else {
19738
+ console.log(`Updated ${path44} \u2014 easyspecs.defaultGitRemoteUrl`);
19739
+ }
19740
+ process.exit(ExitCode.ok);
19741
+ } catch (e) {
19742
+ if (e instanceof EasyspecsConfigInvalidJsonError) {
19743
+ console.error(e.message);
19744
+ process.exit(ExitCode.misconfiguration);
19745
+ }
19746
+ throw e;
19747
+ }
19748
+ }
19749
+ const allowCreateConfig = positionals[0] !== "doctor";
19750
+ let repoConfig;
19751
+ try {
19752
+ repoConfig = ensureEasyspecsConfig(repoRoot, {
19753
+ allowCreate: allowCreateConfig,
19754
+ warnMigration: (m) => logErr(flags, `[EasySpecs] ${m}`)
19755
+ });
19756
+ } catch (e) {
19757
+ if (e instanceof EasyspecsConfigInvalidJsonError) {
19758
+ console.error(e.message);
19759
+ process.exit(ExitCode.misconfiguration);
19760
+ }
19761
+ throw e;
19762
+ }
19763
+ applyCliSessionPathFromRepoConfig(repoRoot, repoConfig);
19764
+ if (flags.sessionPath?.trim()) {
19765
+ const sp = flags.sessionPath.trim();
19766
+ const abs = path43.isAbsolute(sp) ? path43.normalize(sp) : path43.resolve(repoRoot, sp);
19767
+ setCliSessionPathFromConfig(abs);
19768
+ }
19769
+ const apiResolved = initApiBaseUrlForCli(repoRoot, flags, repoConfig);
19770
+ const merged = mergeEasyspecsCliSettings(repoConfig, {
19147
19771
  ci: flags.ci,
19148
19772
  apiBaseUrl: flags.apiBaseUrl,
19149
- promote: flags.promote
19773
+ promote: flags.promote,
19774
+ repoRoot
19150
19775
  });
19151
19776
  const finish = (code, envelope) => {
19152
19777
  if (flags.json) {
@@ -19162,11 +19787,20 @@ async function main() {
19162
19787
  const pos = positionals;
19163
19788
  try {
19164
19789
  if (pos[0] === "doctor") {
19790
+ let modes;
19791
+ try {
19792
+ modes = parseDoctorFlags(pos.slice(1));
19793
+ } catch (e) {
19794
+ logErr(flags, e instanceof Error ? e.message : String(e));
19795
+ logErr(flags, "Usage: easyspecs-cli doctor [--readiness] [--inspect-config]");
19796
+ process.exit(ExitCode.usage);
19797
+ }
19165
19798
  const agentsDir = resolveOpenCodeAgentsDir();
19166
19799
  const agentsOk = fs43.existsSync(agentsDir);
19167
19800
  const oc = getOpenCodeReadiness({
19168
19801
  executable: merged.openCodeExecutable,
19169
- skipCredentialsCheck: merged.openCodeSkipCredentialsCheck
19802
+ skipCredentialsCheck: merged.openCodeSkipCredentialsCheck,
19803
+ providerEnvFromConfig: merged.openCodeChildEnv
19170
19804
  });
19171
19805
  const lines = [
19172
19806
  `repoRoot=${repoRoot}`,
@@ -19174,13 +19808,29 @@ async function main() {
19174
19808
  `opencode installed=${String(oc.installed)} credentialsReady=${String(oc.credentialsReady)}`,
19175
19809
  `agentsDir=${agentsDir} exists=${String(agentsOk)}`
19176
19810
  ];
19811
+ const inspectPayload = buildDoctorInspectPayload(merged, repoConfig);
19177
19812
  if (!flags.json) {
19178
- console.log(lines.join("\n"));
19813
+ const parts = [];
19814
+ if (modes.readiness) {
19815
+ parts.push(lines.join("\n"));
19816
+ }
19817
+ if (modes.inspectConfig) {
19818
+ parts.push(JSON.stringify(inspectPayload, null, 2));
19819
+ }
19820
+ console.log(parts.join(parts.length === 2 ? "\n\n" : ""));
19821
+ }
19822
+ const envelope = { ok: true };
19823
+ if (modes.readiness) {
19824
+ envelope.lines = lines;
19179
19825
  }
19180
- finish(ExitCode.ok, { ok: true, lines });
19826
+ if (modes.inspectConfig) {
19827
+ envelope.config = inspectPayload;
19828
+ }
19829
+ finish(ExitCode.ok, envelope);
19181
19830
  }
19182
19831
  if (pos[0] === "config" && pos[1] === "dump") {
19183
- const redacted = { ...merged, apiBaseUrl: merged.apiBaseUrl ? "(set)" : "" };
19832
+ logErr(flags, "[EasySpecs] `config dump` is deprecated; use `easyspecs-cli doctor --inspect-config`.");
19833
+ const redacted = buildDoctorInspectPayload(merged, repoConfig);
19184
19834
  if (!flags.json) {
19185
19835
  console.log(JSON.stringify(redacted, null, 2));
19186
19836
  }
@@ -19198,33 +19848,121 @@ async function main() {
19198
19848
  finish(ExitCode.ok, { ok: true, authenticated: Boolean(s) });
19199
19849
  }
19200
19850
  if (pos[0] === "auth" && pos[1] === "login") {
19201
- if (flags.ci) {
19202
- const email = process.env.EASYSPECS_EMAIL?.trim();
19203
- const password = process.env.EASYSPECS_PASSWORD?.trim();
19204
- if (!email || !password) {
19205
- finish(ExitCode.misconfiguration, {
19206
- ok: false,
19207
- error: "CI auth: set EASYSPECS_EMAIL and EASYSPECS_PASSWORD"
19208
- });
19209
- }
19210
- const base = apiResolved;
19211
- if (!base) {
19212
- finish(ExitCode.misconfiguration, { ok: false, error: "apiBaseUrl missing" });
19851
+ const tailFull = pos.slice(2);
19852
+ let sessionPathRaw;
19853
+ let tailForCreds;
19854
+ try {
19855
+ const extracted = extractAuthLoginSessionPathFromTail(tailFull);
19856
+ tailForCreds = extracted.rest;
19857
+ sessionPathRaw = extracted.sessionPath;
19858
+ } catch (e) {
19859
+ logErr(flags, e instanceof Error ? e.message : String(e));
19860
+ process.exit(ExitCode.usage);
19861
+ }
19862
+ const parsedCli = parseAuthLoginTail(tailForCreds);
19863
+ const envEmail = process.env.EASYSPECS_EMAIL?.trim();
19864
+ const envPassword = process.env.EASYSPECS_PASSWORD?.trim();
19865
+ const useLegacyCiEnv = !parsedCli.ok && (flags.ci || tailFull.includes("--ci")) && Boolean(envEmail) && Boolean(envPassword);
19866
+ let email;
19867
+ let password;
19868
+ if (parsedCli.ok) {
19869
+ email = parsedCli.creds.email;
19870
+ password = parsedCli.creds.password;
19871
+ } else if (useLegacyCiEnv) {
19872
+ email = envEmail;
19873
+ password = envPassword;
19874
+ } else {
19875
+ logErr(flags, parsedCli.error);
19876
+ logErr(
19877
+ flags,
19878
+ "Usage: easyspecs-cli auth login --email <email> --password <password> [--session-path <path>]"
19879
+ );
19880
+ logErr(
19881
+ flags,
19882
+ "Legacy: easyspecs-cli --ci auth login (requires EASYSPECS_EMAIL and EASYSPECS_PASSWORD)"
19883
+ );
19884
+ process.exit(ExitCode.usage);
19885
+ }
19886
+ if (sessionPathRaw !== void 0) {
19887
+ const stored = normalizeCliSessionPathForConfig(repoRoot, sessionPathRaw);
19888
+ const updatedCfg = updateEasyspecsConfig(
19889
+ repoRoot,
19890
+ { easyspecs: { cliSessionPath: stored } },
19891
+ { warnMigration: (m) => logErr(flags, `[EasySpecs] ${m}`) }
19892
+ );
19893
+ applyCliSessionPathFromRepoConfig(repoRoot, updatedCfg);
19894
+ }
19895
+ const base = apiResolved;
19896
+ if (!base) {
19897
+ if (!flags.json) {
19898
+ console.error("KO: apiBaseUrl missing (set easyspecs.apiBaseUrl or use --api-base-url / --environment)");
19213
19899
  }
19900
+ finish(ExitCode.misconfiguration, {
19901
+ ok: false,
19902
+ error: "apiBaseUrl missing",
19903
+ outcome: "KO"
19904
+ });
19905
+ }
19906
+ try {
19214
19907
  const tokens = await loginWithApi(base, fetch, email, password);
19215
19908
  writeCliSession({
19216
19909
  apiBaseUrl: base,
19217
19910
  accessToken: tokens.accessToken,
19218
19911
  refreshToken: tokens.refreshToken
19219
19912
  });
19220
- finish(ExitCode.ok, { ok: true, message: "Logged in." });
19913
+ if (!flags.json) {
19914
+ console.log("OK");
19915
+ }
19916
+ finish(ExitCode.ok, { ok: true, outcome: "OK", message: "Logged in." });
19917
+ } catch (e) {
19918
+ const err = toFetchErrorMessage(e);
19919
+ const detail = err.message || "Login failed.";
19920
+ if (!flags.json) {
19921
+ console.error(`KO: ${detail}`);
19922
+ }
19923
+ finish(ExitCode.auth, {
19924
+ ok: false,
19925
+ outcome: "KO",
19926
+ error: detail,
19927
+ status: err.status
19928
+ });
19221
19929
  }
19222
- finish(ExitCode.misconfiguration, {
19223
- ok: false,
19224
- error: "Interactive auth not implemented; use --ci with EASYSPECS_EMAIL / EASYSPECS_PASSWORD"
19225
- });
19226
19930
  }
19227
- if (pos[0] === "analyze" && (pos.length === 1 || pos[1] === "run")) {
19931
+ if (pos[0] === "run" && pos[1] === "synthesis" && pos[2] === "resume-missing") {
19932
+ const storage = createFileBackedWorkspaceState(repoRoot);
19933
+ const { worktree } = parseWorktreeFlag(pos.slice(3));
19934
+ const analysisRoot = resolveAdHocCheckoutRoot(repoRoot, storage, worktree);
19935
+ const poolRes = await runResumeRemediationPool(storage, repoRoot, analysisRoot, merged, flags);
19936
+ if (poolRes.cancelled) {
19937
+ finish(ExitCode.cancelled, { ok: false, error: "Remediation cancelled.", analysisWorktreePath: analysisRoot });
19938
+ }
19939
+ if (!poolRes.indexOk) {
19940
+ finish(ExitCode.validation, {
19941
+ ok: false,
19942
+ error: poolRes.indexError ?? "Index failed after remediation.",
19943
+ analysisWorktreePath: analysisRoot
19944
+ });
19945
+ }
19946
+ finish(ExitCode.ok, { ok: true, analysisWorktreePath: analysisRoot });
19947
+ }
19948
+ if (pos[0] === "run" && pos[1] === "synthesis" && pos[2] === "resume-synthesis") {
19949
+ const storage = createFileBackedWorkspaceState(repoRoot);
19950
+ const { worktree } = parseWorktreeFlag(pos.slice(3));
19951
+ const analysisRoot = resolveAdHocCheckoutRoot(repoRoot, storage, worktree);
19952
+ const poolRes = await runResumeRemediationPool(storage, repoRoot, analysisRoot, merged, flags);
19953
+ if (poolRes.cancelled) {
19954
+ finish(ExitCode.cancelled, { ok: false, error: "Remediation cancelled.", analysisWorktreePath: analysisRoot });
19955
+ }
19956
+ if (!poolRes.indexOk) {
19957
+ finish(ExitCode.validation, {
19958
+ ok: false,
19959
+ error: poolRes.indexError ?? "Index failed after remediation.",
19960
+ analysisWorktreePath: analysisRoot
19961
+ });
19962
+ }
19963
+ finish(ExitCode.ok, { ok: true, analysisWorktreePath: analysisRoot });
19964
+ }
19965
+ if (pos[0] === "run" && pos[1] === "synthesis" && pos.length === 2) {
19228
19966
  requireOpenCode(merged, flags);
19229
19967
  const agentsDir = resolveOpenCodeAgentsDir();
19230
19968
  assertAgentsDirExists(agentsDir);
@@ -19271,7 +20009,7 @@ async function main() {
19271
20009
  }
19272
20010
  finish(result.cancelled ? ExitCode.cancelled : ExitCode.validation, {
19273
20011
  ok: false,
19274
- error: result.error ?? "analyze failed",
20012
+ error: result.error ?? "run synthesis failed",
19275
20013
  cancelled: result.cancelled === true,
19276
20014
  analysisWorktreePath: result.retainedWorktree?.path,
19277
20015
  adHocRepositoryRoot: result.retainedWorktree?.repoRoot
@@ -19400,7 +20138,7 @@ async function main() {
19400
20138
  }
19401
20139
  finish(ExitCode.usage, { ok: false, error: `Unknown diagnose subcommand: ${sub ?? ""}` });
19402
20140
  }
19403
- if (pos[0] === "pipeline" && pos[1] === "macro") {
20141
+ if (pos[0] === "analysis") {
19404
20142
  requireOpenCode(merged, flags);
19405
20143
  const synthesisOnly = positionals.includes("--synthesis-only");
19406
20144
  const wantsUpload = positionals.includes("--upload");
@@ -19408,7 +20146,7 @@ async function main() {
19408
20146
  if (wantsUpload && !uploadSession) {
19409
20147
  finish(ExitCode.auth, {
19410
20148
  ok: false,
19411
- error: "pipeline macro --upload requires `easyspecs-cli auth login` (CI: EASYSPECS_EMAIL/PASSWORD)."
20149
+ error: "analysis --upload requires `easyspecs-cli auth login --email \u2026 --password \u2026` (or legacy `--ci` + EASYSPECS_EMAIL/PASSWORD)."
19412
20150
  });
19413
20151
  }
19414
20152
  const skipBackendSync = !wantsUpload;
@@ -19424,9 +20162,12 @@ async function main() {
19424
20162
  return { ok: false, message: "No worktree context to upload." };
19425
20163
  }
19426
20164
  const target = readUploadTargetFile(wtContextDir) ?? readUploadTargetFile(wsContextDir);
19427
- const appId = target?.application_id?.trim() || process.env.EASYSPECS_APPLICATION_ID?.trim();
20165
+ const appId = target?.application_id?.trim() || repoConfig.easyspecs?.easyspecsProjectId?.trim() || process.env.EASYSPECS_APPLICATION_ID?.trim();
19428
20166
  if (!appId) {
19429
- return { ok: false, message: "Missing application_id in easyspecs-upload-target.json" };
20167
+ return {
20168
+ ok: false,
20169
+ message: "Missing application_id in easyspecs-upload-target.json or easyspecs.easyspecsProjectId in .easyspecs/config.json"
20170
+ };
19430
20171
  }
19431
20172
  let access = s.accessToken;
19432
20173
  const refresh = s.refreshToken;
@@ -19486,7 +20227,10 @@ async function main() {
19486
20227
  if (pos[0] === "upload" && (pos[1] === "context" || pos[1] === "republish")) {
19487
20228
  const sessRaw = readCliSession();
19488
20229
  if (sessRaw === void 0) {
19489
- finish(ExitCode.auth, { ok: false, error: "auth login first (--ci with EASYSPECS_EMAIL/PASSWORD)" });
20230
+ finish(ExitCode.auth, {
20231
+ ok: false,
20232
+ error: "auth login first (`auth login --email \u2026 --password \u2026`, or legacy `--ci` + env)"
20233
+ });
19490
20234
  }
19491
20235
  const sess = sessRaw;
19492
20236
  let ctxDir = path43.join(repoRoot, ".gluecharm", "context");
@@ -19501,16 +20245,19 @@ async function main() {
19501
20245
  if (!wt) {
19502
20246
  finish(ExitCode.misconfiguration, {
19503
20247
  ok: false,
19504
- error: "upload republish requires an analysis worktree with .gluecharm/context (run analyze first), or set EASYSPECS_UPLOAD_CONTEXT_DIR."
20248
+ error: "upload republish requires an analysis worktree with .gluecharm/context (`easyspecs-cli run synthesis` first), or set EASYSPECS_UPLOAD_CONTEXT_DIR."
19505
20249
  });
19506
20250
  }
19507
20251
  ctxDir = wt;
19508
20252
  }
19509
20253
  }
19510
20254
  const target = readUploadTargetFile(ctxDir);
19511
- const appIdRaw = target?.application_id?.trim() || process.env.EASYSPECS_APPLICATION_ID?.trim();
20255
+ const appIdRaw = target?.application_id?.trim() || repoConfig.easyspecs?.easyspecsProjectId?.trim() || process.env.EASYSPECS_APPLICATION_ID?.trim();
19512
20256
  if (!appIdRaw) {
19513
- finish(ExitCode.misconfiguration, { ok: false, error: "Missing application_id in easyspecs-upload-target.json" });
20257
+ finish(ExitCode.misconfiguration, {
20258
+ ok: false,
20259
+ error: "Missing application_id in easyspecs-upload-target.json or easyspecs.easyspecsProjectId in .easyspecs/config.json"
20260
+ });
19514
20261
  }
19515
20262
  const applicationId = appIdRaw;
19516
20263
  let access = sess.accessToken;
@@ -19617,40 +20364,6 @@ async function main() {
19617
20364
  cancelled: pool.cancelled
19618
20365
  });
19619
20366
  }
19620
- if (pos[0] === "analyze" && pos[1] === "resume-missing") {
19621
- const storage = createFileBackedWorkspaceState(repoRoot);
19622
- const { worktree } = parseWorktreeFlag(pos.slice(2));
19623
- const analysisRoot = resolveAdHocCheckoutRoot(repoRoot, storage, worktree);
19624
- const poolRes = await runResumeRemediationPool(storage, repoRoot, analysisRoot, merged, flags);
19625
- if (poolRes.cancelled) {
19626
- finish(ExitCode.cancelled, { ok: false, error: "Remediation cancelled.", analysisWorktreePath: analysisRoot });
19627
- }
19628
- if (!poolRes.indexOk) {
19629
- finish(ExitCode.validation, {
19630
- ok: false,
19631
- error: poolRes.indexError ?? "Index failed after remediation.",
19632
- analysisWorktreePath: analysisRoot
19633
- });
19634
- }
19635
- finish(ExitCode.ok, { ok: true, analysisWorktreePath: analysisRoot });
19636
- }
19637
- if (pos[0] === "analyze" && pos[1] === "resume-synthesis") {
19638
- const storage = createFileBackedWorkspaceState(repoRoot);
19639
- const { worktree } = parseWorktreeFlag(pos.slice(2));
19640
- const analysisRoot = resolveAdHocCheckoutRoot(repoRoot, storage, worktree);
19641
- const poolRes = await runResumeRemediationPool(storage, repoRoot, analysisRoot, merged, flags);
19642
- if (poolRes.cancelled) {
19643
- finish(ExitCode.cancelled, { ok: false, error: "Remediation cancelled.", analysisWorktreePath: analysisRoot });
19644
- }
19645
- if (!poolRes.indexOk) {
19646
- finish(ExitCode.validation, {
19647
- ok: false,
19648
- error: poolRes.indexError ?? "Index failed after remediation.",
19649
- analysisWorktreePath: analysisRoot
19650
- });
19651
- }
19652
- finish(ExitCode.ok, { ok: true, analysisWorktreePath: analysisRoot });
19653
- }
19654
20367
  printHelp();
19655
20368
  finish(ExitCode.usage, { ok: false, error: `unknown command: ${cmd}` });
19656
20369
  } catch (e) {