nexgenk 1.3.0 → 1.5.0

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.
Files changed (2) hide show
  1. package/dist/main.mjs +936 -387
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -119809,10 +119809,10 @@ function globSegmentsMatchPrefix(globSegments, relSegments) {
119809
119809
  }
119810
119810
  function compileGrepPattern(req) {
119811
119811
  const flags = req.case_sensitive ? "g" : "gi";
119812
- const body = req.regex ? req.pattern : escapeRegExp$2(req.pattern);
119812
+ const body = req.regex ? req.pattern : escapeRegExp$3(req.pattern);
119813
119813
  return new RegExp(body, flags);
119814
119814
  }
119815
- function escapeRegExp$2(s) {
119815
+ function escapeRegExp$3(s) {
119816
119816
  return s.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
119817
119817
  }
119818
119818
  function stripTrailingNewline(s) {
@@ -123382,8 +123382,8 @@ var init_env$1 = __esmMin((() => {
123382
123382
  ]);
123383
123383
  }));
123384
123384
  // ../../packages/agent-core-v2/src/workspace/workspaceTrust/trustEnv.ts
123385
- function trustWorkspaceEnvTrusted(getEnv) {
123386
- return parseBooleanEnv(getEnv(TRUST_WORKSPACE_ENV)) === true;
123385
+ function workspaceTrustOverride(getEnv) {
123386
+ return parseBooleanEnv(getEnv(TRUST_WORKSPACE_ENV));
123387
123387
  }
123388
123388
  var TRUST_WORKSPACE_ENV;
123389
123389
  var init_trustEnv = __esmMin((() => {
@@ -123483,7 +123483,7 @@ var init_workspaceTrustStore = __esmMin((() => {
123483
123483
  initialization;
123484
123484
  initializationFailed = false;
123485
123485
  root;
123486
- envTrusted;
123486
+ envOverride;
123487
123487
  trusted = false;
123488
123488
  mutationTail = Promise.resolve();
123489
123489
  changeEmitter = this._register(new Emitter());
@@ -123493,7 +123493,7 @@ var init_workspaceTrustStore = __esmMin((() => {
123493
123493
  this.docs = docs;
123494
123494
  this.telemetry = telemetry;
123495
123495
  this.root = root;
123496
- this.envTrusted = trustWorkspaceEnvTrusted((name) => bootstrap.getEnv(name));
123496
+ this.envOverride = workspaceTrustOverride((name) => bootstrap.getEnv(name));
123497
123497
  }
123498
123498
  get ready() {
123499
123499
  if (this.initialization !== void 0) return this.initialization;
@@ -123514,7 +123514,7 @@ var init_workspaceTrustStore = __esmMin((() => {
123514
123514
  return this.trusted;
123515
123515
  }
123516
123516
  isTrusted() {
123517
- return this.envTrusted || this.trusted;
123517
+ return this.trusted || (this.envOverride ?? true);
123518
123518
  }
123519
123519
  async get() {
123520
123520
  await this.ready;
@@ -123627,7 +123627,7 @@ var init_workspaceTrustService = __esmMin((() => {
123627
123627
  }
123628
123628
  async initialize() {
123629
123629
  await this.inner.ready;
123630
- this.trusted = this.inner.isPersistedTrusted();
123630
+ this.trusted = this.inner.isTrusted();
123631
123631
  }
123632
123632
  };
123633
123633
  WorkspaceTrustService = __decorate([
@@ -125726,7 +125726,7 @@ function expandSkillParameters(body, rawArgs, context) {
125726
125726
  for (let index = 0; index < (context.argumentNames?.length ?? 0); index++) {
125727
125727
  const name = context.argumentNames?.[index];
125728
125728
  if (name === void 0) continue;
125729
- const escaped = escapeRegExp$1(name);
125729
+ const escaped = escapeRegExp$2(name);
125730
125730
  content = content.replaceAll(new RegExp(`\\$${escaped}(?![\\[\\w])`, "g"), escapeXmlTags(tokens[index] ?? ""));
125731
125731
  }
125732
125732
  content = content.replaceAll(/\$ARGUMENTS\[(\d+)\]/g, (_match, indexText) => {
@@ -125816,7 +125816,7 @@ function tokenizeArgs(raw) {
125816
125816
  if (hasContent) out.push(current);
125817
125817
  return out;
125818
125818
  }
125819
- function escapeRegExp$1(value) {
125819
+ function escapeRegExp$2(value) {
125820
125820
  return value.replaceAll(/[\\^$.*+?()[\]{}|]/g, "\\$&");
125821
125821
  }
125822
125822
  var LISTING_DESC_MAX, InMemorySkillCatalog, SOURCE_GROUPS, graphemeSegmenter$3;
@@ -219297,6 +219297,26 @@ var init_decide$1 = __esmMin((() => {
219297
219297
  IWaveDecideTool = createDecorator("waveDecideTool");
219298
219298
  }));
219299
219299
  // ../../packages/agent-core-v2/src/features/wave/core/config.ts
219300
+ function seats(prefix, count) {
219301
+ return Array.from({ length: Math.max(0, count) }, (_, index) => `${prefix}${index + 1}`);
219302
+ }
219303
+ function waveRoundSeats(roles, round) {
219304
+ if (round === "propose") return seats("P", roles.proponents);
219305
+ if (round === "defend") return seats("D", roles.defenders);
219306
+ if (round === "attack") return seats("A", roles.adversaries);
219307
+ if (round === "judge") return seats("J", roles.judges);
219308
+ const refiners = seats("R", roles.refiners);
219309
+ return round === "refine" ? refiners.slice(0, 1) : refiners.slice(1);
219310
+ }
219311
+ function waveConfigState(config) {
219312
+ const state = config;
219313
+ return {
219314
+ ...config,
219315
+ version: state.version ?? 1,
219316
+ wavesOpen: state.wavesOpen ?? [],
219317
+ raw: state.raw ?? {}
219318
+ };
219319
+ }
219300
219320
  function record$2(value) {
219301
219321
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
219302
219322
  }
@@ -219317,6 +219337,9 @@ function makeWaveConfig(raw, loadedFrom) {
219317
219337
  const roles = record$2(base["roles"]);
219318
219338
  const quorum = record$2(base["quorum"]);
219319
219339
  return {
219340
+ version: num(base["version"], 1),
219341
+ wavesOpen: strArray$1(base["wavesOpen"], []),
219342
+ raw: base,
219320
219343
  layout: {
219321
219344
  waves: text(layout["waves"], DEFAULT_LAYOUT.waves),
219322
219345
  backlog: text(layout["backlog"], DEFAULT_LAYOUT.backlog),
@@ -219378,8 +219401,24 @@ function containedPath(root, candidate) {
219378
219401
  if (resolved !== resolvedRoot && !resolved.startsWith(`${resolvedRoot}${sep}`)) throw new Error(`ruta fuera del workspace: ${candidate}`);
219379
219402
  return resolved;
219380
219403
  }
219381
- var DEFAULT_LAYOUT, DEFAULT_THRESHOLDS, DEFAULT_VOCABULARY, DEFAULT_ROLES, DEFAULT_QUORUM;
219404
+ var WAVE_ROUNDS, WAVE_ROUND_DIRS, DEFAULT_LAYOUT, DEFAULT_THRESHOLDS, DEFAULT_VOCABULARY, DEFAULT_ROLES, DEFAULT_QUORUM;
219382
219405
  var init_config$6 = __esmMin((() => {
219406
+ WAVE_ROUNDS = [
219407
+ "propose",
219408
+ "defend",
219409
+ "attack",
219410
+ "judge",
219411
+ "refine",
219412
+ "audit"
219413
+ ];
219414
+ WAVE_ROUND_DIRS = {
219415
+ propose: "01-proposals",
219416
+ defend: "02-defenses",
219417
+ attack: "03-attacks",
219418
+ judge: "04-verdicts",
219419
+ refine: "05-refined",
219420
+ audit: "05-refined"
219421
+ };
219383
219422
  DEFAULT_LAYOUT = {
219384
219423
  waves: "design/waves",
219385
219424
  backlog: "backlog",
@@ -219559,23 +219598,16 @@ function evaluateQuorum(input) {
219559
219598
  const winner = ranked[0];
219560
219599
  const second = ranked[1];
219561
219600
  if (winner === void 0) throw new Error("no hay propuestas que comparar");
219562
- if (second === void 0) {
219563
- const perJudge = input.verdicts.map((verdict) => ({
219564
- judge: verdict.judge,
219565
- margin: 0,
219566
- holds: true
219567
- }));
219568
- return {
219569
- verdict: "adopt",
219570
- winner,
219571
- second: void 0,
219572
- medians,
219573
- medianMargin: 0,
219574
- judgesHoldingMargin: perJudge.length,
219575
- perJudge,
219576
- reason: `solo hay una propuesta: ${winner}`
219577
- };
219578
- }
219601
+ if (second === void 0) return {
219602
+ verdict: "refine",
219603
+ winner,
219604
+ second: void 0,
219605
+ medians,
219606
+ medianMargin: 0,
219607
+ judgesHoldingMargin: 0,
219608
+ perJudge: [],
219609
+ reason: `sin comparacion no hay quorum: solo hay una propuesta (${winner})`
219610
+ };
219579
219611
  const winnerMedian = medians[winner] ?? 0;
219580
219612
  const secondMedian = medians[second] ?? 0;
219581
219613
  const medianMargin = Number((winnerMedian - secondMedian).toFixed(6));
@@ -219645,6 +219677,7 @@ function runWaveDecideForRoot(input) {
219645
219677
  isError: true
219646
219678
  };
219647
219679
  }
219680
+ const holding = result.perJudge.length === 0 ? "sin comparacion" : `${result.judgesHoldingMargin}/${result.perJudge.length}`;
219648
219681
  const lines = [
219649
219682
  `QUORUM — ${result.verdict}`,
219650
219683
  "",
@@ -219652,8 +219685,9 @@ function runWaveDecideForRoot(input) {
219652
219685
  "",
219653
219686
  ` ganador: ${result.winner ?? "(empate)"}`,
219654
219687
  ` margen: ${result.medianMargin} (minimo ${input.minPoints ?? config.quorum.minPoints})`,
219655
- ` jueces con margen: ${result.judgesHoldingMargin}/${result.perJudge.length} (minimo ${input.minAxes ?? config.quorum.minAxes})`,
219688
+ ` jueces con margen: ${holding} (minimo ${input.minAxes ?? config.quorum.minAxes})`,
219656
219689
  ...result.perJudge.map((entry) => ` ${entry.judge}: ${entry.margin}${entry.holds ? " ok" : " NO"}`),
219690
+ ...result.perJudge.filter((entry) => entry.margin < 0).map((entry) => ` disidencia: ${entry.judge} ${entry.margin}`),
219657
219691
  "",
219658
219692
  ` motivo: ${result.reason}`
219659
219693
  ];
@@ -219671,7 +219705,7 @@ var init_decideRunner = __esmMin((() => {
219671
219705
  // ../../packages/agent-core-v2/src/features/wave/tools/decide/decide.md?raw
219672
219706
  var decide_default;
219673
219707
  var init_decide = __esmMin((() => {
219674
- decide_default = "Compute the wave quorum from the judges' totals and say what may happen next.\n\nPass one entry per judge with its totals per proposal (`{ judge: \"J1\", scores: { P1: 17, P2: 15, P3: 14 } }`). The tool takes the **median** per proposal, then checks the winner beats the second by `minPoints` in at least `minAxes` judges. Thresholds default to `workflow.config.json`.\n\nAnswers `adopt`, `refine` (no quorum yet, compose R1 and audit with R2) or `deadlock` (two rounds without quorum: adopt R1 with written dissent). A tie in the median never crowns a winner.\n";
219708
+ decide_default = "Compute the wave quorum from the judges' totals and say what may happen next.\n\nPass one entry per judge with its totals per proposal (`{ judge: \"J1\", scores: { P1: 17, P2: 15, P3: 14 } }`). The tool takes the **median** per proposal and applies both conditions of `core/quorum.ts`: the winner's median must beat the second's by `minPoints` (**margen de medianas**), and at least `minAxes` judges must give the winner that margin or more over the second (**mayoría de jueces**). Thresholds default to `workflow.config.json`.\n\nAnswers `adopt`, `refine` (no quorum yet, compose R1 and audit with R2) or `deadlock` (two rounds without quorum: adopt R1 with written dissent). A tie in the median, or a single proposal with nothing to compare, never crowns a winner and never counts as quorum.\n";
219675
219709
  }));
219676
219710
  // ../../packages/agent-core-v2/src/features/wave/tools/decide/decideTool.ts
219677
219711
  var WaveDecideTool;
@@ -219767,6 +219801,18 @@ function walk$1(fs, dir, accept, acc = []) {
219767
219801
  }
219768
219802
  var init_fsJson = __esmMin((() => {}));
219769
219803
  // ../../packages/agent-core-v2/src/features/wave/tools/open/openRunner.ts
219804
+ function briefTemplate(root) {
219805
+ const fs = nodeWaveFs;
219806
+ const local = join(root, BRIEF_TEMPLATE$1);
219807
+ if (fs.exists(local)) return { file: local };
219808
+ if (fs.isDirectory(join(root, "workflow"))) return { missing: BRIEF_TEMPLATE$1 };
219809
+ return fs.exists(BUNDLED_BRIEF_TEMPLATE) ? { file: BUNDLED_BRIEF_TEMPLATE } : { missing: BRIEF_TEMPLATE$1 };
219810
+ }
219811
+ function nextEpic(backlogDir) {
219812
+ const numbers = asArray$2(asRecord$2(readJson$2(nodeWaveFs, join(backlogDir, "epics.json")) ?? {})["epics"]).map((epic) => /^E(\d{2,})$/.exec(String(asRecord$2(epic)["id"]))?.[1]).filter((value) => value !== void 0).map(Number);
219813
+ const next = numbers.length > 0 ? Math.max(...numbers) + 1 : 1;
219814
+ return `E${String(next).padStart(2, "0")}`;
219815
+ }
219770
219816
  function slugify(name) {
219771
219817
  return name.normalize("NFD").replaceAll(/[\u0300-\u036F]/g, "").toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/^-+|-+$/g, "");
219772
219818
  }
@@ -219780,15 +219826,29 @@ function runWaveOpenForRoot(input) {
219780
219826
  };
219781
219827
  const layout = asRecord$2(asRecord$2(rawConfig)["layout"]);
219782
219828
  const wavesRel = typeof layout["waves"] === "string" ? layout["waves"] : "design/waves";
219829
+ const backlogRel = typeof layout["backlog"] === "string" ? layout["backlog"] : "backlog";
219830
+ const minimum = makeWaveConfig(rawConfig).thresholds.minBriefSections;
219783
219831
  let wavesDir;
219832
+ let backlogDir;
219784
219833
  try {
219785
219834
  wavesDir = containedPath(input.root, wavesRel);
219835
+ backlogDir = containedPath(input.root, backlogRel);
219786
219836
  } catch (error) {
219787
219837
  return {
219788
219838
  output: error instanceof Error ? error.message : String(error),
219789
219839
  isError: true
219790
219840
  };
219791
219841
  }
219842
+ const template = briefTemplate(input.root);
219843
+ if (template.file === void 0) return {
219844
+ output: `falta ${template.missing ?? BRIEF_TEMPLATE$1}: no se genera un brief sin la plantilla unica`,
219845
+ isError: true
219846
+ };
219847
+ const source = fs.read(template.file);
219848
+ if (source === void 0) return {
219849
+ output: `no se pudo leer ${template.file}`,
219850
+ isError: true
219851
+ };
219792
219852
  const existing = fs.list(wavesDir).filter((name) => fs.isDirectory(join(wavesDir, name))).map((name) => Number(/^W(\d+)/.exec(name)?.[1] ?? NaN)).filter((value) => !Number.isNaN(value));
219793
219853
  const next = input.id ?? `W${existing.length > 0 ? Math.max(...existing) + 1 : 0}`;
219794
219854
  if (!/^W\d{1,2}$/.test(next)) return {
@@ -219805,8 +219865,9 @@ function runWaveOpenForRoot(input) {
219805
219865
  isError: true
219806
219866
  };
219807
219867
  const dir = join(wavesDir, `${next}-${slug}`);
219868
+ const brief = source.replaceAll("{{WAVE}}", next).replaceAll("{{NAME}}", slug).replaceAll("{{NEXT_EPIC}}", nextEpic(backlogDir));
219808
219869
  for (const sub of SUBDIRS) fs.mkdir(join(dir, sub));
219809
- fs.write(join(dir, "00-brief.md"), BRIEF_SKELETON.replaceAll("{{WAVE}}", next).replaceAll("{{NAME}}", slug));
219870
+ fs.write(join(dir, "00-brief.md"), brief);
219810
219871
  const declared = asArray$2(asRecord$2(rawConfig)["waves"]).map(String);
219811
219872
  if (!declared.includes(next)) writeJson(fs, configFile, {
219812
219873
  ...asRecord$2(rawConfig),
@@ -219814,11 +219875,11 @@ function runWaveOpenForRoot(input) {
219814
219875
  });
219815
219876
  return { output: [
219816
219877
  `oleada creada: ${next}-${slug}`,
219817
- ` brief: ${wavesRel}/${next}-${slug}/00-brief.md (rellena las 7 secciones antes de lanzar asientos)`,
219878
+ ` brief: ${wavesRel}/${next}-${slug}/00-brief.md (rellena las ${minimum} secciones antes de lanzar asientos)`,
219818
219879
  ` config: ${next} declarada en workflow.config.json (el gate la exige desde ahora)`
219819
219880
  ].join("\n") };
219820
219881
  }
219821
- var SUBDIRS, BRIEF_SKELETON;
219882
+ var SUBDIRS, BRIEF_TEMPLATE$1, BUNDLED_BRIEF_TEMPLATE;
219822
219883
  var init_openRunner = __esmMin((() => {
219823
219884
  init_config$6();
219824
219885
  init_fsJson();
@@ -219830,24 +219891,8 @@ var init_openRunner = __esmMin((() => {
219830
219891
  "04-verdicts",
219831
219892
  "05-refined"
219832
219893
  ];
219833
- BRIEF_SKELETON = [
219834
- "# {{WAVE}} — Brief: {{NAME}}",
219835
- "",
219836
- "## 1. Contexto vinculante",
219837
- "",
219838
- "## 2. Alcance",
219839
- "",
219840
- "## 3. Evidencia disponible",
219841
- "",
219842
- "## 4. Preguntas que esta oleada resuelve",
219843
- "",
219844
- "## 5. Formato de salida",
219845
- "",
219846
- "## 6. Reglas duras",
219847
- "",
219848
- "## 7. Rúbrica (0-5)",
219849
- ""
219850
- ].join("\n");
219894
+ BRIEF_TEMPLATE$1 = "workflow/templates/brief.md";
219895
+ BUNDLED_BRIEF_TEMPLATE = resolve(import.meta.dirname, "../../../../../../../workflow/templates/brief.md");
219851
219896
  }));
219852
219897
  // ../../packages/agent-core-v2/src/features/wave/tools/open/open.md?raw
219853
219898
  var open_default;
@@ -219905,6 +219950,85 @@ var init_round$1 = __esmMin((() => {
219905
219950
  }).strict();
219906
219951
  IWaveRoundTool = createDecorator("waveRoundTool");
219907
219952
  }));
219953
+ // ../../packages/agent-core-v2/src/features/wave/tools/submit/submitRunner.ts
219954
+ function seatTicketHash(ticket) {
219955
+ return createHash("sha256").update(ticket).digest("hex");
219956
+ }
219957
+ function findWaveDir(root, wavesRel, wave) {
219958
+ const fs = nodeWaveFs;
219959
+ const wavesDir = containedPath(root, wavesRel);
219960
+ const name = fs.list(wavesDir).filter((entry) => fs.isDirectory(join(wavesDir, entry))).find((entry) => entry === wave || entry.startsWith(`${wave}-`));
219961
+ return name === void 0 ? void 0 : join(wavesDir, name);
219962
+ }
219963
+ function runWaveSubmitForRoot(input) {
219964
+ const fs = nodeWaveFs;
219965
+ const rawConfig = readJson$2(fs, join(input.root, "workflow.config.json"));
219966
+ if (rawConfig === void 0) return {
219967
+ output: `no hay workflow.config.json en ${input.root}`,
219968
+ isError: true
219969
+ };
219970
+ const config = makeWaveConfig(rawConfig);
219971
+ const wavesRel = config.layout.waves;
219972
+ const seats = waveRoundSeats(config.roles, input.round);
219973
+ if (!seats.includes(input.role)) return {
219974
+ output: `seats.unknown_seat: el rol "${input.role}" no pertenece a la ronda ${input.round} (esperado ${seats.join(", ")})`,
219975
+ isError: true
219976
+ };
219977
+ let base;
219978
+ try {
219979
+ base = findWaveDir(input.root, wavesRel, input.wave);
219980
+ } catch (error) {
219981
+ return {
219982
+ output: error instanceof Error ? error.message : String(error),
219983
+ isError: true
219984
+ };
219985
+ }
219986
+ if (base === void 0) return {
219987
+ output: `no existe la oleada ${input.wave} en ${wavesRel}`,
219988
+ isError: true
219989
+ };
219990
+ const dirName = WAVE_ROUND_DIRS[input.round];
219991
+ const dir = join(base, dirName);
219992
+ if (!fs.isDirectory(dir)) return {
219993
+ output: `falta el directorio ${dirName} en la oleada ${input.wave}`,
219994
+ isError: true
219995
+ };
219996
+ const seatsDir = join(base, WAVE_SEATS_DIR);
219997
+ if (fs.isDirectory(seatsDir) && fs.list(seatsDir).length > 0) {
219998
+ const presented = (input.ticket ?? "").trim();
219999
+ if (presented === "") return {
220000
+ output: `seats.foreign_replace: el asiento ${input.role} no presento el ticket que emitio la ronda`,
220001
+ isError: true
220002
+ };
220003
+ const stored = (fs.read(join(seatsDir, input.role)) ?? "").trim();
220004
+ if (stored === "" || stored !== seatTicketHash(presented)) return {
220005
+ output: `seats.foreign_replace: el ticket presentado no es el del asiento ${input.role}`,
220006
+ isError: true
220007
+ };
220008
+ if (input.replace === true && (input.reason ?? "").trim() === "") return {
220009
+ output: `seats.replace_without_reason: reescribir el artefacto del asiento ${input.role} exige un motivo no vacio`,
220010
+ isError: true
220011
+ };
220012
+ }
220013
+ const file = join(dir, `${input.role}.md`);
220014
+ if (fs.exists(file) && input.replace !== true) return {
220015
+ output: `el asiento ${input.role} ya entrego su artefacto; un asiento escribe una vez (usa replace solo para relanzar un fallo)`,
220016
+ isError: true
220017
+ };
220018
+ if (input.artifact.trim() === "") return {
220019
+ output: "el artefacto esta vacio: un asiento que no escribe, fallo",
220020
+ isError: true
220021
+ };
220022
+ fs.write(file, input.artifact.endsWith("\n") ? input.artifact : `${input.artifact}\n`);
220023
+ return { output: `escrito ${wavesRel}/${base.split("/").pop()}/${dirName}/${input.role}.md` };
220024
+ }
220025
+ var WAVE_SEATS_DIR;
220026
+ var init_submitRunner = __esmMin((() => {
220027
+ init_config$6();
220028
+ init_fsJson();
220029
+ init_nodeFs();
220030
+ WAVE_SEATS_DIR = ".seats";
220031
+ }));
219908
220032
  // ../../packages/agent-core-v2/src/features/wave/tools/round/prompts.ts
219909
220033
  var SEAT_PRELUDE, ROLE_PROMPTS, JUDGE_BIASES;
219910
220034
  var init_prompts$2 = __esmMin((() => {
@@ -219932,16 +220056,6 @@ var init_prompts$2 = __esmMin((() => {
219932
220056
  ];
219933
220057
  }));
219934
220058
  // ../../packages/agent-core-v2/src/features/wave/tools/round/roundRunner.ts
219935
- function range(prefix, count) {
219936
- return Array.from({ length: Math.max(1, count) }, (_, index) => `${prefix}${index + 1}`);
219937
- }
219938
- function roleOf(cfg, round) {
219939
- if (round === "propose") return cfg.proponents;
219940
- if (round === "defend") return cfg.defenders;
219941
- if (round === "attack") return cfg.adversaries;
219942
- if (round === "judge") return cfg.judges;
219943
- return 1;
219944
- }
219945
220059
  function runWaveRoundForRoot(input) {
219946
220060
  const fs = nodeWaveFs;
219947
220061
  const rawConfig = readJson$2(fs, join(input.root, "workflow.config.json"));
@@ -219950,9 +220064,8 @@ function runWaveRoundForRoot(input) {
219950
220064
  seats: [],
219951
220065
  isError: true
219952
220066
  };
219953
- const config = asRecord$2(rawConfig);
219954
- const layout = asRecord$2(config["layout"]);
219955
- const wavesRel = typeof layout["waves"] === "string" ? layout["waves"] : "design/waves";
220067
+ const config = makeWaveConfig(rawConfig);
220068
+ const wavesRel = config.layout.waves;
219956
220069
  let wavesDir;
219957
220070
  try {
219958
220071
  wavesDir = containedPath(input.root, wavesRel);
@@ -219977,35 +220090,35 @@ function runWaveRoundForRoot(input) {
219977
220090
  isError: true
219978
220091
  };
219979
220092
  const sections = brief.match(/^##\s+\S/gm)?.length ?? 0;
219980
- if (sections < 7) return {
219981
- output: `el brief tiene ${sections} secciones < 7: rellena las siete antes de lanzar la ronda`,
220093
+ const minimum = config.thresholds.minBriefSections;
220094
+ if (sections < minimum) return {
220095
+ output: `el brief tiene ${sections} secciones < ${minimum}: rellena las ${minimum} antes de lanzar la ronda`,
219982
220096
  seats: [],
219983
220097
  isError: true
219984
220098
  };
219985
- const roles = asRecord$2(config["roles"]);
219986
- const counts = {
219987
- proponents: typeof roles["proponents"] === "number" ? roles["proponents"] : 3,
219988
- defenders: typeof roles["defenders"] === "number" ? roles["defenders"] : 3,
219989
- adversaries: typeof roles["adversaries"] === "number" ? roles["adversaries"] : 3,
219990
- judges: typeof roles["judges"] === "number" ? roles["judges"] : 3
219991
- };
219992
- const shape = ROUNDS[input.round];
219993
- const seats = shape.roles(roleOf(counts, input.round)).map((role) => {
220099
+ const dir = WAVE_ROUND_DIRS[input.round];
220100
+ const seatsDir = join(wavesDir, waveDir, WAVE_SEATS_DIR);
220101
+ const seats = waveRoundSeats(config.roles, input.round).map((role) => {
219994
220102
  const bias = input.round === "judge" ? JUDGE_BIASES[(Number(role.slice(1)) - 1) % JUDGE_BIASES.length] : void 0;
219995
- const file = `${wavesRel}/${waveDir}/${shape.dir}/${role}.md`;
220103
+ const file = `${wavesRel}/${waveDir}/${dir}/${role}.md`;
220104
+ const ticket = randomBytes(16).toString("hex");
220105
+ fs.write(join(seatsDir, role), `${seatTicketHash(ticket)}\n`);
219996
220106
  const lines = [
219997
220107
  SEAT_PRELUDE,
219998
220108
  "",
219999
220109
  ROLE_PROMPTS[input.round] ?? "",
220000
220110
  "",
220111
+ `Perfil: ${WAVE_SEAT_PROFILE}`,
220001
220112
  `Oleada: ${input.wave}. Brief: ${briefRel}.`,
220002
220113
  `Escribe: ${file}.`,
220003
- `Entrega con WaveSubmit({ wave: "${input.wave}", round: "${input.round}", role: "${role}", artifact: "..." }).`
220114
+ `Ticket de asiento: ${ticket}. Es capacidad, no identidad: la ronda lo emitio para este asiento y WaveSubmit lo exige.`,
220115
+ `Entrega con WaveSubmit({ wave: "${input.wave}", round: "${input.round}", role: "${role}", ticket: "${ticket}", artifact: "..." }).`
220004
220116
  ];
220005
220117
  if (bias !== void 0) lines.push(`Tu sesgo declarado es ${bias}: puntua CON el, no contra el.`);
220006
220118
  return {
220007
220119
  role,
220008
220120
  file,
220121
+ ticket,
220009
220122
  prompt: lines.join("\n")
220010
220123
  };
220011
220124
  });
@@ -220014,38 +220127,13 @@ function runWaveRoundForRoot(input) {
220014
220127
  seats
220015
220128
  };
220016
220129
  }
220017
- var ROUNDS;
220018
220130
  var init_roundRunner = __esmMin((() => {
220019
220131
  init_config$6();
220020
220132
  init_fsJson();
220021
220133
  init_nodeFs();
220134
+ init_wave$3();
220135
+ init_submitRunner();
220022
220136
  init_prompts$2();
220023
- ROUNDS = {
220024
- propose: {
220025
- dir: "01-proposals",
220026
- roles: (count) => range("P", count)
220027
- },
220028
- defend: {
220029
- dir: "02-defenses",
220030
- roles: (count) => range("D", count)
220031
- },
220032
- attack: {
220033
- dir: "03-attacks",
220034
- roles: (count) => range("A", count)
220035
- },
220036
- judge: {
220037
- dir: "04-verdicts",
220038
- roles: (count) => range("J", count)
220039
- },
220040
- refine: {
220041
- dir: "05-refined",
220042
- roles: () => ["R1"]
220043
- },
220044
- audit: {
220045
- dir: "05-refined",
220046
- roles: () => ["R2"]
220047
- }
220048
- };
220049
220137
  }));
220050
220138
  // ../../packages/agent-core-v2/src/features/wave/tools/round/round.md?raw
220051
220139
  var round_default;
@@ -220101,120 +220189,17 @@ var init_submit$1 = __esmMin((() => {
220101
220189
  ]),
220102
220190
  role: string().min(2),
220103
220191
  artifact: string().min(1),
220192
+ ticket: string().optional(),
220193
+ reason: string().optional(),
220104
220194
  replace: boolean$1().optional(),
220105
220195
  root: string().optional()
220106
220196
  }).strict();
220107
220197
  IWaveSubmitTool = createDecorator("waveSubmitTool");
220108
220198
  }));
220109
- // ../../packages/agent-core-v2/src/features/wave/tools/submit/submitRunner.ts
220110
- function findWaveDir(root, wavesRel, wave) {
220111
- const fs = nodeWaveFs;
220112
- const wavesDir = containedPath(root, wavesRel);
220113
- const name = fs.list(wavesDir).filter((entry) => fs.isDirectory(join(wavesDir, entry))).find((entry) => entry === wave || entry.startsWith(`${wave}-`));
220114
- return name === void 0 ? void 0 : join(wavesDir, name);
220115
- }
220116
- function runWaveSubmitForRoot(input) {
220117
- const fs = nodeWaveFs;
220118
- const rawConfig = readJson$2(fs, join(input.root, "workflow.config.json"));
220119
- if (rawConfig === void 0) return {
220120
- output: `no hay workflow.config.json en ${input.root}`,
220121
- isError: true
220122
- };
220123
- const layout = asRecord$2(asRecord$2(rawConfig)["layout"]);
220124
- const wavesRel = typeof layout["waves"] === "string" ? layout["waves"] : "design/waves";
220125
- const slot = ROUND_SLOTS[input.round];
220126
- if (!slot.pattern.test(input.role)) return {
220127
- output: `el rol "${input.role}" no pertenece a la ronda ${input.round} (esperado ${slot.names.join(", ")})`,
220128
- isError: true
220129
- };
220130
- let base;
220131
- try {
220132
- base = findWaveDir(input.root, wavesRel, input.wave);
220133
- } catch (error) {
220134
- return {
220135
- output: error instanceof Error ? error.message : String(error),
220136
- isError: true
220137
- };
220138
- }
220139
- if (base === void 0) return {
220140
- output: `no existe la oleada ${input.wave} en ${wavesRel}`,
220141
- isError: true
220142
- };
220143
- const dir = join(base, slot.dir);
220144
- if (!fs.isDirectory(dir)) return {
220145
- output: `falta el directorio ${slot.dir} en la oleada ${input.wave}`,
220146
- isError: true
220147
- };
220148
- const file = join(dir, `${input.role}.md`);
220149
- if (fs.exists(file) && input.replace !== true) return {
220150
- output: `el asiento ${input.role} ya entrego su artefacto; un asiento escribe una vez (usa replace solo para relanzar un fallo)`,
220151
- isError: true
220152
- };
220153
- if (input.artifact.trim() === "") return {
220154
- output: "el artefacto esta vacio: un asiento que no escribe, fallo",
220155
- isError: true
220156
- };
220157
- fs.write(file, input.artifact.endsWith("\n") ? input.artifact : `${input.artifact}\n`);
220158
- return { output: `escrito ${wavesRel}/${base.split("/").pop()}/${slot.dir}/${input.role}.md` };
220159
- }
220160
- var ROUND_SLOTS;
220161
- var init_submitRunner = __esmMin((() => {
220162
- init_config$6();
220163
- init_fsJson();
220164
- init_nodeFs();
220165
- ROUND_SLOTS = {
220166
- propose: {
220167
- dir: "01-proposals",
220168
- pattern: /^P\d+$/,
220169
- names: [
220170
- "P1",
220171
- "P2",
220172
- "P3"
220173
- ]
220174
- },
220175
- defend: {
220176
- dir: "02-defenses",
220177
- pattern: /^D\d+$/,
220178
- names: [
220179
- "D1",
220180
- "D2",
220181
- "D3"
220182
- ]
220183
- },
220184
- attack: {
220185
- dir: "03-attacks",
220186
- pattern: /^A\d+$/,
220187
- names: [
220188
- "A1",
220189
- "A2",
220190
- "A3"
220191
- ]
220192
- },
220193
- judge: {
220194
- dir: "04-verdicts",
220195
- pattern: /^J\d+$/,
220196
- names: [
220197
- "J1",
220198
- "J2",
220199
- "J3"
220200
- ]
220201
- },
220202
- refine: {
220203
- dir: "05-refined",
220204
- pattern: /^R1$/,
220205
- names: ["R1"]
220206
- },
220207
- audit: {
220208
- dir: "05-refined",
220209
- pattern: /^R2$/,
220210
- names: ["R2"]
220211
- }
220212
- };
220213
- }));
220214
220199
  // ../../packages/agent-core-v2/src/features/wave/tools/submit/submit.md?raw
220215
220200
  var submit_default;
220216
220201
  var init_submit = __esmMin((() => {
220217
- submit_default = "Deliver the artifact for your seat. This is the only way a seat writes: the tool binds the role to its slot and refuses anything else.\n\nRole and round must match — `propose` accepts P1..Pn, `defend` D1..Dn, `attack` A1..An, `judge` J1..Jn, `refine` R1, `audit` R2. A seat writes once; use `replace` only to relaunch a failed seat after its artifact was already written.\n\nPass the artifact as the text/JSON body. An empty artifact is rejected: a seat that did not write has failed, and the round must be relaunched rather than summarized by the orchestrator.\n";
220202
+ submit_default = "Deliver the artifact for your seat. This is the only way a seat writes: the tool binds the role to its slot and refuses anything else.\n\nRole and round must match — `propose` accepts P1..Pn, `defend` D1..Dn, `attack` A1..An, `judge` J1..Jn, `refine` R1, `audit` R2..Rn. A seat writes once; use `replace` only to relaunch a failed seat after its artifact was already written.\n\nPass the `ticket` the round gave you: the tool hashes it and compares it with the sha256 the round left in `design/waves/<W>/.seats/<role>`. A missing ticket, or the ticket of another seat, is refused as `seats.foreign_replace`. The ticket is capacity, not identity (capacidad, no identidad): it proves the orchestrator issued this seat, it does not say who wrote the artifact. `replace` also needs a non-empty `reason`, or the tool refuses it as `seats.replace_without_reason`: a silent overwrite is the substitution this tool exists to prevent.\n\nPass the artifact as the text/JSON body. An empty artifact is rejected: a seat that did not write has failed, and the round must be relaunched rather than summarized by the orchestrator.\n";
220218
220203
  }));
220219
220204
  // ../../packages/agent-core-v2/src/features/wave/tools/submit/submitTool.ts
220220
220205
  var WaveSubmitTool;
@@ -220244,6 +220229,8 @@ var init_submitTool = __esmMin((() => {
220244
220229
  round: args.round,
220245
220230
  role: args.role,
220246
220231
  artifact: args.artifact,
220232
+ ticket: args.ticket,
220233
+ reason: args.reason,
220247
220234
  replace: args.replace
220248
220235
  }))
220249
220236
  };
@@ -220251,7 +220238,196 @@ var init_submitTool = __esmMin((() => {
220251
220238
  };
220252
220239
  WaveSubmitTool = __decorate([__decorateParam(0, ISessionContext)], WaveSubmitTool);
220253
220240
  }));
220241
+ // ../../packages/agent-core-v2/src/features/wave/core/verify/runner.ts
220242
+ function splitCommand(command) {
220243
+ const tokens = [];
220244
+ let current = "";
220245
+ let quote;
220246
+ for (const char of command) {
220247
+ if (quote !== void 0) {
220248
+ if (char === quote) quote = void 0;
220249
+ else current += char;
220250
+ continue;
220251
+ }
220252
+ if (char === "\"" || char === "'") {
220253
+ quote = char;
220254
+ continue;
220255
+ }
220256
+ if (SHELL_METACHARACTERS.has(char)) throw new Error(`metacaracter de shell prohibido en: ${command}`);
220257
+ if (char === " " || char === " ") {
220258
+ if (current !== "") {
220259
+ tokens.push(current);
220260
+ current = "";
220261
+ }
220262
+ continue;
220263
+ }
220264
+ current += char;
220265
+ }
220266
+ if (quote !== void 0) throw new Error(`comillas sin cerrar en: ${command}`);
220267
+ if (current !== "") tokens.push(current);
220268
+ if (tokens.length === 0) throw new Error("comando vacio");
220269
+ return tokens;
220270
+ }
220271
+ function containPaths(argv, root) {
220272
+ for (const token of argv) {
220273
+ if (token.startsWith("-")) continue;
220274
+ if (!token.includes("/") && !/\.(mjs|cjs|js|ts|sh)$/.test(token)) continue;
220275
+ containedPath(root, token);
220276
+ }
220277
+ }
220278
+ function runCommand$2(command, options) {
220279
+ let argv;
220280
+ try {
220281
+ argv = splitCommand(command);
220282
+ const binary = (argv[0] ?? "").replace(/\.exe$/i, "");
220283
+ const allowed = options.allowBinaries.map((item) => item.replace(/\.exe$/i, ""));
220284
+ if (!allowed.includes(binary)) throw new Error(`binario no permitido: ${binary} (permitidos: ${allowed.join(", ")})`);
220285
+ containPaths(argv, options.root);
220286
+ } catch (error) {
220287
+ return {
220288
+ ok: false,
220289
+ output: `rechazado: ${error instanceof Error ? error.message : String(error)}`,
220290
+ rejected: true
220291
+ };
220292
+ }
220293
+ const result = spawnSync(argv[0] ?? "", argv.slice(1), {
220294
+ cwd: options.cwd ?? options.root,
220295
+ encoding: "utf8",
220296
+ timeout: options.timeoutMs ?? DEFAULT_TIMEOUT_MS$1,
220297
+ stdio: "pipe",
220298
+ shell: false
220299
+ });
220300
+ const output = `${result.stdout ?? ""}${result.stderr ?? ""}${result.error === void 0 ? "" : result.error.message}`.replaceAll(/\s+/g, " ").trim().slice(0, 300);
220301
+ if (result.error !== void 0) return {
220302
+ ok: false,
220303
+ output: `no se pudo ejecutar: ${output}`,
220304
+ rejected: false
220305
+ };
220306
+ return {
220307
+ ok: result.status === 0,
220308
+ output,
220309
+ rejected: false,
220310
+ status: result.status ?? void 0
220311
+ };
220312
+ }
220313
+ function exitDetail(outcome) {
220314
+ return outcome.status === void 0 ? outcome.output : `exit ${outcome.status}: ${outcome.output}`;
220315
+ }
220316
+ function evaluateMutant(ac, verifyCommand, options) {
220317
+ const script = asText$1(asRecord$2(ac["falsifier"])["script"]);
220318
+ if (script === "") return {
220319
+ state: "declarado",
220320
+ detail: "sin falsifier.script: el AC queda declarado y cuenta como FALLO"
220321
+ };
220322
+ const acId = asText$1(ac["id"]);
220323
+ const apply = runCommand$2(`${script} apply`, options);
220324
+ if (apply.rejected) return {
220325
+ state: "rechazado",
220326
+ detail: `apply rechazado (${script}, ${acId}): ${apply.output}`
220327
+ };
220328
+ let mutated;
220329
+ let restore;
220330
+ try {
220331
+ if (apply.ok) mutated = runCommand$2(verifyCommand, options);
220332
+ } finally {
220333
+ restore = runCommand$2(`${script} restore`, options);
220334
+ }
220335
+ const failures = [];
220336
+ if (!apply.ok) failures.push(`apply fallo (${script}, ${acId}) ${exitDetail(apply)}`);
220337
+ if (restore.rejected || !restore.ok) failures.push(`restore fallo (${script}, ${acId}), el arbol puede estar sucio: ${restore.output}`);
220338
+ if (failures.length > 0) return {
220339
+ state: "error",
220340
+ detail: failures.join(" · ")
220341
+ };
220342
+ if (mutated === void 0) return {
220343
+ state: "error",
220344
+ detail: `no se ejecuto el verify con la mutacion de ${acId}`
220345
+ };
220346
+ if (mutated.ok) return {
220347
+ state: "sobrevive",
220348
+ detail: "el verify pasa con la mutacion aplicada: el AC no muerde"
220349
+ };
220350
+ if (!runCommand$2(verifyCommand, options).ok) return {
220351
+ state: "error",
220352
+ detail: "el verify no vuelve a verde tras restaurar"
220353
+ };
220354
+ return {
220355
+ state: "muerto",
220356
+ detail: "rojo con la mutacion, verde tras restaurar"
220357
+ };
220358
+ }
220359
+ function selectTaskCriteria(fs, root, cfg, taskId, ac) {
220360
+ const tasksDir = containedPath(root, cfg.layout.backlog);
220361
+ const file = walk$1(fs, join(tasksDir, "tasks"), (path) => path.endsWith(`${taskId}.json`))[0];
220362
+ if (file === void 0) throw new Error(`no encuentro la task ${taskId} en ${cfg.layout.backlog}/tasks/`);
220363
+ const criteria = asArray$2(asRecord$2(readJson$2(fs, file))["ac"]).map(asRecord$2);
220364
+ if (ac === void 0) return {
220365
+ taskId,
220366
+ criteria
220367
+ };
220368
+ const selected = criteria.filter((entry) => asText$1(entry["id"]) === ac);
220369
+ if (selected.length === 0) throw new Error(`no existe el criterio ${ac} en la task ${taskId}`);
220370
+ return {
220371
+ taskId,
220372
+ criteria: selected
220373
+ };
220374
+ }
220375
+ function verifyTask(fs, root, cfg, taskId, options) {
220376
+ const { criteria } = selectTaskCriteria(fs, root, cfg, taskId, options?.ac);
220377
+ const runOptions = {
220378
+ root,
220379
+ allowBinaries: cfg.vocabulary.verifyCommands,
220380
+ cwd: options?.cwd,
220381
+ timeoutMs: options?.timeoutMs
220382
+ };
220383
+ const acs = [];
220384
+ let declared = 0;
220385
+ for (const ac of criteria) {
220386
+ const verifyCommand = asText$1(ac["verify"]);
220387
+ const verify = runCommand$2(verifyCommand, runOptions);
220388
+ const mutant = evaluateMutant(ac, verifyCommand, runOptions);
220389
+ if (mutant.state === "declarado") declared++;
220390
+ const declaredOk = options?.allowDeclared === true && mutant.state === "declarado";
220391
+ const ok = verify.ok && (mutant.state === "muerto" || declaredOk);
220392
+ acs.push({
220393
+ acId: asText$1(ac["id"]),
220394
+ verify,
220395
+ mutant,
220396
+ ok
220397
+ });
220398
+ }
220399
+ return {
220400
+ taskId,
220401
+ acs,
220402
+ declared,
220403
+ ok: acs.length > 0 && acs.every((ac) => ac.ok)
220404
+ };
220405
+ }
220406
+ var SHELL_METACHARACTERS, DEFAULT_TIMEOUT_MS$1;
220407
+ var init_runner = __esmMin((() => {
220408
+ init_config$6();
220409
+ init_fsJson();
220410
+ SHELL_METACHARACTERS = /* @__PURE__ */ new Set([
220411
+ "|",
220412
+ "&",
220413
+ ";",
220414
+ "<",
220415
+ ">",
220416
+ "`",
220417
+ "$",
220418
+ "\n",
220419
+ "\r"
220420
+ ]);
220421
+ DEFAULT_TIMEOUT_MS$1 = 6e5;
220422
+ }));
220254
220423
  // ../../packages/agent-core-v2/src/features/wave/core/gate/checks.ts
220424
+ function measurableThreshold(text, units) {
220425
+ const low = normalize$1(text);
220426
+ if (OPERATOR_WITH_NUMBER.test(low)) return true;
220427
+ if (EXACT_VALUE.test(low)) return true;
220428
+ if (units.some((unit) => new RegExp(`\\d\\s*${escapeRegExp$1(unit)}\\b`).test(low))) return true;
220429
+ return low.split(/\s*(?:,|;|\by\b)\s*/).filter(Boolean).length >= 3;
220430
+ }
220255
220431
  function record$1(value) {
220256
220432
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
220257
220433
  }
@@ -220281,13 +220457,349 @@ function walk(fs, dir, accept, acc = []) {
220281
220457
  }
220282
220458
  function requiredArtifacts(cfg) {
220283
220459
  const required = ["00-brief.md"];
220284
- for (let i = 1; i <= cfg.roles.proponents; i++) required.push(`01-proposals/P${i}.md`);
220285
- for (let i = 1; i <= cfg.roles.defenders; i++) required.push(`02-defenses/D${i}.md`);
220286
- for (let i = 1; i <= cfg.roles.adversaries; i++) required.push(`03-attacks/A${i}.md`);
220287
- for (let i = 1; i <= cfg.roles.judges; i++) required.push(`04-verdicts/J${i}.md`);
220288
- required.push("05-refined/R1.md", "05-refined/R2.md", DECISION_FILE, "07-epics.json");
220460
+ for (const round of WAVE_ROUNDS) for (const seat of waveRoundSeats(cfg.roles, round)) required.push(`${WAVE_ROUND_DIRS[round]}/${seat}.md`);
220461
+ required.push(DECISION_FILE, "07-epics.json");
220289
220462
  return required;
220290
220463
  }
220464
+ function briefSections(text) {
220465
+ const sections = [];
220466
+ let current;
220467
+ for (const line of text.split("\n")) {
220468
+ const heading = /^##\s+(.+?)\s*$/.exec(line);
220469
+ if (heading !== null) {
220470
+ current = {
220471
+ title: heading[1] ?? "",
220472
+ body: []
220473
+ };
220474
+ sections.push(current);
220475
+ continue;
220476
+ }
220477
+ if (/^#\s+/.test(line)) {
220478
+ current = void 0;
220479
+ continue;
220480
+ }
220481
+ current?.body.push(line);
220482
+ }
220483
+ return sections.map((section) => ({
220484
+ title: section.title,
220485
+ body: section.body.join("\n").trim()
220486
+ }));
220487
+ }
220488
+ function sectionKeyword(title) {
220489
+ return normalize$1(title).replace(/^\d+\.?\s*/, "").match(/[a-z0-9]+/)?.[0] ?? "";
220490
+ }
220491
+ function decisionStrip(value) {
220492
+ return (value ?? "").replaceAll("*", "").trim();
220493
+ }
220494
+ function decisionSplitRow(line) {
220495
+ return line.trim().replace(/^\|/, "").replace(/\|$/, "").split("|").map((cell) => cell.trim());
220496
+ }
220497
+ function isDecisionSeparator(row) {
220498
+ return row.length > 0 && row.every((cell) => DECISION_SEPARATOR.test(decisionStrip(cell)));
220499
+ }
220500
+ function decisionTables(text) {
220501
+ const blocks = [];
220502
+ let current = [];
220503
+ for (const line of text.split("\n")) {
220504
+ if (line.trim().startsWith("|")) {
220505
+ current.push(decisionSplitRow(line));
220506
+ continue;
220507
+ }
220508
+ if (current.length > 0) blocks.push(current);
220509
+ current = [];
220510
+ }
220511
+ if (current.length > 0) blocks.push(current);
220512
+ return blocks.filter((block) => block.length >= 2).map((block) => ({
220513
+ header: block[0] ?? [],
220514
+ body: block.slice(1).filter((row) => !isDecisionSeparator(row) && row.length === (block[0] ?? []).length)
220515
+ }));
220516
+ }
220517
+ function decisionNumber(cell) {
220518
+ const value = decisionStrip(cell);
220519
+ if (value === "") return void 0;
220520
+ const match = DECISION_LEADING_NUMBER.exec(value);
220521
+ return match === null ? void 0 : Number(match[0].replace(",", "."));
220522
+ }
220523
+ function decisionProposal(cell) {
220524
+ const value = decisionStrip(cell);
220525
+ if (value === "") return void 0;
220526
+ return DECISION_PROPOSAL.test(value.toLowerCase()) ? value.toUpperCase() : void 0;
220527
+ }
220528
+ function decisionAxis(row, axes) {
220529
+ const low = normalize$1(decisionStrip(row));
220530
+ if (low === "") return void 0;
220531
+ const exact = axes.find((axis) => normalize$1(axis) === low);
220532
+ if (exact !== void 0) return exact;
220533
+ return axes.find((axis) => low.includes(normalize$1(axis)));
220534
+ }
220535
+ function isDecisionTotal(cell) {
220536
+ return /\btotal\b/.test(normalize$1(decisionStrip(cell)));
220537
+ }
220538
+ function isDecisionMedian(cell) {
220539
+ return /\bmedian/.test(normalize$1(decisionStrip(cell)));
220540
+ }
220541
+ function decisionColumn(header, predicate) {
220542
+ return header.findIndex((cell) => predicate(cell));
220543
+ }
220544
+ function findDecisionMatrix(blocks, axes) {
220545
+ let best;
220546
+ for (const block of blocks) {
220547
+ const header = block.header.map(decisionStrip);
220548
+ const columnProposals = header.filter((cell) => decisionProposal(cell) !== void 0);
220549
+ const rowProposals = block.body.filter((row) => decisionProposal(row[0]) !== void 0);
220550
+ const axisColumns = header.filter((cell) => decisionAxis(cell, axes) !== void 0 && !isDecisionTotal(cell) && !isDecisionMedian(cell));
220551
+ const axisRows = block.body.filter((row) => decisionAxis(row[0], axes) !== void 0);
220552
+ const byColumn = columnProposals.length >= 2 && axisRows.length > 0;
220553
+ const byRow = rowProposals.length >= 2 && axisColumns.length > 0;
220554
+ if (!byColumn && !byRow) continue;
220555
+ const score = Math.max(axisRows.length, axisColumns.length) * 10 + Math.max(columnProposals.length, rowProposals.length);
220556
+ if (best === void 0 || score > best.score) best = {
220557
+ block,
220558
+ orientation: byColumn ? "columns" : "rows",
220559
+ score
220560
+ };
220561
+ }
220562
+ return best;
220563
+ }
220564
+ function readDecisionColumns(block, axes, proposals, found) {
220565
+ const indexes = /* @__PURE__ */ new Map();
220566
+ for (const [index, cell] of block.header.entries()) {
220567
+ const proposal = decisionProposal(cell);
220568
+ if (proposal !== void 0 && !indexes.has(proposal)) indexes.set(proposal, index);
220569
+ }
220570
+ for (const row of block.body) {
220571
+ const axis = decisionAxis(row[0], axes);
220572
+ if (axis === void 0 || found.has(axis)) continue;
220573
+ const cells = {};
220574
+ found.set(axis, {
220575
+ label: decisionStrip(row[0]),
220576
+ cells
220577
+ });
220578
+ for (const proposal of proposals) {
220579
+ const index = indexes.get(proposal);
220580
+ cells[proposal] = index === void 0 ? void 0 : decisionNumber(row[index]);
220581
+ }
220582
+ }
220583
+ return indexes;
220584
+ }
220585
+ function readDecisionRows(block, axes, found) {
220586
+ const columns = [];
220587
+ for (const [index, cell] of block.header.entries()) {
220588
+ const axis = decisionAxis(cell, axes);
220589
+ if (axis === void 0 || isDecisionTotal(cell) || isDecisionMedian(cell)) continue;
220590
+ columns.push({
220591
+ axis,
220592
+ index,
220593
+ label: decisionStrip(cell)
220594
+ });
220595
+ }
220596
+ const seen = [];
220597
+ for (const row of block.body) {
220598
+ const proposal = decisionProposal(row[0]);
220599
+ if (proposal === void 0) continue;
220600
+ if (!seen.includes(proposal)) seen.push(proposal);
220601
+ for (const column of columns) {
220602
+ const existing = found.get(column.axis);
220603
+ const entry = existing ?? {
220604
+ label: column.label,
220605
+ cells: {}
220606
+ };
220607
+ if (existing === void 0) found.set(column.axis, entry);
220608
+ if (entry.cells[proposal] !== void 0) continue;
220609
+ entry.cells[proposal] = decisionNumber(row[column.index]);
220610
+ }
220611
+ }
220612
+ return seen;
220613
+ }
220614
+ function decisionDeclarations(blocks, matrix, proposals) {
220615
+ const totals = {};
220616
+ const medians = {};
220617
+ const take = (target, proposal, value) => {
220618
+ if (proposal === void 0 || value === void 0 || target[proposal] !== void 0) return;
220619
+ target[proposal] = value;
220620
+ };
220621
+ if (matrix !== void 0) {
220622
+ const header = matrix.block.header.map(decisionStrip);
220623
+ const totalColumn = matrix.orientation === "rows" ? decisionColumn(header, isDecisionTotal) : -1;
220624
+ const medianColumn = matrix.orientation === "rows" ? decisionColumn(header, isDecisionMedian) : -1;
220625
+ for (const row of matrix.block.body) {
220626
+ const proposal = decisionProposal(row[0]);
220627
+ if (proposal !== void 0) {
220628
+ if (totalColumn !== -1) take(totals, proposal, decisionNumber(row[totalColumn]));
220629
+ if (medianColumn !== -1) take(medians, proposal, decisionNumber(row[medianColumn]));
220630
+ continue;
220631
+ }
220632
+ if (isDecisionTotal(row[0]) || isDecisionMedian(row[0])) {
220633
+ const target = isDecisionTotal(row[0]) ? totals : medians;
220634
+ for (const item of proposals) {
220635
+ const index = header.findIndex((cell) => decisionProposal(cell) === item);
220636
+ take(target, item, index === -1 ? void 0 : decisionNumber(row[index]));
220637
+ }
220638
+ }
220639
+ }
220640
+ }
220641
+ for (const block of blocks) {
220642
+ const header = block.header.map(decisionStrip);
220643
+ const totalColumn = decisionColumn(header, isDecisionTotal);
220644
+ const medianColumn = decisionColumn(header, isDecisionMedian);
220645
+ if (totalColumn === -1 && medianColumn === -1) continue;
220646
+ for (const row of block.body) {
220647
+ const proposal = decisionProposal(row[0]);
220648
+ if (proposal === void 0) continue;
220649
+ if (totalColumn !== -1) take(totals, proposal, decisionNumber(row[totalColumn]));
220650
+ if (medianColumn !== -1) take(medians, proposal, decisionNumber(row[medianColumn]));
220651
+ }
220652
+ }
220653
+ return {
220654
+ totals,
220655
+ medians
220656
+ };
220657
+ }
220658
+ function decisionMargins(blocks) {
220659
+ for (const block of blocks) {
220660
+ const header = block.header.map(decisionStrip);
220661
+ const judgeColumn = decisionColumn(header, (cell) => normalize$1(cell).includes("juez"));
220662
+ const marginColumn = decisionColumn(header, (cell) => normalize$1(cell).includes("margen"));
220663
+ if (judgeColumn === -1 || marginColumn === -1) continue;
220664
+ const preferColumn = decisionColumn(header, (cell) => normalize$1(cell).includes("prefer"));
220665
+ const rows = [];
220666
+ for (const row of block.body) {
220667
+ const judge = decisionStrip(row[judgeColumn]);
220668
+ if (!DECISION_JUDGE.test(judge)) continue;
220669
+ const margin = DECISION_SIGNED_MARGIN.exec(decisionStrip(row[marginColumn]));
220670
+ if (margin === null) continue;
220671
+ rows.push({
220672
+ judge,
220673
+ margin: Number((margin[1] ?? "0").replace(",", ".")),
220674
+ prefers: preferColumn === -1 ? void 0 : decisionProposal(row[preferColumn])
220675
+ });
220676
+ }
220677
+ if (rows.length > 0) return rows;
220678
+ }
220679
+ return [];
220680
+ }
220681
+ function decisionDissent(text) {
220682
+ const lines = text.split("\n");
220683
+ const start = lines.findIndex((line) => /^#{1,3}\s+\d*\.?\s*disidencias/i.test(line));
220684
+ if (start === -1) return void 0;
220685
+ const level = /^(#{1,3})/.exec(lines[start] ?? "")?.[1]?.length ?? 1;
220686
+ const collected = [];
220687
+ for (const line of lines.slice(start + 1)) {
220688
+ const heading = /^(#{1,3})\s+\S/.exec(line);
220689
+ if (heading !== null && (heading[1] ?? "").length <= level) break;
220690
+ collected.push(line);
220691
+ }
220692
+ return collected.join("\n").trim();
220693
+ }
220694
+ function parseDecisionMatrix(text, options) {
220695
+ const axes = options.axes.map(String);
220696
+ const blocks = decisionTables(text);
220697
+ const matrix = findDecisionMatrix(blocks, axes);
220698
+ const found = /* @__PURE__ */ new Map();
220699
+ let proposals = options.proposals.map(String);
220700
+ let present = [];
220701
+ if (matrix !== void 0) {
220702
+ if (matrix.orientation === "columns") {
220703
+ if (proposals.length === 0) proposals = matrix.block.header.map(decisionStrip).map(decisionProposal).filter((label) => label !== void 0);
220704
+ const indexes = readDecisionColumns(matrix.block, axes, proposals, found);
220705
+ present = proposals.filter((proposal) => indexes.has(proposal));
220706
+ } else {
220707
+ if (proposals.length === 0) proposals = matrix.block.body.map((row) => decisionProposal(row[0])).filter((label) => label !== void 0);
220708
+ const seen = readDecisionRows(matrix.block, axes, found);
220709
+ present = proposals.filter((proposal) => seen.includes(proposal));
220710
+ }
220711
+ }
220712
+ const axisRows = [];
220713
+ const cells = [];
220714
+ const missing = [];
220715
+ for (const axis of axes) {
220716
+ const row = found.get(axis);
220717
+ if (row === void 0) continue;
220718
+ axisRows.push({
220719
+ axis,
220720
+ label: row.label,
220721
+ cells: row.cells
220722
+ });
220723
+ for (const proposal of present) {
220724
+ const value = row.cells[proposal];
220725
+ if (typeof value === "number") cells.push({
220726
+ axis,
220727
+ proposal,
220728
+ value
220729
+ });
220730
+ else missing.push({
220731
+ axis,
220732
+ proposal
220733
+ });
220734
+ }
220735
+ }
220736
+ const declarations = decisionDeclarations(blocks, matrix, proposals);
220737
+ const axesFound = axisRows.map((row) => row.axis);
220738
+ return {
220739
+ present: matrix !== void 0,
220740
+ orientation: matrix?.orientation,
220741
+ header: matrix === void 0 ? [] : matrix.block.header.map(decisionStrip),
220742
+ proposals,
220743
+ presentProposals: present,
220744
+ axisRows,
220745
+ cells,
220746
+ missing,
220747
+ axesFound,
220748
+ axesMissing: axes.filter((axis) => !axesFound.includes(axis)),
220749
+ totals: declarations.totals,
220750
+ medians: declarations.medians,
220751
+ margins: decisionMargins(blocks),
220752
+ dissent: decisionDissent(text)
220753
+ };
220754
+ }
220755
+ function decisionProposalSum(matrix, proposal) {
220756
+ const values = matrix.axisRows.map((row) => row.cells[proposal]);
220757
+ if (values.length === 0) return void 0;
220758
+ let total = 0;
220759
+ for (const value of values) {
220760
+ if (typeof value !== "number") return void 0;
220761
+ total += value;
220762
+ }
220763
+ return total;
220764
+ }
220765
+ function decisionMedian(values) {
220766
+ if (values.length === 0) return void 0;
220767
+ const sorted = [...values].toSorted((left, right) => left - right);
220768
+ const middle = Math.floor(sorted.length / 2);
220769
+ const current = sorted[middle];
220770
+ if (current === void 0) return void 0;
220771
+ if (sorted.length % 2 === 1) return current;
220772
+ const previous = sorted[middle - 1];
220773
+ return previous === void 0 ? void 0 : (previous + current) / 2;
220774
+ }
220775
+ function decisionRecalculateMedians(verdicts, options) {
220776
+ const tables = [];
220777
+ const missing = [];
220778
+ for (const verdict of verdicts) {
220779
+ const matrix = parseDecisionMatrix(verdict.text, options);
220780
+ if (!(matrix.present && matrix.axesMissing.length === 0 && matrix.missing.length === 0)) {
220781
+ missing.push(verdict.file);
220782
+ continue;
220783
+ }
220784
+ const totals = {};
220785
+ for (const proposal of matrix.proposals) totals[proposal] = decisionProposalSum(matrix, proposal) ?? NaN;
220786
+ tables.push({
220787
+ file: verdict.file,
220788
+ totals
220789
+ });
220790
+ }
220791
+ const medians = {};
220792
+ if (missing.length === 0 && tables.length > 0) for (const proposal of options.proposals) {
220793
+ const values = tables.map((entry) => entry.totals[proposal]).filter((value) => typeof value === "number" && Number.isFinite(value));
220794
+ if (values.length !== tables.length) continue;
220795
+ const value = decisionMedian(values);
220796
+ if (value !== void 0) medians[proposal] = value;
220797
+ }
220798
+ return {
220799
+ medians,
220800
+ missing
220801
+ };
220802
+ }
220291
220803
  function runWaveGate(fs, root, cfg) {
220292
220804
  const findings = [];
220293
220805
  const blocks = [];
@@ -220315,8 +220827,24 @@ function runWaveGate(fs, root, cfg) {
220315
220827
  let epics = [];
220316
220828
  let tasks = [];
220317
220829
  let totalAc = 0;
220830
+ const state = waveConfigState(cfg);
220318
220831
  block("config", () => {
220319
220832
  if (cfg.loadedFrom === void 0) add("config.missing", null, "no se encontro workflow.config.json");
220833
+ const raw = state.raw;
220834
+ const version = typeof raw["version"] === "number" ? raw["version"] : state.version;
220835
+ for (const key of [
220836
+ "version",
220837
+ "layout",
220838
+ "waves",
220839
+ "thresholds",
220840
+ "vocabulary"
220841
+ ]) if (raw[key] === void 0) add("config.missing_key", null, `falta la clave "${key}"`);
220842
+ if (version !== 1 && version !== 2) add("config.version_unknown", null, `version "${String(version)}" fuera de uno y dos: el gate no sabe leerla`);
220843
+ if (version === 2) {
220844
+ if (raw["wavesOpen"] === void 0) add("config.missing_key", null, "falta la clave \"wavesOpen\"");
220845
+ if (raw["vocabulary"] === void 0 || record$1(raw["vocabulary"])["units"] === void 0) add("config.missing_key", null, "falta la clave \"vocabulary.units\"");
220846
+ if (raw["falsifier"] === void 0) add("config.missing_key", null, "falta la clave \"falsifier\"");
220847
+ }
220320
220848
  if (cfg.waves.length === 0) add("config.bad_waves", null, "waves debe ser un array no vacio");
220321
220849
  if (vocabulary.verifyCommands.length === 0) add("config.bad_vocabulary", null, "vocabulary.verifyCommands vacio");
220322
220850
  if (vocabulary.verifyPrefixes.length === 0) add("config.bad_vocabulary", null, "vocabulary.verifyPrefixes vacio");
@@ -220352,19 +220880,89 @@ function runWaveGate(fs, root, cfg) {
220352
220880
  }
220353
220881
  }
220354
220882
  });
220883
+ block("brief", () => {
220884
+ const templateFile = join(root, BRIEF_TEMPLATE);
220885
+ if (!fs.exists(templateFile)) return;
220886
+ const template = briefSections(fs.read(templateFile) ?? "");
220887
+ if (wavesDir === void 0 || !fs.isDirectory(wavesDir)) return;
220888
+ for (const wave of cfg.waves) {
220889
+ const match = waveDirs.find((name) => name === wave || name.startsWith(`${wave}-`));
220890
+ if (match === void 0) continue;
220891
+ const file = join(wavesDir, match, "00-brief.md");
220892
+ const text = fs.read(file);
220893
+ if (text === void 0) continue;
220894
+ const at = relative$1(root, file);
220895
+ const sections = briefSections(text);
220896
+ if (sections.length !== template.length) add("waves.brief_template_drift", at, `${sections.length} secciones frente a ${template.length} de ${BRIEF_TEMPLATE}: el brief ya no sigue la plantilla`);
220897
+ for (const [index, expected] of template.entries()) {
220898
+ const section = sections[index];
220899
+ if (section === void 0) continue;
220900
+ if (sectionKeyword(section.title) !== sectionKeyword(expected.title)) add("waves.brief_template_drift", at, `la seccion ${index + 1} se titula "${section.title}" y ${BRIEF_TEMPLATE} exige "${expected.title}"`);
220901
+ }
220902
+ for (const section of sections) if (section.body === "") add("waves.brief_empty_section", at, `la seccion "${section.title}" no tiene contenido`);
220903
+ }
220904
+ });
220355
220905
  block("decision", () => {
220356
220906
  const dir = wavesDir;
220357
220907
  if (dir === void 0 || !fs.isDirectory(dir)) return;
220908
+ const axes = cfg.quorum.axes;
220909
+ const proposals = Array.from({ length: cfg.roles.proponents }, (_, index) => `P${index + 1}`);
220910
+ const judges = cfg.roles.judges;
220358
220911
  for (const wave of cfg.waves) {
220359
220912
  const match = waveDirs.find((name) => name === wave || name.startsWith(`${wave}-`));
220360
220913
  if (match === void 0) continue;
220361
- const file = join(dir, match, DECISION_FILE);
220914
+ const base = join(dir, match);
220915
+ const file = join(base, DECISION_FILE);
220362
220916
  const text = fs.read(file);
220363
220917
  if (text === void 0) continue;
220364
220918
  const low = normalize$1(text);
220365
220919
  const at = relative$1(root, file);
220366
- for (let i = 1; i <= cfg.roles.proponents; i++) if (!low.includes(`p${i}`)) add("waves.decision_missing_role", at, `no nombra la propuesta P${i} de la matriz`);
220367
- for (const axis of cfg.quorum.axes) if (!low.includes(normalize$1(axis))) add("waves.decision_missing_axis", at, `sin eje "${axis}" en la matriz`);
220920
+ const matrix = parseDecisionMatrix(text, {
220921
+ axes,
220922
+ proposals
220923
+ });
220924
+ for (const proposal of proposals) if (!matrix.presentProposals.includes(proposal)) add("waves.decision_missing_role", at, `no hay columna ${proposal} en la matriz de la ronda`);
220925
+ if (!matrix.present) add("waves.decision_without_quorum", at, "sin tabla de la matriz: la ronda no trae puntuaciones que auditar");
220926
+ else {
220927
+ for (const axis of axes) if (!matrix.axesFound.includes(axis)) add("waves.decision_missing_axis", at, `sin eje "${axis}" en la matriz`);
220928
+ for (const item of matrix.missing) add("waves.decision_matrix_incomplete", at, `eje "${item.axis}" · propuesta ${item.proposal}: celda vacia`);
220929
+ for (const proposal of proposals) {
220930
+ const declared = matrix.totals[proposal];
220931
+ const sum = decisionProposalSum(matrix, proposal);
220932
+ if (declared === void 0 || sum === void 0 || declared === sum) continue;
220933
+ add("waves.decision_total_mismatch", at, `${proposal}: total declarado ${declared} y recalculado ${sum} desde las celdas de la matriz`);
220934
+ }
220935
+ if (proposals.some((proposal) => matrix.medians[proposal] !== void 0)) {
220936
+ const verdictsDir = join(base, "04-verdicts");
220937
+ const recalc = decisionRecalculateMedians(Array.from({ length: judges }, (_, index) => {
220938
+ const name = `J${index + 1}.md`;
220939
+ return {
220940
+ file: `${relative$1(root, verdictsDir)}/${name}`,
220941
+ text: fs.read(join(verdictsDir, name)) ?? ""
220942
+ };
220943
+ }), {
220944
+ axes,
220945
+ proposals
220946
+ });
220947
+ for (const missing of recalc.missing) add("waves.decision_matrix_incomplete", missing, `sin tabla de ${axes.length} ejes por propuesta: no hay de donde recalcular la mediana`);
220948
+ if (recalc.missing.length === 0) {
220949
+ const citation = Array.from({ length: judges }, (_, index) => `04-verdicts/J${index + 1}.md`).join(", ");
220950
+ for (const proposal of proposals) {
220951
+ const declared = matrix.medians[proposal];
220952
+ const value = recalc.medians[proposal];
220953
+ if (declared === void 0 || value === void 0 || declared === value) continue;
220954
+ add("waves.decision_median_mismatch", at, `${proposal}: mediana declarada ${declared} y recalculada ${value} desde ${citation}`);
220955
+ }
220956
+ }
220957
+ }
220958
+ if (matrix.margins.length > 0) {
220959
+ const dissent = normalize$1(matrix.dissent ?? "");
220960
+ for (const row of matrix.margins) {
220961
+ if (row.margin >= 0 || dissent.includes(normalize$1(row.judge))) continue;
220962
+ add("waves.decision_undisclosed_dissent", at, `${row.judge}: margen ${row.margin} y no aparece en "## Disidencias"`);
220963
+ }
220964
+ }
220965
+ }
220368
220966
  if (!/^#{1,3}\s+\d*\.?\s*disidencias/im.test(text)) add("waves.decision_without_dissent", at, "falta la seccion \"## Disidencias\"");
220369
220967
  if (text.length < thresholds.minDecisionBytes) add("waves.decision_thin", at, `${text.length} B < ${thresholds.minDecisionBytes} B minimos`);
220370
220968
  const numbers = text.match(/\b\d+(?:[.,]\d+)?\b/g)?.length ?? 0;
@@ -220453,8 +221051,16 @@ function runWaveGate(fs, root, cfg) {
220453
221051
  if (tasks.length < thresholds.minTasks) add("tasks.count", null, `${tasks.length} tasks < ${thresholds.minTasks} minimas`);
220454
221052
  });
220455
221053
  block("ac", () => {
220456
- const vague = vocabulary.vagueWords.map((word) => normalize$1(word)).filter(Boolean);
221054
+ const vague = vocabulary.vagueWords.map((word) => normalize$1(word)).filter(Boolean).map((word) => ({
221055
+ word,
221056
+ root: word.replace(/[aeiou]$/, "")
221057
+ })).filter((entry) => entry.root !== "");
221058
+ const units = asArray$1(record$1(state.raw["vocabulary"])["units"]).map(str$2).map(normalize$1).filter(Boolean);
221059
+ const falsifierLexicon = record$1(state.raw["falsifier"]);
221060
+ const stateVerbs = asArray$1(falsifierLexicon["stateVerbs"]).map(str$2).map(normalize$1).filter(Boolean);
221061
+ const stateObjects = asArray$1(falsifierLexicon["stateObjects"]).map(str$2).map(normalize$1).filter(Boolean);
220457
221062
  const withFalsifier = /* @__PURE__ */ new Set();
221063
+ const openWaves = new Set(state.wavesOpen);
220458
221064
  for (const task of tasks) {
220459
221065
  const taskId = str$2(task["id"]);
220460
221066
  for (const raw of asArray$1(task["ac"])) {
@@ -220474,17 +221080,53 @@ function runWaveGate(fs, root, cfg) {
220474
221080
  }
220475
221081
  const metric = str$2(ac["metric"]);
220476
221082
  if (metric !== "" && metric.length < thresholds.minMetricChars) add("ac.short_metric", at, `"metric" < ${thresholds.minMetricChars} caracteres`);
220477
- const prose = normalize$1(`${str$2(ac["then"])} ${str$2(ac["threshold"])} ${metric}`);
220478
- const hit = vague.find((word) => new RegExp(`(^|[^a-z0-9])${word}([^a-z0-9]|$)`).test(prose));
220479
- if (hit !== void 0) add("ac.vague_language", at, `"${hit}" no es medible: el AC no muerde`);
221083
+ const threshold = str$2(ac["threshold"]);
221084
+ if (threshold !== "" && !measurableThreshold(threshold, units)) add("ac.threshold_not_measurable", at, `"${threshold}" no compara ninguna magnitud: falta operador con numero, numero con unidad o conjunto enumerable`);
221085
+ const prose = normalize$1(`${str$2(ac["then"])} ${threshold} ${metric}`);
221086
+ const vagueHit = vague.find((entry) => new RegExp(`\\b${escapeRegExp$1(entry.root)}[a-z]{0,10}\\b`).test(prose));
221087
+ if (vagueHit !== void 0) {
221088
+ const matched = new RegExp(`\\b${escapeRegExp$1(vagueHit.root)}[a-z]{0,10}\\b`).exec(prose)?.[0] ?? vagueHit.root;
221089
+ add("ac.vague_language", at, `"${matched}" contiene la raiz "${vagueHit.root}" de "${vagueHit.word}": no es medible`);
221090
+ }
220480
221091
  const verify = str$2(ac["verify"]).trim();
220481
221092
  if (verify !== "") {
220482
221093
  const command = verify.split(/\s+/)[0] ?? "";
220483
221094
  if (!vocabulary.verifyCommands.includes(command)) add("ac.verify_not_command", at, `"${command}" no es un comando de la lista permitida`);
220484
221095
  else {
220485
- const matched = verify.match(/\b((?:[\w.-]+\/)+[\w.-]+)/)?.[1];
220486
- if (matched === void 0) add("ac.verify_outside_test_plan", at, "el comando no apunta a ningun fichero o directorio verificable");
220487
- else if (!vocabulary.verifyPrefixes.some((prefix) => matched.startsWith(prefix))) add("ac.verify_outside_test_plan", at, `"${matched}" fuera del test plan declarado`);
221096
+ let argv;
221097
+ try {
221098
+ argv = splitCommand(verify);
221099
+ } catch (error) {
221100
+ add("ac.verify_command_unsafe", at, error instanceof Error ? error.message : String(error));
221101
+ }
221102
+ if (argv !== void 0) {
221103
+ let escaped;
221104
+ for (const token of argv) {
221105
+ if (token.startsWith("-") || !token.includes("/")) continue;
221106
+ try {
221107
+ containedPath(root, token);
221108
+ } catch {
221109
+ escaped = token;
221110
+ break;
221111
+ }
221112
+ }
221113
+ if (escaped !== void 0) add("ac.verify_command_unsafe", at, `el token "${escaped}" escapa de la raiz del paquete`);
221114
+ else {
221115
+ const token = argv.slice(1).find((item) => !item.startsWith("-") && item.includes("/"));
221116
+ if (token === void 0) add("ac.verify_outside_test_plan", at, "el comando no apunta a ningun fichero o directorio verificable");
221117
+ else {
221118
+ const target = token.replace(/^\.\//, "");
221119
+ if (!vocabulary.verifyPrefixes.some((prefix) => target.startsWith(prefix))) add("ac.verify_outside_test_plan", at, `"${target}" fuera del test plan declarado`);
221120
+ else if (state.version >= 2 && !openWaves.has(str$2(task["wave"])) && !fs.exists(join(root, target))) add("ac.verify_target_missing", at, `el destino "${target}" no existe bajo la raiz del paquete`);
221121
+ const destination = join(root, target);
221122
+ const isTest = /(^|\/)test\//.test(target) || /\.test\./.test(target);
221123
+ const exempt = /(^|\/)scripts\//.test(target);
221124
+ if (isTest && !exempt && fs.exists(destination) && fs.isDirectory(destination) === false) {
221125
+ if ((fs.read(destination) ?? "").match(ASSERTION) === null) add("ac.verify_stub", at, `el destino "${target}" no tiene ninguna asercion: el verify no mide nada`);
221126
+ }
221127
+ }
221128
+ }
221129
+ }
220488
221130
  }
220489
221131
  }
220490
221132
  const falsifier = record$1(ac["falsifier"]);
@@ -220494,6 +221136,11 @@ function runWaveGate(fs, root, cfg) {
220494
221136
  if (mutation.length < thresholds.minFieldChars) add("ac.falsifier_thin", at, "falsifier.mutation insuficiente");
220495
221137
  else if (expect.length < thresholds.minFieldChars) add("ac.falsifier_thin", at, "falsifier.expect insuficiente");
220496
221138
  else withFalsifier.add(taskId);
221139
+ const normalizedMutation = normalize$1(mutation);
221140
+ const verb = stateVerbs.find((entry) => new RegExp(`\\b${escapeRegExp$1(entry)}[a-z]{0,4}\\b`).test(normalizedMutation));
221141
+ const object = stateObjects.find((entry) => new RegExp(`\\b${escapeRegExp$1(entry)}s?\\b`).test(normalizedMutation));
221142
+ const reversible = str$2(falsifier["script"]).trim() !== "" || str$2(falsifier["restore"]).trim() !== "";
221143
+ if (verb !== void 0 && object !== void 0 && !reversible) add("ac.falsifier_not_reversible", at, `la mutacion "${verb} ... ${object}" toca estado sin falsifier.script ni falsifier.restore`);
220497
221144
  }
220498
221145
  }
220499
221146
  }
@@ -220555,14 +221202,17 @@ function runWaveGate(fs, root, cfg) {
220555
221202
  waves: cfg.waves.length,
220556
221203
  epics: epics.length,
220557
221204
  tasks: tasks.length,
220558
- ac: totalAc
221205
+ ac: totalAc,
221206
+ notRun: NOT_RUN_BLOCKS
220559
221207
  }
220560
221208
  };
220561
221209
  }
220562
- var DECISION_FILE, AC_FIELDS, normalize$1, relative$1;
221210
+ var DECISION_FILE, BRIEF_TEMPLATE, AC_FIELDS, OPERATOR_WITH_NUMBER, EXACT_VALUE, ASSERTION, escapeRegExp$1, NOT_RUN_BLOCKS, normalize$1, relative$1, DECISION_SEPARATOR, DECISION_PROPOSAL, DECISION_LEADING_NUMBER, DECISION_JUDGE, DECISION_SIGNED_MARGIN;
220563
221211
  var init_checks = __esmMin((() => {
220564
221212
  init_config$6();
221213
+ init_runner();
220565
221214
  DECISION_FILE = "06-DECISION.md";
221215
+ BRIEF_TEMPLATE = "workflow/templates/brief.md";
220566
221216
  AC_FIELDS = [
220567
221217
  "id",
220568
221218
  "given",
@@ -220573,8 +221223,24 @@ var init_checks = __esmMin((() => {
220573
221223
  "verify",
220574
221224
  "falsifier"
220575
221225
  ];
221226
+ OPERATOR_WITH_NUMBER = /[=<>≥≤]\s*\d|\b(?:mayor|menor|igual|al menos|como maximo|como minimo)\b[^0-9]{0,20}\d/;
221227
+ EXACT_VALUE = /\b\d+(?:[.,]\d+)?\s+(?:exacto|exacta|exactos|exactas|fijo|fija|justo|justa)\b/;
221228
+ ASSERTION = /\bassert\b|\bexpect\s*\(|\bit\s*\(|\btest\s*\(|\bfailures\b|process\.exit\s*\(\s*(?!0\b)/g;
221229
+ escapeRegExp$1 = (value) => value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
221230
+ NOT_RUN_BLOCKS = [{
221231
+ block: "regression",
221232
+ closedBy: "oracle"
221233
+ }, {
221234
+ block: "schemas",
221235
+ closedBy: "oracle"
221236
+ }];
220576
221237
  normalize$1 = (value) => value.normalize("NFD").replaceAll(/[\u0300-\u036F]/g, "").toLowerCase();
220577
221238
  relative$1 = (root, path) => path.startsWith(root) ? path.slice(root.length).replaceAll("\\", "/").replace(/^\/+/, "") : path;
221239
+ DECISION_SEPARATOR = /^:?-{2,}:?$/;
221240
+ DECISION_PROPOSAL = /^p\d+$/;
221241
+ DECISION_LEADING_NUMBER = /^[+-]?\d+(?:[.,]\d+)?/;
221242
+ DECISION_JUDGE = /^J\d+$/;
221243
+ DECISION_SIGNED_MARGIN = /^([+-]?\d+(?:[.,]\d+)?)$/;
220578
221244
  }));
220579
221245
  // ../../packages/agent-core-v2/src/features/wave/tools/gate/gateRunner.ts
220580
221246
  function runWaveGateForRoot(input) {
@@ -220596,6 +221262,7 @@ function runWaveGateForRoot(input) {
220596
221262
  };
220597
221263
  const lines = [`GATE — ${report.ok ? "verde" : "con hallazgos"}`, ""];
220598
221264
  for (const block of report.blocks) lines.push(`${block.ok ? "OK " : "FAIL"} ${block.name}`);
221265
+ for (const entry of report.summary.notRun) lines.push(`NO EJECUTADO ${entry.block} — cerrado por ${entry.closedBy}`);
220599
221266
  if (report.findings.length > 0) {
220600
221267
  lines.push("", `PENDIENTE: ${report.findings.length} hallazgo(s)`, "");
220601
221268
  for (const finding of report.findings) lines.push(` - ${finding.code}${finding.path === null ? "" : ` ${finding.path}`} — ${finding.message}`);
@@ -220654,168 +221321,37 @@ var init_verify$1 = __esmMin((() => {
220654
221321
  init_instantiation();
220655
221322
  WaveVerifyToolInputSchema = object$1({
220656
221323
  task: string().min(3),
220657
- root: string().optional()
221324
+ root: string().optional(),
221325
+ ac: string().min(3).optional(),
221326
+ dryRun: boolean$1().optional(),
221327
+ allowDeclared: boolean$1().optional()
220658
221328
  }).strict();
220659
221329
  IWaveVerifyTool = createDecorator("waveVerifyTool");
220660
221330
  }));
220661
- // ../../packages/agent-core-v2/src/features/wave/core/verify/runner.ts
220662
- function splitCommand(command) {
220663
- const tokens = [];
220664
- let current = "";
220665
- let quote;
220666
- for (const char of command) {
220667
- if (quote !== void 0) {
220668
- if (char === quote) quote = void 0;
220669
- else current += char;
220670
- continue;
220671
- }
220672
- if (char === "\"" || char === "'") {
220673
- quote = char;
220674
- continue;
220675
- }
220676
- if (SHELL_METACHARACTERS.has(char)) throw new Error(`metacaracter de shell prohibido en: ${command}`);
220677
- if (char === " " || char === " ") {
220678
- if (current !== "") {
220679
- tokens.push(current);
220680
- current = "";
220681
- }
220682
- continue;
220683
- }
220684
- current += char;
220685
- }
220686
- if (quote !== void 0) throw new Error(`comillas sin cerrar en: ${command}`);
220687
- if (current !== "") tokens.push(current);
220688
- if (tokens.length === 0) throw new Error("comando vacio");
220689
- return tokens;
220690
- }
220691
- function containPaths(argv, root) {
220692
- for (const token of argv) {
220693
- if (token.startsWith("-")) continue;
220694
- if (!token.includes("/") && !/\.(mjs|cjs|js|ts|sh)$/.test(token)) continue;
220695
- containedPath(root, token);
220696
- }
221331
+ // ../../packages/agent-core-v2/src/features/wave/tools/verify/verifyRunner.ts
221332
+ function failure$1(error) {
221333
+ return {
221334
+ output: error instanceof Error ? error.message : String(error),
221335
+ isError: true
221336
+ };
220697
221337
  }
220698
- function runCommand$2(command, options) {
220699
- let argv;
221338
+ function runDry(root, config, input) {
221339
+ let selected;
220700
221340
  try {
220701
- argv = splitCommand(command);
220702
- const binary = (argv[0] ?? "").replace(/\.exe$/i, "");
220703
- const allowed = options.allowBinaries.map((item) => item.replace(/\.exe$/i, ""));
220704
- if (!allowed.includes(binary)) throw new Error(`binario no permitido: ${binary} (permitidos: ${allowed.join(", ")})`);
220705
- containPaths(argv, options.root);
221341
+ selected = selectTaskCriteria(nodeWaveFs, root, config, input.task, input.ac);
220706
221342
  } catch (error) {
220707
- return {
220708
- ok: false,
220709
- output: `rechazado: ${error instanceof Error ? error.message : String(error)}`,
220710
- rejected: true
220711
- };
221343
+ return failure$1(error);
220712
221344
  }
220713
- const result = spawnSync(argv[0] ?? "", argv.slice(1), {
220714
- cwd: options.cwd ?? options.root,
220715
- encoding: "utf8",
220716
- timeout: options.timeoutMs ?? DEFAULT_TIMEOUT_MS$1,
220717
- stdio: "pipe",
220718
- shell: false
220719
- });
220720
- const output = `${result.stdout ?? ""}${result.stderr ?? ""}${result.error === void 0 ? "" : result.error.message}`.replaceAll(/\s+/g, " ").trim().slice(0, 300);
220721
- if (result.error !== void 0) return {
220722
- ok: false,
220723
- output: `no se pudo ejecutar: ${output}`,
220724
- rejected: false
220725
- };
220726
- return {
220727
- ok: result.status === 0,
220728
- output,
220729
- rejected: false
220730
- };
220731
- }
220732
- function evaluateMutant(ac, verifyCommand, options) {
220733
- const script = asText$1(asRecord$2(ac["falsifier"])["script"]);
220734
- if (script === "") return {
220735
- state: "declarado",
220736
- detail: "sin falsifier.script: el AC queda declarado y cuenta como FALLO"
220737
- };
220738
- const apply = runCommand$2(`${script} apply`, options);
220739
- if (apply.rejected) return {
220740
- state: "rechazado",
220741
- detail: `apply rechazado: ${apply.output}`
220742
- };
220743
- if (!apply.ok) return {
220744
- state: "error",
220745
- detail: `apply fallo: ${apply.output}`
220746
- };
220747
- const mutated = runCommand$2(verifyCommand, options);
220748
- const restore = runCommand$2(`${script} restore`, options);
220749
- const after = runCommand$2(verifyCommand, options);
220750
- if (restore.rejected || !restore.ok) return {
220751
- state: "error",
220752
- detail: `restore fallo, el arbol puede estar sucio: ${restore.output}`
220753
- };
220754
- if (mutated.ok) return {
220755
- state: "sobrevive",
220756
- detail: "el verify pasa con la mutacion aplicada: el AC no muerde"
220757
- };
220758
- if (!after.ok) return {
220759
- state: "error",
220760
- detail: "el verify no vuelve a verde tras restaurar"
220761
- };
220762
- return {
220763
- state: "muerto",
220764
- detail: "rojo con la mutacion, verde tras restaurar"
220765
- };
220766
- }
220767
- function verifyTask(fs, root, cfg, taskId, options) {
220768
- const tasksDir = containedPath(root, cfg.layout.backlog);
220769
- const file = walk$1(fs, join(tasksDir, "tasks"), (path) => path.endsWith(`${taskId}.json`))[0];
220770
- if (file === void 0) throw new Error(`no encuentro la task ${taskId} en ${cfg.layout.backlog}/tasks/`);
220771
- const task = asRecord$2(readJson$2(fs, file));
220772
- const runOptions = {
220773
- root,
220774
- allowBinaries: cfg.vocabulary.verifyCommands,
220775
- cwd: options?.cwd,
220776
- timeoutMs: options?.timeoutMs
220777
- };
220778
- const acs = [];
220779
- let declared = 0;
220780
- for (const raw of asArray$2(task["ac"])) {
220781
- const ac = asRecord$2(raw);
220782
- const verifyCommand = asText$1(ac["verify"]);
220783
- const verify = runCommand$2(verifyCommand, runOptions);
220784
- const mutant = evaluateMutant(ac, verifyCommand, runOptions);
220785
- if (mutant.state === "declarado") declared++;
220786
- const ok = verify.ok && mutant.state === "muerto";
220787
- acs.push({
220788
- acId: asText$1(ac["id"]),
220789
- verify,
220790
- mutant,
220791
- ok
220792
- });
221345
+ const lines = [`TASK ${selected.taskId} · dryRun: no se ejecuta ningun comando`, ""];
221346
+ for (const ac of selected.criteria) {
221347
+ const falsifier = asRecord$2(ac["falsifier"]);
221348
+ lines.push(`dry ${asText$1(ac["id"])}`);
221349
+ lines.push(` verify: ${asText$1(ac["verify"])}`);
221350
+ lines.push(` mutacion: ${asText$1(falsifier["mutation"])}`);
220793
221351
  }
220794
- return {
220795
- taskId,
220796
- acs,
220797
- declared,
220798
- ok: acs.length > 0 && acs.every((ac) => ac.ok)
220799
- };
221352
+ lines.push("", `criterios ${selected.criteria.length} · comandos 0`);
221353
+ return { output: lines.join("\n") };
220800
221354
  }
220801
- var SHELL_METACHARACTERS, DEFAULT_TIMEOUT_MS$1;
220802
- var init_runner = __esmMin((() => {
220803
- init_config$6();
220804
- init_fsJson();
220805
- SHELL_METACHARACTERS = /* @__PURE__ */ new Set([
220806
- "|",
220807
- "&",
220808
- ";",
220809
- "<",
220810
- ">",
220811
- "`",
220812
- "$",
220813
- "\n",
220814
- "\r"
220815
- ]);
220816
- DEFAULT_TIMEOUT_MS$1 = 6e5;
220817
- }));
220818
- // ../../packages/agent-core-v2/src/features/wave/tools/verify/verifyRunner.ts
220819
221355
  function runWaveVerifyForRoot(input) {
220820
221356
  const root = resolve(input.root);
220821
221357
  const config = readWaveConfig(nodeWaveFs, root);
@@ -220823,7 +221359,16 @@ function runWaveVerifyForRoot(input) {
220823
221359
  output: `no hay workflow.config.json en ${root}`,
220824
221360
  isError: true
220825
221361
  };
220826
- const result = verifyTask(nodeWaveFs, root, config, input.task);
221362
+ if (input.dryRun === true) return runDry(root, config, input);
221363
+ let result;
221364
+ try {
221365
+ result = verifyTask(nodeWaveFs, root, config, input.task, {
221366
+ ac: input.ac,
221367
+ allowDeclared: input.allowDeclared
221368
+ });
221369
+ } catch (error) {
221370
+ return failure$1(error);
221371
+ }
220827
221372
  const lines = [`TASK ${result.taskId}`, ""];
220828
221373
  for (const ac of result.acs) {
220829
221374
  lines.push(`${ac.ok ? "ok " : "FAIL"} ${ac.acId} verify ${ac.verify.ok ? "verde" : "rojo"} mutante ${ac.mutant.state}`);
@@ -220839,13 +221384,14 @@ function runWaveVerifyForRoot(input) {
220839
221384
  }
220840
221385
  var init_verifyRunner = __esmMin((() => {
220841
221386
  init_config$6();
221387
+ init_fsJson();
220842
221388
  init_nodeFs();
220843
221389
  init_runner();
220844
221390
  }));
220845
221391
  // ../../packages/agent-core-v2/src/features/wave/tools/verify/verify.md?raw
220846
221392
  var verify_default;
220847
221393
  var init_verify = __esmMin((() => {
220848
- verify_default = "Close a wave task with evidence: run each acceptance criterion's `verify` command and its falsifier.\n\nFor every criterion the tool runs `verify`, then `apply` of `falsifier.script`, then `verify` again (which must now fail), then `restore`, then `verify` once more (which must pass again). A criterion whose falsifier is only declared, whose mutant survives, or whose tree does not return to green is a failure — never a pass with a note.\n\nThe task id (`E01-T01`) is looked up in `backlog/tasks/`. Commands run without a shell and only with the binaries allowed by `vocabulary.verifyCommands`.\n";
221394
+ verify_default = "Close a wave task with evidence: run each acceptance criterion's `verify` command and its falsifier.\n\nFor every criterion the tool runs `verify`, then `apply` of `falsifier.script`, then `verify` again (which must now fail), then `restore`, then `verify` once more (which must pass again). A criterion whose falsifier is only declared, whose mutant survives, or whose tree does not return to green is a failure — never a pass with a note.\n\nThe task id (`E01-T01`) is looked up in `backlog/tasks/`. Commands run without a shell and only with the binaries allowed by `vocabulary.verifyCommands`.\n\nArguments:\n- `task`: task id to verify.\n- `root`: workspace root. Defaults to the current working directory.\n- `ac`: verify only this acceptance criterion instead of every criterion of the task. An id the task does not declare is rejected.\n- `dryRun`: list the criteria and the command each one would run without executing anything, so a coordinator can review before spending the run.\n";
220849
221395
  }));
220850
221396
  // ../../packages/agent-core-v2/src/features/wave/tools/verify/verifyTool.ts
220851
221397
  var WaveVerifyTool;
@@ -220871,7 +221417,10 @@ var init_verifyTool = __esmMin((() => {
220871
221417
  approvalRule: this.name,
220872
221418
  execute: () => Promise.resolve(runWaveVerifyForRoot({
220873
221419
  root: args.root ?? this.sessionContext.cwd,
220874
- task: args.task
221420
+ task: args.task,
221421
+ ac: args.ac,
221422
+ dryRun: args.dryRun,
221423
+ allowDeclared: args.allowDeclared
220875
221424
  }))
220876
221425
  };
220877
221426
  }
@@ -228271,7 +228820,7 @@ var init_mcpRegistryService = __esmMin((() => {
228271
228820
  }
228272
228821
  } else {
228273
228822
  const cwd = canonicalWorkspaceRoot(query.cwd);
228274
- if (!(await readWorkspaceTrust(this.docs, cwd) || trustWorkspaceEnvTrusted((name) => this.bootstrap.getEnv(name)))) {
228823
+ if (!(await readWorkspaceTrust(this.docs, cwd) || (workspaceTrustOverride((name) => this.bootstrap.getEnv(name)) ?? true))) {
228275
228824
  const userEntries = await this.store.list();
228276
228825
  for (const server of userEntries) {
228277
228826
  const { name, ...config } = server;
@@ -477531,8 +478080,8 @@ function buildWavePrompt(args) {
477531
478080
  "",
477532
478081
  "Reglas de la oleada:",
477533
478082
  "- Usa las tools Wave: `WaveOpen` para abrirla, `WaveRound` para obtener el prompt de cada asiento, `WaveSubmit` para que cada asiento entregue su artefacto, `WaveDecide` para el quórum, `WaveGate` para el paquete y `WaveVerify` para cerrar tasks.",
477534
- "- Lanza un agente por asiento con `Agent` (o `AgentSwarm` si el usuario activó swarm) usando el prompt que devuelve `WaveRound`. No inventes el prompt ni cambies el rol.",
477535
- "- Un asiento nunca escribe con Write/Edit: entrega con `WaveSubmit`, que ata rol y ruta.",
478083
+ "- Lanza un agente por asiento con `Agent` (o `AgentSwarm` si el usuario activó swarm) pasando `subagent_type: \"wave-seat\"` y el prompt que devuelve `WaveRound`. No inventes el prompt ni cambies el rol.",
478084
+ "- El asiento es de solo lectura: `Read`, `Glob`, `Grep` y `WaveSubmit`. Nunca escribe con Write/Edit: entrega con `WaveSubmit`, que ata rol y ruta.",
477536
478085
  "- Si el brief no tiene las siete secciones, para y pide que se rellenen; no lo escribas tú.",
477537
478086
  "- Sin `adopt` de `WaveDecide`, lanza `refine` y después `audit`; con dos rondas sin quórum, adopta R1 con disidencia escrita.",
477538
478087
  "- La oleada no está cerrada hasta que `WaveGate` sale en verde. Si sale en rojo, dilo con los códigos de hallazgo."
@@ -501094,7 +501643,7 @@ var NexgenTUI = class {
501094
501643
  const { startup } = this.options;
501095
501644
  const config = await this.harness.getConfig({ reload: true });
501096
501645
  const patch = includeModel ? lazyModelSelection(config, startup.model ?? config.defaultModel) : {};
501097
- if (!startup.auto && !startup.yolo) patch.permissionMode = config.defaultPermissionMode ?? "manual";
501646
+ if (!startup.auto && !startup.yolo) patch.permissionMode = config.defaultPermissionMode ?? "auto";
501098
501647
  patch.configDefaultPlanMode = config.defaultPlanMode === true;
501099
501648
  if (!startup.plan) patch.planMode = config.defaultPlanMode === true;
501100
501649
  if (startup.agentProfile !== void 0 || startup.agentFiles !== void 0) {