delimit-cli 4.1.49 → 4.1.51

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/CHANGELOG.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.1.51] - 2026-04-09
4
+
5
+ ### Fixed (gateway loop engine — LED-814)
6
+ - **`ai/loop_engine.run_governed_iteration` mishandled swarm dispatch statuses.** Only `status=='completed'` was treated as success. The swarm dispatcher returns `'dispatched'` for async handoff, so every build-loop tick fell into the failure branch and logged "Dispatch failed" even though the underlying work shipped. Session `build-loop-2026-04-09` accumulated 6 spurious failures (LED-787 / 788 / 755 / 762 / 799 / 807) for tasks that all actually shipped. Now:
7
+ - `'completed'` → close ledger + notify deploy loop (unchanged)
8
+ - `'dispatched'` → mark ledger `in_progress` with the swarm `task_id`, NOT a failure
9
+ - `'blocked'` → record a founder-approval gate without tripping the circuit breaker
10
+ - anything else → genuine failure, error message includes the unexpected status string for debuggability
11
+ - Verified live against the running MCP session before this release: `iterations 6→7`, `errors 0`, `LED-814` recorded as `dispatched` with `swarm_task_id task-449ecdf9`.
12
+ - Picked up via the standard `npm run sync-gateway` step in `prepublishOnly` (gateway commit `ce802cd` is now on `delimit-ai/delimit-gateway` main).
13
+
14
+ ### Added
15
+ - **`tests/test_loop_engine_dispatch_status.py`** in the gateway — covers all four dispatch status branches (`completed` / `dispatched` / `blocked` / unknown), 154 lines, ships with the bundled gateway.
16
+
17
+ ### Scope
18
+ - Single-purpose patch: gateway loop engine only. This is the deferred half of the multi-model deliberation that produced 4.1.50 — the deliberation explicitly required splitting the gateway fix from the CLAUDE.md regex fix so each ship has a clean rollback story.
19
+
20
+ ### Tests
21
+ - npm CLI: 134/134 still passing (no CLI changes — bundled gateway only).
22
+ - Gateway: new `test_loop_engine_dispatch_status.py` suite passing.
23
+
24
+ ## [4.1.50] - 2026-04-09
25
+
26
+ ### Fixed (CRITICAL — CLAUDE.md in-prose marker clobber)
27
+ - **`upsertDelimitSection` regex was unanchored** — `bin/delimit-setup.js` used `/<!-- delimit:start[^>]*-->/` (no line anchors) to detect the managed-section markers. If a user *quoted* the markers inside backticks in a documentation bullet (e.g. `Use managed-section markers (\`<!-- delimit:start -->\` / \`<!-- delimit:end -->\`)`), the regex matched the prose mention. On the next `delimit setup` run the upsert sliced everything between the prose start and prose end markers and replaced it with a fresh stock template — the exact "never clobber user-customized files" failure mode 4.1.48 / 4.1.49 were written to prevent. Reproduced on `/root/CLAUDE.md` 2026-04-09.
28
+ - Both markers are now anchored to start-of-line with the multiline flag, allow optional leading horizontal whitespace (`/^[ \t]*<!-- delimit:start[^>]*-->[ \t]*$/m`), and the file is BOM-stripped before matching. Result: documentation prose that quotes the markers inside backticks, blockquotes (`> `), or bullets (`- `, `* `) is never matched, while genuine markers — flush-left, indented, or BOM-prefixed — still are. Same fix applied to the test mirror in `tests/setup-onboarding.test.js`.
29
+
30
+ ### Added
31
+ - **Regression tests** in `tests/setup-onboarding.test.js` covering five failure modes the v4.1.49 regex would have matched incorrectly:
32
+ - Markers quoted in a bullet via inline backticks (the exact /root/CLAUDE.md 2026-04-09 incident)
33
+ - Markers with a CRLF (`\r\n`) line ending
34
+ - File starting with a UTF-8 BOM
35
+ - Tab- and space-indented real markers (must still be recognized)
36
+ - Bullet- and blockquote-prefixed markers (must NOT be recognized)
37
+ Each test asserts both the first-run behavior (`appended` vs `updated`) and that user content survives a subsequent version-bump upgrade verbatim.
38
+
39
+ ### Scope
40
+ - Single-purpose patch: CLAUDE.md preservation only. Unrelated gateway fixes (e.g. `loop_engine` LED-814) deferred to 4.1.51 per multi-model deliberation, since 4.1.48 and 4.1.49 both shipped with the regression bug undetected and 4.1.50 must stay laser-focused.
41
+
42
+ ### Tests
43
+ - 134/134 npm CLI tests passing (was 129). New regression suite (`does not match markers quoted in prose`, CRLF, BOM, indented, bullet/blockquote-prefixed) covers every edge case the multi-model deliberation surfaced.
44
+
3
45
  ## [4.1.49] - 2026-04-09
4
46
 
5
47
  ### Fixed (full preservation audit follow-up to 4.1.48)
@@ -1362,24 +1362,38 @@ function upsertDelimitSection(filePath) {
1362
1362
  return { action: 'created' };
1363
1363
  }
1364
1364
 
1365
- const existing = fs.readFileSync(filePath, 'utf-8');
1366
-
1367
- // Check if managed markers already exist
1368
- const startMarkerRe = /<!-- delimit:start[^>]*-->/;
1369
- const endMarker = '<!-- delimit:end -->';
1370
- const hasStart = startMarkerRe.test(existing);
1371
- const hasEnd = existing.includes(endMarker);
1365
+ const rawExisting = fs.readFileSync(filePath, 'utf-8');
1366
+ // Strip a UTF-8 BOM if present so the start-of-line anchor still matches
1367
+ // the very first line of the file. We write back the stripped form to keep
1368
+ // serialization deterministic.
1369
+ const existing = rawExisting.replace(/^\uFEFF/, '');
1370
+
1371
+ // Check if managed markers already exist.
1372
+ // Markers MUST be on their own line — anchored with the multiline flag — so
1373
+ // that documentation prose that quotes the markers (e.g. inside backticks,
1374
+ // bullets, or blockquotes) does NOT get mistaken for a real managed section.
1375
+ // The v4.1.49 unanchored regex caused exactly this clobber on /root/CLAUDE.md.
1376
+ // We allow optional leading horizontal whitespace ([ \t]*) so genuinely
1377
+ // indented markers still match, but NOT a leading "- ", "> ", "`", "*", etc.
1378
+ const startMarkerRe = /^[ \t]*<!-- delimit:start[^>]*-->[ \t]*$/m;
1379
+ const endMarkerRe = /^[ \t]*<!-- delimit:end -->[ \t]*$/m;
1380
+ const startMatch = existing.match(startMarkerRe);
1381
+ const endMatch = existing.match(endMarkerRe);
1382
+ const hasStart = !!startMatch;
1383
+ const hasEnd = !!endMatch;
1372
1384
 
1373
1385
  if (hasStart && hasEnd) {
1374
- // Extract current version from the marker
1375
- const versionMatch = existing.match(/<!-- delimit:start v([^ ]+) -->/);
1386
+ // Extract current version from the marker (also anchored, allows indent)
1387
+ const versionMatch = existing.match(/^[ \t]*<!-- delimit:start v([^ ]+) -->[ \t]*$/m);
1376
1388
  const currentVersion = versionMatch ? versionMatch[1] : '';
1377
1389
  if (currentVersion === version) {
1378
1390
  return { action: 'unchanged' };
1379
1391
  }
1380
1392
  // Replace only the managed region — preserve content above/below
1381
- const before = existing.substring(0, existing.search(startMarkerRe));
1382
- const after = existing.substring(existing.indexOf(endMarker) + endMarker.length);
1393
+ const startIdx = startMatch.index;
1394
+ const endIdx = endMatch.index + endMatch[0].length;
1395
+ const before = existing.substring(0, startIdx);
1396
+ const after = existing.substring(endIdx);
1383
1397
  fs.writeFileSync(filePath, before + newSection + after);
1384
1398
  return { action: 'updated' };
1385
1399
  }
@@ -941,7 +941,12 @@ def run_governed_iteration(session_id: str, hardening: Optional[Any] = None) ->
941
941
  session["cost_incurred"] += cost
942
942
 
943
943
  from ai.ledger_manager import update_item
944
- if dispatch_result.get("status") == "completed":
944
+ dispatch_status = dispatch_result.get("status")
945
+ # "completed" = synchronous success (loop engine closes the ledger).
946
+ # "dispatched" = swarm handed the task to an agent; the ledger stays
947
+ # in_progress until the agent reports back via delimit_agent_complete.
948
+ # Both are success outcomes from the loop's perspective.
949
+ if dispatch_status == "completed":
945
950
  update_item(
946
951
  item_id=task["id"],
947
952
  status="done",
@@ -964,6 +969,35 @@ def run_governed_iteration(session_id: str, hardening: Optional[Any] = None) ->
964
969
  )
965
970
  except Exception as e:
966
971
  logger.warning("Failed to notify deploy loop for %s: %s", task.get("id"), e)
972
+ elif dispatch_status == "dispatched":
973
+ # Async handoff: mark ledger in_progress, leave closure to the agent.
974
+ dispatched_task_id = dispatch_result.get("task_id", "")
975
+ try:
976
+ update_item(
977
+ item_id=task["id"],
978
+ status="in_progress",
979
+ note=(
980
+ f"Dispatched to swarm agent via governed build loop "
981
+ f"(swarm task_id={dispatched_task_id}). Awaiting agent completion."
982
+ ),
983
+ project_path=str(ROOT_LEDGER_PATH),
984
+ )
985
+ except Exception as e:
986
+ logger.warning("Failed to mark %s in_progress after dispatch: %s", task.get("id"), e)
987
+ session["tasks_completed"].append({
988
+ "id": task["id"],
989
+ "status": "dispatched",
990
+ "swarm_task_id": dispatched_task_id,
991
+ "duration": duration,
992
+ "cost": cost,
993
+ })
994
+ elif dispatch_status == "blocked":
995
+ # Founder-approval gate — not a failure, don't trip the breaker.
996
+ session["tasks_completed"].append({
997
+ "id": task["id"],
998
+ "status": "blocked",
999
+ "reason": dispatch_result.get("reason", "Requires founder approval"),
1000
+ })
967
1001
  else:
968
1002
  session["errors"] += 1
969
1003
  if session["errors"] >= session["error_threshold"]:
@@ -971,7 +1005,7 @@ def run_governed_iteration(session_id: str, hardening: Optional[Any] = None) ->
971
1005
  session["tasks_completed"].append({
972
1006
  "id": task["id"],
973
1007
  "status": "failed",
974
- "error": dispatch_result.get("error", "Dispatch failed")
1008
+ "error": dispatch_result.get("error", f"Dispatch failed (status={dispatch_status!r})"),
975
1009
  })
976
1010
 
977
1011
  _save_session(session)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "delimit-cli",
3
3
  "mcpName": "io.github.delimit-ai/delimit-mcp-server",
4
- "version": "4.1.49",
4
+ "version": "4.1.51",
5
5
  "description": "Unify Claude Code, Codex, Cursor, and Gemini CLI with persistent context, governance, and multi-model debate.",
6
6
  "main": "index.js",
7
7
  "files": [