kijito-tools 0.2.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 (104) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +6 -0
  3. package/README.md +220 -0
  4. package/bin/cli.js +34 -0
  5. package/install.sh +161 -0
  6. package/package.json +50 -0
  7. package/providers/_shared/wake-core.mjs +358 -0
  8. package/providers/claude/CLAUDE.md.snippet +31 -0
  9. package/providers/claude/install.sh +186 -0
  10. package/providers/claude/scripts/arm-session.sh +66 -0
  11. package/providers/claude/scripts/claude-armed.sh +101 -0
  12. package/providers/claude/scripts/heartbeat-watchdog.sh +209 -0
  13. package/providers/claude/scripts/inbox-selftest.sh +277 -0
  14. package/providers/claude/scripts/kijito-persona-lib.sh +94 -0
  15. package/providers/claude/scripts/kijito-qa-pass.sh +12 -0
  16. package/providers/claude/scripts/lifecycle-lib.sh +167 -0
  17. package/providers/claude/scripts/myctx.sh +46 -0
  18. package/providers/claude/scripts/self-clear.sh +134 -0
  19. package/providers/claude/scripts/session-autosend.sh +85 -0
  20. package/providers/claude/scripts/session-catchup-hint.sh +330 -0
  21. package/providers/claude/scripts/statusline-context.sh +67 -0
  22. package/providers/claude/skills/kijito-qa-memory/SKILL.md +110 -0
  23. package/providers/claude/skills/kijito-recall/SKILL.md +53 -0
  24. package/providers/claude/skills/kijito-start/SKILL.md +122 -0
  25. package/providers/claude/wiring/README.md +93 -0
  26. package/providers/claude/wiring/kijito-heartbeat@.service +21 -0
  27. package/providers/codex/README.md +75 -0
  28. package/providers/codex/codex-kijito-parity-plan.md +265 -0
  29. package/providers/codex/docs-codex-setup.md +64 -0
  30. package/providers/codex/install.mjs +184 -0
  31. package/providers/codex/n0-capability-probe-protocol.md +472 -0
  32. package/providers/codex/n0-harness/README.md +57 -0
  33. package/providers/codex/n0-harness/cli.mjs +58 -0
  34. package/providers/codex/n0-harness/evidence-manifest.mjs +77 -0
  35. package/providers/codex/n0-harness/fixture.mjs +279 -0
  36. package/providers/codex/n0-harness/lib.mjs +141 -0
  37. package/providers/codex/n0-harness/manifest.mjs +36 -0
  38. package/providers/codex/n0-harness/oracle.mjs +464 -0
  39. package/providers/codex/n0-harness/parser.mjs +200 -0
  40. package/providers/codex/n0-harness/prompt.mjs +43 -0
  41. package/providers/codex/n0-harness/snapshot.mjs +120 -0
  42. package/providers/codex/n0-harness/specimen.mjs +37 -0
  43. package/providers/codex/notify/kijito-notify-count.mjs +117 -0
  44. package/providers/codex/notify/kijito-notify-count.test.mjs +76 -0
  45. package/providers/codex/plans/gate4-battery-log.md +184 -0
  46. package/providers/codex/plans/gate4-battery-protocol.md +43 -0
  47. package/providers/codex/plans/hive-user-first-plan-QA.md +34 -0
  48. package/providers/codex/plans/hive-user-first-plan.md +247 -0
  49. package/providers/codex/release-manifest.json +53 -0
  50. package/providers/codex/same-chat-continuation-plan-gate.md +235 -0
  51. package/providers/codex/same-chat-continuation-plan.md +458 -0
  52. package/providers/codex/skills/kijito-qa-memory/SKILL.md +193 -0
  53. package/providers/codex/skills/kijito-qa-memory/agents/openai.yaml +4 -0
  54. package/providers/codex/skills/kijito-recall/SKILL.md +53 -0
  55. package/providers/codex/skills/kijito-start/SKILL.md +173 -0
  56. package/providers/codex/skills/kijito-start/agents/openai.yaml +4 -0
  57. package/providers/codex/test/n0-cli-refusal.test.mjs +31 -0
  58. package/providers/codex/test/n0-counterexample-matrix.test.mjs +118 -0
  59. package/providers/codex/test/n0-guard-census-core.mjs +1372 -0
  60. package/providers/codex/test/n0-guard-census.json +35479 -0
  61. package/providers/codex/test/n0-guard-census.mjs +31 -0
  62. package/providers/codex/test/n0-guard-census.test.mjs +374 -0
  63. package/providers/codex/test/n0-guard-counterexamples.mjs +1068 -0
  64. package/providers/codex/test/n0-guard-independent-projection.mjs +70 -0
  65. package/providers/codex/test/n0-guard-independent-projection.test.mjs +53 -0
  66. package/providers/codex/test/n0-guard-manifest-author.mjs +424 -0
  67. package/providers/codex/test/n0-guard-mutation-operators.test.mjs +244 -0
  68. package/providers/codex/test/n0-guard-mutation-runner.mjs +599 -0
  69. package/providers/codex/test/n0-guard-remainder-classes.test.mjs +314 -0
  70. package/providers/codex/test/n0-guard-sanitizer-pairs.test.mjs +68 -0
  71. package/providers/codex/test/n0-guard-shared-context-predicates.test.mjs +84 -0
  72. package/providers/codex/test/n0-harness.test.mjs +553 -0
  73. package/providers/codex/test/n0-notable-regressions.test.mjs +70 -0
  74. package/providers/codex/test/n0-subject-boundary.mjs +97 -0
  75. package/providers/codex/test/n0-subject-boundary.test.mjs +72 -0
  76. package/providers/codex/test/same-chat-plan-preflight.sh +167 -0
  77. package/providers/codex/tools/refresh-manifest.mjs +81 -0
  78. package/providers/codex/wake-helper/TRANSPORT-NOTES.md +25 -0
  79. package/providers/codex/wake-helper/integration.test.mjs +285 -0
  80. package/providers/codex/wake-helper/kijito-wake-helper.mjs +488 -0
  81. package/providers/codex/wake-helper/kijito-wake-helper.test.mjs +128 -0
  82. package/providers/codex/wake-helper/mock-daemon.mjs +64 -0
  83. package/providers/codex/wake-helper/status-probe.mjs +45 -0
  84. package/providers/codex/wake-helper/ws-uds.mjs +153 -0
  85. package/providers/monitor/.github/workflows/publish-npm.yml +38 -0
  86. package/providers/monitor/.github/workflows/publish-pypi.yml +30 -0
  87. package/providers/monitor/CHANGELOG.md +465 -0
  88. package/providers/monitor/IMPORT-PROVENANCE.md +68 -0
  89. package/providers/monitor/LICENSE +202 -0
  90. package/providers/monitor/NOTICE +7 -0
  91. package/providers/monitor/OPAQUE-OUTPUT-ENFORCEMENT.md +45 -0
  92. package/providers/monitor/README.md +571 -0
  93. package/providers/monitor/RELEASING.md +125 -0
  94. package/providers/monitor/arm-hive-monitor.sh +13 -0
  95. package/providers/monitor/bin/cli.js +43 -0
  96. package/providers/monitor/com.kijito.inbox-monitor.plist.template +59 -0
  97. package/providers/monitor/docs/DESIGN.md +984 -0
  98. package/providers/monitor/kijito-inbox-monitor@.service.template +95 -0
  99. package/providers/monitor/kijito_inbox_monitor.py +3666 -0
  100. package/providers/monitor/package.json +35 -0
  101. package/providers/monitor/pyproject.toml +42 -0
  102. package/providers/monitor/scripts/mutation-check.py +399 -0
  103. package/providers/monitor/scripts/prepublish-gate.sh +284 -0
  104. package/providers/monitor/test_kijito_monitor.py +5609 -0
@@ -0,0 +1,284 @@
1
+ #!/bin/sh
2
+ # Pre-publish gates for a release. Run from anywhere in the repo; see RELEASING.md step 3.
3
+ #
4
+ # ./scripts/prepublish-gate.sh # canary, then the gates over the public surface
5
+ # ./scripts/prepublish-gate.sh --canary # prove the gate can still fail, then exit
6
+ #
7
+ # Four checks over the PUBLIC surface (this count is the exact thing that drifts - if you add a check,
8
+ # the loops below and RELEASING.md step 3 say it too):
9
+ # 1. typography - em/en dashes, curly quotes, ellipsis
10
+ # 2. memory-ids - internal Kijito memory ids ([[12345]] / [12345])
11
+ # 3. path-escapes - references to paths ABOVE the repository root (../)
12
+ # 4. private-detail - an operator absolute home path, or a private handoff sentinel
13
+ #
14
+ # Why check 3 exists (added 2026-07-29, after finding two live instances): RELEASING.md instructed the
15
+ # reader to run `../bin/producer-health.sh`, a helper that lives in the private workspace ALONGSIDE this
16
+ # repo and is not part of the package. A clone therefore could not run the gate its own release document
17
+ # mandated, and `../bin/...` would resolve to whatever happened to sit above the checkout - so the
18
+ # instruction was not merely broken, it was ambiguous in a way that depends on the reader's directory
19
+ # layout. The second instance was a stale "still open" note in docs/DESIGN.md pointing at a repo-external
20
+ # copy of itself. Both are the same class: THE PUBLIC SURFACE DESCRIBING THINGS THAT ARE NOT IN IT.
21
+ # NOTE ON SCOPE: this bans `../` across the whole surface, code included. There is no legitimate use
22
+ # today. If one ever arises - a JS require, say - that should be a deliberate, visible decision at this
23
+ # gate, not a quietly loosened regex.
24
+ #
25
+ # Properties this script exists to guarantee, each of which has failed in practice:
26
+ #
27
+ # * THE FILE LIST IS RE-DERIVED from `git ls-files`, never hardcoded. A hardcoded list has been
28
+ # wrong twice, and it silently omits exactly the files added since someone last edited it.
29
+ # pyproject.toml and package.json are included deliberately: they carry the PyPI/npm
30
+ # descriptions, which are IMMUTABLE per version and so cannot be fixed after publishing.
31
+ #
32
+ # * PATHS ARE NUL-DELIMITED into `xargs -0`. Writing this inline as
33
+ # FILES=$(git ls-files); grep -nE ... $FILES || echo clean
34
+ # does not word-split in zsh: grep gets one nonexistent filename, exits non-zero, and the
35
+ # `|| echo clean` prints success having inspected NOTHING. That false clean was observed here.
36
+ #
37
+ # * IT NAMES ITS SPECIMEN AND REFUSES A FOREIGN ONE (assay review, 2026-07-30). `git rev-parse
38
+ # --show-toplevel` resolves to whatever repository you happen to be standing in, so run from
39
+ # somewhere else this script cheerfully gated ANOTHER project and printed GATES CLEAN. A pass that
40
+ # does not say what it inspected is not evidence, and a gate that can pass over the wrong subject
41
+ # is an instance of the very wrong-specimen class it was written to catch.
42
+ #
43
+ # * THE CANARY RUNS IN BOTH DIRECTIONS, ONE SHAPE AT A TIME (assay review, 2026-07-30). A gate is
44
+ # only evidence if it can still fail. Two refinements over the original, both learned the hard way:
45
+ # (1) each bad SHAPE is checked ALONE, because a combined fixture proves only that SOME line
46
+ # matched - a pattern narrowed to the one historical string (`\.\./bin/`) would still have passed
47
+ # while missing every other escape; (2) the patterns must also stay SILENT on a good fixture that
48
+ # deliberately CONTAINS the near-miss shapes they must ignore, because a detector sabotaged to flag
49
+ # EVERYTHING passes any bad-input-only canary. A canary can only see the variable it moves.
50
+ set -eu
51
+
52
+ usage() {
53
+ cat <<'USAGE'
54
+ usage: prepublish-gate.sh [--canary]
55
+
56
+ (no args) run the canary, then the gates over the public surface
57
+ --canary run the canary only (prove the gate can still fail), then exit
58
+ -h, --help this text
59
+
60
+ exit: 0 clean | 1 a gate FAILED | 2 the gate could not be trusted (bad args, wrong repo, broken canary)
61
+ USAGE
62
+ }
63
+
64
+ # REAL ARGUMENT HANDLING (assay review, 2026-07-30). This used to accept and silently IGNORE anything,
65
+ # so `--canary` - the flag every other check in this fleet has - ran the FULL gate and reported clean.
66
+ # A tool that ignores an argument you meant is worse than one that rejects it: you believe you ran
67
+ # something you did not.
68
+ canary_only=0
69
+ for arg in "$@"; do
70
+ case "$arg" in
71
+ --canary) canary_only=1 ;;
72
+ -h|--help) usage; exit 0 ;;
73
+ *) printf 'ABORT: unknown argument: %s\n\n' "$arg" >&2; usage >&2; exit 2 ;;
74
+ esac
75
+ done
76
+
77
+ # THE PIPE WARNING, AT RUN TIME, ON STDERR (the 3b339ce fleet standard). A header comment is read by
78
+ # whoever EDITS the script, not by whoever RUNS it - and running it is when the mistake happens. stderr
79
+ # does not travel down the pipe, so this still lands on the terminal in the exact case being warned about.
80
+ if [ ! -t 1 ]; then
81
+ printf '%s\n' "note: stdout is not a terminal. If you piped this, \$? is the LAST pipeline stage's status, NOT this gate's answer. Use \${PIPESTATUS[0]} (bash) / \${pipestatus[1]} (zsh), or run it unpiped." >&2
82
+ fi
83
+
84
+ TYPOGRAPHY='—|–|[“”‘’]|…'
85
+ # MEMORY IDS — DECLARED NARROWING, not an exemption. Read the distinction, it is the whole point:
86
+ # an EXEMPTION skips a SURFACE entirely (nothing on it is checked, by anything); a NARROWING drops one
87
+ # ambiguous PATTERN CLASS while every other check still runs over that same surface.
88
+ #
89
+ # ⚠️ THIS USED TO ALSO MATCH THE BARE `[12345]` FORM, and that is why `test_*` and `.github/` were
90
+ # EXEMPT: `test_kijito_monitor.py` contains `self.assertEqual(em.new_ids, [1200])` and friends, which
91
+ # are Python integer-list literals and not memory ids. The bare form is indistinguishable from a list
92
+ # literal ANYWHERE, so the old shape forced a choice between false positives and skipping whole
93
+ # published files — and it chose the second, on a stated reason that turned out to be false (see the
94
+ # SCOPE block below). Narrowing the PATTERN removes the need to skip the SURFACE.
95
+ # ⇒ COST, STATED RATHER THAN BURIED: a bare `[12345]` memory id written in prose is no longer caught
96
+ # here. Every real leak this row was opened for used the `[[...]]` form (`[[22206]]` in
97
+ # scripts/mutation-check.py), and the private-detail check independently catches the sentinel.
98
+ #
99
+ # 🛑 THE SAME NARROWING IS DECLARED IN River's `gates/public-remote-leak-gate.sh`, AND THE DUPLICATION
100
+ # IS DELIBERATE. Sharing one literal between them would make that gate's INSTRUMENT depend on this
101
+ # repo — "a gate may reach outside its repo for its SUBJECT, never for its INSTRUMENT" — so a single
102
+ # edit here would silently redefine what the external check is able to see. Two independent copies
103
+ # that can drift are strictly safer than one copy that can be moved from the subject side.
104
+ MEMORY_IDS='\[\[[0-9]+\]\]'
105
+ PATH_ESCAPES='\.\./'
106
+ # PRIVATE DETAIL (M185): an operator's absolute home directory, or a private Kijito handoff sentinel,
107
+ # published to a public remote. Both were live here: the tracked LaunchAgent baked ONE operator's home
108
+ # into seven paths, and RELEASING.md named a private current-state sentinel.
109
+ # ★ MATCHED BY CONTENT, NEVER BY FILENAME - the M160 lesson. A filename-based rule ("check the plist")
110
+ # is a rule about the files you already thought of; the leak arrives in the file you did not.
111
+ PRIVATE_DETAIL='/(Users|home)/[A-Za-z0-9._-]+|_CURRENT_STATE_POINTER'
112
+
113
+ # THE SCOPE. EVERYTHING TRACKED IS INSPECTED, MINUS EXACTLY ONE ENUMERATED FILE:
114
+ # scripts/prepublish-gate.sh - THIS FILE, and the only one that HAS to be exempt: it necessarily
115
+ # CONTAINS the literals it bans, because they are its patterns and its own
116
+ # documentation. Any other way of sparing them would mean weakening the patterns.
117
+ #
118
+ # ⚠️ IT USED TO EXEMPT THE WHOLE `scripts/` PREFIX, and that was a silent scope hole (assay's review,
119
+ # 2026-07-30 - M184). Narrowing it to the one file that needs it immediately surfaced TWO REAL LEAKS
120
+ # that had been invisible the whole time: internal Kijito memory ids in `scripts/mutation-check.py`,
121
+ # published to a public remote. A prefix is a cheap way to SPELL an exemption and an expensive way to
122
+ # MEAN one - it silently covers every file added under that prefix later, forever.
123
+ #
124
+ # 🛑🛑 AND IT ALSO EXEMPTED `test_`, `tests/` AND `.github/` UNTIL 2026-08-01, ON A STATED REASON THAT
125
+ # WAS SIMPLY FALSE. The comment read "the suite is not published surface" and "CI configuration is not
126
+ # published surface". Measured on a fresh bare clone of the PUBLIC remote:
127
+ # .github/workflows/publish-npm.yml
128
+ # .github/workflows/publish-pypi.yml
129
+ # test_kijito_monitor.py
130
+ # All three are tracked; every `git clone` hands a stranger all three; all three were skipped by ALL
131
+ # FOUR checks. ⇒ ★ AN EXEMPTION CAN BE WRONG IN ITS PREMISE RATHER THAN ITS FORM. Enumerating those
132
+ # files by name and canarying every direction would have left a well-formed exemption resting on a
133
+ # claim that was not true. CHECK WHETHER THE STATED REASON IS TRUE BEFORE ARGUING ABOUT THE SHAPE.
134
+ #
135
+ # ⚖️ WHY THEY WERE DROPPED RATHER THAN RE-JUSTIFIED (assay's ruling, 2026-08-01). The tempting fix was
136
+ # to keep them with a TRUE reason - "inspected by River's gate_M185 instead", which is factually
137
+ # correct, since that checker covers the whole published surface minus one enumerated path. It was
138
+ # REJECTED on a property neither option named: TIMING. This gate is PREVENTIVE - it runs before
139
+ # content reaches the remote. gate_M185 is DETECTIVE - it measures the remote after the fact. A true
140
+ # justification that routes a surface to the detective control silently converts prevention into
141
+ # post-publish detection: a leak in tests or CI would publish FIRST and be found SECOND, which is the
142
+ # wrong order for the only control class whose entire value is running first.
143
+ # ⇒ The false-positive problem that motivated the original exemption is solved by the DECLARED
144
+ # NARROWING on MEMORY_IDS above, not by skipping files: typography, path-escapes and private-detail
145
+ # now run UNNARROWED over tests and CI, because nothing in those classes collides there.
146
+ EXEMPT='^(scripts/prepublish-gate\.sh$)'
147
+
148
+ fixture=$(mktemp)
149
+ trap 'rm -f "$fixture"' EXIT
150
+
151
+ # ---- canary, direction 1: every bad SHAPE must fire, each checked ALONE ---------------------------
152
+ bad_shape() { # label, pattern, shape
153
+ printf '%s\n' "$3" > "$fixture"
154
+ if ! grep -qE "$2" "$fixture"; then
155
+ printf 'ABORT: %s does NOT fire on a known-bad shape, so a clean result from it means nothing:\n %s\n' "$1" "$3" >&2
156
+ exit 2
157
+ fi
158
+ }
159
+ bad_shape typography "$TYPOGRAPHY" 'an em — dash'
160
+ bad_shape typography "$TYPOGRAPHY" 'an en – dash'
161
+ bad_shape typography "$TYPOGRAPHY" 'curly “double” quotes'
162
+ bad_shape typography "$TYPOGRAPHY" 'curly ‘single’ quotes'
163
+ bad_shape typography "$TYPOGRAPHY" 'an ellipsis… character'
164
+ bad_shape memory-ids "$MEMORY_IDS" 'a wikilink [[12345]] id'
165
+ bad_shape memory-ids "$MEMORY_IDS" 'the real historical leak [[22206]] shape'
166
+ bad_shape path-escapes "$PATH_ESCAPES" ' ../bin/some-helper.sh'
167
+ bad_shape path-escapes "$PATH_ESCAPES" 'require("../lib/thing")'
168
+ bad_shape path-escapes "$PATH_ESCAPES" '../../etc/somewhere'
169
+ bad_shape path-escapes "$PATH_ESCAPES" 'see ../docs/DESIGN.md'
170
+ bad_shape private-detail "$PRIVATE_DETAIL" ' <string>/Users/someone/Code/thing/x.py</string>'
171
+ bad_shape private-detail "$PRIVATE_DETAIL" 'a linux home /home/someone/.config/x'
172
+ bad_shape private-detail "$PRIVATE_DETAIL" 'see the pointer, sentinel SOMEONE_CURRENT_STATE_POINTER_V1'
173
+
174
+ # ---- canary, direction 2: the GOOD fixture CONTAINS what each pattern must ignore -----------------
175
+ # It is not enough for the good fixture to merely OMIT the bad shapes - a detector sabotaged to flag
176
+ # everything passes that. Every line below is a near miss the gate must stay silent about.
177
+ cat > "$fixture" <<'GOOD'
178
+ an ascii - hyphen, "straight" double and 'straight' single quotes, and three dots...
179
+ short ids [12] [123], a longer one [123456], plus [[notdigits]] and [abc]
180
+ the DECLARED NARROWING, pinned: bare [1234] and [12345] and self.assertEqual(em.new_ids, [1200])
181
+ ./relative/path, file..txt, three...dots, /absolute/path, parent/child
182
+ ~/.config/kijito-inbox-monitor/token and $HOME/.cache/x and __HOME__/.cache/y
183
+ /usr/local/bin, /opt/homebrew/bin/python3, /etc/hosts, /var/log/x
184
+ the operator current state pointer, written as lower-case prose
185
+ GOOD
186
+ for label_pattern in "typography:$TYPOGRAPHY" "memory-ids:$MEMORY_IDS" "path-escapes:$PATH_ESCAPES" "private-detail:$PRIVATE_DETAIL"; do
187
+ label=${label_pattern%%:*}
188
+ pattern=${label_pattern#*:}
189
+ if grep -qE "$pattern" "$fixture"; then
190
+ printf 'ABORT: %s fires on known-GOOD input, so it would condemn a release regardless of content:\n' "$label" >&2
191
+ grep -nE "$pattern" "$fixture" >&2
192
+ exit 2
193
+ fi
194
+ done
195
+ # ---- canary, direction 3: the SCOPE, not the patterns (M184) --------------------------------------
196
+ # A pattern that works proves nothing about a file the gate never opens. Two of the three checks above
197
+ # were passing for years while `scripts/` was wholly exempt and leaking. So: assert that a leak planted
198
+ # under scripts/ WOULD be inspected, and that the one file spared is spared by an exemption this script
199
+ # DECLARES. Silence about a skipped path is the failure, not the skipping.
200
+ # ⚠️ THE FORMERLY-EXEMPT PREFIXES ARE PLANTED HERE DELIBERATELY. Until 2026-08-01 not one direction
201
+ # in this loop touched `test_`, `tests/` or `.github/` - so the three prefixes whose justification was
202
+ # false were also the three no control ever exercised. A canary that never touches the contested
203
+ # surface is the defect this whole finding started from, so each of them now has a planted path.
204
+ for planted in "scripts/leak-canary.md" "scripts/mutation-check.py" "docs/DESIGN.md" "README.md" \
205
+ "test_kijito_monitor.py" "tests/test_something.py" ".github/workflows/publish-npm.yml"; do
206
+ if printf '%s\n' "$planted" | grep -qE "$EXEMPT"; then
207
+ printf 'ABORT: %s would be EXEMPT from every check, so a leak planted there is invisible.\n' "$planted" >&2
208
+ exit 2
209
+ fi
210
+ done
211
+ if ! printf '%s\n' "scripts/prepublish-gate.sh" | grep -qE "$EXEMPT"; then
212
+ printf 'ABORT: the gate no longer exempts ITSELF, so it will condemn its own pattern literals.\n' >&2
213
+ exit 2
214
+ fi
215
+ echo "canary: every pattern fires on each known-bad shape and stays silent on known-good input"
216
+ echo "canary: scope holds - a leak planted under scripts/, tests/, test_* or .github/ is inspected;"
217
+ echo " only scripts/prepublish-gate.sh is exempt, and it is declared"
218
+
219
+ if [ "$canary_only" -eq 1 ]; then
220
+ echo "CANARY CLEAN - the gate can still tell good from bad. (No surface inspected: --canary.)"
221
+ exit 0
222
+ fi
223
+
224
+ # ---- name the specimen, and refuse a foreign one --------------------------------------------------
225
+ root=$(git rev-parse --show-toplevel 2>/dev/null) || {
226
+ echo "ABORT: not inside a git repository, so there is no surface to gate." >&2; exit 2; }
227
+ cd "$root"
228
+ if [ ! -f kijito_inbox_monitor.py ] || [ ! -f RELEASING.md ]; then
229
+ printf 'ABORT: %s is not the kijito-inbox-monitor repository (no kijito_inbox_monitor.py + RELEASING.md).\n' "$root" >&2
230
+ printf ' Refusing to report a clean gate over a repository this gate was not written for.\n' >&2
231
+ exit 2
232
+ fi
233
+ echo "specimen: $root"
234
+
235
+ # --cached AND --others, because `git ls-files` alone reproduces one level up the very defect the
236
+ # comment at the top of this file warns about. That comment says a HARDCODED list "silently omits
237
+ # exactly the files added since someone last edited it", and re-deriving from git fixed that - for
238
+ # TRACKED files. An untracked new file is still omitted, and the gate still printed "GATES CLEAN".
239
+ # ⇒ MEASURED 2026-08-06: a new, unstaged kijito-inbox-monitor@.service.template gave
240
+ # "surface: 17 file(s) -> GATES CLEAN" while that file was never opened; `git add` made it 18.
241
+ # ★ A BRAND-NEW FILE IS THE LIKELIEST ONE TO CARRY A LEAK - nobody has ever reviewed it - and it was
242
+ # precisely the one the gate could not see. Silence read as approval.
243
+ # --exclude-standard keeps .gitignore'd build artifacts (build/, *.egg-info/, __pycache__/) out, so
244
+ # this widens the surface to unreviewed SOURCE only, never to generated noise.
245
+ tracked=$(git ls-files -z --cached --others --exclude-standard | tr '\0' '\n' | sort -u)
246
+ files=$(printf '%s\n' "$tracked" | grep -vE "$EXEMPT" || true)
247
+ excluded=$(printf '%s\n' "$tracked" | grep -E "$EXEMPT" || true)
248
+ if [ -z "$files" ]; then
249
+ echo "ABORT: re-derived file list is EMPTY - refusing to report a clean gate over nothing." >&2
250
+ exit 2
251
+ fi
252
+ count=$(printf '%s\n' "$files" | wc -l | tr -d ' ')
253
+ echo "surface: $count file(s)"
254
+ # DECLARE THE EXEMPTIONS. Silence about what was skipped is the failure mode; a reader must be able to
255
+ # tell "clean" from "not looked at" without reading this script.
256
+ if [ -n "$excluded" ]; then
257
+ printf 'exempt: %s file(s) NOT inspected -' "$(printf '%s\n' "$excluded" | wc -l | tr -d ' ')"
258
+ printf ' %s' $(printf '%s\n' "$excluded")
259
+ printf '\n'
260
+ else
261
+ echo "exempt: none"
262
+ fi
263
+ # DECLARE THE NARROWING AT RUN TIME, beside the exemptions. It is a real reduction in what this gate
264
+ # can see, and a reduction that lives only in a source comment is one the reader never learns about
265
+ # at the moment they are deciding whether to trust a clean result.
266
+ echo "narrowed: memory-ids matches the unambiguous [[nnnnn]] form only - the bare [nnnnn] form is"
267
+ echo " indistinguishable from an integer list literal (e.g. new_ids, [1200]) and is NOT checked"
268
+
269
+ status=0
270
+ for label_pattern in "typography:$TYPOGRAPHY" "memory-ids:$MEMORY_IDS" "path-escapes:$PATH_ESCAPES" "private-detail:$PRIVATE_DETAIL"; do
271
+ label=${label_pattern%%:*}
272
+ pattern=${label_pattern#*:}
273
+ hits=$(printf '%s\n' "$files" | tr '\n' '\0' | xargs -0 grep -nE "$pattern" || true)
274
+ if [ -n "$hits" ]; then
275
+ echo "FAIL $label:"
276
+ printf '%s\n' "$hits"
277
+ status=1
278
+ else
279
+ echo "pass $label"
280
+ fi
281
+ done
282
+
283
+ [ "$status" -eq 0 ] && echo "GATES CLEAN over $count file(s) in $root" || echo "GATES FAILED - do not tag"
284
+ exit "$status"