@antoneeo/kb-agentic-skill 1.16.0 → 1.18.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.
@@ -569,6 +569,36 @@ def git_changed_since(root, ref, rel_path):
569
569
  return None
570
570
 
571
571
 
572
+ def git_ref_state(root, ref):
573
+ """Where a recorded commit stands against HEAD (F-056).
574
+
575
+ 'ancestor' -- in HEAD's history; 'orphan' -- a commit HEAD's history does not
576
+ contain (amended, rebased or squash-merged away, or marked on another branch:
577
+ it stops resolving once pruned, and on any fresh clone); 'missing' -- not a
578
+ commit in this repository; 'unknown' -- git gave no yes/no answer.
579
+ Callers pass a hash-shaped ref only: that check is what keeps document
580
+ content from reaching git's argv as an option."""
581
+ try:
582
+ r = subprocess.run(["git", "cat-file", "-e", ref + "^{commit}"],
583
+ cwd=str(root), capture_output=True, text=True, timeout=30)
584
+ if r.returncode != 0:
585
+ return "missing"
586
+ r = subprocess.run(["git", "merge-base", "--is-ancestor", ref, "HEAD"],
587
+ cwd=str(root), capture_output=True, text=True, timeout=30)
588
+ except Exception:
589
+ return "unknown"
590
+ return {0: "ancestor", 1: "orphan"}.get(r.returncode, "unknown")
591
+
592
+
593
+ def git_is_shallow(root):
594
+ try:
595
+ r = subprocess.run(["git", "rev-parse", "--is-shallow-repository"],
596
+ cwd=str(root), capture_output=True, text=True, timeout=10)
597
+ return r.returncode == 0 and r.stdout.strip() == "true"
598
+ except Exception:
599
+ return False
600
+
601
+
572
602
  # -------------------------------------------------------------------- index
573
603
 
574
604
  def build_index(root):
@@ -1543,6 +1573,11 @@ def cmd_stale(root, hybrid=False):
1543
1573
  "(audit not initialized, or Hybrid mode where mapping is delegated to devPNT).")
1544
1574
  return rc # was: return 0 — MUST carry guide rc
1545
1575
  use_git = git_available(root)
1576
+ entry = entry_script()
1577
+ # F-056: an ANALYZED row whose reference cannot be evaluated is UNVERIFIED,
1578
+ # never fresh. Each `continue` below that skips such a row records it here;
1579
+ # skipping silently is how a pruned reference printed `[ok]` over three areas.
1580
+ unverifiable = []
1546
1581
  stale = []
1547
1582
  for row in rows:
1548
1583
  if row["status"] != "ANALYZED":
@@ -1561,16 +1596,42 @@ def cmd_stale(root, hybrid=False):
1561
1596
  print(f"[warn] {rel}: path does not exist")
1562
1597
  continue
1563
1598
  changed = []
1564
- if use_git and re.fullmatch(r"[0-9a-fA-F]{7,40}", ref or ""):
1599
+ remark = f"re-analyze the area, then run: {entry} mark {rel}"
1600
+ if re.fullmatch(r"[0-9a-fA-F]{7,40}", ref or ""):
1601
+ if not use_git:
1602
+ print(f"[stale] {rel}: reference '{ref}' is a git commit, but git is not usable "
1603
+ "here (not a git work tree, git missing, or the repository refused): its "
1604
+ "freshness cannot be evaluated. Run stale inside the git work tree, or "
1605
+ + remark)
1606
+ unverifiable.append(rel)
1607
+ continue
1608
+ state = git_ref_state(root, ref)
1609
+ if state == "missing":
1610
+ shallow = (" This clone is shallow: full history may hold it "
1611
+ "(git fetch --unshallow)." if git_is_shallow(root) else "")
1612
+ print(f"[stale] {rel}: git ref '{ref}' is not a commit in this repository "
1613
+ "(amended, rebased or squash-merged away and pruned, or never fetched "
1614
+ f"here): its freshness cannot be evaluated.{shallow} To fix, " + remark)
1615
+ unverifiable.append(rel)
1616
+ continue
1617
+ if state == "orphan":
1618
+ print(f"[warn] {rel}: git ref '{ref}' is not an ancestor of HEAD (amended, "
1619
+ "rebased, squash-merged, or marked on another branch): it stops "
1620
+ "resolving once git prunes it, and on any fresh clone. Re-mark now: "
1621
+ f"{entry} mark {rel}")
1565
1622
  res = git_changed_since(root, ref, rel.replace("\\", "/"))
1566
1623
  if res is None:
1567
- print(f"[warn] {rel}: git ref '{ref}' unresolvable, cannot evaluate")
1624
+ print(f"[stale] {rel}: git could not compare against ref '{ref}': its "
1625
+ "freshness cannot be evaluated. To fix, " + remark)
1626
+ unverifiable.append(rel)
1568
1627
  continue
1569
1628
  changed = res
1570
1629
  else:
1571
1630
  ts = parse_iso(ref)
1572
1631
  if ts is None:
1573
- print(f"[warn] {rel}: reference '{ref}' not parseable (neither git hash nor ISO UTC)")
1632
+ print(f"[stale] {rel}: reference '{ref}' is neither a git commit nor an ISO "
1633
+ "UTC timestamp: its freshness cannot be evaluated. To fix, " + remark)
1634
+ unverifiable.append(rel)
1574
1635
  continue
1575
1636
  for fp in iter_files(target):
1576
1637
  mtime = datetime.fromtimestamp(fp.stat().st_mtime, tz=timezone.utc)
@@ -1583,8 +1644,11 @@ def cmd_stale(root, hybrid=False):
1583
1644
  if changed:
1584
1645
  stale.append((rel, changed))
1585
1646
 
1647
+ if unverifiable:
1648
+ rc = 1 # unverified is not fresh (F-056)
1586
1649
  if not stale:
1587
- print("[ok] no analyzed area was modified after its last recorded analysis.")
1650
+ if not unverifiable:
1651
+ print("[ok] no analyzed area was modified after its last recorded analysis.")
1588
1652
  return rc # was: return 0 — MUST carry guide rc
1589
1653
  print("Areas modified after the last recorded analysis:")
1590
1654
  for rel, changed in stale:
@@ -1593,7 +1657,7 @@ def cmd_stale(root, hybrid=False):
1593
1657
  print(f" - {c}")
1594
1658
  if len(changed) > 10:
1595
1659
  print(f" ... and {len(changed) - 10} more")
1596
- print("\nAfter re-analyzing, record it with: sdlc_check.py mark <path>")
1660
+ print(f"\nAfter re-analyzing, record it with: {entry} mark <path>")
1597
1661
  return 1 # stale areas dominate: rc already implied
1598
1662
 
1599
1663
 
@@ -1645,6 +1709,13 @@ def cmd_mark(root, paths):
1645
1709
 
1646
1710
  f.parent.mkdir(parents=True, exist_ok=True)
1647
1711
  f.write_text("\n".join(lines) + "\n", encoding="utf-8")
1712
+ if use_git_ref:
1713
+ # F-056: the reference is born here, so this is where the gestures that
1714
+ # orphan it are named. An amend was the observed one.
1715
+ print(f"[info] {ref} is the current HEAD. Commit this audit plan change as a new "
1716
+ "commit: amending, rebasing or squash-merging that commit orphans the "
1717
+ f"reference, and `{entry_script()} stale` fails once it no longer resolves. "
1718
+ "After a squash or rebase merge, re-mark on the integration branch.")
1648
1719
  return 0
1649
1720
 
1650
1721
 
@@ -376,7 +376,7 @@ remains is derived — the next window is the next pending task.
376
376
 
377
377
  ## ai_docs/audit/audit_plan.md (Standalone mode only)
378
378
 
379
- The `Reference` field (git hash or ISO UTC timestamp) is managed by `sdlc_check.py mark` — do not fill it by hand. Freshness is verified with `sdlc_check.py stale`.
379
+ The `Reference` field (git hash or ISO UTC timestamp) is managed by `sdlc_check.py mark` — do not fill it by hand. Freshness is verified with `sdlc_check.py stale`. Commit the `mark` result as a new commit: amending, rebasing or squash-merging the commit it recorded orphans the reference, and `stale` fails once it no longer resolves — after a squash or rebase merge, re-mark on the integration branch.
380
380
 
381
381
  ```markdown
382
382
  # Audit Plan