@staix/agent-mem 0.6.0 → 0.8.0

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 (71) hide show
  1. package/.claude-plugin/marketplace.json +13 -0
  2. package/.claude-plugin/plugin.json +12 -2
  3. package/README.md +143 -18
  4. package/package.json +1 -1
  5. package/src/cli.ts +537 -39
  6. package/src/commands-table.ts +60 -16
  7. package/src/hook-spool.ts +24 -2
  8. package/src/hooks.ts +9 -5
  9. package/src/index.ts +20 -0
  10. package/src/ingest.ts +3 -2
  11. package/src/mcp-memory.ts +168 -33
  12. package/src/mcp.ts +43 -7
  13. package/src/memory/aliases.ts +4 -0
  14. package/src/memory/bridge.ts +8 -2
  15. package/src/memory/candidate-pending.ts +119 -0
  16. package/src/memory/candidates.ts +228 -47
  17. package/src/memory/capacity-split.ts +76 -0
  18. package/src/memory/capacity.ts +139 -11
  19. package/src/memory/capture-input.ts +8 -3
  20. package/src/memory/capture.ts +30 -5
  21. package/src/memory/catalog.ts +43 -10
  22. package/src/memory/commands.ts +98 -44
  23. package/src/memory/config.ts +167 -0
  24. package/src/memory/consolidate.ts +78 -0
  25. package/src/memory/consolidation-clusters.ts +56 -0
  26. package/src/memory/consolidation-model.ts +97 -0
  27. package/src/memory/cross-project.ts +75 -0
  28. package/src/memory/curated-read.ts +29 -0
  29. package/src/memory/decay-review.ts +136 -0
  30. package/src/memory/doctor.ts +415 -0
  31. package/src/memory/editor.ts +58 -0
  32. package/src/memory/eval-fixture.ts +145 -0
  33. package/src/memory/eval-set-ko.json +90 -0
  34. package/src/memory/eval-set.json +443 -0
  35. package/src/memory/eval.ts +152 -0
  36. package/src/memory/evidence.ts +76 -0
  37. package/src/memory/external-import.ts +267 -0
  38. package/src/memory/gc.ts +97 -0
  39. package/src/memory/generation-index.ts +22 -17
  40. package/src/memory/generation-sync.ts +3 -0
  41. package/src/memory/goal-promotion.ts +71 -0
  42. package/src/memory/home.ts +15 -4
  43. package/src/memory/intent-brief.ts +89 -0
  44. package/src/memory/lifecycle-log.ts +54 -0
  45. package/src/memory/lifecycle.ts +61 -0
  46. package/src/memory/metrics.ts +2 -2
  47. package/src/memory/note-add.ts +100 -0
  48. package/src/memory/path-glob.ts +94 -0
  49. package/src/memory/plan-guard.ts +47 -0
  50. package/src/memory/platform.ts +12 -1
  51. package/src/memory/policy.ts +44 -13
  52. package/src/memory/private-spans.ts +32 -0
  53. package/src/memory/project-export.ts +157 -0
  54. package/src/memory/project-import.ts +147 -0
  55. package/src/memory/recall-staleness.ts +56 -0
  56. package/src/memory/recall.ts +74 -0
  57. package/src/memory/repo-scope.ts +404 -0
  58. package/src/memory/shard-sync-format.ts +116 -0
  59. package/src/memory/shard-sync.ts +421 -0
  60. package/src/memory/store.ts +344 -35
  61. package/src/memory/sync-cadence.ts +149 -0
  62. package/src/memory/sync-screen.ts +86 -0
  63. package/src/memory/sync.ts +91 -11
  64. package/src/memory/usage.ts +35 -0
  65. package/src/memory/use-telemetry.ts +51 -0
  66. package/src/memory/work-lists.ts +10 -4
  67. package/src/session-brief-worker.ts +3 -2
  68. package/src/session-brief.ts +74 -15
  69. package/src/setup-owned.ts +120 -0
  70. package/src/setup-toml.ts +296 -0
  71. package/src/setup.ts +327 -0
@@ -0,0 +1,90 @@
1
+ {
2
+ "schema": "amem.eval/v1",
3
+ "entries": [
4
+ { "key": "ko-loan-extend", "kind": "decision", "title": "대출 연장은 두 번까지 허용한다", "text": "회원은 도서 대출을 두 번까지 연장할 수 있고, 예약이 걸린 책은 연장되지 않는다.", "about": ["path:src/loan/extend.ts"] },
5
+ { "key": "ko-overdue-fee", "kind": "decision", "title": "연체료는 하루에 백 원씩 부과된다", "text": "반납 기한을 넘기면 연체료가 하루에 백 원씩 쌓이고, 한 권당 상한은 오천 원이다.", "about": ["path:src/loan/fee.ts"] },
6
+ { "key": "ko-night-return", "kind": "finding", "title": "야간 반납함은 자정에 비운다", "text": "야간 반납함에 넣은 책은 자정에 직원이 꺼내어 다음 날 아침 반납으로 처리한다.", "about": ["path:src/loan/return.ts"] },
7
+ { "key": "ko-card-reissue", "kind": "decision", "title": "분실한 회원증은 창구에서 재발급한다", "text": "회원증을 잃어버리면 신분 확인 후 창구에서 재발급하며, 수수료는 천 원이다.", "about": ["path:src/member/card.ts"] },
8
+ { "key": "ko-ebook-quota", "kind": "contract", "title": "전자책은 동시에 다섯 권까지 빌린다", "text": "전자책은 한 회원이 동시에 다섯 권까지 빌릴 수 있고, 대출 기간은 열흘이다.", "about": ["path:src/ebook/quota.ts"] },
9
+ { "key": "ko-room-booking", "kind": "decision", "title": "스터디룸 예약은 사흘 전부터 열린다", "text": "스터디룸은 이용일 사흘 전 오전 아홉 시부터 예약할 수 있고, 한 번에 최대 두 시간이다.", "about": ["path:src/room/booking.ts"] },
10
+ { "key": "ko-donation-review", "kind": "decision", "title": "기증 도서는 분기마다 심사한다", "text": "기증받은 도서는 분기마다 한 번 모아서 심사하고, 상태가 나쁜 책은 장서에 넣지 않는다.", "about": ["path:src/donation/review.ts"] },
11
+ { "key": "ko-closing-day", "kind": "finding", "title": "휴관일은 매월 첫째 월요일이다", "text": "도서관은 매월 첫째 월요일에 휴관하며, 그날은 홈페이지 공지에 반드시 올린다.", "about": ["path:src/site/notice.ts"] },
12
+
13
+ { "key": "ko-business-plan", "kind": "decision", "title": "확장 사업계획서는 분기 말까지 제출한다", "text": "도서관 확장 사업계획서는 분기 말까지 운영위원회에 제출하고, 예산표를 함께 붙인다.", "about": ["path:docs/plan.md"] },
14
+ { "key": "ko-privacy-policy", "kind": "contract", "title": "개인 정보 처리 방침은 연 1회 갱신한다", "text": "개인 정보 처리 방침은 해마다 한 번 검토하여 갱신하고, 바뀐 내용은 회원에게 알린다.", "about": ["path:docs/privacy.md"] },
15
+ { "key": "ko-reading-room-hours", "kind": "decision", "title": "열람실 이용 시간은 아침 아홉 시부터", "text": "열람실은 아침 아홉 시에 열고, 평일에는 저녁 여덟 시에 닫는다.", "about": ["path:src/room/hours.ts"] },
16
+ { "key": "ko-library-system", "kind": "finding", "title": "도서 관리 시스템은 일요일에 점검한다", "text": "도서 관리 시스템은 매주 일요일 새벽에 정기 점검을 하고, 그동안 대출은 멈춘다.", "about": ["path:ops/maintenance.md"] },
17
+ { "key": "ko-minutes", "kind": "decision", "title": "회의록은 회의 다음 날까지 공유한다", "text": "회의록은 회의가 끝난 다음 날 정오까지 공유 폴더에 올리고, 결정 사항을 맨 위에 적는다.", "about": ["path:docs/minutes.md"] },
18
+ { "key": "ko-catalog-search", "kind": "finding", "title": "자료 검색 화면에서 저자 이름으로 찾는다", "text": "자료 검색 화면의 저자 칸에는 이름만 넣고, 성과 이름 사이는 띄어 쓴다.", "about": ["path:src/catalog/search.ts"] },
19
+ { "key": "ko-bibliography", "kind": "contract", "title": "참고 문헌 목록은 가나다순으로 정렬한다", "text": "논문과 보고서의 참고 문헌 목록은 저자 이름의 가나다순으로 정렬한다.", "about": ["path:docs/style.md"] },
20
+
21
+ { "key": "ko-pr-review", "kind": "decision", "title": "PR 리뷰는 영업일 기준 이틀 안에 끝낸다", "text": "PR 리뷰는 영업일 기준 이틀 안에 끝내고, 이틀이 넘으면 담당자를 바꾼다.", "about": ["path:docs/review.md"] },
22
+ { "key": "ko-vault-note", "kind": "decision", "title": "vault 노트는 금요일에 정리한다", "text": "vault 노트는 매주 금요일 오후에 한 번 훑어서 중복을 합치고 오래된 것은 보관함으로 옮긴다.", "about": ["path:docs/vault.md"] },
23
+ { "key": "ko-staging-smoke", "kind": "contract", "title": "배포 전에 staging 서버에서 스모크 테스트를 돌린다", "text": "배포 전에 staging 서버에서 스모크 테스트를 돌리고, 하나라도 실패하면 배포를 미룬다.", "about": ["path:ops/deploy.md"] },
24
+ { "key": "ko-slack-alert", "kind": "decision", "title": "Slack 알림은 근무 시간에만 보낸다", "text": "Slack 알림은 평일 오전 아홉 시부터 오후 여섯 시 사이에만 보내고, 밤에는 모아서 다음 날 보낸다.", "about": ["path:ops/alerts.md"] },
25
+ { "key": "ko-dark-mode", "kind": "finding", "title": "dark 모드 설정은 사용자 프로필에 저장된다", "text": "dark 모드 설정은 기기가 아니라 사용자 프로필에 저장되어 다른 기기에서도 그대로 보인다.", "about": ["path:src/ui/theme.ts"] },
26
+ { "key": "ko-branch-name", "kind": "decision", "title": "브랜치 이름은 feature 접두사를 쓴다", "text": "새 기능 브랜치 이름은 feature 접두사와 이슈 번호로 짓고, 수정은 fix 접두사를 쓴다.", "about": ["path:docs/git.md"] },
27
+ { "key": "ko-readme-language", "kind": "decision", "title": "docs 폴더의 README는 한국어로 쓴다", "text": "docs 폴더의 README 파일은 한국어로 쓰고, 코드 식별자와 명령어는 영어 그대로 둔다.", "about": ["path:docs/README.md"] },
28
+
29
+ { "key": "ko-ci-fail", "kind": "contract", "title": "CI가 실패하면 머지를 막는다", "text": "CI가 실패하면 머지를 막고 담당자에게 바로 알리며, 재실행은 두 번까지만 허용한다.", "about": ["path:ops/ci.md"] },
30
+ { "key": "ko-api-key", "kind": "decision", "title": "API 키는 90일마다 교체한다", "text": "외부 서비스의 API 키는 90일마다 교체하고, 교체한 날짜를 운영 기록에 남긴다.", "about": ["path:ops/secrets.md"] },
31
+ { "key": "ko-sla", "kind": "finding", "title": "SLA 위반이 쌓이면 공급사와 회의를 연다", "text": "공급사의 SLA 위반이 두 번 쌓이면 공급사와 개선 회의를 열고, 세 번째에는 계약 갱신을 보류한다.", "about": ["path:docs/vendor.md"] },
32
+ { "key": "ko-ocr-check", "kind": "decision", "title": "OCR 결과는 사람이 확인한 뒤 색인한다", "text": "스캔 문서의 OCR 결과는 사람이 한 번 더 확인한 뒤에 색인하고, 오인식이 잦은 글자는 목록으로 관리한다.", "about": ["path:src/scan/ocr.ts"] },
33
+ { "key": "ko-vpn", "kind": "contract", "title": "VPN 접속은 사내망 밖에서만 허용한다", "text": "VPN 접속은 사내망 밖에서만 허용하고, 사내망 안에서 접속을 시도하면 기록만 남기고 거절한다.", "about": ["path:ops/network.md"] },
34
+ { "key": "ko-kpi-report", "kind": "decision", "title": "KPI 보고는 분기마다 PDF로 낸다", "text": "KPI 보고서는 분기마다 PDF 한 부로 만들어 운영위원회에 내고, 원자료는 표 파일로 따로 보관한다.", "about": ["path:docs/kpi.md"] },
35
+ { "key": "ko-db-migration", "kind": "contract", "title": "DB 마이그레이션은 읽기 전용 시간대에 실행한다", "text": "DB 마이그레이션은 새벽 읽기 전용 시간대에만 실행하고, 되돌리는 스크립트를 함께 올린다.", "about": ["path:ops/migration.md"] },
36
+
37
+ { "key": "ko-lights-out-base", "kind": "decision", "title": "열람실 소등은 오후 아홉 시이다", "text": "열람실 조명은 평소 오후 아홉 시에 끄고, 마지막 퇴실 확인은 직원이 한다.", "about": ["path:src/room/lights.ts"] },
38
+ { "key": "ko-newbooks-base", "kind": "decision", "title": "신간 코너는 매월 첫 주에 교체한다", "text": "신간 코너의 책은 매월 첫째 주에 새로 들어온 책으로 교체한다.", "about": ["path:src/shelf/newbooks.ts"] },
39
+ { "key": "ko-reminder-base", "kind": "decision", "title": "반납 알림은 기한 이틀 전에 문자로 보낸다", "text": "반납 알림은 기한 이틀 전에 휴대전화 문자로 보내고, 수신을 거부한 회원은 제외한다.", "about": ["path:src/loan/reminder.ts"] },
40
+ { "key": "ko-copy-base", "kind": "decision", "title": "복사기 요금은 장당 오십 원이다", "text": "복사기 요금은 장당 오십 원이고, 컬러 복사는 장당 이백 원이다.", "about": ["path:src/service/copier.ts"] },
41
+ { "key": "ko-lights-out-exam", "kind": "decision", "tier": "short", "title": "시험 기간에는 열람실 소등을 늦춘다", "text": "시험 기간에는 열람실 소등을 오후 열한 시로 늦춘다.", "about": ["path:src/room/lights.ts"] },
42
+ { "key": "ko-newbooks-delay", "kind": "finding", "tier": "short", "title": "이번 달 신간 코너 교체는 둘째 주로 미룬다", "text": "이번 달은 서가 공사 때문에 신간 코너 교체를 둘째 주로 미룬다.", "about": ["path:src/shelf/newbooks.ts"] },
43
+ { "key": "ko-reminder-mail", "kind": "finding", "tier": "short", "title": "문자 업체 점검 중에는 반납 알림을 이메일로 보낸다", "text": "문자 발송 업체가 점검하는 동안에는 반납 알림을 이메일로 대신 보낸다.", "about": ["path:src/loan/reminder.ts"] },
44
+ { "key": "ko-copy-discount", "kind": "finding", "tier": "short", "title": "복사기 요금을 이번 주만 낮춘다", "text": "복사기 요금을 이번 주에만 장당 삼십 원으로 낮춘다.", "about": ["path:src/service/copier.ts"] }
45
+ ],
46
+ "questions": [
47
+ { "tag": "particle", "q": "대출을 연장할 수 있는 횟수는", "expect": ["ko-loan-extend"] },
48
+ { "tag": "particle", "q": "예약된 책은 기간을 늘릴 수 없나요", "expect": ["ko-loan-extend"] },
49
+ { "tag": "particle", "q": "연체료는 얼마씩 쌓이나", "expect": ["ko-overdue-fee"] },
50
+ { "tag": "particle", "q": "연체료의 한 권당 상한", "expect": ["ko-overdue-fee"] },
51
+ { "tag": "particle", "q": "밤에 넣은 책은 언제 처리되나", "expect": ["ko-night-return"] },
52
+ { "tag": "particle", "q": "회원증이 분실되면 어디서 재발급하나", "expect": ["ko-card-reissue"] },
53
+ { "tag": "particle", "q": "전자책을 한 번에 몇 권까지 빌리나", "expect": ["ko-ebook-quota"] },
54
+ { "tag": "particle", "q": "스터디룸은 언제부터 예약할 수 있나", "expect": ["ko-room-booking"] },
55
+ { "tag": "particle", "q": "기증받은 도서를 심사하는 주기", "expect": ["ko-donation-review"] },
56
+ { "tag": "particle", "q": "매월 첫째 월요일에는 휴관인가", "expect": ["ko-closing-day"] },
57
+
58
+ { "tag": "spacing", "q": "사업 계획서", "expect": ["ko-business-plan"] },
59
+ { "tag": "spacing", "q": "개인정보처리방침", "expect": ["ko-privacy-policy"] },
60
+ { "tag": "spacing", "q": "열람 실 개방", "expect": ["ko-reading-room-hours"] },
61
+ { "tag": "spacing", "q": "도서관리시스템", "expect": ["ko-library-system"] },
62
+ { "tag": "spacing", "q": "회의 록", "expect": ["ko-minutes"] },
63
+ { "tag": "spacing", "q": "자료검색 저자", "expect": ["ko-catalog-search"] },
64
+ { "tag": "spacing", "q": "참고문헌 정렬", "expect": ["ko-bibliography"] },
65
+
66
+ { "tag": "mixed", "q": "PR 리뷰 기한", "expect": ["ko-pr-review"] },
67
+ { "tag": "mixed", "q": "vault 노트 정리 요일", "expect": ["ko-vault-note"] },
68
+ { "tag": "mixed", "q": "staging 서버 스모크 테스트", "expect": ["ko-staging-smoke"] },
69
+ { "tag": "mixed", "q": "Slack 알림은 언제 보내나", "expect": ["ko-slack-alert"] },
70
+ { "tag": "mixed", "q": "dark 모드 설정은 어디에 저장되나", "expect": ["ko-dark-mode"] },
71
+ { "tag": "mixed", "q": "feature 브랜치 이름 규칙", "expect": ["ko-branch-name"] },
72
+ { "tag": "mixed", "q": "README는 어느 언어로 쓰나", "expect": ["ko-readme-language"] },
73
+
74
+ { "tag": "abbreviation", "q": "CI가 빨갛게 뜨면 병합해도 되나", "expect": ["ko-ci-fail"] },
75
+ { "tag": "abbreviation", "q": "API 키를 교체하는 주기", "expect": ["ko-api-key"] },
76
+ { "tag": "abbreviation", "q": "SLA를 어긴 공급사와 회의", "expect": ["ko-sla"] },
77
+ { "tag": "abbreviation", "q": "스캔한 OCR 글자를 믿어도 되나", "expect": ["ko-ocr-check"] },
78
+ { "tag": "abbreviation", "q": "VPN은 어디에서 접속하나", "expect": ["ko-vpn"] },
79
+ { "tag": "abbreviation", "q": "KPI 보고서 형식", "expect": ["ko-kpi-report"] },
80
+ { "tag": "abbreviation", "q": "DB 마이그레이션은 언제 실행하나", "expect": ["ko-db-migration"] },
81
+
82
+ { "tag": "tier", "q": "시험 기간 열람실 소등 시각", "expect": ["ko-lights-out-exam"] },
83
+ { "tag": "tier", "q": "평소 열람실 소등 시각", "expect": ["ko-lights-out-base"] },
84
+ { "tag": "tier", "q": "이번 달 신간 코너 교체는 언제", "expect": ["ko-newbooks-delay"] },
85
+ { "tag": "tier", "q": "신간 코너를 교체하는 주기", "expect": ["ko-newbooks-base"] },
86
+ { "tag": "tier", "q": "문자 업체 점검 중 반납 알림 방법", "expect": ["ko-reminder-mail"] },
87
+ { "tag": "tier", "q": "이번 주 복사기 요금", "expect": ["ko-copy-discount"] },
88
+ { "tag": "tier", "q": "복사기 요금은 장당 얼마", "expect": ["ko-copy-base"] }
89
+ ]
90
+ }
@@ -0,0 +1,443 @@
1
+ {
2
+ "schema": "amem.eval/v1",
3
+ "entries": [
4
+ {
5
+ "key": "shift-handover-old",
6
+ "kind": "decision",
7
+ "tier": "long",
8
+ "title": "Shift handover happens at the gate office",
9
+ "text": "The harbour shift handover is done at the gate office with a signed paper log.",
10
+ "about": [
11
+ "path:src/shift/handover.ts"
12
+ ]
13
+ },
14
+ {
15
+ "key": "harbor-retry",
16
+ "kind": "decision",
17
+ "title": "Harbor client retries idempotent calls three times",
18
+ "text": "The Harbor ferry client retries an idempotent call three times with exponential backoff, and never retries a booking write.",
19
+ "about": [
20
+ "path:src/ferry/client.ts"
21
+ ]
22
+ },
23
+ {
24
+ "key": "harbor-timeout",
25
+ "kind": "finding",
26
+ "title": "Dock sensor requests time out after eight seconds",
27
+ "text": "A dock sensor request that takes longer than eight seconds is abandoned and reported as stale, not as an error.",
28
+ "about": [
29
+ "path:src/dock/sensor.ts"
30
+ ],
31
+ "tier": "short"
32
+ },
33
+ {
34
+ "key": "tide-cache",
35
+ "kind": "decision",
36
+ "title": "Tide tables are cached for one day",
37
+ "text": "Tide tables come from the almanac service and are cached on disk for one day, because the almanac rate-limits per hour.",
38
+ "about": [
39
+ "path:src/tide/cache.ts"
40
+ ]
41
+ },
42
+ {
43
+ "key": "tide-rounding",
44
+ "kind": "finding",
45
+ "title": "Tide heights round half to even",
46
+ "text": "Tide heights are rounded half to even before display, so 2.5 metres shows as 2 and 3.5 as 4.",
47
+ "about": [
48
+ "path:src/tide/format.ts"
49
+ ]
50
+ },
51
+ {
52
+ "key": "crane-lock",
53
+ "kind": "contract",
54
+ "title": "Only one crane operator lock per berth",
55
+ "text": "A berth holds at most one crane operator lock. A second request waits, it does not steal the lock, and the lock expires after ninety seconds without a heartbeat.",
56
+ "about": [
57
+ "path:src/crane/lock.ts"
58
+ ]
59
+ },
60
+ {
61
+ "key": "crane-heartbeat",
62
+ "kind": "finding",
63
+ "title": "Crane heartbeat drift on the night shift",
64
+ "text": "The night shift crane heartbeat drifted by up to four seconds because the controller clock was not synced; the lock expiry still held.",
65
+ "about": [
66
+ "path:src/crane/heartbeat.ts"
67
+ ],
68
+ "tier": "short"
69
+ },
70
+ {
71
+ "key": "manifest-schema",
72
+ "kind": "contract",
73
+ "title": "Cargo manifest rows carry a container prefix",
74
+ "text": "Every cargo manifest row carries a four-letter container prefix and a seven digit serial. A row without both is rejected at import.",
75
+ "about": [
76
+ "path:src/cargo/manifest.ts"
77
+ ]
78
+ },
79
+ {
80
+ "key": "manifest-import",
81
+ "kind": "fail",
82
+ "title": "Importing manifests in one transaction blew the memory budget",
83
+ "text": "Importing a whole cargo manifest in one transaction used more than a gigabyte. Stream it in batches of five hundred rows instead.",
84
+ "about": [
85
+ "path:src/cargo/import.ts"
86
+ ]
87
+ },
88
+ {
89
+ "key": "pilot-roster",
90
+ "kind": "decision",
91
+ "title": "Pilot roster is published every Friday",
92
+ "text": "The pilot roster for the coming week is published on Friday at noon local time and is frozen afterwards except for sick cover.",
93
+ "about": [
94
+ "path:src/pilot/roster.ts"
95
+ ]
96
+ },
97
+ {
98
+ "key": "pilot-fatigue",
99
+ "kind": "contract",
100
+ "title": "A pilot may not exceed twelve hours on duty",
101
+ "text": "The scheduler refuses an assignment that takes a pilot past twelve hours on duty in any rolling twenty-four hours.",
102
+ "about": [
103
+ "path:src/pilot/fatigue.ts"
104
+ ]
105
+ },
106
+ {
107
+ "key": "fuel-ledger",
108
+ "kind": "decision",
109
+ "title": "Fuel is recorded in litres, converted once",
110
+ "text": "Bunker fuel is recorded in litres. Conversion from tonnes happens once at the edge, using the density on the delivery note.",
111
+ "about": [
112
+ "path:src/fuel/ledger.ts"
113
+ ]
114
+ },
115
+ {
116
+ "key": "fuel-density",
117
+ "kind": "finding",
118
+ "title": "Delivery note density is sometimes missing",
119
+ "text": "About one delivery note in forty has no fuel density. The ledger falls back to the supplier default and flags the row for review.",
120
+ "about": [
121
+ "path:src/fuel/density.ts"
122
+ ],
123
+ "tier": "short"
124
+ },
125
+ {
126
+ "key": "berth-clash",
127
+ "kind": "fail",
128
+ "title": "Two vessels were booked into the same berth window",
129
+ "text": "Two vessels were booked into one berth window because the overlap check compared local times across a daylight saving change. Compare in UTC.",
130
+ "about": [
131
+ "path:src/berth/overlap.ts"
132
+ ]
133
+ },
134
+ {
135
+ "key": "berth-priority",
136
+ "kind": "decision",
137
+ "title": "Passenger ferries outrank freight at the quay",
138
+ "text": "When a quay berth is contested the passenger ferry keeps it and the freight vessel moves to the outer berth.",
139
+ "about": [
140
+ "path:src/berth/priority.ts"
141
+ ]
142
+ },
143
+ {
144
+ "key": "alarm-quiet",
145
+ "kind": "decision",
146
+ "title": "Fog alarms are quiet between midnight and five",
147
+ "text": "The fog horn test alarm is silenced between midnight and five in the morning, the real fog alarm is never silenced.",
148
+ "about": [
149
+ "path:src/alarm/fog.ts"
150
+ ],
151
+ "tier": "short"
152
+ },
153
+ {
154
+ "key": "alarm-escalate",
155
+ "kind": "contract",
156
+ "title": "Unacknowledged alarms escalate after ten minutes",
157
+ "text": "An alarm nobody acknowledges within ten minutes escalates to the harbour master's phone, then to the duty manager after another ten.",
158
+ "about": [
159
+ "path:src/alarm/escalate.ts"
160
+ ]
161
+ },
162
+ {
163
+ "key": "radio-channel",
164
+ "kind": "finding",
165
+ "title": "Channel sixteen is monitored, not used for routine traffic",
166
+ "text": "Channel sixteen is monitored for distress calls only. Routine traffic moves to the working channel named in the berth assignment.",
167
+ "about": [
168
+ "path:src/radio/channels.ts"
169
+ ]
170
+ },
171
+ {
172
+ "key": "radio-log",
173
+ "kind": "decision",
174
+ "title": "Radio calls are logged with a vessel callsign",
175
+ "text": "Every radio call is logged with the vessel callsign and a UTC timestamp, never with the master's name.",
176
+ "about": [
177
+ "path:src/radio/log.ts"
178
+ ]
179
+ },
180
+ {
181
+ "key": "gate-badge",
182
+ "kind": "contract",
183
+ "title": "Gate badges expire at end of shift",
184
+ "text": "A gate badge is valid until the end of the holder's shift and a contractor badge never outlives the contract end date.",
185
+ "about": [
186
+ "path:src/gate/badge.ts"
187
+ ]
188
+ },
189
+ {
190
+ "key": "gate-camera",
191
+ "kind": "finding",
192
+ "title": "Gate camera plate reader fails in heavy rain",
193
+ "text": "The gate camera plate reader misreads plates in heavy rain; the guard confirms by hand when confidence is below ninety percent.",
194
+ "about": [
195
+ "path:src/gate/camera.ts"
196
+ ],
197
+ "tier": "short"
198
+ },
199
+ {
200
+ "key": "report-monthly",
201
+ "kind": "decision",
202
+ "title": "Throughput report closes on the third working day",
203
+ "text": "The monthly throughput report closes on the third working day, late manifests are counted in the next month.",
204
+ "about": [
205
+ "path:src/report/monthly.ts"
206
+ ]
207
+ },
208
+ {
209
+ "key": "report-units",
210
+ "kind": "finding",
211
+ "title": "Throughput is reported in twenty-foot equivalents",
212
+ "text": "Throughput is reported in twenty-foot equivalent units, a forty-foot container counts as two.",
213
+ "about": [
214
+ "path:src/report/units.ts"
215
+ ]
216
+ },
217
+ {
218
+ "key": "backup-nightly",
219
+ "kind": "decision",
220
+ "title": "Database backup runs at two in the morning",
221
+ "text": "The operations database is backed up at two in the morning and the copy is verified by a test restore every Sunday.",
222
+ "about": [
223
+ "path:ops/backup.sh"
224
+ ]
225
+ },
226
+ {
227
+ "key": "backup-restore",
228
+ "kind": "fail",
229
+ "title": "A backup restored without the audit table",
230
+ "text": "A restore test passed while the audit table was missing, because the verification only counted the cargo tables. Count every table.",
231
+ "about": [
232
+ "path:ops/restore-check.sh"
233
+ ]
234
+ },
235
+ {
236
+ "key": "shift-handover-new",
237
+ "kind": "finding",
238
+ "tier": "short",
239
+ "title": "Shift handover happens at the gate office",
240
+ "text": "The harbour shift handover is done at the gate office, and the signed paper log is being replaced by a tablet.",
241
+ "about": [
242
+ "path:src/shift/handover.ts"
243
+ ]
244
+ }
245
+ ],
246
+ "questions": [
247
+ {
248
+ "q": "how many times does the ferry client retry",
249
+ "expect": [
250
+ "harbor-retry"
251
+ ]
252
+ },
253
+ {
254
+ "q": "retries backoff idempotent",
255
+ "expect": [
256
+ "harbor-retry"
257
+ ]
258
+ },
259
+ {
260
+ "q": "dock sensor timeout",
261
+ "expect": [
262
+ "harbor-timeout"
263
+ ]
264
+ },
265
+ {
266
+ "q": "why are tide tables cached",
267
+ "expect": [
268
+ "tide-cache"
269
+ ]
270
+ },
271
+ {
272
+ "q": "almanac rate limit",
273
+ "expect": [
274
+ "tide-cache"
275
+ ]
276
+ },
277
+ {
278
+ "q": "round half to even tide height",
279
+ "expect": [
280
+ "tide-rounding"
281
+ ]
282
+ },
283
+ {
284
+ "q": "crane lock expiry without heartbeat",
285
+ "expect": [
286
+ "crane-lock",
287
+ "crane-heartbeat"
288
+ ]
289
+ },
290
+ {
291
+ "q": "night shift clock drift",
292
+ "expect": [
293
+ "crane-heartbeat"
294
+ ]
295
+ },
296
+ {
297
+ "q": "container prefix serial number manifest",
298
+ "expect": [
299
+ "manifest-schema"
300
+ ]
301
+ },
302
+ {
303
+ "q": "manifest import memory too large",
304
+ "expect": [
305
+ "manifest-import"
306
+ ]
307
+ },
308
+ {
309
+ "q": "when is the pilot roster published",
310
+ "expect": [
311
+ "pilot-roster"
312
+ ]
313
+ },
314
+ {
315
+ "q": "pilot twelve hours on duty",
316
+ "expect": [
317
+ "pilot-fatigue"
318
+ ]
319
+ },
320
+ {
321
+ "q": "fuel density litres tonnes",
322
+ "expect": [
323
+ "fuel-ledger",
324
+ "fuel-density"
325
+ ]
326
+ },
327
+ {
328
+ "q": "missing density on delivery note",
329
+ "expect": [
330
+ "fuel-density"
331
+ ]
332
+ },
333
+ {
334
+ "q": "berth double booking daylight saving",
335
+ "expect": [
336
+ "berth-clash"
337
+ ]
338
+ },
339
+ {
340
+ "q": "who wins a contested quay berth",
341
+ "expect": [
342
+ "berth-priority"
343
+ ]
344
+ },
345
+ {
346
+ "q": "fog horn alarm silenced at night",
347
+ "expect": [
348
+ "alarm-quiet"
349
+ ]
350
+ },
351
+ {
352
+ "q": "unacknowledged alarm escalates",
353
+ "expect": [
354
+ "alarm-escalate"
355
+ ]
356
+ },
357
+ {
358
+ "q": "distress channel sixteen",
359
+ "expect": [
360
+ "radio-channel"
361
+ ]
362
+ },
363
+ {
364
+ "q": "radio call log callsign",
365
+ "expect": [
366
+ "radio-log"
367
+ ]
368
+ },
369
+ {
370
+ "q": "contractor badge expiry",
371
+ "expect": [
372
+ "gate-badge"
373
+ ]
374
+ },
375
+ {
376
+ "q": "plate reader rain",
377
+ "expect": [
378
+ "gate-camera"
379
+ ]
380
+ },
381
+ {
382
+ "q": "monthly throughput report close date",
383
+ "expect": [
384
+ "report-monthly"
385
+ ]
386
+ },
387
+ {
388
+ "q": "forty foot container counts as two",
389
+ "expect": [
390
+ "report-units"
391
+ ]
392
+ },
393
+ {
394
+ "q": "database backup schedule test restore",
395
+ "expect": [
396
+ "backup-nightly",
397
+ "backup-restore"
398
+ ]
399
+ },
400
+ {
401
+ "q": "restore check missed the audit table",
402
+ "expect": [
403
+ "backup-restore"
404
+ ]
405
+ },
406
+ {
407
+ "q": "what stops two tugs sharing a mooring",
408
+ "expect": [
409
+ "berth-clash"
410
+ ]
411
+ },
412
+ {
413
+ "q": "staff badge validity period",
414
+ "expect": [
415
+ "gate-badge"
416
+ ]
417
+ },
418
+ {
419
+ "q": "vessel arrival prediction model",
420
+ "expect": [
421
+ "tide-cache"
422
+ ]
423
+ },
424
+ {
425
+ "q": "who approves overtime",
426
+ "expect": [
427
+ "pilot-fatigue"
428
+ ]
429
+ },
430
+ {
431
+ "q": "where is the shift handover done",
432
+ "expect": [
433
+ "shift-handover-new"
434
+ ]
435
+ },
436
+ {
437
+ "q": "paper log shift handover gate office",
438
+ "expect": [
439
+ "shift-handover-old"
440
+ ]
441
+ }
442
+ ]
443
+ }