@wopr-network/defcon 1.14.0 → 1.15.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.
@@ -8,6 +8,8 @@ import { buildInvocation } from "./invocation-builder.js";
8
8
  import { executeOnEnter } from "./on-enter.js";
9
9
  import { executeOnExit } from "./on-exit.js";
10
10
  import { findTransition, isTerminal } from "./state-machine.js";
11
+ const MERGE_BLOCKED_THRESHOLD = 3;
12
+ const MERGE_BLOCKED_STUCK_MESSAGE = "PR blocked in merge queue 3+ times — likely branch protection or queue contention issue";
11
13
  export class Engine {
12
14
  entityRepo;
13
15
  flowRepo;
@@ -93,10 +95,31 @@ export class Engine {
93
95
  return { gated: true, ...routing, terminal: false };
94
96
  }
95
97
  // Gate passed or redirected — determine the actual destination.
96
- const toState = routing.kind === "redirect" ? routing.toState : transition.toState;
98
+ let toState = routing.kind === "redirect" ? routing.toState : transition.toState;
97
99
  const trigger = routing.kind === "redirect" ? routing.trigger : signal;
98
100
  const spawnFlow = routing.kind === "redirect" ? null : transition.spawnFlow;
99
101
  const { gatesPassed } = routing;
102
+ // 4a. Merge-blocked loop detection: if the watcher sends "blocked" from
103
+ // "merging", increment a counter. After MERGE_BLOCKED_THRESHOLD cycles,
104
+ // override the destination to "stuck" to break the loop.
105
+ if (signal === "blocked" && entity.state === "merging") {
106
+ const prevCount = (typeof entity.artifacts?.merge_blocked_count === "number" ? entity.artifacts.merge_blocked_count : 0);
107
+ const newCount = prevCount + 1;
108
+ await this.entityRepo.updateArtifacts(entityId, { merge_blocked_count: newCount });
109
+ if (newCount >= MERGE_BLOCKED_THRESHOLD) {
110
+ const stuckState = flow.states.find((s) => s.name === "stuck");
111
+ if (stuckState) {
112
+ toState = "stuck";
113
+ await this.entityRepo.updateArtifacts(entityId, {
114
+ merge_blocked_message: MERGE_BLOCKED_STUCK_MESSAGE,
115
+ });
116
+ this.logger.warn(`[engine] Entity ${entityId} merge-blocked ${newCount} times, transitioning to stuck`);
117
+ }
118
+ else {
119
+ this.logger.warn(`merge_blocked_count >= threshold but no stuck state in flow ${flow.name} — entity ${entityId} will continue looping`);
120
+ }
121
+ }
122
+ }
100
123
  // 4b. Execute onExit hook on the DEPARTING state (before transition)
101
124
  const departingStateDef = flow.states.find((s) => s.name === entity.state);
102
125
  if (departingStateDef?.onExit) {
@@ -46,7 +46,7 @@ export async function buildInvocation(state, entity, adapters, flow, logger = co
46
46
  let systemPrompt = "";
47
47
  let userContent = "";
48
48
  if (state.promptTemplate) {
49
- const template = getHandlebars().compile(state.promptTemplate);
49
+ const template = getHandlebars().compile(state.promptTemplate, { noEscape: true });
50
50
  prompt = template(context);
51
51
  systemPrompt = `${INJECTION_WARNING}\n\n${prompt}`;
52
52
  userContent = `<external-data>\n${JSON.stringify(entityDataForContext(entity), null, 2)}\n</external-data>`;
@@ -474,9 +474,11 @@ program
474
474
  sqlite.close();
475
475
  });
476
476
  // ─── provision-worktree ───
477
+ // TODO(WOP-2014): Remove this subcommand once nuke containers are deployed and stable.
478
+ // Nuke workers provision their own workspace inside the container, making this unnecessary.
477
479
  program
478
480
  .command("provision-worktree")
479
- .description("Provision a git worktree and branch for an issue")
481
+ .description("[DEPRECATED] Provision a git worktree and branch for an issue (superseded by nuke containers)")
480
482
  .argument("<repo>", "GitHub repo (e.g. wopr-network/defcon)")
481
483
  .argument("<issue-key>", "Issue key (e.g. WOP-392)")
482
484
  .option("--base-path <path>", "Worktree base directory", join(homedir(), "worktrees"))
@@ -1,3 +1,8 @@
1
+ /**
2
+ * @deprecated provision-worktree is superseded by nuke containerized workers (WOP-2014).
3
+ * Nuke containers provision their own workspace inside the container.
4
+ * TODO: remove this file once nuke is deployed and stable.
5
+ */
1
6
  import { execFileSync } from "node:child_process";
2
7
  import { existsSync } from "node:fs";
3
8
  import { homedir } from "node:os";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wopr-network/defcon",
3
- "version": "1.14.0",
3
+ "version": "1.15.0",
4
4
  "type": "module",
5
5
  "packageManager": "pnpm@9.15.4",
6
6
  "engines": {