@mestreyoda/fabrica 0.2.21 → 0.2.23

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/index.js CHANGED
@@ -26948,14 +26948,14 @@ var require_tls_helpers = __commonJS({
26948
26948
  Object.defineProperty(exports2, "__esModule", { value: true });
26949
26949
  exports2.CIPHER_SUITES = void 0;
26950
26950
  exports2.getDefaultRootsData = getDefaultRootsData;
26951
- var fs45 = __require("fs");
26951
+ var fs46 = __require("fs");
26952
26952
  exports2.CIPHER_SUITES = process.env.GRPC_SSL_CIPHER_SUITES;
26953
26953
  var DEFAULT_ROOTS_FILE_PATH = process.env.GRPC_DEFAULT_SSL_ROOTS_FILE_PATH;
26954
26954
  var defaultRootsData = null;
26955
26955
  function getDefaultRootsData() {
26956
26956
  if (DEFAULT_ROOTS_FILE_PATH) {
26957
26957
  if (defaultRootsData === null) {
26958
- defaultRootsData = fs45.readFileSync(DEFAULT_ROOTS_FILE_PATH);
26958
+ defaultRootsData = fs46.readFileSync(DEFAULT_ROOTS_FILE_PATH);
26959
26959
  }
26960
26960
  return defaultRootsData;
26961
26961
  }
@@ -31086,7 +31086,7 @@ var require_fetch = __commonJS({
31086
31086
  module2.exports = fetch3;
31087
31087
  var asPromise = require_aspromise();
31088
31088
  var inquire2 = require_inquire();
31089
- var fs45 = inquire2("fs");
31089
+ var fs46 = inquire2("fs");
31090
31090
  function fetch3(filename, options, callback) {
31091
31091
  if (typeof options === "function") {
31092
31092
  callback = options;
@@ -31095,8 +31095,8 @@ var require_fetch = __commonJS({
31095
31095
  options = {};
31096
31096
  if (!callback)
31097
31097
  return asPromise(fetch3, this, filename, options);
31098
- if (!options.xhr && fs45 && fs45.readFile)
31099
- return fs45.readFile(filename, function fetchReadFileCallback(err, contents) {
31098
+ if (!options.xhr && fs46 && fs46.readFile)
31099
+ return fs46.readFile(filename, function fetchReadFileCallback(err, contents) {
31100
31100
  return err && typeof XMLHttpRequest !== "undefined" ? fetch3.xhr(filename, options, callback) : err ? callback(err) : callback(null, options.binary ? contents : contents.toString("utf8"));
31101
31101
  });
31102
31102
  return fetch3.xhr(filename, options, callback);
@@ -37370,7 +37370,7 @@ var require_util3 = __commonJS({
37370
37370
  "use strict";
37371
37371
  Object.defineProperty(exports2, "__esModule", { value: true });
37372
37372
  exports2.addCommonProtos = exports2.loadProtosWithOptionsSync = exports2.loadProtosWithOptions = void 0;
37373
- var fs45 = __require("fs");
37373
+ var fs46 = __require("fs");
37374
37374
  var path47 = __require("path");
37375
37375
  var Protobuf = require_protobufjs();
37376
37376
  function addIncludePathResolver(root, includePaths) {
@@ -37382,7 +37382,7 @@ var require_util3 = __commonJS({
37382
37382
  for (const directory of includePaths) {
37383
37383
  const fullPath = path47.join(directory, target);
37384
37384
  try {
37385
- fs45.accessSync(fullPath, fs45.constants.R_OK);
37385
+ fs46.accessSync(fullPath, fs46.constants.R_OK);
37386
37386
  return fullPath;
37387
37387
  } catch (err) {
37388
37388
  continue;
@@ -47204,7 +47204,7 @@ var require_certificate_provider = __commonJS({
47204
47204
  "use strict";
47205
47205
  Object.defineProperty(exports2, "__esModule", { value: true });
47206
47206
  exports2.FileWatcherCertificateProvider = void 0;
47207
- var fs45 = __require("fs");
47207
+ var fs46 = __require("fs");
47208
47208
  var logging = require_logging();
47209
47209
  var constants_1 = require_constants2();
47210
47210
  var util_1 = __require("util");
@@ -47212,7 +47212,7 @@ var require_certificate_provider = __commonJS({
47212
47212
  function trace2(text) {
47213
47213
  logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME2, text);
47214
47214
  }
47215
- var readFilePromise = (0, util_1.promisify)(fs45.readFile);
47215
+ var readFilePromise = (0, util_1.promisify)(fs46.readFile);
47216
47216
  var FileWatcherCertificateProvider = class {
47217
47217
  constructor(config2) {
47218
47218
  this.config = config2;
@@ -49124,7 +49124,7 @@ var require_otlp_grpc_env_configuration = __commonJS({
49124
49124
  var core_1 = require_src();
49125
49125
  var grpc_exporter_transport_1 = require_grpc_exporter_transport();
49126
49126
  var node_http_1 = (init_index_node_http(), __toCommonJS(index_node_http_exports));
49127
- var fs45 = __require("fs");
49127
+ var fs46 = __require("fs");
49128
49128
  var path47 = __require("path");
49129
49129
  var api_1 = (init_esm(), __toCommonJS(esm_exports));
49130
49130
  function fallbackIfNullishOrBlank(signalSpecific, nonSignalSpecific) {
@@ -49174,7 +49174,7 @@ var require_otlp_grpc_env_configuration = __commonJS({
49174
49174
  const filePath = fallbackIfNullishOrBlank(signalSpecificPath, nonSignalSpecificPath);
49175
49175
  if (filePath != null) {
49176
49176
  try {
49177
- return fs45.readFileSync(path47.resolve(process.cwd(), filePath));
49177
+ return fs46.readFileSync(path47.resolve(process.cwd(), filePath));
49178
49178
  } catch {
49179
49179
  api_1.diag.warn(warningMessage);
49180
49180
  return void 0;
@@ -59234,14 +59234,14 @@ var require_parser = __commonJS({
59234
59234
  case "scalar":
59235
59235
  case "single-quoted-scalar":
59236
59236
  case "double-quoted-scalar": {
59237
- const fs45 = this.flowScalar(this.type);
59237
+ const fs46 = this.flowScalar(this.type);
59238
59238
  if (atNextItem || it.value) {
59239
- map2.items.push({ start, key: fs45, sep: [] });
59239
+ map2.items.push({ start, key: fs46, sep: [] });
59240
59240
  this.onKeyLine = true;
59241
59241
  } else if (it.sep) {
59242
- this.stack.push(fs45);
59242
+ this.stack.push(fs46);
59243
59243
  } else {
59244
- Object.assign(it, { key: fs45, sep: [] });
59244
+ Object.assign(it, { key: fs46, sep: [] });
59245
59245
  this.onKeyLine = true;
59246
59246
  }
59247
59247
  return;
@@ -59369,13 +59369,13 @@ var require_parser = __commonJS({
59369
59369
  case "scalar":
59370
59370
  case "single-quoted-scalar":
59371
59371
  case "double-quoted-scalar": {
59372
- const fs45 = this.flowScalar(this.type);
59372
+ const fs46 = this.flowScalar(this.type);
59373
59373
  if (!it || it.value)
59374
- fc.items.push({ start: [], key: fs45, sep: [] });
59374
+ fc.items.push({ start: [], key: fs46, sep: [] });
59375
59375
  else if (it.sep)
59376
- this.stack.push(fs45);
59376
+ this.stack.push(fs46);
59377
59377
  else
59378
- Object.assign(it, { key: fs45, sep: [] });
59378
+ Object.assign(it, { key: fs46, sep: [] });
59379
59379
  return;
59380
59380
  }
59381
59381
  case "flow-map-end":
@@ -59691,7 +59691,7 @@ var require_FileConfigFactory = __commonJS({
59691
59691
  exports2.setLoggerProvider = exports2.getSeverity = exports2.setMeterProvider = exports2.getTemporalityPreference = exports2.setTracerProvider = exports2.setPropagator = exports2.setAttributeLimits = exports2.setResourceAttributes = exports2.parseConfigFile = exports2.hasValidConfigFile = exports2.FileConfigFactory = void 0;
59692
59692
  var core_1 = require_src();
59693
59693
  var configModel_1 = require_configModel();
59694
- var fs45 = __require("fs");
59694
+ var fs46 = __require("fs");
59695
59695
  var yaml = require_dist();
59696
59696
  var utils_1 = require_utils10();
59697
59697
  var commonModel_1 = require_commonModel();
@@ -59713,7 +59713,7 @@ var require_FileConfigFactory = __commonJS({
59713
59713
  function hasValidConfigFile() {
59714
59714
  const configFile = (0, core_1.getStringFromEnv)("OTEL_EXPERIMENTAL_CONFIG_FILE");
59715
59715
  if (configFile) {
59716
- if (!(configFile.endsWith(".yaml") || configFile.endsWith(".yml")) || !fs45.existsSync(configFile)) {
59716
+ if (!(configFile.endsWith(".yaml") || configFile.endsWith(".yml")) || !fs46.existsSync(configFile)) {
59717
59717
  api_1.diag.warn(`Config file ${configFile} set on OTEL_EXPERIMENTAL_CONFIG_FILE is not valid`);
59718
59718
  return false;
59719
59719
  }
@@ -59725,7 +59725,7 @@ var require_FileConfigFactory = __commonJS({
59725
59725
  function parseConfigFile(config2) {
59726
59726
  const supportedFileVersions = ["1.0-rc.3"];
59727
59727
  const configFile = (0, core_1.getStringFromEnv)("OTEL_EXPERIMENTAL_CONFIG_FILE") || "";
59728
- const file2 = fs45.readFileSync(configFile, "utf8");
59728
+ const file2 = fs46.readFileSync(configFile, "utf8");
59729
59729
  const parsedContent = yaml.parse(file2);
59730
59730
  if (parsedContent["file_format"] && supportedFileVersions.includes(parsedContent["file_format"])) {
59731
59731
  const disabled = (0, utils_1.getBooleanFromConfigFile)(parsedContent["disabled"]);
@@ -61436,7 +61436,7 @@ var require_instrumentation3 = __commonJS({
61436
61436
  Object.defineProperty(exports2, "__esModule", { value: true });
61437
61437
  exports2.AwsLambdaInstrumentation = exports2.AWS_HANDLER_STREAMING_RESPONSE = exports2.AWS_HANDLER_STREAMING_SYMBOL = exports2.lambdaMaxInitInMilliseconds = void 0;
61438
61438
  var path47 = __require("path");
61439
- var fs45 = __require("fs");
61439
+ var fs46 = __require("fs");
61440
61440
  var instrumentation_1 = require_src10();
61441
61441
  var api_1 = (init_esm(), __toCommonJS(esm_exports));
61442
61442
  var semantic_conventions_1 = (init_esm2(), __toCommonJS(esm_exports2));
@@ -61482,15 +61482,15 @@ var require_instrumentation3 = __commonJS({
61482
61482
  let filename = path47.resolve(taskRoot, moduleRoot, module3);
61483
61483
  if (!filename.endsWith(".js")) {
61484
61484
  try {
61485
- fs45.statSync(`${filename}.js`);
61485
+ fs46.statSync(`${filename}.js`);
61486
61486
  filename += ".js";
61487
61487
  } catch (e2) {
61488
61488
  try {
61489
- fs45.statSync(`${filename}.mjs`);
61489
+ fs46.statSync(`${filename}.mjs`);
61490
61490
  filename += ".mjs";
61491
61491
  } catch (e22) {
61492
61492
  try {
61493
- fs45.statSync(`${filename}.cjs`);
61493
+ fs46.statSync(`${filename}.cjs`);
61494
61494
  filename += ".cjs";
61495
61495
  } catch (e3) {
61496
61496
  this._diag.warn("No handler file was able to resolved with one of the known extensions for the file", filename);
@@ -66162,19 +66162,19 @@ var require_utils17 = __commonJS({
66162
66162
  }
66163
66163
  }
66164
66164
  exports2.splitTwoLevels = splitTwoLevels;
66165
- function indexFs(fs45, member) {
66165
+ function indexFs(fs46, member) {
66166
66166
  if (!member)
66167
66167
  throw new Error(JSON.stringify({ member }));
66168
66168
  const splitResult = splitTwoLevels(member);
66169
66169
  const [functionName1, functionName2] = splitResult;
66170
66170
  if (functionName2) {
66171
66171
  return {
66172
- objectToPatch: fs45[functionName1],
66172
+ objectToPatch: fs46[functionName1],
66173
66173
  functionNameToPatch: functionName2
66174
66174
  };
66175
66175
  } else {
66176
66176
  return {
66177
- objectToPatch: fs45,
66177
+ objectToPatch: fs46,
66178
66178
  functionNameToPatch: functionName1
66179
66179
  };
66180
66180
  }
@@ -66205,16 +66205,16 @@ var require_instrumentation12 = __commonJS({
66205
66205
  }
66206
66206
  init() {
66207
66207
  return [
66208
- new instrumentation_1.InstrumentationNodeModuleDefinition("fs", ["*"], (fs45) => {
66208
+ new instrumentation_1.InstrumentationNodeModuleDefinition("fs", ["*"], (fs46) => {
66209
66209
  for (const fName of constants_1.SYNC_FUNCTIONS) {
66210
- const { objectToPatch, functionNameToPatch } = (0, utils_1.indexFs)(fs45, fName);
66210
+ const { objectToPatch, functionNameToPatch } = (0, utils_1.indexFs)(fs46, fName);
66211
66211
  if ((0, instrumentation_1.isWrapped)(objectToPatch[functionNameToPatch])) {
66212
66212
  this._unwrap(objectToPatch, functionNameToPatch);
66213
66213
  }
66214
66214
  this._wrap(objectToPatch, functionNameToPatch, this._patchSyncFunction.bind(this, fName));
66215
66215
  }
66216
66216
  for (const fName of constants_1.CALLBACK_FUNCTIONS) {
66217
- const { objectToPatch, functionNameToPatch } = (0, utils_1.indexFs)(fs45, fName);
66217
+ const { objectToPatch, functionNameToPatch } = (0, utils_1.indexFs)(fs46, fName);
66218
66218
  if ((0, instrumentation_1.isWrapped)(objectToPatch[functionNameToPatch])) {
66219
66219
  this._unwrap(objectToPatch, functionNameToPatch);
66220
66220
  }
@@ -66225,30 +66225,30 @@ var require_instrumentation12 = __commonJS({
66225
66225
  this._wrap(objectToPatch, functionNameToPatch, this._patchCallbackFunction.bind(this, fName));
66226
66226
  }
66227
66227
  for (const fName of constants_1.PROMISE_FUNCTIONS) {
66228
- if ((0, instrumentation_1.isWrapped)(fs45.promises[fName])) {
66229
- this._unwrap(fs45.promises, fName);
66228
+ if ((0, instrumentation_1.isWrapped)(fs46.promises[fName])) {
66229
+ this._unwrap(fs46.promises, fName);
66230
66230
  }
66231
- this._wrap(fs45.promises, fName, this._patchPromiseFunction.bind(this, fName));
66231
+ this._wrap(fs46.promises, fName, this._patchPromiseFunction.bind(this, fName));
66232
66232
  }
66233
- return fs45;
66234
- }, (fs45) => {
66235
- if (fs45 === void 0)
66233
+ return fs46;
66234
+ }, (fs46) => {
66235
+ if (fs46 === void 0)
66236
66236
  return;
66237
66237
  for (const fName of constants_1.SYNC_FUNCTIONS) {
66238
- const { objectToPatch, functionNameToPatch } = (0, utils_1.indexFs)(fs45, fName);
66238
+ const { objectToPatch, functionNameToPatch } = (0, utils_1.indexFs)(fs46, fName);
66239
66239
  if ((0, instrumentation_1.isWrapped)(objectToPatch[functionNameToPatch])) {
66240
66240
  this._unwrap(objectToPatch, functionNameToPatch);
66241
66241
  }
66242
66242
  }
66243
66243
  for (const fName of constants_1.CALLBACK_FUNCTIONS) {
66244
- const { objectToPatch, functionNameToPatch } = (0, utils_1.indexFs)(fs45, fName);
66244
+ const { objectToPatch, functionNameToPatch } = (0, utils_1.indexFs)(fs46, fName);
66245
66245
  if ((0, instrumentation_1.isWrapped)(objectToPatch[functionNameToPatch])) {
66246
66246
  this._unwrap(objectToPatch, functionNameToPatch);
66247
66247
  }
66248
66248
  }
66249
66249
  for (const fName of constants_1.PROMISE_FUNCTIONS) {
66250
- if ((0, instrumentation_1.isWrapped)(fs45.promises[fName])) {
66251
- this._unwrap(fs45.promises, fName);
66250
+ if ((0, instrumentation_1.isWrapped)(fs46.promises[fName])) {
66251
+ this._unwrap(fs46.promises, fName);
66252
66252
  }
66253
66253
  }
66254
66254
  }),
@@ -79247,14 +79247,14 @@ var require_AwsBeanstalkDetector = __commonJS({
79247
79247
  var core_1 = require_src();
79248
79248
  var semantic_conventions_1 = (init_esm2(), __toCommonJS(esm_exports2));
79249
79249
  var semconv_1 = require_semconv30();
79250
- var fs45 = __require("fs");
79250
+ var fs46 = __require("fs");
79251
79251
  var util = __require("util");
79252
79252
  var DEFAULT_BEANSTALK_CONF_PATH = "/var/elasticbeanstalk/xray/environment.conf";
79253
79253
  var WIN_OS_BEANSTALK_CONF_PATH = "C:\\Program Files\\Amazon\\XRay\\environment.conf";
79254
79254
  var AwsBeanstalkDetector = class _AwsBeanstalkDetector {
79255
79255
  BEANSTALK_CONF_PATH;
79256
- static readFileAsync = util.promisify(fs45.readFile);
79257
- static fileAccessAsync = util.promisify(fs45.access);
79256
+ static readFileAsync = util.promisify(fs46.readFile);
79257
+ static fileAccessAsync = util.promisify(fs46.access);
79258
79258
  constructor() {
79259
79259
  if (process.platform === "win32") {
79260
79260
  this.BEANSTALK_CONF_PATH = WIN_OS_BEANSTALK_CONF_PATH;
@@ -79283,7 +79283,7 @@ var require_AwsBeanstalkDetector = __commonJS({
79283
79283
  */
79284
79284
  async _gatherData() {
79285
79285
  try {
79286
- await _AwsBeanstalkDetector.fileAccessAsync(this.BEANSTALK_CONF_PATH, fs45.constants.R_OK);
79286
+ await _AwsBeanstalkDetector.fileAccessAsync(this.BEANSTALK_CONF_PATH, fs46.constants.R_OK);
79287
79287
  const rawData = await _AwsBeanstalkDetector.readFileAsync(this.BEANSTALK_CONF_PATH, "utf8");
79288
79288
  const parsedData = JSON.parse(rawData);
79289
79289
  return {
@@ -79458,14 +79458,14 @@ var require_AwsEcsDetector = __commonJS({
79458
79458
  var semconv_1 = require_semconv30();
79459
79459
  var http3 = __require("http");
79460
79460
  var util = __require("util");
79461
- var fs45 = __require("fs");
79461
+ var fs46 = __require("fs");
79462
79462
  var os5 = __require("os");
79463
79463
  var HTTP_TIMEOUT_IN_MS = 1e3;
79464
79464
  var AwsEcsDetector = class _AwsEcsDetector {
79465
79465
  static CONTAINER_ID_LENGTH = 64;
79466
79466
  static CONTAINER_ID_LENGTH_MIN = 32;
79467
79467
  static DEFAULT_CGROUP_PATH = "/proc/self/cgroup";
79468
- static readFileAsync = util.promisify(fs45.readFile);
79468
+ static readFileAsync = util.promisify(fs46.readFile);
79469
79469
  detect() {
79470
79470
  const attributes = api_1.context.with((0, core_1.suppressTracing)(api_1.context.active()), () => this._getAttributes());
79471
79471
  return { attributes };
@@ -79669,7 +79669,7 @@ var require_AwsEksDetector = __commonJS({
79669
79669
  var core_1 = require_src();
79670
79670
  var semconv_1 = require_semconv30();
79671
79671
  var https2 = __require("https");
79672
- var fs45 = __require("fs");
79672
+ var fs46 = __require("fs");
79673
79673
  var util = __require("util");
79674
79674
  var api_2 = (init_esm(), __toCommonJS(esm_exports));
79675
79675
  var AwsEksDetector = class _AwsEksDetector {
@@ -79682,8 +79682,8 @@ var require_AwsEksDetector = __commonJS({
79682
79682
  DEFAULT_CGROUP_PATH = "/proc/self/cgroup";
79683
79683
  TIMEOUT_MS = 2e3;
79684
79684
  UTF8_UNICODE = "utf8";
79685
- static readFileAsync = util.promisify(fs45.readFile);
79686
- static fileAccessAsync = util.promisify(fs45.access);
79685
+ static readFileAsync = util.promisify(fs46.readFile);
79686
+ static fileAccessAsync = util.promisify(fs46.access);
79687
79687
  detect() {
79688
79688
  const dataPromise = api_1.context.with((0, core_1.suppressTracing)(api_1.context.active()), () => this._gatherData());
79689
79689
  const attrNames = [
@@ -80015,7 +80015,7 @@ var require_ContainerDetector = __commonJS({
80015
80015
  "use strict";
80016
80016
  Object.defineProperty(exports2, "__esModule", { value: true });
80017
80017
  exports2.containerDetector = exports2.ContainerDetector = void 0;
80018
- var fs45 = __require("fs");
80018
+ var fs46 = __require("fs");
80019
80019
  var util = __require("util");
80020
80020
  var api_1 = (init_esm(), __toCommonJS(esm_exports));
80021
80021
  var core_1 = require_src();
@@ -80028,7 +80028,7 @@ var require_ContainerDetector = __commonJS({
80028
80028
  UTF8_UNICODE = "utf8";
80029
80029
  HOSTNAME = "hostname";
80030
80030
  MARKING_PREFIX = ["containers", "overlay-containers"];
80031
- static readFileAsync = util.promisify(fs45.readFile);
80031
+ static readFileAsync = util.promisify(fs46.readFile);
80032
80032
  detect() {
80033
80033
  const attributes = {
80034
80034
  [semconv_1.ATTR_CONTAINER_ID]: this._getContainerIdWithSuppressedTracing()
@@ -91095,7 +91095,7 @@ var require_AzureAksDetector = __commonJS({
91095
91095
  "use strict";
91096
91096
  Object.defineProperty(exports2, "__esModule", { value: true });
91097
91097
  exports2.azureAksDetector = void 0;
91098
- var fs45 = __require("fs");
91098
+ var fs46 = __require("fs");
91099
91099
  var api_1 = (init_esm(), __toCommonJS(esm_exports));
91100
91100
  var semconv_1 = require_semconv33();
91101
91101
  var types_1 = require_types13();
@@ -91136,10 +91136,10 @@ var require_AzureAksDetector = __commonJS({
91136
91136
  }
91137
91137
  getAksMetadataFromFile() {
91138
91138
  try {
91139
- if (!fs45.existsSync(types_1.AKS_METADATA_FILE_PATH)) {
91139
+ if (!fs46.existsSync(types_1.AKS_METADATA_FILE_PATH)) {
91140
91140
  return void 0;
91141
91141
  }
91142
- const content = fs45.readFileSync(types_1.AKS_METADATA_FILE_PATH, "utf8");
91142
+ const content = fs46.readFileSync(types_1.AKS_METADATA_FILE_PATH, "utf8");
91143
91143
  const metadata = {};
91144
91144
  const lines = content.split("\n");
91145
91145
  for (const line of lines) {
@@ -92919,7 +92919,7 @@ var require_atomic_sleep = __commonJS({
92919
92919
  var require_sonic_boom = __commonJS({
92920
92920
  "node_modules/sonic-boom/index.js"(exports2, module2) {
92921
92921
  "use strict";
92922
- var fs45 = __require("fs");
92922
+ var fs46 = __require("fs");
92923
92923
  var EventEmitter = __require("events");
92924
92924
  var inherits = __require("util").inherits;
92925
92925
  var path47 = __require("path");
@@ -92976,20 +92976,20 @@ var require_sonic_boom = __commonJS({
92976
92976
  const mode = sonic.mode;
92977
92977
  if (sonic.sync) {
92978
92978
  try {
92979
- if (sonic.mkdir) fs45.mkdirSync(path47.dirname(file2), { recursive: true });
92980
- const fd = fs45.openSync(file2, flags, mode);
92979
+ if (sonic.mkdir) fs46.mkdirSync(path47.dirname(file2), { recursive: true });
92980
+ const fd = fs46.openSync(file2, flags, mode);
92981
92981
  fileOpened(null, fd);
92982
92982
  } catch (err) {
92983
92983
  fileOpened(err);
92984
92984
  throw err;
92985
92985
  }
92986
92986
  } else if (sonic.mkdir) {
92987
- fs45.mkdir(path47.dirname(file2), { recursive: true }, (err) => {
92987
+ fs46.mkdir(path47.dirname(file2), { recursive: true }, (err) => {
92988
92988
  if (err) return fileOpened(err);
92989
- fs45.open(file2, flags, mode, fileOpened);
92989
+ fs46.open(file2, flags, mode, fileOpened);
92990
92990
  });
92991
92991
  } else {
92992
- fs45.open(file2, flags, mode, fileOpened);
92992
+ fs46.open(file2, flags, mode, fileOpened);
92993
92993
  }
92994
92994
  }
92995
92995
  function SonicBoom(opts) {
@@ -93030,8 +93030,8 @@ var require_sonic_boom = __commonJS({
93030
93030
  this.flush = flushBuffer;
93031
93031
  this.flushSync = flushBufferSync;
93032
93032
  this._actualWrite = actualWriteBuffer;
93033
- fsWriteSync = () => fs45.writeSync(this.fd, this._writingBuf);
93034
- fsWrite = () => fs45.write(this.fd, this._writingBuf, this.release);
93033
+ fsWriteSync = () => fs46.writeSync(this.fd, this._writingBuf);
93034
+ fsWrite = () => fs46.write(this.fd, this._writingBuf, this.release);
93035
93035
  } else if (contentMode === void 0 || contentMode === kContentModeUtf8) {
93036
93036
  this._writingBuf = "";
93037
93037
  this.write = write2;
@@ -93040,15 +93040,15 @@ var require_sonic_boom = __commonJS({
93040
93040
  this._actualWrite = actualWrite;
93041
93041
  fsWriteSync = () => {
93042
93042
  if (Buffer.isBuffer(this._writingBuf)) {
93043
- return fs45.writeSync(this.fd, this._writingBuf);
93043
+ return fs46.writeSync(this.fd, this._writingBuf);
93044
93044
  }
93045
- return fs45.writeSync(this.fd, this._writingBuf, "utf8");
93045
+ return fs46.writeSync(this.fd, this._writingBuf, "utf8");
93046
93046
  };
93047
93047
  fsWrite = () => {
93048
93048
  if (Buffer.isBuffer(this._writingBuf)) {
93049
- return fs45.write(this.fd, this._writingBuf, this.release);
93049
+ return fs46.write(this.fd, this._writingBuf, this.release);
93050
93050
  }
93051
- return fs45.write(this.fd, this._writingBuf, "utf8", this.release);
93051
+ return fs46.write(this.fd, this._writingBuf, "utf8", this.release);
93052
93052
  };
93053
93053
  } else {
93054
93054
  throw new Error(`SonicBoom supports "${kContentModeUtf8}" and "${kContentModeBuffer}", but passed ${contentMode}`);
@@ -93105,7 +93105,7 @@ var require_sonic_boom = __commonJS({
93105
93105
  }
93106
93106
  }
93107
93107
  if (this._fsync) {
93108
- fs45.fsyncSync(this.fd);
93108
+ fs46.fsyncSync(this.fd);
93109
93109
  }
93110
93110
  const len = this._len;
93111
93111
  if (this._reopening) {
@@ -93219,7 +93219,7 @@ var require_sonic_boom = __commonJS({
93219
93219
  const onDrain = () => {
93220
93220
  if (!this._fsync) {
93221
93221
  try {
93222
- fs45.fsync(this.fd, (err) => {
93222
+ fs46.fsync(this.fd, (err) => {
93223
93223
  this._flushPending = false;
93224
93224
  cb(err);
93225
93225
  });
@@ -93321,7 +93321,7 @@ var require_sonic_boom = __commonJS({
93321
93321
  const fd = this.fd;
93322
93322
  this.once("ready", () => {
93323
93323
  if (fd !== this.fd) {
93324
- fs45.close(fd, (err) => {
93324
+ fs46.close(fd, (err) => {
93325
93325
  if (err) {
93326
93326
  return this.emit("error", err);
93327
93327
  }
@@ -93370,7 +93370,7 @@ var require_sonic_boom = __commonJS({
93370
93370
  buf = this._bufs[0];
93371
93371
  }
93372
93372
  try {
93373
- const n = Buffer.isBuffer(buf) ? fs45.writeSync(this.fd, buf) : fs45.writeSync(this.fd, buf, "utf8");
93373
+ const n = Buffer.isBuffer(buf) ? fs46.writeSync(this.fd, buf) : fs46.writeSync(this.fd, buf, "utf8");
93374
93374
  const releasedBufObj = releaseWritingBuf(buf, this._len, n);
93375
93375
  buf = releasedBufObj.writingBuf;
93376
93376
  this._len = releasedBufObj.len;
@@ -93386,7 +93386,7 @@ var require_sonic_boom = __commonJS({
93386
93386
  }
93387
93387
  }
93388
93388
  try {
93389
- fs45.fsyncSync(this.fd);
93389
+ fs46.fsyncSync(this.fd);
93390
93390
  } catch {
93391
93391
  }
93392
93392
  }
@@ -93407,7 +93407,7 @@ var require_sonic_boom = __commonJS({
93407
93407
  buf = mergeBuf(this._bufs[0], this._lens[0]);
93408
93408
  }
93409
93409
  try {
93410
- const n = fs45.writeSync(this.fd, buf);
93410
+ const n = fs46.writeSync(this.fd, buf);
93411
93411
  buf = buf.subarray(n);
93412
93412
  this._len = Math.max(this._len - n, 0);
93413
93413
  if (buf.length <= 0) {
@@ -93435,13 +93435,13 @@ var require_sonic_boom = __commonJS({
93435
93435
  this._writingBuf = this._writingBuf.length ? this._writingBuf : this._bufs.shift() || "";
93436
93436
  if (this.sync) {
93437
93437
  try {
93438
- const written = Buffer.isBuffer(this._writingBuf) ? fs45.writeSync(this.fd, this._writingBuf) : fs45.writeSync(this.fd, this._writingBuf, "utf8");
93438
+ const written = Buffer.isBuffer(this._writingBuf) ? fs46.writeSync(this.fd, this._writingBuf) : fs46.writeSync(this.fd, this._writingBuf, "utf8");
93439
93439
  release(null, written);
93440
93440
  } catch (err) {
93441
93441
  release(err);
93442
93442
  }
93443
93443
  } else {
93444
- fs45.write(this.fd, this._writingBuf, release);
93444
+ fs46.write(this.fd, this._writingBuf, release);
93445
93445
  }
93446
93446
  }
93447
93447
  function actualWriteBuffer() {
@@ -93450,7 +93450,7 @@ var require_sonic_boom = __commonJS({
93450
93450
  this._writingBuf = this._writingBuf.length ? this._writingBuf : mergeBuf(this._bufs.shift(), this._lens.shift());
93451
93451
  if (this.sync) {
93452
93452
  try {
93453
- const written = fs45.writeSync(this.fd, this._writingBuf);
93453
+ const written = fs46.writeSync(this.fd, this._writingBuf);
93454
93454
  release(null, written);
93455
93455
  } catch (err) {
93456
93456
  release(err);
@@ -93459,7 +93459,7 @@ var require_sonic_boom = __commonJS({
93459
93459
  if (kCopyBuffer) {
93460
93460
  this._writingBuf = Buffer.from(this._writingBuf);
93461
93461
  }
93462
- fs45.write(this.fd, this._writingBuf, release);
93462
+ fs46.write(this.fd, this._writingBuf, release);
93463
93463
  }
93464
93464
  }
93465
93465
  function actualClose(sonic) {
@@ -93475,12 +93475,12 @@ var require_sonic_boom = __commonJS({
93475
93475
  sonic._lens = [];
93476
93476
  assert2(typeof sonic.fd === "number", `sonic.fd must be a number, got ${typeof sonic.fd}`);
93477
93477
  try {
93478
- fs45.fsync(sonic.fd, closeWrapped);
93478
+ fs46.fsync(sonic.fd, closeWrapped);
93479
93479
  } catch {
93480
93480
  }
93481
93481
  function closeWrapped() {
93482
93482
  if (sonic.fd !== 1 && sonic.fd !== 2) {
93483
- fs45.close(sonic.fd, done);
93483
+ fs46.close(sonic.fd, done);
93484
93484
  } else {
93485
93485
  done();
93486
93486
  }
@@ -96472,9 +96472,9 @@ var require_pump = __commonJS({
96472
96472
  "node_modules/pump/index.js"(exports2, module2) {
96473
96473
  var once = require_once();
96474
96474
  var eos = require_end_of_stream();
96475
- var fs45;
96475
+ var fs46;
96476
96476
  try {
96477
- fs45 = __require("fs");
96477
+ fs46 = __require("fs");
96478
96478
  } catch (e2) {
96479
96479
  }
96480
96480
  var noop2 = function() {
@@ -96485,8 +96485,8 @@ var require_pump = __commonJS({
96485
96485
  };
96486
96486
  var isFS = function(stream) {
96487
96487
  if (!ancient) return false;
96488
- if (!fs45) return false;
96489
- return (stream instanceof (fs45.ReadStream || noop2) || stream instanceof (fs45.WriteStream || noop2)) && isFn(stream.close);
96488
+ if (!fs46) return false;
96489
+ return (stream instanceof (fs46.ReadStream || noop2) || stream instanceof (fs46.WriteStream || noop2)) && isFn(stream.close);
96490
96490
  };
96491
96491
  var isRequest2 = function(stream) {
96492
96492
  return stream.setHeader && isFn(stream.abort);
@@ -113905,8 +113905,8 @@ import fsSync from "node:fs";
113905
113905
  import path5 from "node:path";
113906
113906
  import { fileURLToPath as fileURLToPath3 } from "node:url";
113907
113907
  function getCurrentVersion() {
113908
- if ("0.2.21") {
113909
- return "0.2.21";
113908
+ if ("0.2.23") {
113909
+ return "0.2.23";
113910
113910
  }
113911
113911
  try {
113912
113912
  const pkgPath = path5.join(THIS_DIR, "..", "..", "package.json");
@@ -115213,7 +115213,9 @@ var init_labels = __esm({
115213
115213
  { name: "type:research", color: "0075CA" },
115214
115214
  { name: "type:infra", color: "5319E7" },
115215
115215
  { name: "needs-human", color: "d73a4a" },
115216
- { name: "approved", color: "0e8a16" }
115216
+ { name: "approved", color: "0e8a16" },
115217
+ { name: "decomposition:parent", color: "1d76db" },
115218
+ { name: "decomposition:child", color: "5319e7" }
115217
115219
  ];
115218
115220
  STEP_ROUTING_COLOR = "#d93f0b";
115219
115221
  NOTIFY_LABEL_PREFIX = "notify:";
@@ -117293,21 +117295,21 @@ var init_types4 = __esm({
117293
117295
  });
117294
117296
 
117295
117297
  // lib/github/file-event-store.ts
117296
- import fs27 from "node:fs/promises";
117298
+ import fs28 from "node:fs/promises";
117297
117299
  import path27 from "node:path";
117298
117300
  function encodeId(id) {
117299
117301
  return Buffer.from(id, "utf-8").toString("base64url");
117300
117302
  }
117301
117303
  async function atomicWriteJson(filePath, value) {
117302
- await fs27.mkdir(path27.dirname(filePath), { recursive: true });
117304
+ await fs28.mkdir(path27.dirname(filePath), { recursive: true });
117303
117305
  const tmpPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
117304
- await fs27.writeFile(tmpPath, JSON.stringify(value, null, 2) + "\n", "utf-8");
117305
- await fs27.rename(tmpPath, filePath);
117306
+ await fs28.writeFile(tmpPath, JSON.stringify(value, null, 2) + "\n", "utf-8");
117307
+ await fs28.rename(tmpPath, filePath);
117306
117308
  }
117307
117309
  async function writeNewJsonExclusive(filePath, value) {
117308
- await fs27.mkdir(path27.dirname(filePath), { recursive: true });
117310
+ await fs28.mkdir(path27.dirname(filePath), { recursive: true });
117309
117311
  try {
117310
- await fs27.writeFile(filePath, JSON.stringify(value, null, 2) + "\n", {
117312
+ await fs28.writeFile(filePath, JSON.stringify(value, null, 2) + "\n", {
117311
117313
  encoding: "utf-8",
117312
117314
  flag: "wx"
117313
117315
  });
@@ -117319,7 +117321,7 @@ async function writeNewJsonExclusive(filePath, value) {
117319
117321
  }
117320
117322
  async function readJsonFile(filePath) {
117321
117323
  try {
117322
- const raw = await fs27.readFile(filePath, "utf-8");
117324
+ const raw = await fs28.readFile(filePath, "utf-8");
117323
117325
  return JSON.parse(raw);
117324
117326
  } catch (error48) {
117325
117327
  if (error48?.code === "ENOENT") return null;
@@ -117378,7 +117380,7 @@ var init_file_event_store = __esm({
117378
117380
  const dir = this.storeDir;
117379
117381
  let entries = [];
117380
117382
  try {
117381
- entries = await fs27.readdir(dir);
117383
+ entries = await fs28.readdir(dir);
117382
117384
  } catch (error48) {
117383
117385
  if (error48?.code === "ENOENT") return [];
117384
117386
  throw error48;
@@ -117396,7 +117398,7 @@ var init_file_event_store = __esm({
117396
117398
  const dir = this.storeDir;
117397
117399
  let entries = [];
117398
117400
  try {
117399
- entries = await fs27.readdir(dir);
117401
+ entries = await fs28.readdir(dir);
117400
117402
  } catch (error48) {
117401
117403
  if (error48?.code === "ENOENT") return [];
117402
117404
  throw error48;
@@ -117466,14 +117468,14 @@ var init_file_event_store = __esm({
117466
117468
  async save(run) {
117467
117469
  const validated = fabricaRunSchema.parse(run);
117468
117470
  try {
117469
- const entries = await fs27.readdir(this.storeDir);
117471
+ const entries = await fs28.readdir(this.storeDir);
117470
117472
  for (const entry of entries) {
117471
117473
  const filePath = path27.join(this.storeDir, entry);
117472
117474
  const existing = await readJsonFile(filePath);
117473
117475
  if (!existing) continue;
117474
117476
  const parsed = fabricaRunSchema.parse(existing);
117475
117477
  if (parsed.runId !== validated.runId && parsed.installationId === validated.installationId && parsed.repositoryId === validated.repositoryId && parsed.prNumber === validated.prNumber) {
117476
- await fs27.rm(filePath, { force: true });
117478
+ await fs28.rm(filePath, { force: true });
117477
117479
  }
117478
117480
  }
117479
117481
  } catch (error48) {
@@ -117489,7 +117491,7 @@ var init_file_event_store = __esm({
117489
117491
  const dir = this.storeDir;
117490
117492
  let entries = [];
117491
117493
  try {
117492
- entries = await fs27.readdir(dir);
117494
+ entries = await fs28.readdir(dir);
117493
117495
  } catch (error48) {
117494
117496
  if (error48?.code === "ENOENT") return [];
117495
117497
  throw error48;
@@ -117510,7 +117512,7 @@ var init_file_event_store = __esm({
117510
117512
  });
117511
117513
 
117512
117514
  // lib/github/sqlite-event-store.ts
117513
- import fs28 from "node:fs/promises";
117515
+ import fs29 from "node:fs/promises";
117514
117516
  import path28 from "node:path";
117515
117517
  function asNumber(value) {
117516
117518
  if (typeof value === "number" && Number.isFinite(value)) return value;
@@ -117569,7 +117571,7 @@ async function loadSqliteModule() {
117569
117571
  return loaded;
117570
117572
  }
117571
117573
  async function openDatabase(dbPath) {
117572
- await fs28.mkdir(path28.dirname(dbPath), { recursive: true });
117574
+ await fs29.mkdir(path28.dirname(dbPath), { recursive: true });
117573
117575
  const sqlite = await loadSqliteModule();
117574
117576
  const db = new sqlite.DatabaseSync(dbPath);
117575
117577
  db.exec(`
@@ -118183,7 +118185,7 @@ var init_extract_json = __esm({
118183
118185
  });
118184
118186
 
118185
118187
  // lib/intake/lib/runtime-paths.ts
118186
- import fs30 from "node:fs";
118188
+ import fs31 from "node:fs";
118187
118189
  import fsp from "node:fs/promises";
118188
118190
  import os4 from "node:os";
118189
118191
  import path30 from "node:path";
@@ -118191,7 +118193,7 @@ import { fileURLToPath as fileURLToPath4 } from "node:url";
118191
118193
  function isUsableFile(candidate) {
118192
118194
  if (!candidate) return false;
118193
118195
  try {
118194
- return fs30.existsSync(candidate) && fs30.statSync(candidate).isFile();
118196
+ return fs31.existsSync(candidate) && fs31.statSync(candidate).isFile();
118195
118197
  } catch {
118196
118198
  return false;
118197
118199
  }
@@ -118199,7 +118201,7 @@ function isUsableFile(candidate) {
118199
118201
  function isUsableDir(candidate) {
118200
118202
  if (!candidate) return false;
118201
118203
  try {
118202
- return fs30.existsSync(candidate) && fs30.statSync(candidate).isDirectory();
118204
+ return fs31.existsSync(candidate) && fs31.statSync(candidate).isDirectory();
118203
118205
  } catch {
118204
118206
  return false;
118205
118207
  }
@@ -118207,7 +118209,7 @@ function isUsableDir(candidate) {
118207
118209
  function findNewestNvmOpenClaw(homeDir, env) {
118208
118210
  const versionsDir = path30.join(homeDir, ".nvm", "versions", "node");
118209
118211
  try {
118210
- const versions = fs30.readdirSync(versionsDir).sort().reverse();
118212
+ const versions = fs31.readdirSync(versionsDir).sort().reverse();
118211
118213
  for (const version2 of versions) {
118212
118214
  const candidate = path30.join(versionsDir, version2, "bin", "openclaw");
118213
118215
  if (isUsableFile(candidate)) return candidate;
@@ -118618,9 +118620,9 @@ function fallbackSpecData(type, rawIdea) {
118618
118620
  default:
118619
118621
  base.scope_v1 = deriveFeatureScopeFromRawIdea(rawIdea);
118620
118622
  base.acceptance_criteria = [
118621
- "The primary workflow works end to end as requested",
118622
- "Role, validation, or delivery constraints from the request are enforced",
118623
- "The asynchronous/background behavior works for the main operational path"
118623
+ "Allows operators to complete the primary workflow end to end as requested",
118624
+ "Validates and enforces the role, permission, or delivery constraints described in the request",
118625
+ "Processes the asynchronous or background behavior required for the main operational path"
118624
118626
  ];
118625
118627
  }
118626
118628
  return base;
@@ -122062,6 +122064,37 @@ function getRoleWorker(project, role) {
122062
122064
  function getIssueRuntime(project, issueId) {
122063
122065
  return project.issueRuntime?.[String(issueId)];
122064
122066
  }
122067
+ function isParentIssue(project, issueId) {
122068
+ const runtime = getIssueRuntime(project, issueId);
122069
+ return !!(runtime && ((runtime.childIssueIds?.length ?? 0) > 0 || runtime.decompositionMode === "parent_child"));
122070
+ }
122071
+ function isChildIssue(project, issueId) {
122072
+ const runtime = getIssueRuntime(project, issueId);
122073
+ return runtime?.parentIssueId != null;
122074
+ }
122075
+ function getParentIssueRuntime(project, issueId) {
122076
+ const runtime = getIssueRuntime(project, issueId);
122077
+ if (!runtime?.parentIssueId) return void 0;
122078
+ return getIssueRuntime(project, runtime.parentIssueId);
122079
+ }
122080
+ function getChildIssueRuntimes(project, issueId) {
122081
+ const runtime = getIssueRuntime(project, issueId);
122082
+ return (runtime?.childIssueIds ?? []).map((childIssueId) => ({
122083
+ issueId: childIssueId,
122084
+ runtime: getIssueRuntime(project, childIssueId)
122085
+ }));
122086
+ }
122087
+ function getDependencyIssueRuntimes(project, issueId) {
122088
+ const runtime = getIssueRuntime(project, issueId);
122089
+ return (runtime?.dependencyIssueIds ?? []).map((dependencyIssueId) => ({
122090
+ issueId: dependencyIssueId,
122091
+ runtime: getIssueRuntime(project, dependencyIssueId)
122092
+ }));
122093
+ }
122094
+ function isIssueExecutionComplete(project, issueId) {
122095
+ const runtime = getIssueRuntime(project, issueId);
122096
+ return !!(runtime?.decompositionStatus === "completed" || runtime?.artifactOfRecord?.mergedAt || runtime?.sessionCompletedAt);
122097
+ }
122065
122098
  async function updateSlot(workspaceDir, slugOrChannelId, role, level, slotIndex, updates) {
122066
122099
  const { data } = await withProjectsMutation(workspaceDir, (data2) => {
122067
122100
  const slug = resolveProjectSlug(data2, slugOrChannelId);
@@ -122226,7 +122259,22 @@ async function clearIssueRuntime(workspaceDir, slugOrChannelId, issueId) {
122226
122259
  }
122227
122260
  const project = data2.projects[slug];
122228
122261
  if (project.issueRuntime) {
122229
- delete project.issueRuntime[String(issueId)];
122262
+ const key = String(issueId);
122263
+ const existing = project.issueRuntime[key];
122264
+ const preserved = existing ? {
122265
+ parentIssueId: existing.parentIssueId ?? null,
122266
+ childIssueIds: existing.childIssueIds,
122267
+ dependencyIssueIds: existing.dependencyIssueIds,
122268
+ decompositionMode: existing.decompositionMode ?? null,
122269
+ decompositionStatus: existing.parentIssueId ? "completed" : existing.decompositionStatus ?? null,
122270
+ sessionCompletedAt: existing.sessionCompletedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
122271
+ artifactOfRecord: existing.artifactOfRecord ?? null
122272
+ } : null;
122273
+ if (preserved && (preserved.parentIssueId != null || (preserved.childIssueIds?.length ?? 0) > 0 || (preserved.dependencyIssueIds?.length ?? 0) > 0 || preserved.decompositionMode != null)) {
122274
+ project.issueRuntime[key] = preserved;
122275
+ } else {
122276
+ delete project.issueRuntime[key];
122277
+ }
122230
122278
  }
122231
122279
  });
122232
122280
  return data;
@@ -123104,11 +123152,11 @@ var GitLabProvider = class {
123104
123152
  const token = tokenRaw.stdout.trim();
123105
123153
  if (!token) return null;
123106
123154
  const os5 = await import("node:os");
123107
- const fs45 = await import("node:fs/promises");
123155
+ const fs46 = await import("node:fs/promises");
123108
123156
  const path47 = await import("node:path");
123109
- const tmpDir = await fs45.mkdtemp(path47.join(os5.tmpdir(), "fabrica-upload-"));
123157
+ const tmpDir = await fs46.mkdtemp(path47.join(os5.tmpdir(), "fabrica-upload-"));
123110
123158
  const tmpFile = path47.join(tmpDir, file2.filename);
123111
- await fs45.writeFile(tmpFile, file2.buffer);
123159
+ await fs46.writeFile(tmpFile, file2.buffer);
123112
123160
  try {
123113
123161
  const apiBase = webUrl.replace(/\/[^/]+\/[^/]+\/?$/, "");
123114
123162
  const result = await this.runCommand(
@@ -123130,9 +123178,9 @@ var GitLabProvider = class {
123130
123178
  if (parsed.url) return `${webUrl}${parsed.url}`;
123131
123179
  return null;
123132
123180
  } finally {
123133
- await fs45.unlink(tmpFile).catch(() => {
123181
+ await fs46.unlink(tmpFile).catch(() => {
123134
123182
  });
123135
- await fs45.rmdir(tmpDir).catch(() => {
123183
+ await fs46.rmdir(tmpDir).catch(() => {
123136
123184
  });
123137
123185
  }
123138
123186
  } catch {
@@ -125007,12 +125055,12 @@ function prLink(url2) {
125007
125055
  function buildMessage(event) {
125008
125056
  switch (event.type) {
125009
125057
  case "workerStart": {
125010
- const action = event.sessionAction === "spawn" ? "\u{1F680} Started" : "\u25B6\uFE0F Resumed";
125058
+ const semanticAction = event.dispatchSemantic === "feedback_redispatch" ? "\u{1F501} Re-dispatched after feedback" : event.dispatchSemantic === "recovery_redispatch" ? "\u267B\uFE0F Re-dispatched after recovery" : event.dispatchSemantic === "bootstrap_initial_dispatch" ? "\u{1F9F1} Started from bootstrap" : event.sessionAction === "spawn" ? "\u{1F680} Started" : "\u25B6\uFE0F Resumed";
125011
125059
  const worker = formatWorkerString(event.role, {
125012
125060
  name: event.name,
125013
125061
  level: event.level
125014
125062
  });
125015
- let msg = `${action} ${worker} on #${event.issueId}: ${event.issueTitle}
125063
+ let msg = `${semanticAction} ${worker} on #${event.issueId}: ${event.issueTitle}
125016
125064
  \u{1F517} [Issue #${event.issueId}](${event.issueUrl})`;
125017
125065
  if (event.modelDowngraded && event.originalModel && event.effectiveModel) {
125018
125066
  msg += `
@@ -125457,6 +125505,45 @@ init_audit();
125457
125505
 
125458
125506
  // lib/services/queue-scan.ts
125459
125507
  init_roles();
125508
+
125509
+ // lib/services/family-scheduler.ts
125510
+ function countActiveSiblingChildren(project, parentIssueId, currentIssueId) {
125511
+ const activeIssueIds = /* @__PURE__ */ new Set();
125512
+ for (const roleWorker of Object.values(project.workers ?? {})) {
125513
+ for (const slots of Object.values(roleWorker.levels ?? {})) {
125514
+ for (const slot of slots) {
125515
+ if (!slot.active || !slot.issueId) continue;
125516
+ const activeIssueId = Number(slot.issueId);
125517
+ if (!Number.isFinite(activeIssueId) || activeIssueId === currentIssueId) continue;
125518
+ activeIssueIds.add(activeIssueId);
125519
+ }
125520
+ }
125521
+ }
125522
+ return [...activeIssueIds].filter((issueId) => getIssueRuntime(project, issueId)?.parentIssueId === parentIssueId).length;
125523
+ }
125524
+ function getFamilyDispatchBlockReason(project, issueId, role) {
125525
+ const runtime = getIssueRuntime(project, issueId);
125526
+ if (isParentIssue(project, issueId)) {
125527
+ return role === "developer" ? "family_parent_coordinator_only" : "family_parent_not_executable";
125528
+ }
125529
+ if (isChildIssue(project, issueId)) {
125530
+ const parentRuntime = getParentIssueRuntime(project, issueId);
125531
+ if (parentRuntime?.decompositionStatus === "draft") return "family_parent_decomposition_draft";
125532
+ if (parentRuntime?.decompositionStatus === "blocked") return "family_parent_blocked";
125533
+ if (parentRuntime?.decompositionStatus === "completed") return "family_parent_completed";
125534
+ if (!runtime?.parentIssueId) return "family_child_missing_parent_binding";
125535
+ const maxParallelChildren = parentRuntime?.maxParallelChildren ?? null;
125536
+ if (maxParallelChildren && maxParallelChildren > 0) {
125537
+ const activeSiblingChildren = countActiveSiblingChildren(project, runtime.parentIssueId, issueId);
125538
+ if (activeSiblingChildren >= maxParallelChildren) return `family_parallel_limit_reached:${maxParallelChildren}`;
125539
+ }
125540
+ const incompleteDependencies = getDependencyIssueRuntimes(project, issueId).filter((dependency) => !isIssueExecutionComplete(project, dependency.issueId)).map((dependency) => dependency.issueId);
125541
+ if (incompleteDependencies.length > 0) return `family_child_dependencies_pending:${incompleteDependencies.join(",")}`;
125542
+ }
125543
+ return null;
125544
+ }
125545
+
125546
+ // lib/services/queue-scan.ts
125460
125547
  init_workflow();
125461
125548
  function detectRoleLevelFromLabels(labels) {
125462
125549
  for (const label of labels) {
@@ -125476,19 +125563,144 @@ function detectStepRouting(labels, step) {
125476
125563
  const match = labels.find((l) => l.toLowerCase().startsWith(prefix));
125477
125564
  return match ? match.slice(prefix.length).toLowerCase() : null;
125478
125565
  }
125479
- async function findNextIssueForRole(provider, role, workflow, instanceName) {
125566
+ async function findNextIssueForRole(provider, role, workflow, instanceName, project) {
125480
125567
  const labels = getQueueLabels(workflow, role);
125481
125568
  for (const label of labels) {
125482
125569
  try {
125483
125570
  const issues = await provider.listIssuesByLabel(label);
125484
125571
  const eligible = instanceName ? issues.filter((i2) => isOwnedByOrUnclaimed(i2.labels, instanceName)) : issues;
125485
- if (eligible.length > 0) return { issue: eligible[eligible.length - 1], label };
125572
+ for (let index = eligible.length - 1; index >= 0; index -= 1) {
125573
+ const issue2 = eligible[index];
125574
+ const blockReason = project ? getFamilyDispatchBlockReason(project, issue2.iid, role) : null;
125575
+ if (!blockReason) return { issue: issue2, label };
125576
+ }
125486
125577
  } catch {
125487
125578
  }
125488
125579
  }
125489
125580
  return null;
125490
125581
  }
125491
125582
 
125583
+ // lib/services/parent-lifecycle.ts
125584
+ function getTerminalLabel(workflow) {
125585
+ return Object.values(workflow.states).find((state) => state.type === "terminal")?.label ?? null;
125586
+ }
125587
+ function getCurrentWorkflowLabel(issueLabels, workflow) {
125588
+ const labels = new Set(issueLabels);
125589
+ return Object.values(workflow.states).find((state) => labels.has(state.label))?.label ?? null;
125590
+ }
125591
+ function sameIds(a, b) {
125592
+ const left = [...a ?? []].sort((x2, y) => x2 - y);
125593
+ const right = [...b ?? []].sort((x2, y) => x2 - y);
125594
+ return left.length === right.length && left.every((value, index) => value === right[index]);
125595
+ }
125596
+ function resolveChildRollupState(runtime) {
125597
+ if (!runtime) return "pending_without_runtime";
125598
+ if (runtime.decompositionStatus === "blocked") return "blocked";
125599
+ if (runtime.artifactOfRecord?.mergedAt) return "completed_via_artifact";
125600
+ if (runtime.sessionCompletedAt) return "completed_via_session";
125601
+ if (runtime.currentPrUrl && runtime.currentPrState && runtime.currentPrState !== "merged" && runtime.currentPrState !== "closed") {
125602
+ return "pending_with_open_pr";
125603
+ }
125604
+ if (runtime.currentPrUrl) return "pending_with_pr";
125605
+ return "pending_without_pr";
125606
+ }
125607
+ function formatChildRollupLine(child) {
125608
+ const runtime = child.runtime;
125609
+ const state = resolveChildRollupState(runtime);
125610
+ const prUrl = runtime?.artifactOfRecord?.url ?? runtime?.currentPrUrl ?? null;
125611
+ const mergedAt = runtime?.artifactOfRecord?.mergedAt ?? null;
125612
+ const headSha = runtime?.artifactOfRecord?.headSha ?? runtime?.lastHeadSha ?? null;
125613
+ const extras = [
125614
+ prUrl ? `PR: ${prUrl}` : null,
125615
+ mergedAt ? `mergedAt: ${mergedAt}` : null,
125616
+ headSha ? `headSha: ${headSha}` : null
125617
+ ].filter(Boolean);
125618
+ return `- #${child.issueId} \u2014 ${state}${extras.length > 0 ? ` \u2014 ${extras.join(" \u2014 ")}` : ""}`;
125619
+ }
125620
+ var PARENT_ROLLUP_START = "<!-- fabrica:parent-rollup:start -->";
125621
+ var PARENT_ROLLUP_END = "<!-- fabrica:parent-rollup:end -->";
125622
+ function buildParentRollupComment(status, completedChildIds, blockedChildIds, children) {
125623
+ const allChildIds = children.map((child) => child.issueId);
125624
+ const pendingChildIds = allChildIds.filter((id) => !completedChildIds.includes(id) && !blockedChildIds.includes(id));
125625
+ return [
125626
+ "## Parent Rollup",
125627
+ `- Status: ${status}`,
125628
+ `- Completed children (${completedChildIds.length}/${allChildIds.length}): ${completedChildIds.length > 0 ? completedChildIds.map((id) => `#${id}`).join(", ") : "none"}`,
125629
+ `- Pending children: ${pendingChildIds.length > 0 ? pendingChildIds.map((id) => `#${id}`).join(", ") : "none"}`,
125630
+ `- Blocked children: ${blockedChildIds.length > 0 ? blockedChildIds.map((id) => `#${id}`).join(", ") : "none"}`,
125631
+ "",
125632
+ "### Child Status",
125633
+ ...children.map((child) => formatChildRollupLine(child))
125634
+ ].join("\n");
125635
+ }
125636
+ function upsertParentRollupBlock(body, rollup) {
125637
+ const current = body ?? "";
125638
+ const block = `${PARENT_ROLLUP_START}
125639
+ ${rollup}
125640
+ ${PARENT_ROLLUP_END}`;
125641
+ const pattern = new RegExp(`${PARENT_ROLLUP_START}[\\s\\S]*?${PARENT_ROLLUP_END}`, "m");
125642
+ if (pattern.test(current)) {
125643
+ return current.replace(pattern, block);
125644
+ }
125645
+ return `${current.trimEnd()}${current.trim().length > 0 ? "\n\n" : ""}${block}`;
125646
+ }
125647
+ async function reconcileParentLifecycleForIssue(opts) {
125648
+ const project = await loadProjectBySlug(opts.workspaceDir, opts.projectSlug);
125649
+ if (!project || !isChildIssue(project, opts.issueId)) return;
125650
+ const childRuntime = getIssueRuntime(project, opts.issueId);
125651
+ const parentRuntime = getParentIssueRuntime(project, opts.issueId);
125652
+ const parentIssueId = childRuntime?.parentIssueId;
125653
+ if (!parentIssueId || !parentRuntime) return;
125654
+ const children = getChildIssueRuntimes(project, parentIssueId);
125655
+ if (children.length === 0) return;
125656
+ const completedChildIds = children.filter((child) => isIssueExecutionComplete(project, child.issueId)).map((child) => child.issueId);
125657
+ const blockedChildIds = children.filter((child) => child.runtime?.decompositionStatus === "blocked").map((child) => child.issueId);
125658
+ const hasBlockedChild = blockedChildIds.length > 0;
125659
+ const allChildrenComplete = completedChildIds.length === children.length;
125660
+ const targetStatus = allChildrenComplete ? "completed" : hasBlockedChild ? "blocked" : "active";
125661
+ const statusChanged = parentRuntime.decompositionStatus !== targetStatus;
125662
+ const completedChanged = !sameIds(parentRuntime.completedChildIssueIds, completedChildIds);
125663
+ const blockedChanged = !sameIds(parentRuntime.blockedChildIssueIds, blockedChildIds);
125664
+ const shouldUpdateRuntime = statusChanged || completedChanged || blockedChanged;
125665
+ const shouldCommentRollup = statusChanged || blockedChanged;
125666
+ if (shouldUpdateRuntime) {
125667
+ await updateIssueRuntime(opts.workspaceDir, opts.projectSlug, parentIssueId, {
125668
+ decompositionStatus: targetStatus,
125669
+ completedChildIssueIds: completedChildIds,
125670
+ blockedChildIssueIds: blockedChildIds,
125671
+ lastParentRollupAt: (/* @__PURE__ */ new Date()).toISOString()
125672
+ }).catch(() => {
125673
+ });
125674
+ }
125675
+ const parentIssue = await opts.provider.getIssue(parentIssueId).catch(() => null);
125676
+ if (!parentIssue) return;
125677
+ const rollup = buildParentRollupComment(targetStatus, completedChildIds, blockedChildIds, children);
125678
+ if (shouldUpdateRuntime) {
125679
+ await opts.provider.editIssue(parentIssueId, {
125680
+ body: upsertParentRollupBlock(parentIssue.description, rollup)
125681
+ }).catch(() => {
125682
+ });
125683
+ if (shouldCommentRollup) {
125684
+ await opts.provider.addComment(parentIssueId, rollup).catch(() => {
125685
+ });
125686
+ }
125687
+ }
125688
+ if (!allChildrenComplete) return;
125689
+ const terminalLabel = getTerminalLabel(opts.workflow);
125690
+ const currentLabel = getCurrentWorkflowLabel(parentIssue.labels, opts.workflow);
125691
+ if (terminalLabel && currentLabel && currentLabel !== terminalLabel && !parentIssue.labels.includes(terminalLabel)) {
125692
+ await opts.provider.transitionLabel(parentIssueId, currentLabel, terminalLabel).catch(() => {
125693
+ });
125694
+ }
125695
+ await opts.provider.addComment(parentIssueId, [
125696
+ "\u2705 Parent coordination complete.",
125697
+ "",
125698
+ "All child issues in this decomposition family have completed execution.",
125699
+ ...children.map((child) => formatChildRollupLine(child))
125700
+ ].join("\n")).catch(() => {
125701
+ });
125702
+ }
125703
+
125492
125704
  // lib/services/pipeline.ts
125493
125705
  init_workflow();
125494
125706
  init_context3();
@@ -125765,7 +125977,33 @@ async function executeCompletion(opts) {
125765
125977
  break;
125766
125978
  }
125767
125979
  }
125980
+ if (issueRuntime?.parentIssueId) {
125981
+ const childDecompositionStatus = effectiveResult === "blocked" || effectiveResult === "refine" || effectiveResult === "fail_infra" ? "blocked" : effectiveResult === "fail" || effectiveResult === "reject" ? "active" : issueRuntime.decompositionStatus;
125982
+ if (childDecompositionStatus !== issueRuntime.decompositionStatus) {
125983
+ await updateIssueRuntime(workspaceDir, projectSlug, issueId, {
125984
+ decompositionStatus: childDecompositionStatus
125985
+ }).catch(() => {
125986
+ });
125987
+ }
125988
+ }
125989
+ if (issueRuntime?.parentIssueId) {
125990
+ const childDecompositionStatus = effectiveResult === "blocked" || effectiveResult === "refine" || effectiveResult === "fail_infra" ? "blocked" : effectiveResult === "fail" || effectiveResult === "reject" ? "active" : issueRuntime.decompositionStatus;
125991
+ if (childDecompositionStatus !== issueRuntime.decompositionStatus) {
125992
+ await updateIssueRuntime(workspaceDir, projectSlug, issueId, {
125993
+ decompositionStatus: childDecompositionStatus
125994
+ }).catch(() => {
125995
+ });
125996
+ }
125997
+ }
125768
125998
  await deactivateWorker(workspaceDir, projectSlug, role, { level: opts.level, slotIndex: opts.slotIndex, issueId: String(issueId) });
125999
+ await reconcileParentLifecycleForIssue({
126000
+ workspaceDir,
126001
+ projectSlug,
126002
+ issueId,
126003
+ provider,
126004
+ workflow
126005
+ }).catch(() => {
126006
+ });
125769
126007
  notify(
125770
126008
  {
125771
126009
  type: "workerComplete",
@@ -126138,7 +126376,7 @@ function validateCanonicalQaEvidence(body) {
126138
126376
  }
126139
126377
  function formatQaEvidenceValidationFailure(validation, actor) {
126140
126378
  const intro = actor === "developer" ? "Cannot mark work_finish(done) with invalid QA Evidence in the PR body." : "Cannot approve review with invalid QA Evidence in the PR body.";
126141
- const guidance = actor === "developer" ? 'Replace the existing "## QA Evidence" section with fresh sanitized output from scripts/qa.sh (exactly one section, Exit code: 0), then call work_finish again.' : 'Reject the PR and instruct the developer to replace the existing "## QA Evidence" section in the PR body with fresh sanitized output from scripts/qa.sh (exactly one section, Exit code: 0).';
126379
+ const guidance = actor === "developer" ? 'Replace the existing "## QA Evidence" section with fresh sanitized output from scripts/qa.sh (exactly one section, Exit code: 0), then call work_finish again. Do not rewrite or weaken scripts/qa.sh into ad-hoc scenario checks \u2014 preserve the canonical lint/types/security/tests/coverage gates and fix the underlying code or project setup instead.' : 'Reject the PR and instruct the developer to replace the existing "## QA Evidence" section in the PR body with fresh sanitized output from scripts/qa.sh (exactly one section, Exit code: 0). Do not accept ad-hoc scenario scripts or weakened QA gates in place of the canonical lint/types/security/tests/coverage contract.';
126142
126380
  const allIssues = [...validation.problems, ...validation.errors];
126143
126381
  return `${intro}
126144
126382
 
@@ -126826,13 +127064,17 @@ function createTaskCreateTool(ctx) {
126826
127064
  });
126827
127065
  if (parentIssueId) {
126828
127066
  await updateIssueRuntime(workspaceDir, project.slug, issue2.iid, {
126829
- parentIssueId
127067
+ parentIssueId,
127068
+ decompositionMode: "none",
127069
+ decompositionStatus: null
126830
127070
  }).catch(() => {
126831
127071
  });
126832
127072
  const parentRuntime = project.issueRuntime?.[String(parentIssueId)] ?? {};
126833
127073
  const previousChildren = Array.isArray(parentRuntime.childIssueIds) ? parentRuntime.childIssueIds : [];
126834
127074
  await updateIssueRuntime(workspaceDir, project.slug, parentIssueId, {
126835
- childIssueIds: [.../* @__PURE__ */ new Set([...previousChildren, issue2.iid])]
127075
+ childIssueIds: [.../* @__PURE__ */ new Set([...previousChildren, issue2.iid])],
127076
+ decompositionMode: "parent_child",
127077
+ decompositionStatus: "active"
126836
127078
  }).catch(() => {
126837
127079
  });
126838
127080
  provider.addComment(
@@ -127716,6 +127958,7 @@ function createTaskOwnerTool(ctx) {
127716
127958
 
127717
127959
  // lib/dispatch/index.ts
127718
127960
  init_audit();
127961
+ import fs22 from "node:fs/promises";
127719
127962
  import { randomUUID as randomUUID2 } from "node:crypto";
127720
127963
  init_roles();
127721
127964
  init_workflow();
@@ -128136,6 +128379,13 @@ init_roles();
128136
128379
  function toBranchSlug(value) {
128137
128380
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "task";
128138
128381
  }
128382
+ function resolveExecutionSetup(opts) {
128383
+ const branchName = opts.prBranchName?.trim() ? opts.prBranchName.trim() : `feature/${opts.issueId}-${toBranchSlug(opts.projectName)}`;
128384
+ return {
128385
+ branchName,
128386
+ worktreePath: `${opts.repo}.worktrees/${branchName}`
128387
+ };
128388
+ }
128139
128389
  function buildTaskMessage(opts) {
128140
128390
  const {
128141
128391
  projectName,
@@ -128150,8 +128400,12 @@ function buildTaskMessage(opts) {
128150
128400
  } = opts;
128151
128401
  const repoDisplay = repo;
128152
128402
  const isFeedbackCycle = !!opts.prFeedback;
128153
- const branchName = opts.prFeedback?.branchName?.trim() ? opts.prFeedback.branchName.trim() : `feature/${issueId}-${toBranchSlug(projectName)}`;
128154
- const worktreePath = `${repoDisplay}.worktrees/${branchName}`;
128403
+ const { branchName, worktreePath } = resolveExecutionSetup({
128404
+ projectName,
128405
+ issueId,
128406
+ repo: repoDisplay,
128407
+ prBranchName: opts.prFeedback?.branchName
128408
+ });
128155
128409
  const parts = [
128156
128410
  `${role.toUpperCase()} task for project "${projectName}" \u2014 Issue #${issueId}`,
128157
128411
  ``,
@@ -128172,10 +128426,11 @@ ${issueDescription}` : ""
128172
128426
  ``,
128173
128427
  `## Execution Setup (do this before editing files)`,
128174
128428
  `Repo: ${repoDisplay} | Base branch: ${baseBranch} | ${issueUrl}`,
128175
- `Execution path: ${repoDisplay}`,
128429
+ `Execution path: ${worktreePath}`,
128430
+ `Main checkout: ${repoDisplay}`,
128176
128431
  `Required branch: ${branchName}`,
128177
128432
  `Required worktree: ${worktreePath}`,
128178
- `Before editing any file, create or reuse the required worktree above and work only there.`,
128433
+ `Before editing any file, change into the execution path above and work only there.`,
128179
128434
  `Do not re-initialize or replace the scaffold in the main checkout (for example: do not run npm init, cargo init, uv init, or create a second project skeleton) when the repo already contains scaffolded files. Modify the existing scaffold inside the worktree instead.`,
128180
128435
  `If the repo path is missing or inaccessible, return the canonical blocked result instead of improvising in ~/.openclaw/workspace.`
128181
128436
  );
@@ -128242,8 +128497,12 @@ function buildConflictFixMessage(opts) {
128242
128497
  prFeedback
128243
128498
  } = opts;
128244
128499
  const repoDisplay = repo;
128245
- const branchName = prFeedback.branchName?.trim() ? prFeedback.branchName.trim() : `feature/${issueId}-${toBranchSlug(projectName)}`;
128246
- const worktreePath = `${repoDisplay}.worktrees/${branchName}`;
128500
+ const { branchName, worktreePath } = resolveExecutionSetup({
128501
+ projectName,
128502
+ issueId,
128503
+ repo: repoDisplay,
128504
+ prBranchName: prFeedback.branchName
128505
+ });
128247
128506
  const parts = [
128248
128507
  `${role.toUpperCase()} task for project "${projectName}" \u2014 Issue #${issueId}`,
128249
128508
  ``,
@@ -128256,10 +128515,11 @@ function buildConflictFixMessage(opts) {
128256
128515
  ``,
128257
128516
  `Repo: ${repoDisplay} | Base branch: ${baseBranch} | ${issueUrl}`,
128258
128517
  `Project: ${projectName} | Channel: ${channelId}`,
128259
- `Execution path: ${repoDisplay}`,
128518
+ `Execution path: ${worktreePath}`,
128519
+ `Main checkout: ${repoDisplay}`,
128260
128520
  `Required branch: ${branchName}`,
128261
128521
  `Required worktree: ${worktreePath}`,
128262
- `Start by changing into the canonical repo path above before reusing the PR branch or creating its worktree. Reuse the exact PR branch named above; do not switch to a new canonical issue branch during a feedback cycle. Do not resolve the issue inside ~/.openclaw/workspace unless the repo path itself points there.`
128522
+ `Start by changing into the execution path above before reusing the PR branch. Reuse the exact PR branch named above; do not switch to a new canonical issue branch during a feedback cycle. Do not resolve the issue inside ~/.openclaw/workspace unless the repo path itself points there.`
128263
128523
  );
128264
128524
  parts.push(...buildCompletionContract(role));
128265
128525
  return parts.join("\n");
@@ -128592,23 +128852,24 @@ ${roleInstructions}`;
128592
128852
  }
128593
128853
  return effortPrefix ?? roleInstructions ?? "";
128594
128854
  }
128595
- function sendToAgent(sessionKey, taskMessage, opts) {
128855
+ async function sendToAgent(sessionKey, taskMessage, opts) {
128596
128856
  const epoch = opts.dispatchEpoch ?? (/* @__PURE__ */ new Date()).toISOString();
128597
128857
  const idempotencyKey = `fabrica-${opts.projectName}-${opts.issueId}-${opts.role}-${opts.level ?? "unknown"}-${opts.slotIndex ?? 0}-${opts.fromLabel ?? "unknown"}-${sessionKey}-${epoch}`;
128598
128858
  if (opts.runtime?.subagent?.run) {
128599
- opts.runtime.subagent.run({
128600
- sessionKey,
128601
- message: taskMessage,
128602
- idempotencyKey,
128603
- lane: "subagent",
128604
- deliver: false,
128605
- ...opts.extraSystemPrompt ? { extraSystemPrompt: opts.extraSystemPrompt } : {}
128606
- }).then((result) => {
128859
+ try {
128860
+ const result = await opts.runtime.subagent.run({
128861
+ sessionKey,
128862
+ message: taskMessage,
128863
+ idempotencyKey,
128864
+ lane: "subagent",
128865
+ deliver: false,
128866
+ ...opts.extraSystemPrompt ? { extraSystemPrompt: opts.extraSystemPrompt } : {}
128867
+ });
128607
128868
  if (result?.runId) {
128608
- bindDispatchRunIdBySessionKey(opts.workspaceDir, sessionKey, result.runId).catch(() => {
128869
+ await bindDispatchRunIdBySessionKey(opts.workspaceDir, sessionKey, result.runId).catch(() => {
128609
128870
  });
128610
128871
  }
128611
- log(opts.workspaceDir, "dispatch_agent_sent", {
128872
+ await log(opts.workspaceDir, "dispatch_agent_sent", {
128612
128873
  step: "sendToAgent",
128613
128874
  sessionKey,
128614
128875
  issue: opts.issueId,
@@ -128617,7 +128878,7 @@ function sendToAgent(sessionKey, taskMessage, opts) {
128617
128878
  runId: result?.runId ?? null
128618
128879
  }).catch(() => {
128619
128880
  });
128620
- recordIssueLifecycle({
128881
+ await recordIssueLifecycle({
128621
128882
  workspaceDir: opts.workspaceDir,
128622
128883
  slug: opts.projectSlug ?? opts.projectName,
128623
128884
  issueId: opts.issueId,
@@ -128626,18 +128887,23 @@ function sendToAgent(sessionKey, taskMessage, opts) {
128626
128887
  details: { method: "runtime.subagent.run" }
128627
128888
  }).catch(() => {
128628
128889
  });
128629
- }).catch((err) => {
128890
+ return { method: "runtime.subagent.run", runId: result?.runId ?? null };
128891
+ } catch (err) {
128892
+ const errorMessage = err.message ?? String(err);
128630
128893
  log(opts.workspaceDir, "dispatch_warning", {
128631
128894
  step: "sendToAgent",
128632
128895
  sessionKey,
128633
128896
  issue: opts.issueId,
128634
128897
  role: opts.role,
128635
128898
  method: "runtime.subagent.run",
128636
- error: err.message ?? String(err)
128899
+ error: errorMessage
128637
128900
  }).catch(() => {
128638
128901
  });
128639
- });
128640
- return;
128902
+ if (/only available during a gateway request/i.test(errorMessage)) {
128903
+ } else {
128904
+ throw err;
128905
+ }
128906
+ }
128641
128907
  }
128642
128908
  const rc = opts.runCommand;
128643
128909
  const gatewayParams = JSON.stringify({
@@ -128650,11 +128916,45 @@ function sendToAgent(sessionKey, taskMessage, opts) {
128650
128916
  ...opts.orchestratorSessionKey ? { spawnedBy: opts.orchestratorSessionKey } : {},
128651
128917
  ...opts.extraSystemPrompt ? { extraSystemPrompt: opts.extraSystemPrompt } : {}
128652
128918
  });
128653
- rc(
128654
- ["openclaw", "gateway", "call", "agent", "--params", gatewayParams, "--expect-final", "--json"],
128655
- { timeoutMs: opts.dispatchTimeoutMs ?? 6e4 }
128656
- ).catch((err) => {
128657
- log(opts.workspaceDir, "dispatch_warning", {
128919
+ try {
128920
+ const response = await rc(
128921
+ ["openclaw", "gateway", "call", "agent", "--params", gatewayParams, "--expect-final", "--json"],
128922
+ { timeoutMs: opts.dispatchTimeoutMs ?? 6e4 }
128923
+ );
128924
+ const stdout = String(response.stdout ?? "").trim();
128925
+ let runId = null;
128926
+ if (stdout) {
128927
+ try {
128928
+ const parsed = JSON.parse(stdout);
128929
+ runId = parsed.runId ?? parsed.result?.runId ?? null;
128930
+ } catch {
128931
+ }
128932
+ }
128933
+ if (runId) {
128934
+ await bindDispatchRunIdBySessionKey(opts.workspaceDir, sessionKey, runId).catch(() => {
128935
+ });
128936
+ }
128937
+ await log(opts.workspaceDir, "dispatch_agent_sent", {
128938
+ step: "sendToAgent",
128939
+ sessionKey,
128940
+ issue: opts.issueId,
128941
+ role: opts.role,
128942
+ method: "subprocess_fallback",
128943
+ runId
128944
+ }).catch(() => {
128945
+ });
128946
+ await recordIssueLifecycle({
128947
+ workspaceDir: opts.workspaceDir,
128948
+ slug: opts.projectSlug ?? opts.projectName,
128949
+ issueId: opts.issueId,
128950
+ stage: "agent_accepted",
128951
+ sessionKey,
128952
+ details: { method: "subprocess_fallback", runId }
128953
+ }).catch(() => {
128954
+ });
128955
+ return { method: "subprocess_fallback", runId };
128956
+ } catch (err) {
128957
+ await log(opts.workspaceDir, "dispatch_warning", {
128658
128958
  step: "sendToAgent",
128659
128959
  sessionKey,
128660
128960
  issue: opts.issueId,
@@ -128663,7 +128963,8 @@ function sendToAgent(sessionKey, taskMessage, opts) {
128663
128963
  error: err.message ?? String(err)
128664
128964
  }).catch(() => {
128665
128965
  });
128666
- });
128966
+ throw err;
128967
+ }
128667
128968
  }
128668
128969
  function normalizeGatewaySessionLabel(label, maxLength = GATEWAY_SESSION_LABEL_MAX) {
128669
128970
  if (!label) return { truncated: false };
@@ -128784,7 +129085,8 @@ async function dispatchTask(opts) {
128784
129085
  existingSessionKey = null;
128785
129086
  }
128786
129087
  }
128787
- if (existingSessionKey && role === "developer" && isFeedbackState(resolvedConfig.workflow, fromLabel)) {
129088
+ const feedbackFreshSession = !!(existingSessionKey && role === "developer" && isFeedbackState(resolvedConfig.workflow, fromLabel));
129089
+ if (feedbackFreshSession) {
128788
129090
  await rc(
128789
129091
  ["openclaw", "gateway", "call", "sessions.delete", "--params", JSON.stringify({ key: existingSessionKey })],
128790
129092
  { timeoutMs: 1e4 }
@@ -128818,8 +129120,10 @@ async function dispatchTask(opts) {
128818
129120
  existingSessionKey = null;
128819
129121
  }
128820
129122
  }
129123
+ const dispatchCycleId = randomUUID2();
128821
129124
  const botName = slotName(project.name, role, level, slotIndex);
128822
- const sessionKey = `agent:${agentId ?? "unknown"}:subagent:${project.name}-${role}-${level}-${botName.toLowerCase()}`;
129125
+ const deterministicSessionKey = `agent:${agentId ?? "unknown"}:subagent:${project.name}-${role}-${level}-${botName.toLowerCase()}`;
129126
+ const sessionKey = feedbackFreshSession ? `${deterministicSessionKey}-retry-${dispatchCycleId.slice(0, 8)}` : deterministicSessionKey;
128823
129127
  if (existingSessionKey && existingSessionKey !== sessionKey) {
128824
129128
  const existingKeyIsForSameRole = existingSessionKey.includes(`:${project.name}-${role}-`);
128825
129129
  if (existingKeyIsForSameRole) {
@@ -128830,8 +129134,27 @@ async function dispatchTask(opts) {
128830
129134
  existingSessionKey = null;
128831
129135
  }
128832
129136
  }
129137
+ if (!existingSessionKey) {
129138
+ const gatewaySessions = await fetchGatewaySessions(void 0, rc).catch(() => null);
129139
+ if (gatewaySessions && isSessionAlive(sessionKey, gatewaySessions)) {
129140
+ await rc(
129141
+ ["openclaw", "gateway", "call", "sessions.delete", "--params", JSON.stringify({ key: sessionKey })],
129142
+ { timeoutMs: 1e4 }
129143
+ ).catch((err) => console.error("[fabrica] silent-catch:", err.message));
129144
+ await log(workspaceDir, "session_orphan_reset", {
129145
+ project: project.name,
129146
+ projectSlug: project.slug,
129147
+ issue: issueId,
129148
+ role,
129149
+ level,
129150
+ sessionKey,
129151
+ reason: feedbackFreshSession ? "gateway_session_alive_without_local_slot_tracking_after_feedback_reset" : "gateway_session_alive_without_local_slot_tracking"
129152
+ }).catch(() => {
129153
+ });
129154
+ }
129155
+ }
128833
129156
  const sessionAction = existingSessionKey ? "send" : "spawn";
128834
- const dispatchCycleId = randomUUID2();
129157
+ const dispatchSemantic = feedbackFreshSession ? "feedback_redispatch" : sessionAction === "send" ? "session_resume" : "fresh_dispatch";
128835
129158
  const allComments = await provider.listComments(issueId);
128836
129159
  const { workflow } = resolvedConfig;
128837
129160
  const prFeedback = isFeedbackState(workflow, fromLabel) ? await fetchPrFeedback(provider, issueId, prSelector) : void 0;
@@ -128850,6 +129173,60 @@ async function dispatchTask(opts) {
128850
129173
  const primaryChannelId = project.slug;
128851
129174
  const isConflictFix = prFeedback?.reason === "merge_conflict";
128852
129175
  const repoContext = project.repo ? resolveRepoPath(project.repo) : project.repoRemote?.replace(/\.git$/, "") || project.slug;
129176
+ const executionSetup = resolveExecutionSetup({
129177
+ projectName: project.name,
129178
+ issueId,
129179
+ repo: repoContext,
129180
+ prBranchName: prFeedback?.branchName
129181
+ });
129182
+ const localRepoPath = project.repo ? resolveRepoPath(project.repo) : null;
129183
+ if (localRepoPath) {
129184
+ const remoteStartRef = prFeedback?.branchName?.trim() ? `origin/${executionSetup.branchName}` : `origin/${project.baseBranch}`;
129185
+ await fs22.mkdir(`${localRepoPath}.worktrees`, { recursive: true }).catch(() => {
129186
+ });
129187
+ const worktreeExists = await fs22.stat(executionSetup.worktreePath).then(() => true).catch(() => false);
129188
+ if (!worktreeExists) {
129189
+ await rc(["git", "fetch", "origin", project.baseBranch], { timeoutMs: 3e4, cwd: localRepoPath }).catch(() => {
129190
+ });
129191
+ if (prFeedback?.branchName?.trim()) {
129192
+ await rc(["git", "fetch", "origin", executionSetup.branchName], { timeoutMs: 3e4, cwd: localRepoPath }).catch(() => {
129193
+ });
129194
+ }
129195
+ await rc(
129196
+ ["git", "worktree", "add", executionSetup.worktreePath, "-B", executionSetup.branchName, remoteStartRef],
129197
+ { timeoutMs: 6e4, cwd: localRepoPath }
129198
+ );
129199
+ await log(workspaceDir, "dispatch_worktree_prepared", {
129200
+ project: project.name,
129201
+ projectSlug: project.slug,
129202
+ issue: issueId,
129203
+ role,
129204
+ level,
129205
+ branchName: executionSetup.branchName,
129206
+ worktreePath: executionSetup.worktreePath,
129207
+ mode: "create"
129208
+ }).catch(() => {
129209
+ });
129210
+ } else {
129211
+ await rc(["git", "checkout", executionSetup.branchName], { timeoutMs: 3e4, cwd: executionSetup.worktreePath }).catch(() => {
129212
+ });
129213
+ await rc(["git", "reset", "--hard", remoteStartRef], { timeoutMs: 3e4, cwd: executionSetup.worktreePath }).catch(() => {
129214
+ });
129215
+ await rc(["git", "clean", "-fd"], { timeoutMs: 3e4, cwd: executionSetup.worktreePath }).catch(() => {
129216
+ });
129217
+ await log(workspaceDir, "dispatch_worktree_prepared", {
129218
+ project: project.name,
129219
+ projectSlug: project.slug,
129220
+ issue: issueId,
129221
+ role,
129222
+ level,
129223
+ branchName: executionSetup.branchName,
129224
+ worktreePath: executionSetup.worktreePath,
129225
+ mode: "reuse"
129226
+ }).catch(() => {
129227
+ });
129228
+ }
129229
+ }
128853
129230
  const taskMessage = isConflictFix && prFeedback ? buildConflictFixMessage({
128854
129231
  projectName: project.name,
128855
129232
  channelId: primaryChannelId,
@@ -128993,6 +129370,62 @@ async function dispatchTask(opts) {
128993
129370
  }
128994
129371
  } catch {
128995
129372
  }
129373
+ const dispatchEpoch = (/* @__PURE__ */ new Date()).toISOString();
129374
+ let sendResult;
129375
+ try {
129376
+ sendResult = await sendToAgent(sessionKey, taskMessage, {
129377
+ agentId,
129378
+ projectName: project.name,
129379
+ projectSlug: project.slug,
129380
+ issueId,
129381
+ role,
129382
+ level,
129383
+ slotIndex,
129384
+ fromLabel,
129385
+ orchestratorSessionKey: opts.sessionKey,
129386
+ workspaceDir,
129387
+ dispatchTimeoutMs: timeouts.dispatchMs,
129388
+ extraSystemPrompt: buildEffortPrompt(
129389
+ resolvedRole?.effort?.[level],
129390
+ roleInstructions.trim() || void 0
129391
+ ) || void 0,
129392
+ runCommand: rc,
129393
+ runtime,
129394
+ dispatchEpoch
129395
+ });
129396
+ } catch (err) {
129397
+ try {
129398
+ await resilientLabelTransition(
129399
+ provider,
129400
+ issueId,
129401
+ toLabel,
129402
+ fromLabel,
129403
+ (msg) => log(workspaceDir, "dispatch_warning", { step: "send_rollback_label", issue: issueId, msg }).catch(() => {
129404
+ })
129405
+ );
129406
+ await deactivateWorker(workspaceDir, project.slug, role, { level, slotIndex, issueId: String(issueId) });
129407
+ } catch {
129408
+ }
129409
+ throw new Error(`Dispatch send failed for issue #${issueId}: ${err.message ?? String(err)}`);
129410
+ }
129411
+ await recordIssueLifecycle({
129412
+ workspaceDir,
129413
+ slug: project.slug,
129414
+ issueId,
129415
+ stage: "dispatch_requested",
129416
+ sessionKey,
129417
+ details: { role, level, slotIndex, sessionAction }
129418
+ }).catch((err) => {
129419
+ log(workspaceDir, "dispatch_warning", {
129420
+ project: project.name,
129421
+ issue: issueId,
129422
+ role,
129423
+ warning: "Lifecycle event failed after successful dispatch",
129424
+ error: err.message,
129425
+ sessionKey
129426
+ }).catch(() => {
129427
+ });
129428
+ });
128996
129429
  const notifyConfig = getNotificationConfig(pluginConfig);
128997
129430
  const notifyTarget = resolveNotifyChannel(issue2?.labels ?? [], project.channels);
128998
129431
  notify(
@@ -129006,11 +129439,12 @@ async function dispatchTask(opts) {
129006
129439
  level,
129007
129440
  name: botName,
129008
129441
  sessionAction,
129442
+ dispatchSemantic,
129009
129443
  modelDowngraded: effectiveModel.downgraded,
129010
129444
  originalModel: effectiveModel.downgraded ? resolvedModel : void 0,
129011
129445
  effectiveModel: effectiveModel.downgraded ? model : void 0,
129012
129446
  dispatchCycleId,
129013
- dispatchRunId: null
129447
+ dispatchRunId: sendResult.runId
129014
129448
  },
129015
129449
  {
129016
129450
  workspaceDir,
@@ -129030,45 +129464,6 @@ async function dispatchTask(opts) {
129030
129464
  error: err.message ?? String(err)
129031
129465
  }).catch((auditErr) => console.error("[fabrica] silent-catch:", auditErr.message));
129032
129466
  });
129033
- const dispatchEpoch = (/* @__PURE__ */ new Date()).toISOString();
129034
- sendToAgent(sessionKey, taskMessage, {
129035
- agentId,
129036
- projectName: project.name,
129037
- projectSlug: project.slug,
129038
- issueId,
129039
- role,
129040
- level,
129041
- slotIndex,
129042
- fromLabel,
129043
- orchestratorSessionKey: opts.sessionKey,
129044
- workspaceDir,
129045
- dispatchTimeoutMs: timeouts.dispatchMs,
129046
- extraSystemPrompt: buildEffortPrompt(
129047
- resolvedRole?.effort?.[level],
129048
- roleInstructions.trim() || void 0
129049
- ) || void 0,
129050
- runCommand: rc,
129051
- runtime,
129052
- dispatchEpoch
129053
- });
129054
- await recordIssueLifecycle({
129055
- workspaceDir,
129056
- slug: project.slug,
129057
- issueId,
129058
- stage: "dispatch_requested",
129059
- sessionKey,
129060
- details: { role, level, slotIndex, sessionAction }
129061
- }).catch((err) => {
129062
- log(workspaceDir, "dispatch_warning", {
129063
- project: project.name,
129064
- issue: issueId,
129065
- role,
129066
- warning: "Lifecycle event failed after successful dispatch",
129067
- error: err.message,
129068
- sessionKey
129069
- }).catch(() => {
129070
- });
129071
- });
129072
129467
  await auditDispatch(workspaceDir, {
129073
129468
  project: project.name,
129074
129469
  issueId,
@@ -129786,7 +130181,7 @@ function createProjectStatusTool(ctx) {
129786
130181
  }
129787
130182
 
129788
130183
  // lib/tools/admin/project-register.ts
129789
- import fs23 from "node:fs/promises";
130184
+ import fs24 from "node:fs/promises";
129790
130185
  import path24 from "node:path";
129791
130186
  init_audit();
129792
130187
  init_roles();
@@ -129794,7 +130189,7 @@ init_workflow();
129794
130189
  init_constants();
129795
130190
 
129796
130191
  // lib/telegram/topic-service.ts
129797
- import fs22 from "node:fs/promises";
130192
+ import fs23 from "node:fs/promises";
129798
130193
  async function sleep2(ms) {
129799
130194
  await new Promise((resolve3) => setTimeout(resolve3, ms));
129800
130195
  }
@@ -129824,7 +130219,7 @@ async function tryReadTokenFile(filePath) {
129824
130219
  const resolvedPath = readString(filePath);
129825
130220
  if (!resolvedPath) return void 0;
129826
130221
  try {
129827
- const token = await fs22.readFile(resolvedPath, "utf-8");
130222
+ const token = await fs23.readFile(resolvedPath, "utf-8");
129828
130223
  const trimmed = token.trim();
129829
130224
  return trimmed || void 0;
129830
130225
  } catch {
@@ -129985,14 +130380,14 @@ function buildForumTopicArtifactId(channelId, messageThreadId) {
129985
130380
  async function scaffoldPromptFiles(workspaceDir, projectName) {
129986
130381
  const projectDir = path24.join(workspaceDir, DATA_DIR, "projects", projectName);
129987
130382
  const promptsDir = path24.join(projectDir, "prompts");
129988
- await fs23.mkdir(promptsDir, { recursive: true });
130383
+ await fs24.mkdir(promptsDir, { recursive: true });
129989
130384
  const readmePath = path24.join(projectDir, "README.md");
129990
130385
  try {
129991
- await fs23.access(readmePath);
130386
+ await fs24.access(readmePath);
129992
130387
  return false;
129993
130388
  } catch {
129994
130389
  const roles = getAllRoleIds().join(", ");
129995
- await fs23.writeFile(readmePath, `# Project Overrides
130390
+ await fs24.writeFile(readmePath, `# Project Overrides
129996
130391
 
129997
130392
  This directory holds project-specific configuration that overrides the workspace defaults.
129998
130393
 
@@ -130048,12 +130443,12 @@ function getProjectReadmePath(workspaceDir, projectName) {
130048
130443
  async function ensureAutonomousWorkflowOverride(workspaceDir, projectName, reviewPolicy = "agent") {
130049
130444
  const projectDir = getProjectDir(workspaceDir, projectName);
130050
130445
  const workflowPath = getProjectWorkflowPath(workspaceDir, projectName);
130051
- await fs23.mkdir(projectDir, { recursive: true });
130446
+ await fs24.mkdir(projectDir, { recursive: true });
130052
130447
  try {
130053
- await fs23.access(workflowPath);
130448
+ await fs24.access(workflowPath);
130054
130449
  return false;
130055
130450
  } catch {
130056
- await fs23.writeFile(workflowPath, `workflow:
130451
+ await fs24.writeFile(workflowPath, `workflow:
130057
130452
  reviewPolicy: ${reviewPolicy}
130058
130453
  `, "utf-8");
130059
130454
  return true;
@@ -130062,7 +130457,7 @@ async function ensureAutonomousWorkflowOverride(workspaceDir, projectName, revie
130062
130457
  async function hasProjectWorkflowOverride(workspaceDir, projectName) {
130063
130458
  const workflowPath = getProjectWorkflowPath(workspaceDir, projectName);
130064
130459
  try {
130065
- await fs23.access(workflowPath);
130460
+ await fs24.access(workflowPath);
130066
130461
  return true;
130067
130462
  } catch {
130068
130463
  return false;
@@ -130077,7 +130472,7 @@ async function pruneEmptyProjectDirs(workspaceDir, projectName) {
130077
130472
  for (const dir of candidateDirs2) {
130078
130473
  if (!dir.startsWith(stopDir)) continue;
130079
130474
  try {
130080
- await fs23.rmdir(dir);
130475
+ await fs24.rmdir(dir);
130081
130476
  } catch (error48) {
130082
130477
  const code = error48.code;
130083
130478
  if (code !== "ENOENT" && code !== "ENOTEMPTY") {
@@ -130088,10 +130483,10 @@ async function pruneEmptyProjectDirs(workspaceDir, projectName) {
130088
130483
  }
130089
130484
  async function cleanupLocalRegisterResidue(workspaceDir, projectName, opts) {
130090
130485
  if (opts.removeWorkflowOverride) {
130091
- await fs23.rm(getProjectWorkflowPath(workspaceDir, projectName), { force: true });
130486
+ await fs24.rm(getProjectWorkflowPath(workspaceDir, projectName), { force: true });
130092
130487
  }
130093
130488
  if (opts.removePromptReadme) {
130094
- await fs23.rm(getProjectReadmePath(workspaceDir, projectName), { force: true });
130489
+ await fs24.rm(getProjectReadmePath(workspaceDir, projectName), { force: true });
130095
130490
  }
130096
130491
  await pruneEmptyProjectDirs(workspaceDir, projectName);
130097
130492
  }
@@ -130124,7 +130519,7 @@ var NODE_STACKS = /* @__PURE__ */ new Set(["nextjs", "node-cli", "express"]);
130124
130519
  var PYTHON_STACKS = /* @__PURE__ */ new Set(["fastapi", "flask", "django", "python-cli"]);
130125
130520
  async function pathExists(candidate) {
130126
130521
  try {
130127
- await fs23.access(candidate);
130522
+ await fs24.access(candidate);
130128
130523
  return true;
130129
130524
  } catch {
130130
130525
  return false;
@@ -130549,7 +130944,7 @@ init_labels();
130549
130944
 
130550
130945
  // lib/services/worker-completion.ts
130551
130946
  init_audit();
130552
- import fs24 from "node:fs/promises";
130947
+ import fs25 from "node:fs/promises";
130553
130948
  init_workflow();
130554
130949
 
130555
130950
  // lib/services/worker-result.ts
@@ -130688,7 +131083,7 @@ function parseSessionTranscript(raw) {
130688
131083
  async function readWorkerResultFromSessionFile(role, sessionFile) {
130689
131084
  if (!sessionFile) return null;
130690
131085
  try {
130691
- const raw = await fs24.readFile(sessionFile, "utf-8");
131086
+ const raw = await fs25.readFile(sessionFile, "utf-8");
130692
131087
  const messages = parseSessionTranscript(raw);
130693
131088
  const result = extractWorkerResultFromMessages(role, messages);
130694
131089
  return result ? {
@@ -132778,7 +133173,7 @@ ${channelList}`;
132778
133173
  // lib/setup/index.ts
132779
133174
  var import_yaml3 = __toESM(require_dist(), 1);
132780
133175
  init_roles();
132781
- import fs38 from "node:fs/promises";
133176
+ import fs39 from "node:fs/promises";
132782
133177
  import path39 from "node:path";
132783
133178
 
132784
133179
  // lib/setup/binding-manager.ts
@@ -132801,7 +133196,7 @@ async function migrateChannelBinding(api, channel, fromAgentId, toAgentId) {
132801
133196
 
132802
133197
  // lib/setup/agent.ts
132803
133198
  init_logger();
132804
- import fs25 from "node:fs/promises";
133199
+ import fs26 from "node:fs/promises";
132805
133200
  import path25 from "node:path";
132806
133201
  async function createAgent(api, name, runCommand, channelBinding) {
132807
133202
  const rc = runCommand;
@@ -132830,11 +133225,11 @@ function resolveWorkspacePath(api, agentId) {
132830
133225
  }
132831
133226
  async function cleanupWorkspace(workspacePath) {
132832
133227
  try {
132833
- await fs25.rm(path25.join(workspacePath, ".git"), { recursive: true });
133228
+ await fs26.rm(path25.join(workspacePath, ".git"), { recursive: true });
132834
133229
  } catch {
132835
133230
  }
132836
133231
  try {
132837
- await fs25.unlink(path25.join(workspacePath, "BOOTSTRAP.md"));
133232
+ await fs26.unlink(path25.join(workspacePath, "BOOTSTRAP.md"));
132838
133233
  } catch {
132839
133234
  }
132840
133235
  }
@@ -132897,7 +133292,7 @@ init_workspace();
132897
133292
 
132898
133293
  // lib/services/heartbeat/agent-discovery.ts
132899
133294
  init_constants();
132900
- import fs26 from "node:fs";
133295
+ import fs27 from "node:fs";
132901
133296
  import path26 from "node:path";
132902
133297
  function discoverAgents(config2) {
132903
133298
  const seen = /* @__PURE__ */ new Set();
@@ -132924,7 +133319,7 @@ function discoverAgents(config2) {
132924
133319
  return agents;
132925
133320
  }
132926
133321
  function hasProjects(workspace) {
132927
- return fs26.existsSync(path26.join(workspace, DATA_DIR, "projects.json")) || fs26.existsSync(path26.join(workspace, "projects.json")) || fs26.existsSync(path26.join(workspace, "projects", "projects.json"));
133322
+ return fs27.existsSync(path26.join(workspace, DATA_DIR, "projects.json")) || fs27.existsSync(path26.join(workspace, "projects.json")) || fs27.existsSync(path26.join(workspace, "projects", "projects.json"));
132928
133323
  }
132929
133324
 
132930
133325
  // lib/services/heartbeat/config.ts
@@ -137273,7 +137668,7 @@ async function processPendingGitHubEventsForWorkspace(params) {
137273
137668
  }
137274
137669
 
137275
137670
  // lib/machines/lifecycle-service.ts
137276
- import fs29 from "node:fs/promises";
137671
+ import fs30 from "node:fs/promises";
137277
137672
  import path29 from "node:path";
137278
137673
  init_constants();
137279
137674
  init_migrate_layout();
@@ -137366,9 +137761,9 @@ async function lifecycleSnapshotPath(workspaceDir) {
137366
137761
  }
137367
137762
  async function persistSnapshot(workspaceDir, actor) {
137368
137763
  const filePath = await lifecycleSnapshotPath(workspaceDir);
137369
- await fs29.mkdir(path29.dirname(filePath), { recursive: true });
137764
+ await fs30.mkdir(path29.dirname(filePath), { recursive: true });
137370
137765
  const snapshot = actor.getSnapshot();
137371
- await fs29.writeFile(filePath, JSON.stringify({
137766
+ await fs30.writeFile(filePath, JSON.stringify({
137372
137767
  value: snapshot.value,
137373
137768
  context: snapshot.context,
137374
137769
  updatedAt: snapshot.context.updatedAt
@@ -137461,7 +137856,7 @@ async function raceWithTimeout(fn, timeoutMs, onTimeout) {
137461
137856
  // lib/dispatch/telegram-bootstrap-hook.ts
137462
137857
  import { randomUUID as randomUUID5 } from "node:crypto";
137463
137858
  import { homedir as homedir2 } from "node:os";
137464
- import fs36 from "node:fs/promises";
137859
+ import fs37 from "node:fs/promises";
137465
137860
  import path37 from "node:path";
137466
137861
 
137467
137862
  // lib/dispatch/attachment-hook.ts
@@ -137908,7 +138303,7 @@ init_conduct_interview();
137908
138303
  init_generate_spec();
137909
138304
 
137910
138305
  // lib/intake/lib/project-map.ts
137911
- import fs31 from "node:fs/promises";
138306
+ import fs32 from "node:fs/promises";
137912
138307
  import path31 from "node:path";
137913
138308
  var IGNORED_DIRS = /* @__PURE__ */ new Set([
137914
138309
  ".git",
@@ -138008,7 +138403,7 @@ function matchProjectByRef(projects, ref) {
138008
138403
  async function tryStatDir(candidate) {
138009
138404
  if (!candidate) return false;
138010
138405
  try {
138011
- return (await fs31.stat(candidate)).isDirectory();
138406
+ return (await fs32.stat(candidate)).isDirectory();
138012
138407
  } catch {
138013
138408
  return false;
138014
138409
  }
@@ -138109,7 +138504,7 @@ async function resolveProjectTarget(payload, workspaceDir, homeDir) {
138109
138504
  async function collectFiles(root, relativeDir = "", files = [], limit = 800) {
138110
138505
  if (files.length >= limit) return files;
138111
138506
  const currentDir = path31.join(root, relativeDir);
138112
- const entries = await fs31.readdir(currentDir, { withFileTypes: true });
138507
+ const entries = await fs32.readdir(currentDir, { withFileTypes: true });
138113
138508
  for (const entry of entries) {
138114
138509
  if (files.length >= limit) break;
138115
138510
  if (entry.name.startsWith(".") && entry.name !== ".env.example") {
@@ -138303,16 +138698,16 @@ var impactStep = {
138303
138698
  const modules = map2?.modules ?? [];
138304
138699
  const specText = `${spec.title} ${spec.objective} ${spec.scope_v1.join(" ")}`.toLowerCase();
138305
138700
  const affected = symbols.filter((s2) => specText.includes(s2.name.toLowerCase())).map((s2) => s2.file);
138306
- const unique = [...new Set(affected)];
138701
+ const unique2 = [...new Set(affected)];
138307
138702
  const affectedModules = modules.filter((moduleName2) => specText.includes(moduleName2.toLowerCase()));
138308
- const confidence = map2?.confidence === "low" || !unique.length && !affectedModules.length ? "low" : "high";
138703
+ const confidence = map2?.confidence === "low" || !unique2.length && !affectedModules.length ? "low" : "high";
138309
138704
  impact = {
138310
138705
  is_greenfield: false,
138311
- affected_files: unique,
138706
+ affected_files: unique2,
138312
138707
  affected_modules: [...new Set(affectedModules)],
138313
138708
  new_files_needed: [],
138314
138709
  risk_areas: spec.risks,
138315
- estimated_files_changed: Math.max(1, unique.length || affectedModules.length),
138710
+ estimated_files_changed: Math.max(1, unique2.length || affectedModules.length),
138316
138711
  confidence
138317
138712
  };
138318
138713
  }
@@ -138334,7 +138729,7 @@ init_runtime_paths();
138334
138729
 
138335
138730
  // lib/test-env/bootstrap.ts
138336
138731
  import { createHash as createHash3 } from "node:crypto";
138337
- import fs32 from "node:fs/promises";
138732
+ import fs33 from "node:fs/promises";
138338
138733
  import path32 from "node:path";
138339
138734
  var NODE_STACKS2 = /* @__PURE__ */ new Set(["nextjs", "node-cli", "express"]);
138340
138735
  var PYTHON_STACKS2 = /* @__PURE__ */ new Set(["fastapi", "flask", "django", "python-cli"]);
@@ -138401,7 +138796,7 @@ var BOOTSTRAP_STATE_FILE = "test-env.sha256";
138401
138796
  var DEFAULT_TIMEOUT = 18e4;
138402
138797
  async function pathExists2(candidate) {
138403
138798
  try {
138404
- await fs32.access(candidate);
138799
+ await fs33.access(candidate);
138405
138800
  return true;
138406
138801
  } catch {
138407
138802
  return false;
@@ -138409,7 +138804,7 @@ async function pathExists2(candidate) {
138409
138804
  }
138410
138805
  async function isValidBinary(filePath) {
138411
138806
  try {
138412
- const stat2 = await fs32.stat(filePath);
138807
+ const stat2 = await fs33.stat(filePath);
138413
138808
  return stat2.size > 0;
138414
138809
  } catch {
138415
138810
  return false;
@@ -138577,7 +138972,7 @@ async function computeFingerprint(repoPath, files) {
138577
138972
  for (const file2 of existing.sort()) {
138578
138973
  hash2.update(`${file2}
138579
138974
  `);
138580
- hash2.update(await fs32.readFile(path32.join(repoPath, file2)));
138975
+ hash2.update(await fs33.readFile(path32.join(repoPath, file2)));
138581
138976
  hash2.update("\n");
138582
138977
  }
138583
138978
  return hash2.digest("hex");
@@ -138585,15 +138980,15 @@ async function computeFingerprint(repoPath, files) {
138585
138980
  async function readBootstrapFingerprint(repoPath) {
138586
138981
  const stateFile = path32.join(repoPath, BOOTSTRAP_STATE_DIR, BOOTSTRAP_STATE_FILE);
138587
138982
  try {
138588
- return (await fs32.readFile(stateFile, "utf-8")).trim() || null;
138983
+ return (await fs33.readFile(stateFile, "utf-8")).trim() || null;
138589
138984
  } catch {
138590
138985
  return null;
138591
138986
  }
138592
138987
  }
138593
138988
  async function writeBootstrapFingerprint(repoPath, fingerprint) {
138594
138989
  const stateDir = path32.join(repoPath, BOOTSTRAP_STATE_DIR);
138595
- await fs32.mkdir(stateDir, { recursive: true });
138596
- await fs32.writeFile(path32.join(stateDir, BOOTSTRAP_STATE_FILE), fingerprint, "utf-8");
138990
+ await fs33.mkdir(stateDir, { recursive: true });
138991
+ await fs33.writeFile(path32.join(stateDir, BOOTSTRAP_STATE_FILE), fingerprint, "utf-8");
138597
138992
  }
138598
138993
  async function toolExists(runCommand, cmd, args = ["--version"]) {
138599
138994
  try {
@@ -138652,7 +139047,7 @@ Details: ${(install.stderr || install.stdout || "unknown error").trim()}`
138652
139047
  const home = process.env.HOME ?? "";
138653
139048
  const fallbackPath = path32.join(home, ".local", "bin", "uv");
138654
139049
  try {
138655
- await fs32.access(fallbackPath);
139050
+ await fs33.access(fallbackPath);
138656
139051
  emit2(`[test-env] uv installed at fallback path: ${fallbackPath}`);
138657
139052
  return fallbackPath;
138658
139053
  } catch {
@@ -138682,16 +139077,16 @@ async function ensurePythonToolchain(runCommand, homeDir) {
138682
139077
  const expectedFp = await toolchainFingerprint(runCommand);
138683
139078
  if (await isValidBinary(ruffPath)) {
138684
139079
  try {
138685
- const currentFp = (await fs32.readFile(fingerprintPath, "utf-8")).trim();
139080
+ const currentFp = (await fs33.readFile(fingerprintPath, "utf-8")).trim();
138686
139081
  if (currentFp === expectedFp) {
138687
139082
  return toolchainPath;
138688
139083
  }
138689
139084
  } catch {
138690
139085
  }
138691
- await fs32.rm(toolchainPath, { recursive: true, force: true });
139086
+ await fs33.rm(toolchainPath, { recursive: true, force: true });
138692
139087
  }
138693
139088
  const uvCmd = await ensureUv(runCommand);
138694
- await fs32.mkdir(path32.dirname(toolchainPath), { recursive: true });
139089
+ await fs33.mkdir(path32.dirname(toolchainPath), { recursive: true });
138695
139090
  const venvResult = await runCommand(uvCmd, ["venv", toolchainPath], { timeout: 6e4 });
138696
139091
  if (venvResult.exitCode !== 0) {
138697
139092
  throw new Error(`Failed to create toolchain venv: ${venvResult.stderr}`);
@@ -138706,13 +139101,13 @@ async function ensurePythonToolchain(runCommand, homeDir) {
138706
139101
  if (installResult.exitCode !== 0) {
138707
139102
  throw new Error(`Failed to install toolchain packages: ${installResult.stderr}`);
138708
139103
  }
138709
- await fs32.writeFile(fingerprintPath, expectedFp, "utf-8");
139104
+ await fs33.writeFile(fingerprintPath, expectedFp, "utf-8");
138710
139105
  return toolchainPath;
138711
139106
  }
138712
139107
  async function hasPyprojectDevExtra(repoPath) {
138713
139108
  const pyprojectPath = path32.join(repoPath, "pyproject.toml");
138714
139109
  if (!await pathExists2(pyprojectPath)) return false;
138715
- const content = await fs32.readFile(pyprojectPath, "utf-8");
139110
+ const content = await fs33.readFile(pyprojectPath, "utf-8");
138716
139111
  const match = content.match(/\[project\.optional-dependencies\]([\s\S]*?)(?:\n\[|$)/);
138717
139112
  if (!match) return false;
138718
139113
  return /^\s*dev\s*=\s*\[/m.test(match[1] ?? "");
@@ -139352,7 +139747,7 @@ var scaffoldPassthroughStep = {
139352
139747
  };
139353
139748
 
139354
139749
  // lib/intake/lib/repository-provision-service.ts
139355
- import fs33 from "node:fs/promises";
139750
+ import fs34 from "node:fs/promises";
139356
139751
  import path34 from "node:path";
139357
139752
  function normalizeText2(value) {
139358
139753
  if (typeof value !== "string") return null;
@@ -139393,7 +139788,7 @@ function adaptRunCommand(ctx) {
139393
139788
  async function pathExists3(candidate) {
139394
139789
  if (!candidate) return false;
139395
139790
  try {
139396
- await fs33.access(candidate);
139791
+ await fs34.access(candidate);
139397
139792
  return true;
139398
139793
  } catch {
139399
139794
  return false;
@@ -139764,6 +140159,287 @@ var securityReviewStep = {
139764
140159
 
139765
140160
  // lib/intake/steps/create-task.ts
139766
140161
  init_workflow();
140162
+
140163
+ // lib/dispatch/telegram-bootstrap-session.ts
140164
+ init_constants();
140165
+ import { createHash as createHash4, randomUUID as randomUUID4 } from "node:crypto";
140166
+ import fs35 from "node:fs/promises";
140167
+ import path35 from "node:path";
140168
+ var SESSION_TTL_MS = 10 * 6e4;
140169
+ var CLASSIFYING_TTL_MS = 15e3;
140170
+ var RELEASED_CLASSIFY_ERRORS = /* @__PURE__ */ new Set([
140171
+ "classify_invalid_result",
140172
+ "classify_low_confidence",
140173
+ "classify_not_project",
140174
+ "classify_result_expired"
140175
+ ]);
140176
+ function toCanonicalTelegramBootstrapConversationId(conversationId) {
140177
+ const trimmed = conversationId.trim();
140178
+ if (!trimmed) return trimmed;
140179
+ return trimmed.startsWith("telegram:") ? trimmed : `telegram:${trimmed}`;
140180
+ }
140181
+ function sessionsDir(workspaceDir) {
140182
+ return path35.join(workspaceDir, DATA_DIR, "bootstrap-sessions");
140183
+ }
140184
+ function sessionPath(workspaceDir, conversationId) {
140185
+ return path35.join(
140186
+ sessionsDir(workspaceDir),
140187
+ `${toCanonicalTelegramBootstrapConversationId(conversationId)}.json`
140188
+ );
140189
+ }
140190
+ function alternateSessionPath(workspaceDir, conversationId) {
140191
+ const canonical = toCanonicalTelegramBootstrapConversationId(conversationId);
140192
+ const bare = canonical.startsWith("telegram:") ? canonical.slice("telegram:".length) : canonical;
140193
+ if (!bare || bare === canonical) return null;
140194
+ return path35.join(sessionsDir(workspaceDir), `${bare}.json`);
140195
+ }
140196
+ function normalizeStoredSession(session) {
140197
+ const canonicalConversationId = toCanonicalTelegramBootstrapConversationId(session.conversationId);
140198
+ if (canonicalConversationId === session.conversationId) {
140199
+ return session;
140200
+ }
140201
+ return {
140202
+ ...session,
140203
+ id: buildBootstrapSessionId(canonicalConversationId, session.rawIdea),
140204
+ conversationId: canonicalConversationId
140205
+ };
140206
+ }
140207
+ function stableHash(input) {
140208
+ return createHash4("sha256").update(input).digest("hex").slice(0, 16);
140209
+ }
140210
+ function buildBootstrapSessionId(conversationId, rawIdea) {
140211
+ return `tgdm-${conversationId}-${stableHash(rawIdea.trim().toLowerCase())}`;
140212
+ }
140213
+ function buildBootstrapRequestFingerprint(input) {
140214
+ return stableHash(JSON.stringify({
140215
+ rawIdea: input.rawIdea.trim().toLowerCase(),
140216
+ projectName: input.projectName?.trim().toLowerCase() || null,
140217
+ stackHint: input.stackHint?.trim().toLowerCase() || null,
140218
+ repoUrl: input.repoUrl?.trim().toLowerCase() || null,
140219
+ repoPath: input.repoPath?.trim().toLowerCase() || null
140220
+ }));
140221
+ }
140222
+ function buildBootstrapRequestHash(input) {
140223
+ return buildBootstrapRequestFingerprint(input);
140224
+ }
140225
+ function nextSuppressUntil(status) {
140226
+ const ttl = status === "classifying" || status === "pending_classify" ? CLASSIFYING_TTL_MS : SESSION_TTL_MS;
140227
+ return new Date(Date.now() + ttl).toISOString();
140228
+ }
140229
+ function isReleasedClassifyFailure(error48) {
140230
+ return Boolean(error48 && RELEASED_CLASSIFY_ERRORS.has(error48));
140231
+ }
140232
+ function resolveNullableField(inputValue, existingValue, fallback = null) {
140233
+ return inputValue !== void 0 ? inputValue : existingValue ?? fallback;
140234
+ }
140235
+ function defaultNextRetryAtForStatus(status, existingValue) {
140236
+ if (status === "bootstrapping" || status === "dispatching") {
140237
+ return existingValue ?? null;
140238
+ }
140239
+ return null;
140240
+ }
140241
+ var MONOTONIC_BOOTSTRAP_FIELDS = [
140242
+ "ackSentAt",
140243
+ "projectRegisteredAt",
140244
+ "topicKickoffSentAt",
140245
+ "projectTickedAt",
140246
+ "completionAckSentAt"
140247
+ ];
140248
+ function shouldPersistBootstrapCheckpoint(current, next) {
140249
+ if (!current) return { ok: true };
140250
+ if (current.conversationId !== next.conversationId) return { ok: true };
140251
+ const currentAttemptSeq = current.attemptSeq ?? null;
140252
+ const nextAttemptSeq = next.attemptSeq ?? null;
140253
+ if (currentAttemptSeq != null && nextAttemptSeq != null && nextAttemptSeq < currentAttemptSeq) {
140254
+ return { ok: false, reason: "stale_regression" };
140255
+ }
140256
+ const sameAttempt = Boolean(
140257
+ current.attemptId && next.attemptId && current.attemptSeq != null && next.attemptSeq != null && current.attemptId === next.attemptId && current.attemptSeq === next.attemptSeq
140258
+ );
140259
+ if (!sameAttempt) return { ok: true };
140260
+ for (const field of MONOTONIC_BOOTSTRAP_FIELDS) {
140261
+ if (current[field] && !next[field]) {
140262
+ return { ok: false, reason: "stale_regression" };
140263
+ }
140264
+ }
140265
+ return { ok: true };
140266
+ }
140267
+ async function readTelegramBootstrapSession(workspaceDir, conversationId) {
140268
+ const canonicalConversationId = toCanonicalTelegramBootstrapConversationId(conversationId);
140269
+ const paths = [
140270
+ sessionPath(workspaceDir, canonicalConversationId),
140271
+ alternateSessionPath(workspaceDir, conversationId)
140272
+ ].filter((entry) => Boolean(entry));
140273
+ try {
140274
+ for (const candidatePath of paths) {
140275
+ try {
140276
+ const raw = await fs35.readFile(candidatePath, "utf-8");
140277
+ const session = normalizeStoredSession(JSON.parse(raw));
140278
+ if (session.status === "failed" && isReleasedClassifyFailure(session.error)) {
140279
+ await deleteTelegramBootstrapSession(workspaceDir, canonicalConversationId);
140280
+ return null;
140281
+ }
140282
+ return session;
140283
+ } catch (error48) {
140284
+ if (error48?.code === "ENOENT") continue;
140285
+ throw error48;
140286
+ }
140287
+ }
140288
+ return null;
140289
+ } catch (error48) {
140290
+ if (error48?.code === "ENOENT") return null;
140291
+ throw error48;
140292
+ }
140293
+ }
140294
+ async function deleteTelegramBootstrapSession(workspaceDir, conversationId) {
140295
+ await fs35.unlink(sessionPath(workspaceDir, conversationId)).catch(() => {
140296
+ });
140297
+ const legacyPath = alternateSessionPath(workspaceDir, conversationId);
140298
+ if (legacyPath) {
140299
+ await fs35.unlink(legacyPath).catch(() => {
140300
+ });
140301
+ }
140302
+ }
140303
+ async function writeTelegramBootstrapSession(workspaceDir, session) {
140304
+ const canonicalSession = normalizeStoredSession(session);
140305
+ const dir = sessionsDir(workspaceDir);
140306
+ await fs35.mkdir(dir, { recursive: true });
140307
+ const file2 = sessionPath(workspaceDir, canonicalSession.conversationId);
140308
+ const tmp = `${file2}.${randomUUID4()}.tmp`;
140309
+ await fs35.writeFile(tmp, JSON.stringify(canonicalSession, null, 2) + "\n", "utf-8");
140310
+ await fs35.rename(tmp, file2);
140311
+ const legacyPath = alternateSessionPath(workspaceDir, session.conversationId);
140312
+ if (legacyPath) {
140313
+ await fs35.unlink(legacyPath).catch(() => {
140314
+ });
140315
+ }
140316
+ }
140317
+ async function upsertTelegramBootstrapSession(workspaceDir, input) {
140318
+ const conversationId = toCanonicalTelegramBootstrapConversationId(input.conversationId);
140319
+ const existing = await readTelegramBootstrapSession(workspaceDir, conversationId);
140320
+ const resolvedSourceRoute = resolveNullableField(input.sourceRoute, existing?.sourceRoute);
140321
+ const resolvedProjectRoute = resolveNullableField(input.projectRoute, existing?.projectRoute);
140322
+ const resolvedProjectName = resolveNullableField(input.projectName, existing?.projectName);
140323
+ const resolvedStackHint = resolveNullableField(input.stackHint, existing?.stackHint);
140324
+ const resolvedRepoUrl = resolveNullableField(input.repoUrl, existing?.repoUrl);
140325
+ const resolvedRepoPath = resolveNullableField(input.repoPath, existing?.repoPath);
140326
+ const resolvedProjectSlug = resolveNullableField(input.projectSlug, existing?.projectSlug);
140327
+ const resolvedIssueId = resolveNullableField(input.issueId, existing?.issueId);
140328
+ const resolvedIssueUrl = resolveNullableField(input.issueUrl, existing?.issueUrl);
140329
+ const resolvedTriageReadyForDispatch = resolveNullableField(input.triageReadyForDispatch, existing?.triageReadyForDispatch);
140330
+ const resolvedTriageErrors = input.triageErrors !== void 0 ? input.triageErrors : existing?.triageErrors ?? null;
140331
+ const resolvedMessageThreadId = resolveNullableField(input.messageThreadId, existing?.messageThreadId);
140332
+ const resolvedProjectChannelId = resolveNullableField(input.projectChannelId, existing?.projectChannelId);
140333
+ const resolvedAttemptCount = resolveNullableField(input.attemptCount, existing?.attemptCount, 0);
140334
+ const resolvedAttemptId = resolveNullableField(input.attemptId, existing?.attemptId);
140335
+ const resolvedAttemptSeq = resolveNullableField(input.attemptSeq, existing?.attemptSeq);
140336
+ const resolvedClassifySessionKey = resolveNullableField(input.classifySessionKey, existing?.classifySessionKey);
140337
+ const resolvedClassifyRunId = resolveNullableField(input.classifyRunId, existing?.classifyRunId);
140338
+ const resolvedClassifyStartedAt = resolveNullableField(input.classifyStartedAt, existing?.classifyStartedAt);
140339
+ const resolvedBootstrapStep = resolveNullableField(input.bootstrapStep, existing?.bootstrapStep);
140340
+ const resolvedNextRetryAt = input.nextRetryAt !== void 0 ? input.nextRetryAt : defaultNextRetryAtForStatus(input.status, existing?.nextRetryAt);
140341
+ const resolvedAckSentAt = resolveNullableField(input.ackSentAt, existing?.ackSentAt);
140342
+ const resolvedProjectRegisteredAt = resolveNullableField(input.projectRegisteredAt, existing?.projectRegisteredAt);
140343
+ const resolvedTopicKickoffSentAt = resolveNullableField(input.topicKickoffSentAt, existing?.topicKickoffSentAt);
140344
+ const resolvedProjectTickedAt = resolveNullableField(input.projectTickedAt, existing?.projectTickedAt);
140345
+ const resolvedCompletionAckSentAt = resolveNullableField(input.completionAckSentAt, existing?.completionAckSentAt);
140346
+ const resolvedError = input.error !== void 0 ? input.error : input.lastError !== void 0 ? input.lastError : existing?.error ?? existing?.lastError ?? null;
140347
+ const requestHash = buildBootstrapRequestHash({
140348
+ rawIdea: input.rawIdea,
140349
+ projectName: resolvedProjectName,
140350
+ stackHint: resolvedStackHint,
140351
+ repoUrl: resolvedRepoUrl,
140352
+ repoPath: resolvedRepoPath
140353
+ });
140354
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
140355
+ const session = {
140356
+ id: existing?.id ?? buildBootstrapSessionId(conversationId, input.rawIdea),
140357
+ conversationId,
140358
+ sourceChannel: input.sourceChannel ?? input.sourceRoute?.channel ?? existing?.sourceChannel ?? "telegram",
140359
+ sourceRoute: resolvedSourceRoute,
140360
+ projectRoute: resolvedProjectRoute,
140361
+ requestHash,
140362
+ requestFingerprint: requestHash,
140363
+ lastCompletedRequestHash: input.status === "completed" ? requestHash : existing?.lastCompletedRequestHash ?? null,
140364
+ rawIdea: input.rawIdea,
140365
+ projectName: resolvedProjectName,
140366
+ stackHint: resolvedStackHint,
140367
+ repoUrl: resolvedRepoUrl,
140368
+ repoPath: resolvedRepoPath,
140369
+ projectSlug: resolvedProjectSlug,
140370
+ issueId: resolvedIssueId,
140371
+ issueUrl: resolvedIssueUrl,
140372
+ triageReadyForDispatch: resolvedTriageReadyForDispatch,
140373
+ triageErrors: resolvedTriageErrors,
140374
+ messageThreadId: resolvedMessageThreadId,
140375
+ projectChannelId: resolvedProjectChannelId,
140376
+ language: input.language ?? existing?.language,
140377
+ status: input.status,
140378
+ attemptId: resolvedAttemptId,
140379
+ attemptSeq: resolvedAttemptSeq,
140380
+ classifySessionKey: resolvedClassifySessionKey,
140381
+ classifyRunId: resolvedClassifyRunId,
140382
+ classifyStartedAt: resolvedClassifyStartedAt,
140383
+ bootstrapStep: resolvedBootstrapStep,
140384
+ attemptCount: resolvedAttemptCount,
140385
+ lastError: resolvedError,
140386
+ nextRetryAt: resolvedNextRetryAt,
140387
+ ackSentAt: resolvedAckSentAt,
140388
+ projectRegisteredAt: resolvedProjectRegisteredAt,
140389
+ topicKickoffSentAt: resolvedTopicKickoffSentAt,
140390
+ projectTickedAt: resolvedProjectTickedAt,
140391
+ completionAckSentAt: resolvedCompletionAckSentAt,
140392
+ pendingClarification: input.pendingClarification !== void 0 ? input.pendingClarification : existing?.pendingClarification ?? null,
140393
+ orphanedArtifacts: input.orphanedArtifacts !== void 0 ? input.orphanedArtifacts : existing?.orphanedArtifacts ?? null,
140394
+ createdAt: existing?.createdAt ?? now2,
140395
+ updatedAt: now2,
140396
+ suppressUntil: nextSuppressUntil(input.status),
140397
+ error: resolvedError
140398
+ };
140399
+ const writeDecision = shouldPersistBootstrapCheckpoint(existing, session);
140400
+ if (!writeDecision.ok && existing) {
140401
+ return existing;
140402
+ }
140403
+ await writeTelegramBootstrapSession(workspaceDir, session);
140404
+ return session;
140405
+ }
140406
+ function shouldSuppressTelegramBootstrapReply(session, request) {
140407
+ if (!session) return false;
140408
+ if (isTelegramBootstrapSessionExpired(session)) return false;
140409
+ if (session.status === "completed" || session.status === "failed") {
140410
+ if (session.status === "failed" && isReleasedClassifyFailure(session.error)) return false;
140411
+ if (!request) return false;
140412
+ return buildBootstrapRequestFingerprint(request) === session.requestHash;
140413
+ }
140414
+ if (!request) return true;
140415
+ return buildBootstrapRequestFingerprint(request) === session.requestHash;
140416
+ }
140417
+ function isTelegramBootstrapSessionExpired(session, now2 = Date.now()) {
140418
+ if (!session) return false;
140419
+ const suppressUntil = Date.parse(session.suppressUntil);
140420
+ return !Number.isNaN(suppressUntil) && suppressUntil < now2;
140421
+ }
140422
+ function isRecoverableTelegramBootstrapSession(session) {
140423
+ return session?.status === "bootstrapping" || session?.status === "dispatching";
140424
+ }
140425
+ function isClaimableTelegramBootstrapSession(session, now2 = Date.now()) {
140426
+ if (!isRecoverableTelegramBootstrapSession(session)) return false;
140427
+ if (!session.nextRetryAt) return true;
140428
+ const retryAt = Date.parse(session.nextRetryAt);
140429
+ return Number.isNaN(retryAt) || retryAt <= now2;
140430
+ }
140431
+ function isSupersededTelegramBootstrapAttempt(current, candidate) {
140432
+ if (!current || !candidate) return false;
140433
+ if (current.conversationId !== candidate.conversationId) return true;
140434
+ const currentHasAttempt = current.attemptId != null && current.attemptSeq != null;
140435
+ const candidateHasAttempt = candidate.attemptId != null && candidate.attemptSeq != null;
140436
+ if (currentHasAttempt || candidateHasAttempt) {
140437
+ return current.attemptId !== candidate.attemptId || current.attemptSeq !== candidate.attemptSeq;
140438
+ }
140439
+ return current.requestHash !== candidate.requestHash || current.status !== candidate.status || current.updatedAt !== candidate.updatedAt;
140440
+ }
140441
+
140442
+ // lib/intake/steps/create-task.ts
139767
140443
  function buildIssueBody(payload) {
139768
140444
  const spec = payload.spec;
139769
140445
  const sections = [];
@@ -139835,6 +140511,26 @@ var createTaskStep = {
139835
140511
  created_at: (/* @__PURE__ */ new Date()).toISOString()
139836
140512
  };
139837
140513
  ctx.log(`Issue created via provider: #${issue3.number} \u2014 ${issue3.url}`);
140514
+ const bootstrapConversationId2 = payload.metadata?.source === "telegram-dm-bootstrap" ? payload.metadata?.channel_id : null;
140515
+ if (bootstrapConversationId2) {
140516
+ await upsertTelegramBootstrapSession(ctx.workspaceDir, {
140517
+ conversationId: String(bootstrapConversationId2),
140518
+ rawIdea: payload.raw_idea,
140519
+ projectName: payload.metadata?.project_name ?? null,
140520
+ stackHint: payload.metadata?.stack_hint ?? null,
140521
+ repoUrl: repoUrl ?? null,
140522
+ repoPath: repoPath ?? null,
140523
+ status: "dispatching",
140524
+ bootstrapStep: "project_registered",
140525
+ projectSlug: projectSlug ?? null,
140526
+ issueId: issue3.number,
140527
+ issueUrl: issue3.url,
140528
+ projectChannelId: payload.metadata?.channel_id ?? null,
140529
+ messageThreadId: payload.metadata?.message_thread_id ?? null,
140530
+ projectRegisteredAt: (/* @__PURE__ */ new Date()).toISOString()
140531
+ }).catch(() => {
140532
+ });
140533
+ }
139838
140534
  return {
139839
140535
  ...payload,
139840
140536
  step: "create-task",
@@ -139870,6 +140566,26 @@ var createTaskStep = {
139870
140566
  created_at: (/* @__PURE__ */ new Date()).toISOString()
139871
140567
  };
139872
140568
  ctx.log(`Issue created via compatibility fallback: #${issueNumber} \u2014 ${issueUrl}`);
140569
+ const bootstrapConversationId = payload.metadata?.source === "telegram-dm-bootstrap" ? payload.metadata?.channel_id : null;
140570
+ if (bootstrapConversationId) {
140571
+ await upsertTelegramBootstrapSession(ctx.workspaceDir, {
140572
+ conversationId: String(bootstrapConversationId),
140573
+ rawIdea: payload.raw_idea,
140574
+ projectName: payload.metadata?.project_name ?? null,
140575
+ stackHint: payload.metadata?.stack_hint ?? null,
140576
+ repoUrl: repoUrl ?? null,
140577
+ repoPath: repoPath ?? null,
140578
+ status: "dispatching",
140579
+ bootstrapStep: "project_registered",
140580
+ projectSlug: projectSlug ?? null,
140581
+ issueId: issue2.number,
140582
+ issueUrl: issue2.url,
140583
+ projectChannelId: payload.metadata?.channel_id ?? null,
140584
+ messageThreadId: payload.metadata?.message_thread_id ?? null,
140585
+ projectRegisteredAt: (/* @__PURE__ */ new Date()).toISOString()
140586
+ }).catch(() => {
140587
+ });
140588
+ }
139873
140589
  return {
139874
140590
  ...payload,
139875
140591
  step: "create-task",
@@ -139982,9 +140698,66 @@ function determineLevel(effort, targetState) {
139982
140698
  if (targetState === "To Research" && level === "medior") level = "junior";
139983
140699
  return level;
139984
140700
  }
140701
+ function assessComplexity(input) {
140702
+ const broadSignals = detectRawIdeaComplexity(input.rawIdea).signals.length;
140703
+ if (input.filesChanged >= 18 || input.acCount >= 8 || broadSignals >= 4 || input.totalRisks >= 4) return "high";
140704
+ if (input.filesChanged >= 8 || input.acCount >= 4 || broadSignals >= 2 || input.totalRisks >= 2) return "medium";
140705
+ return "low";
140706
+ }
140707
+ function assessCoupling(input) {
140708
+ const text = `${input.rawIdea} ${input.scopeText}`.toLowerCase();
140709
+ const crossCuttingSignals = [
140710
+ /\bauth\b|\bpermission\b|\brbac\b/,
140711
+ /\bmigration\b|\bschema\b|\bdatabase\b/,
140712
+ /\bbackground\b|\bworker\b|\bqueue\b|\bcron\b/,
140713
+ /\bfrontend\b|\bdashboard\b|\bui\b/,
140714
+ /\bintegration\b|\bwebhook\b|\bexternal\b/
140715
+ ].filter((regex) => regex.test(text)).length;
140716
+ if (crossCuttingSignals >= 4) return "high";
140717
+ if (crossCuttingSignals >= 2) return "medium";
140718
+ return "low";
140719
+ }
140720
+ function assessParallelizability(input, coupling) {
140721
+ const text = `${input.rawIdea} ${input.scopeText}`.toLowerCase();
140722
+ const capabilitySignals = [
140723
+ /\bauth\b/,
140724
+ /\bapi\b|\bendpoint\b|\broute\b/,
140725
+ /\bfrontend\b|\bdashboard\b|\bui\b/,
140726
+ /\bworker\b|\bqueue\b|\bbackground\b/,
140727
+ /\bnotification\b|\bemail\b|\bsms\b|\balert\b/,
140728
+ /\bdatabase\b|\bmigration\b|\bschema\b/
140729
+ ].filter((regex) => regex.test(text)).length;
140730
+ if (coupling === "high") return "low";
140731
+ if (capabilitySignals >= 4) return "high";
140732
+ if (capabilitySignals >= 2) return "medium";
140733
+ return "low";
140734
+ }
140735
+ function assessQualityCriticality(input) {
140736
+ const text = `${input.rawIdea} ${input.objective} ${input.scopeText}`.toLowerCase();
140737
+ if (/\bauth\b|\boauth\b|\bjwt\b|\brbac\b|\bpayment\b|\bbilling\b|\bpii\b|\bsecure\b|\bsecurity\b/.test(text)) return "high";
140738
+ if (/\bperformance\b|\bscalable\b|\bbackground\b|\bworker\b|\bintegration\b|\bwebhook\b/.test(text)) return "medium";
140739
+ return "low";
140740
+ }
140741
+ function buildRiskProfile(input) {
140742
+ const text = `${input.rawIdea} ${input.objective} ${input.scopeText}`.toLowerCase();
140743
+ const risks = [
140744
+ /\bauth\b|\boauth\b|\bjwt\b|\brbac\b/.test(text) ? "auth" : null,
140745
+ /\bpayment\b|\bbilling\b|\bsubscription\b/.test(text) ? "payments" : null,
140746
+ /\bdatabase\b|\bmigration\b|\bschema\b/.test(text) ? "data_model" : null,
140747
+ /\bworker\b|\bqueue\b|\bbackground\b|\bcron\b/.test(text) ? "async_processing" : null,
140748
+ /\bintegration\b|\bwebhook\b|\bexternal\b/.test(text) ? "external_integration" : null,
140749
+ /\bperformance\b|\blatency\b|\bthroughput\b/.test(text) ? "performance" : null
140750
+ ];
140751
+ return Array.from(new Set(risks.filter((risk) => Boolean(risk))));
140752
+ }
139985
140753
  function runTriageLogic(input, matrix) {
139986
140754
  const effort = calculateEffort(input.filesChanged, input.acCount, input.rawIdea);
139987
140755
  const { priority, label: priorityLabel } = calculatePriority(input.type, effort, input.totalRisks, matrix);
140756
+ const complexity = assessComplexity(input);
140757
+ const coupling = assessCoupling(input);
140758
+ const parallelizability = assessParallelizability(input, coupling);
140759
+ const qualityCriticality = assessQualityCriticality(input);
140760
+ const riskProfile = buildRiskProfile(input);
139988
140761
  const effortLabel = matrix.effort_rules[effort]?.label ?? `effort:${effort}`;
139989
140762
  const typeLabel = matrix.auto_labels[input.type] ?? "";
139990
140763
  const targetState = matrix.target_state_by_type[input.type] ?? matrix.target_state_by_type.default ?? "To Do";
@@ -139996,7 +140769,7 @@ function runTriageLogic(input, matrix) {
139996
140769
  objective: input.objective ?? input.rawIdea,
139997
140770
  scopeItems: (input.scopeText ?? "").split("\n").filter((l) => l.trim()),
139998
140771
  acceptanceCriteria: (input.acText ?? "").split("\n").filter((l) => l.trim()),
139999
- dod: input.scopeText ?? ""
140772
+ dod: input.dodText ?? ""
140000
140773
  });
140001
140774
  const specQualityBlock = specQualityErrors.length > 0;
140002
140775
  return {
@@ -140004,6 +140777,11 @@ function runTriageLogic(input, matrix) {
140004
140777
  priorityLabel,
140005
140778
  effort,
140006
140779
  effortLabel,
140780
+ complexity,
140781
+ coupling,
140782
+ parallelizability,
140783
+ qualityCriticality,
140784
+ riskProfile,
140007
140785
  typeLabel,
140008
140786
  targetState,
140009
140787
  dispatchLabel,
@@ -140038,47 +140816,333 @@ function validateSpecQuality(input) {
140038
140816
  return errors;
140039
140817
  }
140040
140818
 
140819
+ // lib/intake/lib/decomposition-planner.ts
140820
+ var CAPABILITY_PATTERNS = [
140821
+ { area: "auth", label: "Authentication & Access", regex: /\b(auth|oauth|jwt|login|register|session|permission|rbac|role)\b/i },
140822
+ { area: "worker", label: "Background Jobs & Automation", regex: /\b(worker|background|queue|job|scheduler|reminder|async|cron)\b/i },
140823
+ { area: "notifications", label: "Notifications & Delivery", regex: /\b(notification|notify|email|sms|webhook|alert|message)\b/i },
140824
+ { area: "frontend", label: "Frontend & UX", regex: /\b(ui|frontend|page|screen|dashboard|view|form)\b/i },
140825
+ { area: "data", label: "Data Model & Persistence", regex: /\b(database|schema|model|migration|persist|storage|repository|crud)\b/i },
140826
+ { area: "api", label: "API & Application Flow", regex: /\b(api|endpoint|route|http|rest|graphql|handler)\b/i },
140827
+ { area: "integration", label: "Integrations & External Services", regex: /\b(integration|provider|third-party|external|sync|import|export)\b/i },
140828
+ { area: "qa", label: "QA, Docs & Release Readiness", regex: /\b(test|qa|coverage|document|docs|guide|readme|validation)\b/i },
140829
+ { area: "core", label: "Core Workflow", regex: /./i }
140830
+ ];
140831
+ var AREA_LABEL = Object.fromEntries(
140832
+ CAPABILITY_PATTERNS.map((entry) => [entry.area, entry.label])
140833
+ );
140834
+ function normalizeLines(items) {
140835
+ return items.map((item) => item.trim()).filter(Boolean);
140836
+ }
140837
+ function detectCapabilityArea(text, deliveryTarget) {
140838
+ const normalized = text.trim();
140839
+ for (const entry of CAPABILITY_PATTERNS) {
140840
+ if (entry.regex.test(normalized)) return entry.area;
140841
+ }
140842
+ if (deliveryTarget === "api") return "api";
140843
+ if (deliveryTarget === "web-ui") return "frontend";
140844
+ return "core";
140845
+ }
140846
+ function matchesArea(text, area) {
140847
+ const entry = CAPABILITY_PATTERNS.find((candidate) => candidate.area === area);
140848
+ return entry ? entry.regex.test(text) : false;
140849
+ }
140850
+ function estimateEffort(scopeItems, area) {
140851
+ if (scopeItems.length >= 3 || ["auth", "worker", "integration"].includes(area)) return "large";
140852
+ if (scopeItems.length === 2 || ["api", "data", "frontend", "notifications"].includes(area)) return "medium";
140853
+ return "small";
140854
+ }
140855
+ function recommendedLevelFor(area, estimatedEffort) {
140856
+ if (estimatedEffort === "large") return "senior";
140857
+ if (["auth", "worker", "integration"].includes(area)) return "senior";
140858
+ if (estimatedEffort === "medium") return "medior";
140859
+ if (area === "qa") return "junior";
140860
+ return "medior";
140861
+ }
140862
+ function isFoundationArea(area) {
140863
+ return area === "auth" || area === "data" || area === "core";
140864
+ }
140865
+ function filterRelevantItems(items, scopeItems, area) {
140866
+ const normalized = normalizeLines(items);
140867
+ if (normalized.length === 0) return [];
140868
+ const scopeText = scopeItems.join(" ");
140869
+ const matches2 = normalized.filter((item) => matchesArea(item, area) || scopeItems.some((scopeItem) => item.includes(scopeItem)) || item.includes(scopeText));
140870
+ if (matches2.length > 0) return matches2;
140871
+ return normalized.slice(0, Math.min(normalized.length, Math.max(2, scopeItems.length)));
140872
+ }
140873
+ function buildObjective(parentObjective, area, scopeItems) {
140874
+ const label = AREA_LABEL[area];
140875
+ const primaryScope = scopeItems[0] ?? parentObjective;
140876
+ return `Deliver the ${label.toLowerCase()} slice of the parent initiative by completing ${primaryScope.toLowerCase()}.`;
140877
+ }
140878
+ function buildDependencyIndexes(area, priorAreas) {
140879
+ const indexes = [];
140880
+ priorAreas.forEach((priorArea, index) => {
140881
+ if (isFoundationArea(priorArea) && !isFoundationArea(area)) indexes.push(index);
140882
+ if (area !== "auth" && priorArea === "auth") indexes.push(index);
140883
+ if (["api", "frontend", "worker", "notifications"].includes(area) && priorArea === "data") indexes.push(index);
140884
+ if (["worker", "notifications", "integration"].includes(area) && priorArea === "api") indexes.push(index);
140885
+ });
140886
+ return Array.from(new Set(indexes));
140887
+ }
140888
+ function buildDependencyHints(area, priorAreas, allAreas) {
140889
+ const hints = [];
140890
+ if (area !== "auth" && allAreas.includes("auth")) hints.push("Coordinate with the authentication/access child before changing protected flows.");
140891
+ if (["worker", "notifications", "integration"].includes(area) && allAreas.includes("api")) {
140892
+ hints.push("Align payload contracts and triggering conditions with the API/application-flow child.");
140893
+ }
140894
+ if (["api", "frontend", "worker", "notifications"].includes(area) && allAreas.includes("data")) {
140895
+ hints.push("Reuse the canonical data model/migration decisions from the persistence child.");
140896
+ }
140897
+ if (priorAreas.some(isFoundationArea) && !isFoundationArea(area)) {
140898
+ hints.push("Start after foundational contracts are stable, but keep implementation isolated enough for a separate PR.");
140899
+ }
140900
+ return Array.from(new Set(hints));
140901
+ }
140902
+ function buildPlannedChildren(spec, effort) {
140903
+ const scopeItems = normalizeLines(spec.scope_v1);
140904
+ const maxChildren = effort === "xlarge" ? 4 : 3;
140905
+ const orderedAreas = [];
140906
+ const areaToItems = /* @__PURE__ */ new Map();
140907
+ for (const item of scopeItems) {
140908
+ const area = detectCapabilityArea(item, spec.delivery_target);
140909
+ if (!areaToItems.has(area)) {
140910
+ areaToItems.set(area, []);
140911
+ orderedAreas.push(area);
140912
+ }
140913
+ areaToItems.get(area).push(item);
140914
+ }
140915
+ let planned = orderedAreas.map((area) => ({ capabilityArea: area, scopeItems: areaToItems.get(area) ?? [] }));
140916
+ if (planned.length > maxChildren) {
140917
+ const kept = planned.slice(0, maxChildren - 1);
140918
+ const merged = planned.slice(maxChildren - 1).flatMap((entry) => entry.scopeItems);
140919
+ kept.push({ capabilityArea: planned[maxChildren - 1]?.capabilityArea ?? "core", scopeItems: merged });
140920
+ planned = kept;
140921
+ }
140922
+ if (planned.length === 1 && scopeItems.length >= 4) {
140923
+ const midpoint = Math.ceil(scopeItems.length / 2);
140924
+ planned = [
140925
+ { capabilityArea: planned[0]?.capabilityArea ?? "core", scopeItems: scopeItems.slice(0, midpoint) },
140926
+ { capabilityArea: planned[0]?.capabilityArea ?? "core", scopeItems: scopeItems.slice(midpoint) }
140927
+ ];
140928
+ }
140929
+ return planned.filter((entry) => entry.scopeItems.length > 0);
140930
+ }
140931
+ function buildDescription(opts) {
140932
+ const { issueNumber, child, outOfScope, constraints, risks } = opts;
140933
+ return [
140934
+ "## Objective",
140935
+ child.objective,
140936
+ "",
140937
+ "## Parent Issue",
140938
+ `Parent issue: #${issueNumber}`,
140939
+ "",
140940
+ "## Capability Area",
140941
+ `- ${AREA_LABEL[child.capabilityArea]}`,
140942
+ "",
140943
+ "## Execution Profile",
140944
+ `- Recommended level: ${child.recommendedLevel}`,
140945
+ `- Estimated effort: ${child.estimatedEffort}`,
140946
+ `- Parallelizable: ${child.parallelizable ? "yes" : "no"}`,
140947
+ ...child.dependencyHints.map((hint) => `- Dependency hint: ${hint}`),
140948
+ "",
140949
+ "## Scope",
140950
+ ...child.scopeItems.map((item) => `- ${item}`),
140951
+ "",
140952
+ "## Acceptance Criteria",
140953
+ ...child.acceptanceCriteria.map((item) => `- ${item}`),
140954
+ "",
140955
+ "## Definition of Done",
140956
+ ...child.definitionOfDone.map((item) => `- ${item}`),
140957
+ ...outOfScope.length > 0 ? ["", "## Out of Scope", ...outOfScope.map((item) => `- ${item}`)] : [],
140958
+ ...constraints?.trim() ? ["", "## Constraints", constraints.trim()] : [],
140959
+ ...risks.length > 0 ? ["", "## Risks / Coordination Notes", ...risks.map((item) => `- ${item}`)] : []
140960
+ ].join("\n");
140961
+ }
140962
+ function buildDecompositionChildDrafts(spec, issueNumber, effort) {
140963
+ const plannedChildren = buildPlannedChildren(spec, effort);
140964
+ const allAreas = plannedChildren.map((entry) => entry.capabilityArea);
140965
+ const areaCounts = /* @__PURE__ */ new Map();
140966
+ return plannedChildren.map((plannedChild, index) => {
140967
+ const estimatedEffort = estimateEffort(plannedChild.scopeItems, plannedChild.capabilityArea);
140968
+ const recommendedLevel = recommendedLevelFor(plannedChild.capabilityArea, estimatedEffort);
140969
+ const priorAreas = plannedChildren.slice(0, index).map((entry) => entry.capabilityArea);
140970
+ const dependencyIndexes = buildDependencyIndexes(plannedChild.capabilityArea, priorAreas);
140971
+ const dependencyHints = buildDependencyHints(plannedChild.capabilityArea, priorAreas, allAreas);
140972
+ const parallelizable = dependencyIndexes.length === 0 && !isFoundationArea(plannedChild.capabilityArea);
140973
+ const acceptanceCriteria = filterRelevantItems(spec.acceptance_criteria, plannedChild.scopeItems, plannedChild.capabilityArea);
140974
+ const definitionOfDone = filterRelevantItems(spec.definition_of_done, plannedChild.scopeItems, plannedChild.capabilityArea);
140975
+ const occurrence = (areaCounts.get(plannedChild.capabilityArea) ?? 0) + 1;
140976
+ areaCounts.set(plannedChild.capabilityArea, occurrence);
140977
+ const titleSuffix = occurrence > 1 ? ` ${occurrence}` : "";
140978
+ const title = `${spec.title} \u2014 ${AREA_LABEL[plannedChild.capabilityArea]}${titleSuffix}`;
140979
+ const child = {
140980
+ title,
140981
+ objective: buildObjective(spec.objective, plannedChild.capabilityArea, plannedChild.scopeItems),
140982
+ scopeItems: plannedChild.scopeItems,
140983
+ acceptanceCriteria,
140984
+ definitionOfDone,
140985
+ recommendedLevel,
140986
+ estimatedEffort,
140987
+ dependencyHints,
140988
+ dependencyIndexes,
140989
+ capabilityArea: plannedChild.capabilityArea,
140990
+ parallelizable,
140991
+ description: ""
140992
+ };
140993
+ child.description = buildDescription({
140994
+ issueNumber,
140995
+ parentObjective: spec.objective,
140996
+ child,
140997
+ outOfScope: normalizeLines(spec.out_of_scope),
140998
+ constraints: spec.constraints,
140999
+ risks: normalizeLines(spec.risks)
141000
+ });
141001
+ return child;
141002
+ });
141003
+ }
141004
+
141005
+ // lib/intake/lib/fidelity-brief.ts
141006
+ var HARD_CONSTRAINT_PATTERNS = [
141007
+ { regex: /\bmust use ([a-z0-9.+#_-]+)/i, value: "explicit_stack_requirement" },
141008
+ { regex: /\buse ([a-z0-9.+#_-]+)\b/i, value: "explicit_technology_requirement" },
141009
+ { regex: /\bwithout ([a-z0-9.+#_-]+)/i, value: "explicit_technology_exclusion" },
141010
+ { regex: /\bself-host(?:ed)?\b/i, value: "self_hosted" },
141011
+ { regex: /\boffline\b/i, value: "offline_capable" },
141012
+ { regex: /\bopen source\b/i, value: "open_source_only" }
141013
+ ];
141014
+ var SOFT_PREFERENCE_PATTERNS = [
141015
+ { regex: /\bprefer\b/i, value: "explicit_preference" },
141016
+ { regex: /\bclean (?:architecture|code)\b/i, value: "clean_code_preference" },
141017
+ { regex: /\bmodern\b/i, value: "modern_stack_preference" },
141018
+ { regex: /\bsimple\b/i, value: "simplicity_preference" }
141019
+ ];
141020
+ var QUALITY_EXPECTATION_PATTERNS = [
141021
+ { regex: /\bproduction[- ]ready\b/i, value: "production_ready" },
141022
+ { regex: /\bhigh[- ]performance\b|\bperformant\b/i, value: "performance" },
141023
+ { regex: /\bsecure\b|\bsecurity\b/i, value: "security" },
141024
+ { regex: /\bscalable\b|\bscale\b/i, value: "scalability" },
141025
+ { regex: /\bmaintainable\b|\bclean code\b/i, value: "maintainability" },
141026
+ { regex: /\bwell[- ]tested\b|\bhigh[- ]quality\b/i, value: "high_quality" }
141027
+ ];
141028
+ var RISK_SIGNAL_PATTERNS = [
141029
+ { regex: /\bauth\b|\blogin\b|\boauth\b|\bjwt\b|\brbac\b|\bpermission/i, value: "auth_security_sensitive" },
141030
+ { regex: /\bpayment\b|\bbilling\b|\bsubscription/i, value: "payment_sensitive" },
141031
+ { regex: /\bmigration\b|\bschema\b|\bdatabase\b/i, value: "data_model_change" },
141032
+ { regex: /\bworker\b|\bqueue\b|\bcron\b|\bbackground job\b/i, value: "async_orchestration" },
141033
+ { regex: /\bintegration\b|\bwebhook\b|\bthird-party\b|\bexternal service\b/i, value: "external_integration" },
141034
+ { regex: /\bperformance\b|\bhigh traffic\b|\blow latency\b/i, value: "performance_sensitive" }
141035
+ ];
141036
+ var EXPLICIT_NON_GOAL_PATTERNS = [
141037
+ { regex: /\bdo not add ([^.\n]+)/i, value: "no_extra_features" },
141038
+ { regex: /\bout of scope\b/i, value: "explicit_out_of_scope" },
141039
+ { regex: /\bnot required\b/i, value: "explicit_not_required" }
141040
+ ];
141041
+ function unique(values) {
141042
+ return Array.from(new Set(values.filter((value) => Boolean(value && value.trim()))));
141043
+ }
141044
+ function normalizeText3(text) {
141045
+ return text.replace(/\s+/g, " ").trim();
141046
+ }
141047
+ function inferDeliverable(rawIdea, spec, metadata) {
141048
+ const target = spec?.delivery_target ?? metadata?.delivery_target;
141049
+ if (target && target !== "unknown") return target;
141050
+ const text = rawIdea.toLowerCase();
141051
+ if (/\bcli\b|\bcommand line\b/.test(text)) return "cli";
141052
+ if (/\bapi\b|\bendpoint\b|\brest\b|\bgraphql\b/.test(text)) return "api";
141053
+ if (/\bweb app\b|\bfrontend\b|\bui\b|\bdashboard\b|\bpage\b/.test(text)) return "web-ui";
141054
+ if (/\blibrary\b|\bsdk\b|\bpackage\b/.test(text)) return "library";
141055
+ if (/\bautomation\b|\bworker\b|\bjob\b|\bbot\b/.test(text)) return "automation";
141056
+ return "unknown";
141057
+ }
141058
+ function inferPrimaryObjective(rawIdea, spec) {
141059
+ if (spec?.objective?.trim()) return normalizeText3(spec.objective);
141060
+ const sentence = rawIdea.split(/(?<=[.!?])\s+/)[0] ?? rawIdea;
141061
+ return normalizeText3(sentence);
141062
+ }
141063
+ function collectMatches(text, patterns) {
141064
+ return unique(patterns.map((pattern) => pattern.regex.test(text) ? pattern.value : null));
141065
+ }
141066
+ function deriveAmbiguityFlags(rawIdea, deliverable, requestedStack, inferredStack, spec) {
141067
+ const flags = [];
141068
+ if (deliverable === "unknown") flags.push("ambiguous_deliverable");
141069
+ if (!requestedStack && !inferredStack && /\bbuild|create|make\b/i.test(rawIdea) && deliverable !== "unknown") {
141070
+ flags.push("missing_stack_preference");
141071
+ }
141072
+ if (!spec?.scope_v1?.length) flags.push("missing_structured_scope");
141073
+ if (spec && spec.acceptance_criteria.length === 0) flags.push("missing_acceptance_criteria");
141074
+ return unique(flags);
141075
+ }
141076
+ function deriveConfidence(ambiguityFlags, spec, metadata) {
141077
+ if (metadata?.stack_confidence === "high" && ambiguityFlags.length === 0 && (spec?.acceptance_criteria.length ?? 0) >= 2) {
141078
+ return "high";
141079
+ }
141080
+ if (ambiguityFlags.some((flag) => flag === "ambiguous_deliverable" || flag === "missing_structured_scope")) {
141081
+ return "low";
141082
+ }
141083
+ return "medium";
141084
+ }
141085
+ function buildFidelityBrief(opts) {
141086
+ const rawIdea = normalizeText3(opts.rawIdea);
141087
+ const spec = opts.spec;
141088
+ const metadata = opts.metadata;
141089
+ const combinedText = [
141090
+ rawIdea,
141091
+ spec?.objective,
141092
+ spec?.constraints,
141093
+ ...spec?.scope_v1 ?? [],
141094
+ ...spec?.out_of_scope ?? [],
141095
+ ...spec?.acceptance_criteria ?? [],
141096
+ ...spec?.definition_of_done ?? [],
141097
+ ...spec?.risks ?? []
141098
+ ].filter(Boolean).join("\n");
141099
+ const requestedDeliverable = inferDeliverable(rawIdea, spec, metadata);
141100
+ const requestedStack = metadata?.stack_hint ?? null;
141101
+ const inferredStack = requestedStack ?? null;
141102
+ const hardConstraints = collectMatches(combinedText, HARD_CONSTRAINT_PATTERNS);
141103
+ const softPreferences = collectMatches(combinedText, SOFT_PREFERENCE_PATTERNS);
141104
+ const explicitNonGoals = unique([
141105
+ ...collectMatches(combinedText, EXPLICIT_NON_GOAL_PATTERNS),
141106
+ ...(spec?.out_of_scope ?? []).map((item) => normalizeText3(item))
141107
+ ]);
141108
+ const qualityExpectations = collectMatches(combinedText, QUALITY_EXPECTATION_PATTERNS);
141109
+ const riskSignals = collectMatches(combinedText, RISK_SIGNAL_PATTERNS);
141110
+ const ambiguityFlags = deriveAmbiguityFlags(rawIdea, requestedDeliverable, requestedStack, inferredStack, spec);
141111
+ const confidence = deriveConfidence(ambiguityFlags, spec, metadata);
141112
+ return {
141113
+ primary_objective: inferPrimaryObjective(rawIdea, spec),
141114
+ requested_deliverable: requestedDeliverable,
141115
+ requested_stack: requestedStack,
141116
+ inferred_stack: inferredStack,
141117
+ hard_constraints: hardConstraints,
141118
+ soft_preferences: softPreferences,
141119
+ explicit_non_goals: explicitNonGoals,
141120
+ quality_expectations: qualityExpectations,
141121
+ ambiguity_flags: ambiguityFlags,
141122
+ risk_signals: riskSignals,
141123
+ confidence
141124
+ };
141125
+ }
141126
+
140041
141127
  // lib/intake/steps/triage.ts
140042
141128
  import { createRequire as createRequire3 } from "node:module";
140043
141129
  var _require3 = createRequire3(import.meta.url);
140044
141130
  var cachedMatrix = null;
141131
+ function shouldAutoDecompose(decision) {
141132
+ if (!decision.readyForDispatch || decision.specQualityBlock) return false;
141133
+ if (decision.effort === "xlarge") return true;
141134
+ if (decision.effort !== "large") return false;
141135
+ return decision.parallelizability !== "low" && decision.coupling !== "high";
141136
+ }
141137
+ function computeMaxParallelChildren(drafts) {
141138
+ const parallelizableCount = drafts.filter((draft) => draft.parallelizable).length;
141139
+ return Math.min(4, Math.max(1, parallelizableCount || 1));
141140
+ }
140045
141141
  function loadMatrix() {
140046
141142
  if (cachedMatrix) return cachedMatrix;
140047
141143
  cachedMatrix = _require3("../configs/triage-matrix.json");
140048
141144
  return cachedMatrix;
140049
141145
  }
140050
- function buildChildDrafts(payload, issueNumber, effort) {
140051
- const spec = payload.spec;
140052
- const scopeItems = spec.scope_v1.filter((item) => item.trim().length > 0);
140053
- const maxChildren = effort === "xlarge" ? 4 : 3;
140054
- const chunkSize = 2;
140055
- const drafts = [];
140056
- for (let i2 = 0; i2 < scopeItems.length && drafts.length < maxChildren; i2 += chunkSize) {
140057
- const slice = scopeItems.slice(i2, i2 + chunkSize);
140058
- if (slice.length === 0) continue;
140059
- const childIndex = drafts.length + 1;
140060
- drafts.push({
140061
- title: `${spec.title} \u2014 Part ${childIndex}`,
140062
- description: [
140063
- `## Objective`,
140064
- spec.objective,
140065
- "",
140066
- `## Parent Issue`,
140067
- `Parent issue: #${issueNumber}`,
140068
- "",
140069
- `## This Part`,
140070
- ...slice.map((item) => `- ${item}`),
140071
- "",
140072
- `## Acceptance Criteria`,
140073
- ...spec.acceptance_criteria.map((item) => `- ${item}`),
140074
- "",
140075
- `## Definition of Done`,
140076
- ...spec.definition_of_done.map((item) => `- ${item}`)
140077
- ].join("\n")
140078
- });
140079
- }
140080
- return drafts;
140081
- }
140082
141146
  var triageStep = {
140083
141147
  name: "triage",
140084
141148
  shouldRun: (payload) => !!payload.issues?.length && !payload.dry_run,
@@ -140088,6 +141152,11 @@ var triageStep = {
140088
141152
  const impact = payload.impact;
140089
141153
  const issue2 = payload.issues[0];
140090
141154
  ctx.log(`Running triage for issue #${issue2.number}`);
141155
+ const fidelityBrief = buildFidelityBrief({
141156
+ rawIdea: payload.raw_idea,
141157
+ spec,
141158
+ metadata: payload.metadata
141159
+ });
140091
141160
  const decision = runTriageLogic({
140092
141161
  type: spec.type,
140093
141162
  deliveryTarget: spec.delivery_target,
@@ -140100,8 +141169,9 @@ var triageStep = {
140100
141169
  rawIdea: payload.raw_idea,
140101
141170
  acText: spec.acceptance_criteria.join("\n"),
140102
141171
  scopeText: spec.scope_v1.join("\n"),
141172
+ dodText: spec.definition_of_done.join("\n"),
140103
141173
  oosText: spec.out_of_scope.join("\n"),
140104
- authSignal: payload.metadata?.auth_gate?.signal ?? false
141174
+ authSignal: /\b(login|register|jwt|oauth|auth|role-based access|rbac|permission)\b/i.test(`${payload.raw_idea} ${spec.objective}`)
140105
141175
  }, matrix);
140106
141176
  const repoUrl = payload.scaffold?.repo_url ?? payload.metadata?.repo_url ?? "";
140107
141177
  const repoPath = payload.provisioning?.repo_local ?? payload.scaffold?.repo_local ?? payload.metadata?.repo_path ?? payload.project_map?.root ?? void 0;
@@ -140112,8 +141182,9 @@ var triageStep = {
140112
141182
  if (decision.typeLabel) allLabels.push(decision.typeLabel);
140113
141183
  if (!decision.readyForDispatch || decision.specQualityBlock) allLabels.push("needs-human");
140114
141184
  const uniqueLabels = Array.from(new Set(allLabels.filter(Boolean)));
140115
- const shouldDecompose = decision.readyForDispatch && !decision.specQualityBlock && ["large", "xlarge"].includes(decision.effort);
140116
- const decompositionDrafts = shouldDecompose ? buildChildDrafts(payload, issue2.number, decision.effort) : [];
141185
+ const decompositionRequested = decision.readyForDispatch && !decision.specQualityBlock && ["large", "xlarge"].includes(decision.effort);
141186
+ const shouldDecompose = shouldAutoDecompose(decision);
141187
+ const decompositionDrafts = shouldDecompose ? buildDecompositionChildDrafts(spec, issue2.number, decision.effort) : [];
140117
141188
  const canDecompose = decompositionDrafts.length >= 2;
140118
141189
  let createdChildIssueNumbers = [];
140119
141190
  if (ctx.createIssueProvider && repoPath) {
@@ -140134,25 +141205,55 @@ var triageStep = {
140134
141205
  } else if (canDecompose) {
140135
141206
  await provider.addLabel(issue2.number, "decomposition:parent");
140136
141207
  const childIssues = [];
141208
+ const createdChildren = [];
140137
141209
  for (const draft of decompositionDrafts) {
140138
141210
  const child = await provider.createIssue(draft.title, draft.description, initialLabel);
140139
141211
  childIssues.push({ iid: child.iid, title: child.title, web_url: child.web_url });
141212
+ createdChildren.push({ iid: child.iid, title: child.title, web_url: child.web_url, draft });
140140
141213
  createdChildIssueNumbers.push(child.iid);
140141
141214
  for (const label of uniqueLabels) {
140142
141215
  await provider.addLabel(child.iid, label);
140143
141216
  }
140144
141217
  await provider.addLabel(child.iid, "decomposition:child");
141218
+ await provider.transitionLabel(child.iid, initialLabel, decision.targetState);
141219
+ if (decision.targetState === "To Do" && decision.dispatchLabel) {
141220
+ await provider.addLabel(child.iid, decision.dispatchLabel);
141221
+ }
141222
+ if (decision.targetState === "To Do") {
141223
+ await provider.addLabel(child.iid, `developer:${draft.recommendedLevel}`);
141224
+ }
141225
+ }
141226
+ if (projectSlug) {
141227
+ for (const child of createdChildren) {
141228
+ await updateIssueRuntime(ctx.workspaceDir, projectSlug, child.iid, {
141229
+ parentIssueId: issue2.number,
141230
+ dependencyIssueIds: child.draft.dependencyIndexes.map((index) => createdChildren[index]?.iid).filter((value) => Number.isFinite(value)),
141231
+ childReadyForDispatch: decision.targetState === "To Do",
141232
+ parallelizable: child.draft.parallelizable,
141233
+ recommendedLevel: child.draft.recommendedLevel,
141234
+ decompositionMode: "none",
141235
+ decompositionStatus: null
141236
+ }).catch(() => {
141237
+ });
141238
+ }
141239
+ await updateIssueRuntime(ctx.workspaceDir, projectSlug, issue2.number, {
141240
+ childIssueIds: createdChildIssueNumbers,
141241
+ maxParallelChildren: computeMaxParallelChildren(decompositionDrafts),
141242
+ decompositionMode: "parent_child",
141243
+ decompositionStatus: "active"
141244
+ }).catch(() => {
141245
+ });
140145
141246
  }
140146
141247
  await provider.addComment(issue2.number, [
140147
141248
  "## Decomposition Plan",
140148
141249
  ...childIssues.map((child) => `- [ ] #${child.iid} ${child.title}`)
140149
141250
  ].join("\n"));
140150
- } else if (shouldDecompose) {
141251
+ } else if (decompositionRequested && shouldDecompose) {
140151
141252
  await provider.addLabel(issue2.number, "needs-human");
140152
141253
  await provider.addComment(issue2.number, [
140153
- "\u26A0\uFE0F Automatic decomposition was requested, but the generated spec did not yield at least two independently scoped child tasks.",
141254
+ shouldDecompose ? "\u26A0\uFE0F Automatic decomposition was requested, but the generated spec did not yield at least two independently scoped child tasks." : "\u26A0\uFE0F This request is large, but triage detected high coupling / low parallelizability. Fabrica will keep it as one executable issue unless a human explicitly splits it.",
140154
141255
  "",
140155
- "Refine the scope/acceptance criteria or split the plan manually before dispatch."
141256
+ shouldDecompose ? "Refine the scope/acceptance criteria or split the plan manually before dispatch." : "If you still want multiple child issues, split the plan manually along stable boundaries before dispatch."
140156
141257
  ].join("\n"));
140157
141258
  } else if (decision.readyForDispatch) {
140158
141259
  await provider.transitionLabel(issue2.number, initialLabel, decision.targetState);
@@ -140214,6 +141315,11 @@ var triageStep = {
140214
141315
  const triage = {
140215
141316
  priority: decision.priority,
140216
141317
  effort: decision.effort,
141318
+ complexity: decision.complexity,
141319
+ coupling: decision.coupling,
141320
+ parallelizability: decision.parallelizability,
141321
+ quality_criticality: decision.qualityCriticality,
141322
+ risk_profile: decision.riskProfile,
140217
141323
  target_state: decision.targetState,
140218
141324
  project_slug: payload.scaffold?.project_slug ?? null,
140219
141325
  project_channel_id: null,
@@ -140223,15 +141329,41 @@ var triageStep = {
140223
141329
  errors: [
140224
141330
  ...decision.errors,
140225
141331
  ...decision.specQualityBlock ? ["spec_quality_block"] : [],
140226
- ...shouldDecompose && !canDecompose ? ["decomposition_needs_human"] : []
141332
+ ...decompositionRequested && shouldDecompose && !canDecompose ? ["decomposition_needs_human"] : []
140227
141333
  ],
140228
141334
  decomposition_mode: canDecompose ? "parent_child" : "none",
140229
141335
  child_issue_numbers: createdChildIssueNumbers
140230
141336
  };
140231
141337
  ctx.log(`Triage: ${triage.priority}, effort=${triage.effort}, ready=${triage.ready_for_dispatch}`);
141338
+ const bootstrapConversationId = payload.metadata?.source === "telegram-dm-bootstrap" ? payload.metadata?.channel_id : null;
141339
+ if (bootstrapConversationId) {
141340
+ await upsertTelegramBootstrapSession(ctx.workspaceDir, {
141341
+ conversationId: String(bootstrapConversationId),
141342
+ rawIdea: payload.raw_idea,
141343
+ projectName: payload.metadata?.project_name ?? null,
141344
+ stackHint: payload.metadata?.stack_hint ?? null,
141345
+ repoUrl: payload.provisioning?.repo_url ?? payload.scaffold?.repo_url ?? payload.metadata?.repo_url ?? null,
141346
+ repoPath: payload.provisioning?.repo_local ?? payload.scaffold?.repo_local ?? payload.metadata?.repo_path ?? null,
141347
+ status: triage.ready_for_dispatch ? "dispatching" : "completed",
141348
+ bootstrapStep: triage.ready_for_dispatch ? "project_ticked" : "completed",
141349
+ projectSlug: payload.metadata?.project_slug ?? payload.scaffold?.project_slug ?? null,
141350
+ issueId: issue2.number,
141351
+ issueUrl: issue2.url,
141352
+ projectChannelId: triage.project_channel_id ?? payload.metadata?.channel_id ?? null,
141353
+ messageThreadId: payload.metadata?.message_thread_id ?? null,
141354
+ triageReadyForDispatch: triage.ready_for_dispatch,
141355
+ triageErrors: triage.errors
141356
+ }).catch(() => {
141357
+ });
141358
+ }
140232
141359
  return {
140233
141360
  ...payload,
140234
141361
  step: "triage",
141362
+ fidelity_brief: fidelityBrief,
141363
+ metadata: {
141364
+ ...payload.metadata,
141365
+ fidelity_brief: fidelityBrief
141366
+ },
140235
141367
  triage
140236
141368
  };
140237
141369
  }
@@ -140359,6 +141491,43 @@ function mergeArtifacts(primary, secondary) {
140359
141491
  return merged;
140360
141492
  }
140361
141493
 
141494
+ // lib/intake/lib/clarification-policy.ts
141495
+ function detectScopeClarificationNeed(rawIdea, stackHint) {
141496
+ const text = rawIdea.toLowerCase();
141497
+ if (/\b(livre|free.?choice|your.?call|pode.?escolher|voc[eê].?decide|qualquer)\b/i.test(text)) {
141498
+ return { ask: false };
141499
+ }
141500
+ const subsystems = [
141501
+ /\bauth\b|\blogin\b|\bregister\b|\bjwt\b|\boauth\b/,
141502
+ /\bpayment\b|\bbilling\b|\bsubscription\b/,
141503
+ /\bemail\b|\bnotification\b|\balert\b|\bsms\b/,
141504
+ /\badmin\b|\bdashboard\b|\breport\b/,
141505
+ /\bqueue\b|\bworker\b|\bcron\b|\bjob\b/,
141506
+ /\bdatabase\b|\bmigration\b|\bschema\b/,
141507
+ /\bapi\b|\bwebhook\b|\bintegration\b|\bexternal service\b/
141508
+ ].filter((regex) => regex.test(text)).length;
141509
+ const missingKeyTechChoice = !stackHint || (!/postgres|mysql|sqlite|mongodb/i.test(text) || !/jwt|oauth|session|rbac/i.test(text));
141510
+ if (subsystems >= 3 && missingKeyTechChoice) {
141511
+ return { ask: true, kind: "scope", reason: "scope_ambiguity_across_multiple_subsystems" };
141512
+ }
141513
+ return { ask: false };
141514
+ }
141515
+ function decideBootstrapClarification(opts) {
141516
+ if (opts.scopeAmbiguous) {
141517
+ return { ask: true, kind: "scope", reason: "scope_ambiguity_requires_structuring" };
141518
+ }
141519
+ if (!opts.stackHint && !opts.projectName) {
141520
+ return { ask: true, kind: "stack_and_name", reason: "missing_stack_and_name" };
141521
+ }
141522
+ if (!opts.stackHint) {
141523
+ return { ask: true, kind: "stack", reason: "missing_stack" };
141524
+ }
141525
+ if (!opts.projectName) {
141526
+ return { ask: true, kind: "name", reason: "missing_project_name" };
141527
+ }
141528
+ return { ask: false };
141529
+ }
141530
+
140362
141531
  // lib/services/tick.ts
140363
141532
  init_audit();
140364
141533
 
@@ -140576,23 +141745,25 @@ init_workflow();
140576
141745
 
140577
141746
  // lib/dispatch/dispatch-dedup.ts
140578
141747
  init_constants();
140579
- import fs34 from "node:fs/promises";
140580
- import path35 from "node:path";
140581
- import { createHash as createHash4 } from "node:crypto";
141748
+ import fs36 from "node:fs/promises";
141749
+ import path36 from "node:path";
141750
+ import { createHash as createHash5 } from "node:crypto";
140582
141751
  var DEDUP_FILE = "dispatch-dedup.ndjson";
140583
141752
  var BUCKET_MS = 5 * 6e4;
140584
141753
  var DEFAULT_TTL_MS2 = 30 * 6e4;
141754
+ var ACTIVE_CLAIM_TTL_MS = 10 * 6e4;
141755
+ var activeClaims = /* @__PURE__ */ new Map();
140585
141756
  function dedupPath(workspaceDir) {
140586
- return path35.join(workspaceDir, DATA_DIR, DEDUP_FILE);
141757
+ return path36.join(workspaceDir, DATA_DIR, DEDUP_FILE);
140587
141758
  }
140588
141759
  function computeDispatchId(projectSlug, issueId, role, level, now2 = Date.now()) {
140589
141760
  const bucket = Math.floor(now2 / BUCKET_MS);
140590
141761
  const input = `${projectSlug}:${issueId}:${role}:${level}:${bucket}`;
140591
- return createHash4("sha256").update(input).digest("hex").slice(0, 16);
141762
+ return createHash5("sha256").update(input).digest("hex").slice(0, 16);
140592
141763
  }
140593
141764
  async function readEntries2(filePath) {
140594
141765
  try {
140595
- const content = await fs34.readFile(filePath, "utf-8");
141766
+ const content = await fs36.readFile(filePath, "utf-8");
140596
141767
  return content.split("\n").filter(Boolean).map((line) => {
140597
141768
  try {
140598
141769
  return JSON.parse(line);
@@ -140605,15 +141776,34 @@ async function readEntries2(filePath) {
140605
141776
  throw err;
140606
141777
  }
140607
141778
  }
141779
+ function sweepExpiredClaims(now2 = Date.now()) {
141780
+ for (const [dispatchId, expiresAt] of activeClaims.entries()) {
141781
+ if (expiresAt <= now2) activeClaims.delete(dispatchId);
141782
+ }
141783
+ }
140608
141784
  async function isDuplicate(workspaceDir, dispatchId) {
141785
+ sweepExpiredClaims();
141786
+ if (activeClaims.has(dispatchId)) return true;
140609
141787
  const entries = await readEntries2(dedupPath(workspaceDir));
140610
141788
  return entries.some((e2) => e2.id === dispatchId);
140611
141789
  }
141790
+ async function claimDispatch(workspaceDir, dispatchId) {
141791
+ sweepExpiredClaims();
141792
+ if (activeClaims.has(dispatchId)) return false;
141793
+ const entries = await readEntries2(dedupPath(workspaceDir));
141794
+ if (entries.some((e2) => e2.id === dispatchId)) return false;
141795
+ activeClaims.set(dispatchId, Date.now() + ACTIVE_CLAIM_TTL_MS);
141796
+ return true;
141797
+ }
141798
+ function releaseDispatchClaim(dispatchId) {
141799
+ activeClaims.delete(dispatchId);
141800
+ }
140612
141801
  async function recordDispatch(workspaceDir, dispatchId) {
140613
141802
  const filePath = dedupPath(workspaceDir);
140614
- await fs34.mkdir(path35.dirname(filePath), { recursive: true });
141803
+ await fs36.mkdir(path36.dirname(filePath), { recursive: true });
140615
141804
  const entry = { id: dispatchId, ts: Date.now() };
140616
- await fs34.appendFile(filePath, JSON.stringify(entry) + "\n", "utf-8");
141805
+ await fs36.appendFile(filePath, JSON.stringify(entry) + "\n", "utf-8");
141806
+ activeClaims.delete(dispatchId);
140617
141807
  }
140618
141808
  async function cleanupExpired(workspaceDir, ttlMs = DEFAULT_TTL_MS2) {
140619
141809
  const filePath = dedupPath(workspaceDir);
@@ -140623,8 +141813,8 @@ async function cleanupExpired(workspaceDir, ttlMs = DEFAULT_TTL_MS2) {
140623
141813
  if (kept.length < entries.length) {
140624
141814
  const content = kept.map((e2) => JSON.stringify(e2)).join("\n") + (kept.length > 0 ? "\n" : "");
140625
141815
  const tmpPath = filePath + ".tmp";
140626
- await fs34.writeFile(tmpPath, content, "utf-8");
140627
- await fs34.rename(tmpPath, filePath);
141816
+ await fs36.writeFile(tmpPath, content, "utf-8");
141817
+ await fs36.rename(tmpPath, filePath);
140628
141818
  }
140629
141819
  }
140630
141820
 
@@ -140701,7 +141891,7 @@ async function projectTick(opts) {
140701
141891
  continue;
140702
141892
  }
140703
141893
  }
140704
- const next = await findNextIssueForRole(provider, role, workflow, instanceName);
141894
+ const next = await findNextIssueForRole(provider, role, workflow, instanceName, fresh);
140705
141895
  if (!next) continue;
140706
141896
  const { issue: issue2, label: currentLabel } = next;
140707
141897
  const targetLabel = getActiveLabel(workflow, role);
@@ -140842,11 +142032,6 @@ async function projectTick(opts) {
140842
142032
  skipped.push({ role, reason: `${effectiveLevel} slots full` });
140843
142033
  continue;
140844
142034
  }
140845
- const dispatchId = computeDispatchId(projectSlug, issue2.iid, role, effectiveLevel);
140846
- if (await isDuplicate(workspaceDir, dispatchId)) {
140847
- skipped.push({ role, reason: `dispatch_dedup: ${dispatchId}` });
140848
- continue;
140849
- }
140850
142035
  if (role === "developer" || role === "tester") {
140851
142036
  const stack = fresh.stack;
140852
142037
  if (!stack) {
@@ -140893,6 +142078,17 @@ async function projectTick(opts) {
140893
142078
  continue;
140894
142079
  }
140895
142080
  }
142081
+ const dispatchId = computeDispatchId(projectSlug, issue2.iid, role, effectiveLevel);
142082
+ if (!dryRun) {
142083
+ if (await isDuplicate(workspaceDir, dispatchId)) {
142084
+ skipped.push({ role, reason: `dispatch_dedup: ${dispatchId}` });
142085
+ continue;
142086
+ }
142087
+ if (!await claimDispatch(workspaceDir, dispatchId)) {
142088
+ skipped.push({ role, reason: `dispatch_claimed: ${dispatchId}` });
142089
+ continue;
142090
+ }
142091
+ }
140896
142092
  if (dryRun) {
140897
142093
  const existingSession = roleWorker.levels[effectiveLevel]?.[freeSlot]?.sessionKey;
140898
142094
  pickups.push({
@@ -140942,6 +142138,7 @@ async function projectTick(opts) {
140942
142138
  await recordDispatch(workspaceDir, dispatchId).catch(() => {
140943
142139
  });
140944
142140
  } catch (err) {
142141
+ releaseDispatchClaim(dispatchId);
140945
142142
  skipped.push({ role, reason: `Dispatch failed: ${err.message}` });
140946
142143
  continue;
140947
142144
  }
@@ -140967,281 +142164,6 @@ function getFeedbackQueueLabel(workflow) {
140967
142164
  // lib/dispatch/telegram-bootstrap-hook.ts
140968
142165
  init_audit();
140969
142166
  init_zod();
140970
-
140971
- // lib/dispatch/telegram-bootstrap-session.ts
140972
- init_constants();
140973
- import { createHash as createHash5, randomUUID as randomUUID4 } from "node:crypto";
140974
- import fs35 from "node:fs/promises";
140975
- import path36 from "node:path";
140976
- var SESSION_TTL_MS = 10 * 6e4;
140977
- var CLASSIFYING_TTL_MS = 15e3;
140978
- var RELEASED_CLASSIFY_ERRORS = /* @__PURE__ */ new Set([
140979
- "classify_invalid_result",
140980
- "classify_low_confidence",
140981
- "classify_not_project",
140982
- "classify_result_expired"
140983
- ]);
140984
- function toCanonicalTelegramBootstrapConversationId(conversationId) {
140985
- const trimmed = conversationId.trim();
140986
- if (!trimmed) return trimmed;
140987
- return trimmed.startsWith("telegram:") ? trimmed : `telegram:${trimmed}`;
140988
- }
140989
- function sessionsDir(workspaceDir) {
140990
- return path36.join(workspaceDir, DATA_DIR, "bootstrap-sessions");
140991
- }
140992
- function sessionPath(workspaceDir, conversationId) {
140993
- return path36.join(
140994
- sessionsDir(workspaceDir),
140995
- `${toCanonicalTelegramBootstrapConversationId(conversationId)}.json`
140996
- );
140997
- }
140998
- function alternateSessionPath(workspaceDir, conversationId) {
140999
- const canonical = toCanonicalTelegramBootstrapConversationId(conversationId);
141000
- const bare = canonical.startsWith("telegram:") ? canonical.slice("telegram:".length) : canonical;
141001
- if (!bare || bare === canonical) return null;
141002
- return path36.join(sessionsDir(workspaceDir), `${bare}.json`);
141003
- }
141004
- function normalizeStoredSession(session) {
141005
- const canonicalConversationId = toCanonicalTelegramBootstrapConversationId(session.conversationId);
141006
- if (canonicalConversationId === session.conversationId) {
141007
- return session;
141008
- }
141009
- return {
141010
- ...session,
141011
- id: buildBootstrapSessionId(canonicalConversationId, session.rawIdea),
141012
- conversationId: canonicalConversationId
141013
- };
141014
- }
141015
- function stableHash(input) {
141016
- return createHash5("sha256").update(input).digest("hex").slice(0, 16);
141017
- }
141018
- function buildBootstrapSessionId(conversationId, rawIdea) {
141019
- return `tgdm-${conversationId}-${stableHash(rawIdea.trim().toLowerCase())}`;
141020
- }
141021
- function buildBootstrapRequestFingerprint(input) {
141022
- return stableHash(JSON.stringify({
141023
- rawIdea: input.rawIdea.trim().toLowerCase(),
141024
- projectName: input.projectName?.trim().toLowerCase() || null,
141025
- stackHint: input.stackHint?.trim().toLowerCase() || null,
141026
- repoUrl: input.repoUrl?.trim().toLowerCase() || null,
141027
- repoPath: input.repoPath?.trim().toLowerCase() || null
141028
- }));
141029
- }
141030
- function buildBootstrapRequestHash(input) {
141031
- return buildBootstrapRequestFingerprint(input);
141032
- }
141033
- function nextSuppressUntil(status) {
141034
- const ttl = status === "classifying" || status === "pending_classify" ? CLASSIFYING_TTL_MS : SESSION_TTL_MS;
141035
- return new Date(Date.now() + ttl).toISOString();
141036
- }
141037
- function isReleasedClassifyFailure(error48) {
141038
- return Boolean(error48 && RELEASED_CLASSIFY_ERRORS.has(error48));
141039
- }
141040
- function resolveNullableField(inputValue, existingValue, fallback = null) {
141041
- return inputValue !== void 0 ? inputValue : existingValue ?? fallback;
141042
- }
141043
- function defaultNextRetryAtForStatus(status, existingValue) {
141044
- if (status === "bootstrapping" || status === "dispatching") {
141045
- return existingValue ?? null;
141046
- }
141047
- return null;
141048
- }
141049
- var MONOTONIC_BOOTSTRAP_FIELDS = [
141050
- "ackSentAt",
141051
- "projectRegisteredAt",
141052
- "topicKickoffSentAt",
141053
- "projectTickedAt",
141054
- "completionAckSentAt"
141055
- ];
141056
- function shouldPersistBootstrapCheckpoint(current, next) {
141057
- if (!current) return { ok: true };
141058
- if (current.conversationId !== next.conversationId) return { ok: true };
141059
- const currentAttemptSeq = current.attemptSeq ?? null;
141060
- const nextAttemptSeq = next.attemptSeq ?? null;
141061
- if (currentAttemptSeq != null && nextAttemptSeq != null && nextAttemptSeq < currentAttemptSeq) {
141062
- return { ok: false, reason: "stale_regression" };
141063
- }
141064
- const sameAttempt = Boolean(
141065
- current.attemptId && next.attemptId && current.attemptSeq != null && next.attemptSeq != null && current.attemptId === next.attemptId && current.attemptSeq === next.attemptSeq
141066
- );
141067
- if (!sameAttempt) return { ok: true };
141068
- for (const field of MONOTONIC_BOOTSTRAP_FIELDS) {
141069
- if (current[field] && !next[field]) {
141070
- return { ok: false, reason: "stale_regression" };
141071
- }
141072
- }
141073
- return { ok: true };
141074
- }
141075
- async function readTelegramBootstrapSession(workspaceDir, conversationId) {
141076
- const canonicalConversationId = toCanonicalTelegramBootstrapConversationId(conversationId);
141077
- const paths = [
141078
- sessionPath(workspaceDir, canonicalConversationId),
141079
- alternateSessionPath(workspaceDir, conversationId)
141080
- ].filter((entry) => Boolean(entry));
141081
- try {
141082
- for (const candidatePath of paths) {
141083
- try {
141084
- const raw = await fs35.readFile(candidatePath, "utf-8");
141085
- const session = normalizeStoredSession(JSON.parse(raw));
141086
- if (session.status === "failed" && isReleasedClassifyFailure(session.error)) {
141087
- await deleteTelegramBootstrapSession(workspaceDir, canonicalConversationId);
141088
- return null;
141089
- }
141090
- return session;
141091
- } catch (error48) {
141092
- if (error48?.code === "ENOENT") continue;
141093
- throw error48;
141094
- }
141095
- }
141096
- return null;
141097
- } catch (error48) {
141098
- if (error48?.code === "ENOENT") return null;
141099
- throw error48;
141100
- }
141101
- }
141102
- async function deleteTelegramBootstrapSession(workspaceDir, conversationId) {
141103
- await fs35.unlink(sessionPath(workspaceDir, conversationId)).catch(() => {
141104
- });
141105
- const legacyPath = alternateSessionPath(workspaceDir, conversationId);
141106
- if (legacyPath) {
141107
- await fs35.unlink(legacyPath).catch(() => {
141108
- });
141109
- }
141110
- }
141111
- async function writeTelegramBootstrapSession(workspaceDir, session) {
141112
- const canonicalSession = normalizeStoredSession(session);
141113
- const dir = sessionsDir(workspaceDir);
141114
- await fs35.mkdir(dir, { recursive: true });
141115
- const file2 = sessionPath(workspaceDir, canonicalSession.conversationId);
141116
- const tmp = `${file2}.${randomUUID4()}.tmp`;
141117
- await fs35.writeFile(tmp, JSON.stringify(canonicalSession, null, 2) + "\n", "utf-8");
141118
- await fs35.rename(tmp, file2);
141119
- const legacyPath = alternateSessionPath(workspaceDir, session.conversationId);
141120
- if (legacyPath) {
141121
- await fs35.unlink(legacyPath).catch(() => {
141122
- });
141123
- }
141124
- }
141125
- async function upsertTelegramBootstrapSession(workspaceDir, input) {
141126
- const conversationId = toCanonicalTelegramBootstrapConversationId(input.conversationId);
141127
- const existing = await readTelegramBootstrapSession(workspaceDir, conversationId);
141128
- const resolvedSourceRoute = resolveNullableField(input.sourceRoute, existing?.sourceRoute);
141129
- const resolvedProjectRoute = resolveNullableField(input.projectRoute, existing?.projectRoute);
141130
- const resolvedProjectName = resolveNullableField(input.projectName, existing?.projectName);
141131
- const resolvedStackHint = resolveNullableField(input.stackHint, existing?.stackHint);
141132
- const resolvedRepoUrl = resolveNullableField(input.repoUrl, existing?.repoUrl);
141133
- const resolvedRepoPath = resolveNullableField(input.repoPath, existing?.repoPath);
141134
- const resolvedProjectSlug = resolveNullableField(input.projectSlug, existing?.projectSlug);
141135
- const resolvedIssueId = resolveNullableField(input.issueId, existing?.issueId);
141136
- const resolvedMessageThreadId = resolveNullableField(input.messageThreadId, existing?.messageThreadId);
141137
- const resolvedProjectChannelId = resolveNullableField(input.projectChannelId, existing?.projectChannelId);
141138
- const resolvedAttemptCount = resolveNullableField(input.attemptCount, existing?.attemptCount, 0);
141139
- const resolvedAttemptId = resolveNullableField(input.attemptId, existing?.attemptId);
141140
- const resolvedAttemptSeq = resolveNullableField(input.attemptSeq, existing?.attemptSeq);
141141
- const resolvedClassifySessionKey = resolveNullableField(input.classifySessionKey, existing?.classifySessionKey);
141142
- const resolvedClassifyRunId = resolveNullableField(input.classifyRunId, existing?.classifyRunId);
141143
- const resolvedClassifyStartedAt = resolveNullableField(input.classifyStartedAt, existing?.classifyStartedAt);
141144
- const resolvedBootstrapStep = resolveNullableField(input.bootstrapStep, existing?.bootstrapStep);
141145
- const resolvedNextRetryAt = input.nextRetryAt !== void 0 ? input.nextRetryAt : defaultNextRetryAtForStatus(input.status, existing?.nextRetryAt);
141146
- const resolvedAckSentAt = resolveNullableField(input.ackSentAt, existing?.ackSentAt);
141147
- const resolvedProjectRegisteredAt = resolveNullableField(input.projectRegisteredAt, existing?.projectRegisteredAt);
141148
- const resolvedTopicKickoffSentAt = resolveNullableField(input.topicKickoffSentAt, existing?.topicKickoffSentAt);
141149
- const resolvedProjectTickedAt = resolveNullableField(input.projectTickedAt, existing?.projectTickedAt);
141150
- const resolvedCompletionAckSentAt = resolveNullableField(input.completionAckSentAt, existing?.completionAckSentAt);
141151
- const resolvedError = input.error !== void 0 ? input.error : input.lastError !== void 0 ? input.lastError : existing?.error ?? existing?.lastError ?? null;
141152
- const requestHash = buildBootstrapRequestHash({
141153
- rawIdea: input.rawIdea,
141154
- projectName: resolvedProjectName,
141155
- stackHint: resolvedStackHint,
141156
- repoUrl: resolvedRepoUrl,
141157
- repoPath: resolvedRepoPath
141158
- });
141159
- const now2 = (/* @__PURE__ */ new Date()).toISOString();
141160
- const session = {
141161
- id: existing?.id ?? buildBootstrapSessionId(conversationId, input.rawIdea),
141162
- conversationId,
141163
- sourceChannel: input.sourceChannel ?? input.sourceRoute?.channel ?? existing?.sourceChannel ?? "telegram",
141164
- sourceRoute: resolvedSourceRoute,
141165
- projectRoute: resolvedProjectRoute,
141166
- requestHash,
141167
- requestFingerprint: requestHash,
141168
- lastCompletedRequestHash: input.status === "completed" ? requestHash : existing?.lastCompletedRequestHash ?? null,
141169
- rawIdea: input.rawIdea,
141170
- projectName: resolvedProjectName,
141171
- stackHint: resolvedStackHint,
141172
- repoUrl: resolvedRepoUrl,
141173
- repoPath: resolvedRepoPath,
141174
- projectSlug: resolvedProjectSlug,
141175
- issueId: resolvedIssueId,
141176
- messageThreadId: resolvedMessageThreadId,
141177
- projectChannelId: resolvedProjectChannelId,
141178
- language: input.language ?? existing?.language,
141179
- status: input.status,
141180
- attemptId: resolvedAttemptId,
141181
- attemptSeq: resolvedAttemptSeq,
141182
- classifySessionKey: resolvedClassifySessionKey,
141183
- classifyRunId: resolvedClassifyRunId,
141184
- classifyStartedAt: resolvedClassifyStartedAt,
141185
- bootstrapStep: resolvedBootstrapStep,
141186
- attemptCount: resolvedAttemptCount,
141187
- lastError: resolvedError,
141188
- nextRetryAt: resolvedNextRetryAt,
141189
- ackSentAt: resolvedAckSentAt,
141190
- projectRegisteredAt: resolvedProjectRegisteredAt,
141191
- topicKickoffSentAt: resolvedTopicKickoffSentAt,
141192
- projectTickedAt: resolvedProjectTickedAt,
141193
- completionAckSentAt: resolvedCompletionAckSentAt,
141194
- pendingClarification: input.pendingClarification !== void 0 ? input.pendingClarification : existing?.pendingClarification ?? null,
141195
- orphanedArtifacts: input.orphanedArtifacts !== void 0 ? input.orphanedArtifacts : existing?.orphanedArtifacts ?? null,
141196
- createdAt: existing?.createdAt ?? now2,
141197
- updatedAt: now2,
141198
- suppressUntil: nextSuppressUntil(input.status),
141199
- error: resolvedError
141200
- };
141201
- const writeDecision = shouldPersistBootstrapCheckpoint(existing, session);
141202
- if (!writeDecision.ok && existing) {
141203
- return existing;
141204
- }
141205
- await writeTelegramBootstrapSession(workspaceDir, session);
141206
- return session;
141207
- }
141208
- function shouldSuppressTelegramBootstrapReply(session, request) {
141209
- if (!session) return false;
141210
- if (isTelegramBootstrapSessionExpired(session)) return false;
141211
- if (session.status === "completed" || session.status === "failed") {
141212
- if (session.status === "failed" && isReleasedClassifyFailure(session.error)) return false;
141213
- if (!request) return false;
141214
- return buildBootstrapRequestFingerprint(request) === session.requestHash;
141215
- }
141216
- if (!request) return true;
141217
- return buildBootstrapRequestFingerprint(request) === session.requestHash;
141218
- }
141219
- function isTelegramBootstrapSessionExpired(session, now2 = Date.now()) {
141220
- if (!session) return false;
141221
- const suppressUntil = Date.parse(session.suppressUntil);
141222
- return !Number.isNaN(suppressUntil) && suppressUntil < now2;
141223
- }
141224
- function isRecoverableTelegramBootstrapSession(session) {
141225
- return session?.status === "bootstrapping" || session?.status === "dispatching";
141226
- }
141227
- function isClaimableTelegramBootstrapSession(session, now2 = Date.now()) {
141228
- if (!isRecoverableTelegramBootstrapSession(session)) return false;
141229
- if (!session.nextRetryAt) return true;
141230
- const retryAt = Date.parse(session.nextRetryAt);
141231
- return Number.isNaN(retryAt) || retryAt <= now2;
141232
- }
141233
- function isSupersededTelegramBootstrapAttempt(current, candidate) {
141234
- if (!current || !candidate) return false;
141235
- if (current.conversationId !== candidate.conversationId) return true;
141236
- const currentHasAttempt = current.attemptId != null && current.attemptSeq != null;
141237
- const candidateHasAttempt = candidate.attemptId != null && candidate.attemptSeq != null;
141238
- if (currentHasAttempt || candidateHasAttempt) {
141239
- return current.attemptId !== candidate.attemptId || current.attemptSeq !== candidate.attemptSeq;
141240
- }
141241
- return current.requestHash !== candidate.requestHash || current.status !== candidate.status || current.updatedAt !== candidate.updatedAt;
141242
- }
141243
-
141244
- // lib/dispatch/telegram-bootstrap-hook.ts
141245
142167
  init_constants();
141246
142168
  var BOOTSTRAP_RETRY_DELAY_MS = 15e3;
141247
142169
  var LAYER3_CONFIDENCE_THRESHOLD = 0.6;
@@ -141332,26 +142254,7 @@ I'll continue the flow at ${link}`
141332
142254
  }
141333
142255
  };
141334
142256
  function detectScopeAmbiguity(rawIdea, stackHint) {
141335
- const text = rawIdea.toLowerCase();
141336
- if (/\b(livre|free.?choice|your.?call|pode.?escolher|voc[eê].?decide|qualquer)\b/i.test(text)) {
141337
- return false;
141338
- }
141339
- const subsystems = [
141340
- /\b(worker|background.?job|queue|task.?runner|celery|bull)\b/i,
141341
- /\b(websocket|sse|real.?time|realtime|push.?notif)\b/i,
141342
- /\b(auth|oauth|jwt|login|register|signup|autenticac)\b/i,
141343
- /\b(notif|alert|assinatura|subscription|subscribe|email|sms)\b/i,
141344
- /\b(banco|database|db|postgres|mysql|mongodb|redis|sqlite)\b/i,
141345
- /\b(api\s+rest|rest\s+api|endpoint|graphql|grpc)\b/i,
141346
- /\b(dashboard|frontend|interface|ui|tela)\b/i
141347
- ];
141348
- const matchedSubsystems = subsystems.filter((r2) => r2.test(text)).length;
141349
- if (matchedSubsystems < 3) return false;
141350
- const hasExplicitDB = /\b(postgres(ql)?|mysql|mongodb|mongo|redis|sqlite|supabase|dynamodb|cockroach)\b/i.test(text);
141351
- const hasExplicitAuth = /\b(jwt|oauth2?|basic.?auth|api.?key|session.?based|cookie.?auth)\b/i.test(text);
141352
- const hasExplicitStack = !!stackHint && !["api", "rest-api", "backend"].includes(stackHint);
141353
- const unspecifiedDimensions = [!hasExplicitDB, !hasExplicitAuth, !hasExplicitStack].filter(Boolean).length;
141354
- return unspecifiedDimensions >= 2;
142257
+ return detectScopeClarificationNeed(rawIdea, stackHint).ask;
141355
142258
  }
141356
142259
  function inferProjectSlug(text) {
141357
142260
  let cleaned = text.replace(/^(create|build|crie|cria|criar|fazer?|quero|i need|i want)\s+(uma|um|me\s+a?|an|a)?\s*/i, "").replace(/\s+(that|which|que|para|for|pra)\s+.*/i, "").trim();
@@ -141360,11 +142263,11 @@ function inferProjectSlug(text) {
141360
142263
  return slug || void 0;
141361
142264
  }
141362
142265
  function normalizeProjectNameCandidate(value) {
141363
- const normalized = normalizeText3(value);
142266
+ const normalized = normalizeText4(value);
141364
142267
  if (!normalized) return void 0;
141365
142268
  return inferProjectSlug(normalized) ?? void 0;
141366
142269
  }
141367
- function normalizeText3(value) {
142270
+ function normalizeText4(value) {
141368
142271
  const trimmed = value?.trim();
141369
142272
  return trimmed ? trimmed : void 0;
141370
142273
  }
@@ -141389,15 +142292,19 @@ function parseField(text, labels) {
141389
142292
  for (const label of labels) {
141390
142293
  const regex = new RegExp(`(?:^|[\\n.,;!?]\\s*)${label}\\s*:\\s*(.+)$`, "im");
141391
142294
  const match = text.match(regex);
141392
- if (match?.[1]) return normalizeText3(match[1]);
142295
+ if (match?.[1]) return normalizeText4(match[1]);
141393
142296
  }
141394
142297
  return void 0;
141395
142298
  }
141396
142299
  function parseIdeaBlock(text) {
141397
142300
  const match = text.match(/^\s*(idea|ideia|objetivo)\s*:\s*([\s\S]+)$/im);
141398
- return normalizeText3(match?.[2]);
142301
+ return normalizeText4(match?.[2]);
141399
142302
  }
141400
142303
  function parseExplicitProjectName(text) {
142304
+ const naturalLanguageMatch = text.match(/\b(?:please\s+)?(?:use|set|keep)\s+(?:the\s+)?(?:project name|repo name|repository name|nome do projeto)\s+(?:as\s+)?[`"'“”‘’]?([a-z0-9][a-z0-9-]{1,63})[`"'“”‘’]?(?=$|[\s.,!?;:])/i);
142305
+ if (naturalLanguageMatch?.[1]) {
142306
+ return normalizeProjectNameCandidate(naturalLanguageMatch[1].toLowerCase());
142307
+ }
141401
142308
  const match = text.match(/\b(?:called|named|chamado)\s+[`"'“”‘’]?([a-z0-9][a-z0-9-]{1,63})[`"'“”‘’]?(?=$|[\s.,!?;:])/i);
141402
142309
  return normalizeProjectNameCandidate(match?.[1]?.toLowerCase());
141403
142310
  }
@@ -141635,6 +142542,9 @@ function buildClarificationMessage(parsed, pendingClarification, language = "pt"
141635
142542
  if (pendingClarification === "name") {
141636
142543
  return BOOTSTRAP_MESSAGES.clarifyName[language];
141637
142544
  }
142545
+ if (pendingClarification === "scope") {
142546
+ return BOOTSTRAP_MESSAGES.clarifyScope[language](parsed.rawIdea);
142547
+ }
141638
142548
  if (pendingClarification === "stack_and_name" || !parsed.stackHint && !parsed.projectName) {
141639
142549
  return BOOTSTRAP_MESSAGES.clarifyBoth[language];
141640
142550
  }
@@ -141835,12 +142745,12 @@ async function handleTelegramBootstrapDmMessage(ctx, rawConversationId, content)
141835
142745
  conversationId,
141836
142746
  rawIdea: content,
141837
142747
  stackHint: parsed.stackHint ?? void 0,
141838
- projectName: parsed.projectName ?? parsed.projectSlug ?? void 0,
142748
+ projectName: parsed.projectName ?? inferProjectSlug(parsed.rawIdea) ?? void 0,
141839
142749
  status: "clarifying",
141840
142750
  pendingClarification: "scope",
141841
142751
  language
141842
142752
  });
141843
- const clarifyMsg = BOOTSTRAP_MESSAGES.clarifyScope[language](content);
142753
+ const clarifyMsg = buildClarificationMessage(parsed, "scope", language);
141844
142754
  await sendTelegramText(ctx, rawConversationId, clarifyMsg);
141845
142755
  return;
141846
142756
  }
@@ -142026,6 +142936,9 @@ async function persistOwnedBootstrapCheckpoint(workspaceDir, owner, input) {
142026
142936
  bootstrapStep: input.bootstrapStep,
142027
142937
  projectSlug: input.projectSlug ?? void 0,
142028
142938
  issueId: input.issueId,
142939
+ issueUrl: input.issueUrl,
142940
+ triageReadyForDispatch: input.triageReadyForDispatch,
142941
+ triageErrors: input.triageErrors,
142029
142942
  messageThreadId: input.messageThreadId,
142030
142943
  projectChannelId: input.projectChannelId,
142031
142944
  language: input.language,
@@ -142491,7 +143404,7 @@ async function recoverDueTelegramBootstrapSessions(ctx, workspaceDir) {
142491
143404
  const sessionsDir2 = path37.join(workspaceDir, DATA_DIR, "bootstrap-sessions");
142492
143405
  let files;
142493
143406
  try {
142494
- files = await fs36.readdir(sessionsDir2);
143407
+ files = await fs37.readdir(sessionsDir2);
142495
143408
  } catch {
142496
143409
  return 0;
142497
143410
  }
@@ -142877,20 +143790,25 @@ async function continueBootstrap(ctx, conversationId, workspaceDir, request, sou
142877
143790
  return;
142878
143791
  }
142879
143792
  const telegramConfig = readFabricaTelegramConfig(ctx.pluginConfig);
142880
- const stackHint = request.stackHint;
142881
- if (!stackHint) {
142882
- const existingSession = await readTelegramBootstrapSession(workspaceDir, conversationId);
142883
- const lang = existingSession?.language ?? "pt";
143793
+ const existingSession = await readTelegramBootstrapSession(workspaceDir, conversationId);
143794
+ const lang = existingSession?.language ?? "pt";
143795
+ const inferredProjectName = request.projectName ?? inferProjectSlug(request.rawIdea) ?? null;
143796
+ const clarificationDecision = decideBootstrapClarification({
143797
+ projectName: inferredProjectName,
143798
+ stackHint: request.stackHint
143799
+ });
143800
+ if (clarificationDecision.ask && (clarificationDecision.kind === "stack" || clarificationDecision.kind === "stack_and_name")) {
143801
+ const clarificationKind = clarificationDecision.kind;
142884
143802
  const owner = buildBootstrapAttemptOwner(existingSession);
142885
143803
  if (owner) {
142886
143804
  await persistOwnedBootstrapCheckpoint(workspaceDir, owner, {
142887
143805
  rawIdea: request.rawIdea,
142888
- projectName: request.projectName ?? void 0,
143806
+ projectName: inferredProjectName ?? void 0,
142889
143807
  stackHint: request.stackHint ?? void 0,
142890
143808
  sourceRoute,
142891
143809
  status: "clarifying",
142892
143810
  bootstrapStep: existingSession?.bootstrapStep ?? "awaiting_pipeline",
142893
- pendingClarification: "stack",
143811
+ pendingClarification: clarificationKind,
142894
143812
  language: lang,
142895
143813
  ackSentAt: existingSession?.ackSentAt ?? null,
142896
143814
  lastError: null,
@@ -142900,64 +143818,63 @@ async function continueBootstrap(ctx, conversationId, workspaceDir, request, sou
142900
143818
  await upsertTelegramBootstrapSession(workspaceDir, {
142901
143819
  conversationId,
142902
143820
  rawIdea: request.rawIdea,
142903
- projectName: request.projectName ?? void 0,
143821
+ projectName: inferredProjectName ?? void 0,
142904
143822
  status: "clarifying",
142905
- pendingClarification: "stack",
143823
+ pendingClarification: clarificationKind,
142906
143824
  language: lang
142907
143825
  });
142908
143826
  }
142909
143827
  await sendTelegramText(ctx, conversationId, buildClarificationMessage(
142910
- { rawIdea: request.rawIdea, projectName: request.projectName ?? void 0, stackHint: request.stackHint ?? void 0 },
142911
- "stack",
143828
+ { rawIdea: request.rawIdea, projectName: inferredProjectName ?? void 0, stackHint: request.stackHint ?? void 0 },
143829
+ clarificationKind,
142912
143830
  lang
142913
143831
  ));
142914
143832
  return;
142915
143833
  }
142916
- if (!request.projectName) {
143834
+ if (inferredProjectName && !request.projectName) {
143835
+ request.projectName = inferredProjectName;
143836
+ }
143837
+ if (clarificationDecision.ask && clarificationDecision.kind === "name") {
142917
143838
  const inferredSlug = inferProjectSlug(request.rawIdea);
142918
143839
  if (inferredSlug) {
142919
143840
  request.projectName = inferredSlug;
143841
+ } else if (existingSession?.pendingClarification === "name") {
143842
+ request.projectName = `project-${Date.now()}`;
142920
143843
  } else {
142921
- const existingSession = await readTelegramBootstrapSession(workspaceDir, conversationId);
142922
- if (existingSession?.pendingClarification === "name") {
142923
- request.projectName = `project-${Date.now()}`;
143844
+ const owner = existingSession ? buildBootstrapAttemptOwner(existingSession) : null;
143845
+ if (existingSession && owner) {
143846
+ await persistOwnedBootstrapCheckpoint(workspaceDir, owner, {
143847
+ rawIdea: request.rawIdea,
143848
+ stackHint: request.stackHint ?? void 0,
143849
+ sourceRoute,
143850
+ status: "clarifying",
143851
+ bootstrapStep: existingSession.bootstrapStep ?? "awaiting_pipeline",
143852
+ pendingClarification: "name",
143853
+ language: lang,
143854
+ ackSentAt: existingSession.ackSentAt ?? null,
143855
+ lastError: null,
143856
+ nextRetryAt: null
143857
+ });
142924
143858
  } else {
142925
- const lang = existingSession?.language ?? "pt";
142926
- const owner = existingSession ? buildBootstrapAttemptOwner(existingSession) : null;
142927
- if (existingSession && owner) {
142928
- await persistOwnedBootstrapCheckpoint(workspaceDir, owner, {
142929
- rawIdea: request.rawIdea,
142930
- stackHint: request.stackHint ?? void 0,
142931
- sourceRoute,
142932
- status: "clarifying",
142933
- bootstrapStep: existingSession.bootstrapStep ?? "awaiting_pipeline",
142934
- pendingClarification: "name",
142935
- language: lang,
142936
- ackSentAt: existingSession.ackSentAt ?? null,
142937
- lastError: null,
142938
- nextRetryAt: null
142939
- });
142940
- } else {
142941
- await upsertTelegramBootstrapSession(workspaceDir, {
142942
- conversationId,
142943
- rawIdea: request.rawIdea,
142944
- stackHint: request.stackHint ?? void 0,
142945
- status: "clarifying",
142946
- pendingClarification: "name",
142947
- language: lang
142948
- });
142949
- }
142950
- await sendTelegramText(
142951
- ctx,
143859
+ await upsertTelegramBootstrapSession(workspaceDir, {
142952
143860
  conversationId,
142953
- buildClarificationMessage(
142954
- { rawIdea: request.rawIdea, projectName: void 0, stackHint: request.stackHint ?? void 0 },
142955
- "name",
142956
- lang
142957
- )
142958
- );
142959
- return;
143861
+ rawIdea: request.rawIdea,
143862
+ stackHint: request.stackHint ?? void 0,
143863
+ status: "clarifying",
143864
+ pendingClarification: "name",
143865
+ language: lang
143866
+ });
142960
143867
  }
143868
+ await sendTelegramText(
143869
+ ctx,
143870
+ conversationId,
143871
+ buildClarificationMessage(
143872
+ { rawIdea: request.rawIdea, projectName: void 0, stackHint: request.stackHint ?? void 0 },
143873
+ "name",
143874
+ lang
143875
+ )
143876
+ );
143877
+ return;
142961
143878
  }
142962
143879
  }
142963
143880
  const stepCtx = {
@@ -142988,10 +143905,11 @@ async function continueBootstrap(ctx, conversationId, workspaceDir, request, sou
142988
143905
  config: ctx.config,
142989
143906
  pluginConfig: ctx.pluginConfig
142990
143907
  };
143908
+ const stackHint = request.stackHint ?? null;
142991
143909
  const payload = {
142992
- session_id: `telegram-bootstrap-${Date.now()}`,
143910
+ session_id: randomUUID5(),
142993
143911
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
142994
- step: "init",
143912
+ step: "telegram-bootstrap",
142995
143913
  raw_idea: request.rawIdea,
142996
143914
  answers: {},
142997
143915
  metadata: {
@@ -143089,6 +144007,8 @@ Erro: ${result.error ?? "erro desconhecido"}`
143089
144007
  const projectChannelId = result.payload.metadata.channel_id ?? telegramConfig.projectsForumChatId;
143090
144008
  const messageThreadId = result.payload.metadata.message_thread_id;
143091
144009
  const projectSlug = result.payload.metadata.project_slug ?? result.payload.scaffold?.project_slug ?? null;
144010
+ const createdIssue = result.payload.issues?.[0] ?? null;
144011
+ const triage = result.payload.triage ?? null;
143092
144012
  if (projectChannelId && messageThreadId) {
143093
144013
  const projectRoute = {
143094
144014
  channel: "telegram",
@@ -143103,6 +144023,10 @@ Erro: ${result.error ?? "erro desconhecido"}`
143103
144023
  bootstrapStep: "project_registered",
143104
144024
  projectName: resolvedProjectName,
143105
144025
  projectSlug: projectSlug ?? void 0,
144026
+ issueId: createdIssue?.number ?? null,
144027
+ issueUrl: createdIssue?.url ?? null,
144028
+ triageReadyForDispatch: triage?.ready_for_dispatch ?? null,
144029
+ triageErrors: triage?.errors ?? null,
143106
144030
  projectRegisteredAt: (/* @__PURE__ */ new Date()).toISOString(),
143107
144031
  projectChannelId: String(projectChannelId),
143108
144032
  messageThreadId,
@@ -143118,6 +144042,10 @@ Erro: ${result.error ?? "erro desconhecido"}`
143118
144042
  status: "dispatching",
143119
144043
  projectName: resolvedProjectName,
143120
144044
  projectSlug: projectSlug ?? void 0,
144045
+ issueId: createdIssue?.number ?? null,
144046
+ issueUrl: createdIssue?.url ?? null,
144047
+ triageReadyForDispatch: triage?.ready_for_dispatch ?? null,
144048
+ triageErrors: triage?.errors ?? null,
143121
144049
  projectRegisteredAt: (/* @__PURE__ */ new Date()).toISOString(),
143122
144050
  projectChannelId: String(projectChannelId),
143123
144051
  messageThreadId,
@@ -143133,6 +144061,10 @@ Erro: ${result.error ?? "erro desconhecido"}`
143133
144061
  ...registeredSession,
143134
144062
  projectName: resolvedProjectName,
143135
144063
  projectSlug: projectSlug ?? registeredSession.projectSlug ?? void 0,
144064
+ issueId: createdIssue?.number ?? registeredSession.issueId ?? null,
144065
+ issueUrl: createdIssue?.url ?? registeredSession.issueUrl ?? null,
144066
+ triageReadyForDispatch: triage?.ready_for_dispatch ?? registeredSession.triageReadyForDispatch ?? null,
144067
+ triageErrors: triage?.errors ?? registeredSession.triageErrors ?? null,
143136
144068
  projectRegisteredAt: registeredSession.projectRegisteredAt ?? (/* @__PURE__ */ new Date()).toISOString(),
143137
144069
  language: currentSession?.language ?? "pt"
143138
144070
  });
@@ -144727,7 +145659,7 @@ async function runPrDiscoveryPass(params) {
144727
145659
  // lib/services/heartbeat/genesis-health.ts
144728
145660
  init_logger();
144729
145661
  init_constants();
144730
- import fs37 from "node:fs/promises";
145662
+ import fs38 from "node:fs/promises";
144731
145663
  import path38 from "node:path";
144732
145664
  var STALE_THRESHOLD_MS = 5 * 6e4;
144733
145665
  var STALE_STATUSES = /* @__PURE__ */ new Set(["pending_classify", "classifying"]);
@@ -144742,7 +145674,7 @@ async function checkGenesisHealth(workspaceDir) {
144742
145674
  const sessionsDir2 = path38.join(workspaceDir, DATA_DIR, "bootstrap-sessions");
144743
145675
  let files;
144744
145676
  try {
144745
- files = await fs37.readdir(sessionsDir2);
145677
+ files = await fs38.readdir(sessionsDir2);
144746
145678
  } catch {
144747
145679
  return;
144748
145680
  }
@@ -144750,7 +145682,7 @@ async function checkGenesisHealth(workspaceDir) {
144750
145682
  for (const file2 of files) {
144751
145683
  if (!file2.endsWith(".json")) continue;
144752
145684
  try {
144753
- const raw = await fs37.readFile(path38.join(sessionsDir2, file2), "utf-8");
145685
+ const raw = await fs38.readFile(path38.join(sessionsDir2, file2), "utf-8");
144754
145686
  const data = JSON.parse(raw);
144755
145687
  const id = data.conversationId ?? file2.replace(/\.json$/, "");
144756
145688
  const updatedAt = typeof data.updatedAt === "string" ? new Date(data.updatedAt).getTime() : Number(data.updatedAt ?? 0);
@@ -145374,7 +146306,7 @@ async function writeModelsToWorkflow(workspacePath, models) {
145374
146306
  const workflowPath = path39.join(workspacePath, DATA_DIR, "workflow.yaml");
145375
146307
  let content = "";
145376
146308
  try {
145377
- content = await fs38.readFile(workflowPath, "utf-8");
146309
+ content = await fs39.readFile(workflowPath, "utf-8");
145378
146310
  } catch {
145379
146311
  }
145380
146312
  const doc = content ? import_yaml3.default.parseDocument(content) : new import_yaml3.default.Document({});
@@ -145390,7 +146322,7 @@ async function writeModelsToWorkflow(workspacePath, models) {
145390
146322
  roleNode.set("models", doc.createNode(levels));
145391
146323
  }
145392
146324
  }
145393
- await fs38.writeFile(workflowPath, doc.toString({ lineWidth: 120 }), "utf-8");
146325
+ await fs39.writeFile(workflowPath, doc.toString({ lineWidth: 120 }), "utf-8");
145394
146326
  }
145395
146327
 
145396
146328
  // lib/tools/admin/setup.ts
@@ -145514,7 +146446,7 @@ ${written.map((f3) => ` ${f3}`).join("\n")}` : "All files already exist \u2014
145514
146446
 
145515
146447
  // lib/setup/onboarding.ts
145516
146448
  init_roles();
145517
- import fs39 from "node:fs/promises";
146449
+ import fs40 from "node:fs/promises";
145518
146450
  import path40 from "node:path";
145519
146451
  function isPluginConfigured(pluginConfig) {
145520
146452
  return !!pluginConfig && Object.keys(pluginConfig).length > 0;
@@ -145522,7 +146454,7 @@ function isPluginConfigured(pluginConfig) {
145522
146454
  async function hasWorkspaceFiles(workspaceDir) {
145523
146455
  if (!workspaceDir) return false;
145524
146456
  try {
145525
- const content = await fs39.readFile(
146457
+ const content = await fs40.readFile(
145526
146458
  path40.join(workspaceDir, "AGENTS.md"),
145527
146459
  "utf-8"
145528
146460
  );
@@ -146338,7 +147270,7 @@ These completely replace (not merge with) the workspace-level prompts for that r
146338
147270
  }
146339
147271
 
146340
147272
  // lib/tools/admin/config.ts
146341
- import fs40 from "node:fs/promises";
147273
+ import fs41 from "node:fs/promises";
146342
147274
  import path41 from "node:path";
146343
147275
  init_workspace();
146344
147276
  init_templates();
@@ -146443,7 +147375,7 @@ async function handleDiff(workspacePath) {
146443
147375
  summary: "No workflow.yaml found in workspace \u2014 using package defaults."
146444
147376
  });
146445
147377
  }
146446
- const current = await fs40.readFile(workflowPath, "utf-8");
147378
+ const current = await fs41.readFile(workflowPath, "utf-8");
146447
147379
  const template = WORKFLOW_YAML_TEMPLATE;
146448
147380
  if (current.trim() === template.trim()) {
146449
147381
  return jsonResult({
@@ -146603,6 +147535,8 @@ function extractExplicitProjectName(text) {
146603
147535
  if (!text) return null;
146604
147536
  const fieldMatch = text.match(/(?:^|[\n.,;!?]\s*)(?:project name|repo name|repository name|nome do projeto)\s*:\s*([a-z0-9][a-z0-9-]{1,63})\b/i);
146605
147537
  if (fieldMatch?.[1]) return fieldMatch[1].trim().toLowerCase();
147538
+ const naturalLanguageMatch = text.match(/\b(?:please\s+)?(?:use|set|keep)\s+(?:the\s+)?(?:project name|repo name|repository name|nome do projeto)\s+(?:as\s+)?[`"'“”‘’]?([a-z0-9][a-z0-9-]{1,63})[`"'“”‘’]?(?=$|[\s.,!?;:])/i);
147539
+ if (naturalLanguageMatch?.[1]) return naturalLanguageMatch[1].trim().toLowerCase();
146606
147540
  const inlineMatch = text.match(/\b(?:called|named|chamado)\s+[`"'“”‘’]?([a-z0-9][a-z0-9-]{1,63})[`"'“”‘’]?(?=$|[\s.,!?;:])/i);
146607
147541
  if (inlineMatch?.[1]) return inlineMatch[1].trim().toLowerCase();
146608
147542
  return null;
@@ -147117,7 +148051,7 @@ function getPendingQuestions(payload, answers = payload.answers ?? {}) {
147117
148051
  var import_yaml4 = __toESM(require_dist(), 1);
147118
148052
  init_constants();
147119
148053
  init_migrate_layout();
147120
- import fs41 from "node:fs/promises";
148054
+ import fs42 from "node:fs/promises";
147121
148055
  import path42 from "node:path";
147122
148056
  init_roles();
147123
148057
  async function runDoctor(opts) {
@@ -147162,14 +148096,14 @@ async function runDoctor(opts) {
147162
148096
  if (check2.name.startsWith("file:")) {
147163
148097
  const recheckPath = check2.name.replace("file:", "");
147164
148098
  try {
147165
- await fs41.access(recheckPath);
148099
+ await fs42.access(recheckPath);
147166
148100
  recheckOk = true;
147167
148101
  } catch {
147168
148102
  }
147169
148103
  } else if (check2.name.startsWith("dir:")) {
147170
148104
  const recheckPath = check2.name.replace("dir:", "");
147171
148105
  try {
147172
- const stat2 = await fs41.stat(recheckPath);
148106
+ const stat2 = await fs42.stat(recheckPath);
147173
148107
  recheckOk = stat2.isDirectory();
147174
148108
  } catch {
147175
148109
  }
@@ -147198,7 +148132,7 @@ async function runDoctor(opts) {
147198
148132
  }
147199
148133
  async function checkDirExists(dirPath, label) {
147200
148134
  try {
147201
- const stat2 = await fs41.stat(dirPath);
148135
+ const stat2 = await fs42.stat(dirPath);
147202
148136
  if (stat2.isDirectory()) {
147203
148137
  return { name: `dir:${dirPath}`, severity: "ok", message: `${label} exists` };
147204
148138
  }
@@ -147209,7 +148143,7 @@ async function checkDirExists(dirPath, label) {
147209
148143
  }
147210
148144
  async function checkFileExists(filePath, label) {
147211
148145
  try {
147212
- await fs41.access(filePath);
148146
+ await fs42.access(filePath);
147213
148147
  return { name: `file:${filePath}`, severity: "ok", message: `${label} exists` };
147214
148148
  } catch {
147215
148149
  return { name: `file:${filePath}`, severity: "error", message: `${label} missing` };
@@ -147218,7 +148152,7 @@ async function checkFileExists(filePath, label) {
147218
148152
  async function checkWorkflowYaml(dataDir) {
147219
148153
  const filePath = path42.join(dataDir, "workflow.yaml");
147220
148154
  try {
147221
- const content = await fs41.readFile(filePath, "utf-8");
148155
+ const content = await fs42.readFile(filePath, "utf-8");
147222
148156
  const parsed = import_yaml4.default.parse(content);
147223
148157
  if (!parsed || typeof parsed !== "object") {
147224
148158
  return { name: "yaml:workflow", severity: "error", message: "workflow.yaml is empty or not an object" };
@@ -147237,7 +148171,7 @@ async function checkWorkflowYaml(dataDir) {
147237
148171
  async function checkProjectsJson(dataDir) {
147238
148172
  const filePath = path42.join(dataDir, "projects.json");
147239
148173
  try {
147240
- const content = await fs41.readFile(filePath, "utf-8");
148174
+ const content = await fs42.readFile(filePath, "utf-8");
147241
148175
  const parsed = JSON.parse(content);
147242
148176
  if (!parsed || typeof parsed !== "object") {
147243
148177
  return { name: "json:projects", severity: "error", message: "projects.json is not an object" };
@@ -147314,7 +148248,7 @@ async function checkProjects(dataDir) {
147314
148248
  const filePath = path42.join(dataDir, "projects.json");
147315
148249
  let data;
147316
148250
  try {
147317
- const content = await fs41.readFile(filePath, "utf-8");
148251
+ const content = await fs42.readFile(filePath, "utf-8");
147318
148252
  data = JSON.parse(content);
147319
148253
  } catch {
147320
148254
  return results;
@@ -147354,7 +148288,7 @@ async function checkProjects(dataDir) {
147354
148288
  if (project.repo && path42.isAbsolute(project.repo)) {
147355
148289
  const qaPath = path42.join(project.repo, "scripts", "qa.sh");
147356
148290
  try {
147357
- await fs41.access(qaPath);
148291
+ await fs42.access(qaPath);
147358
148292
  } catch {
147359
148293
  results.push({
147360
148294
  name: `project:${slug}:qa`,
@@ -147478,7 +148412,7 @@ function formatMetrics(metrics2) {
147478
148412
 
147479
148413
  // lib/setup/security-doctor.ts
147480
148414
  init_constants();
147481
- import fs42 from "node:fs/promises";
148415
+ import fs43 from "node:fs/promises";
147482
148416
  import path43 from "node:path";
147483
148417
  var LEGACY_EXTENSION_PATH_PATTERNS = [
147484
148418
  "/extensions/secureclaw/",
@@ -147503,7 +148437,7 @@ async function runSecurityDoctor(openclawHome) {
147503
148437
  const checklistPath = path43.join(workspacePath, DATA_DIR, "prompts", "security-checklist.md");
147504
148438
  let config2 = null;
147505
148439
  try {
147506
- config2 = JSON.parse(await fs42.readFile(configPath, "utf-8"));
148440
+ config2 = JSON.parse(await fs43.readFile(configPath, "utf-8"));
147507
148441
  checks.push({
147508
148442
  name: "config:openclaw-json",
147509
148443
  severity: "ok",
@@ -147570,7 +148504,7 @@ async function runSecurityDoctor(openclawHome) {
147570
148504
  message: genesisBinding ? "Genesis agent owns the channel-wide Telegram DM binding" : "Genesis agent does not own a channel-wide Telegram binding"
147571
148505
  });
147572
148506
  try {
147573
- await fs42.access(checklistPath);
148507
+ await fs43.access(checklistPath);
147574
148508
  checks.push({
147575
148509
  name: "workspace:security-checklist",
147576
148510
  severity: "ok",
@@ -147584,7 +148518,7 @@ async function runSecurityDoctor(openclawHome) {
147584
148518
  });
147585
148519
  }
147586
148520
  try {
147587
- const jobsJson = JSON.parse(await fs42.readFile(jobsPath, "utf-8"));
148521
+ const jobsJson = JSON.parse(await fs43.readFile(jobsPath, "utf-8"));
147588
148522
  const jobs = jobsJson.jobs ?? [];
147589
148523
  const legacyJobFindings = jobs.filter((job) => job.enabled !== false).flatMap((job) => {
147590
148524
  const text = `${job.payload?.text ?? ""} ${job.payload?.message ?? ""}`;
@@ -148724,7 +149658,7 @@ function registerGitHubWebhookRoute(api, ctx) {
148724
149658
  // lib/setup/gateway-lifecycle-hook.ts
148725
149659
  init_constants();
148726
149660
  init_audit();
148727
- import * as fs43 from "node:fs/promises";
149661
+ import * as fs44 from "node:fs/promises";
148728
149662
  import * as path45 from "node:path";
148729
149663
  function registerGatewayLifecycleHook(api, ctx) {
148730
149664
  const workspaceDir = resolveWorkspaceDir(ctx.config);
@@ -148733,7 +149667,7 @@ function registerGatewayLifecycleHook(api, ctx) {
148733
149667
  const bootTime = Date.now();
148734
149668
  const dataPath = path45.join(workspaceDir, DATA_DIR);
148735
149669
  try {
148736
- await fs43.access(dataPath);
149670
+ await fs44.access(dataPath);
148737
149671
  } catch {
148738
149672
  await log(workspaceDir, "gateway_start_warning", {
148739
149673
  message: `Workspace data directory missing: ${dataPath}`
@@ -148743,7 +149677,7 @@ function registerGatewayLifecycleHook(api, ctx) {
148743
149677
  }
148744
149678
  const projectsPath2 = path45.join(dataPath, "projects.json");
148745
149679
  try {
148746
- const raw = await fs43.readFile(projectsPath2, "utf-8");
149680
+ const raw = await fs44.readFile(projectsPath2, "utf-8");
148747
149681
  JSON.parse(raw);
148748
149682
  } catch (err) {
148749
149683
  await log(workspaceDir, "gateway_start_warning", {
@@ -149025,7 +149959,7 @@ function registerSubagentLifecycleHook(api, ctx) {
149025
149959
  init_constants();
149026
149960
  init_registry();
149027
149961
  init_roles();
149028
- import * as fs44 from "node:fs";
149962
+ import * as fs45 from "node:fs";
149029
149963
  import * as path46 from "node:path";
149030
149964
  var ESCALATION_MAP = {
149031
149965
  junior: "medior",
@@ -149052,7 +149986,7 @@ function registerModelResolveHook(api, ctx) {
149052
149986
  if (!level) return;
149053
149987
  try {
149054
149988
  const projectsPath2 = path46.join(workspaceDir, DATA_DIR, "projects.json");
149055
- const raw = await fs44.promises.readFile(projectsPath2, "utf-8");
149989
+ const raw = await fs45.promises.readFile(projectsPath2, "utf-8");
149056
149990
  const projects = JSON.parse(raw);
149057
149991
  const project = Object.values(projects).find((p) => p.slug === projectName);
149058
149992
  if (!project?.workers?.[role]?.levels?.[level]) return;
@@ -149121,6 +150055,9 @@ Do not delegate implementation, testing, review, or planning to another coding a
149121
150055
  Do not use nested coding agents.
149122
150056
  Do not use planning or meta-skills such as brainstorming, writing-plans, or coding-agent.
149123
150057
  Do not spawn, supervise, or instruct another agent to do the work for you.
150058
+ Do not weaken, replace, or bypass the canonical scripts/qa.sh contract just to make the task pass.
150059
+ Preserve the canonical QA gates: lint, types, security, tests, and coverage.
150060
+ If QA fails, fix the product code or project setup instead of rewriting the QA contract into ad-hoc scenario checks.
149124
150061
  If you cannot proceed directly in the assigned worktree, end with your role's canonical blocked result line.
149125
150062
  `;
149126
150063
  var REVIEWER_EXECUTION_CONTRACT_CONTEXT = `## Execution Contract