@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.
- package/AGENTS.md +45 -0
- package/CONTRIBUTING.md +49 -0
- package/LICENSE +201 -0
- package/README.md +149 -0
- package/SECURITY.md +37 -0
- package/bin/cairn +73 -0
- package/claude/agents/code-reviewer.md +174 -0
- package/claude/agents/security-investigator.md +112 -0
- package/claude/agents/security-target-selector.md +90 -0
- package/claude/agents/security-validator.md +97 -0
- package/claude/agents/wiki-ingester.md +108 -0
- package/claude/agents/wiki-lint-auditor.md +94 -0
- package/claude/agents/wiki-query-analyst.md +84 -0
- package/claude/commands/context-explore.md +40 -0
- package/claude/commands/graphify.md +150 -0
- package/claude/commands/memory-review.md +85 -0
- package/claude/commands/memory-sync.md +71 -0
- package/claude/commands/recall.md +32 -0
- package/claude/commands/remember.md +43 -0
- package/claude/commands/repo-review.md +191 -0
- package/claude/commands/security-audit.md +174 -0
- package/claude/commands/wiki-ingest.md +112 -0
- package/claude/commands/wiki-lint.md +86 -0
- package/claude/commands/wiki-query.md +92 -0
- package/claude/hooks/context-explore-pretask.sh +93 -0
- package/claude/hooks/memory-capture.sh +59 -0
- package/claude/hooks/memory-recall.sh +84 -0
- package/claude/hooks/memory-wakeup.sh +45 -0
- package/docs/building-an-overlay.md +117 -0
- package/docs/domain-knowledge.md +86 -0
- package/docs/git-providers.md +36 -0
- package/docs/operating.md +366 -0
- package/mcp-memory-server/dist/embeddings.js +122 -0
- package/mcp-memory-server/dist/explore-cache.js +97 -0
- package/mcp-memory-server/dist/index.js +1256 -0
- package/mcp-memory-server/package.json +36 -0
- package/opencode/agents/code-reviewer.md +173 -0
- package/opencode/agents/security-investigator.md +111 -0
- package/opencode/agents/security-target-selector.md +89 -0
- package/opencode/agents/security-validator.md +96 -0
- package/opencode/agents/wiki-ingester.md +107 -0
- package/opencode/agents/wiki-lint-auditor.md +93 -0
- package/opencode/agents/wiki-query-analyst.md +83 -0
- package/opencode/command/code-review.md +199 -0
- package/opencode/command/context-explore.md +42 -0
- package/opencode/command/graphify.md +153 -0
- package/opencode/command/memory-review.md +44 -0
- package/opencode/command/memory-sync.md +94 -0
- package/opencode/command/recall.md +38 -0
- package/opencode/command/remember.md +46 -0
- package/opencode/command/security-audit.md +80 -0
- package/opencode/command/wiki-ingest.md +55 -0
- package/opencode/command/wiki-lint.md +51 -0
- package/opencode/command/wiki-query.md +50 -0
- package/opencode/plugins/memory-capture.ts +188 -0
- package/opencode/plugins/memory-recall.ts +132 -0
- package/opencode/plugins/memory-wakeup.ts +102 -0
- package/opencode/templates/security-finding-register.yaml.template +51 -0
- package/opencode/templates/security-issue.md.template +43 -0
- package/opencode/templates/security-policy.md.template +29 -0
- package/opencode/templates/security-report.md.template +43 -0
- package/opencode/templates/wiki-contradictions.md.template +31 -0
- package/opencode/templates/wiki-index.md.template +40 -0
- package/opencode/templates/wiki-lint-report.md.template +38 -0
- package/opencode/templates/wiki-log.md.template +11 -0
- package/opencode/templates/wiki-policy.md.template +47 -0
- package/opencode/templates/wiki-query-answer.md.template +26 -0
- package/opencode/templates/wiki-source-summary.md.template +33 -0
- package/opencode/workflows/memory-review-workflow.md +66 -0
- package/opencode/workflows/security-audit-workflow.md +197 -0
- package/opencode/workflows/wiki-ingest-workflow.md +149 -0
- package/opencode/workflows/wiki-lint-workflow.md +128 -0
- package/opencode/workflows/wiki-query-workflow.md +122 -0
- package/package.json +56 -0
- package/scripts/bootstrap.sh +99 -0
- package/scripts/doctor.sh +91 -0
- package/scripts/install-audit-timer.sh +68 -0
- package/scripts/lib/assert-tool-event.mjs +50 -0
- package/scripts/memory-store.sh +71 -0
- package/scripts/memory-wiki-audit.sh +156 -0
- package/scripts/sync-claude-assets.sh +182 -0
- package/scripts/sync-opencode-explore-assets.sh +187 -0
- package/scripts/sync-opencode-graphify-assets.sh +152 -0
- package/scripts/sync-opencode-memory-assets.sh +160 -0
- package/scripts/sync-opencode-plugin-assets.sh +163 -0
- package/scripts/sync-opencode-security-assets.sh +195 -0
- package/scripts/sync-opencode-wiki-assets.sh +250 -0
- package/scripts/test-audit-timer.sh +29 -0
- package/scripts/test-bootstrap-untracked.sh +41 -0
- package/scripts/test-cli-dispatch.sh +21 -0
- package/scripts/test-doctor.sh +32 -0
- package/scripts/test-launcher-hooks.sh +66 -0
- package/scripts/test-memory-store.sh +44 -0
- package/scripts/test-remember-recall-assertions.sh +70 -0
- package/scripts/transcript-to-text.mjs +53 -0
- package/scripts/verify-docs-parity.sh +112 -0
- package/scripts/verify-explore-maturation.sh +288 -0
- package/scripts/verify-fastcontext-reliability.sh +624 -0
- package/scripts/verify-no-private-references.sh +145 -0
- package/scripts/verify-opencode-live-parity.sh +864 -0
- package/scripts/verify-routing-seam.sh +169 -0
- package/scripts/verify-token-savings-ab.sh +557 -0
- package/templates/PROJECT-INSTRUCTIONS.md.template +42 -0
- package/templates/alignment-gap-register.yaml.template +56 -0
- package/templates/alignment-policy.md.template +28 -0
- package/templates/alignment-pr-template.md.template +18 -0
- package/templates/alignment-report.md.template +56 -0
- package/templates/env.example.template +25 -0
- package/templates/graph-policy.md.template +37 -0
- package/templates/graphs-gitignore.template +3 -0
- package/templates/memory-wiki-audit.service.template +6 -0
- package/templates/memory-wiki-audit.timer.template +9 -0
- package/templates/memory.json.template +4 -0
- package/templates/planning-config.json.template +6 -0
- package/templates/project-brief.md.template +40 -0
- package/templates/security-finding-register.yaml.template +51 -0
- package/templates/security-issue.md.template +43 -0
- package/templates/security-policy.md.template +29 -0
- package/templates/security-report.md.template +43 -0
- package/templates/start-claude.sh.template +46 -0
- package/templates/start-opencode.sh.template +41 -0
- package/templates/structured-change-canvas.md.template +63 -0
- package/templates/wiki-contradictions.md.template +31 -0
- package/templates/wiki-index.md.template +40 -0
- package/templates/wiki-lint-report.md.template +38 -0
- package/templates/wiki-log.md.template +11 -0
- package/templates/wiki-policy.md.template +47 -0
- package/templates/wiki-query-answer.md.template +26 -0
- 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.
|