Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .fallow/dupes-baseline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"clone_groups": [
"src/__tests__/memory-route-config-seam.test.ts:130-135|src/app/__tests__/composed-boot-parity.test.ts:117-122",
"src/__tests__/route-validation.test.ts:247-254|src/__tests__/route-validation.test.ts:79-86",
"src/__tests__/smoke.test.ts:11-24|src/app/__tests__/research-consumption-seams.test.ts:22-35",
"src/__tests__/smoke.test.ts:48-52|src/app/__tests__/research-consumption-seams.test.ts:57-61",
"src/app/__tests__/composed-boot-parity.test.ts:73-82|src/app/__tests__/composed-boot-parity.test.ts:89-101",
"src/app/runtime-container.ts:227-242|src/routes/memories.ts:611-626",
"src/db/memory-repository.ts:87-409|src/db/stores.ts:31-90",
"src/db/memory-repository.ts:124-145|src/db/pg-memory-store.ts:51-52",
"src/db/memory-repository.ts:157-165|src/db/pg-memory-store.ts:36-38",
"src/db/memory-repository.ts:197-213|src/db/pg-search-store.ts:26-42",
"src/db/memory-repository.ts:231-243|src/db/pg-memory-store.ts:40-41",
"src/db/memory-repository.ts:256-264|src/db/pg-memory-store.ts:41-42",
"src/db/memory-repository.ts:309-317|src/db/pg-representation-store.ts:25-27",
"src/db/memory-repository.ts:317-325|src/db/pg-representation-store.ts:23-25",
"src/db/memory-repository.ts:363-375|src/db/pg-search-store.ts:47-51",
"src/db/memory-repository.ts:379-403|src/db/pg-search-store.ts:55-62",
"src/db/repository-claims.ts:360-374|src/services/memory-lineage.ts:67-81",
"src/routes/memories.ts:144-149|src/routes/memories.ts:158-165",
"src/routes/memories.ts:465-476|src/routes/memories.ts:496-507",
"src/schemas/__tests__/agents.test.ts:18-26|src/schemas/__tests__/memories.test.ts:22-27",
"src/schemas/agents.ts:36-41|src/schemas/memories.ts:88-93",
"src/services/__tests__/ingest-fact-pipeline-workspace.test.ts:14-24|src/services/__tests__/memory-ingest-runtime-config.test.ts:51-63",
"src/services/__tests__/ingest-fact-pipeline-workspace.test.ts:22-27|src/services/__tests__/memory-ingest-runtime-config.test.ts:73-78|src/services/__tests__/search-pipeline-runtime-config.test.ts:72-77",
"src/services/__tests__/memory-ingest-runtime-config.test.ts:139-158|src/services/__tests__/memory-ingest-runtime-config.test.ts:178-194",
"src/services/__tests__/memory-ingest-runtime-config.test.ts:209-229|src/services/__tests__/memory-ingest-runtime-config.test.ts:251-271",
"src/services/__tests__/memory-service-config.test.ts:133-150|src/services/__tests__/memory-service-config.test.ts:174-191|src/services/__tests__/memory-service-config.test.ts:97-114",
"src/services/ingest-fact-pipeline.ts:167-170|src/services/ingest-fact-pipeline.ts:83-87",
"src/services/ingest-fact-pipeline.ts:127-131|src/services/ingest-fact-pipeline.ts:165-172",
"src/services/memory-lineage.ts:284-291|src/services/memory-lineage.ts:319-326"
]
}
26 changes: 26 additions & 0 deletions .fallow/health-baseline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"findings": [
"src/db/repository-write.ts:buildBaseParams:140",
"src/db/repository-claims.ts:createClaimVersionWithClient:256",
"src/services/llm.ts:chat:176",
"src/services/memory-audn.ts:tryOpinionIntercept:132",
"src/services/search-pipeline.ts:applyExpansionAndReranking:667",
"src/services/extraction.ts:repairTruncatedJson:38",
"src/services/llm.ts:cleaned:78",
"src/services/extraction-enrichment.ts:inferCrossEntityRelations:205",
"src/services/deferred-audn.ts:applyDeferredDecision:196",
"src/services/llm.ts:recordOpenAICost:147",
"src/services/llm.ts:chat:233",
"src/services/quick-extraction.ts:extractFactBearingTurns:82",
"src/db/repository-claims.ts:createClaimWithClient:58",
"src/services/embedding.ts:requestAndTrack:99",
"src/services/supplemental-extraction.ts:shouldIncludeSupplementalFact:41",
"src/services/__tests__/poisoning-dataset.ts:generateLegitimateVariations:286",
"src/services/atomicmem-uri.ts:resolve:33"
],
"production_coverage_findings": [],
"target_keys": [
"src/services/conflict-policy.ts:high impact",
"src/services/composite-dedup.ts:high impact"
]
}
28 changes: 23 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ jobs:

steps:
- uses: actions/checkout@v4
with:
# Full history so `fallow audit` can compute `--base main ... HEAD`
# against a real ref. Default fetch-depth: 1 leaves only HEAD and
# audit fails with "could not detect base branch".
fetch-depth: 0

- uses: actions/setup-node@v4
with:
Expand All @@ -56,11 +61,24 @@ jobs:
# lands with schemas changed but spec not regenerated.
run: npm run check:openapi

- name: Code health (fallow)
# Pinned to the last version main's CI passed cleanly with. Newer
# versions (2.45.0) flag pre-existing complexity on untouched files.
# Bump deliberately alongside a pass to address flagged issues.
run: npx fallow@2.43.0 --no-cache
- name: Code health (fallow audit vs baselines)
# Audit changed files against frozen baselines in .fallow/. New
# complexity / duplication regressions fail; pre-existing
# baseline-level debt is grandfathered. Refactor + regenerate the
# baseline to lower the floor — the ratchet step below enforces
# monotonic improvement.
run: npx fallow audit
--health-baseline=.fallow/health-baseline.json
--dupes-baseline=.fallow/dupes-baseline.json
--base=origin/${{ github.base_ref || github.event.repository.default_branch }}
--no-cache

- name: Baseline ratchet (shrink-only)
# Blocks PRs that grow .fallow/*-baseline.json. Refactors that
# drop entries pass freely; new entries fail even if they'd be
# inside the grandfathered audit. Ensures debt goes monotonically
# to zero over time.
run: ./scripts/check-baseline-ratchet.sh origin/${{ github.base_ref || github.event.repository.default_branch }}

- name: Run tests
run: npm test
17 changes: 12 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ jobs:

steps:
- uses: actions/checkout@v4
with:
# Full history so `fallow audit` can walk back to main.
fetch-depth: 0

- uses: actions/setup-node@v4
with:
Expand All @@ -45,11 +48,15 @@ jobs:
- name: Type check
run: npx tsc --noEmit

- name: Code health (fallow)
# Pinned to the last version main's CI passed cleanly with. Newer
# versions (2.45.0) flag pre-existing complexity on untouched files.
# Bump deliberately alongside a pass to address flagged issues.
run: npx fallow@2.43.0 --no-cache
- name: Code health (fallow audit vs baselines)
# Tag pushes: compare against main so the release's diff is
# scoped the same way PRs are. `origin/main` is available
# because fetch-depth: 0 pulled the full history.
run: npx fallow audit
--health-baseline=.fallow/health-baseline.json
--dupes-baseline=.fallow/dupes-baseline.json
--base=origin/main
--no-cache

- name: Run tests
run: npm test
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ coverage/
.nyc_output/

# Code analysis tools
.fallow/
# Baselines (health-baseline.json, dupes-baseline.json) ARE tracked so CI
# and pre-commit hooks share the same floor; only the incremental cache
# and churn snapshot are regenerable and ignored.
.fallow/cache.bin
.fallow/churn.bin
.brv/
.perf-baselines/

Expand Down
11 changes: 11 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env sh
# Fallow gate: block commits that introduce new complexity or duplication
# beyond the frozen baselines in .fallow/. Audit auto-detects the base
# branch (typically main) and scopes to files changed on this branch —
# the same semantics CI uses, so a commit that passes here will pass CI.
# To lower a baseline, refactor the flagged code and regenerate:
# npx fallow health --save-baseline=.fallow/health-baseline.json
# npx fallow dupes --save-baseline=.fallow/dupes-baseline.json
npx fallow audit \
--health-baseline=.fallow/health-baseline.json \
--dupes-baseline=.fallow/dupes-baseline.json
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@
"test:deployment": "vitest run src/__tests__/deployment-config.test.ts --reporter verbose",
"test:docker-smoke": "./scripts/docker-smoke-test.sh",
"migrate": "dotenv -e .env -- tsx src/db/migrate.ts",
"migrate:test": "dotenv -e .env.test -- tsx src/db/migrate.ts"
"migrate:test": "dotenv -e .env.test -- tsx src/db/migrate.ts",
"prepare": "husky"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.80.0",
Expand All @@ -86,6 +87,7 @@
"@types/node": "^22.0.0",
"@types/pg": "^8.15.0",
"dotenv-cli": "^8.0.0",
"husky": "^9.1.7",
"tsx": "^4.19.0",
"typescript": "^5.7.0",
"vitest": "^4.1.3",
Expand Down
50 changes: 50 additions & 0 deletions scripts/check-baseline-ratchet.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env bash
# Enforces monotonic improvement on fallow baselines: CI fails if a PR
# grows .fallow/health-baseline.json or .fallow/dupes-baseline.json
# beyond what's on the base branch. Refactors that shrink the baseline
# are celebrated; regressions are blocked.
#
# Runs in CI after the fallow audit step. Requires `jq` and a full
# git history (fetch-depth: 0 in the workflow's checkout step).
set -euo pipefail

BASE_REF="${1:-origin/main}"

check_baseline() {
local file="$1"
local json_path="$2"
local label="$3"

if [ ! -f "$file" ]; then
echo "$label: baseline missing at $file; skipping"
return 0
fi

# If the baseline file is brand new on this PR (not tracked on the base
# branch), there's nothing to ratchet against — the PR IS the floor.
if ! git cat-file -e "$BASE_REF:$file" 2>/dev/null; then
echo "$label: new baseline on this PR; skipping ratchet check"
return 0
fi

local base_count head_count
base_count=$(git show "$BASE_REF:$file" | jq "$json_path | length")
head_count=$(jq "$json_path | length" "$file")

if [ "$head_count" -gt "$base_count" ]; then
echo "::error file=$file::baseline grew from $base_count to $head_count entries. Baselines ratchet: refactor the flagged code and regenerate the baseline, do not add new entries."
return 1
fi

local delta=$((base_count - head_count))
if [ "$delta" -gt 0 ]; then
echo "$label: $base_count -> $head_count (-$delta) ✓ shrunk"
else
echo "$label: $base_count -> $head_count (unchanged)"
fi
}

fail=0
check_baseline .fallow/health-baseline.json '.findings' health || fail=1
check_baseline .fallow/dupes-baseline.json '.clone_groups' dupes || fail=1
exit "$fail"
55 changes: 55 additions & 0 deletions tech-debt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Tech debt

Items deferred rather than fixed inline. Fallow baselines at
`.fallow/health-baseline.json` and `.fallow/dupes-baseline.json` keep
these from blocking PRs; the CI ratchet check keeps the baseline
monotonic (shrink-only), so every refactor that lowers an entry counts.

When fixing an item: refactor the code, regenerate the relevant
baseline (`npx fallow health --save-baseline=.fallow/health-baseline.json`
or `npx fallow dupes --save-baseline=.fallow/dupes-baseline.json`),
commit both the code change and the smaller baseline, and delete the
entry from this file.

## HIGH-complexity functions (baseline: 4 HIGH + 13 other, 17 total)

Each entry blocks the ceiling; splitting any of them lets the baseline
shrink.

- [ ] `src/db/repository-write.ts:140 buildBaseParams` — 15 cyclomatic / 14 cognitive / 26 lines / CRAP 63.6. Param-building branch tree; candidate for a config-table pattern.
- [ ] `src/db/repository-claims.ts:256 createClaimVersionWithClient` — 15 / 9 / 46. Multi-insert transaction; extract the slot-resolution and version-linking branches.
- [ ] `src/services/llm.ts:176 chat` — 14 / 13 / 40. Provider-dispatch plus retry; split the retry wrapper from the provider selection.
- [ ] `src/services/memory-audn.ts:132 tryOpinionIntercept` — 14 / 8 / 28. Confidence-threshold cascade; table-drive the decision.
- [ ] `src/services/search-pipeline.ts:667 applyExpansionAndReranking` — 13 / 12 / 100 lines. The longest function in core; split into expansion / reranking / packaging phases.
- [ ] `src/services/extraction.ts:38 repairTruncatedJson` — 11 / 10 / 38.
- [ ] `src/services/llm.ts:78 cleaned` — 11 / 9 / 11.
- [ ] `src/services/extraction-enrichment.ts:205 inferCrossEntityRelations` — 11 / 14 / 20.
- [ ] `src/services/deferred-audn.ts:196 applyDeferredDecision` — 11 / 7 / 49.
- [ ] `src/services/llm.ts:147 recordOpenAICost` — 10 / 6 / 18.
- [ ] `src/services/llm.ts:233 chat` (second `chat`) — 10 / 6 / 23. Same file as the HIGH one above; may dedupe via shared base.
- [ ] `src/services/quick-extraction.ts:82 extractFactBearingTurns` — 10 / 11 / 40.
- [ ] `src/db/repository-claims.ts:58 createClaimWithClient` — 10 / 5 / 25.
- [ ] `src/services/embedding.ts:99 requestAndTrack` — 10 / 5 / 13.
- [ ] `src/services/supplemental-extraction.ts:41 shouldIncludeSupplementalFact` — 10 / 6 / 35.
- [ ] `src/services/__tests__/poisoning-dataset.ts` — 10 / 9 / 112 (test fixture; may be fine as-is).
- [ ] `src/services/atomicmem-uri.ts:33 resolve` — 10 / 13 / 34.

## Clone groups (baseline: 29 groups, 848 lines)

Biggest wins come from extracting shared helpers across the storage
adapter / DB layer.

- [ ] **memory-repository ↔ pg-*-store near-duplicates** (~375 lines across 3 clone groups). `src/db/memory-repository.ts:87-409` vs `src/db/stores.ts:31-90`, and several smaller blocks against `pg-memory-store.ts` and `pg-search-store.ts`. Extract a shared DB-access helper into `src/db/`. Biggest single win.
- [ ] **runtime-container ↔ formatHealthConfig** (16 lines). `src/app/runtime-container.ts:227-242` vs `src/routes/memories.ts:611-626`. Both snapshot the same runtime config fields; extract a single `snapshotRuntimeConfig` helper.
- [ ] **repository-claims ↔ memory-lineage** (15 lines). `src/db/repository-claims.ts:360-374` vs `src/services/memory-lineage.ts:67-81`.
- [ ] **test setup duplication**. `memory-ingest-runtime-config.test.ts` has 41 lines of internal dupes across 2 groups; `memory-service-config.test.ts` has 18 lines across 3 instances; `smoke.test.ts` + `research-consumption-seams.test.ts` share 14 lines of bootstrap. Extract to test-fixtures.
- [ ] 20 remaining smaller clone groups — run `npx fallow dupes` for the full list.

## OpenAPI response schemas (separate from fallow)

- [ ] Response bodies aren't declared in the OpenAPI spec today — only request bodies. Adding them would let `npm run check:openapi` catch wire-shape drift automatically (the earlier snake_case flip couldn't have silently changed the spec). See `atomicmemory-core/src/schemas/openapi.ts`.

## Fallow version pin removal

Landed in the baseline-upgrade PR. No action required — CI now uses
fallow's latest via `npx fallow audit`.
Loading