delimit-cli 4.6.1 → 4.6.2
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 +60 -0
- package/bin/delimit-cli.js +42 -6
- package/bin/delimit-setup.js +7 -3
- package/gateway/ai/backends/gateway_core.py +6 -0
- package/gateway/ai/backends/memory_bridge.py +210 -53
- package/gateway/ai/backends/tools_infra.py +80 -0
- package/gateway/ai/backends/tools_real.py +53 -7
- package/gateway/ai/session_phoenix.py +121 -0
- package/gateway/core/diff_engine_v2.py +517 -54
- package/gateway/core/semver_classifier.py +52 -6
- package/package.json +1 -1
|
@@ -234,6 +234,101 @@ def get_latest_soul(project_path: str = "") -> Optional[SessionSoul]:
|
|
|
234
234
|
return None
|
|
235
235
|
|
|
236
236
|
|
|
237
|
+
def _soul_sort_key(soul: SessionSoul, fallback_path: Path) -> str:
|
|
238
|
+
"""Sort key for global recency ranking. Prefer the soul's own
|
|
239
|
+
created_at (ISO-8601, lexically sortable); fall back to the file's
|
|
240
|
+
mtime when created_at is missing so a malformed/legacy soul still
|
|
241
|
+
orders sensibly rather than sinking to the bottom unconditionally."""
|
|
242
|
+
if soul.created_at:
|
|
243
|
+
return soul.created_at
|
|
244
|
+
try:
|
|
245
|
+
# Fall back to the file mtime, rendered as an ISO-8601 string so it
|
|
246
|
+
# compares lexically against real created_at values on the same
|
|
247
|
+
# scale. Only reached when created_at is empty.
|
|
248
|
+
return datetime.fromtimestamp(
|
|
249
|
+
fallback_path.stat().st_mtime, timezone.utc
|
|
250
|
+
).isoformat()
|
|
251
|
+
except (OSError, ValueError):
|
|
252
|
+
return ""
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def find_most_recent_soul_across_projects(
|
|
256
|
+
exclude_project_path: str = "",
|
|
257
|
+
) -> Optional[Dict[str, Any]]:
|
|
258
|
+
"""Scan every project-hash soul directory under SOULS_BASE_DIR and
|
|
259
|
+
return the globally-most-recent soul, with its originating project.
|
|
260
|
+
|
|
261
|
+
LED-218 FIX D: cross-venture fallback for `revive()` when the current
|
|
262
|
+
working directory resolves to a project that has no souls (e.g. running
|
|
263
|
+
from /root). Read-only; never writes. Returns None when no souls exist
|
|
264
|
+
anywhere.
|
|
265
|
+
|
|
266
|
+
Args:
|
|
267
|
+
exclude_project_path: if set, the soul directory for this project
|
|
268
|
+
is skipped (it already had no usable soul, so re-scanning it is
|
|
269
|
+
wasted work and could otherwise re-surface a stale latest.json).
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
{"soul": SessionSoul, "project_hash": str, "project_path": str}
|
|
273
|
+
for the most recent soul found, or None.
|
|
274
|
+
"""
|
|
275
|
+
if not SOULS_BASE_DIR.exists():
|
|
276
|
+
return None
|
|
277
|
+
|
|
278
|
+
exclude_hash = _project_hash(exclude_project_path) if exclude_project_path else None
|
|
279
|
+
|
|
280
|
+
best: Optional[SessionSoul] = None
|
|
281
|
+
best_key: str = ""
|
|
282
|
+
best_hash: str = ""
|
|
283
|
+
|
|
284
|
+
for proj_dir in SOULS_BASE_DIR.iterdir():
|
|
285
|
+
if not proj_dir.is_dir():
|
|
286
|
+
continue
|
|
287
|
+
if exclude_hash and proj_dir.name == exclude_hash:
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
# Prefer the per-project latest.json; fall back to scanning the
|
|
291
|
+
# timestamped soul files if latest.json is absent/corrupt.
|
|
292
|
+
candidate: Optional[SessionSoul] = None
|
|
293
|
+
candidate_path: Optional[Path] = None
|
|
294
|
+
|
|
295
|
+
latest = proj_dir / "latest.json"
|
|
296
|
+
if latest.exists():
|
|
297
|
+
candidate = _load_soul(latest)
|
|
298
|
+
candidate_path = latest
|
|
299
|
+
|
|
300
|
+
if candidate is None:
|
|
301
|
+
soul_files = sorted(
|
|
302
|
+
[f for f in proj_dir.iterdir()
|
|
303
|
+
if f.name != "latest.json" and f.suffix == ".json"],
|
|
304
|
+
key=lambda f: f.name,
|
|
305
|
+
reverse=True,
|
|
306
|
+
)
|
|
307
|
+
for f in soul_files:
|
|
308
|
+
candidate = _load_soul(f)
|
|
309
|
+
if candidate is not None:
|
|
310
|
+
candidate_path = f
|
|
311
|
+
break
|
|
312
|
+
|
|
313
|
+
if candidate is None or candidate_path is None:
|
|
314
|
+
continue
|
|
315
|
+
|
|
316
|
+
key = _soul_sort_key(candidate, candidate_path)
|
|
317
|
+
if best is None or key > best_key:
|
|
318
|
+
best = candidate
|
|
319
|
+
best_key = key
|
|
320
|
+
best_hash = proj_dir.name
|
|
321
|
+
|
|
322
|
+
if best is None:
|
|
323
|
+
return None
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
"soul": best,
|
|
327
|
+
"project_hash": best_hash,
|
|
328
|
+
"project_path": best.project_path,
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
|
|
237
332
|
def _format_revival(soul: SessionSoul) -> str:
|
|
238
333
|
"""Format a soul into a readable context string for any AI model."""
|
|
239
334
|
lines = []
|
|
@@ -339,6 +434,32 @@ def revive(project_path: str = "", soul_id: str = "") -> Dict[str, Any]:
|
|
|
339
434
|
# Get latest
|
|
340
435
|
soul = get_latest_soul(project_path)
|
|
341
436
|
if not soul:
|
|
437
|
+
# FIX D — cross-venture fallback. The current working directory
|
|
438
|
+
# resolved to a project with no soul (common when reviving from a
|
|
439
|
+
# neutral dir like /root). Rather than dead-ending at "no_souls",
|
|
440
|
+
# surface the globally-most-recent soul from any other venture /
|
|
441
|
+
# project so the operator still gets continuity. Clearly labeled
|
|
442
|
+
# via `recovered_from_venture` so the caller knows it came from a
|
|
443
|
+
# different project. This ADDITIVE path only fires when the
|
|
444
|
+
# resolved project itself is empty AND no explicit soul_id was
|
|
445
|
+
# given, so existing single-project users see no change.
|
|
446
|
+
fallback = find_most_recent_soul_across_projects(
|
|
447
|
+
exclude_project_path=project_path
|
|
448
|
+
)
|
|
449
|
+
if fallback:
|
|
450
|
+
recovered = fallback["soul"]
|
|
451
|
+
return {
|
|
452
|
+
"status": "revived",
|
|
453
|
+
"soul": asdict(recovered),
|
|
454
|
+
"context": _format_revival(recovered),
|
|
455
|
+
"recovered_from_venture": recovered.project_path
|
|
456
|
+
or fallback.get("project_hash", ""),
|
|
457
|
+
"recovered_project_hash": fallback.get("project_hash", ""),
|
|
458
|
+
"note": (
|
|
459
|
+
f"No soul for {project_path}; recovered the most recent "
|
|
460
|
+
f"soul from {recovered.project_path or fallback.get('project_hash', '')}."
|
|
461
|
+
),
|
|
462
|
+
}
|
|
342
463
|
return {
|
|
343
464
|
"status": "no_souls",
|
|
344
465
|
"message": f"No session souls found for {project_path}. Nothing to revive.",
|