refactor: make ARNES external-repo based with ticket publish flow

This commit is contained in:
rikrdo
2026-05-18 00:26:32 +02:00
parent 3ff9b70e4c
commit b396b6d3c9
101 changed files with 810 additions and 6140 deletions

133
scripts/publish_ticket.py Executable file
View File

@@ -0,0 +1,133 @@
#!/usr/bin/env python3
import argparse
import json
import subprocess
from datetime import datetime, timezone
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
BACKLOG = ROOT / 'backlog' / 'features.json'
ARTIFACTS = ROOT / 'work' / 'artifacts'
def now_iso():
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace('+00:00', 'Z')
def run_git(*args, check=True):
result = subprocess.run(
['git', *args],
cwd=ROOT,
text=True,
capture_output=True,
)
if check and result.returncode != 0:
msg = result.stderr.strip() or result.stdout.strip() or f'git command failed: {args}'
raise SystemExit(msg)
return result
def ensure_git_repo():
result = run_git('rev-parse', '--is-inside-work-tree', check=False)
if result.returncode != 0 or result.stdout.strip() != 'true':
raise SystemExit('This project is not inside a git repository. Run git init or clone a repo first.')
def load_backlog():
return json.loads(BACKLOG.read_text(encoding='utf-8'))
def find_feature(feature_id):
data = load_backlog()
for feature in data.get('features', []):
if str(feature.get('id')) == feature_id:
return feature
raise SystemExit(f'Feature not found in backlog: {feature_id}')
def current_branch():
branch = run_git('symbolic-ref', '--quiet', '--short', 'HEAD', check=False).stdout.strip()
if not branch:
branch = run_git('rev-parse', '--abbrev-ref', 'HEAD', check=False).stdout.strip()
if not branch or branch == 'HEAD':
raise SystemExit('Detached HEAD is not supported for publish. Checkout a branch first.')
return branch
def ensure_remote(remote):
remotes = [line.strip() for line in run_git('remote').stdout.splitlines() if line.strip()]
if remote not in remotes:
raise SystemExit(f'Remote not found: {remote}. Add it first with git remote add {remote} <url>.')
def status_porcelain():
return run_git('status', '--porcelain').stdout.strip()
def default_commit_message(feature):
feature_id = feature['id']
ticket_type = feature.get('type')
title = str(feature.get('title', '')).strip()
if ticket_type:
return f'{feature_id} {ticket_type}: {title}'
return f'{feature_id}: {title}'
def write_publish_artifact(feature_id, payload):
feature_dir = ARTIFACTS / feature_id
feature_dir.mkdir(parents=True, exist_ok=True)
path = feature_dir / 'publish.json'
path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + '\n', encoding='utf-8')
return path
def ensure_git_identity():
name = run_git('config', 'user.name', check=False).stdout.strip()
email = run_git('config', 'user.email', check=False).stdout.strip()
if not name or not email:
raise SystemExit('Missing git identity. Configure git user.name and user.email before publish.')
def main():
parser = argparse.ArgumentParser(description='Commit and push one ticket, then write publish.json evidence.')
parser.add_argument('--feature-id', required=True)
parser.add_argument('--remote', default='origin')
parser.add_argument('--branch', default='')
parser.add_argument('--commit-message', default='')
args = parser.parse_args()
ensure_git_repo()
ensure_git_identity()
feature = find_feature(args.feature_id)
remote = args.remote.strip() or 'origin'
branch = args.branch.strip() or current_branch()
ensure_remote(remote)
if not status_porcelain():
raise SystemExit('No git changes to publish. Nothing to commit.')
commit_message = args.commit_message.strip() or default_commit_message(feature)
payload = {
'agent': 'leader',
'verdict': 'PUBLISHED',
'feature_id': args.feature_id,
'branch': branch,
'remote': remote,
'message': commit_message,
'pushed': True,
'published_at': now_iso(),
'note': 'This artifact is committed inside the publish commit for this ticket.'
}
artifact_path = write_publish_artifact(args.feature_id, payload)
run_git('add', '-A')
if not status_porcelain():
raise SystemExit('No staged git changes after git add -A. Nothing to commit.')
run_git('commit', '-m', commit_message)
run_git('push', remote, branch)
print(f'done -> {artifact_path}')
if __name__ == '__main__':
main()