@cairnkeep/cli 1.0.2

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 (129) hide show
  1. package/AGENTS.md +45 -0
  2. package/CONTRIBUTING.md +49 -0
  3. package/LICENSE +201 -0
  4. package/README.md +149 -0
  5. package/SECURITY.md +37 -0
  6. package/bin/cairn +73 -0
  7. package/claude/agents/code-reviewer.md +174 -0
  8. package/claude/agents/security-investigator.md +112 -0
  9. package/claude/agents/security-target-selector.md +90 -0
  10. package/claude/agents/security-validator.md +97 -0
  11. package/claude/agents/wiki-ingester.md +108 -0
  12. package/claude/agents/wiki-lint-auditor.md +94 -0
  13. package/claude/agents/wiki-query-analyst.md +84 -0
  14. package/claude/commands/context-explore.md +40 -0
  15. package/claude/commands/graphify.md +150 -0
  16. package/claude/commands/memory-review.md +85 -0
  17. package/claude/commands/memory-sync.md +71 -0
  18. package/claude/commands/recall.md +32 -0
  19. package/claude/commands/remember.md +43 -0
  20. package/claude/commands/repo-review.md +191 -0
  21. package/claude/commands/security-audit.md +174 -0
  22. package/claude/commands/wiki-ingest.md +112 -0
  23. package/claude/commands/wiki-lint.md +86 -0
  24. package/claude/commands/wiki-query.md +92 -0
  25. package/claude/hooks/context-explore-pretask.sh +93 -0
  26. package/claude/hooks/memory-capture.sh +59 -0
  27. package/claude/hooks/memory-recall.sh +84 -0
  28. package/claude/hooks/memory-wakeup.sh +45 -0
  29. package/docs/building-an-overlay.md +117 -0
  30. package/docs/domain-knowledge.md +86 -0
  31. package/docs/git-providers.md +36 -0
  32. package/docs/operating.md +366 -0
  33. package/mcp-memory-server/dist/embeddings.js +122 -0
  34. package/mcp-memory-server/dist/explore-cache.js +97 -0
  35. package/mcp-memory-server/dist/index.js +1256 -0
  36. package/mcp-memory-server/package.json +36 -0
  37. package/opencode/agents/code-reviewer.md +173 -0
  38. package/opencode/agents/security-investigator.md +111 -0
  39. package/opencode/agents/security-target-selector.md +89 -0
  40. package/opencode/agents/security-validator.md +96 -0
  41. package/opencode/agents/wiki-ingester.md +107 -0
  42. package/opencode/agents/wiki-lint-auditor.md +93 -0
  43. package/opencode/agents/wiki-query-analyst.md +83 -0
  44. package/opencode/command/code-review.md +199 -0
  45. package/opencode/command/context-explore.md +42 -0
  46. package/opencode/command/graphify.md +153 -0
  47. package/opencode/command/memory-review.md +44 -0
  48. package/opencode/command/memory-sync.md +94 -0
  49. package/opencode/command/recall.md +38 -0
  50. package/opencode/command/remember.md +46 -0
  51. package/opencode/command/security-audit.md +80 -0
  52. package/opencode/command/wiki-ingest.md +55 -0
  53. package/opencode/command/wiki-lint.md +51 -0
  54. package/opencode/command/wiki-query.md +50 -0
  55. package/opencode/plugins/memory-capture.ts +188 -0
  56. package/opencode/plugins/memory-recall.ts +132 -0
  57. package/opencode/plugins/memory-wakeup.ts +102 -0
  58. package/opencode/templates/security-finding-register.yaml.template +51 -0
  59. package/opencode/templates/security-issue.md.template +43 -0
  60. package/opencode/templates/security-policy.md.template +29 -0
  61. package/opencode/templates/security-report.md.template +43 -0
  62. package/opencode/templates/wiki-contradictions.md.template +31 -0
  63. package/opencode/templates/wiki-index.md.template +40 -0
  64. package/opencode/templates/wiki-lint-report.md.template +38 -0
  65. package/opencode/templates/wiki-log.md.template +11 -0
  66. package/opencode/templates/wiki-policy.md.template +47 -0
  67. package/opencode/templates/wiki-query-answer.md.template +26 -0
  68. package/opencode/templates/wiki-source-summary.md.template +33 -0
  69. package/opencode/workflows/memory-review-workflow.md +66 -0
  70. package/opencode/workflows/security-audit-workflow.md +197 -0
  71. package/opencode/workflows/wiki-ingest-workflow.md +149 -0
  72. package/opencode/workflows/wiki-lint-workflow.md +128 -0
  73. package/opencode/workflows/wiki-query-workflow.md +122 -0
  74. package/package.json +56 -0
  75. package/scripts/bootstrap.sh +99 -0
  76. package/scripts/doctor.sh +91 -0
  77. package/scripts/install-audit-timer.sh +68 -0
  78. package/scripts/lib/assert-tool-event.mjs +50 -0
  79. package/scripts/memory-store.sh +71 -0
  80. package/scripts/memory-wiki-audit.sh +156 -0
  81. package/scripts/sync-claude-assets.sh +182 -0
  82. package/scripts/sync-opencode-explore-assets.sh +187 -0
  83. package/scripts/sync-opencode-graphify-assets.sh +152 -0
  84. package/scripts/sync-opencode-memory-assets.sh +160 -0
  85. package/scripts/sync-opencode-plugin-assets.sh +163 -0
  86. package/scripts/sync-opencode-security-assets.sh +195 -0
  87. package/scripts/sync-opencode-wiki-assets.sh +250 -0
  88. package/scripts/test-audit-timer.sh +29 -0
  89. package/scripts/test-bootstrap-untracked.sh +41 -0
  90. package/scripts/test-cli-dispatch.sh +21 -0
  91. package/scripts/test-doctor.sh +32 -0
  92. package/scripts/test-launcher-hooks.sh +66 -0
  93. package/scripts/test-memory-store.sh +44 -0
  94. package/scripts/test-remember-recall-assertions.sh +70 -0
  95. package/scripts/transcript-to-text.mjs +53 -0
  96. package/scripts/verify-docs-parity.sh +112 -0
  97. package/scripts/verify-explore-maturation.sh +288 -0
  98. package/scripts/verify-fastcontext-reliability.sh +624 -0
  99. package/scripts/verify-no-private-references.sh +145 -0
  100. package/scripts/verify-opencode-live-parity.sh +864 -0
  101. package/scripts/verify-routing-seam.sh +169 -0
  102. package/scripts/verify-token-savings-ab.sh +557 -0
  103. package/templates/PROJECT-INSTRUCTIONS.md.template +42 -0
  104. package/templates/alignment-gap-register.yaml.template +56 -0
  105. package/templates/alignment-policy.md.template +28 -0
  106. package/templates/alignment-pr-template.md.template +18 -0
  107. package/templates/alignment-report.md.template +56 -0
  108. package/templates/env.example.template +25 -0
  109. package/templates/graph-policy.md.template +37 -0
  110. package/templates/graphs-gitignore.template +3 -0
  111. package/templates/memory-wiki-audit.service.template +6 -0
  112. package/templates/memory-wiki-audit.timer.template +9 -0
  113. package/templates/memory.json.template +4 -0
  114. package/templates/planning-config.json.template +6 -0
  115. package/templates/project-brief.md.template +40 -0
  116. package/templates/security-finding-register.yaml.template +51 -0
  117. package/templates/security-issue.md.template +43 -0
  118. package/templates/security-policy.md.template +29 -0
  119. package/templates/security-report.md.template +43 -0
  120. package/templates/start-claude.sh.template +46 -0
  121. package/templates/start-opencode.sh.template +41 -0
  122. package/templates/structured-change-canvas.md.template +63 -0
  123. package/templates/wiki-contradictions.md.template +31 -0
  124. package/templates/wiki-index.md.template +40 -0
  125. package/templates/wiki-lint-report.md.template +38 -0
  126. package/templates/wiki-log.md.template +11 -0
  127. package/templates/wiki-policy.md.template +47 -0
  128. package/templates/wiki-query-answer.md.template +26 -0
  129. package/templates/wiki-source-summary.md.template +33 -0
@@ -0,0 +1,557 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # CTX-07 A/B token-savings harness (Phase 9 Plan 01/02).
5
+ #
6
+ # Computes the native-exploration ("before") vs context_explore ("after")
7
+ # byte/char delta deterministically against a real repo (default: cairnkeep's
8
+ # own repo, D-02). The byte/char delta is the tokenizer-free ground-truth
9
+ # anchor; the additionally-reported token number is a provider-neutral
10
+ # chars/4 estimate (D-01a) - never a vendor tokenizer. No endpoint host/IP,
11
+ # model alias, or vendor tokenizer is ever committed or written to the
12
+ # evidence log; only loopback/placeholder defaults are committed and real
13
+ # values (if any) come from the ambient shell / gitignored .ai/.env
14
+ # (DEC-no-private-references).
15
+ #
16
+ # Stages:
17
+ # --self-test offline Nyquist backstop (byte-delta arithmetic, chars/4
18
+ # estimate, D-03 gate both directions, renderCitations-shape
19
+ # reproduction) - no network, no backend. (Task 3)
20
+ # --native offline "before" side - reads only the local git repo via a
21
+ # fixed grep -> window recipe (D-02, reproducible/auditable).
22
+ # (Task 2)
23
+ # --explore live "after" side (D-04, operator-gated) - shells out to the
24
+ # token_miser explore binary and counts the renderCitations
25
+ # citation-text shape, not the full Evidence JSON. Fails loud
26
+ # (never a silent skip) when the binary is absent, not
27
+ # executable, times out, or exits non-zero. (Task 2)
28
+ # --full runs both sides over the same query set, computes the
29
+ # per-query byte/char/token-estimate delta, and applies the
30
+ # D-03 net-savings gate to the aggregate byte totals. (Task 2)
31
+ #
32
+ # Mirrors scripts/verify-fastcontext-reliability.sh's committed, env-driven,
33
+ # loopback-only, staged-with-generous-timeouts harness discipline (Phase 6
34
+ # D-01/D-02, re-runnability).
35
+ #
36
+ # Native-exploration recipe (D-02 - reproducible/auditable baseline; each
37
+ # pattern is run via `git grep -nE` against tracked files only):
38
+ #
39
+ # # | query | git grep -nE pattern | window | hit cap
40
+ # 1 | scope path containment implemented in this repo | containment | +/-25 | 12
41
+ # 2 | AgentFS project scope resolved in this repo | AgentFS.*scope | +/-25 | 12
42
+ # 3 | git-provider abstraction configured in this repo | git-provider | +/-25 | 12
43
+ # 4 | OpenCode memory-wakeup plugin lives in this repo | memory-wakeup | +/-25 | 12
44
+ # 5 | asset-sync scripts render the infra-root placeholder value | infraRoot | +/-25 | 12
45
+
46
+ usage() {
47
+ cat <<'EOF'
48
+ Usage: verify-token-savings-ab.sh --self-test
49
+ verify-token-savings-ab.sh --native [--repo <path>]
50
+ verify-token-savings-ab.sh --explore [--repo <path>]
51
+ verify-token-savings-ab.sh --full [--repo <path>]
52
+ verify-token-savings-ab.sh -h|--help
53
+
54
+ CTX-07 A/B token-savings harness: measures the byte/char delta between a
55
+ fixed native-exploration recipe ("before") and context_explore's compact
56
+ citation output ("after") against a real repo, and reports a provider-
57
+ neutral chars/4 token estimate alongside the mandatory byte-delta anchor.
58
+
59
+ Options:
60
+ --self-test
61
+ Offline: proves the byte/char-count arithmetic, the chars/4 token
62
+ estimate, the D-03 net-savings gate (both PASS and loud-FAIL
63
+ directions), and the explore-side renderCitations-shape reproduction
64
+ against canned fixtures. No network, no backend, no live binary. This
65
+ is the automated Nyquist backstop for the operator-gated live run.
66
+ --native
67
+ Offline: reads only the local git repo. For each query, runs a fixed
68
+ `git grep` pattern, takes up to 12 hits, extracts a +/-25 line window
69
+ around each hit, dedupes identical spans, and reports the total
70
+ byte/char count per query. No backend required.
71
+ --explore
72
+ Live, operator-gated (D-04): shells out to the token_miser explore
73
+ binary for each query and counts the bytes/chars of the exact
74
+ renderCitations `path:start-end` citation text (never the full
75
+ Evidence JSON). Fails loud and exits non-zero if the binary is
76
+ absent/not executable, times out, or exits non-zero - never a silent
77
+ skip.
78
+ --full
79
+ Runs --native then --explore over the same query set, computes the
80
+ per-query byte/char/token-estimate delta and the median byte-savings
81
+ percentage, and applies the D-03 net-savings gate to the aggregate
82
+ byte totals. Exits 0 only when the gate passes.
83
+ -h, --help
84
+ Show this help text.
85
+
86
+ Environment:
87
+ CAIRN_EXPLORE_BINARY Absolute path to the token_miser binary (the
88
+ same env var context_explore reads). Defaults to
89
+ empty; falls back to `token_miser` on PATH.
90
+ Never echoed - only a presence indicator is
91
+ logged.
92
+ CAIRN_EXPLORE_REPO_ROOT Repo root to measure against. Defaults to this
93
+ repo (cairnkeep's own repo, D-02). Overridable
94
+ per-invocation via --repo. Never echoed - only a
95
+ presence indicator is logged.
96
+ AB_EVIDENCE_LOG Path to the evidence log. Defaults to a *.log
97
+ file under this phase's directory, already
98
+ excluded from commits by the repo .gitignore.
99
+
100
+ --repo <path>
101
+ Override the repo root for this invocation (takes precedence over
102
+ CAIRN_EXPLORE_REPO_ROOT). Useful for pointing at a fresh `cairn
103
+ bootstrap` scratch project (D-02).
104
+ EOF
105
+ }
106
+
107
+ ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
108
+
109
+ CAIRN_EXPLORE_BINARY="${CAIRN_EXPLORE_BINARY:-}"
110
+ CAIRN_EXPLORE_REPO_ROOT="${CAIRN_EXPLORE_REPO_ROOT:-$ROOT_DIR}"
111
+ AB_EVIDENCE_LOG="${AB_EVIDENCE_LOG:-$ROOT_DIR/.planning/phases/09-live-verification-a-b-token-savings/09-AB.log}"
112
+
113
+ NATIVE_WINDOW=25
114
+ NATIVE_HIT_CAP=12
115
+
116
+ # 5 curated cairnkeep exploration prompts (same corpus Phase 6 probed),
117
+ # paired 1:1 with a fixed `git grep -nE` pattern per D-02 - see the recipe
118
+ # table in the header comment above for the reproducible/auditable baseline.
119
+ QUERIES=(
120
+ "Where is scope path containment implemented in this repo?"
121
+ "Where is the AgentFS project scope resolved in this repo?"
122
+ "Where is the git-provider abstraction configured in this repo?"
123
+ "Where does the OpenCode memory-wakeup plugin live in this repo?"
124
+ "Where do the asset-sync scripts render the infra-root placeholder value?"
125
+ )
126
+ PATTERNS=(
127
+ "containment"
128
+ "AgentFS.*scope"
129
+ "git-provider"
130
+ "memory-wakeup"
131
+ "infraRoot"
132
+ )
133
+
134
+ NATIVE_QUERY_BYTES=()
135
+ NATIVE_QUERY_CHARS=()
136
+ EXPLORE_QUERY_BYTES=()
137
+ EXPLORE_QUERY_CHARS=()
138
+
139
+ # log_repo_presence(override): logs only whether --repo/env overrode the
140
+ # default repo root - never the path value (mirrors log_endpoint_presence()
141
+ # in verify-fastcontext-reliability.sh, T-09-01).
142
+ log_repo_presence() {
143
+ local override="${1:-}"
144
+ local overridden="no"
145
+ if [[ -n "$override" || "$CAIRN_EXPLORE_REPO_ROOT" != "$ROOT_DIR" ]]; then
146
+ overridden="yes"
147
+ fi
148
+ echo "[env] repo root overridden from default (cairnkeep's own repo): $overridden" >&2
149
+ }
150
+
151
+ # append_evidence(line): appends one line to the evidence log, creating the
152
+ # parent directory if needed. Callers never pass an endpoint/model/secret
153
+ # value through this helper (T-09-02).
154
+ append_evidence() {
155
+ local line="$1"
156
+ mkdir -p "$(dirname "$AB_EVIDENCE_LOG")"
157
+ echo "$line" >> "$AB_EVIDENCE_LOG"
158
+ }
159
+
160
+ # count_bytes / count_chars: read from stdin, print an integer with no
161
+ # surrounding whitespace.
162
+ count_bytes() {
163
+ wc -c | tr -d '[:space:]'
164
+ }
165
+
166
+ count_chars() {
167
+ wc -m | tr -d '[:space:]'
168
+ }
169
+
170
+ # est_tokens(chars): provider-neutral chars/4 estimate (N=4 - the
171
+ # industry-standard ~4-chars/token English approximation). Dependency-free
172
+ # and provider-neutral; the byte delta remains the mandatory anchor (D-01a).
173
+ # Do NOT vendor a real tokenizer here.
174
+ est_tokens() {
175
+ local chars="$1"
176
+ echo $(( chars / 4 ))
177
+ }
178
+
179
+ # net_savings_gate(native_bytes, explore_bytes): the D-03 milestone-close
180
+ # gate. PASS (0) only when native strictly exceeds explore; otherwise a loud
181
+ # FAIL (1) with a documented-finding line - a regression must never pass
182
+ # silently.
183
+ net_savings_gate() {
184
+ local native_bytes="$1" explore_bytes="$2"
185
+ local delta=$((native_bytes - explore_bytes))
186
+ if [[ "$delta" -gt 0 ]]; then
187
+ return 0
188
+ fi
189
+ echo "FATAL: net savings not positive (native=$native_bytes explore=$explore_bytes delta=$delta) - documented finding, not a silent pass (D-03)" >&2
190
+ append_evidence "[gate] FATAL: net savings not positive (native=$native_bytes explore=$explore_bytes delta=$delta) - documented finding, not a silent pass (D-03)"
191
+ return 1
192
+ }
193
+
194
+ # median(...): integer median of the given numbers (nearest-integer average
195
+ # for an even count).
196
+ median() {
197
+ local -a sorted
198
+ sorted=($(printf '%s\n' "$@" | sort -n))
199
+ local n=${#sorted[@]}
200
+ local mid=$((n / 2))
201
+ if (( n % 2 == 1 )); then
202
+ echo "${sorted[$mid]}"
203
+ else
204
+ echo $(( (sorted[mid-1] + sorted[mid]) / 2 ))
205
+ fi
206
+ }
207
+
208
+ # resolve_explore_binary(): CAIRN_EXPLORE_BINARY (the real env var
209
+ # context_explore reads) or a `token_miser` on PATH. Prints the resolved
210
+ # path on success; callers must never echo it further (T-09-01).
211
+ resolve_explore_binary() {
212
+ if [[ -n "$CAIRN_EXPLORE_BINARY" ]]; then
213
+ echo "$CAIRN_EXPLORE_BINARY"
214
+ return 0
215
+ fi
216
+ if command -v token_miser >/dev/null 2>&1; then
217
+ command -v token_miser
218
+ return 0
219
+ fi
220
+ return 1
221
+ }
222
+
223
+ # render_citation_text(evidence_json): reproduces mcp-memory-server's
224
+ # renderCitations() shape byte-for-byte (index.ts ~L604-615) - the compact
225
+ # `path:start-end` newline-joined text, or the exact empty-citations note
226
+ # with turns/tool_calls interpolated. Fails (non-zero) on malformed JSON.
227
+ render_citation_text() {
228
+ local evidence_json="$1"
229
+ jq -e -r '
230
+ if (.citations | length) == 0 then
231
+ "(no citations found; turns=" + (.stats.turns|tostring) + ", tool_calls=" + (.stats.tool_calls|tostring) + ")"
232
+ else
233
+ [.citations[] | "\(.path):\(.start_line)-\(.end_line)"] | join("\n")
234
+ end
235
+ ' <<< "$evidence_json"
236
+ }
237
+
238
+ # run_native(repo): the "before" side, fully offline (D-02). For each query,
239
+ # runs the fixed git-grep pattern, takes up to NATIVE_HIT_CAP hits, extracts
240
+ # a +/-NATIVE_WINDOW line window per hit, dedupes identical (path,window)
241
+ # spans, and totals the bytes/chars. Populates NATIVE_QUERY_BYTES/CHARS for
242
+ # --full.
243
+ run_native() {
244
+ local repo="$1"
245
+ local qi pattern hits path line _rest start end key
246
+ local -A seen_spans
247
+ local hit_count query_bytes query_chars span_bytes span_chars
248
+
249
+ NATIVE_QUERY_BYTES=()
250
+ NATIVE_QUERY_CHARS=()
251
+
252
+ for qi in "${!QUERIES[@]}"; do
253
+ pattern="${PATTERNS[$qi]}"
254
+ seen_spans=()
255
+ hit_count=0
256
+ query_bytes=0
257
+ query_chars=0
258
+
259
+ hits=$(git -C "$repo" grep -nE "$pattern" -- . 2>/dev/null | head -n "$NATIVE_HIT_CAP" || true)
260
+
261
+ while IFS=: read -r path line _rest; do
262
+ [[ -z "$path" ]] && continue
263
+ hit_count=$((hit_count + 1))
264
+ start=$(( line - NATIVE_WINDOW ))
265
+ [[ "$start" -lt 1 ]] && start=1
266
+ end=$(( line + NATIVE_WINDOW ))
267
+ key="${path}:${start}-${end}"
268
+ [[ -n "${seen_spans[$key]:-}" ]] && continue
269
+ seen_spans[$key]=1
270
+
271
+ span_bytes=$(sed -n "${start},${end}p" "$repo/$path" | count_bytes)
272
+ span_chars=$(sed -n "${start},${end}p" "$repo/$path" | count_chars)
273
+ query_bytes=$((query_bytes + span_bytes))
274
+ query_chars=$((query_chars + span_chars))
275
+ done <<< "$hits"
276
+
277
+ NATIVE_QUERY_BYTES[$qi]=$query_bytes
278
+ NATIVE_QUERY_CHARS[$qi]=$query_chars
279
+ echo "[native] query=$((qi + 1)) hits=$hit_count bytes=$query_bytes chars=$query_chars"
280
+ append_evidence "[native] query=$((qi + 1)) hits=$hit_count bytes=$query_bytes chars=$query_chars"
281
+ done
282
+ }
283
+
284
+ # run_explore(repo): the "after" side, live and operator-gated (D-04). Fails
285
+ # loud (non-zero, documented gap) on a missing/non-executable binary, a
286
+ # timeout, a non-zero exit, or malformed Evidence JSON - never a silent
287
+ # skip. Counts only the renderCitations citation-text shape (never the raw
288
+ # Evidence JSON). Populates EXPLORE_QUERY_BYTES/CHARS for --full.
289
+ run_explore() {
290
+ local repo="$1"
291
+ local binary qi query raw_out exit_code citation_text bytes chars n_citations
292
+
293
+ if ! binary=$(resolve_explore_binary); then
294
+ echo "FATAL: no exploration binary found (CAIRN_EXPLORE_BINARY unset and token_miser absent from PATH) - documented gap, not a silent skip (D-04)" >&2
295
+ append_evidence "[explore] FATAL: no exploration binary found - documented gap (D-04)"
296
+ return 1
297
+ fi
298
+ if [[ ! -x "$binary" ]] && ! command -v "$binary" >/dev/null 2>&1; then
299
+ echo "FATAL: resolved exploration binary is not executable - documented gap (D-04)" >&2
300
+ append_evidence "[explore] FATAL: resolved exploration binary is not executable - documented gap (D-04)"
301
+ return 1
302
+ fi
303
+
304
+ EXPLORE_QUERY_BYTES=()
305
+ EXPLORE_QUERY_CHARS=()
306
+
307
+ for qi in "${!QUERIES[@]}"; do
308
+ query="${QUERIES[$qi]}"
309
+ exit_code=0
310
+ raw_out=$(NO_COLOR=1 timeout 120 "$binary" explore --query "$query" --repo-root "$repo" 2>/dev/null) || exit_code=$?
311
+
312
+ if [[ "$exit_code" -ne 0 ]]; then
313
+ echo "FATAL: exploration binary failed or timed out for query $((qi + 1)) (exit=$exit_code) - documented gap, not a silent skip (D-04)" >&2
314
+ append_evidence "[explore] FATAL: query=$((qi + 1)) exit=$exit_code - documented gap (D-04)"
315
+ return 1
316
+ fi
317
+
318
+ if ! citation_text=$(render_citation_text "$raw_out"); then
319
+ echo "FATAL: malformed Evidence JSON for query $((qi + 1)) - documented gap (D-04)" >&2
320
+ append_evidence "[explore] FATAL: query=$((qi + 1)) malformed Evidence JSON - documented gap (D-04)"
321
+ return 1
322
+ fi
323
+
324
+ bytes=$(printf '%s' "$citation_text" | count_bytes)
325
+ chars=$(printf '%s' "$citation_text" | count_chars)
326
+ n_citations=$(echo "$raw_out" | jq '.citations | length? // 0' 2>/dev/null || echo 0)
327
+
328
+ EXPLORE_QUERY_BYTES[$qi]=$bytes
329
+ EXPLORE_QUERY_CHARS[$qi]=$chars
330
+ echo "[explore] query=$((qi + 1)) citations=$n_citations bytes=$bytes chars=$chars"
331
+ append_evidence "[explore] query=$((qi + 1)) citations=$n_citations bytes=$bytes chars=$chars"
332
+ done
333
+ }
334
+
335
+ # run_full(repo): runs both sides over the same query set, computes the
336
+ # per-query delta + median byte-savings percentage, and applies the D-03
337
+ # net-savings gate to the aggregate byte totals (D-03's "shows net savings >
338
+ # 0" is an aggregate/total check across the whole query set; the median
339
+ # per-query percentage is reported alongside it as an additional statistic,
340
+ # not the gate's input - the gate signature takes byte totals, not a
341
+ # percentage).
342
+ run_full() {
343
+ local repo="$1"
344
+ local qi native_b explore_b native_c explore_c delta_b delta_c est_delta pct
345
+ local total_native=0 total_explore=0
346
+ local -a pct_list=()
347
+
348
+ run_native "$repo"
349
+ if ! run_explore "$repo"; then
350
+ return 1
351
+ fi
352
+
353
+ append_evidence "=== per-query delta ($(date -u +%FT%TZ)) ==="
354
+ for qi in "${!QUERIES[@]}"; do
355
+ native_b="${NATIVE_QUERY_BYTES[$qi]}"
356
+ explore_b="${EXPLORE_QUERY_BYTES[$qi]}"
357
+ native_c="${NATIVE_QUERY_CHARS[$qi]}"
358
+ explore_c="${EXPLORE_QUERY_CHARS[$qi]}"
359
+ delta_b=$((native_b - explore_b))
360
+ delta_c=$((native_c - explore_c))
361
+ est_delta=$(( $(est_tokens "$native_c") - $(est_tokens "$explore_c") ))
362
+
363
+ if [[ "$native_b" -gt 0 ]]; then
364
+ pct=$(( delta_b * 100 / native_b ))
365
+ else
366
+ pct=0
367
+ fi
368
+ pct_list+=("$pct")
369
+
370
+ total_native=$((total_native + native_b))
371
+ total_explore=$((total_explore + explore_b))
372
+
373
+ local line="[delta] query=$((qi + 1)) native_bytes=$native_b explore_bytes=$explore_b byte_delta=$delta_b char_delta=$delta_c token_delta_est=$est_delta savings_pct=${pct}%"
374
+ echo "$line"
375
+ append_evidence "$line"
376
+ done
377
+
378
+ local median_pct
379
+ median_pct=$(median "${pct_list[@]}")
380
+ echo "[median] byte-savings percentage across queries: ${median_pct}%"
381
+ append_evidence "[median] byte-savings percentage across queries: ${median_pct}%"
382
+
383
+ local verdict_line
384
+ if net_savings_gate "$total_native" "$total_explore"; then
385
+ verdict_line="[verdict] PASS: net savings > 0 (total_native=$total_native total_explore=$total_explore) (D-03)"
386
+ echo "$verdict_line"
387
+ append_evidence "$verdict_line"
388
+ append_evidence "[scrub-check] this log never contains an endpoint host/IP, model alias, or binary path"
389
+ return 0
390
+ fi
391
+ append_evidence "[scrub-check] this log never contains an endpoint host/IP, model alias, or binary path"
392
+ return 1
393
+ }
394
+
395
+ # --- self-test fixtures (Task 3): offline Nyquist backstop for the
396
+ # operator-gated live --explore/--full runs. No network, no backend, no
397
+ # live binary. ---
398
+
399
+ # self_test_delta(): asserts count_bytes/count_chars on a fixed string
400
+ # return the known length, and est_tokens on a known char count returns the
401
+ # expected chars/4 integer.
402
+ self_test_delta() {
403
+ local sample="Hello, World! This is a fixed test string."
404
+ local expected actual_bytes actual_chars est
405
+
406
+ expected=${#sample}
407
+ actual_bytes=$(printf '%s' "$sample" | count_bytes)
408
+ actual_chars=$(printf '%s' "$sample" | count_chars)
409
+
410
+ if [[ "$actual_bytes" -ne "$expected" ]]; then
411
+ echo "[self-test:delta] FAIL: count_bytes expected $expected got $actual_bytes" >&2
412
+ return 1
413
+ fi
414
+ if [[ "$actual_chars" -ne "$expected" ]]; then
415
+ echo "[self-test:delta] FAIL: count_chars expected $expected got $actual_chars" >&2
416
+ return 1
417
+ fi
418
+
419
+ est=$(est_tokens 100)
420
+ if [[ "$est" -ne 25 ]]; then
421
+ echo "[self-test:delta] FAIL: est_tokens(100) expected 25 got $est" >&2
422
+ return 1
423
+ fi
424
+
425
+ echo "[self-test:delta] OK: count_bytes/count_chars/est_tokens verified"
426
+ }
427
+
428
+ # self_test_gate() (D-03, both directions): a native>explore fixture must
429
+ # PASS; native==explore and native<explore fixtures must FAIL AND emit the
430
+ # loud documented-finding line - proving a regression cannot pass silently.
431
+ self_test_gate() {
432
+ local failures=0 out
433
+
434
+ if ! net_savings_gate 1000 500 >/dev/null 2>&1; then
435
+ echo "[self-test:gate] FAIL: expected PASS for native>explore" >&2
436
+ failures=1
437
+ fi
438
+
439
+ out=$(net_savings_gate 500 500 2>&1) && {
440
+ echo "[self-test:gate] FAIL: expected FAIL for native==explore" >&2
441
+ failures=1
442
+ }
443
+ if ! echo "$out" | grep -qi "documented finding"; then
444
+ echo "[self-test:gate] FAIL: native==explore case did not emit a documented-finding line" >&2
445
+ failures=1
446
+ fi
447
+
448
+ out=$(net_savings_gate 500 600 2>&1) && {
449
+ echo "[self-test:gate] FAIL: expected FAIL for native<explore" >&2
450
+ failures=1
451
+ }
452
+ if ! echo "$out" | grep -qi "documented finding"; then
453
+ echo "[self-test:gate] FAIL: native<explore case did not emit a documented-finding line" >&2
454
+ failures=1
455
+ fi
456
+
457
+ if [[ "$failures" -eq 0 ]]; then
458
+ echo "[self-test:gate] OK: PASS/FAIL directions verified with a documented-finding line on regression"
459
+ return 0
460
+ fi
461
+ return 1
462
+ }
463
+
464
+ # self_test_render(): feeds canned Evidence JSON fixtures through the same
465
+ # render_citation_text() run_explore uses and asserts the output matches
466
+ # renderCitations() verbatim - both the populated-citations case and the
467
+ # exact empty-citations note string.
468
+ self_test_render() {
469
+ local fixture expected actual empty_fixture empty_expected empty_actual
470
+
471
+ fixture='{"citations":[{"path":"src/a.ts","start_line":10,"end_line":20},{"path":"src/b.ts","start_line":1,"end_line":5}],"stats":{"turns":2,"tool_calls":3}}'
472
+ expected=$'src/a.ts:10-20\nsrc/b.ts:1-5'
473
+ actual=$(render_citation_text "$fixture")
474
+ if [[ "$actual" != "$expected" ]]; then
475
+ echo "[self-test:render] FAIL: citation text mismatch (expected [$expected] got [$actual])" >&2
476
+ return 1
477
+ fi
478
+
479
+ empty_fixture='{"citations":[],"stats":{"turns":4,"tool_calls":7}}'
480
+ empty_expected="(no citations found; turns=4, tool_calls=7)"
481
+ empty_actual=$(render_citation_text "$empty_fixture")
482
+ if [[ "$empty_actual" != "$empty_expected" ]]; then
483
+ echo "[self-test:render] FAIL: empty-citations text mismatch (expected [$empty_expected] got [$empty_actual])" >&2
484
+ return 1
485
+ fi
486
+
487
+ echo "[self-test:render] OK: citation-text shape matches renderCitations verbatim"
488
+ }
489
+
490
+ # run_self_test(): runs all self-test concerns and returns non-zero if any
491
+ # failed - the automated Nyquist backstop for the operator-gated live run.
492
+ run_self_test() {
493
+ local failures=0
494
+ self_test_delta || failures=1
495
+ self_test_gate || failures=1
496
+ self_test_render || failures=1
497
+
498
+ if [[ "$failures" -ne 0 ]]; then
499
+ echo "[self-test] FAILED" >&2
500
+ return 1
501
+ fi
502
+ echo "[self-test] PASSED"
503
+ }
504
+
505
+ main() {
506
+ local stage="" repo_override=""
507
+
508
+ while [[ $# -gt 0 ]]; do
509
+ case "$1" in
510
+ --self-test|--native|--explore|--full)
511
+ stage="$1"
512
+ shift
513
+ ;;
514
+ --repo)
515
+ repo_override="${2:-}"
516
+ if [[ -z "$repo_override" ]]; then
517
+ echo "FATAL: --repo requires a path argument" >&2
518
+ exit 2
519
+ fi
520
+ shift 2
521
+ ;;
522
+ -h|--help)
523
+ usage
524
+ exit 0
525
+ ;;
526
+ *)
527
+ usage >&2
528
+ exit 2
529
+ ;;
530
+ esac
531
+ done
532
+
533
+ if [[ -z "$stage" ]]; then
534
+ usage >&2
535
+ exit 2
536
+ fi
537
+
538
+ local repo="${repo_override:-$CAIRN_EXPLORE_REPO_ROOT}"
539
+ log_repo_presence "$repo_override"
540
+
541
+ case "$stage" in
542
+ --self-test)
543
+ run_self_test
544
+ ;;
545
+ --native)
546
+ run_native "$repo"
547
+ ;;
548
+ --explore)
549
+ run_explore "$repo"
550
+ ;;
551
+ --full)
552
+ run_full "$repo"
553
+ ;;
554
+ esac
555
+ }
556
+
557
+ main "$@"
@@ -0,0 +1,42 @@
1
+ # [PROJECT_NAME] - Project Guidelines
2
+
3
+ ## Project Identity
4
+ <!-- What is this project? One paragraph. -->
5
+
6
+ ## Technology Stack
7
+ <!-- List specific technologies and roles -->
8
+
9
+ ## Architectural Decisions
10
+ <!-- Number them for reference -->
11
+
12
+ ## Decision Matrix
13
+
14
+ ### Always Self-Decide
15
+ | Question Pattern | Default Answer |
16
+ |---|---|
17
+ | "Which approach for X?" | Most consistent with existing codebase |
18
+ | "Should we add tests?" | Yes, always |
19
+
20
+ ### Escalate Only If
21
+ - Deleting or restructuring domain entities
22
+ - Breaking schema changes
23
+ - New external dependencies
24
+ - Switching core technologies
25
+
26
+ ## Conventions
27
+ <!-- File naming, import style, patterns -->
28
+
29
+ ## Known Pitfalls
30
+ <!-- Hard-won lessons. Add as you go. -->
31
+
32
+ ## Memory Configuration
33
+ Scopes defined in `.opencode/memory.json`:
34
+ - `identity` - work developer preferences
35
+ - `project` - this project's decisions and history
36
+ <!-- Add domain scopes and AnythingLLM workspaces as needed -->
37
+
38
+ ## Tool Roles
39
+ - AgentFS is the durable structured memory layer.
40
+ - AnythingLLM is the long-form document retrieval layer.
41
+ - `lean-ctx` is the default context layer (reads, search, exploration), not durable project memory.
42
+ - RTK is a recommended companion for shell-heavy sessions (git, listings, failing builds); Token Savior is not default.
@@ -0,0 +1,56 @@
1
+ version: 1
2
+ project: REPLACE_WITH_PROJECT_NAME
3
+ default_branch: main
4
+ last_full_audit:
5
+ commit: null
6
+ at: null
7
+ report: null
8
+ gaps: []
9
+
10
+ # Gap entry shape:
11
+ # - id: GAP-0001
12
+ # title: README still advertises unsupported SPDX 2.3 output mode
13
+ # kind: code_ahead_of_docs
14
+ # severity: high
15
+ # status: new
16
+ # truth_source: code
17
+ # resolution_type: docs_and_memory
18
+ # opened_by: alignment-audit
19
+ # opened_at: 2026-04-16T12:30:00Z
20
+ # claimed_by: null
21
+ # claimed_at: null
22
+ # branch: null
23
+ # pr: null
24
+ # proposal_type: primary
25
+ # evidence:
26
+ # code:
27
+ # - src/convert/mod.rs
28
+ # docs:
29
+ # - README.md
30
+ # memory:
31
+ # - project/capabilities/export-mode
32
+ # notes: >-
33
+ # The code no longer exposes the documented mode. README and memory should be updated.
34
+ # supersedes: []
35
+ # superseded_by: null
36
+ # quorum:
37
+ # machine_checks:
38
+ # - alignment-audit
39
+ # human_approvals_required: 1
40
+ # owner_roles: []
41
+ # planning:
42
+ # status: unplanned
43
+ # plan_path: null
44
+ # planned_at: null
45
+ # planned_by: null
46
+ # task_type: null
47
+ # resolution:
48
+ # commit: null
49
+ # merged_at: null
50
+ # verified_at: null
51
+ # promotion:
52
+ # promoted_at: null
53
+ # promoted_by: null
54
+ # report_path: null
55
+ # anythingllm_workspace: null
56
+ # anythingllm_reembedded_at: null
@@ -0,0 +1,28 @@
1
+ # Alignment Collaboration Policy
2
+
3
+ This repository uses the alignment-governance workflow to keep code, repo docs, `AGENTS.md`, and durable memory consistent.
4
+
5
+ ## Branch Rules
6
+
7
+ - Default branch: `REPLACE_WITH_DEFAULT_BRANCH`
8
+ - Do not push feature work directly to `REPLACE_WITH_DEFAULT_BRANCH`.
9
+ - Use a feature branch, run `/gsd-align-audit` when the branch changes code, docs, `AGENTS.md`, or durable behavior, and keep branch-local findings under `.planning/alignment/BRANCH-NOTES/` until the work is accepted.
10
+
11
+ ## Gap Lifecycle
12
+
13
+ - Open or refresh gaps with `/gsd-align-audit`.
14
+ - Turn an accepted gap into a remediation plan with `/gsd-align-plan <gap-id>`.
15
+ - If multiple people propose different fixes for the same gap, keep one canonical gap ID and record alternative proposals through `proposal_type`, `supersedes`, and `superseded_by` instead of opening unrelated duplicates.
16
+ - Close accepted gaps only from the default branch with `/gsd-align-promote`.
17
+
18
+ ## Review Expectations
19
+
20
+ - Blocker and high-severity gaps should not merge without at least one human review.
21
+ - Treat the repo-local gap ledger as the shared state for ownership, severity, and promotion status.
22
+ - If a gap is already claimed by another branch or person, coordinate before taking it over.
23
+
24
+ ## Promotion Rules
25
+
26
+ - Durable project memory only receives accepted branch-safe conclusions.
27
+ - Refresh the project-doc AnythingLLM workspace after merge when docs or `AGENTS.md` changed in a way retrieval should reflect.
28
+ - Keep durable memory entries concise and factual.