@gluecharm-lab/easyspecs-cli 0.0.13 → 0.0.14

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
@@ -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 = resolve15.call(this, root, ref);
3003
+ let _sch = resolve17.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 resolve15(root, ref) {
3030
+ function resolve17(root, ref) {
3031
3031
  let sch;
3032
3032
  while (typeof (sch = this.refs[ref]) == "string")
3033
3033
  ref = sch;
@@ -3242,8 +3242,8 @@ var require_utils = __commonJS({
3242
3242
  }
3243
3243
  return ind;
3244
3244
  }
3245
- function removeDotSegments(path45) {
3246
- let input = path45;
3245
+ function removeDotSegments(path47) {
3246
+ let input = path47;
3247
3247
  const output = [];
3248
3248
  let nextSlash = -1;
3249
3249
  let len = 0;
@@ -3442,8 +3442,8 @@ var require_schemes = __commonJS({
3442
3442
  wsComponent.secure = void 0;
3443
3443
  }
3444
3444
  if (wsComponent.resourceName) {
3445
- const [path45, query] = wsComponent.resourceName.split("?");
3446
- wsComponent.path = path45 && path45 !== "/" ? path45 : void 0;
3445
+ const [path47, query] = wsComponent.resourceName.split("?");
3446
+ wsComponent.path = path47 && path47 !== "/" ? path47 : void 0;
3447
3447
  wsComponent.query = query;
3448
3448
  wsComponent.resourceName = void 0;
3449
3449
  }
@@ -3602,55 +3602,55 @@ var require_fast_uri = __commonJS({
3602
3602
  }
3603
3603
  return uri;
3604
3604
  }
3605
- function resolve15(baseURI, relativeURI, options) {
3605
+ function resolve17(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, relative12, options, skipNormalization) {
3611
+ function resolveComponent(base, relative13, options, skipNormalization) {
3612
3612
  const target = {};
3613
3613
  if (!skipNormalization) {
3614
3614
  base = parse(serialize(base, options), options);
3615
- relative12 = parse(serialize(relative12, options), options);
3615
+ relative13 = parse(serialize(relative13, options), options);
3616
3616
  }
3617
3617
  options = options || {};
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;
3618
+ if (!options.tolerant && relative13.scheme) {
3619
+ target.scheme = relative13.scheme;
3620
+ target.userinfo = relative13.userinfo;
3621
+ target.host = relative13.host;
3622
+ target.port = relative13.port;
3623
+ target.path = removeDotSegments(relative13.path || "");
3624
+ target.query = relative13.query;
3625
3625
  } else {
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;
3626
+ if (relative13.userinfo !== void 0 || relative13.host !== void 0 || relative13.port !== void 0) {
3627
+ target.userinfo = relative13.userinfo;
3628
+ target.host = relative13.host;
3629
+ target.port = relative13.port;
3630
+ target.path = removeDotSegments(relative13.path || "");
3631
+ target.query = relative13.query;
3632
3632
  } else {
3633
- if (!relative12.path) {
3633
+ if (!relative13.path) {
3634
3634
  target.path = base.path;
3635
- if (relative12.query !== void 0) {
3636
- target.query = relative12.query;
3635
+ if (relative13.query !== void 0) {
3636
+ target.query = relative13.query;
3637
3637
  } else {
3638
3638
  target.query = base.query;
3639
3639
  }
3640
3640
  } else {
3641
- if (relative12.path[0] === "/") {
3642
- target.path = removeDotSegments(relative12.path);
3641
+ if (relative13.path[0] === "/") {
3642
+ target.path = removeDotSegments(relative13.path);
3643
3643
  } else {
3644
3644
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
3645
- target.path = "/" + relative12.path;
3645
+ target.path = "/" + relative13.path;
3646
3646
  } else if (!base.path) {
3647
- target.path = relative12.path;
3647
+ target.path = relative13.path;
3648
3648
  } else {
3649
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative12.path;
3649
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative13.path;
3650
3650
  }
3651
3651
  target.path = removeDotSegments(target.path);
3652
3652
  }
3653
- target.query = relative12.query;
3653
+ target.query = relative13.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 = relative12.fragment;
3661
+ target.fragment = relative13.fragment;
3662
3662
  return target;
3663
3663
  }
3664
3664
  function equal(uriA, uriB, options) {
@@ -3829,7 +3829,7 @@ var require_fast_uri = __commonJS({
3829
3829
  var fastUri = {
3830
3830
  SCHEMES,
3831
3831
  normalize: normalize6,
3832
- resolve: resolve15,
3832
+ resolve: resolve17,
3833
3833
  resolveComponent,
3834
3834
  equal,
3835
3835
  serialize,
@@ -7629,12 +7629,12 @@ var require_dist = __commonJS({
7629
7629
  throw new Error(`Unknown format "${name}"`);
7630
7630
  return f;
7631
7631
  };
7632
- function addFormats2(ajv2, list, fs45, exportName) {
7632
+ function addFormats2(ajv2, list, fs47, exportName) {
7633
7633
  var _a;
7634
7634
  var _b;
7635
7635
  (_a = (_b = ajv2.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
7636
7636
  for (const f of list)
7637
- ajv2.addFormat(f, fs45[f]);
7637
+ ajv2.addFormat(f, fs47[f]);
7638
7638
  }
7639
7639
  module2.exports = exports2 = formatsPlugin;
7640
7640
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -7643,8 +7643,8 @@ var require_dist = __commonJS({
7643
7643
  });
7644
7644
 
7645
7645
  // src/cli/main.ts
7646
- var fs44 = __toESM(require("node:fs"));
7647
- var path44 = __toESM(require("node:path"));
7646
+ var fs46 = __toESM(require("node:fs"));
7647
+ var path46 = __toESM(require("node:path"));
7648
7648
 
7649
7649
  // src/cli/argv.ts
7650
7650
  function isGlobalFlag(s) {
@@ -8597,6 +8597,22 @@ function ensureEasyspecsConfig(repoRoot, opts) {
8597
8597
  atomicWriteFile(configPath, body);
8598
8598
  return readEasyspecsConfig(repoRoot);
8599
8599
  }
8600
+ function syncDefaultGitRemoteUrlIfEmpty(repoRoot, cfg, opts) {
8601
+ const current = (cfg.easyspecs?.defaultGitRemoteUrl ?? "").trim();
8602
+ if (current.length > 0) {
8603
+ return cfg;
8604
+ }
8605
+ const configPath = easyspecsConfigPath(repoRoot);
8606
+ if (!fs6.existsSync(configPath)) {
8607
+ return cfg;
8608
+ }
8609
+ const gitRoot = findGitRepoRoot(repoRoot) ?? repoRoot;
8610
+ const url = runGit(gitRoot, ["remote", "get-url", "origin"])?.trim();
8611
+ if (!url) {
8612
+ return cfg;
8613
+ }
8614
+ return updateEasyspecsConfig(repoRoot, { easyspecs: { defaultGitRemoteUrl: url } }, opts);
8615
+ }
8600
8616
  function updateEasyspecsConfig(repoRoot, patch, opts) {
8601
8617
  const configPath = easyspecsConfigPath(repoRoot);
8602
8618
  const base = fs6.existsSync(configPath) ? readEasyspecsConfig(repoRoot) : buildFreshEasyspecsConfigFromLegacy(repoRoot, opts?.warnMigration);
@@ -8964,6 +8980,7 @@ function loadSrsDiscoveryIdMapFromContextDir(contextDir2, log) {
8964
8980
  }
8965
8981
 
8966
8982
  // src/analysis/contextSrsDiscoveryUpload.ts
8983
+ var UPLOAD_TARGET_FILENAME = "easyspecs-upload-target.json";
8967
8984
  var ADAPTIVE_B_INITIAL = 100;
8968
8985
  var ADAPTIVE_B_MIN = 1;
8969
8986
  var ADAPTIVE_B_MAX = 500;
@@ -9197,7 +9214,7 @@ function stringifyForSrs13Debug(value) {
9197
9214
  return truncateForSrs13DebugLog(String(value));
9198
9215
  }
9199
9216
  }
9200
- function logSrs13HttpDebug(log, path45, method, requestBody, error) {
9217
+ function logSrs13HttpDebug(log, path47, method, requestBody, error) {
9201
9218
  if (!log) {
9202
9219
  return;
9203
9220
  }
@@ -9207,7 +9224,7 @@ function logSrs13HttpDebug(log, path45, method, requestBody, error) {
9207
9224
  } catch {
9208
9225
  bodyBytes = 0;
9209
9226
  }
9210
- log(`[upload] debug ${method} ${path45} \u2014 request body ~${bodyBytes} bytes (JSON below):`);
9227
+ log(`[upload] debug ${method} ${path47} \u2014 request body ~${bodyBytes} bytes (JSON below):`);
9211
9228
  log(stringifyForSrs13Debug(requestBody));
9212
9229
  const raw = httpApiResponseBody(error);
9213
9230
  const status = error && typeof error === "object" && "status" in error ? Number(error.status) : void 0;
@@ -9311,18 +9328,18 @@ async function runParallelSinglesIntoAccum(requestJson, applicationId, paths, on
9311
9328
  const recentWaveMs = [];
9312
9329
  let idx = 0;
9313
9330
  const runOne = async (absPath) => {
9314
- const basename12 = path12.basename(absPath);
9331
+ const basename13 = path12.basename(absPath);
9315
9332
  let content;
9316
9333
  try {
9317
9334
  content = fs11.readFileSync(absPath, "utf8");
9318
9335
  } catch (e) {
9319
9336
  const msg = errorMessage(e);
9320
- logSrs13Failure(log, `read file file=${basename12}`, msg);
9337
+ logSrs13Failure(log, `read file file=${basename13}`, msg);
9321
9338
  accum.failed.push({ path: absPath, message: msg });
9322
9339
  return;
9323
9340
  }
9324
9341
  const existingId = resolveExistingId(absPath);
9325
- const payload = buildSrsDiscoverySavePayload(applicationId, basename12, content, existingId);
9342
+ const payload = buildSrsDiscoverySavePayload(applicationId, basename13, content, existingId);
9326
9343
  try {
9327
9344
  await postSingleCreate(requestJson, payload, log);
9328
9345
  accum.succeeded.push(absPath);
@@ -9336,7 +9353,7 @@ async function runParallelSinglesIntoAccum(requestJson, applicationId, paths, on
9336
9353
  throw e;
9337
9354
  }
9338
9355
  const msg = errorMessage(e);
9339
- logSrs13Failure(log, `POST /api/content/srs_discovery file=${basename12}`, msg);
9356
+ logSrs13Failure(log, `POST /api/content/srs_discovery file=${basename13}`, msg);
9340
9357
  accum.failed.push({ path: absPath, message: msg });
9341
9358
  }
9342
9359
  };
@@ -9418,18 +9435,18 @@ async function executeContextSrsDiscoveryUploadPhase(filePaths, phaseOpts, accum
9418
9435
  const items = [];
9419
9436
  const chunkPaths = [];
9420
9437
  for (const absPath of chunk) {
9421
- const basename12 = path12.basename(absPath);
9438
+ const basename13 = path12.basename(absPath);
9422
9439
  let content;
9423
9440
  try {
9424
9441
  content = fs11.readFileSync(absPath, "utf8");
9425
9442
  } catch (e) {
9426
9443
  const msg = errorMessage(e);
9427
- logSrs13Failure(log, `read file file=${basename12}`, msg);
9444
+ logSrs13Failure(log, `read file file=${basename13}`, msg);
9428
9445
  accum.failed.push({ path: absPath, message: msg });
9429
9446
  continue;
9430
9447
  }
9431
9448
  const existingId = resolveExistingId(absPath);
9432
- items.push(buildSrsDiscoverySavePayloadForBatch(applicationId, basename12, content, existingId));
9449
+ items.push(buildSrsDiscoverySavePayloadForBatch(applicationId, basename13, content, existingId));
9433
9450
  chunkPaths.push(absPath);
9434
9451
  }
9435
9452
  if (items.length === 0) {
@@ -9836,9 +9853,9 @@ var ANALYSIS_STATIC_CONTEXT_OUTPUTS = [
9836
9853
  "tech-stack-list.json"
9837
9854
  ];
9838
9855
  function discoverDynamicAnalysisTestSteps(contextDir2) {
9839
- const staticOutputs = ANALYSIS_STATIC_CONTEXT_OUTPUTS.map((basename12) => ({
9840
- basename: basename12,
9841
- exists: nonEmptyContextFile(path14.join(contextDir2, basename12))
9856
+ const staticOutputs = ANALYSIS_STATIC_CONTEXT_OUTPUTS.map((basename13) => ({
9857
+ basename: basename13,
9858
+ exists: nonEmptyContextFile(path14.join(contextDir2, basename13))
9842
9859
  }));
9843
9860
  const featuresPath = path14.join(contextDir2, "features-list.json");
9844
9861
  const featuresData = readJson(featuresPath);
@@ -10009,8 +10026,8 @@ function aceCurationPath(contextDir2, agentStem, runId) {
10009
10026
  function aceConsolidatedSessionsJsonlPath(contextDir2) {
10010
10027
  return path15.join(aceLearningsRoot(contextDir2), ACE_CONSOLIDATED_SESSIONS_JSONL);
10011
10028
  }
10012
- function opencodeAceSchemaPath(worktreeRoot, basename12) {
10013
- return path15.join(worktreeRoot, ".opencode", "schemas", "ace", basename12);
10029
+ function opencodeAceSchemaPath(worktreeRoot, basename13) {
10030
+ return path15.join(worktreeRoot, ".opencode", "schemas", "ace", basename13);
10014
10031
  }
10015
10032
 
10016
10033
  // src/analysis/aceJsonValidate.ts
@@ -10824,9 +10841,9 @@ function runOpenCodeAgent(cwd, args, options) {
10824
10841
  log?.(`[AgentCode] command: ${formatCliCommandForLog(cmd, args)}`);
10825
10842
  log?.(`[AgentCode] cwd: ${JSON.stringify(cwd)}`);
10826
10843
  log?.(`[AgentCode] argv: ${JSON.stringify(args)}`);
10827
- return new Promise((resolve15) => {
10844
+ return new Promise((resolve17) => {
10828
10845
  if (sig?.aborted) {
10829
- resolve15({ ok: false, message: "Stopped by user.", cancelled: true });
10846
+ resolve17({ ok: false, message: "Stopped by user.", cancelled: true });
10830
10847
  return;
10831
10848
  }
10832
10849
  const spawnEnv = options?.childEnv && Object.keys(options.childEnv).length > 0 ? { ...process.env, ...options.childEnv } : process.env;
@@ -10896,7 +10913,7 @@ ${truncateForDiag(outBody, DIAG_STDOUT_MAX)}`);
10896
10913
  if (diag) {
10897
10914
  finishDiag(diag.label, diag.code, diag.dumpStreams);
10898
10915
  }
10899
- resolve15(result);
10916
+ resolve17(result);
10900
10917
  };
10901
10918
  let onAbort;
10902
10919
  const clearAbortHandler = () => {
@@ -12958,16 +12975,16 @@ function outputPaths(step, worktreeRoot, listTarget) {
12958
12975
  if (!fe) {
12959
12976
  throw new Error("listUseCases requires listTarget.featureCode");
12960
12977
  }
12961
- const basename12 = `${fe}-use-cases-list.json`;
12962
- return { absolute: path24.join(ctx, basename12), basename: basename12 };
12978
+ const basename13 = `${fe}-use-cases-list.json`;
12979
+ return { absolute: path24.join(ctx, basename13), basename: basename13 };
12963
12980
  }
12964
12981
  case "reviewUseCasesList": {
12965
12982
  const fe = listTarget?.featureCode;
12966
12983
  if (!fe) {
12967
12984
  throw new Error("reviewUseCasesList requires listTarget.featureCode");
12968
12985
  }
12969
- const basename12 = `${fe}-use-cases-list.json`;
12970
- return { absolute: path24.join(ctx, basename12), basename: basename12 };
12986
+ const basename13 = `${fe}-use-cases-list.json`;
12987
+ return { absolute: path24.join(ctx, basename13), basename: basename13 };
12971
12988
  }
12972
12989
  case "listScenarios": {
12973
12990
  const fe = listTarget?.featureCode;
@@ -12975,8 +12992,8 @@ function outputPaths(step, worktreeRoot, listTarget) {
12975
12992
  if (!fe || !uc) {
12976
12993
  throw new Error("listScenarios requires listTarget.featureCode and useCaseCode");
12977
12994
  }
12978
- const basename12 = `${fe}_${uc}-scenarios-list.json`;
12979
- return { absolute: path24.join(ctx, basename12), basename: basename12 };
12995
+ const basename13 = `${fe}_${uc}-scenarios-list.json`;
12996
+ return { absolute: path24.join(ctx, basename13), basename: basename13 };
12980
12997
  }
12981
12998
  case "reviewScenariosList": {
12982
12999
  const fe = listTarget?.featureCode;
@@ -12984,24 +13001,24 @@ function outputPaths(step, worktreeRoot, listTarget) {
12984
13001
  if (!fe || !uc) {
12985
13002
  throw new Error("reviewScenariosList requires listTarget.featureCode and useCaseCode");
12986
13003
  }
12987
- const basename12 = `${fe}_${uc}-scenarios-list.json`;
12988
- return { absolute: path24.join(ctx, basename12), basename: basename12 };
13004
+ const basename13 = `${fe}_${uc}-scenarios-list.json`;
13005
+ return { absolute: path24.join(ctx, basename13), basename: basename13 };
12989
13006
  }
12990
13007
  case "listEntityFields": {
12991
13008
  const dm = listTarget?.entityCode;
12992
13009
  if (!dm) {
12993
13010
  throw new Error("listEntityFields requires listTarget.entityCode");
12994
13011
  }
12995
- const basename12 = `${dm}-fields-list.json`;
12996
- return { absolute: path24.join(ctx, basename12), basename: basename12 };
13012
+ const basename13 = `${dm}-fields-list.json`;
13013
+ return { absolute: path24.join(ctx, basename13), basename: basename13 };
12997
13014
  }
12998
13015
  case "reviewEntityFieldsList": {
12999
13016
  const dm = listTarget?.entityCode;
13000
13017
  if (!dm) {
13001
13018
  throw new Error("reviewEntityFieldsList requires listTarget.entityCode");
13002
13019
  }
13003
- const basename12 = `${dm}-fields-list.json`;
13004
- return { absolute: path24.join(ctx, basename12), basename: basename12 };
13020
+ const basename13 = `${dm}-fields-list.json`;
13021
+ return { absolute: path24.join(ctx, basename13), basename: basename13 };
13005
13022
  }
13006
13023
  default: {
13007
13024
  const _u = step;
@@ -15136,91 +15153,91 @@ function mdItem(id, parentIds, t) {
15136
15153
  };
15137
15154
  }
15138
15155
  function featureDetailTarget(contextDir2, code, name, slug) {
15139
- const basename12 = `${code}-${slug}.md`;
15156
+ const basename13 = `${code}-${slug}.md`;
15140
15157
  return {
15141
15158
  openCodeAgentStem: "agent-md-feature-detail",
15142
15159
  agentId: "ctx-md-feature-detail",
15143
15160
  displayName: "Feature detail",
15144
- outputBasename: basename12,
15161
+ outputBasename: basename13,
15145
15162
  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).`,
15146
- exists: fs31.existsSync(path29.join(contextDir2, basename12)) && fs31.statSync(path29.join(contextDir2, basename12)).size > 0
15163
+ exists: fs31.existsSync(path29.join(contextDir2, basename13)) && fs31.statSync(path29.join(contextDir2, basename13)).size > 0
15147
15164
  };
15148
15165
  }
15149
15166
  function viewDetailTarget(row2, contextDir2) {
15150
- const basename12 = `${row2.code}-${row2.slug}.md`;
15167
+ const basename13 = `${row2.code}-${row2.slug}.md`;
15151
15168
  return {
15152
15169
  openCodeAgentStem: "agent-md-view-detail",
15153
15170
  agentId: "ctx-md-view-detail",
15154
15171
  displayName: "View detail",
15155
- outputBasename: basename12,
15172
+ outputBasename: basename13,
15156
15173
  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.`,
15157
- exists: fs31.existsSync(path29.join(contextDir2, basename12)) && fs31.statSync(path29.join(contextDir2, basename12)).size > 0
15174
+ exists: fs31.existsSync(path29.join(contextDir2, basename13)) && fs31.statSync(path29.join(contextDir2, basename13)).size > 0
15158
15175
  };
15159
15176
  }
15160
15177
  function interactionDetailTarget(viewCode, ix, contextDir2) {
15161
- const basename12 = `${viewCode}_${ix.code}-${ix.slug}.md`;
15178
+ const basename13 = `${viewCode}_${ix.code}-${ix.slug}.md`;
15162
15179
  return {
15163
15180
  openCodeAgentStem: "agent-md-interaction-detail",
15164
15181
  agentId: "ctx-md-interaction-detail",
15165
15182
  displayName: "Interaction detail",
15166
- outputBasename: basename12,
15183
+ outputBasename: basename13,
15167
15184
  taskDescription: `Document interaction **${ix.code}** (**${ix.name}**) on view **${viewCode}** per \`.gluecharm/context/experiences-list.json\`.`,
15168
- exists: fs31.existsSync(path29.join(contextDir2, basename12)) && fs31.statSync(path29.join(contextDir2, basename12)).size > 0
15185
+ exists: fs31.existsSync(path29.join(contextDir2, basename13)) && fs31.statSync(path29.join(contextDir2, basename13)).size > 0
15169
15186
  };
15170
15187
  }
15171
15188
  function serviceDetailTarget(row2, contextDir2) {
15172
- const basename12 = `${row2.code}-${row2.slug}.md`;
15189
+ const basename13 = `${row2.code}-${row2.slug}.md`;
15173
15190
  return {
15174
15191
  openCodeAgentStem: "agent-md-service-detail",
15175
15192
  agentId: "ctx-md-service-detail",
15176
15193
  displayName: "Service detail",
15177
- outputBasename: basename12,
15194
+ outputBasename: basename13,
15178
15195
  taskDescription: `Describe service **${row2.code}** (**${row2.name}**, slug **${row2.slug}**) per \`.gluecharm/context/services-list.json\`: responsibilities, consumers, errors, integration points.`,
15179
- exists: fs31.existsSync(path29.join(contextDir2, basename12)) && fs31.statSync(path29.join(contextDir2, basename12)).size > 0
15196
+ exists: fs31.existsSync(path29.join(contextDir2, basename13)) && fs31.statSync(path29.join(contextDir2, basename13)).size > 0
15180
15197
  };
15181
15198
  }
15182
15199
  function methodDetailTarget(svc, m, contextDir2) {
15183
- const basename12 = `${svc.code}_${m.code}-${m.slug}.md`;
15200
+ const basename13 = `${svc.code}_${m.code}-${m.slug}.md`;
15184
15201
  return {
15185
15202
  openCodeAgentStem: "agent-md-method-detail",
15186
15203
  agentId: "ctx-md-method-detail",
15187
15204
  displayName: "Method detail",
15188
- outputBasename: basename12,
15205
+ outputBasename: basename13,
15189
15206
  taskDescription: `Document method **${m.code}** (**${m.name}**) on service **${svc.code}** per \`.gluecharm/context/services-list.json\`.`,
15190
- exists: fs31.existsSync(path29.join(contextDir2, basename12)) && fs31.statSync(path29.join(contextDir2, basename12)).size > 0
15207
+ exists: fs31.existsSync(path29.join(contextDir2, basename13)) && fs31.statSync(path29.join(contextDir2, basename13)).size > 0
15191
15208
  };
15192
15209
  }
15193
15210
  function entityDetailTarget(dmCode, ename, slug, contextDir2) {
15194
- const basename12 = `${dmCode}-${slug}.md`;
15211
+ const basename13 = `${dmCode}-${slug}.md`;
15195
15212
  return {
15196
15213
  openCodeAgentStem: "agent-md-entity-detail",
15197
15214
  agentId: "ctx-md-entity-detail",
15198
15215
  displayName: "Entity detail",
15199
- outputBasename: basename12,
15216
+ outputBasename: basename13,
15200
15217
  taskDescription: `Describe entity **${dmCode}** (**${ename}**, slug **${slug}**) per \`.gluecharm/context/data-model-list.json\`: lifecycle, invariants, storage, ORM/schema mapping.`,
15201
- exists: fs31.existsSync(path29.join(contextDir2, basename12)) && fs31.statSync(path29.join(contextDir2, basename12)).size > 0
15218
+ exists: fs31.existsSync(path29.join(contextDir2, basename13)) && fs31.statSync(path29.join(contextDir2, basename13)).size > 0
15202
15219
  };
15203
15220
  }
15204
15221
  function fieldDetailTarget(dmCode, fdCode, fname, fSlug, contextDir2) {
15205
- const basename12 = `${dmCode}_${fdCode}-${fSlug}.md`;
15222
+ const basename13 = `${dmCode}_${fdCode}-${fSlug}.md`;
15206
15223
  return {
15207
15224
  openCodeAgentStem: "agent-md-field-detail",
15208
15225
  agentId: "ctx-md-field-detail",
15209
15226
  displayName: "Field detail",
15210
- outputBasename: basename12,
15227
+ outputBasename: basename13,
15211
15228
  taskDescription: `Document field **${fdCode}** (**${fname}**) on entity **${dmCode}** per \`.gluecharm/context/${dmCode}-fields-list.json\`. Cite types and constraints with file and line.`,
15212
- exists: fs31.existsSync(path29.join(contextDir2, basename12)) && fs31.statSync(path29.join(contextDir2, basename12)).size > 0
15229
+ exists: fs31.existsSync(path29.join(contextDir2, basename13)) && fs31.statSync(path29.join(contextDir2, basename13)).size > 0
15213
15230
  };
15214
15231
  }
15215
15232
  function toolDetailTarget(row2, contextDir2) {
15216
- const basename12 = `${row2.code}-${row2.slug}.md`;
15233
+ const basename13 = `${row2.code}-${row2.slug}.md`;
15217
15234
  return {
15218
15235
  openCodeAgentStem: "agent-md-tool-detail",
15219
15236
  agentId: "ctx-md-tool-detail",
15220
15237
  displayName: "Tool detail",
15221
- outputBasename: basename12,
15238
+ outputBasename: basename13,
15222
15239
  taskDescription: `Describe tool **${row2.code}** (**${row2.name}**, slug **${row2.slug}**) per \`.gluecharm/context/tech-stack-list.json\`: role, version hints, configuration, boundaries.`,
15223
- exists: fs31.existsSync(path29.join(contextDir2, basename12)) && fs31.statSync(path29.join(contextDir2, basename12)).size > 0
15240
+ exists: fs31.existsSync(path29.join(contextDir2, basename13)) && fs31.statSync(path29.join(contextDir2, basename13)).size > 0
15224
15241
  };
15225
15242
  }
15226
15243
  function useCaseDetailTarget(feCode, ucCode, ucName, ucSlug, contextDir2) {
@@ -15237,14 +15254,14 @@ Follow bundled agent **agent-md-use-case-detail**: include **## Data inputs and
15237
15254
  };
15238
15255
  }
15239
15256
  function scenarioDetailTarget(feCode, ucCode, scCode, scName, contextDir2) {
15240
- const basename12 = `${feCode}_${ucCode}_${scCode}.md`;
15257
+ const basename13 = `${feCode}_${ucCode}_${scCode}.md`;
15241
15258
  return {
15242
15259
  openCodeAgentStem: "agent-md-scenario-detail",
15243
15260
  agentId: "ctx-md-scenario-detail",
15244
15261
  displayName: "Scenario detail",
15245
- outputBasename: basename12,
15262
+ outputBasename: basename13,
15246
15263
  taskDescription: `Document scenario **${scCode}** (**${scName}**) for **${feCode}** / **${ucCode}** per \`.gluecharm/context/${feCode}_${ucCode}-scenarios-list.json\`. Cite steps with file and line where possible.`,
15247
- exists: fs31.existsSync(path29.join(contextDir2, basename12)) && fs31.statSync(path29.join(contextDir2, basename12)).size > 0
15264
+ exists: fs31.existsSync(path29.join(contextDir2, basename13)) && fs31.statSync(path29.join(contextDir2, basename13)).size > 0
15248
15265
  };
15249
15266
  }
15250
15267
  function parentsDone(item, byId) {
@@ -15470,15 +15487,15 @@ function expandAfterSuccess(completed, contextDir2, byId, fifo) {
15470
15487
  return;
15471
15488
  }
15472
15489
  if (completed.kind === "markdown") {
15473
- const basename12 = completed.payload.outputBasename;
15490
+ const basename13 = completed.payload.outputBasename;
15474
15491
  const mdId = completed.id;
15475
- const feFromFeatureMd = basename12.match(/^(FE-\d+)-[^/]+\.md$/);
15492
+ const feFromFeatureMd = basename13.match(/^(FE-\d+)-[^/]+\.md$/);
15476
15493
  if (feFromFeatureMd) {
15477
15494
  const fe = feFromFeatureMd[1];
15478
15495
  add(coordItem(`coord:uc:${fe}`, [mdId], "listUseCases", { featureCode: fe }));
15479
15496
  return;
15480
15497
  }
15481
- const xpFromView = basename12.match(/^(XP-\d+)-[^/]+\.md$/);
15498
+ const xpFromView = basename13.match(/^(XP-\d+)-[^/]+\.md$/);
15482
15499
  if (xpFromView) {
15483
15500
  const xp = xpFromView[1];
15484
15501
  const row2 = discoverExperienceTreeRows(contextDir2).find((r) => r.code === xp);
@@ -15490,7 +15507,7 @@ function expandAfterSuccess(completed, contextDir2, byId, fifo) {
15490
15507
  }
15491
15508
  return;
15492
15509
  }
15493
- const svFromSvc = basename12.match(/^(SV-\d+)-[^/]+\.md$/);
15510
+ const svFromSvc = basename13.match(/^(SV-\d+)-[^/]+\.md$/);
15494
15511
  if (svFromSvc) {
15495
15512
  const sv = svFromSvc[1];
15496
15513
  const srow = discoverServiceTreeRows(contextDir2).find((r) => r.code === sv);
@@ -15502,7 +15519,7 @@ function expandAfterSuccess(completed, contextDir2, byId, fifo) {
15502
15519
  }
15503
15520
  return;
15504
15521
  }
15505
- const dmFromEnt = basename12.match(/^(DM-\d+)-[^/]+\.md$/);
15522
+ const dmFromEnt = basename13.match(/^(DM-\d+)-[^/]+\.md$/);
15506
15523
  if (dmFromEnt) {
15507
15524
  const dm = dmFromEnt[1];
15508
15525
  add(coordItem(`coord:ef:${dm}`, [mdId], "listEntityFields", { entityCode: dm }));
@@ -15668,8 +15685,8 @@ async function drainArtefactWorkPool(p) {
15668
15685
  const artefactRunId = readArtefactRunSnapshot(storageContext)?.runId ?? "unknown-run";
15669
15686
  let active = 0;
15670
15687
  let wake;
15671
- const waitTurn = () => new Promise((resolve15) => {
15672
- wake = resolve15;
15688
+ const waitTurn = () => new Promise((resolve17) => {
15689
+ wake = resolve17;
15673
15690
  });
15674
15691
  const persist = async () => {
15675
15692
  const items = {};
@@ -15997,7 +16014,7 @@ function macroSleep(ms, signal) {
15997
16014
  if (signal.aborted) {
15998
16015
  return Promise.reject(new DOMException("The operation was aborted.", "AbortError"));
15999
16016
  }
16000
- return new Promise((resolve15, reject) => {
16017
+ return new Promise((resolve17, reject) => {
16001
16018
  const onAbort = () => {
16002
16019
  clearTimeout(t);
16003
16020
  signal.removeEventListener("abort", onAbort);
@@ -16005,7 +16022,7 @@ function macroSleep(ms, signal) {
16005
16022
  };
16006
16023
  const t = setTimeout(() => {
16007
16024
  signal.removeEventListener("abort", onAbort);
16008
- resolve15();
16025
+ resolve17();
16009
16026
  }, ms);
16010
16027
  signal.addEventListener("abort", onAbort, { once: true });
16011
16028
  });
@@ -16373,8 +16390,8 @@ async function runMacroAnalysisOrchestration(deps) {
16373
16390
  }
16374
16391
 
16375
16392
  // src/analysis/macroHeadlessHost.ts
16376
- var fs40 = __toESM(require("node:fs"));
16377
- var path39 = __toESM(require("node:path"));
16393
+ var fs41 = __toESM(require("node:fs"));
16394
+ var path40 = __toESM(require("node:path"));
16378
16395
 
16379
16396
  // src/stores/analysisPipelineStore.ts
16380
16397
  var STORAGE_KEY2 = "easyspecs.analysis.pipelineRun.v1";
@@ -16540,8 +16557,8 @@ function expectedFeatureDetailBasenameFromRow(row2) {
16540
16557
  }
16541
16558
  return `${code}-${slug}.md`;
16542
16559
  }
16543
- function ctxPath(contextDir2, basename12) {
16544
- return path31.join(contextDir2, basename12);
16560
+ function ctxPath(contextDir2, basename13) {
16561
+ return path31.join(contextDir2, basename13);
16545
16562
  }
16546
16563
  function pushTarget(targets, stem, outputBasename, taskDescription, contextDir2) {
16547
16564
  const meta = STEM_TO_AGENT[stem];
@@ -16570,11 +16587,11 @@ function discoverDetailMarkdownGroups(contextDir2) {
16570
16587
  continue;
16571
16588
  }
16572
16589
  const name = safeStr3(row2.name) || code;
16573
- const basename12 = `${code}-${slug}.md`;
16590
+ const basename13 = `${code}-${slug}.md`;
16574
16591
  pushTarget(
16575
16592
  featureTargets,
16576
16593
  "agent-md-feature-detail",
16577
- basename12,
16594
+ basename13,
16578
16595
  `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).`,
16579
16596
  contextDir2
16580
16597
  );
@@ -17762,8 +17779,8 @@ function formatAjvErrors6(errors) {
17762
17779
  });
17763
17780
  }
17764
17781
  var ajv = new import__6.default({ allErrors: true, strict: false });
17765
- function compileSchema(basename12) {
17766
- const schemaPath = path36.join(schemasDir(), basename12);
17782
+ function compileSchema(basename13) {
17783
+ const schemaPath = path36.join(schemasDir(), basename13);
17767
17784
  const schemaRaw = stripUtf8Bom5(fs37.readFileSync(schemaPath, "utf-8"));
17768
17785
  const schema = JSON.parse(schemaRaw);
17769
17786
  return ajv.compile(schema);
@@ -19024,6 +19041,31 @@ async function runReferenceCoverageExecutionReport(p) {
19024
19041
  return { ok: true, outputAbsolutePath: outAbs };
19025
19042
  }
19026
19043
 
19044
+ // src/gluecharm/minimalGluecharmLayout.ts
19045
+ var fs40 = __toESM(require("node:fs"));
19046
+ var path39 = __toESM(require("node:path"));
19047
+ var MINIMAL_GLUECHARM_RELATIVE_DIRS = [
19048
+ [".gluecharm", "docs", "srs"],
19049
+ [".gluecharm", "content"],
19050
+ [".gluecharm", "logs"],
19051
+ [".gluecharm", "context"]
19052
+ ];
19053
+ function ensureMinimalGluecharmLayoutNode(repoRootAbs) {
19054
+ const root = path39.resolve(repoRootAbs);
19055
+ for (const segments of MINIMAL_GLUECHARM_RELATIVE_DIRS) {
19056
+ const dir = path39.join(root, ...segments);
19057
+ try {
19058
+ fs40.mkdirSync(dir, { recursive: true });
19059
+ } catch (e) {
19060
+ const err = e;
19061
+ const msg = e instanceof Error ? e.message : String(e);
19062
+ const code = err.code ? ` (${err.code})` : "";
19063
+ return { ok: false, error: `Could not create directory ${dir}${code}: ${msg}` };
19064
+ }
19065
+ }
19066
+ return { ok: true };
19067
+ }
19068
+
19027
19069
  // src/analysis/macroHeadlessHost.ts
19028
19070
  function buildMacroOrchestrationDepsHeadless(input) {
19029
19071
  const { storageContext, repoRoot, agentsDirFs, buildOpenCodeOptions, log, signal, macroConfig } = input;
@@ -19042,13 +19084,13 @@ function buildMacroOrchestrationDepsHeadless(input) {
19042
19084
  },
19043
19085
  runPrepareAnalysisWorktree: async (resume) => {
19044
19086
  if (resume) {
19045
- if (adHocWorktree && fs40.existsSync(path39.join(adHocWorktree.path, ".git"))) {
19087
+ if (adHocWorktree && fs41.existsSync(path40.join(adHocWorktree.path, ".git"))) {
19046
19088
  return { ok: true };
19047
19089
  }
19048
19090
  const snap = readAnalysisWorkspaceSnapshot(storageContext);
19049
19091
  const wtPath = snap?.adHocWorktreePath?.trim();
19050
19092
  const repo = snap?.adHocRepositoryRoot?.trim() || repoRoot;
19051
- if (wtPath && fs40.existsSync(path39.join(wtPath, ".git"))) {
19093
+ if (wtPath && fs41.existsSync(path40.join(wtPath, ".git"))) {
19052
19094
  adHocWorktree = attachWorktreeHandle(wtPath, repo);
19053
19095
  macroSourceBranch = snap?.adHocSourceBranchAtCreation;
19054
19096
  macroFinalize = () => {
@@ -19113,10 +19155,11 @@ function buildMacroOrchestrationDepsHeadless(input) {
19113
19155
  return { ok: false, error: "No analysis checkout." };
19114
19156
  }
19115
19157
  if (resume) {
19116
- const ctxDir = path39.join(ar, ".gluecharm", "context");
19117
- if (!fs40.existsSync(ctxDir)) {
19118
- return { ok: false, error: "Missing .gluecharm/context in the analysis worktree." };
19158
+ const layout = ensureMinimalGluecharmLayoutNode(ar);
19159
+ if (!layout.ok) {
19160
+ return { ok: false, error: layout.error };
19119
19161
  }
19162
+ const ctxDir = path40.join(ar, ".gluecharm", "context");
19120
19163
  const snap = readArtefactRunSnapshot(storageContext);
19121
19164
  const rows = listMissingArtefacts(ctxDir, ar, snap);
19122
19165
  if (rows.length === 0) {
@@ -19130,7 +19173,7 @@ function buildMacroOrchestrationDepsHeadless(input) {
19130
19173
  storageContext,
19131
19174
  repositoryRoot: repoRoot,
19132
19175
  worktreeRoot: ar,
19133
- workspaceLabel: path39.basename(ar),
19176
+ workspaceLabel: path40.basename(ar),
19134
19177
  oc: {
19135
19178
  ...oc,
19136
19179
  aceEnabled: getAceAnalysisEnabledForCheckout(ar),
@@ -19158,7 +19201,7 @@ function buildMacroOrchestrationDepsHeadless(input) {
19158
19201
  }
19159
19202
  try {
19160
19203
  await startPipelineRun(storageContext, repoRoot);
19161
- const folderName = path39.basename(repoRoot);
19204
+ const folderName = path40.basename(repoRoot);
19162
19205
  const oc = buildOpenCodeOptions(handle.path);
19163
19206
  const result = await runContextArtefactPipelineDrainFromPreparedWorktree(
19164
19207
  storageContext,
@@ -19215,7 +19258,7 @@ function buildMacroOrchestrationDepsHeadless(input) {
19215
19258
  if (!ar) {
19216
19259
  return 0;
19217
19260
  }
19218
- const ctxDir = path39.join(ar, ".gluecharm", "context");
19261
+ const ctxDir = path40.join(ar, ".gluecharm", "context");
19219
19262
  const snap = readArtefactRunSnapshot(storageContext);
19220
19263
  return listMissingArtefacts(ctxDir, ar, snap).length;
19221
19264
  },
@@ -19224,10 +19267,11 @@ function buildMacroOrchestrationDepsHeadless(input) {
19224
19267
  if (!ar) {
19225
19268
  return { ok: false, message: "No analysis worktree." };
19226
19269
  }
19227
- const contextDir2 = path39.join(ar, ".gluecharm", "context");
19228
- if (!fs40.existsSync(contextDir2)) {
19229
- return { ok: false, message: "Missing .gluecharm/context." };
19270
+ const layout = ensureMinimalGluecharmLayoutNode(ar);
19271
+ if (!layout.ok) {
19272
+ return { ok: false, message: layout.error };
19230
19273
  }
19274
+ const contextDir2 = path40.join(ar, ".gluecharm", "context");
19231
19275
  log(`[setup] macro \u2014 reference coverage (worktree) \u2014 ${ar}`);
19232
19276
  const res = runCoverageReferenceValidation({
19233
19277
  repositoryRootAbs: ar,
@@ -19276,6 +19320,10 @@ function buildMacroOrchestrationDepsHeadless(input) {
19276
19320
  if (!ar) {
19277
19321
  return { ok: false, message: "No analysis worktree." };
19278
19322
  }
19323
+ const execLayout = ensureMinimalGluecharmLayoutNode(ar);
19324
+ if (!execLayout.ok) {
19325
+ return { ok: false, message: execLayout.error };
19326
+ }
19279
19327
  log(`[setup] macro \u2014 reference coverage execution report \u2014 ${(/* @__PURE__ */ new Date()).toISOString()}`);
19280
19328
  const res = await runReferenceCoverageExecutionReport({
19281
19329
  repositoryRootAbs: ar,
@@ -19283,7 +19331,7 @@ function buildMacroOrchestrationDepsHeadless(input) {
19283
19331
  diagnosticLog: log
19284
19332
  });
19285
19333
  if (res.ok) {
19286
- return { ok: true, message: `Report: ${path39.basename(res.outputAbsolutePath)}` };
19334
+ return { ok: true, message: `Report: ${path40.basename(res.outputAbsolutePath)}` };
19287
19335
  }
19288
19336
  if (res.cancelled) {
19289
19337
  return { ok: false, cancelled: true, message: "Report generation stopped." };
@@ -19295,10 +19343,11 @@ function buildMacroOrchestrationDepsHeadless(input) {
19295
19343
  if (!snap?.adHocWorktreePath) {
19296
19344
  return { ok: false, message: "No analysis worktree for index assembly." };
19297
19345
  }
19298
- const contextDir2 = path39.join(snap.adHocWorktreePath, ".gluecharm", "context");
19299
- if (!fs40.existsSync(contextDir2)) {
19300
- return { ok: false, message: "Missing .gluecharm/context." };
19346
+ const idxLayout = ensureMinimalGluecharmLayoutNode(snap.adHocWorktreePath);
19347
+ if (!idxLayout.ok) {
19348
+ return { ok: false, message: idxLayout.error };
19301
19349
  }
19350
+ const contextDir2 = path40.join(snap.adHocWorktreePath, ".gluecharm", "context");
19302
19351
  try {
19303
19352
  writeIndexApplicationContext(contextDir2, void 0, {
19304
19353
  sourceBranchAtWorktreeCreation: snap.adHocSourceBranchAtCreation
@@ -19317,10 +19366,14 @@ function buildMacroOrchestrationDepsHeadless(input) {
19317
19366
  return input.runBackendSyncImpl();
19318
19367
  }
19319
19368
  const snap = readAnalysisWorkspaceSnapshot(storageContext);
19320
- const wtContextDir = snap?.adHocWorktreePath && fs40.existsSync(path39.join(snap.adHocWorktreePath, ".gluecharm", "context")) ? path39.join(snap.adHocWorktreePath, ".gluecharm", "context") : "";
19321
- if (!wtContextDir) {
19369
+ if (!snap?.adHocWorktreePath) {
19322
19370
  return { ok: false, message: "No worktree context to upload." };
19323
19371
  }
19372
+ const syncLayout = ensureMinimalGluecharmLayoutNode(snap.adHocWorktreePath);
19373
+ if (!syncLayout.ok) {
19374
+ return { ok: false, message: syncLayout.error };
19375
+ }
19376
+ const wtContextDir = path40.join(snap.adHocWorktreePath, ".gluecharm", "context");
19324
19377
  let applicationId;
19325
19378
  try {
19326
19379
  applicationId = getEasyspecsProjectIdFromRepoConfig(readEasyspecsConfig(repoRoot))?.trim();
@@ -19336,8 +19389,8 @@ function buildMacroOrchestrationDepsHeadless(input) {
19336
19389
  }
19337
19390
 
19338
19391
  // src/analysis/coordinationDuplicatesDiagnosis.ts
19339
- var fs41 = __toESM(require("fs"));
19340
- var path40 = __toESM(require("path"));
19392
+ var fs42 = __toESM(require("fs"));
19393
+ var path41 = __toESM(require("path"));
19341
19394
  var import__7 = __toESM(require__());
19342
19395
  var COORDINATION_DUPLICATES_REPORT_BASENAME = "coordination-duplicates-report.json";
19343
19396
  var COORDINATION_LIST_SCAN_ENTRIES = [
@@ -19362,30 +19415,30 @@ var RE_MD_SV = /^SV-\d+-.+\.md$/i;
19362
19415
  var RE_MD_DM_FD = /^DM-\d+_FD-\d+-.+\.md$/i;
19363
19416
  var RE_MD_DM = /^DM-\d+-.+\.md$/i;
19364
19417
  var RE_MD_TS = /^TS-\d+-.+\.md$/i;
19365
- function looksLikeCoordinationDetailMarkdownBasename(basename12) {
19366
- if (!basename12 || basename12 !== path40.basename(basename12) || !/\.md$/i.test(basename12)) {
19418
+ function looksLikeCoordinationDetailMarkdownBasename(basename13) {
19419
+ if (!basename13 || basename13 !== path41.basename(basename13) || !/\.md$/i.test(basename13)) {
19367
19420
  return false;
19368
19421
  }
19369
- if (STAPLE_CONTEXT_MARKDOWN_BASENAMES.has(basename12)) {
19422
+ if (STAPLE_CONTEXT_MARKDOWN_BASENAMES.has(basename13)) {
19370
19423
  return false;
19371
19424
  }
19372
- 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);
19425
+ return RE_MD_FE_UC_SC.test(basename13) || RE_MD_FE_UC_SLUG.test(basename13) || RE_MD_FE_UC_PLAIN.test(basename13) || RE_MD_FE_FEATURE.test(basename13) || RE_MD_XP_BH.test(basename13) || RE_MD_XP_VIEW.test(basename13) || RE_MD_SV_ME.test(basename13) || RE_MD_SV.test(basename13) || RE_MD_DM_FD.test(basename13) || RE_MD_DM.test(basename13) || RE_MD_TS.test(basename13);
19373
19426
  }
19374
19427
  function loadRawFeatureRows(contextDirAbs) {
19375
- const p = path40.join(contextDirAbs, "features-list.json");
19376
- if (!fs41.existsSync(p)) {
19428
+ const p = path41.join(contextDirAbs, "features-list.json");
19429
+ if (!fs42.existsSync(p)) {
19377
19430
  return [];
19378
19431
  }
19379
19432
  try {
19380
- const raw = stripUtf8Bom6(fs41.readFileSync(p, "utf-8"));
19433
+ const raw = stripUtf8Bom6(fs42.readFileSync(p, "utf-8"));
19381
19434
  const doc = JSON.parse(raw);
19382
19435
  return Array.isArray(doc.features) ? doc.features : [];
19383
19436
  } catch {
19384
19437
  return [];
19385
19438
  }
19386
19439
  }
19387
- function hintForOrphanFeatureMarkdown(basename12, featureRows) {
19388
- const m = /^FE-(\d+)-(.+)\.md$/i.exec(basename12);
19440
+ function hintForOrphanFeatureMarkdown(basename13, featureRows) {
19441
+ const m = /^FE-(\d+)-(.+)\.md$/i.exec(basename13);
19389
19442
  if (!m) {
19390
19443
  return void 0;
19391
19444
  }
@@ -19397,8 +19450,8 @@ function hintForOrphanFeatureMarkdown(basename12, featureRows) {
19397
19450
  continue;
19398
19451
  }
19399
19452
  const expected = expectedFeatureDetailBasenameFromRow(row2);
19400
- if (expected && expected !== basename12) {
19401
- return `features-list row ${code} currently implies detail file ${expected} (slug/name changed \u2014 ${basename12} may be stale).`;
19453
+ if (expected && expected !== basename13) {
19454
+ return `features-list row ${code} currently implies detail file ${expected} (slug/name changed \u2014 ${basename13} may be stale).`;
19402
19455
  }
19403
19456
  if (!expected) {
19404
19457
  return `features-list row ${code} has no resolvable slug for a detail markdown basename.`;
@@ -19412,7 +19465,7 @@ function findOrphanCoordinationMarkdown(contextDirAbs) {
19412
19465
  const featureRows = loadRawFeatureRows(contextDirAbs);
19413
19466
  let dirents;
19414
19467
  try {
19415
- dirents = fs41.readdirSync(contextDirAbs, { withFileTypes: true });
19468
+ dirents = fs42.readdirSync(contextDirAbs, { withFileTypes: true });
19416
19469
  } catch {
19417
19470
  return [];
19418
19471
  }
@@ -19665,14 +19718,14 @@ function buildCoordinationDuplicatesReport(input) {
19665
19718
  const lists = [];
19666
19719
  const duplicateGroups = [];
19667
19720
  for (const entry of COORDINATION_LIST_SCAN_ENTRIES) {
19668
- const filePath = path40.join(input.contextDirAbsolute, entry.basename);
19669
- if (!fs41.existsSync(filePath)) {
19721
+ const filePath = path41.join(input.contextDirAbsolute, entry.basename);
19722
+ if (!fs42.existsSync(filePath)) {
19670
19723
  lists.push({ basename: entry.basename, status: "missing" });
19671
19724
  continue;
19672
19725
  }
19673
19726
  let raw;
19674
19727
  try {
19675
- raw = stripUtf8Bom6(fs41.readFileSync(filePath, "utf-8"));
19728
+ raw = stripUtf8Bom6(fs42.readFileSync(filePath, "utf-8"));
19676
19729
  } catch (e) {
19677
19730
  lists.push({
19678
19731
  basename: entry.basename,
@@ -19720,8 +19773,8 @@ var validateReportCompiled;
19720
19773
  function validateReportData(data) {
19721
19774
  if (!validateReportCompiled) {
19722
19775
  const ajv2 = new import__7.default({ allErrors: true, strict: false });
19723
- const schemaPath = path40.join(resolveContextListSchemasDir(), "coordination-duplicates-report.schema.json");
19724
- const schemaRaw = stripUtf8Bom6(fs41.readFileSync(schemaPath, "utf-8"));
19776
+ const schemaPath = path41.join(resolveContextListSchemasDir(), "coordination-duplicates-report.schema.json");
19777
+ const schemaRaw = stripUtf8Bom6(fs42.readFileSync(schemaPath, "utf-8"));
19725
19778
  validateReportCompiled = ajv2.compile(JSON.parse(schemaRaw));
19726
19779
  }
19727
19780
  if (validateReportCompiled(data)) {
@@ -19739,20 +19792,20 @@ function runCoordinationDuplicatesDiagnosis(input) {
19739
19792
  if (!v.ok) {
19740
19793
  return { ok: false, message: `Report validation failed: ${v.errors.join("; ")}` };
19741
19794
  }
19742
- const outPath = path40.join(input.contextDirAbsolute, COORDINATION_DUPLICATES_REPORT_BASENAME);
19795
+ const outPath = path41.join(input.contextDirAbsolute, COORDINATION_DUPLICATES_REPORT_BASENAME);
19743
19796
  const payload = `${JSON.stringify(report, null, 2)}
19744
19797
  `;
19745
19798
  const tmp = `${outPath}.tmp.${process.pid}`;
19746
19799
  try {
19747
- fs41.writeFileSync(tmp, payload, "utf-8");
19800
+ fs42.writeFileSync(tmp, payload, "utf-8");
19748
19801
  } catch (e) {
19749
19802
  return { ok: false, message: e instanceof Error ? e.message : String(e) };
19750
19803
  }
19751
19804
  try {
19752
- fs41.renameSync(tmp, outPath);
19805
+ fs42.renameSync(tmp, outPath);
19753
19806
  } catch (e) {
19754
19807
  try {
19755
- fs41.unlinkSync(tmp);
19808
+ fs42.unlinkSync(tmp);
19756
19809
  } catch {
19757
19810
  }
19758
19811
  return { ok: false, message: e instanceof Error ? e.message : String(e) };
@@ -19785,6 +19838,247 @@ function runCoordinationDuplicatesDiagnosis(input) {
19785
19838
  };
19786
19839
  }
19787
19840
 
19841
+ // src/analysis/contextSrsDiscoveryDownload.ts
19842
+ var fs43 = __toESM(require("node:fs"));
19843
+ var path42 = __toESM(require("node:path"));
19844
+ var SRS_DISCOVERY_BATCH_GET_CHUNK_SIZE = 200;
19845
+ function isRecord6(v) {
19846
+ return Boolean(v) && typeof v === "object" && !Array.isArray(v);
19847
+ }
19848
+ function extractSrsDiscoveryIdsFromApplicationProperties(properties) {
19849
+ const raw = properties.srs_discovery;
19850
+ if (!Array.isArray(raw)) {
19851
+ return [];
19852
+ }
19853
+ const out = [];
19854
+ for (const item of raw) {
19855
+ if (typeof item === "string") {
19856
+ const t = item.trim();
19857
+ if (t.length > 0) {
19858
+ out.push(t);
19859
+ }
19860
+ } else if (isRecord6(item)) {
19861
+ const id = typeof item.id === "string" ? item.id.trim() : "";
19862
+ if (id.length > 0) {
19863
+ out.push(id);
19864
+ }
19865
+ }
19866
+ }
19867
+ return [...new Set(out)];
19868
+ }
19869
+ async function fetchApplicationPropertiesForDownload(requestJson, applicationId) {
19870
+ const p = `/api/content/application/${encodeURIComponent(applicationId)}`;
19871
+ const body = await requestJson(p, { method: "GET", timeoutMs: 6e4 });
19872
+ const full = parseApplicationPropertiesFromContentGet(body);
19873
+ if (!full || full.id !== applicationId) {
19874
+ throw new Error("Application not found or unexpected response shape.");
19875
+ }
19876
+ return full.properties;
19877
+ }
19878
+ function parseBatchGetSrsDiscoveryEntities(res) {
19879
+ assertBasicResponseSuccess(res);
19880
+ const root = isRecord6(res) ? res.data : void 0;
19881
+ if (root === void 0 || root === null) {
19882
+ throw new Error("Batch get response missing data.");
19883
+ }
19884
+ if (isRecord6(root) && Array.isArray(root.entities)) {
19885
+ return root.entities.filter(isRecord6);
19886
+ }
19887
+ if (Array.isArray(root)) {
19888
+ return root.filter(isRecord6);
19889
+ }
19890
+ if (isRecord6(root) && Array.isArray(root.nodes)) {
19891
+ return root.nodes.filter(isRecord6);
19892
+ }
19893
+ throw new Error("Unexpected batch get response shape (expected data.entities).");
19894
+ }
19895
+ function entityBag(record) {
19896
+ const nested = record.properties;
19897
+ if (isRecord6(nested)) {
19898
+ return nested;
19899
+ }
19900
+ return record;
19901
+ }
19902
+ function extractSrsDiscoveryEntityId(record) {
19903
+ const direct = typeof record.id === "string" ? record.id.trim() : "";
19904
+ if (direct.length > 0) {
19905
+ return direct;
19906
+ }
19907
+ const bag = entityBag(record);
19908
+ const id = typeof bag.id === "string" ? bag.id.trim() : "";
19909
+ return id.length > 0 ? id : void 0;
19910
+ }
19911
+ function extractSrsDiscoveryEntityName(record) {
19912
+ const bag = entityBag(record);
19913
+ const n = bag.name;
19914
+ return typeof n === "string" ? n.trim() : "";
19915
+ }
19916
+ function extractSrsDiscoveryEntityBody(record) {
19917
+ const bag = entityBag(record);
19918
+ const ld = bag.long_description;
19919
+ return typeof ld === "string" ? ld : "";
19920
+ }
19921
+ function resolveSafeContextOutputPath(contextDirAbs, nameRaw) {
19922
+ const norm = nameRaw.replace(/\\/g, "/").trim();
19923
+ if (!norm || norm.includes("\0")) {
19924
+ return null;
19925
+ }
19926
+ const trimmed = norm.replace(/^\/+/, "");
19927
+ const segments = trimmed.split("/");
19928
+ for (const seg of segments) {
19929
+ if (seg.length === 0 || seg === "." || seg === "..") {
19930
+ return null;
19931
+ }
19932
+ }
19933
+ const resolved = path42.resolve(contextDirAbs, ...segments);
19934
+ const rel = path42.relative(contextDirAbs, resolved);
19935
+ if (rel.startsWith("..") || path42.isAbsolute(rel)) {
19936
+ return null;
19937
+ }
19938
+ return resolved;
19939
+ }
19940
+ function chunkIds(ids, size) {
19941
+ const out = [];
19942
+ for (let i = 0; i < ids.length; i += size) {
19943
+ out.push(ids.slice(i, i + size));
19944
+ }
19945
+ return out;
19946
+ }
19947
+ function clearContextDirectoryForCloudReplace(contextDirAbs) {
19948
+ if (!fs43.existsSync(contextDirAbs) || !fs43.statSync(contextDirAbs).isDirectory()) {
19949
+ return { filesRemoved: 0 };
19950
+ }
19951
+ const preserveAbs = path42.resolve(contextDirAbs, UPLOAD_TARGET_FILENAME);
19952
+ const preserveSet = /* @__PURE__ */ new Set();
19953
+ if (fs43.existsSync(preserveAbs) && fs43.statSync(preserveAbs).isFile()) {
19954
+ preserveSet.add(preserveAbs);
19955
+ }
19956
+ let filesRemoved = 0;
19957
+ const walkRm = (dir) => {
19958
+ let entries;
19959
+ try {
19960
+ entries = fs43.readdirSync(dir, { withFileTypes: true });
19961
+ } catch {
19962
+ return;
19963
+ }
19964
+ for (const e of entries) {
19965
+ const full = path42.join(dir, e.name);
19966
+ if (e.isDirectory()) {
19967
+ walkRm(full);
19968
+ try {
19969
+ fs43.rmdirSync(full);
19970
+ } catch {
19971
+ }
19972
+ } else if (e.isFile()) {
19973
+ const abs = path42.resolve(full);
19974
+ if (preserveSet.has(abs)) {
19975
+ continue;
19976
+ }
19977
+ try {
19978
+ fs43.unlinkSync(abs);
19979
+ filesRemoved += 1;
19980
+ } catch {
19981
+ }
19982
+ }
19983
+ }
19984
+ };
19985
+ walkRm(contextDirAbs);
19986
+ return { filesRemoved };
19987
+ }
19988
+ var BATCH_GET_PATH = "/api/batch/content/srs_discovery/get";
19989
+ async function runContextSrsDiscoveryDownload(opts) {
19990
+ const chunkSize = opts.batchChunkSize ?? SRS_DISCOVERY_BATCH_GET_CHUNK_SIZE;
19991
+ const timeoutMs = opts.timeoutMs ?? SRS_DISCOVERY_BATCH_SAVE_TIMEOUT_MS;
19992
+ const log = opts.log;
19993
+ const props = await fetchApplicationPropertiesForDownload(opts.requestJson, opts.applicationId);
19994
+ const ids = extractSrsDiscoveryIdsFromApplicationProperties(props);
19995
+ let localFilesRemoved = 0;
19996
+ if (opts.replaceFromCloud) {
19997
+ const clr = clearContextDirectoryForCloudReplace(opts.contextDirAbs);
19998
+ localFilesRemoved = clr.filesRemoved;
19999
+ log?.(`[download] removed ${String(localFilesRemoved)} local file(s) before cloud fetch (--replace-from-cloud).`);
20000
+ }
20001
+ if (ids.length === 0) {
20002
+ return {
20003
+ downloaded: 0,
20004
+ skipped: 0,
20005
+ failed: [],
20006
+ localFilesRemoved,
20007
+ succeededIds: {}
20008
+ };
20009
+ }
20010
+ const entityById = /* @__PURE__ */ new Map();
20011
+ const batches = chunkIds(ids, chunkSize);
20012
+ for (const chunk of batches) {
20013
+ const body = {
20014
+ ids: chunk,
20015
+ content_schema_prefix: SRS_DISCOVERY_CONTENT_SCHEMA_PREFIX
20016
+ };
20017
+ const req = {
20018
+ method: "POST",
20019
+ body,
20020
+ timeoutMs
20021
+ };
20022
+ const res = await opts.requestJson(BATCH_GET_PATH, req);
20023
+ const entities = parseBatchGetSrsDiscoveryEntities(res);
20024
+ for (const ent of entities) {
20025
+ const rowId = extractSrsDiscoveryEntityId(ent);
20026
+ if (rowId) {
20027
+ entityById.set(rowId, ent);
20028
+ }
20029
+ }
20030
+ }
20031
+ const failed = [];
20032
+ let downloaded = 0;
20033
+ let skipped = 0;
20034
+ const succeededIds = {};
20035
+ for (const id of ids) {
20036
+ const ent = entityById.get(id);
20037
+ if (!ent) {
20038
+ failed.push({ id, message: "Not returned by batch get." });
20039
+ continue;
20040
+ }
20041
+ const name = extractSrsDiscoveryEntityName(ent);
20042
+ if (!name) {
20043
+ failed.push({ id, message: "Missing name on srs_discovery node." });
20044
+ continue;
20045
+ }
20046
+ if (name === UPLOAD_TARGET_FILENAME || path42.basename(name) === UPLOAD_TARGET_FILENAME) {
20047
+ skipped += 1;
20048
+ log?.(`[download] skip ${name} (upload target row).`);
20049
+ continue;
20050
+ }
20051
+ const bodyText = extractSrsDiscoveryEntityBody(ent);
20052
+ const outAbs = resolveSafeContextOutputPath(opts.contextDirAbs, name);
20053
+ if (!outAbs) {
20054
+ failed.push({ id, name, message: "Unsafe or invalid name for local path." });
20055
+ continue;
20056
+ }
20057
+ fs43.mkdirSync(path42.dirname(outAbs), { recursive: true });
20058
+ const exists = fs43.existsSync(outAbs);
20059
+ if (exists && !opts.force && !opts.replaceFromCloud) {
20060
+ skipped += 1;
20061
+ log?.(`[download] skip existing ${name} (use --force to overwrite).`);
20062
+ continue;
20063
+ }
20064
+ try {
20065
+ fs43.writeFileSync(outAbs, bodyText, "utf8");
20066
+ downloaded += 1;
20067
+ succeededIds[outAbs] = id;
20068
+ } catch (e) {
20069
+ const msg = e instanceof Error ? e.message : String(e);
20070
+ failed.push({ id, name, message: msg });
20071
+ }
20072
+ }
20073
+ if (Object.keys(succeededIds).length > 0) {
20074
+ const mergeRes = mergeUploadIdsIntoIndexOnDisk(opts.contextDirAbs, succeededIds);
20075
+ if (!mergeRes.ok) {
20076
+ log?.(`[download] merge ids into index failed: ${mergeRes.message}`);
20077
+ }
20078
+ }
20079
+ return { downloaded, skipped, failed, localFilesRemoved, succeededIds };
20080
+ }
20081
+
19788
20082
  // src/auth/authApi.ts
19789
20083
  var API_TIMEOUT_MS = 1e4;
19790
20084
  async function fetchWithTimeout(url, init, fetchImpl) {
@@ -19843,12 +20137,12 @@ function toFetchErrorMessage(e) {
19843
20137
 
19844
20138
  // src/auth/gluecharmContentNegotiation.ts
19845
20139
  var GLUECHARM_WS_LEGACY_JSON = "application/vnd.gluecharm.v1.ws-legacy+json";
19846
- function pathWithoutQuery(path45) {
19847
- const q = path45.indexOf("?");
19848
- return q === -1 ? path45 : path45.slice(0, q);
20140
+ function pathWithoutQuery(path47) {
20141
+ const q = path47.indexOf("?");
20142
+ return q === -1 ? path47 : path47.slice(0, q);
19849
20143
  }
19850
- function isGluecharmContentApiPath(path45) {
19851
- const p = pathWithoutQuery(path45);
20144
+ function isGluecharmContentApiPath(path47) {
20145
+ const p = pathWithoutQuery(path47);
19852
20146
  return p.startsWith("/api/content/") || p.startsWith("/api/batch/content/");
19853
20147
  }
19854
20148
  function gluecharmContentHeaders(method) {
@@ -19886,7 +20180,7 @@ async function fetchWithTimeout2(url, init, fetchImpl, timeoutMs, externalSignal
19886
20180
  }
19887
20181
  function createAuthenticatedRequestJson(deps) {
19888
20182
  const fetchImpl = deps.fetchImpl ?? fetch;
19889
- async function requestJson(path45, options = {}) {
20183
+ async function requestJson(path47, options = {}) {
19890
20184
  const base = deps.getApiBaseUrl();
19891
20185
  if (!base) {
19892
20186
  const err = { status: 0, message: "easyspecs.apiBaseUrl is not configured." };
@@ -19894,7 +20188,7 @@ function createAuthenticatedRequestJson(deps) {
19894
20188
  }
19895
20189
  const method = options.method ?? "GET";
19896
20190
  const headers = { ...options.headers };
19897
- if (isGluecharmContentApiPath(path45)) {
20191
+ if (isGluecharmContentApiPath(path47)) {
19898
20192
  Object.assign(headers, gluecharmContentHeaders(method));
19899
20193
  } else {
19900
20194
  if (!headers.Accept) {
@@ -19908,7 +20202,7 @@ function createAuthenticatedRequestJson(deps) {
19908
20202
  if (options.withAuth !== false && access) {
19909
20203
  headers.Authorization = `Bearer ${access}`;
19910
20204
  }
19911
- const url = `${base}${path45}`;
20205
+ const url = `${base}${path47}`;
19912
20206
  const timeoutMs = options.timeoutMs ?? API_TIMEOUT_MS;
19913
20207
  const response = await fetchWithTimeout2(
19914
20208
  url,
@@ -19929,7 +20223,7 @@ function createAuthenticatedRequestJson(deps) {
19929
20223
  if (shouldRetryUnauthorized) {
19930
20224
  const refreshed = await deps.refreshSession();
19931
20225
  if (refreshed) {
19932
- return requestJson(path45, { ...options, retryOnUnauthorized: false });
20226
+ return requestJson(path47, { ...options, retryOnUnauthorized: false });
19933
20227
  }
19934
20228
  }
19935
20229
  const fallback = payload == null ? `${response.statusText || "HTTP error"} (response body empty or not JSON)` : "Request failed.";
@@ -19939,15 +20233,15 @@ function createAuthenticatedRequestJson(deps) {
19939
20233
  }
19940
20234
 
19941
20235
  // src/cli/cliSession.ts
19942
- var fs42 = __toESM(require("node:fs"));
20236
+ var fs44 = __toESM(require("node:fs"));
19943
20237
  var os2 = __toESM(require("node:os"));
19944
- var path41 = __toESM(require("node:path"));
20238
+ var path43 = __toESM(require("node:path"));
19945
20239
  function defaultSessionPath() {
19946
- return path41.join(os2.homedir(), ".easyspecs", "cli-session.json");
20240
+ return path43.join(os2.homedir(), ".easyspecs", "cli-session.json");
19947
20241
  }
19948
20242
  var configSessionPath;
19949
20243
  function setCliSessionPathFromConfig(absPath) {
19950
- configSessionPath = absPath?.trim() ? path41.resolve(absPath) : void 0;
20244
+ configSessionPath = absPath?.trim() ? path43.resolve(absPath) : void 0;
19951
20245
  }
19952
20246
  function applyCliSessionPathFromRepoConfig(repoRoot, cfg) {
19953
20247
  const raw = cfg.easyspecs?.cliSessionPath?.trim();
@@ -19955,7 +20249,7 @@ function applyCliSessionPathFromRepoConfig(repoRoot, cfg) {
19955
20249
  setCliSessionPathFromConfig(void 0);
19956
20250
  return;
19957
20251
  }
19958
- const abs = path41.isAbsolute(raw) ? raw : path41.join(repoRoot, raw);
20252
+ const abs = path43.isAbsolute(raw) ? raw : path43.join(repoRoot, raw);
19959
20253
  setCliSessionPathFromConfig(abs);
19960
20254
  }
19961
20255
  function normalizeCliSessionPathForConfig(repoRoot, raw) {
@@ -19963,15 +20257,15 @@ function normalizeCliSessionPathForConfig(repoRoot, raw) {
19963
20257
  if (!t) {
19964
20258
  return "";
19965
20259
  }
19966
- const resolvedRepo = path41.resolve(repoRoot);
19967
- const abs = path41.isAbsolute(t) ? path41.normalize(t) : path41.resolve(resolvedRepo, t);
19968
- const rel = path41.relative(resolvedRepo, abs);
20260
+ const resolvedRepo = path43.resolve(repoRoot);
20261
+ const abs = path43.isAbsolute(t) ? path43.normalize(t) : path43.resolve(resolvedRepo, t);
20262
+ const rel = path43.relative(resolvedRepo, abs);
19969
20263
  if (rel === "") {
19970
20264
  return abs;
19971
20265
  }
19972
- const underRepo = !rel.startsWith("..") && !path41.isAbsolute(rel);
20266
+ const underRepo = !rel.startsWith("..") && !path43.isAbsolute(rel);
19973
20267
  if (underRepo) {
19974
- return rel.split(path41.sep).join("/");
20268
+ return rel.split(path43.sep).join("/");
19975
20269
  }
19976
20270
  return abs;
19977
20271
  }
@@ -19984,10 +20278,10 @@ function effectiveCliSessionPath() {
19984
20278
  function readCliSession() {
19985
20279
  const p = effectiveCliSessionPath();
19986
20280
  try {
19987
- if (!fs42.existsSync(p)) {
20281
+ if (!fs44.existsSync(p)) {
19988
20282
  return void 0;
19989
20283
  }
19990
- const j = JSON.parse(fs42.readFileSync(p, "utf8"));
20284
+ const j = JSON.parse(fs44.readFileSync(p, "utf8"));
19991
20285
  const apiBaseUrl = typeof j.apiBaseUrl === "string" ? j.apiBaseUrl.trim() : "";
19992
20286
  const accessToken = typeof j.accessToken === "string" ? j.accessToken : "";
19993
20287
  const refreshToken = typeof j.refreshToken === "string" ? j.refreshToken : "";
@@ -20001,33 +20295,33 @@ function readCliSession() {
20001
20295
  }
20002
20296
  function writeCliSession(s) {
20003
20297
  const p = effectiveCliSessionPath();
20004
- fs42.mkdirSync(path41.dirname(p), { recursive: true });
20005
- fs42.writeFileSync(p, `${JSON.stringify(s, null, 2)}
20298
+ fs44.mkdirSync(path43.dirname(p), { recursive: true });
20299
+ fs44.writeFileSync(p, `${JSON.stringify(s, null, 2)}
20006
20300
  `, "utf8");
20007
20301
  }
20008
20302
  function clearCliSession() {
20009
20303
  const p = effectiveCliSessionPath();
20010
20304
  try {
20011
- fs42.unlinkSync(p);
20305
+ fs44.unlinkSync(p);
20012
20306
  } catch {
20013
20307
  }
20014
20308
  }
20015
20309
 
20016
20310
  // src/analysis/acePendingTraces.ts
20017
- var fs43 = __toESM(require("fs"));
20018
- var path42 = __toESM(require("path"));
20311
+ var fs45 = __toESM(require("fs"));
20312
+ var path44 = __toESM(require("path"));
20019
20313
  function normalizeAceTraceRelativePath(rel) {
20020
20314
  return rel.split(/[/\\]/).join("/");
20021
20315
  }
20022
20316
  function readCompletedTraceRelativePaths(contextDir2) {
20023
20317
  const set = /* @__PURE__ */ new Set();
20024
20318
  const jsonlPath = aceConsolidatedSessionsJsonlPath(contextDir2);
20025
- if (!fs43.existsSync(jsonlPath)) {
20319
+ if (!fs45.existsSync(jsonlPath)) {
20026
20320
  return set;
20027
20321
  }
20028
20322
  let raw;
20029
20323
  try {
20030
- raw = fs43.readFileSync(jsonlPath, "utf8");
20324
+ raw = fs45.readFileSync(jsonlPath, "utf8");
20031
20325
  } catch {
20032
20326
  return set;
20033
20327
  }
@@ -20058,14 +20352,14 @@ function readCompletedTraceRelativePaths(contextDir2) {
20058
20352
  }
20059
20353
  function listPendingAceTraceFiles(contextDir2, worktreeRoot) {
20060
20354
  const traceSchema = opencodeAceSchemaPath(worktreeRoot, ACE_SCHEMA_TRACE);
20061
- if (!fs43.existsSync(traceSchema)) {
20355
+ if (!fs45.existsSync(traceSchema)) {
20062
20356
  return [];
20063
20357
  }
20064
20358
  const completed = readCompletedTraceRelativePaths(contextDir2);
20065
20359
  const allAbs = listAceTraceFiles(contextDir2);
20066
20360
  const pending = [];
20067
20361
  for (const abs of allAbs) {
20068
- const rel = normalizeAceTraceRelativePath(path42.relative(contextDir2, abs));
20362
+ const rel = normalizeAceTraceRelativePath(path44.relative(contextDir2, abs));
20069
20363
  const v = validateAceJsonFile(abs, traceSchema);
20070
20364
  if (!v.ok) {
20071
20365
  continue;
@@ -20080,7 +20374,7 @@ function listPendingAceTraceFiles(contextDir2, worktreeRoot) {
20080
20374
  }
20081
20375
 
20082
20376
  // src/analysis/aceAutoLearnPool.ts
20083
- var path43 = __toESM(require("path"));
20377
+ var path45 = __toESM(require("path"));
20084
20378
  function clampConcurrency2(n) {
20085
20379
  if (!Number.isFinite(n)) {
20086
20380
  return 30;
@@ -20094,8 +20388,8 @@ async function runAceAutoLearnPool(p) {
20094
20388
  const fifo = [...p.traceAbsolutePaths];
20095
20389
  let active = 0;
20096
20390
  let wake;
20097
- const waitTurn = () => new Promise((resolve15) => {
20098
- wake = resolve15;
20391
+ const waitTurn = () => new Promise((resolve17) => {
20392
+ wake = resolve17;
20099
20393
  });
20100
20394
  const pump = () => {
20101
20395
  wake?.();
@@ -20115,7 +20409,7 @@ async function runAceAutoLearnPool(p) {
20115
20409
  abortSignal.addEventListener("abort", onPipelineAbort, { once: true });
20116
20410
  }
20117
20411
  }
20118
- const traceRel = (abs) => path43.relative(contextDir2, abs).split(path43.sep).join("/");
20412
+ const traceRel = (abs) => path45.relative(contextDir2, abs).split(path45.sep).join("/");
20119
20413
  const runOne = async (traceAbs) => {
20120
20414
  if (abortSignal?.aborted) {
20121
20415
  active -= 1;
@@ -20564,7 +20858,7 @@ function formatCliStderrLine(line, useAnsi) {
20564
20858
  }
20565
20859
 
20566
20860
  // src/cli/main.ts
20567
- var PKG_VERSION = "0.0.12";
20861
+ var PKG_VERSION = "0.0.14";
20568
20862
  function isEasyspecsConfigReadError(e) {
20569
20863
  return e instanceof EasyspecsConfigInvalidJsonError || e instanceof EasyspecsConfigSchemaError;
20570
20864
  }
@@ -20623,6 +20917,7 @@ Commands:
20623
20917
  diagnose coverage-report --root workspace|worktree [--worktree <path>]
20624
20918
  diagnose missing-artefacts --root workspace|worktree [--worktree <path>]
20625
20919
  diagnose zero-reference [--worktree <path>]
20920
+ download context [--force] [--replace-from-cloud]
20626
20921
  upload context | upload republish
20627
20922
  ace clear | ace learn | ace auto-learn [--worktree <path>]
20628
20923
  config init [--overwrite]
@@ -20643,23 +20938,23 @@ function resolveAnalysisRoot(repoRoot, rootKind, worktreePath) {
20643
20938
  return repoRoot;
20644
20939
  }
20645
20940
  const wt = worktreePath?.trim();
20646
- if (wt && fs44.existsSync(path44.join(wt, ".git"))) {
20647
- return path44.resolve(wt);
20941
+ if (wt && fs46.existsSync(path46.join(wt, ".git"))) {
20942
+ return path46.resolve(wt);
20648
20943
  }
20649
20944
  throw new Error("worktree mode requires --worktree <path> to an existing analysis checkout.");
20650
20945
  }
20651
20946
  function resolveAdHocCheckoutRoot(_repoRoot, storage, worktreeFlag) {
20652
20947
  const w = worktreeFlag?.trim();
20653
20948
  if (w) {
20654
- const abs = path44.resolve(w);
20655
- if (fs44.existsSync(path44.join(abs, ".git"))) {
20949
+ const abs = path46.resolve(w);
20950
+ if (fs46.existsSync(path46.join(abs, ".git"))) {
20656
20951
  return abs;
20657
20952
  }
20658
20953
  throw new Error(`Invalid --worktree (not a git checkout): ${abs}`);
20659
20954
  }
20660
20955
  const snap = readAnalysisWorkspaceSnapshot(storage);
20661
20956
  const p = snap?.adHocWorktreePath?.trim();
20662
- if (p && fs44.existsSync(path44.join(p, ".git"))) {
20957
+ if (p && fs46.existsSync(path46.join(p, ".git"))) {
20663
20958
  return p;
20664
20959
  }
20665
20960
  throw new Error("No analysis checkout: run `easyspecs-cli run synthesis` first or pass `--worktree <path>`.");
@@ -20686,7 +20981,7 @@ async function runResumeRemediationPool(storage, repoRoot, analysisRoot, merged,
20686
20981
  requireOpenCode(merged, flags);
20687
20982
  const agentsDir = resolveOpenCodeAgentsDir(repoRoot, repoConfig);
20688
20983
  assertAgentsDirExists(agentsDir);
20689
- const ctxDir = path44.join(analysisRoot, ".gluecharm", "context");
20984
+ const ctxDir = path46.join(analysisRoot, ".gluecharm", "context");
20690
20985
  const snap = readArtefactRunSnapshot(storage);
20691
20986
  const rows = listMissingArtefacts(ctxDir, analysisRoot, snap);
20692
20987
  if (rows.length === 0) {
@@ -20704,7 +20999,7 @@ async function runResumeRemediationPool(storage, repoRoot, analysisRoot, merged,
20704
20999
  storageContext: storage,
20705
21000
  repositoryRoot: repoRoot,
20706
21001
  worktreeRoot: analysisRoot,
20707
- workspaceLabel: path44.basename(analysisRoot),
21002
+ workspaceLabel: path46.basename(analysisRoot),
20708
21003
  oc,
20709
21004
  log: (line) => logErr(flags, line),
20710
21005
  abortSignal: void 0,
@@ -20823,17 +21118,17 @@ async function main() {
20823
21118
  { easyspecs: { defaultGitRemoteUrl: url } },
20824
21119
  { warnMigration: (m) => logErr(flags, `[EasySpecs] ${m}`) }
20825
21120
  );
20826
- const path45 = easyspecsConfigPath(repoRoot);
21121
+ const path47 = easyspecsConfigPath(repoRoot);
20827
21122
  if (flags.json) {
20828
21123
  printJsonLine({
20829
21124
  command: "config set-git-remote",
20830
21125
  durationMs: Date.now() - t0,
20831
21126
  ok: true,
20832
- path: path45,
21127
+ path: path47,
20833
21128
  defaultGitRemoteUrl: cfg.easyspecs?.defaultGitRemoteUrl ?? ""
20834
21129
  });
20835
21130
  } else {
20836
- console.log(`Updated ${path45} \u2014 easyspecs.defaultGitRemoteUrl`);
21131
+ console.log(`Updated ${path47} \u2014 easyspecs.defaultGitRemoteUrl`);
20837
21132
  }
20838
21133
  process.exit(ExitCode.ok);
20839
21134
  } catch (e) {
@@ -20851,6 +21146,9 @@ async function main() {
20851
21146
  allowCreate: allowCreateConfig,
20852
21147
  warnMigration: (m) => logErr(flags, `[EasySpecs] ${m}`)
20853
21148
  });
21149
+ repoConfig = syncDefaultGitRemoteUrlIfEmpty(repoRoot, repoConfig, {
21150
+ warnMigration: (m) => logErr(flags, `[EasySpecs] ${m}`)
21151
+ });
20854
21152
  } catch (e) {
20855
21153
  if (isEasyspecsConfigReadError(e)) {
20856
21154
  console.error(e.message);
@@ -20861,7 +21159,7 @@ async function main() {
20861
21159
  applyCliSessionPathFromRepoConfig(repoRoot, repoConfig);
20862
21160
  if (flags.sessionPath?.trim()) {
20863
21161
  const sp = flags.sessionPath.trim();
20864
- const abs = path44.isAbsolute(sp) ? path44.normalize(sp) : path44.resolve(repoRoot, sp);
21162
+ const abs = path46.isAbsolute(sp) ? path46.normalize(sp) : path46.resolve(repoRoot, sp);
20865
21163
  setCliSessionPathFromConfig(abs);
20866
21164
  }
20867
21165
  const apiResolved = initApiBaseUrlForCli(repoRoot, flags, repoConfig);
@@ -20882,6 +21180,12 @@ async function main() {
20882
21180
  process.exit(code);
20883
21181
  throw new Error("unreachable");
20884
21182
  };
21183
+ const requireMinimalGluecharmLayoutAt = (absRoot) => {
21184
+ const r = ensureMinimalGluecharmLayoutNode(absRoot);
21185
+ if (!r.ok) {
21186
+ finish(ExitCode.misconfiguration, { ok: false, error: r.error });
21187
+ }
21188
+ };
20885
21189
  const pos = positionals;
20886
21190
  try {
20887
21191
  resetCliDiagnosticPhaseState();
@@ -20895,7 +21199,7 @@ async function main() {
20895
21199
  process.exit(ExitCode.usage);
20896
21200
  }
20897
21201
  const agentsDir = resolveOpenCodeAgentsDir(repoRoot, repoConfig);
20898
- const agentsOk = fs44.existsSync(agentsDir);
21202
+ const agentsOk = fs46.existsSync(agentsDir);
20899
21203
  const oc = getOpenCodeReadiness({
20900
21204
  executable: merged.openCodeExecutable,
20901
21205
  skipCredentialsCheck: merged.openCodeSkipCredentialsCheck,
@@ -21070,7 +21374,7 @@ async function main() {
21070
21374
  const result = await runContextArtefactPipelineAsync(
21071
21375
  storage,
21072
21376
  repoRoot,
21073
- path44.basename(repoRoot),
21377
+ path46.basename(repoRoot),
21074
21378
  agentsDir,
21075
21379
  {
21076
21380
  ...merged.pipelineOpenCode
@@ -21124,7 +21428,8 @@ async function main() {
21124
21428
  const worktree = wtExplicit ?? wtFromRoot;
21125
21429
  if (sub === "reference-coverage") {
21126
21430
  const rootAbs = resolveAnalysisRoot(repoRoot, rootKind, worktree);
21127
- const contextDir2 = path44.join(rootAbs, ".gluecharm", "context");
21431
+ requireMinimalGluecharmLayoutAt(rootAbs);
21432
+ const contextDir2 = path46.join(rootAbs, ".gluecharm", "context");
21128
21433
  const res = runCoverageReferenceValidation({
21129
21434
  repositoryRootAbs: rootAbs,
21130
21435
  contextDirAbs: contextDir2,
@@ -21146,7 +21451,8 @@ async function main() {
21146
21451
  }
21147
21452
  if (sub === "coordination-duplicates") {
21148
21453
  const rootAbs = resolveAnalysisRoot(repoRoot, rootKind, worktree);
21149
- const contextDir2 = path44.join(rootAbs, ".gluecharm", "context");
21454
+ requireMinimalGluecharmLayoutAt(rootAbs);
21455
+ const contextDir2 = path46.join(rootAbs, ".gluecharm", "context");
21150
21456
  const res = runCoordinationDuplicatesDiagnosis({
21151
21457
  contextDirAbsolute: contextDir2,
21152
21458
  sourceRoot: rootKind === "worktree" ? "worktree" : "workspace"
@@ -21168,6 +21474,7 @@ async function main() {
21168
21474
  }
21169
21475
  if (sub === "coverage-report") {
21170
21476
  const rootAbs = resolveAnalysisRoot(repoRoot, rootKind, worktree);
21477
+ requireMinimalGluecharmLayoutAt(rootAbs);
21171
21478
  const rep = await runReferenceCoverageExecutionReport({
21172
21479
  repositoryRootAbs: rootAbs,
21173
21480
  diagnosticLog: (line) => logErr(flags, line)
@@ -21180,7 +21487,8 @@ async function main() {
21180
21487
  }
21181
21488
  if (sub === "missing-artefacts") {
21182
21489
  const rootAbs = resolveAnalysisRoot(repoRoot, rootKind, worktree);
21183
- const ctxDir = path44.join(rootAbs, ".gluecharm", "context");
21490
+ requireMinimalGluecharmLayoutAt(rootAbs);
21491
+ const ctxDir = path46.join(rootAbs, ".gluecharm", "context");
21184
21492
  const storage = createFileBackedWorkspaceState(repoRoot);
21185
21493
  const snap = readArtefactRunSnapshot(storage);
21186
21494
  const rows = listMissingArtefacts(ctxDir, rootAbs, snap);
@@ -21201,6 +21509,7 @@ async function main() {
21201
21509
  const storage = createFileBackedWorkspaceState(repoRoot);
21202
21510
  const { worktree: wtAce } = parseWorktreeFlag(pos.slice(2));
21203
21511
  const analysisRoot = resolveAdHocCheckoutRoot(repoRoot, storage, wtAce);
21512
+ requireMinimalGluecharmLayoutAt(analysisRoot);
21204
21513
  const cov = readNonReferencedFilesFromRepositoryRoot(analysisRoot);
21205
21514
  if (!cov.ok) {
21206
21515
  finish(ExitCode.validation, { ok: false, error: cov.error });
@@ -21260,6 +21569,7 @@ async function main() {
21260
21569
  cloudContextAnalyzedAt: cloudCachedAt
21261
21570
  });
21262
21571
  }
21572
+ requireMinimalGluecharmLayoutAt(repoRoot);
21263
21573
  requireOpenCode(merged, flags);
21264
21574
  const wantsUpload = positionals.includes("--upload");
21265
21575
  const uploadSession = wantsUpload ? readCliSession() : void 0;
@@ -21276,8 +21586,8 @@ async function main() {
21276
21586
  const runBackendSyncImpl = wantsUpload && uploadSession ? async () => {
21277
21587
  const s = uploadSession;
21278
21588
  const snap = readAnalysisWorkspaceSnapshot(storage);
21279
- const wsContextDir = path44.join(repoRoot, ".gluecharm", "context");
21280
- const wtContextDir = snap?.adHocWorktreePath && fs44.existsSync(path44.join(snap.adHocWorktreePath, ".gluecharm", "context")) ? path44.join(snap.adHocWorktreePath, ".gluecharm", "context") : "";
21589
+ const wsContextDir = path46.join(repoRoot, ".gluecharm", "context");
21590
+ const wtContextDir = snap?.adHocWorktreePath && fs46.existsSync(path46.join(snap.adHocWorktreePath, ".gluecharm", "context")) ? path46.join(snap.adHocWorktreePath, ".gluecharm", "context") : "";
21281
21591
  if (!wtContextDir) {
21282
21592
  return { ok: false, message: "No worktree context to upload." };
21283
21593
  }
@@ -21343,6 +21653,104 @@ async function main() {
21343
21653
  totalElapsedMs: res.totalElapsedMs
21344
21654
  });
21345
21655
  }
21656
+ if (pos[0] === "download" && pos[1] === "context") {
21657
+ const sessRaw = readCliSession();
21658
+ if (sessRaw === void 0) {
21659
+ finish(ExitCode.auth, {
21660
+ ok: false,
21661
+ error: "auth login first (`easyspecs-cli auth login --email \u2026 --password \u2026`, or `--ci` with easyspecs.auth.ciLogin in .easyspecs/config.json)"
21662
+ });
21663
+ }
21664
+ let force = false;
21665
+ let replaceFromCloud = false;
21666
+ for (const a of pos.slice(2)) {
21667
+ if (a === "--force") {
21668
+ force = true;
21669
+ continue;
21670
+ }
21671
+ if (a === "--replace-from-cloud") {
21672
+ replaceFromCloud = true;
21673
+ continue;
21674
+ }
21675
+ finish(ExitCode.usage, { ok: false, error: `unknown download context flag: ${a}` });
21676
+ }
21677
+ requireMinimalGluecharmLayoutAt(repoRoot);
21678
+ const ctxDir = path46.resolve(path46.join(repoRoot, ".gluecharm", "context"));
21679
+ const gluecharmParent = path46.dirname(ctxDir);
21680
+ if (path46.basename(gluecharmParent) === ".gluecharm" && path46.basename(ctxDir) === "context") {
21681
+ requireMinimalGluecharmLayoutAt(path46.dirname(gluecharmParent));
21682
+ }
21683
+ const appIdRaw = getEasyspecsProjectIdFromRepoConfig(repoConfig)?.trim();
21684
+ if (!appIdRaw) {
21685
+ finish(ExitCode.misconfiguration, {
21686
+ ok: false,
21687
+ error: "Missing easyspecs.easyspecsProjectId in .easyspecs/config.json."
21688
+ });
21689
+ }
21690
+ const applicationId = appIdRaw;
21691
+ const sess = sessRaw;
21692
+ let access = sess.accessToken;
21693
+ const refresh = sess.refreshToken;
21694
+ const requestJson = createAuthenticatedRequestJson({
21695
+ getApiBaseUrl: () => sess.apiBaseUrl,
21696
+ getAccessToken: () => access,
21697
+ getRefreshToken: () => refresh,
21698
+ refreshSession: async () => {
21699
+ try {
21700
+ const r = await refreshTokenWithApi(sess.apiBaseUrl, fetch, refresh);
21701
+ access = r.accessToken;
21702
+ writeCliSession({
21703
+ apiBaseUrl: sess.apiBaseUrl,
21704
+ accessToken: r.accessToken,
21705
+ refreshToken: r.refreshToken
21706
+ });
21707
+ return true;
21708
+ } catch {
21709
+ return false;
21710
+ }
21711
+ }
21712
+ });
21713
+ try {
21714
+ const result = await runContextSrsDiscoveryDownload({
21715
+ requestJson,
21716
+ applicationId,
21717
+ contextDirAbs: ctxDir,
21718
+ force,
21719
+ replaceFromCloud,
21720
+ log: (line) => logErr(flags, line)
21721
+ });
21722
+ const failCount = result.failed.length;
21723
+ const cmdOk = failCount === 0;
21724
+ if (!flags.json) {
21725
+ logErr(
21726
+ flags,
21727
+ `[download] downloaded=${String(result.downloaded)} skipped=${String(result.skipped)} failed=${String(failCount)} localRemoved=${String(result.localFilesRemoved)}`
21728
+ );
21729
+ if (failCount > 0) {
21730
+ for (const f of result.failed.slice(0, 12)) {
21731
+ logErr(
21732
+ flags,
21733
+ `[download] failed ${f.name ?? "?"} (${f.id ?? "?"}): ${f.message}`
21734
+ );
21735
+ }
21736
+ if (result.failed.length > 12) {
21737
+ logErr(flags, `[download] \u2026 and ${String(result.failed.length - 12)} more`);
21738
+ }
21739
+ }
21740
+ }
21741
+ finish(cmdOk ? ExitCode.ok : ExitCode.upload, {
21742
+ ok: cmdOk,
21743
+ downloaded: result.downloaded,
21744
+ skipped: result.skipped,
21745
+ failed: failCount,
21746
+ localRemoved: result.localFilesRemoved,
21747
+ ...flags.verbose && failCount > 0 ? { failures: result.failed } : {}
21748
+ });
21749
+ } catch (e) {
21750
+ const msg = e instanceof Error ? e.message : String(e);
21751
+ finish(ExitCode.internal, { ok: false, error: msg });
21752
+ }
21753
+ }
21346
21754
  if (pos[0] === "upload" && (pos[1] === "context" || pos[1] === "republish")) {
21347
21755
  const sessRaw = readCliSession();
21348
21756
  if (sessRaw === void 0) {
@@ -21352,16 +21760,17 @@ async function main() {
21352
21760
  });
21353
21761
  }
21354
21762
  const sess = sessRaw;
21355
- let ctxDir = path44.join(repoRoot, ".gluecharm", "context");
21763
+ requireMinimalGluecharmLayoutAt(repoRoot);
21764
+ let ctxDir = path46.join(repoRoot, ".gluecharm", "context");
21356
21765
  if (pos[1] === "republish") {
21357
21766
  const fromCfg = repoConfig.easyspecs?.upload?.contextDirectory?.trim();
21358
- const resolvedOverride = fromCfg && fromCfg.length > 0 ? path44.isAbsolute(fromCfg) ? path44.normalize(fromCfg) : path44.resolve(repoRoot, fromCfg) : "";
21359
- if (resolvedOverride && fs44.existsSync(path44.join(resolvedOverride, ".."))) {
21767
+ const resolvedOverride = fromCfg && fromCfg.length > 0 ? path46.isAbsolute(fromCfg) ? path46.normalize(fromCfg) : path46.resolve(repoRoot, fromCfg) : "";
21768
+ if (resolvedOverride && fs46.existsSync(path46.join(resolvedOverride, ".."))) {
21360
21769
  ctxDir = resolvedOverride;
21361
21770
  } else {
21362
21771
  const storage = createFileBackedWorkspaceState(repoRoot);
21363
21772
  const snap = readAnalysisWorkspaceSnapshot(storage);
21364
- const wt = snap?.adHocWorktreePath && fs44.existsSync(path44.join(snap.adHocWorktreePath, ".gluecharm", "context")) ? path44.join(snap.adHocWorktreePath, ".gluecharm", "context") : "";
21773
+ const wt = snap?.adHocWorktreePath && fs46.existsSync(path46.join(snap.adHocWorktreePath, ".gluecharm", "context")) ? path46.join(snap.adHocWorktreePath, ".gluecharm", "context") : "";
21365
21774
  if (!wt) {
21366
21775
  finish(ExitCode.misconfiguration, {
21367
21776
  ok: false,
@@ -21371,6 +21780,11 @@ async function main() {
21371
21780
  ctxDir = wt;
21372
21781
  }
21373
21782
  }
21783
+ const ctxResolved = path46.resolve(ctxDir);
21784
+ const gluecharmParent = path46.dirname(ctxResolved);
21785
+ if (path46.basename(gluecharmParent) === ".gluecharm" && path46.basename(ctxResolved) === "context") {
21786
+ requireMinimalGluecharmLayoutAt(path46.dirname(gluecharmParent));
21787
+ }
21374
21788
  const appIdRaw = getEasyspecsProjectIdFromRepoConfig(repoConfig)?.trim();
21375
21789
  if (!appIdRaw) {
21376
21790
  finish(ExitCode.misconfiguration, {
@@ -21473,18 +21887,18 @@ async function main() {
21473
21887
  finish(failed ? ExitCode.upload : ExitCode.ok, primary);
21474
21888
  }
21475
21889
  if (pos[0] === "ace" && pos[1] === "clear") {
21476
- const learnings = path44.join(repoRoot, ".gluecharm", "context", "learnings");
21477
- if (!fs44.existsSync(learnings)) {
21890
+ const learnings = path46.join(repoRoot, ".gluecharm", "context", "learnings");
21891
+ if (!fs46.existsSync(learnings)) {
21478
21892
  finish(ExitCode.ok, { ok: true, message: "nothing to clear" });
21479
21893
  }
21480
- fs44.rmSync(learnings, { recursive: true, force: true });
21894
+ fs46.rmSync(learnings, { recursive: true, force: true });
21481
21895
  finish(ExitCode.ok, { ok: true, message: `cleared ${learnings}` });
21482
21896
  }
21483
21897
  if (pos[0] === "ace" && pos[1] === "learn") {
21484
21898
  requireOpenCode(merged, flags);
21485
21899
  const { worktree } = parseWorktreeFlag(pos.slice(2));
21486
- const root = worktree && fs44.existsSync(path44.join(worktree, ".opencode", "schemas", "ace")) ? worktree : repoRoot;
21487
- const contextDir2 = path44.join(root, ".gluecharm", "context");
21900
+ const root = worktree && fs46.existsSync(path46.join(worktree, ".opencode", "schemas", "ace")) ? worktree : repoRoot;
21901
+ const contextDir2 = path46.join(root, ".gluecharm", "context");
21488
21902
  const traces = listAceTraceFiles(contextDir2);
21489
21903
  if (traces.length === 0) {
21490
21904
  finish(ExitCode.ok, { ok: true, message: "no traces", traceCount: 0 });
@@ -21508,8 +21922,8 @@ async function main() {
21508
21922
  if (pos[0] === "ace" && pos[1] === "auto-learn") {
21509
21923
  requireOpenCode(merged, flags);
21510
21924
  const { worktree } = parseWorktreeFlag(pos.slice(2));
21511
- const root = worktree && fs44.existsSync(path44.join(worktree, ".git")) ? worktree : repoRoot;
21512
- const contextDir2 = path44.join(root, ".gluecharm", "context");
21925
+ const root = worktree && fs46.existsSync(path46.join(worktree, ".git")) ? worktree : repoRoot;
21926
+ const contextDir2 = path46.join(root, ".gluecharm", "context");
21513
21927
  const pending = listPendingAceTraceFiles(contextDir2, root);
21514
21928
  if (pending.length === 0) {
21515
21929
  finish(ExitCode.ok, { ok: true, pending: 0 });