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.
|
|
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.
|
|
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,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"timestamp": "2026-11-13T20:
|
|
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":
|
|
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
|
-
|
|
447
|
-
|
|
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)
|