Skip to content

fix: decisions-ledger rendering and working-memory pipeline defects (#306, #308) - #310

Merged
dean0x merged 37 commits into
mainfrom
fix/306-308-decisions-memory-pipeline
Aug 31, 2026
Merged

fix: decisions-ledger rendering and working-memory pipeline defects (#306, #308)#310
dean0x merged 37 commits into
mainfrom
fix/306-308-decisions-memory-pipeline

Conversation

@dean0x

@dean0x dean0x commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Summary

One PR closing #306 and #308. Two workstreams: the decisions-ledger render pipeline (parse/projection defects) and the working-memory pipeline (worker verification + bootstrap + prompt-design defects). Every fix landed test-first; the suite grew from 3255 to 3834 tests (104 files), green at every commit.

Issue #308 — decisions-ledger rendering

Original verified defects: formatDecisionBody/formatPitfallBody parsed details with /key:\s*([^;]+)/i — silent truncation at internal semicolons (measured on this repo's own ledger: content truncated on 51 of 65 rows with the old parser, 0 with the new one); unanchored-key false matches (reissue: matched issue: — PF-014's Resolution rendered empty); first-match-wins hijack; newline breakage. Independently, the ledger row was a frozen write-once projection — post-promotion reinforcement of a log row never reached rendered output.

Scrutiny findings also fixed here: the double-assign guard was dead (anchor_id never written back to the log row → silent duplicate anchors possible); pitfall ledger rows never received date (the Learning agent's 7-day protection window was inert for all pitfalls); the projected amendments field was never rendered (and the schema's {date, note} object shape would have rendered as [object Object]); the render summary under-reported index.md bytes (character count vs bytes).

Fixes: exported segmentDetails() semicolon-safe anchored-key parser; armed guard via log write-back; unconditional date stamping + date-pure rendering (row.date || ''); new refresh-anchor <anchor_id> ledger op — finds the ledger row by anchor, the log observation by the ledger row's id (works for the entire pre-existing corpus: 65/65 anchors resolvable, measured), re-projects via toLedgerRow preserving id/anchor_id/decisions_status/ledger-date and stripping legacy fields, re-renders all three files atomically in the lock, echoes the anchor id; amendments rendered last in the entry body (never in index lines, ADR-007); index extraction regexes line-anchored against amendment-text hijack.

Issue #306 — working memory

All three reported symptoms were structurally produced by the design. Scrutiny found two implementation bugs, both fixed: the worker's success check accepted ANY mtime bump with an intact line-1 stamp prefix — a concurrent human edit read as worker success, silently destroying the claimed queue batch; and pre-compact-memory bootstrapped an unstamped file that rendered as "synced @ unknown".

Fixes: staged-write compare-and-swap (the model writes only WORKING-MEMORY.md.new; cksum baseline with ABSENT sentinel taken before the content read so races resolve toward false-conflict, never false-success; on conflict the human edit wins, the staged file is discarded, and .pending-turns.processing is kept as the retry vehicle; the watchdog can now only tear the disposable staged file; residual ms-scale TOCTOU documented and accepted). Pre-compact bootstrap is stamped and gated on non-empty branch + 40-hex HEAD sha (detached HEAD and unborn-branch skip — an unstamped bootstrap would recreate the defect). The design gaps (stale claims outliving windowed evidence, no reconciliation instruction, conversation-coined labels promoted to durable state) are closed LLM-centrically in the worker prompt: RECONCILE BEFORE CARRYING FORWARD, STATUS DISCIPLINE BOTH DIRECTIONS, PROVENANCE blocks, bounded hex-gated+ancestry-checked COMMITS_SINCE evidence, TURNS_NOTE disclosure when the turns window caps — deterministic code stays plumbing. Refresh-failing detection now counts an orphaned .pending-turns.processing toward queue depth (CONFLICT makes it a designed recurring state).

Design decisions

ADR-022: the log is the single content authority; the ledger is an anchor registry; ops project log→ledger→.md; citation preservation routes through log-edit + refresh-anchor (direct ledger edits removed from the Learning agent contract). D2: strict canonical re-projection with incremental legacy-field normalization (no migration). D3: pattern refreshes too — consumers match ## (ADR|PF)-NNN: anchors, never titles. D4: raw_body mirrors the log row (refresh drops it if the log row lost it — by design, the sanctioned un-freeze path). D5: no date backfill (backfill = fabrication); protection-window fallback to the log row's last_seen at read time. ADR-023: staged CAS chosen over nonce-in-stamp. Stamp format and the 5-section memory set are byte-unchanged throughout.

Changes

Hook scripts

  • src/assets/scripts/hooks/background-memory-update — staged-write CAS, reconciliation prompt, TURNS_NOTE, refresh-failing detection
  • src/assets/scripts/hooks/json-helper.cjs — date stamping, double-assign guard, refresh-anchor arithmetic
  • src/assets/scripts/hooks/lib/decisions-format.cjs — segmentDetails parser, amendments rendering, date-pure fields, index regex anchoring
  • src/assets/scripts/hooks/lib/render-decisions.cjs — amendments field rendering, byte-count correction
  • src/assets/scripts/hooks/lib/mkdir-lock.cjs — minor update
  • src/assets/scripts/hooks/pre-compact-memory — stamped bootstrap, detached-HEAD gate, non-empty branch check
  • src/assets/scripts/hooks/session-start-memory — timestamp sync updates

Agent contract

  • src/assets/agents/learning.md — refresh-anchor op signature, log-projection semantics, amendment disclosure

Documentation & Reference

  • docs/reference/file-organization.md — four-op ledger semantics, CAS design, pipeline sections
  • docs/working-memory.md — reconciliation discipline, TURNS_NOTE semantics, staged-write guarantees
  • CLAUDE.md — Worker prompt disciplines and ADR/PF protections documented
  • CHANGELOG.md — Release notes

Feature knowledge & learning

  • .devflow/features/index.md — learning-capture-system index update
  • .devflow/features/learning-capture-system/KNOWLEDGE.md — four-op ledger, memory CAS, refresh-anchor, ADR-022/ADR-023

Observability & core

  • src/core/observations.ts — minor updates

Tests (104 files; from 3255 to 3834 tests)

  • tests/decisions/decisions-format.test.ts — 544 lines: semicolon round-trip, segmentDetails correctness, anchored-key matching
  • tests/decisions/index-content.test.ts — 78 lines: index extraction regex line-anchoring
  • tests/decisions/render-decisions.test.ts — 56 lines: amendments rendering, byte-count accuracy
  • tests/decisions/ledger-ops.test.ts — 391 lines: refresh-anchor corpus resolvability (65/65), projection invariants, armed guard, pattern refresh
  • tests/eager-memory-refresh.test.ts — 745 lines: CAS success/conflict/disobedient-model paths, stamped bootstrap, human-edit survival, queue depth CONFLICT accounting
  • tests/capture-hooks.test.ts — updated capture scenarios
  • tests/learning-agent.test.ts — updated curation flow
  • tests/shell-hooks.test.ts — hook file presence check

Commits (oldest first)

a584916 test: harden memory-worker spawn tests and add background-memory-update to bash -n gate
3cdc60b fix(learning): segment-parse details fields semicolon-safely in decisions-format
53eaf59 fix(learning): arm assign-anchor double-assign guard via anchor_id write-back
7878bd8 fix(learning): stamp date on pitfall ledger rows and make render date-pure
5ce582e feat(learning): add refresh-anchor ledger op for post-promotion re-projection
99e8ce7 feat(learning): render amendments line and line-anchor index extraction regexes
5cd5338 fix(memory): staged-write compare-and-swap replaces mtime success verification
6965605 fix(memory): stamp pre-compact bootstrap and align canonical sections
02f5843 feat(memory): reconciliation-aware worker prompt with bounded git evidence
da299d0 fix(memory): count orphaned .processing in refresh-failing queue depth
2f9e78e fix(learning): report actual index.md byte count in render summary
1f04ccd docs(learning): four-op ledger contract, refresh-anchor, memory CAS and pipeline docs
58d1448 refactor: simplify buildIndexContent loops, factor CLI USAGE, fix refresh-anchor error wording
a494646 fix(learning): render {date,note} amendments instead of [object Object]
5b3de20 test(memory): pin ADR-023 CAS guarantees and non-empty scan corpora
f3f0402 fix(learning): resolve refresh-anchor log row by ledger id per ADR-022 projection algorithm
6de7c18 docs: correct date-field comments and backup.json attribution
9636675 fix(memory): reconciliation prompt headers, TURNS_NOTE, and detached-HEAD bootstrap gate
3b0a2cf fix(learning): refresh-anchor emits anchor_id to stdout and corrects algorithm comment
2e11e8b docs: correct stale descriptions and add acceptance-criteria pins
d4b8cc7 docs(knowledge): refresh learning-capture-system KB for four-op ledger and memory CAS

Testing & verification

Per-commit green discipline (full build + suite at every commit). Final: 3834 tests / 104 files, build clean. Independent re-validation passed. An adversarial self-review plus a plan-alignment review each ran against the branch; everything they found was fixed in-branch (amendments object shape, refresh-anchor lookup key + stdout, prompt-block completeness, bootstrap gate scope, stale docs) — the fixes are in the commit list. Sandboxed end-to-end QA executed 12 acceptance scenarios against the real scripts (semicolon round-trip, frozen-projection flip ABSENT→PRESENT via refresh-anchor, legacy-corpus resolvability, armed guard, amendments, render-decisions.cjs --check drift-free, CONFLICT with surviving human edit, success swap, disobedient-model FAIL path, stamped bootstrap → synced @ <real-sha>, prompt-evidence capture, State-C visibility) — all passed. Corpus measurement: old parser truncated 51/65 rows, new parser 0/65; refresh-anchor resolves 65/65 anchors.

Additional findings (documented only — pre-existing, out of scope)

  • decisions-log.jsonl is rewritten whole-file under two different locks (assign-anchor under .decisions.lock, rotate-observations under .observations.lock) — two locks, one file.
  • parseLedger silently drops malformed JSONL lines; combined with whole-file rewrites a malformed line can be dropped permanently.
  • pattern is interpolated into ## {anchor}: {pattern} headings without the newline collapse applied to details fields.
  • Test residue: two perf tests carry expect(true).toBe(true) fallback branches; one always-true conjunct; shell-hooks.test.ts silently skips a missing hook file.
  • Deliberate normalization (pinned): the segment rejoin uses '; ', so a tight TL;DR renders as TL; DR — content-preserving.

Rollout

Installed projects pick these up on the next devflow init (hooks copy verbatim; no build step). Rendered decisions/pitfalls .md may show one-time --check drift under the new parser — self-heals on the next ledger op.

Related Issues

Closes #306
Closes #308

dean0x added 21 commits August 30, 2026 01:34
…te to bash -n gate

- Add background-memory-update to HOOK_SCRIPTS syntax-check list in shell-hooks.test.ts
- Deflake capture-hooks.test.ts 'spawn happens' test: bounded poll (4000ms × ≤3 attempts)
  for terminal worker-log line, explicit 15000ms it-timeout, replaces 5000ms deadline
- Deflake eager-memory-refresh.test.ts S11: same bounded terminal-log poll before
  dream-dir assertion prevents afterEach rmSync racing with the detached worker
…ions-format

Add segmentDetails(detailsStr, keys) to decisions-format.cjs.  The function
splits on ';' and anchors key detection to the START of each trimmed segment
rather than scanning the full string with unanchored regexes.  This fixes two
classes of bug (PF-042: 111 measured truncations):

- Embedded semicolons in a field value were silently truncated at the first ';'
  (e.g. "area: src/hooks/; src/core/" was read as "src/hooks/" only).
- Unanchored regexes false-matched substrings: "issue:" inside "reissue:" caused
  the issue field to capture the wrong value; "issue:" embedded in an area value
  caused the same hijack.

A segment with no recognised key is treated as a continuation of the previous
field's value so embedded semicolons are preserved.  Newlines in values are
collapsed to a single space.  All seven field keys (context, decision, rationale,
area, issue, impact, resolution) are covered.

Wire both formatDecisionBody and formatPitfallBody to use segmentDetails via the
new ADR_KEYS / PF_KEYS module-level constants.  Export segmentDetails for direct
testing and future consumers.  Update BYTE-COMPAT CONTRACT comment to document
the parser strategy.  applies PF-042.
…ite-back

The double-assign guard (b) checked whether the observation row already had
anchor_id set, but assign-anchor only wrote status: 'created' back to the log
after promotion — never anchor_id.  A second assign-anchor call for the same
obs_id would read aaObs.anchor_id as undefined, pass the guard silently, and
mint a duplicate ADR/PF number corrupting the committed ledger.

Write anchor_id: aaAnchorId alongside status: 'created' in the log write-back
so the guard fires correctly on any subsequent call.  Add a regression test
that performs the full live double-assign round-trip (first call succeeds and
mints ADR-001; second call is rejected).  applies ADR-022.
…-pure

Two related fixes (A3):

D5 (render purity): formatDecisionBody used `row.date || new Date()...` as a
clock-read fallback making the formatter non-deterministic.  Replace with
`row.date || ''` so an absent date renders an empty Date line rather than
injecting today's date — the renderer is now pure and idempotent.

Unconditional date stamp: assign-anchor previously set date only on decisions
(`assignType === 'decision' ? ... : undefined`).  Pitfall ledger rows had no
date field, so refresh-anchor could not re-project them correctly (ADR-022).
Stamp all entry types with the obs date (falling back to today) to close the
asymmetry.  The formatted pitfall body is unchanged — formatPitfallBody never
renders a Date field regardless of whether the row carries one.

Invert the "does NOT set date on pitfalls" ledger-ops test to assert the new
behaviour.  Add the D5 dateless-decision test for formatDecisionBody.
applies ADR-022, D5.
…ojection

refresh-anchor <anchor_id> re-projects the current log observation onto the
committed ledger row and re-renders both .md files.  This closes the log-
authority gap: once the Learning agent reinforces an existing obs in the log
(updated details or pattern), calling refresh-anchor propagates those changes
into the ledger without re-minting a new anchor number (ADR-022).

Algorithm:
  1. Locate the obs in decisions-log.jsonl by anchor_id (log is content authority)
  2. Locate the existing ledger row by anchor_id (to recover decisions_status)
  3. Re-project via toLedgerRow — strict D2 canonical projection strips all
     observation-lifecycle fields (confidence, evidence, quality_ok, …)
  4. Replace the ledger row, write back atomically, re-render both .md files

Locking discipline mirrors retire-anchor: holds .decisions.lock; uses throw
(never process.exit) inside the locked region so finally always releases the
lock (PF-014).  Bare-dir path creates .devflow/learning/ via mkdirSync on
path.dirname(lockDir) before acquiring the lock (PF-013).
applies ADR-022.
…on regexes

Two changes (A5):

formatAmendmentsLine: new exported pure helper that renders an amendments array
as a single '- **Amendments**: text1; text2\n' line, or returns '' for absent
or empty arrays.  Wire into formatDecisionBody and formatPitfallBody so amendment
history is visible in both decisions.md and pitfalls.md (ADR-007: NOT in
index.md).  The byte-compat contract comment is updated to document the new
optional Amendments line position (after Source).

extractEntryFromBlock hardening: the Status and Area regexes inside
buildIndexContent used unanchored patterns (/- \*\*Status\*\*: (.+)/ and
/- \*\*Area\*\*: (.+)/).  Amendment text that happens to contain those patterns
— e.g. "[2026-01-01] changed from - **Status**: Deprecated to Accepted" —
could appear before the real field line in a raw_body entry, causing the index
to show the wrong status or area.  Replace both with line-anchored multiline
patterns (/^- \*\*Status\*\*: (.+)/m and /^- \*\*Area\*\*: (.+)/m) so only
actual list-item lines at column 0 are matched.  applies ADR-007.
…ification

The previous verification check accepted any mtime bump as success, including
human edits to an already-stamped file. On false-success the worker deleted the
unprocessed queue batch and touched .last-refresh-ok — silent data loss.

B1 changes:
- Define STAGED_FILE=WORKING-MEMORY.md.new; clean stale staged after lock acquire
- Capture PRE_RUN_CKSUM (cksum/ABSENT sentinel) before content read
- Repoint prompt write instruction to STAGED_FILE, not the real path
- Replace mtime check with CAS: staged present+stamped → cksum re-check → mv
  - CONFLICT (cksum mismatch): discard staged, retain .processing, no ok-touch
  - FAIL (staged absent/unstamped): retain .processing, no ok-touch (S17 pin preserved)

Tests: update 7 fake-claude shim sites to write ${memFile}.new; add cksum to
buildNoJsonParsePath symlink farm; add S21 suite (4 tests) exercising absent-
pre-run success, CONFLICT path, stale-staged cleanup, and staged-path-in-prompt.
applies ADR-023 (staged compare-and-swap)
Without a stamp on line 1 of the bootstrapped file, session-start-memory's
parse_and_validate_stamp returned STAMP_SHA="unknown", rendering the
three-state header as "synced @ unknown" instead of a real SHA.

B2 changes in pre-compact-memory:
- Capture GIT_HEAD_SHA from git rev-parse HEAD
- Bootstrap guard: require non-empty GIT_HEAD_SHA (non-git dirs skip bootstrap)
- 40-hex length+case gate before embedding SHA in stamp line
- Line 1 of bootstrapped file: <!-- memory-head: <sha> branch: <branch> -->
- Canonical 5 sections: ## Now, ## Progress, ## Decisions, ## Context, ## Session Log
- Modified files info moved under ## Context; ## Modified Files removed

Tests: S22 suite — 40-hex stamp gate, all 5 sections present, non-git skips,
existing file untouched.
…dence

Adds commits-since-stamp evidence so the LLM can reconcile the memory
against actual repo history rather than only the captured turn buffer.

B3 changes in background-memory-update:
- Compute TODAY (YYYY-MM-DD UTC) for provenance line in prompt
- Extract STAMP_SHA from EXISTING_MEMORY line 1 via parameter expansion (PF-008 safe)
- Hex-gate (7-40 chars, lowercase) + ancestry check before running git log
- COMMITS_SINCE_NOTE: N commits since stamp, "(none — memory is current)", or
  a diagnostic when stamp is absent/invalid/non-ancestor
- Add RECONCILE section to prompt with COMMITS_SINCE_NOTE
- Add PROVENANCE line to prompt: "today is ${TODAY}"

Tests: S23 suite — TODAY in prompt, commits-since with valid ancestor stamp,
no-stamp diagnostic path.
applies ADR-023 (provenance)
Before this fix, detect_refresh_failing only counted .pending-turns.jsonl
lines for _queue_depth. An orphaned .processing file (a crashed worker's
atomic queue claim — mtime between 0s and the D56c 300s cold-path gate)
was invisible to State-C and never triggered the REFRESH FAILING banner.

B4 changes in session-start-memory:
- detect_refresh_failing also sums .pending-turns.processing line count
  into _queue_depth (additive; existing .jsonl count unchanged)
- Test S24: orphaned .processing at 200s (above 0, below 300s cold-path
  threshold) triggers State-C; .processing with fresh .last-refresh-ok
  does not (pipeline healthy)
String.length returns UTF-16 code units, not UTF-8 bytes. The em dash
separator in index entry Area fields (U+2014, 3 UTF-8 bytes, 1 JS char)
caused the logged index.md size to undercount by 2 bytes per entry.

Switch all three file size reports to Buffer.byteLength(content), matching
how bytes are actually written. For index.md, include the trailing '\n'
added before writeAtomic so the logged count equals the on-disk file size.

Add a pin test (render-decisions.test.ts) that renders a pitfall with an
Area field, parses the three logged byte counts, and asserts each equals
the actual file's stat().size.
…nd pipeline docs

Commit 2/2 on fix/306-308-decisions-memory-pipeline.

Learning agent (learning.md):
- Add refresh-anchor as the third ledger op between retire-anchor and
  rotate-observations; update the four-op environment table and the
  ≤5 curation-bound note (refresh-anchor calls don't count toward it)
- Document details field grammar: Key:value segments separated by ';';
  recognised keys and semicolon-in-value preservation (fixes PF-042)
- Add post-reinforce bullet: run refresh-anchor after sharpening an
  already-anchored observation so updated log content reaches rendered
  output (D1/ADR-022)
- Extend D5 fallback: if ledger row lacks a date field, fall back to
  last_seen from the log row; if also missing, treat as outside the
  protection window (pitfall rows promoted before date-stamping)
- Add PF-040 gate: classify paths in ledger entries as live pointers
  (repair drift) vs historical citations (leave intact) before deciding
  whether to act on a missing path
- Rewrite citation-preservation guidance to use log + refresh-anchor
  (log-is-content-authority, ADR-022); no direct ledger-row edits

Pin tests (learning-agent.test.ts, learning-curation.test.ts):
- Extend four-ops test to assert refresh-anchor in both test files
- Add D5 fallback regex pin to the 7-day protection-window test

Docs:
- CLAUDE.md: add refresh-anchor to LLM-vs-plumbing op list; update
  Working Memory paragraph with CAS flow (STAGED_FILE, PRE_RUN_CKSUM,
  UPDATED/CONFLICT/FAIL, reconciliation-aware prompt), State-C .processing
  count, PreCompact bootstrap stamp; add WORKING-MEMORY.md.new to file
  listing; update ledger comments (anchor registry / content authority)
- docs/working-memory.md: document CAS flow, pre-compact bootstrap,
  WORKING-MEMORY.md.new staged file in the file structure table
- docs/reference/file-organization.md: update background-memory-update
  row with CAS flow; add four-op ledger paragraph; add refresh-anchor
  to Project Knowledge table

CHANGELOG.md: record all Phase-1/2/3 fixes under [Unreleased]:
  refresh-anchor (Added); semicolon-safe details parsing, armed
  double-assign guard, pitfall date stamping, amendments rendering,
  memory worker staged CAS, pre-compact bootstrap stamp, reconciliation-
  aware prompt, State-C .processing visibility, byte-count log fix (Fixed)
…resh-anchor error wording

- decisions-format.cjs: replace two for-loop/push/join patterns in buildIndexContent
  with Array.map + spread — eliminates the `lines` intermediate in each block
- render-decisions.cjs: factor duplicated CLI usage string into a USAGE constant;
  introduce `indexLine` to avoid computing `indexContent + '\n'` twice in
  renderAndWriteAll
- json-helper.cjs: fix double-negative in refresh-anchor error message
  ("no obs ... not found" → "obs ... not found")
formatAmendmentsLine joined the amendments array directly, but
src/core/observations.ts declares `amendments?: { date: string; note: string }[]`
on BOTH LearningObservation and LedgerRow, and isLearningObservation REJECTS a
plain string element. toLedgerRow copies obs.amendments through verbatim, so the
object shape is the only shape that can legitimately reach the formatter — and it
rendered as `- **Amendments**: [object Object]`. The formatter's unit tests only
ever fed it string[], the one shape the schema rejects, so the sole reachable
input was the untested one (PF-018: the missed site was ratified by a test).

Normalise per entry instead: objects render as `[date] note` (bare note when the
date is absent), strings pass through, newlines collapse to preserve the
single-line field contract, and unrenderable entries are dropped rather than
thrown — a formatter running under .decisions.lock must degrade, never throw.
The object form is byte-identical to the `[date] note` string convention the
existing tests already use, so no pinned output changes.

Also close two branch-introduced contradictions in the Learning agent contract:
the intro still said "the three ledger ops below" above four bullets, and the
Iron Law omitted refresh-anchor from the render-invoking ops — the agent is the
only caller of the op this branch adds.

Adds 12 tests: object/mixed/note-only/newline/unrenderable shapes, both body
formatters, index-leak, plus three segmentDetails specimens that isolate the
anchoring property (the existing decoy tests place the real key after the decoy,
so startsWith -> includes kept them green; the new ones go RED).
Four CAS behaviours the branch introduced had no coverage, each reachable by a
plausible regression:

- ABSENT sentinel: every existing CAS test is ABSENT->ABSENT, so the stated
  "resolves toward false-conflict, never false-success" guarantee was unpinned.
  Adding `|| [ "$PRE_RUN_CKSUM" = "ABSENT" ]` to the swap condition passed the
  whole suite; it now goes RED.
- Prompt write-target: STAGED_FILE is literally MEMORY_FILE + ".new", so the
  existing toContain('WORKING-MEMORY.md.new') is satisfied by a prompt naming
  both paths. Pins the negative half with a lookahead.
- Un-stamped staged file: the stamp-prefix branch of the CAS case statement was
  untested — a loosened prefix check would have mv-ed a disobedient model's
  output over the real file.
- Worker hex gate: the injection guard before `git log <sha>..HEAD` had no test.
  S2's malformed-stamp tests exercise session-start-memory's analogous gate in a
  different file, which reads like coverage but is not.

Also adds the non-empty-corpus assertion to the two learning-agent scan tests
(avoids PF-018 (2): a scan whose corpus empties after a rename passes vacuously
and silently stops guarding anything).
…2 projection algorithm

The refresh-anchor op previously located the log observation by matching
anchor_id in the log — but assign-anchor only writes anchor_id back to
the log row as the double-assign guard (a post-promotion invariant). All
pre-existing anchored entries (65 of 65 on this repo's own ledger) carry
no anchor_id in the log, so the old lookup resolved 0 of 65 entries.

Fix: swap step order — (1) find ledger row by anchor_id first (the stable
canonical key, miss → throw); (2) find log obs by ledger row's id field
(logObs.id === ledgerRow.id), which resolves all 65 of 65 entries. Error
message for the log miss now references the obs id and the anchor it
belongs to so the existing stderr.toContain('not found') assertions keep
passing.

Date preservation: change rfObs.date || rfExistingRow.date to
rfExistingRow.date. This preserves the ledger's existing promotion date
verbatim (D5 no-backfill: a dateless legacy row stays dateless; the
protection-window fallback happens at read time in the Learning agent).

Tests (RED first, then GREEN):
- resolves log row by ledger id when log obs has no anchor_id field
- pitfall-anchor refresh re-renders pitfalls.md and index.md
- date-pin: ledger row date wins over obs date
- date-pin: dateless legacy ledger row stays dateless (D5 no-backfill)

All 3819 tests pass (104 files).
Three comment sites in src/core/observations.ts claimed "Decisions only;
pitfalls have no date field (byte-compat contract)" — now false since this
branch stamps date on all types at assign-anchor time. Each now reads
"Both decisions and pitfalls carry this field (stamped at assign-anchor
time); legacy pre-stamp rows may lack it."

CLAUDE.md (~:177) and docs/working-memory.md (~:38) both attributed the
pre-compact line-1 HEAD-SHA stamp to backup.json — wrong file. The stamp
goes on line 1 of the bootstrapped WORKING-MEMORY.md; backup.json is plain
JSON with no stamp. Both files corrected: backup.json now reads "(plain
JSON — no stamp)" and WORKING-MEMORY.md in CLAUDE.md notes the stamp on
line 1 explicitly.
…HEAD bootstrap gate

Item 1 — worker prompt (background-memory-update):
- Rename reconciliation section to literal header RECONCILE BEFORE CARRYING FORWARD
  with claim-verification guidance (re-verify Now/Progress against evidence,
  contradicted items rewritten, finished/irrelevant moved to Session Log)
- Add STATUS DISCIPLINE, BOTH DIRECTIONS section (never upgrade without evidence
  AND never restate stale claim past contradicting evidence — newer evidence wins)
- Add TURNS_NOTE disclosure in prompt when turn window capped (TOTAL_LINES > MAX_LINES):
  "(showing newest N of M turns — prefer git evidence over conversational claims)"
  Absent from prompt when not capped.
- Preserve PROVENANCE, CONFLICT/FAIL strings, stamp format, staging-path line verbatim

Item 3 — pre-compact bootstrap gate (pre-compact-memory):
- Gate bootstrap on BOTH non-empty branch AND 40-hex sha (was sha-only)
- Detached HEAD: git branch --show-current returns "" → gate fails → skip
- Unborn branch: git rev-parse HEAD fails → sha="" → gate fails → skip
- Added inline comment explaining why each condition prevents the
  "synced @ unknown" / blank-branch defect

Tests: S22 detached-HEAD and unborn-branch tests (RED→GREEN); S23 literal header
and TURNS_NOTE tests (RED→GREEN); Item 5 exact-banner pin and CONFLICT read-back
(acceptance-criteria pins, GREEN by design)
…algorithm comment

- Add process.stdout.write(refreshAnchorId + '\n') after renderAndWriteAll in the
  refresh-anchor try block, mirroring assign-anchor's stdout contract so callers
  can confirm which row was refreshed without parsing stderr
- Fix the numbered algorithm comment (steps 1-4) which still described the old
  anchor_id-field lookup; corrected to describe the id-based ledger-row lookup
  that the code actually performs (id-based covers pre-existing obs that predate
  anchor_id write-back — the anchor_id strategy resolves 0 entries in practice)

Test: RED→GREEN stdout assertion in tests/decisions/ledger-ops.test.ts
Item 4 — stale docs/comments:
- file-organization.md ~:69: background-memory-update comment now says
  "staged write → CAS swap" instead of "rewrites WORKING-MEMORY.md"
- file-organization.md ~:185: pre-compact-memory table row now documents the
  bootstrap path including HEAD-SHA stamp requirement and detached/unborn skip
- file-organization.md ~:189: flow description now says "staged CAS to
  WORKING-MEMORY.md" instead of "rewrites WORKING-MEMORY.md directly via claude -p"
- render-decisions.cjs ~:227: add refresh-anchor to the caller enumeration in
  the renderAndWriteAll JSDoc (was "assign-anchor, retire-anchor")
- mkdir-lock.cjs ~:51: add refresh-anchor to the caller enumeration in
  acquireMkdirLock JSDoc (was "assign-anchor, retire-anchor, render CLI")

Item 5 — acceptance-criteria pins (decisions-format.test.ts, no prod changes):
- Comma positive-control: segmentDetails and formatDecisionBody/formatPitfallBody
  preserve commas verbatim (only ';' splits segments)
- Amendments position pin: Amendments renders LAST (after Source) in both
  formatDecisionBody and formatPitfallBody
- Rejoin-normalization pin: TL;DR in a field value renders as "TL; DR" (the
  documented '; ' rejoin normalization — pinned with an explanatory comment)
@dean0x

dean0x commented Aug 30, 2026

Copy link
Copy Markdown
Owner Author

Code Review — Cycle 1

Full summary withheld (public repository).

Category CRITICAL HIGH MEDIUM LOW Total
Blocking 2 7 15 - 24
Should Fix - 1 6 - 7
Pre-existing - - 2 1 3

Full report: /Users/dean/Sandbox/devflow/.devflow/docs/reviews/fix-306-308-decisions-memory-pipeline/2026-08-30_1658/review-summary.md (not committed; ask the author)


Posted by devflow · cycle 1

dean0x and others added 8 commits August 30, 2026 17:42
…x-sha helper with true-count disclosure

- Create src/assets/scripts/hooks/is-hex-sha: pure-shell hex-SHA validator
  (is_hex_sha <value> [min] [max], no forks, PF-008-safe) replacing three
  divergent inline validators across session-start-memory, background-memory-update,
  and pre-compact-memory (COMP-3 / MEDIUM-2)

- Extract compute_commits_since_note() in background-memory-update: flattens the
  52-line depth-5 reconciliation-evidence block to an early-return function at
  depth 1, leaving the git rev-parse block responsible only for GIT_STATE (COMP-1 / HIGH-1)

- Fix true-count disclosure in compute_commits_since_note: use git rev-list --count
  for the actual total and emit "(showing newest 20)" when total > 20, matching the
  TURNS_NOTE cap-disclosure pattern (REL-4 / MEDIUM)

- Remove awk fork for stamp SHA extraction: pure parameter expansion
  (_rest="${_stamp_line#<!-- memory-head: }"; _stamp_sha="${_rest%% *}") makes
  the PF-008-safe comment accurate (CON-4 / MEDIUM)

- Bound git log subject length: git log --format='%h %.100s' caps each subject
  at 100 chars inside compute_commits_since_note (PERF-S3 / LOW)

- Add is-hex-sha to shell-hooks.test.ts bash -n gate list

Installer ships is-hex-sha automatically via the existing copyDirectory verbatim copy
of src/assets/scripts/; no registration required.
… assert re-projection preconditions

ISSUE 1 (REG-1, avoids PF-044): add divergence guard — if the ledger row
carries details/pattern content absent from the log obs (whitespace-normalised
containment check), throw so curated ledger amendments are never silently
discarded. Also reconciled 22 diverged rows in .devflow/learning/
decisions-log.jsonl (gitignored; not committed) so all 67 anchored entries now
pass the guard. Tests: RED then GREEN regression tests added.

ISSUE 2 (TS-2): add three precondition assertions inside the refresh-anchor
locked try block — no id on ledger row throws, no decisions_status throws, type
mismatch between log obs and committed anchor throws. Tests added.

ISSUE 3 (SEC-S3): throw before mkdirSync when decisions-ledger.jsonl does not
exist at the resolved project root — prevents a stray .devflow/learning/
directory being materialised on a wrong-cwd invocation. Test updated.

ISSUE 4 (CON-6): reframe the id-lookup comment at the log-obs lookup site from
a transient corpus-count snapshot to a permanent invariant with a rationale
(avoids PF-041) and a measured-at-change note.

ISSUE 5 (CON-8): add '// PF-013: ensure parent directory exists before
acquiring lock' above both mkdirSync calls in assign-anchor and retire-anchor,
and above the equivalent call in refresh-anchor.
…RF-2 block reuse

REG-2 (Careful): add recovery pass to segmentDetails — for any key the anchored
(segment-start) pass left unset, an unanchored regex
'(?:^|[.;\\s])key:\\s*([^;]+)' is tried against the full details string.
Handles legacy corpus rows (PF-009, ADR-004) written before the
';'-delimited grammar was documented, where fields are separated by '. '
rather than ';'. Recovery never overrides an anchored match. applies PF-044.

TS-1 (Careful): replace all five bare /\\n/g collapse sites with the shared
LINE_TERMINATORS constant (/[\\r\\n\\u2028\\u2029]/g), covering the full JS
LineTerminator set.  Sites: segmentDetails ×2 (anchored value, continuation
append), recovery pass ×1, amendmentToString ×2 (string form, object note
and date). Guards the single-line field contract against CR-only and LS/PS
terminators that the old /\\n/g pattern passed through unmodified.

PERF-3 (Standard): hoist trimmed.toLowerCase() before the inner key loop in
segmentDetails, avoiding one allocation per key per segment.

SEC-S1 (Standard): correct segmentDetails docstring — "priority order" was
wrong; implementation is last-match-wins (each segment-start match overwrites).

PERF-2 (Standard): accept optional decisionBlocks/pitfallBlocks in
buildIndexContent opts. renderAndWriteAll now pre-renders blocks once via
buildBodyBlocks/buildFileFromBlocks, passes them to buildIndexContent, and
eliminates the previously duplicated per-row render pass.

Tests: 15 new tests (13 in decisions-format, 2 in render-decisions) covering
PF-009-shaped/ADR-004-shaped recovery fixtures, all four LineTerminator
characters through segmentDetails and amendmentToString, the CR→Status-tag
hijack guard in buildIndexContent, the last-match-wins duplicate-key pin, and
byte-equality for pre-rendered-block vs fallback-render index output.
…l log, orphan gate sees retry batch

ISSUE 1 (COMP-2): extract verify_and_swap() — single OUTCOME variable (updated|conflict|failed)
replaces the two-boolean UPDATED/CONFLICT state machine; each state assigned once where decided,
no-op branch and second dispatch eliminated. All existing log strings preserved byte-for-byte.

ISSUE 2 (REL-1): CONFLICT path heartbeats .processing mtime so session-start-memory's 300s cold
path measures worker liveness rather than queue-file turn age. Touch applied at claim time (mv
preserves source mtime) and again on CONFLICT outcome in the OUTCOME dispatch.

ISSUE 3 (REL-3): cksum startup assert guards against absent binary before lock/claim; separate
CKSUM_FAILED flag tracks per-invocation failure on either CAS side and forces conflict outcome,
never a match — keeps the "resolves toward false-conflict, never false-success" invariant true
even when cksum is present but errors for a specific file (EACCES, network mount, etc.).

ISSUE 4 (CON-7): CONFLICT arm in OUTCOME dispatch now emits a terminal summary log line
"CONFLICT: queue retained in .processing, .last-refresh-ok untouched" — matching the terminal
log discipline of the updated and failed arms; mid-stream CONFLICT log line preserved verbatim.

ISSUE 5 (REG-3): orphan-only short-circuit gated on absence of a retry .processing batch via
added `! -f "$PROCESSING_FILE"` conjunct — prevents a CONFLICT batch from being stranded while
a subsequent user-only .jsonl is drained.

Applies ADR-023 (staged CAS). RED-first evidence: all 4 new S25 tests (REL-1, REL-3a, REL-3b,
REG-3) confirmed RED before implementation, GREEN after.

Co-Authored-By: Claude <noreply@anthropic.com>
…thDecisionsLock extraction

SEC-1 / PF-023: Validate LLM-authored log fields at the toLedgerRow convergence point so
assign-anchor and all future ops inherit the guards without repeating them:
  (a) Pattern: collapse JS LineTerminators to a single space — a newline in pattern would
      forge '- **Status**:' lines or second '## ADR-NNN:' headings that the line-anchored
      index regexes in extractEntryFromBlock would match first (BEFORE the real field).
  (b) expectType option: toLedgerRow throws on obs.type !== expectType; passed from
      refresh-anchor as expectType: rfExistingRow.type (B1's outer throw fires first with
      its clearer message; the sink is the PF-023 authority for all callers).
  (c) raw_body gate: isSafeRawBody(body, anchorId) — accepts only a string whose
      ^## (ADR|PF)-\d+: headings number exactly one AND match ## anchorId:; a rejected
      raw_body is dropped so the entry renders through the sanitised formatters (ADR-022 D4).
  ARCH-S3: add ADR-022 D4 comment on the raw_body pass-through in toLedgerRow.

GUARD HARMONIZATION: narrowed REG-1 divergence guard to DETAILS only (avoids PF-044).
  Pattern replacement is sanctioned per D3 — consumers match ## (ADR|PF)-NNN: anchors,
  never titles; raw_body loss is sanctioned per D4; isSafeRawBody handles it in toLedgerRow.
  Removed the pattern containment check; kept the details containment check.

COMP-4 / PF-013 / PF-014: Extract withDecisionsLock(opName, projectRoot, fn) and
  serializeLedger(rows) helpers shared by assign-anchor, retire-anchor, and refresh-anchor.
  Named LOCK_ACQUIRE_TIMEOUT_MS / LOCK_STALE_MS constants eliminate magic numbers at all
  call sites. PF-013 (parent-dir creation) and PF-014 (throw-not-exit inside fn) are now
  structurally enforced by the helper rather than repeated by hand at four sites.

PERF-1 / PF-026: Make refresh-anchor variadic — refresh-anchor <id> [<id>...] acquires
  ONE lock, parses ledger+log ONCE, re-projects all anchors with all-or-nothing semantics
  (any throw leaves nothing written), writes the ledger ONCE, renders ONCE. Single-anchor
  invocation is byte-compatible (same stdout, same exit codes). Usage error on zero args.

REL-6: Assert row count unchanged before writing refreshed ledger — bounds the documented
  parseLedger silent-drop exposure (rfExpectedRowCount guard before writeFileAtomic).

CON-P1: retire-anchor now echoes anchor_id to stdout, matching assign-anchor and
  refresh-anchor so all four ops report their result without parsing stderr.

Tests (18 new, all green after implementation):
  SEC-1: pattern newline collapse, raw_body second-heading dropped, mismatched-anchor
    dropped, safe body preserved, expectType mismatch throws, end-to-end buildIndexContent
    hijacking prevented, isSafeRawBody unit tests (8 assertions).
  Guard harmonization: D3 pattern-replacement succeeds + rendered heading updates,
    D4 raw_body-lost refresh succeeds + entry renders formatter-generated.
  PERF-1: multi-anchor happy path (both rows re-projected, stdout = both ids joined),
    one-bad-anchor aborts with nothing written, zero-args usage error.
  REL-6: row count invariant check passes on valid multi-anchor refresh.
  CON-P1: retire-anchor stdout echo confirmed.
  RED-first evidence: (1) pattern divergence test was RED (exit 0 instead of non-zero) —
    updated to D3 test; (2) all other new tests were written RED-first before implementation.
…etry, dedupe poll helper

Issue 1 (TEST-1, HIGH): strengthen the commits-since positive-branch test to pin
the exact count literal '1 commit(s) since last memory update:' and add
.not.toContain guards for the no-stamp and up-to-date literals. The previous
assertion on the commit subject alone was vacuous — the subject already appears
in GIT_STATE's git log -5 output regardless of whether the COMMITS_SINCE block
ran (avoids PF-018). PF-018 neuter-verified: with compute_commits_since_note
suppressed the count assertion goes RED and .not.toContain(no-stamp) goes RED,
while the subject-only assertion stays GREEN — confirming the old test was
vacuous and the new one is not.

Issue 2 (TEST-2, MEDIUM): tighten the no-stamp test to the exact literal
'(no stamp found in existing memory — full synthesis)' and add two missing
branch tests: stamp SHA not an ancestor of HEAD (divergent branch/rebase path)
and HEAD == stamp (memory-is-current path). All five COMMITS_SINCE branches now
pinned by their exact literals (avoids PF-018 alternation-regex weakness).

Issue 3 (TEST-S1, LOW→FIX_NOW): add end-to-end two-run CONFLICT→clean test
that composes the CONFLICT retention guarantee with the leftover-merge path,
proving no turns are lost across a conflict (applies ADR-023).

Issue 4 (TEST-S2, LOW→FIX_NOW): extract pollForTerminalLine into
tests/helpers/poll-for-terminal-line.ts and import it from both
eager-memory-refresh.test.ts and capture-hooks.test.ts, keeping the total
12 s bound (3×4000 ms) in one place.

Issue 5 (CON-S3, LOW): replace the bare 'B4:' plan-marker comment in
session-start-memory (depth-count region) with descriptive prose
'orphaned-processing depth fix:' to avoid collision with the unrelated
'B4:' in src/cli/commands/init.ts:658.
… hatch, variadic+bounded refresh contract

ARCH-1: Add amendments authoring clause to learning.md Part 1 — when reinforcing an anchored
observation with a dated correction/ratification, APPEND {date, note} to the log row's
amendments array and call refresh-anchor to propagate (ADR-022, avoids PF-024). Update
KNOWLEDGE.md amendments/formatAmendmentsLine passage to name the Learning agent as producer.

ARCH-2: Remove the render-decisions.cjs manual-re-render escape hatch from the Iron Law;
replace with "every ledger op re-renders internally; there is no separate render step."
Drop render-decisions.cjs from KNOWLEDGE.md ownership list — .md files are exclusively owned
by the three ledger ops (assign-anchor/retire-anchor/refresh-anchor), each rendering internally.

DOC-4: Change toLedgerRow description in KNOWLEDGE.md from a strip-list to a positive
WHITELIST: committed row is {id, type, pattern, details, anchor_id, decisions_status} plus
optional date/raw_body/amendments; every other field dropped. Note collapsing of line
terminators in pattern, expectType enforcement, and isSafeRawBody gate.

DOC-5: Update details grammar in learning.md to be per-type and disjoint — decisions:
context/decision/rationale; pitfalls: area/issue/impact/resolution. Cross-type keys are
silently appended to the previous field; note the parser's recovery pass for legacy keys.

DOC-8: Update KNOWLEDGE.md Directory bootstrapping to enumerate all three .decisions.lock
callers (assign-anchor, retire-anchor, refresh-anchor) using the invariant phrasing (PF-013).

PERF-1: Update refresh-anchor references (reinforcement step + citation-preservation step)
and KNOWLEDGE.md op list/signature to variadic form. Instruct the agent to BATCH anchor ids
into ONE call per phase instead of one call per row.

REL-6: Replace unbounded refresh-anchor exemption with an explicit bound — at most 10 anchors
per run, batched into a single variadic call. Mirror in KNOWLEDGE.md anti-pattern and bound
passages.

CON-1: Resolve plan-local D-tokens in learning.md — (D1/ADR-022) at :42 and :211 → (ADR-022);
bare (D5) at :163 → inline rationale (no backfill: a fabricated date would be worse than an
unprotected entry — ADR-022).

Applies ADR-022, ADR-003, avoids PF-024, PF-025, PF-026, PF-013.
dean0x and others added 7 commits August 30, 2026 18:30
… PF-043 cross-check

ISSUE 1 (CON-1): comment-only, zero behavior change
- decisions-format.cjs: renumber new segmentDetails doc from D001 → D002
  (pre-existing toLedgerRow D001 at line 309 stays untouched)
- decisions-format.cjs: replace plan-local D5 shorthands (lines 12, 252) and
  D4 shorthands (lines 207, 351) with spelled-out ADR-022 references
- json-helper.cjs: replace three D3 / A3 plan-local shorthands (assign-anchor
  date-stamp comment, refresh-anchor algorithm comment, divergence-guard comment)
  with spelled-out ADR-022 references

ISSUE 2 (TEST-3): D3 rendered-file assertion already present post-commit 0abbbe6;
  D4 direction-1 (log lost raw_body) already covered; adds D4 direction-2 test:
  log row carries safe raw_body → survives into refreshed ledger row

ISSUE 3 (TEST-4): import isLearningObservation in decisions-format.test.ts and
  add PF-043 cross-check test — canonical {date,note} fixture passes the guard
  AND formatAmendmentsLine renders it correctly, making the two suites unable to
  drift silently (avoids PF-043)

ISSUE 4 (REG-S1): frozen corpus fixtures derived from live ADR-001 and PF-001
  ledger rows (PF-044 fixture lesson, PF-035 no-cat); asserts refresh-anchor
  succeeds and renders non-empty Context/Decision and Impact/Resolution fields

ISSUE 5 (PERF-S1): NO_CHANGE_NEEDED — each Buffer.byteLength call is already
  computed exactly once per string at the point the string is final; no
  redundancy to eliminate

476 tests green (was 472)
…g Changed entry, window example

- DOC-P1: fix false PF-003 rationale at 3 sites (learning.md Finishing, KNOWLEDGE.md
  Final-act bullet, KNOWLEDGE.md anti-pattern). The deny rule keys on the FLAGS, not the
  verb: `rm -f` is denied; `unlink` and a flagless `rm` both pass.

- REL-S2: correct "re-renders all three files atomically" overstatement in learning.md Iron
  Law and KNOWLEDGE.md assign-anchor description. Now reads: each write atomic; the sequence
  is not transactional — a crash between writes self-heals on the next op.

- CON-S1: change "unlinks `.new`" → "discards `.new`" in docs/working-memory.md CONFLICT row
  (unlink is reserved for flagless-rm-equivalent; `rm -f` is the actual code path).

- CON-S2: add `# same op, both types` comment on second assign-anchor line in KNOWLEDGE.md
  ledger-ops block so the "exactly four" count reads true.

- DOC-S3: add ### Changed entry under [Unreleased] in CHANGELOG.md documenting the
  ADR-022 ledger-semantics demotion (content authority → anchor registry, direct-edit path
  removed). Tooling that reads/writes decisions-ledger.jsonl directly is affected.

- COMP-S2: add 2-line worked example after the 7-day protection window fallback chain in
  learning.md Part 2, making "unavailable" concrete.

- test: update learning-agent.test.ts pin — tighten regex from `/\brm -/` (too broad, caught
  PF-003 explanatory text) to `/\brm -[rf]+[^\n]*\.pending-turns\.processing/` (guards the
  actual delete command, not explanatory prose). Name updated to describe the corrected rule.
…lpers (B12)

CON-2 (HIGH): Remove all RED/GREEN/A-series transition markers from 5 test files
— 24 markers stripped, rewritten as present-tense behavioral invariants.
Applies ADR-003 (end-state, not transition) per PF-025.

COMP-5 (MEDIUM): Extract createPromptCapturingShim helper; replace 11 byte-identical
4-line inline shim writes in S23 with a single helper call. One variant
(branch: \${defaultBranch}) left in place — it differs meaningfully.

COMP-S3 (LOW): Extract makeWorkerFixture(prefix, assign) factory for S21/S23/S25
describe blocks, which share identical beforeEach/afterEach setup. Callback
pattern keeps all test bodies unchanged (PF-018: no assertion weakening).

TEST-S3 (LOW): Tighten learning-curation dotall regex to an exact phrase
from the current learning.md protection-window passage — eliminates the
overly broad /is flag spanning hundreds of lines (ADR-022).

REL-S2 (LOW): Correct json-helper.cjs comment near refresh-anchor renderAndWriteAll
— "re-render both .md files" → "re-render all three files (each write atomic;
sequence not transactional)" per PF-025.

All 555 tests pass at the same count as the pre-B12 baseline.
TASK-ID: resolve-b12
Remove three transition-prose violations (ADR-003) introduced across
parallel agent edits today:

- json-helper.cjs: strip "The old decision-only asymmetry is removed"
  from assign-anchor date comment; rephrase to end-state
- json-helper.cjs: flatten doubled "pattern replacement is sanctioned"
  parenthetical in refresh-anchor algorithm comment
- background-memory-update: strip "replaces the prior two-boolean
  (UPDATED/CONFLICT) encoding" from verify_and_swap contract comment

Zero behavior change. 587 tests unchanged.
Adds is-hex-sha shared helper (min/max-len bounds, three callers),
compute_commits_since_note() five outcome literals (test contract),
verify_and_swap() OUTCOME enum + CKSUM_FAILED fail-closed sentinel,
CONFLICT heartbeat-touch semantics, refresh-anchor REG-1/SEC-S3/REL-6
guards + withDecisionsLock/serializeLedger extraction, segmentDetails
recovery pass + LINE_TERMINATORS full set, noclobber-atomic pre-compact
bootstrap, and prompt named XML data-containment tags (PF-023).
Today's edits introduced legitimate technical terms ("schema validator",
"hex-validator") that case-insensitively match the retired agent name
'Validator'. Rewording only — no logic changes:

- src/assets/agents/learning.md: "schema validator" → "schema guard"
- src/assets/scripts/hooks/is-hex-sha: "hex-validator" → "hex-check helper"
- .devflow/features/learning-capture-system/KNOWLEDGE.md:
  "hex validator" → "hex-check helper" (×2)

All 16 agent-name-guards tests pass; 57 learning/curation tests pass.
No RETIRED_ALLOWLIST entries needed — pure rewording.
…stic

Replace subtractive PATH filtering (PATH=${farm}:/bin) with an additive
symlink farm that never contains cksum. On Linux, /bin carries cksum so
appending /bin to PATH silently re-introduced it, causing the startup
assert to skip and WORKING-MEMORY.md to be created — making the test
fail despite the behavior under test being correct.

buildNoCksumPath() mirrors buildNoJsonParsePath(): it builds a farm with
all tools the worker and helpers need (sourced from /usr/bin then /bin
to cover both macOS and Linux layouts), but deliberately omits cksum.
PATH is set to only that farm dir; command -v cksum fails on any platform.

RED/GREEN verified locally: removing the startup assert (lines 96-99)
makes the test fail on the log assertion; restoring it makes all 79
tests pass.

Co-Authored-By: Claude <noreply@anthropic.com>
@dean0x

dean0x commented Aug 30, 2026

Copy link
Copy Markdown
Owner Author

Resolution Summary

Full summary withheld (public repository).

Metric Value
Total Issues 78
Fixed 60
False Positive 2
By Design 4
Deferred 1
Blocked 0
Escalated 0
Duplicate 11

Full report: .devflow/docs/reviews/fix-306-308-decisions-memory-pipeline/2026-08-30_1658/resolution-summary.md (not committed; ask the author)

Posted by devflow

@dean0x
dean0x merged commit a8fdeaf into main Aug 31, 2026
1 check was pending
@dean0x
dean0x deleted the fix/306-308-decisions-memory-pipeline branch August 31, 2026 07:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant