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 +42 -0
- package/bin/delimit-setup.js +25 -11
- package/gateway/ai/loop_engine.py +36 -2
- package/package.json +1 -1
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)
|
package/bin/delimit-setup.js
CHANGED
|
@@ -1362,24 +1362,38 @@ function upsertDelimitSection(filePath) {
|
|
|
1362
1362
|
return { action: 'created' };
|
|
1363
1363
|
}
|
|
1364
1364
|
|
|
1365
|
-
const
|
|
1366
|
-
|
|
1367
|
-
//
|
|
1368
|
-
|
|
1369
|
-
const
|
|
1370
|
-
|
|
1371
|
-
|
|
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(
|
|
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
|
|
1382
|
-
const
|
|
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
|
-
|
|
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.
|
|
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": [
|