@appchy/jarvis 0.1.39 → 0.1.40

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.
@@ -485,8 +485,14 @@ def gate(root, task, accept: str = "", owner: str = "") -> list:
485
485
  f"`jarvis work verify --task {task.name}`")
486
486
  else:
487
487
  head = _head(root.parent)
488
+ # WHAT MOVED, not merely THAT it moved. A board write cannot change what a
489
+ # test proved, and neither can a commit inside a system this task does not
490
+ # own (`M-86`, and `git.moved_only_elsewhere` for the measurement and for
491
+ # why a path nothing claims stays strict).
488
492
  if (head and sha not in ("no-git", "") and not head.startswith(sha)
489
- and not git.only_board_moved(root.parent, sha)):
493
+ and not git.only_board_moved(root.parent, sha)
494
+ and not git.moved_only_elsewhere(
495
+ root.parent, sha, root, list(task.code or []))):
490
496
  reasons.append(f"code moved since verify passed (proven at {sha}, "
491
497
  f"HEAD is {head[:12]}) — re-run `jarvis work verify "
492
498
  f"--task {task.name}`")
@@ -423,6 +423,57 @@ def only_board_moved(repo, sha: str) -> bool:
423
423
  for f in out.splitlines() if f.strip())
424
424
 
425
425
 
426
+ def moved_only_elsewhere(repo, sha: str, root, regions: list) -> bool:
427
+ """Is everything between `sha` and HEAD outside the systems that own `regions`?
428
+
429
+ **The completion gate is a race, and this is what makes it winnable.** It refuses
430
+ unless the verify sha IS HEAD, which is right in spirit — evidence is about the
431
+ tree it was taken on. On a tree several sessions share it is close to unwinnable:
432
+ MEASURED on mixbrix 2026-09-07, the median gap between commits that touch code is
433
+ 284s and a gate run is 287s, so about half of all attempts finish their run only to
434
+ be told the code moved. A session that stops there leaves finished work sitting in
435
+ `in-progress` for good — `the-benches-become-sessions` sat there a fortnight with
436
+ every criterion ticked and its own handoff saying exactly why.
437
+
438
+ **This is not a new licence; it is `M-86` applied one step earlier.** That rule
439
+ already says a suite failure OUTSIDE your `code:` regions warns rather than
440
+ refuses. If a failure over there cannot refuse you, a COMMIT over there cannot
441
+ invalidate you either — holding both at once was the inconsistency.
442
+
443
+ **A path no system claims counts as the code moving, and that is the whole safety
444
+ of it.** `system_for_path` answers `None` both for a file nothing declares and for
445
+ a repo whose systems declare nothing, and the two are indistinguishable from here.
446
+ Reading either as *outside your regions* would turn every repo that never filled in
447
+ `paths:` into one where the gate silently passes anything — a vacuous green, which
448
+ is worse than the race it replaces. So unknown is always strict, and a repo earns
449
+ this only by declaring where its code lives.
450
+ """
451
+ if not enabled() or not sha or not regions:
452
+ return False
453
+ code, out, _ = _git(repo, "diff", "--name-only", f"{sha}..HEAD")
454
+ if code != 0:
455
+ return False
456
+ from .registry import scan_systems, system_for_path
457
+ systems = scan_systems(root)
458
+ if not systems:
459
+ return False
460
+ mine = {r for r in regions if r}
461
+ board = [r.rstrip("/") for r in (GIT.get("paths") or [])]
462
+ moved = False
463
+ for name in out.splitlines():
464
+ name = name.strip()
465
+ if not name:
466
+ continue
467
+ if any(name == r or name.startswith(f"{r}/") for r in board):
468
+ continue
469
+ owner = system_for_path(root, name, systems)
470
+ # Nothing claims it, or one of MY systems does -> the evidence is stale.
471
+ if owner is None or (mine & set(owner.code)):
472
+ return False
473
+ moved = True
474
+ return moved
475
+
476
+
426
477
  def land(repo, item: str, rows: list) -> tuple:
427
478
  """Commit what changed and push it. Returns (committed, pushed, note).
428
479
 
@@ -3140,6 +3140,53 @@ def test_a_board_commit_does_not_count_as_the_code_moving():
3140
3140
  config.apply(config.DEFAULTS)
3141
3141
 
3142
3142
 
3143
+ def test_a_commit_in_another_system_does_not_invalidate_your_verify():
3144
+ # The completion gate refused unless the verify sha WAS head, which on a tree
3145
+ # several sessions share is close to unwinnable — measured on mixbrix, the median
3146
+ # gap between code commits is about as long as a gate run, so half of all attempts
3147
+ # lose. `M-86` already says a suite failure outside your `code:` regions warns
3148
+ # rather than refuses; a COMMIT outside them cannot invalidate you either.
3149
+ with tempfile.TemporaryDirectory() as tmp:
3150
+ try:
3151
+ repo = _git_repo(tmp)
3152
+ arch = repo / "work" / "architecture"
3153
+ arch.mkdir(parents=True, exist_ok=True)
3154
+ (arch / "backoffice.md").write_text(
3155
+ "---\ntype: system\ncode: [backoffice]\npaths: [apps/backoffice]\n"
3156
+ "depends_on: []\n---\n\n# Backoffice\n")
3157
+ (arch / "engine.md").write_text(
3158
+ "---\ntype: system\ncode: [runtime]\npaths: [packages/engine]\n"
3159
+ "depends_on: []\n---\n\n# Engine\n")
3160
+ for f in ("apps/backoffice/page.tsx", "packages/engine/run.cpp",
3161
+ "scripts/loose.py"):
3162
+ (repo / f).parent.mkdir(parents=True, exist_ok=True)
3163
+ (repo / f).write_text("first\n")
3164
+ _git(repo, "add", "-A")
3165
+ _git(repo, "commit", "-qm", "the code under test")
3166
+ proven_at = _git(repo, "rev-parse", "HEAD").stdout.strip()[:12]
3167
+ root = repo / "work"
3168
+
3169
+ # A PEER MOVES ANOTHER SYSTEM. Your evidence still stands.
3170
+ (repo / "packages/engine/run.cpp").write_text("a peer's change\n")
3171
+ _git(repo, "add", "-A")
3172
+ _git(repo, "commit", "-qm", "somebody else's system")
3173
+ assert git.moved_only_elsewhere(repo, proven_at, root, ["backoffice"]), \
3174
+ "a commit in a system you do not own cannot invalidate your run"
3175
+ assert not git.moved_only_elsewhere(repo, proven_at, root, ["runtime"]), \
3176
+ "and the very same commit DOES invalidate the task that owns it"
3177
+
3178
+ # A PATH NOTHING CLAIMS IS ALWAYS STRICT. This is the safety property: a
3179
+ # repo that never filled in `paths:` must not get a gate that passes
3180
+ # everything, which is a worse failure than the race it replaces.
3181
+ (repo / "scripts/loose.py").write_text("claimed by no system\n")
3182
+ _git(repo, "add", "-A")
3183
+ _git(repo, "commit", "-qm", "a file under no system at all")
3184
+ assert not git.moved_only_elsewhere(repo, proven_at, root, ["backoffice"]), \
3185
+ "a path no system declares must count as the code moving"
3186
+ finally:
3187
+ config.apply(config.DEFAULTS)
3188
+
3189
+
3143
3190
  def test_a_repo_that_asked_for_git_and_HAS_none_is_told_so():
3144
3191
  # The silent case, and the worst one: `git.commit` is set, there is no repository
3145
3192
  # under the tree, and the write reaches the disk with nothing said. That is the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appchy/jarvis",
3
- "version": "0.1.39",
3
+ "version": "0.1.40",
4
4
  "description": "Jarvis \u2014 local AI coding assistant CLI",
5
5
  "private": false,
6
6
  "type": "module",