esys-gitlog 0.1.0__tar.gz → 0.3.0__tar.gz

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.
Files changed (51) hide show
  1. esys_gitlog-0.3.0/PKG-INFO +119 -0
  2. esys_gitlog-0.3.0/README.md +97 -0
  3. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/pyproject.toml +7 -1
  4. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog/__init__.py +1 -1
  5. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog/cli.py +101 -7
  6. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog/config.py +12 -5
  7. esys_gitlog-0.3.0/src/esys_gitlog/detection.py +377 -0
  8. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog/display.py +21 -11
  9. esys_gitlog-0.3.0/src/esys_gitlog/identifiers.py +86 -0
  10. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog/interpret.py +32 -4
  11. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog/links.py +21 -5
  12. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog/preferences.py +129 -2
  13. esys_gitlog-0.3.0/src/esys_gitlog.egg-info/PKG-INFO +119 -0
  14. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog.egg-info/SOURCES.txt +2 -0
  15. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog.egg-info/entry_points.txt +1 -0
  16. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/tests/test_browser_display.py +23 -1
  17. esys_gitlog-0.3.0/tests/test_coverage_release_030.py +347 -0
  18. esys_gitlog-0.3.0/tests/test_detection.py +238 -0
  19. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/tests/test_installed_launcher.py +31 -12
  20. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/tests/test_preferences.py +49 -2
  21. esys_gitlog-0.3.0/tests/test_seed_jira_live_fixture.py +29 -0
  22. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/tests/test_semantic_default.py +9 -0
  23. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/tests/test_semantic_p4.py +26 -0
  24. esys_gitlog-0.1.0/PKG-INFO +0 -77
  25. esys_gitlog-0.1.0/README.md +0 -55
  26. esys_gitlog-0.1.0/src/esys_gitlog/detection.py +0 -145
  27. esys_gitlog-0.1.0/src/esys_gitlog/identifiers.py +0 -46
  28. esys_gitlog-0.1.0/src/esys_gitlog.egg-info/PKG-INFO +0 -77
  29. esys_gitlog-0.1.0/tests/test_detection.py +0 -77
  30. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/LICENSE.txt +0 -0
  31. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/setup.cfg +0 -0
  32. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog/__main__.py +0 -0
  33. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog/app.py +0 -0
  34. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog/git_adapter.py +0 -0
  35. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog/model.py +0 -0
  36. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog/pager.py +0 -0
  37. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog/pipeline.py +0 -0
  38. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog/qa.py +0 -0
  39. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog/render.py +0 -0
  40. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog/report.py +0 -0
  41. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog/tool_paths.py +0 -0
  42. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog.egg-info/dependency_links.txt +0 -0
  43. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog.egg-info/requires.txt +0 -0
  44. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/src/esys_gitlog.egg-info/top_level.txt +0 -0
  45. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/tests/test_cli.py +0 -0
  46. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/tests/test_coverage_gaps.py +0 -0
  47. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/tests/test_git_adapter.py +0 -0
  48. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/tests/test_pager.py +0 -0
  49. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/tests/test_render.py +0 -0
  50. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/tests/test_smoke.py +0 -0
  51. {esys_gitlog-0.1.0 → esys_gitlog-0.3.0}/tests/test_tool_paths.py +0 -0
@@ -0,0 +1,119 @@
1
+ Metadata-Version: 2.4
2
+ Name: esys-gitlog
3
+ Version: 0.3.0
4
+ Summary: Python implementation of git elog
5
+ Author: Project maintainers
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE.txt
10
+ Requires-Dist: typer>=0.12
11
+ Requires-Dist: rich>=13.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest>=8.0; extra == "dev"
14
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
15
+ Requires-Dist: ruff>=0.8; extra == "dev"
16
+ Requires-Dist: build>=1.0; extra == "dev"
17
+ Requires-Dist: twine>=5.0; extra == "dev"
18
+ Requires-Dist: pre-commit>=3.0; extra == "dev"
19
+ Provides-Extra: standalone
20
+ Requires-Dist: pyinstaller>=6.0; extra == "standalone"
21
+ Dynamic: license-file
22
+
23
+ # esys-gitlog
24
+
25
+ Python implementation of `git elog`. The distribution name is `esys-gitlog`. The import package is `esys_gitlog`. Installed commands: `git-elog` (so Git can run `git elog`) and the shorter alias `elog`. Prefer `elog --help` — `git elog --help` is handled by Git's own help path, not the Typer CLI.
26
+
27
+ The shared contract lives in `esys-gitlog-spec`. This repository carries that contract in Python. It does not define a second one.
28
+
29
+ ## Smoke
30
+
31
+ ```bash
32
+ pip install .
33
+ elog --help
34
+ elog --json
35
+ ```
36
+
37
+ `--json` writes one QA report document to stdout. The first skeleton returns an empty `commits` array.
38
+
39
+ ## Release / publish
40
+
41
+ Tag-driven CI publishes to GitLab Package Registry, TestPyPI, then PyPI (Deckicorn-style ADR-0009 flow). See [docs/release-pipeline.md](docs/release-pipeline.md).
42
+
43
+ ```bash
44
+ python scripts/publish_release.py --dry-run --bump patch
45
+ python scripts/build_package.py
46
+ python scripts/smoke_install_package.py --dist dist --expected-version 0.1.0
47
+ ```
48
+
49
+ ## Preferences and profiles
50
+
51
+ Layered tool preferences (user → `.tools/esys/esys-gitlog/` → `.tools_local/…`)
52
+ and named profiles follow root SADR-0009 / SADR-0010 and contract CADR-0012.
53
+
54
+ ```bash
55
+ elog --preferences path.json # preference overlay
56
+ elog --profile acme # built-in or profiles/acme.json
57
+ elog --config path/profile.json # one-off profile file
58
+ elog preferences detect # probe jira-cli/acli + git remote; write missing user keys
59
+ elog preferences detect --json --no-write # machine-readable candidates (extension setup)
60
+ elog preferences set-link jira-id 'https://host/browse/{id}'
61
+ elog preferences clear-link jira-id
62
+ elog preferences set-tool jira 'C:\tools\jira.exe' # optional absolute path
63
+ elog preferences set-tool acli /usr/local/bin/acli
64
+ ```
65
+
66
+ Jira browse bases come from local **jira-cli** config and/or **Atlassian acli**
67
+ (`acli jira config get site`), never from per-commit HTTP (SADR-0010). Detection
68
+ only fills unset user keys unless you use `set-link`. Absolute paths live under
69
+ `detection.jiraExecutable` / `detection.acliExecutable` when the tools are not
70
+ on `PATH`.
71
+
72
+ ### Live Jira fixture (SCRUM)
73
+
74
+ The free site [libesys.atlassian.net](https://libesys.atlassian.net/browse/SCRUM-1)
75
+ uses project key **`SCRUM`** only (do not invent other project keys).
76
+
77
+ 1. Configure acli or jira-cli against that site (optional absolute paths via
78
+ `elog preferences set-tool`).
79
+ 2. Seed once (creates 10 new issues, writes the pin — not part of every CI run):
80
+
81
+ ```bash
82
+ python scripts/seed_jira_live_fixture.py --project SCRUM --count 10
83
+ ```
84
+
85
+ 3. Commit `tests/integration/jira_live.pin.json`.
86
+ 4. Materialize a durable Git repo for the VS Code extension / manual checks:
87
+
88
+ ```bash
89
+ python scripts/materialize_jira_live_repo.py
90
+ ```
91
+
92
+ Output: `tests/integration/jira_live_repo/` (gitignored except a placeholder
93
+ README). Open that folder, store the browse template, refresh the commit browser.
94
+ 5. Run the live test locally or enable GitLab variable `ESYS_GITLOG_JIRA_LIVE=1`
95
+ for job `test:jira-live`:
96
+
97
+ ```bash
98
+ ESYS_GITLOG_JIRA_LIVE=1 pytest -m jira_live -o addopts= --no-cov
99
+ ```
100
+
101
+ Default `pytest` excludes `@pytest.mark.jira_live` so coverage stays offline.
102
+
103
+ ## Standalone binary (optional)
104
+
105
+ The primary install remains the wheel + `git-elog` / `elog` console scripts. For a
106
+ frozen one-file binary (for example Cursor `esysGitlog.executablePath` without
107
+ a Python env on PATH):
108
+
109
+ ```bash
110
+ python scripts/build_standalone.py
111
+ ```
112
+
113
+ Output: `dist/git-elog.exe` (Windows) or `dist/git-elog` (Linux/macOS). Build on
114
+ the OS you need — this does not cross-compile.
115
+
116
+ - [Architecture](docs/architecture/overview.md)
117
+ - [ADRs](docs/adr/INDEX.md)
118
+ - [ASRs](docs/asr/INDEX.md)
119
+ - [Implementation plan](plans/implementation.md)
@@ -0,0 +1,97 @@
1
+ # esys-gitlog
2
+
3
+ Python implementation of `git elog`. The distribution name is `esys-gitlog`. The import package is `esys_gitlog`. Installed commands: `git-elog` (so Git can run `git elog`) and the shorter alias `elog`. Prefer `elog --help` — `git elog --help` is handled by Git's own help path, not the Typer CLI.
4
+
5
+ The shared contract lives in `esys-gitlog-spec`. This repository carries that contract in Python. It does not define a second one.
6
+
7
+ ## Smoke
8
+
9
+ ```bash
10
+ pip install .
11
+ elog --help
12
+ elog --json
13
+ ```
14
+
15
+ `--json` writes one QA report document to stdout. The first skeleton returns an empty `commits` array.
16
+
17
+ ## Release / publish
18
+
19
+ Tag-driven CI publishes to GitLab Package Registry, TestPyPI, then PyPI (Deckicorn-style ADR-0009 flow). See [docs/release-pipeline.md](docs/release-pipeline.md).
20
+
21
+ ```bash
22
+ python scripts/publish_release.py --dry-run --bump patch
23
+ python scripts/build_package.py
24
+ python scripts/smoke_install_package.py --dist dist --expected-version 0.1.0
25
+ ```
26
+
27
+ ## Preferences and profiles
28
+
29
+ Layered tool preferences (user → `.tools/esys/esys-gitlog/` → `.tools_local/…`)
30
+ and named profiles follow root SADR-0009 / SADR-0010 and contract CADR-0012.
31
+
32
+ ```bash
33
+ elog --preferences path.json # preference overlay
34
+ elog --profile acme # built-in or profiles/acme.json
35
+ elog --config path/profile.json # one-off profile file
36
+ elog preferences detect # probe jira-cli/acli + git remote; write missing user keys
37
+ elog preferences detect --json --no-write # machine-readable candidates (extension setup)
38
+ elog preferences set-link jira-id 'https://host/browse/{id}'
39
+ elog preferences clear-link jira-id
40
+ elog preferences set-tool jira 'C:\tools\jira.exe' # optional absolute path
41
+ elog preferences set-tool acli /usr/local/bin/acli
42
+ ```
43
+
44
+ Jira browse bases come from local **jira-cli** config and/or **Atlassian acli**
45
+ (`acli jira config get site`), never from per-commit HTTP (SADR-0010). Detection
46
+ only fills unset user keys unless you use `set-link`. Absolute paths live under
47
+ `detection.jiraExecutable` / `detection.acliExecutable` when the tools are not
48
+ on `PATH`.
49
+
50
+ ### Live Jira fixture (SCRUM)
51
+
52
+ The free site [libesys.atlassian.net](https://libesys.atlassian.net/browse/SCRUM-1)
53
+ uses project key **`SCRUM`** only (do not invent other project keys).
54
+
55
+ 1. Configure acli or jira-cli against that site (optional absolute paths via
56
+ `elog preferences set-tool`).
57
+ 2. Seed once (creates 10 new issues, writes the pin — not part of every CI run):
58
+
59
+ ```bash
60
+ python scripts/seed_jira_live_fixture.py --project SCRUM --count 10
61
+ ```
62
+
63
+ 3. Commit `tests/integration/jira_live.pin.json`.
64
+ 4. Materialize a durable Git repo for the VS Code extension / manual checks:
65
+
66
+ ```bash
67
+ python scripts/materialize_jira_live_repo.py
68
+ ```
69
+
70
+ Output: `tests/integration/jira_live_repo/` (gitignored except a placeholder
71
+ README). Open that folder, store the browse template, refresh the commit browser.
72
+ 5. Run the live test locally or enable GitLab variable `ESYS_GITLOG_JIRA_LIVE=1`
73
+ for job `test:jira-live`:
74
+
75
+ ```bash
76
+ ESYS_GITLOG_JIRA_LIVE=1 pytest -m jira_live -o addopts= --no-cov
77
+ ```
78
+
79
+ Default `pytest` excludes `@pytest.mark.jira_live` so coverage stays offline.
80
+
81
+ ## Standalone binary (optional)
82
+
83
+ The primary install remains the wheel + `git-elog` / `elog` console scripts. For a
84
+ frozen one-file binary (for example Cursor `esysGitlog.executablePath` without
85
+ a Python env on PATH):
86
+
87
+ ```bash
88
+ python scripts/build_standalone.py
89
+ ```
90
+
91
+ Output: `dist/git-elog.exe` (Windows) or `dist/git-elog` (Linux/macOS). Build on
92
+ the OS you need — this does not cross-compile.
93
+
94
+ - [Architecture](docs/architecture/overview.md)
95
+ - [ADRs](docs/adr/INDEX.md)
96
+ - [ASRs](docs/asr/INDEX.md)
97
+ - [Implementation plan](plans/implementation.md)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "esys-gitlog"
7
- version = "0.1.0"
7
+ version = "0.3.0"
8
8
  description = "Python implementation of git elog"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -30,6 +30,7 @@ standalone = [
30
30
 
31
31
  [project.scripts]
32
32
  git-elog = "esys_gitlog.cli:main"
33
+ elog = "esys_gitlog.cli:main"
33
34
 
34
35
  [tool.setuptools]
35
36
  package-dir = { "" = "src" }
@@ -40,7 +41,12 @@ where = ["src"]
40
41
  [tool.pytest.ini_options]
41
42
  testpaths = ["tests"]
42
43
  pythonpath = [".", "src"]
44
+ markers = [
45
+ "jira_live: live Jira fixture (ESYS_GITLOG_JIRA_LIVE=1 + seeded pin)",
46
+ ]
43
47
  addopts = [
48
+ "-m",
49
+ "not jira_live",
44
50
  "--cov=esys_gitlog",
45
51
  "--cov-report=term-missing",
46
52
  "--cov-fail-under=95.01",
@@ -1,3 +1,3 @@
1
1
  """esys_gitlog — installable package."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.3.0"
@@ -1,4 +1,4 @@
1
- """Thin Typer command boundary for ``git-elog`` / ``git elog``."""
1
+ """Thin Typer command boundary for ``git-elog`` / ``elog`` / ``git elog``."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
@@ -11,13 +11,17 @@ from typer.core import TyperGroup
11
11
 
12
12
  from esys_gitlog import app as application
13
13
  from esys_gitlog.config import DisplayMode, QaMode
14
- from esys_gitlog.detection import detect_links, dump_detection_debug
14
+ from esys_gitlog.detection import detect_report, dump_detection_debug
15
15
  from esys_gitlog.git_adapter import GitError, filter_git_log_args
16
16
  from esys_gitlog.preferences import (
17
+ LinkPreferences,
18
+ clear_user_ref_link,
17
19
  fill_link_gaps,
18
20
  load_layered_preferences,
19
21
  merge_missing_links_into_user_file,
20
22
  preferences_to_object,
23
+ set_user_detection_executables,
24
+ set_user_ref_link,
21
25
  )
22
26
  from esys_gitlog.tool_paths import user_preferences_path
23
27
 
@@ -299,13 +303,40 @@ def preferences_detect(
299
303
  "--debug",
300
304
  help="Print which vendor CLIs are on PATH.",
301
305
  ),
306
+ as_json: bool = typer.Option(
307
+ False,
308
+ "--json",
309
+ help="Print a machine-readable detection report (candidates, sources, tools).",
310
+ ),
311
+ probe_auth: bool = typer.Option(
312
+ False,
313
+ "--probe-auth",
314
+ help="Optionally probe jira-cli/acli auth (setup only; may touch the network).",
315
+ ),
302
316
  ) -> None:
303
- """Probe local Jira/GitHub/GitLab CLIs and git remote; optionally persist."""
317
+ """Probe local Jira (jira-cli/acli), GitHub/GitLab CLIs and git remote; optionally persist."""
304
318
  cwd = Path.cwd()
305
319
  prefs = load_layered_preferences(cwd)
306
- discovered = detect_links(cwd, detection=prefs.detection)
307
- if show_debug:
308
- typer.echo(dump_detection_debug())
320
+ report = detect_report(
321
+ cwd,
322
+ detection=prefs.detection,
323
+ probe_auth=probe_auth,
324
+ )
325
+ discovered = LinkPreferences(
326
+ ref_links=dict(report.get("refLinks") or {}),
327
+ commit_link=report.get("commitLink"),
328
+ )
329
+ if show_debug and not as_json:
330
+ typer.echo(dump_detection_debug(prefs.detection))
331
+ if as_json:
332
+ if write:
333
+ path = user_preferences_path()
334
+ merge_missing_links_into_user_file(discovered=discovered, user_path=path)
335
+ report["wrote"] = str(path)
336
+ else:
337
+ report["wrote"] = None
338
+ typer.echo(json.dumps(report, indent=2))
339
+ return
309
340
  merged = fill_link_gaps(prefs, discovered)
310
341
  typer.echo(json.dumps(preferences_to_object(merged), indent=2))
311
342
  if write:
@@ -314,9 +345,72 @@ def preferences_detect(
314
345
  typer.echo(f"wrote missing keys to {path}", err=True)
315
346
 
316
347
 
348
+ @preferences_app.command("set-link")
349
+ def preferences_set_link(
350
+ family: str = typer.Argument(help="Identifier family, e.g. jira-id"),
351
+ template: str = typer.Argument(
352
+ help="URL template with {id}, e.g. https://host/browse/{id}",
353
+ ),
354
+ ) -> None:
355
+ """Create or overwrite a user-layer ref link template (explicit settings edit)."""
356
+ path = user_preferences_path()
357
+ set_user_ref_link(family, template, user_path=path)
358
+ typer.echo(json.dumps({"family": family, "template": template, "path": str(path)}, indent=2))
359
+
360
+
361
+ @preferences_app.command("clear-link")
362
+ def preferences_clear_link(
363
+ family: str = typer.Argument(help="Identifier family, e.g. jira-id"),
364
+ ) -> None:
365
+ """Remove a user-layer ref link template."""
366
+ path = user_preferences_path()
367
+ clear_user_ref_link(family, user_path=path)
368
+ typer.echo(json.dumps({"family": family, "cleared": True, "path": str(path)}, indent=2))
369
+
370
+
371
+ @preferences_app.command("set-tool")
372
+ def preferences_set_tool(
373
+ tool: str = typer.Argument(help="Tool name: jira or acli"),
374
+ executable: str = typer.Argument(help="Absolute path to the executable"),
375
+ ) -> None:
376
+ """Store an absolute path for jira-cli or acli (when not on PATH)."""
377
+ name = tool.strip().lower()
378
+ path = user_preferences_path()
379
+ if name in ("jira", "jira-cli"):
380
+ set_user_detection_executables(jira_executable=executable, user_path=path)
381
+ elif name == "acli":
382
+ set_user_detection_executables(acli_executable=executable, user_path=path)
383
+ else:
384
+ typer.echo("tool must be 'jira' or 'acli'", err=True)
385
+ raise typer.Exit(code=2)
386
+ typer.echo(json.dumps({"tool": name, "executable": executable, "path": str(path)}, indent=2))
387
+
388
+
389
+ @preferences_app.command("clear-tool")
390
+ def preferences_clear_tool(
391
+ tool: str = typer.Argument(help="Tool name: jira or acli"),
392
+ ) -> None:
393
+ """Remove a configured jira-cli or acli path (fall back to PATH)."""
394
+ name = tool.strip().lower()
395
+ path = user_preferences_path()
396
+ if name in ("jira", "jira-cli"):
397
+ set_user_detection_executables(clear_jira=True, user_path=path)
398
+ elif name == "acli":
399
+ set_user_detection_executables(clear_acli=True, user_path=path)
400
+ else:
401
+ typer.echo("tool must be 'jira' or 'acli'", err=True)
402
+ raise typer.Exit(code=2)
403
+ typer.echo(json.dumps({"tool": name, "cleared": True, "path": str(path)}, indent=2))
404
+
405
+
317
406
  def main() -> None:
318
407
  """Console script entry point."""
319
- app(args=prepare_argv(sys.argv[1:]), prog_name="git-elog")
408
+ # Normalize Windows separators so ``Path.name`` works when run under POSIX.
409
+ invoked = Path(sys.argv[0].replace("\\", "/")).name
410
+ if invoked.lower().endswith(".exe"):
411
+ invoked = invoked[:-4]
412
+ prog_name = invoked or "git-elog"
413
+ app(args=prepare_argv(sys.argv[1:]), prog_name=prog_name)
320
414
 
321
415
 
322
416
  if __name__ == "__main__":
@@ -49,9 +49,9 @@ class ProfileRules:
49
49
 
50
50
  name: str
51
51
  conventional: bool = True
52
- conventional_locations: tuple[str, ...] = ("title",)
52
+ conventional_locations: tuple[str, ...] = ("title", "trailer")
53
53
  references: bool = True
54
- reference_locations: tuple[str, ...] = ("refs",)
54
+ reference_locations: tuple[str, ...] = ("prefix", "suffix", "refs")
55
55
  identifier_family: str = "jira-id"
56
56
  signed_off_by: bool = True
57
57
  types: tuple[str, ...] = DEFAULT_TYPES
@@ -70,7 +70,7 @@ class AnalysisConfig:
70
70
  display: DisplayMode = "subject-precedence"
71
71
  preferences: Preferences = field(default_factory=empty_preferences)
72
72
  # Human history: fixed-width refs / minimum Type(Scope) columns.
73
- ref_id_width: int = 10
73
+ ref_id_width: int = 14
74
74
  type_width: int = 10
75
75
  show_author: bool = False
76
76
  show_date: bool = False
@@ -87,6 +87,7 @@ def _trailer_profile() -> ProfileRules:
87
87
  return ProfileRules(
88
88
  name="trailer",
89
89
  conventional_locations=("trailer",),
90
+ reference_locations=("refs",),
90
91
  )
91
92
 
92
93
 
@@ -97,8 +98,14 @@ KNOWN_PROFILES: dict[str, ProfileRules] = {
97
98
 
98
99
 
99
100
  def _profile_from_object(data: dict[str, Any], *, name: str = "custom") -> ProfileRules:
100
- conventional_locations = data.get("conventionalLocations", ["title"])
101
- reference_locations = data.get("referenceLocations", ["refs"])
101
+ conventional_locations = data.get(
102
+ "conventionalLocations",
103
+ ["title", "trailer"],
104
+ )
105
+ reference_locations = data.get(
106
+ "referenceLocations",
107
+ ["prefix", "suffix", "refs"],
108
+ )
102
109
  types = data.get("types", list(DEFAULT_TYPES))
103
110
  reference_keys = data.get("referenceKeys", ["Refs"])
104
111
  # Legacy profile link keys are ignored (CADR-0012); prefer preference files.