Skip to content

fix: duplicate goal tool registrations, dead RetryPolicy, and stale config docs - #98

Merged
serkanalgur merged 6 commits into
mainfrom
fix/remove-dead-retry-policy
Sep 28, 2026
Merged

serkanalgur merged 6 commits into
mainfrom
fix/remove-dead-retry-policy

Conversation

@serkanalgur

Copy link
Copy Markdown
Owner

Closes #86, #87, #89. Closes #85 (already fixed by 07460d5).

#85 — bare-id model autocomplete

Already resolved in 07460d5: src/orchestrator.ts matches via
tryParseModelRef(m)?.id instead of split('/')[1], so multi-slash refs
no longer throw. No code change here.

#86 — docs/API.md claimed four config blocks; the code writes eight

getSaveableConfig() (src/config.ts:1429-1484) writes eight blocks:
models, budget, selfHealing, dashboard, notifications, gitFlow, effort,
customRoles. The doc said "all four".

The adjacent "not in this schema" list was also wrong in the other
direction: memory, security and communication are not on
NexusConfig at all (deliberately removed — see the doc comments in
src/types.ts), while agents was missing. Corrected to
agents, learning, cost.

Docs-only. No config-writing behaviour touched.

#87 — three goal tools registered twice

goal.status, goal.complete and goal.list were each added to the
nexus namespace twice. The registry keys on namespace_name, so the
second registration overwrote the first and the first set was
unreachable. Removed 38 lines of dead code (51 → 48 registrations, 48
unique, exact 1:1 with the README tool table).

The surviving set is the richer one: it returns autoContinue,
shouldContinue() and createdAt from goal.status, and goal.complete
is the one that persists state. The removed set would have silently
dropped the write had the order been reversed, so the two were compared
field by field before deleting.

Note: the key this writes (nexus-goal) is written but never read —
GoalManager is an in-memory Map. That is pre-existing and out of
scope here, but it means a goal that is never completed does not survive
a reload. Worth a separate issue.

#89 — dead RetryPolicy and an unreachable 'medium' arm

RetryPolicy and Task.retryPolicy were declared but never read or
written anywhere — the real backoff is selfHealing.retryDelay
(src/orchestrator.ts:1123, 1231, 2049, 3379), which already carries
backoffMultiplier. Removed the duplicate concept.

analyzeComplexity only ever produced 'low' | 'high', so the
riskLevel === 'medium' ? 15 : 0 arm at src/orchestrator.ts:3934
could not run. Narrowed the local type and removed the arm.

ComplexityScore.riskLevel stays 'low' | 'medium' | 'high' — that is
public shape and src/templates.ts:65 still produces 'medium', which
test/templates.test.ts:121 asserts. It simply never reaches the
difficulty ladder, because selectQualifiedModel recomputes via
analyzeComplexity instead of reading task.complexity.

Also updated the stale retryPolicy reference in TECHNICAL_DESIGN.md
and three comments in test/config-knobs.test.ts that justified
themselves by pointing at the deleted type.

Tests

test/issue-regressions.test.ts, 14 tests, each verified by mutation
(reverting the corresponding fix makes the right tests fail):

  • no duplicate tool names, and exact 1:1 with the README tool table
  • getSaveableConfig() block count matches what API.md documents, and
    the "not in this schema" list matches NexusConfig in both directions
  • retryPolicy appears in no source file and no doc
  • analyzeComplexity exposes no third risk level

Typecheck, lint and build are clean. bun test goes 1322 → 1335 passing,
0 failing.

Review notes

Two things a reviewer may want to weigh in on:

  1. The "second registration wins" reasoning for bug: goal.status, goal.complete and goal.list are each registered twice; the second silently shadows the first #87 relies on OpenCode
    SDK editor.add behaviour, which is not verifiable from this repo (the
    plugin package is not vendored). The conclusion is safe either way —
    the set that survives is the richer one, so removing the other is an
    improvement under both readings.
  2. Task.retryPolicy is a type-level breaking change for any external
    caller constructing a Task literal with that field. Nothing in this
    repo did, and the field was never read.

@serkanalgur
serkanalgur force-pushed the fix/remove-dead-retry-policy branch from 0f903e0 to 7bbedcc Compare September 28, 2026 06:17
… wrong

None of the three is observable at a call boundary, so each is asserted
against the file it was wrong in rather than against behaviour.

#87  src/index.ts registered goal.status, goal.complete and goal.list
     twice. A duplicate editor.add does not throw, the second simply wins,
     so nothing failed. The test parses the registrations out of the
     blanked source and asserts no name repeats, and that the set matches
     README's Tools table in both directions. Two-way matters: a count
     comparison passes when a tool is renamed on one side only.

#86  docs/API.md said "all four" blocks where getSaveableConfig() writes
     eight. The block list is read out of the method body by brace
     matching from the declaration, not the first mention, which is a
     call site. It is then cross-checked against what saveProjectConfig
     actually writes, and against every number word API.md uses to count
     blocks, so the next added block moves the doc with it.

#89  RetryPolicy was a type nothing implemented, and analyzeComplexity
     carried a 15-point 'medium' risk arm no producer could reach. The
     test greps src/ for both names. The 30-point weight is pinned too,
     but note the delta alone cannot see an unreachable branch: no input
     produces 'medium', so a behavioural test stays green. The source
     assertion carries that half, which is why both exist.

Mutation-checked: reverting ce17786 fails the three #87 tests, reverting
6930376 fails the two #86 tests, re-adding the field fails the grep, and
re-adding the arm and its cast fails the body test. 1322 -> 1334.
docs/API.md named memory, security and communication as constructor-only
NexusConfig blocks. None of the three exist on the type: they were
deleted rather than left unread, and saveProjectConfig cannot drop what is
not there. The same sentence omitted agents, learning and cost, which do
exist, so the list was wrong in both directions.

TECHNICAL_DESIGN.md still sketched retryPolicy?: RetryPolicy on Task,
which #89 removed from the type. The doc-sweep test from the previous
commit asserted that stale copy as its expected result, so fixing the
doc would have failed the test; it now asserts the field appears in no
source file and no doc.

The block list itself was untested, which is how it drifted. A new test
derives the constructor-only complement from src/types.ts and compares it
to the sentence in both directions.

Two smaller ones: numberWord() returned undefined past twelve while the
mention regex stopped at twelve, so a thirteenth block would have compared
undefined to undefined. It now throws, naming what to extend. And
saveableConfigBlocks() re-implemented brace matching that already exists
in test/helpers/dashboard-page.ts; it uses matchBrace() instead.
@serkanalgur
serkanalgur force-pushed the fix/remove-dead-retry-policy branch from 7bbedcc to eef7d13 Compare September 28, 2026 06:25
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