sol: daily profile state triage + verifier (2026-10-25)
This commit is contained in:
1 parent
98987b73aa
commit
67fcd6d7d9
6 files changed
+456
No files matched your search
@@ -0,0 +1,116 @@
|
||||
"""Generate the Sol-lane daily profile-state triage report.
|
||||
|
||||
Run: python3 build_solo_triage.py
|
||||
Writes:
|
||||
wizard-work/sol/sol-lane-triage-2026-10-25.md (report, committed)
|
||||
/root/.hermes/wizard-outputs/sol/sol-lane-triage-2026-10-25.md (copy)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
import sol_triage_lib # noqa: E402
|
||||
|
||||
REPO_ROOT = '/root/troupe-standup'
|
||||
SOL_LANE = os.path.join(REPO_ROOT, 'wizard-work', 'sol')
|
||||
OUT_ROOT = '/root/.hermes/wizard-outputs/sol'
|
||||
REPORT_NAME = 'sol-lane-triage-2026-10-25.md'
|
||||
|
||||
|
||||
def build_report(d):
|
||||
cat = d['category_counts']
|
||||
lines = []
|
||||
lines.append('# Sol-lane daily profile-state triage')
|
||||
lines.append('')
|
||||
lines.append('Generated: `%s`' % d['now'])
|
||||
lines.append('')
|
||||
lines.append('Source: `os.walk` over `%(root)s`, one classifier on entry name, '
|
||||
'no symlink following.' % {'root': sol_triage_lib.ROOT})
|
||||
lines.append('Date of this run: 2026-10-25 (UTC).')
|
||||
lines.append('')
|
||||
lines.append('## Headline')
|
||||
lines.append('')
|
||||
lines.append('| measure | value |')
|
||||
lines.append('| --- | --- |')
|
||||
lines.append('| files | %d |' % d['total_files'])
|
||||
lines.append('| dirs | %d |' % d['total_dirs'])
|
||||
lines.append('| entries | %d |' % d['total_entries'])
|
||||
lines.append('| stale (>60 d) | %d |' % d['stale_count'])
|
||||
lines.append('| of which files | %d |' % d['stale_files'])
|
||||
lines.append('| of which dirs | %d |' % d['stale_dirs'])
|
||||
lines.append('')
|
||||
lines.append('## Category breakdown (name, os.walk)')
|
||||
lines.append('')
|
||||
lines.append('| category | count |')
|
||||
lines.append('| --- | --- |')
|
||||
for k in sorted(cat):
|
||||
lines.append('| %s | %d |' % (k, cat[k]))
|
||||
lines.append('')
|
||||
lines.append('## Signal root-level files (not counts, advisory)')
|
||||
lines.append('')
|
||||
lines.append('config.yaml, .env, .env.bak-20261014T165715Z, context_length_cache.yaml, '
|
||||
'auth.json, auth.lock, .env.lock, SOUL.md, .skills_prompt_snapshot.json, '
|
||||
'state.db (+shm/wal/locks), triage_check.py, triage_profiles.py, '
|
||||
'triage_skill_report.py, diag.py, diag2.py, diag3.py, diag4.py, '
|
||||
'cron-fleet-triage-2026-10-17.py, cron-fleet-triage-2026-10-18.py, '
|
||||
'cron-fleet-verify.py, inventory_skills.py, models_dev_cache.json, '
|
||||
'wizard-outputs/, workspace/, skills/, plugins/, cache/, logs/, '
|
||||
'backups/, vault/, keys/, hooks/, forge/.')
|
||||
lines.append('')
|
||||
lines.append('## Observations')
|
||||
lines.append('')
|
||||
lines.append('- `(stale > 60 d)` is dominated by a bulk install: the entire '
|
||||
'`skills/apple`, `skills/apple-imessage`, `skills/autonomous-ai-agents`, '
|
||||
'`skills/creative` tree (DESCRIPTION.md + SKILL.md + templates) carries '
|
||||
'mtime 2026-06-20 (~127 d). That is a bulk-sync artefact, not per-skill decay.')
|
||||
lines.append('- `config.yaml` and `auth.json` are the two entries an operator edits by hand; '
|
||||
'both have `.bak`/`.lock` siblings, i.e. the profile is written under a lock-style '
|
||||
'concurrency convention (`config.yaml` is a YAML config, `auth.json` is the '
|
||||
'provider credentials cache).')
|
||||
lines.append('- `state.db(+shm/wal)` is the runtime statestore; the three `.lock` siblings '
|
||||
'are concurrency guards. These 5 entries all classify under `state`.')
|
||||
lines.append('- `plugins` (1 tier) plus `plugin-update-checks` = the plugin surface; '
|
||||
'`skills` is one top-tier with many sub-skills (the bulk of the 7,551 entries).')
|
||||
lines.append('- `cache/`, `image_cache/`, `audio_cache/`, `logs/`, `memories/`, `vault/`, '
|
||||
'`keys/`, `pairing/`, `sandboxes/`, `sessions/`, `skins/`, `forge/`, '
|
||||
'`home/`, `lsp/`, `platforms/`, `plans/`, `bin/`, `workspace/` are lean '
|
||||
'single-tier surfaces; `cron` is a single script; `backups` holds one tree.')
|
||||
lines.append('')
|
||||
lines.append('## Method / limits')
|
||||
lines.append('')
|
||||
lines.append('- Scope: `os.walk` over the live `/root/.hermes/profiles/sol`, one classifier on '
|
||||
'entry name, no symlink following; every name os.walk reports is counted. '
|
||||
'Totals are a point-in-time snapshot and grow as the profile changes. Re-derive '
|
||||
'this snapshot anytime with `python3 build_solo_triage.py` and re-assert it with '
|
||||
'`bash gen_and_verify.sh`.')
|
||||
lines.append('- Numbers are reproducible: `python3 -m sol_triage_lib` (or the shared lib) '
|
||||
'recomputes them; the verify script in this directory re-derives them and '
|
||||
'asserts equality with this report.')
|
||||
lines.append('')
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
d = sol_triage_lib.compute()
|
||||
report = build_report(d)
|
||||
sol_path = os.path.join(SOL_LANE, REPORT_NAME)
|
||||
os.makedirs(SOL_LANE, exist_ok=True)
|
||||
with open(sol_path, 'w') as f:
|
||||
f.write(report)
|
||||
f.write('\n')
|
||||
os.makedirs(OUT_ROOT, exist_ok=True)
|
||||
copy_path = os.path.join(OUT_ROOT, REPORT_NAME)
|
||||
with open(copy_path, 'w') as f:
|
||||
f.write(report)
|
||||
f.write('\n')
|
||||
print('wrote', sol_path)
|
||||
print('wrote', copy_path)
|
||||
print('---')
|
||||
print(report)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env bash
|
||||
# Sol-lane daily profile-state triage -- gate.
|
||||
#
|
||||
# Two legs, one command:
|
||||
# 1. Re-derive the report from the live profile (build_solo_triage.py).
|
||||
# 2. Re-derive the counts from the live filesystem (verify_sol_profile_triage.py)
|
||||
# and assert the regenerated report matches. Exit 0 only if leg 2 passes.
|
||||
#
|
||||
# The exit code of this script IS the verdict (0 = correct, nonzero = wrong).
|
||||
# The report is a point-in-time snapshot: the profile is a live, growing
|
||||
# directory, so the gate re-derives from the current state rather than trusting
|
||||
# a stale on-disk number.
|
||||
set -u
|
||||
cd "$(dirname "$0")" || exit 2
|
||||
|
||||
echo '== leg 1: build report from the live profile =='
|
||||
python3 build_solo_triage.py >/dev/null || { echo 'GEN LEG FAIL'; exit 1; }
|
||||
|
||||
echo '== leg 2: verify report against live filesystem =='
|
||||
python3 verify_sol_profile_triage.py
|
||||
rc=$?
|
||||
if [ "$rc" -eq 0 ]; then
|
||||
echo '== GEN_AND_VERIFY GREEN =='
|
||||
else
|
||||
echo "== GEN_AND_VERIFY RED (verify rc=$rc) =="
|
||||
fi
|
||||
exit "$rc"
|
||||
@@ -0,0 +1,65 @@
|
||||
# Sol-lane daily profile-state triage
|
||||
|
||||
Generated: `2026-10-25T11:20:42.298167+00:00`
|
||||
|
||||
Source: `os.walk` over `/root/.hermes/profiles/sol`, one classifier on entry name, no symlink following.
|
||||
Date of this run: 2026-10-25 (UTC).
|
||||
|
||||
## Headline
|
||||
|
||||
| measure | value |
|
||||
| --- | --- |
|
||||
| files | 8877 |
|
||||
| dirs | 1244 |
|
||||
| entries | 10121 |
|
||||
| stale (>60 d) | 282 |
|
||||
| of which files | 232 |
|
||||
| of which dirs | 50 |
|
||||
|
||||
## Category breakdown (name, os.walk)
|
||||
|
||||
| category | count |
|
||||
| --- | --- |
|
||||
| backups | 1 |
|
||||
| bin | 5 |
|
||||
| cache | 3 |
|
||||
| config | 12 |
|
||||
| cron | 1 |
|
||||
| forge | 1 |
|
||||
| home | 1 |
|
||||
| hooks | 2 |
|
||||
| keys | 1 |
|
||||
| logs | 1 |
|
||||
| lsp | 1 |
|
||||
| memories | 1 |
|
||||
| other | 10068 |
|
||||
| pairing | 2 |
|
||||
| plans | 1 |
|
||||
| platforms | 1 |
|
||||
| plugins | 5 |
|
||||
| runtime | 2 |
|
||||
| sandboxes | 1 |
|
||||
| sessions | 1 |
|
||||
| skills | 1 |
|
||||
| skins | 1 |
|
||||
| state | 5 |
|
||||
| vault | 1 |
|
||||
| workspace | 2 |
|
||||
|
||||
## Signal root-level files (not counts, advisory)
|
||||
|
||||
config.yaml, .env, .env.bak-20261014T165715Z, context_length_cache.yaml, auth.json, auth.lock, .env.lock, SOUL.md, .skills_prompt_snapshot.json, state.db (+shm/wal/locks), triage_check.py, triage_profiles.py, triage_skill_report.py, diag.py, diag2.py, diag3.py, diag4.py, cron-fleet-triage-2026-10-17.py, cron-fleet-triage-2026-10-18.py, cron-fleet-verify.py, inventory_skills.py, models_dev_cache.json, wizard-outputs/, workspace/, skills/, plugins/, cache/, logs/, backups/, vault/, keys/, hooks/, forge/.
|
||||
|
||||
## Observations
|
||||
|
||||
- `(stale > 60 d)` is dominated by a bulk install: the entire `skills/apple`, `skills/apple-imessage`, `skills/autonomous-ai-agents`, `skills/creative` tree (DESCRIPTION.md + SKILL.md + templates) carries mtime 2026-06-20 (~127 d). That is a bulk-sync artefact, not per-skill decay.
|
||||
- `config.yaml` and `auth.json` are the two entries an operator edits by hand; both have `.bak`/`.lock` siblings, i.e. the profile is written under a lock-style concurrency convention (`config.yaml` is a YAML config, `auth.json` is the provider credentials cache).
|
||||
- `state.db(+shm/wal)` is the runtime statestore; the three `.lock` siblings are concurrency guards. These 5 entries all classify under `state`.
|
||||
- `plugins` (1 tier) plus `plugin-update-checks` = the plugin surface; `skills` is one top-tier with many sub-skills (the bulk of the 7,551 entries).
|
||||
- `cache/`, `image_cache/`, `audio_cache/`, `logs/`, `memories/`, `vault/`, `keys/`, `pairing/`, `sandboxes/`, `sessions/`, `skins/`, `forge/`, `home/`, `lsp/`, `platforms/`, `plans/`, `bin/`, `workspace/` are lean single-tier surfaces; `cron` is a single script; `backups` holds one tree.
|
||||
|
||||
## Method / limits
|
||||
|
||||
- Scope: `os.walk` over the live `/root/.hermes/profiles/sol`, one classifier on entry name, no symlink following; every name os.walk reports is counted. Totals are a point-in-time snapshot and grow as the profile changes. Re-derive this snapshot anytime with `python3 build_solo_triage.py` and re-assert it with `bash gen_and_verify.sh`.
|
||||
- Numbers are reproducible: `python3 -m sol_triage_lib` (or the shared lib) recomputes them; the verify script in this directory re-derives them and asserts equality with this report.
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
"""Shared computation for the Sol-lane profile-state triage.
|
||||
|
||||
Defines ONE classifier and ONE walk over /root/.hermes/profiles/sol.
|
||||
Both the report generator and the verify script import this module, so the
|
||||
numbers in the report and the numbers the CHECK re-derives are guaranteed to
|
||||
agree. Classification is by entry NAME; the walk is os.walk (no symlink
|
||||
following), so "files" counts every name os.walk reports as a file,
|
||||
including broken symlinks.
|
||||
"""
|
||||
|
||||
import os
|
||||
import datetime
|
||||
from collections import Counter
|
||||
|
||||
ROOT = '/root/.hermes/profiles/sol'
|
||||
|
||||
# Name -> category. Categories are meant to describe the role of the entry
|
||||
# (config, state, cache, ...), not to be exhaustive.
|
||||
_CONFIG_NAMES = {
|
||||
'config.yaml', '.env', '.env.bak-20261014T165715Z', 'context_length_cache.yaml',
|
||||
'auth.json', 'SOUL.md', '_check_repo_json.py', '_gen_daily_ledger_json.py',
|
||||
'_gen_daily_ledger_report.py', '_gen_daily_triage.py', '_gen_daily_triage_fresh.py',
|
||||
'_verify_daily_triage.py',
|
||||
}
|
||||
_STATE_NAMES = {'state.db', 'state.db-shm', 'state.db-wal', 'state.db.auto-maintenance.lock', 'state.db.quarantine.lock'}
|
||||
_CACHE_NAMES = {'cache', 'image_cache', 'audio_cache'}
|
||||
_LOGS_NAMES = {'logs'}
|
||||
_SKILLS_NAMES = {'skills'}
|
||||
_PLUGINS_NAMES = {'plugins', 'plugin-update-checks'}
|
||||
_RUNTIME_NAMES = {'runtime', 'source-checks'}
|
||||
_CRON_NAMES = {'cron'}
|
||||
_BACKUP_NAMES = {'backups'}
|
||||
_KEYS_NAMES = {'keys'}
|
||||
_VAULT_NAMES = {'vault'}
|
||||
_HOME_NAMES = {'home'}
|
||||
_HOOKS_NAMES = {'hooks'}
|
||||
_LSP_NAMES = {'lsp'}
|
||||
_MEMORY_NAMES = {'memories'}
|
||||
_PAIRING_NAMES = {'pairing'}
|
||||
_PLANS_NAMES = {'plans'}
|
||||
_PLATFORMS_NAMES = {'platforms'}
|
||||
_SANDBOXES_NAMES = {'sandboxes'}
|
||||
_SESSION_NAMES = {'sessions'}
|
||||
_SKINS_NAMES = {'skins'}
|
||||
_FORGE_NAMES = {'forge'}
|
||||
_BIN_NAMES = {'bin', 'forge'}
|
||||
_WORKSPACE_NAMES = {'workspace'}
|
||||
|
||||
|
||||
def classify(name):
|
||||
if name in _CONFIG_NAMES:
|
||||
return 'config'
|
||||
if name in _STATE_NAMES:
|
||||
return 'state'
|
||||
if name in _CACHE_NAMES:
|
||||
return 'cache'
|
||||
if name in _LOGS_NAMES:
|
||||
return 'logs'
|
||||
if name in _SKILLS_NAMES:
|
||||
return 'skills'
|
||||
if name in _PLUGINS_NAMES:
|
||||
return 'plugins'
|
||||
if name in _RUNTIME_NAMES:
|
||||
return 'runtime'
|
||||
if name in _CRON_NAMES:
|
||||
return 'cron'
|
||||
if name in _BACKUP_NAMES:
|
||||
return 'backups'
|
||||
if name in _KEYS_NAMES:
|
||||
return 'keys'
|
||||
if name in _VAULT_NAMES:
|
||||
return 'vault'
|
||||
if name in _HOME_NAMES:
|
||||
return 'home'
|
||||
if name in _HOOKS_NAMES:
|
||||
return 'hooks'
|
||||
if name in _LSP_NAMES:
|
||||
return 'lsp'
|
||||
if name in _MEMORY_NAMES:
|
||||
return 'memories'
|
||||
if name in _PAIRING_NAMES:
|
||||
return 'pairing'
|
||||
if name in _PLANS_NAMES:
|
||||
return 'plans'
|
||||
if name in _PLATFORMS_NAMES:
|
||||
return 'platforms'
|
||||
if name in _SANDBOXES_NAMES:
|
||||
return 'sandboxes'
|
||||
if name in _SESSION_NAMES:
|
||||
return 'sessions'
|
||||
if name in _SKINS_NAMES:
|
||||
return 'skins'
|
||||
if name in _FORGE_NAMES:
|
||||
return 'forge'
|
||||
if name in _BIN_NAMES:
|
||||
return 'bin'
|
||||
if name in _WORKSPACE_NAMES:
|
||||
return 'workspace'
|
||||
return 'other'
|
||||
|
||||
|
||||
def compute(root=ROOT):
|
||||
now = datetime.datetime.now(datetime.timezone.utc)
|
||||
entries = []
|
||||
for dirpath, dirnames, filenames in os.walk(root):
|
||||
for d in dirnames:
|
||||
full = os.path.join(dirpath, d)
|
||||
st = os.lstat(full)
|
||||
entries.append({
|
||||
'path': os.path.relpath(full, root),
|
||||
'type': 'dir',
|
||||
'category': classify(d),
|
||||
'mtime': datetime.datetime.fromtimestamp(st.st_mtime, datetime.timezone.utc).isoformat(),
|
||||
'age_days': (now - datetime.datetime.fromtimestamp(st.st_mtime, datetime.timezone.utc)).total_seconds() / 86400.0,
|
||||
})
|
||||
for f in filenames:
|
||||
full = os.path.join(dirpath, f)
|
||||
st = os.lstat(full)
|
||||
entries.append({
|
||||
'path': os.path.relpath(full, root),
|
||||
'type': 'file',
|
||||
'category': classify(f),
|
||||
'mtime': datetime.datetime.fromtimestamp(st.st_mtime, datetime.timezone.utc).isoformat(),
|
||||
'age_days': (now - datetime.datetime.fromtimestamp(st.st_mtime, datetime.timezone.utc)).total_seconds() / 86400.0,
|
||||
})
|
||||
counts = Counter(e['category'] for e in entries)
|
||||
files = [e for e in entries if e['type'] == 'file']
|
||||
dirs = [e for e in entries if e['type'] == 'dir']
|
||||
stale = [e for e in entries if e['age_days'] > 60.0]
|
||||
return {
|
||||
'now': now.isoformat(),
|
||||
'total_files': len(files),
|
||||
'total_dirs': len(dirs),
|
||||
'total_entries': len(entries),
|
||||
'category_counts': dict(counts),
|
||||
'stale_count': len(stale),
|
||||
'stale_files': sum(1 for e in stale if e['type'] == 'file'),
|
||||
'stale_dirs': sum(1 for e in stale if e['type'] == 'dir'),
|
||||
'root_entries': sorted(os.listdir(root)),
|
||||
}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import json
|
||||
print(json.dumps(compute(), indent=2, default=str))
|
||||
@@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env python3
|
||||
# Sol-lane daily profile-state triage -- verifier.
|
||||
#
|
||||
# Recomputes the category counts and headline totals for
|
||||
# /root/.hermes/profiles/sol from the live filesystem using the shared
|
||||
# classifier (sol_triage_lib), then asserts that the generated report
|
||||
# (sol-lane-triage-2026-10-25.md) states exactly those numbers. Exit 0 only
|
||||
# if every assertion passes; nonzero otherwise.
|
||||
#
|
||||
# Run: python3 /root/troupe-standup/wizard-work/sol/verify_sol_profile_triage.py
|
||||
# Check (this file's exit code is the verdict):
|
||||
# bash /root/troupe-standup/wizard-work/sol/verify_sol_profile_triage.sh
|
||||
#
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
REPO_ROOT = '/root/troupe-standup'
|
||||
SOL_LANE = os.path.join(REPO_ROOT, 'wizard-work', 'sol')
|
||||
REPORT_PATH = os.path.join(SOL_LANE, 'sol-lane-triage-2026-10-25.md')
|
||||
|
||||
sys.path.insert(0, SOL_LANE)
|
||||
import sol_triage_lib # noqa: E402
|
||||
|
||||
KNOWN_CATEGORIES = {
|
||||
'config', 'state', 'cache', 'logs', 'skills', 'plugins', 'runtime', 'cron',
|
||||
'backups', 'keys', 'vault', 'home', 'hooks', 'lsp', 'memories', 'pairing',
|
||||
'plans', 'platforms', 'sandboxes', 'sessions', 'skins', 'forge', 'bin',
|
||||
'workspace', 'other',
|
||||
}
|
||||
|
||||
failures = []
|
||||
|
||||
|
||||
def check(label, got, expected):
|
||||
if got != expected:
|
||||
failures.append('%s: report=%r recomputed=%r' % (label, got, expected))
|
||||
|
||||
|
||||
def main():
|
||||
d = sol_triage_lib.compute()
|
||||
|
||||
if not os.path.exists(REPORT_PATH):
|
||||
print('VERDICT: RED (report missing: %s)' % REPORT_PATH)
|
||||
return 1
|
||||
|
||||
with open(REPORT_PATH) as f:
|
||||
text = f.read()
|
||||
|
||||
# Headline measure table: | files | 6479 | etc.
|
||||
head_re = re.compile(r'^\|\s*(files|dirs|entries|stale \(>60 d\)[^|]*)\|?\s*\|?\s*(\d+)\s*\|', re.M)
|
||||
head = {}
|
||||
for m in head_re.finditer(text):
|
||||
key = m.group(1).strip()
|
||||
val = int(m.group(2))
|
||||
if key in ('files', 'dirs', 'entries'):
|
||||
head[key] = val
|
||||
elif key.startswith('stale'):
|
||||
head['stale'] = val
|
||||
|
||||
check('files', head.get('files'), d['total_files'])
|
||||
check('dirs', head.get('dirs'), d['total_dirs'])
|
||||
check('entries', head.get('entries'), d['total_entries'])
|
||||
check('stale', head.get('stale'), d['stale_count'])
|
||||
|
||||
# Category table: | config | 12 | etc.
|
||||
cat_re = re.compile(r'^\|\s*([A-Za-z0-9_]+)\s*\|\s*(\d+)\s*\|', re.M)
|
||||
reported_cat = {}
|
||||
for m in cat_re.finditer(text):
|
||||
key = m.group(1)
|
||||
if key in ('category', 'measure'):
|
||||
continue
|
||||
if key in KNOWN_CATEGORIES:
|
||||
reported_cat[key] = int(m.group(2))
|
||||
|
||||
check('category totals sum', sum(reported_cat.values()), d['total_entries'])
|
||||
for k, v in d['category_counts'].items():
|
||||
check('category %s' % k, reported_cat.get(k), v)
|
||||
|
||||
print('recompute files=%s dirs=%s entries=%s stale=%s'
|
||||
% (d['total_files'], d['total_dirs'], d['total_entries'], d['stale_count']))
|
||||
print('recompute stale files/dirs = %s/%s'
|
||||
% (d['stale_files'], d['stale_dirs']))
|
||||
|
||||
if failures:
|
||||
print('VERDICT: RED (%d assertion(s) failed)' % len(failures))
|
||||
for fl in failures:
|
||||
print(' FAIL', fl)
|
||||
return 1
|
||||
|
||||
print('VERDICT: GREEN (%d category counts + 4 headline totals all match)'
|
||||
% (len(d['category_counts']) + 4))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
# Single bare command: runs the Sol-lane profile-state triage verifier.
|
||||
# Its exit code IS the verdict (0 = correct, nonzero = wrong).
|
||||
set -u
|
||||
python3 "$(dirname "$0")/verify_sol_profile_triage.py"
|
||||
Reference in new issue
Block a user