Skip to content

eidetic-memory: refresh wrappers (eidetic 0.10) + memory-discipline convention#3

Merged
OriNachum merged 1 commit into
mainfrom
rollout/eidetic-memory-0.10
Jul 4, 2026
Merged

eidetic-memory: refresh wrappers (eidetic 0.10) + memory-discipline convention#3
OriNachum merged 1 commit into
mainfrom
rollout/eidetic-memory-0.10

Conversation

@OriNachum

Copy link
Copy Markdown
Contributor

Added

  • Memory-discipline "Conventions and workflow" section in CLAUDE.md — a
    per-task recall-before / remember-after convention (scope localized to this
    repo's nick) so the vendored remember / recall skills are actually used,
    not just present: /recall before non-trivial work to build on prior
    decisions instead of re-deriving them, and /remember when a non-obvious
    decision, constraint, fix-and-why, or hard-won gotcha surfaces. The section
    documents this repo's memory as in-repo and public — records resolve to
    <repo-root>/.eidetic/memory (committed, team- and mesh-shared). Inserted
    idempotently (skipped if already present), slotted under an existing
    "Conventions and workflow" heading when one exists, else appended.

Changed

  • Refreshed the remember + recall wrappers from eidetic-cli 0.10.0
    (cite-don't-import) — picks up eidetic's project-local store default: the
    files backend now resolves per record by visibility — PUBLIC records inside a
    git repo go to <repo-root>/.eidetic/memory (committed, team-shared), PRIVATE
    records (or any record outside a repo) go to $HOME/.eidetic/memory (never
    committed), an explicit EIDETIC_DATA_DIR still wins, and recall reads both
    stores and merges. Also carries the 0.9.3 hardening (interactive-stdin guard,
    help as a search term, SIGPIPE-safe suffix parsing). Recipe policy
    override (the wrappers here are NOT byte-verbatim):
    the injected default
    visibility is flipped from eidetic's private to public, so a plain
    /remember lands the note in ./.eidetic/memory in this repo, kept as part
    of the repo — pass --visibility private to route a record to $HOME
    instead. remember drives eidetic remember (idempotent upsert of one JSON
    record or an NDJSON batch on stdin); recall drives eidetic recall with
    four search modes (exact / approximate / keyword / hybrid). Each SKILL.md is
    localized only in the illustrative --scope <nick> examples (Provenance keeps
    "First-party to eidetic-cli"). Runtime dep: the eidetic CLI on PATH (else a
    local eidetic-cli checkout with uv) — eidetic >= 0.10.0 for the
    in-repo routing; on an older CLI the public records still work but are stored
    in $HOME/.eidetic/memory instead of in-repo. Propagated by rollout-cli's
    eidetic-memory recipe.

- **Memory-discipline "Conventions and workflow" section in `CLAUDE.md`** — a
  per-task *recall-before / remember-after* convention (scope localized to this
  repo's nick) so the vendored `remember` / `recall` skills are actually used,
  not just present: `/recall` before non-trivial work to build on prior
  decisions instead of re-deriving them, and `/remember` when a non-obvious
  decision, constraint, fix-and-why, or hard-won gotcha surfaces. The section
  documents this repo's memory as **in-repo and public** — records resolve to
  `<repo-root>/.eidetic/memory` (committed, team- and mesh-shared). Inserted
  idempotently (skipped if already present), slotted under an existing
  "Conventions and workflow" heading when one exists, else appended.

### Changed

- **Refreshed the `remember` + `recall` wrappers from eidetic-cli 0.10.0**
  (cite-don't-import) — picks up eidetic's **project-local store default**: the
  files backend now resolves per record by visibility — PUBLIC records inside a
  git repo go to `<repo-root>/.eidetic/memory` (committed, team-shared), PRIVATE
  records (or any record outside a repo) go to `$HOME/.eidetic/memory` (never
  committed), an explicit `EIDETIC_DATA_DIR` still wins, and recall reads both
  stores and merges. Also carries the 0.9.3 hardening (interactive-stdin guard,
  `help` as a search term, SIGPIPE-safe suffix parsing). **Recipe policy
  override (the wrappers here are NOT byte-verbatim):** the injected default
  visibility is flipped from eidetic's `private` to **`public`**, so a plain
  `/remember` lands the note in `./.eidetic/memory` in this repo, kept as part
  of the repo — pass `--visibility private` to route a record to `$HOME`
  instead. `remember` drives `eidetic remember` (idempotent upsert of one JSON
  record or an NDJSON batch on stdin); `recall` drives `eidetic recall` with
  four search modes (exact / approximate / keyword / hybrid). Each `SKILL.md` is
  localized only in the illustrative `--scope <nick>` examples (Provenance keeps
  "First-party to eidetic-cli"). Runtime dep: the `eidetic` CLI on PATH (else a
  local eidetic-cli checkout with `uv`) — **`eidetic >= 0.10.0`** for the
  in-repo routing; on an older CLI the public records still work but are stored
  in `$HOME/.eidetic/memory` instead of in-repo. Propagated by rollout-cli's
  `eidetic-memory` recipe.
@qodo-code-review

Copy link
Copy Markdown

Looking for bugs?

Check back in a few minutes. Qodo's review agents are on it.

@sonarqubecloud

Copy link
Copy Markdown

@OriNachum OriNachum merged commit d33c56a into main Jul 4, 2026
8 checks passed
@OriNachum OriNachum deleted the rollout/eidetic-memory-0.10 branch July 4, 2026 06:59
OriNachum added a commit that referenced this pull request Jul 6, 2026
1. Recorded vectors gain a model tie-out (Qodo #1): recorded_vectors.json is
   now {metadata, vectors} — the refresh script stamps the embedding model/
   endpoint it actually ran against, the committed fixture is wrapped post-hoc
   with its known 2026-07-04 provenance (vector values unchanged), and the
   loader exposes load_recorded_metadata() with legacy-format fallback.
2. --json placement no longer matters (Qodo #2): verb-level --json flags use
   default=argparse.SUPPRESS via a shared helper so they never clobber a
   noun-level --json; applied to all six noun groups including meaning.
3. signal collect rejects non-object measurements (Qodo #3): valid JSON that
   is not an object raises SeriesError(collect_measurement_not_an_object)
   naming the offending file — exit 1, never an AttributeError.
4. Duplication: the per-noun file-error guard, remediation strings, and
   reference-date parsing move to cli/_commands/_artifact_io.py; the five
   noun modules keep only their domain-specific branches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk
OriNachum added a commit that referenced this pull request Jul 6, 2026
…iture, frames (closes #8 #9 #10 #11) (#14)

* t1: shared measurement envelope — coherence/schema.py + docs/envelope.md

Adds the stdlib-only shared measurement envelope (domain, score_type,
scores, frame, diagnostics) that every new domain noun (quality, signal,
investiture, assess) will emit from day one. build_envelope/validate_envelope
round-trip a valid envelope unchanged; every violation (missing key,
non-dict scores, non-numeric score value, malformed diagnostic entry, etc.)
raises the dedicated EnvelopeError with a machine-readable code. An absent
frame is representable explicitly via frame=None (paired with a diagnostic)
or the null_frame() helper dict — never a missing key.

docs/envelope.md documents all five fields and the two-speed adoption rule:
new nouns emit the full envelope; existing meaning verbs keep their pinned
v0.5.0 shape and only gain additive top-level keys (domain, score_type,
frame), with the scores-nesting migration deferred to an explicitly
versioned future change.

20 new tests in tests/test_schema.py, all offline. Full suite: 146 -> 166
passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* t2: frame provenance core

Add coherence/frames/{__init__.py,provenance.py} — build_frame() assembles
the frame provenance block (embedding_model, embedding_endpoint, anchor_set,
axis/axes, projection_method, score_type) for embedding-derived measurements.

embedding_model/embedding_endpoint are resolved at call time by reusing
coherence.meaning.embed's own COHERENCE_EMBED_URL/COHERENCE_EMBED_MODEL
resolution functions directly (not duplicated default literals), satisfying
t2's acceptance criterion that a monkeypatched env changes the emitted block
and defaults only appear when the env is unset.

Absent provenance re-exports coherence.schema.null_frame verbatim (asserted
via identity in tests) rather than reinventing the null-frame shape.

17 new offline tests in tests/test_frames_provenance.py; full suite 166 -> 183
passing.

* t3: signal series schema + robust loader

Add coherence/signal as the source-agnostic series analysis layer's input
contract. coherence/signal/schema.py defines the series shape (optional
domain + ordered points; per point id/index/timestamp/values + optional
per-point frame) and load_series(), a robust loader that normalizes into
typed Series/SeriesPoint records plus a mutable diagnostics list.

- values are arbitrarily named numerics (caller-defined, never enumerated);
  missing/null/non-numeric values and booleans are skipped WITH a diagnostic,
  never a crash. bool is not numeric.
- per-point frames are surfaced on the normalized point and diagnostics is
  left appendable, so a later mixed-frame guard plugs in cleanly.
- only top-level structural failure raises SeriesError (machine-readable
  code), mirroring coherence.schema.EnvelopeError.
- series_from_meaning_trend() converts a real meaning-trend result into a
  valid series dict, proving source-agnosticism (same loader as hand-written).
- documented in docs/signal-series.md, referenced from the module docstring.

Tests: tests/test_signal_schema.py (37 tests, fully offline; the trend JSON
is built by driving the real trend engine with the synthetic embed_fn over
the recorded series fixtures). Full suite 166 -> 203, green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* t11: quality domain engine — offline heuristics

First honest implementation of the "quality" coherence domain: fully offline,
deterministic, rule-based scoring of freshness/provenance/fidelity. Emits the
shared measurement envelope (coherence/schema.py) with domain=quality,
score_type=rule_based_heuristic, and an explicit null-frame
(rule_based_no_embedding_frame) — never a fabricated embedding frame.

Honesty contract:
- diagnostics NAME what rules could not verify (source_liveness_unverified,
  publication_date_unverified, quote_accuracy_unverified);
- absent signals lower per-component confidence with a diagnostic
  (no_dateable_statements / no_source_attribution / no_verbatim_signal),
  never fabricating a score;
- confidence is visible in the scores map as <component>_confidence.

- coherence/quality/heuristics.py: rule-based detectors + component scorers
- coherence/quality/score.py: assess() raw breakdown + score_text() envelope
- tests: 43 new, fully offline (socket-blocked fixture proves zero network)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* t12: quality compare — signed component deltas

Implement coherence/quality/compare.py mirroring the shape of
coherence/meaning/compare.py: before/after full quality envelopes plus a
delta map with signed floats for all score components (freshness, provenance,
fidelity and their _confidence entries). Delta = after - before for each
component.

- Fully offline: reuses score_text engine, threaded with reference_date
- Open component registry: delta keys mirror scores map keys
- Comprehensive test suite: 13 tests covering shape, delta arithmetic, edge
  cases, socket blocking (zero network access), and string/Path handling

All 276 tests pass (263 existing + 13 new).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* t9: signal collect — measurements to series

Add coherence/signal/collect.py with shape-driven extraction that builds valid
series from N measurement JSONs of any domain (meaning, quality, investiture).

Extraction rule: if measurement has dict-valued `scores` → extract those (full
envelope path); otherwise harvest numeric leaves generically (top-level numeric
keys + numeric entries from any top-level dict like subdimensions).

API:
- collect(measurements: list[Mapping], *, ids: list[str] | None = None) -> dict
- collect_files(paths: list[str]) -> dict

Output per point: id, index, timestamp (None), values (extracted numerics),
frame (carried from measurement verbatim or None). Series-level domain: set
when all inputs agree on one domain string, else None (no error).

Zero numeric leaves in an input → raises SeriesError with code
"collect_no_numeric_values" (matches schema error convention).

All 25 new collect tests pass; full suite 288 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* t7: signal resonance engine — signed alignment

Add coherence/signal/resonance.py: pairwise Pearson alignment between a
series' numeric fields, reported as ONE signed metric. Positive alignment
is labeled "resonance" (streams reinforce), negative is "interference"
(streams conflict) — both derived from the sign of the same computation,
never two separate code paths. A neutral band (NEUTRAL_BAND = 0.1) around
zero labels weak alignment as neither.

Pairs with fewer than MIN_COMMON_POINTS (3) shared points, or with a
constant (zero-variance) field, are excluded with a diagnostic rather than
producing a NaN or fabricated correlation. A defensive non-finite-result
guard (CODE_NON_FINITE_CORRELATION) covers any other source of a
non-finite value.

Tests (tests/test_signal_resonance.py, 15 new, offline/numpy-only) cover
sign-carries-meaning (co-rising -> resonance, co-falling -> interference,
sign-symmetry under negation), exact +-1 correlation cases, the neutral
band, and every exclusion path (too-few-points, one/both constant fields,
disjoint fields, NaN-poisoned values bypassing the loader).

* t4: signal trend engine

* t6: signal pattern engine

Add coherence/signal/pattern.py: motif detection over a loaded Series, per
numeric field. Six motifs (increasing, decreasing, plateau, spike, reversal,
stair_step), each a deterministic, tolerance-gated rule against the field's
own observed range — no learned/tuned parameters. Sparse fields are analyzed
over their present values only (gaps get a diagnostic, never interpolated);
series/fields with fewer than 3 usable points get an explicit
insufficient-points diagnostic instead of motifs.

tests/test_signal_pattern.py covers all 6 motifs on synthetic + counterexample
series (12+ assertions), short-series guards, sparse-field gaps, per-field
insufficiency, multi-field independence, and JSON-serializability.

* t14: frames inspect/diff + mixed-frame guard

* t10: meaning outputs gain additive envelope keys

Score/compare/trend JSON gains three ADDITIVE top-level keys — domain,
score_type, frame — while every pre-existing key stays byte-identical
(the two-speed envelope rule; docs/envelope.md). meaning keeps its pinned
v0.5.0 shape and only grows.

- score.py: DOMAIN/SCORE_TYPE constants; meaning_frame() resolves the frame
  from the runtime embed config at call time via frames.provenance.build_frame
  (axes = meaning + the five subdimensions); score() = clean v0.5.0 core
  (_score_v050) + the three keys; offline_result() is the offline-diagnostics
  path — an explicit null_frame (code embed_endpoint_unreachable), never a
  fabricated frame. score() still raises EmbedUnavailable (exit-2 path
  unchanged).
- compare.py: one shared top-level frame; before/after stay clean v0.5.0.
- trend.py: one shared top-level frame added; difference math untouched.
- CLI unchanged (it dumps the engine dict verbatim in --json mode).
- New tests/test_meaning_envelope_keys.py (9): runtime-resolved frame, golden
  subset byte-identity on recorded vectors, offline null-frame.
- Narrow additive-tolerance relaxations to existing meaning shape assertions.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* t8: signal forecast — labeled extrapolation

* t5: meaning trend delegates difference math to signal layer

Point coherence/meaning/trend.py's f'/f'' differencing at the generic
coherence.signal.trend.first_difference / second_difference functions so
meaning trend is a dimension-specific wrapper (embed, label, assemble)
rather than a private reimplementation. Output is byte-identical: pinned
via full-result JSON goldens captured from the pre-refactor code
(recorded-vector + synthetic-embed series) in the new
tests/test_meaning_trend_delegation.py.

Removed the private _first_difference and _slot helpers; retained
_cosine_distance (drift distance), _second_unavailable_reason (labeling),
and the _derivatives_from_* helpers (imported by test_meaning_envelope_keys).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* t13: investiture engine — estimated micro-investiture

Add coherence/investiture/{__init__,score,compare}.py: the MVP measures
estimated micro-investiture (meaning that becomes causal, issue #8) as
meaning_score * agency * future_constraint * affordance, reusing
coherence.meaning.score.score() for every embedding/axis computation
(no duplicate embed path, same EmbedUnavailable exit-2 behavior).
score() output satisfies both the shared measurement envelope (domain,
score_type, scores, frame, diagnostics) and the issue-#8 JSON contract
(investiture_score, mode="estimated", components with the unmeasured
persistence_signal/integration_signal/behavioral_effect explicit nulls,
evidence) in one dict, since validate_envelope tolerates extra
top-level keys. compare() mirrors quality/compare.py's shape: before/
after are full score() results (each independently validating as an
envelope, frame passed through verbatim from meaning), delta is a
signed investiture_score plus the four numeric component deltas.

34 new tests in tests/test_investiture_{score,compare}.py, fully
offline via the shared synthetic hash embedder; full suite 423 -> 457
green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* t15: assess engine — multi-domain report with honest availability

Adds coherence/assess.py: one verb (assess) that runs every applicable
domain (quality, meaning, investiture) against an artifact and returns a
single report. assess itself ships the full shared envelope (domain,
score_type, scores={}, frame=None, diagnostics) per the two-speed adoption
rule for new nouns, with domains/unavailable/artifact layered on top.

Quality always runs offline. Meaning/investiture need the embedding
endpoint; when it's down, both are listed in `unavailable` with a
machine-readable code + reason (investiture is skipped rather than
re-attempted, since it derives from meaning), and meaning's offline
rule-based diagnostics are still surfaced under
unavailable["meaning"]["offline_diagnostics"] — partial availability is a
normal, honest result, never silently dropped.

tests/test_assess.py adds 19 fully-offline tests (synthetic embed_fn) for
both acceptance criteria: endpoint down (quality + offline diagnostics,
meaning/investiture unavailable with reasons) and endpoint up (all three
domains present, unavailable empty). Full suite: 490 -> 509 passing.

* t17: README + docs — five-domain positioning

Rewrite README.md so coherence-cli is positioned as a five-domain coherence
engine (quality, meaning, signal, investiture, frames): new lead tagline, a
"why agents need coherence" intro, a five-domain overview with quality named
as the first practical domain (not the whole product), the shared envelope +
two-speed adoption rule (linking docs/envelope.md), verb-level CLI examples
for every domain, a planned-extensions list naming what is explicitly not
built yet (issues #4/#6/#8/#9/#10), and a language/falsifiability section.
The Meaning Gradient section, its consumer map, and the scaffold-verb CLI
table are preserved; the consumer map gains colleague as the first wired
consumer of `meaning score --json`.

Add docs/domains.md as the one-page per-domain reference (question / verbs /
output shape / honest limitations), and tests/test_docs_language.py as a
grep-able, word-boundary-sensitive banned-terms guard over README.md and the
domain/envelope/series docs (excluding docs/specs/**, docs/plans/**, and
.devague/**, which quote the spec's non-goals verbatim). Rewrote the
README/domains.md non-goals language itself to avoid the banned substrings
("soul", "mystical", "literal physics", "universal meaning") rather than
negate them in place, since a grep-based guard can't distinguish a claim from
its own disclaimer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* t16: CLI wiring for quality/signal/investiture/frames/assess + explain catalog

Wires the five merged domain engines into the CLI, mirroring the existing
meaning noun's pattern: quality (score/compare), signal (trend/pattern/
resonance/forecast/collect), investiture (score/compare, sharing meaning's
EmbedUnavailable exit-2 path), frames (inspect/diff, where absent/partial
provenance is a normal exit-0 result), and assess (a global verb; partial
domain availability is exit 0, never an error). Extends the explain catalog
with an entry per new noun/verb plus the eight frame-vocabulary concept terms,
and extends cli overview/learn to list all five domains.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* docs: README examples section reflects wired CLI (post-t16 integration touch-up)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* t18: additive-compatibility + structure verification

Deliverable 1 (tests/test_additive_compatibility.py): additive-only pre/post
diff proof. meaning score/compare/trend, driven offline with the synthetic
embedder, have {domain, score_type, frame} stripped and the remainder asserted
byte-identical to an independent v0.5.0 reconstruction; before/after and
per-point blocks carry no new keys. Scaffold verbs (whoami/learn/explain/
overview/doctor/cli overview) pin their established --json key sets unchanged.

Deliverable 2 (tests/test_five_domain_structure.py): a package home per domain
(quality/meaning/signal/investiture/frames + assess/schema) is importable;
quality/signal/investiture/frames/assess are registered CLI nouns (top-level
help, -h, explain); and the offline nouns never dial the embed endpoint
(localhost:8002) — proven by a socket guard plus the green offline suite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* chore: bump version to 0.6.0 + build-session memory

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* fix(ci): black-format four agent test files; make trend-delegation goldens float-tolerant across CPU/BLAS (structure still exact)

The golden literals were captured pre-refactor and reproduced byte-for-byte
on the capture machine; CI CPUs shift the last float ulp, so the assertion
is now a deep compare — exact keys/strings/nulls, floats at 1e-9 rel tol.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* fix(ci): black-format quality/compare.py and signal/collect.py (missed in previous commit)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* fix(ci): backtick the assess <file> placeholder in CHANGELOG (MD033)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* refactor: address SonarCloud findings on PR #14 new code

- merge implicit string concatenations (S5799) in overview/quality CLI verbs
- constants for duplicated literals in signal CLI (S1192)
- drop redundant JSONDecodeError from except clause (S5713)
- split validate_envelope and _extract_values into per-field helpers to cut
  cognitive complexity below the threshold (S3776); codes, messages, and
  extraction semantics unchanged (suite-pinned)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* chore: eidetic memory — CI gotchas from the workforce build

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* fix: address Qodo review findings + CLI boilerplate duplication (PR #14)

1. Recorded vectors gain a model tie-out (Qodo #1): recorded_vectors.json is
   now {metadata, vectors} — the refresh script stamps the embedding model/
   endpoint it actually ran against, the committed fixture is wrapped post-hoc
   with its known 2026-07-04 provenance (vector values unchanged), and the
   loader exposes load_recorded_metadata() with legacy-format fallback.
2. --json placement no longer matters (Qodo #2): verb-level --json flags use
   default=argparse.SUPPRESS via a shared helper so they never clobber a
   noun-level --json; applied to all six noun groups including meaning.
3. signal collect rejects non-object measurements (Qodo #3): valid JSON that
   is not an object raises SeriesError(collect_measurement_not_an_object)
   naming the offending file — exit 1, never an AttributeError.
4. Duplication: the per-noun file-error guard, remediation strings, and
   reference-date parsing move to cli/_commands/_artifact_io.py; the five
   noun modules keep only their domain-specific branches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

* refactor: split collect() point-building into helpers (S3776, complexity 17 -> under 15)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmwUt5WBBbrUc1vt2RdQYk

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant