guardian-framework 0.1.161 → 0.1.163

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/dist/cli.js CHANGED
@@ -3206,7 +3206,7 @@ __export(exports_package, {
3206
3206
  bin: () => bin,
3207
3207
  author: () => author
3208
3208
  });
3209
- var name = "guardian-framework", version = "0.1.161", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
3209
+ var name = "guardian-framework", version = "0.1.163", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
3210
3210
  var init_package = __esm(() => {
3211
3211
  exports = {
3212
3212
  ".": {
package/dist/exports.js CHANGED
@@ -1991,7 +1991,7 @@ __export(exports_package, {
1991
1991
  bin: () => bin,
1992
1992
  author: () => author
1993
1993
  });
1994
- var name = "guardian-framework", version = "0.1.161", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
1994
+ var name = "guardian-framework", version = "0.1.163", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
1995
1995
  var init_package = __esm(() => {
1996
1996
  exports = {
1997
1997
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardian-framework",
3
- "version": "0.1.161",
3
+ "version": "0.1.163",
4
4
  "description": "Token-optimized agentic framework scaffolder with pi-first architecture",
5
5
  "type": "module",
6
6
  "main": "dist/exports.js",
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-11-13T20:08:59Z",
2
+ "timestamp": "2026-11-13T20:44:26Z",
3
3
  "mode": "all",
4
4
  "stages_run": [],
5
5
  "summary": {
@@ -8,7 +8,7 @@
8
8
  "failed": 1,
9
9
  "skipped": 12
10
10
  },
11
- "duration_seconds": 1,
11
+ "duration_seconds": 0,
12
12
  "results": [
13
13
  {
14
14
  "name": "check_mr_traceability.sh",
@@ -206,6 +206,20 @@ SHAPE_ASSERT = re.compile(
206
206
  r"\bnames\b|annotations\[|startswith\(\s*[\"']set_|__dict__|dir\()"
207
207
  )
208
208
 
209
+ # Guardian's own generated scaffolding: `assert False, "TODO: ..."`. Red by
210
+ # construction, proves nothing — accepting it is how a "freeze" ends up with no
211
+ # net at all.
212
+ PLACEHOLDER = re.compile(
213
+ r"(assert\s+False\b|fail\(\s*[\"']TODO|t\.Fatal\(\s*[\"']Test not yet implemented|"
214
+ r"throw new Error\(\s*[\"']TODO|panic!\(\s*[\"']Test not yet implemented|"
215
+ r"TODO: uncomment when|TODO: implement dependency test)",
216
+ re.I,
217
+ )
218
+
219
+ def is_placeholder(src):
220
+ return bool(PLACEHOLDER.search(src))
221
+
222
+
209
223
  def classify(code):
210
224
  """Return 'behaviour' | 'structural' | 'none'."""
211
225
  asserts = [l for l in code.splitlines() if re.match(r"\s*assert\b", l)]
@@ -231,6 +245,13 @@ def find_tests(slug):
231
245
  if not re.search(r"\.(py|ts|rs|go|java)$", f):
232
246
  continue
233
247
  low = os.path.join(dp, f).lower()
248
+ if "__pycache__" in low:
249
+ continue
250
+ # Only actual test files: conftest.py, _fixtures.py and
251
+ # __init__.py are support, not suites (they made counts lie).
252
+ stem = f.rsplit(".", 1)[0]
253
+ if not (stem.startswith("test_") or stem.endswith("_test") or stem in ("tests", "test")):
254
+ continue
234
255
  if slug in low or slug.replace("-", "") in low.replace("-", ""):
235
256
  hits.append(os.path.join(dp, f))
236
257
  if hits:
@@ -239,13 +260,18 @@ def find_tests(slug):
239
260
 
240
261
  def analyse_tests(slug, tests):
241
262
  """Return (total, behaviour, structural_only, ac_named, details)."""
242
- total = behaviour = struct_only = ac_named = 0
263
+ total = behaviour = struct_only = ac_named = placeholders = 0
243
264
  details = []
244
265
  for f in tests:
245
266
  src = read(f)
246
267
  if not src.strip():
247
268
  continue
248
269
  code = "\n".join(l for l in src.splitlines() if not l.strip().startswith("#"))
270
+ if is_placeholder(code):
271
+ placeholders += 1
272
+ details.append(f"{os.path.relpath(f, root)}: placeholder scaffolding (assert False/TODO)")
273
+ total += 1
274
+ continue
249
275
  kind = classify(code)
250
276
  body_struct = kind == "structural"
251
277
  body_behav = kind == "behaviour"
@@ -260,7 +286,7 @@ def analyse_tests(slug, tests):
260
286
  details.append(f"{os.path.relpath(f, root)}: structural assertions only")
261
287
  if acs:
262
288
  ac_named += 1
263
- return total, behaviour, struct_only, ac_named, details
289
+ return total, behaviour, struct_only, ac_named, details, placeholders
264
290
 
265
291
  def run_python_tests(tests):
266
292
  """Return (status, reason, sample) where status: red_ok|red_wrong|green|unknown."""
@@ -318,6 +344,8 @@ def verify(slug):
318
344
  sig_files = []
319
345
  for d in src_dirs:
320
346
  for dp, _dn, fs in os.walk(d):
347
+ if "__pycache__" in dp:
348
+ continue
321
349
  sig_files += [os.path.join(dp, f) for f in fs if re.search(r"\.(py|ts|java|rs|go)$", f)]
322
350
  ok_sigs = bool(sig_files)
323
351
  checks.append(("frozen signatures", ok_sigs, f"{len(sig_files)} source file(s)" if ok_sigs else f"no src/{slug}/"))
@@ -325,11 +353,17 @@ def verify(slug):
325
353
 
326
354
  # 3. tests exist, are behaviour (not structural-only), and name ACs
327
355
  tests = find_tests(slug)
328
- total, behaviour, struct_only, ac_named, tdetails = analyse_tests(slug, tests)
356
+ total, behaviour, struct_only, ac_named, tdetails, placeholders = analyse_tests(slug, tests)
329
357
  ok_tests = bool(tests)
330
358
  checks.append(("contract tests", ok_tests, f"{len(tests)} file(s)" if tests else "no tests referencing the module"))
331
359
  ok_all &= ok_tests
332
360
 
361
+ ok_placeholder = placeholders == 0
362
+ checks.append(("no placeholders", ok_placeholder,
363
+ "no generated scaffolding" if ok_placeholder
364
+ else f"{placeholders} suite(s) are guardian scaffolding (assert False / TODO), not tests"))
365
+ ok_all &= ok_placeholder
366
+
333
367
  # Advisory only: a syntactic shape-vs-behaviour classifier is always
334
368
  # arguable. The objective gate is the RED check below — tests that are GREEN
335
369
  # over NotImplementedError bodies are structural by definition, whatever the
@@ -347,12 +381,72 @@ def verify(slug):
347
381
  # 4. RED for the right reason
348
382
  status, reason, sample = run_python_tests(tests) if tests else ("unknown", "no tests", "")
349
383
  ok_red = status == "red_ok"
384
+ if placeholders > 0 and status == "red_ok":
385
+ ok_red = False
386
+ reason = (f"{placeholders} placeholder suite(s) fail by construction — "
387
+ f"red is not evidence until they assert a rule")
350
388
  if status == "green":
351
389
  reason = ("tests are GREEN while the module is unimplemented — they assert shape, "
352
390
  "not rules; a freeze needs failing behaviour tests")
353
391
  checks.append(("red state", ok_red, f"{status}: {reason}"))
354
392
  ok_all &= ok_red
355
393
 
394
+ # 4b. recorded evidence must still hold (spec: a contract change means
395
+ # the module is no longer frozen and must be re-frozen).
396
+ st_rec = load_state()
397
+ ent = entry(st_rec, slug)
398
+ if ent.get("state") == "frozen" and (ent.get("evidence") or {}).get("commit"):
399
+ ev = ent["evidence"]
400
+ sha = ev["commit"]
401
+ base_ref = ev.get("branch") or base
402
+ # realpath both sides: on macOS /tmp is a symlink to /private/tmp, and a
403
+ # symlinked project dir made relpath produce ../../../tmp/... so git saw
404
+ # a path outside the repo and reported "no changes".
405
+ rel = os.path.relpath(os.path.realpath(root), os.path.realpath(git_top))
406
+ prefix = "" if rel == "." else f"{rel}/"
407
+
408
+ on_base = subprocess.run(
409
+ ["git", "-C", git_top, "merge-base", "--is-ancestor", sha, base_ref],
410
+ capture_output=True).returncode == 0
411
+ checks.append(("recorded commit", on_base,
412
+ f"{sha[:8]} is an ancestor of {base_ref}" if on_base
413
+ else f"{sha[:8]} is NOT an ancestor of {base_ref}"))
414
+ ok_all &= on_base
415
+
416
+ missing = [p for p in (ev.get("contracts") or []) if not os.path.exists(os.path.join(root, p))]
417
+ checks.append(("evidence files", not missing,
418
+ "recorded contract/test files exist" if not missing
419
+ else "missing: " + ", ".join(missing)))
420
+ ok_all &= not missing
421
+
422
+ def changed_since(paths):
423
+ """Files changed since the freeze — committed OR uncommitted.
424
+
425
+ `git diff <sha> -- <paths>` compares the recorded commit against the
426
+ WORKING TREE, so it catches both later commits and local edits.
427
+ Diffing commit-to-commit silently ignored uncommitted tampering.
428
+ """
429
+ if not paths:
430
+ return []
431
+ args = ["git", "-C", git_top, "diff", "--name-only", sha, "--"]
432
+ args += [f"{prefix}{p}" for p in paths]
433
+ out = subprocess.run(args, capture_output=True, text=True).stdout.strip()
434
+ if os.environ.get("GUARDIAN_FREEZE_DEBUG"):
435
+ print(f"DEBUG changed_since args={args} out={out!r}", file=sys.stderr)
436
+ return [l for l in out.splitlines() if l.strip()]
437
+
438
+ contract_changes = changed_since(ev.get("contracts") or [])
439
+ checks.append(("contracts unchanged", not contract_changes,
440
+ "contracts unchanged since the freeze" if not contract_changes
441
+ else "changed after the freeze — re-freeze: " + ", ".join(contract_changes)))
442
+ ok_all &= not contract_changes
443
+
444
+ test_changes = changed_since(ev.get("tests") or [])
445
+ checks.append(("tests unchanged", not test_changes,
446
+ "frozen test files unchanged" if not test_changes
447
+ else "tests changed after the freeze — re-freeze: " + ", ".join(test_changes[:3])))
448
+ ok_all &= not test_changes
449
+
356
450
  # 5. dependencies frozen/implemented
357
451
  bad_deps = []
358
452
  st = load_state()
@@ -443,8 +537,8 @@ if cmd == "list":
443
537
 
444
538
  if cmd == "status":
445
539
  if not module:
446
- print("usage: freeze.sh status --module <m>", file=sys.stderr)
447
- raise SystemExit(2)
540
+ # no module given: same as list
541
+ cmd = "list"
448
542
  slug = slugify(module)
449
543
  st = load_state()
450
544
  e = entry(st, slug)