When the grade_takes phase auto-resolves a take as 'incorrect' or 'partial',
optionally write a learning entry to gstack's per-project learnings.jsonl
so other gstack skills (plan-ceo-review, ship, investigate, ...) can pull
it as context when relevant. The brain teaches every other tool about
the user's track record.
Config gate (D5 / CDX-17 mitigation):
`cycle.grade_takes.write_gstack_learnings` defaults FALSE. External
users may not have gstack installed; the gstack-learnings binary API
isn't stable yet. Garry's brain flips it true to opt in.
Quality gate:
Only 'incorrect' and 'partial' verdicts trigger the write. 'correct'
resolutions are noise (we expected the take to hold up — no learning).
'unresolvable' has no canonical column. Defense-in-depth runtime guard
in writeIncorrectResolution() rejects ineligible qualities with
reason='quality_not_eligible' so a caller misuse never surfaces a
malformed learning entry.
Auto-apply only:
Coupling fires only when grade_takes both auto-applies AND the verdict
is incorrect/partial AND the config flag is enabled. Manual resolutions
via `gbrain takes resolve` intentionally DO NOT propagate to gstack —
manual writes already carry operator intent; the calibration loop is
the noise-prone path that earns coupling.
Namespace:
Every entry's key starts with 'gbrain:calibration:v0.36.0.0:'. Lane D
`gbrain calibration --undo-wave v0.36.0.0` (T17) filters on this prefix
for the optional gstack-scrub step. First active bias tag suffixes the
key (e.g. 'take-42:over-confident-geography') so future analysis can
group learnings by bias pattern.
Architecture:
buildLearningEntry — pure. Truncates claim at 200 chars + ellipsis;
emits Pattern: line when activeBiasTags present; defaults confidence
to 0.8 when caller omits it.
writeIncorrectResolution — async wrapper. Honors config gate; honors
quality gate; calls the injected writer (or defaultGstackWriter in
production). Failures are non-fatal: returns
{ written: false, reason: 'write_failed' | 'binary_missing', error }.
The grade_takes phase logs to result.warnings and continues — gstack
coupling failure NEVER aborts a cycle.
defaultGstackWriter — shells out to gstack-learnings-log binary via
execFileSync. Throws GBrainError('GSTACK_BINARY_NOT_FOUND') when the
binary isn't on PATH; writeIncorrectResolution classifies that error
to reason='binary_missing' so the operator sees the install hint
instead of a generic write_failed.
Wired into grade-takes.ts after engine.resolveTake() inside the
auto-apply block. Only fires when shouldApply=true.
Tests: 14 cases.
buildLearningEntry (7): canonical shape, partial vs incorrect wording,
bias-tag suffix, no-tag fallback, claim truncation, default confidence,
no-reasoning omission.
writeIncorrectResolution (7): config gate, quality gate, happy path,
writer-throw graceful degrade, binary-missing classification, async
writer awaited, partial quality writes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>