Skip to content

fix(mt#4901): Credit an inline gloss, and resolve a truncated ref from contextRefs - #3571

Merged
edobry merged 3 commits into
mainfrom
task/mt-4901
Sep 2, 2026
Merged

edobry merged 3 commits into
mainfrom
task/mt-4901

Conversation

@minsky-ai

@minsky-ai minsky-ai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Two advisory ask-form-lint checks judged the question body in isolation, so both fired on asks
that already contained the remedy the warning asked for. Measured in the 2026-09-02
/calibration-review window (8 unreviewed records, 9 matches): 2 of the 5 false positives were these.

  • domain-jargon warned "say what it means, or move the reference to contextRefs" on ask#10650,
    whose first sentence read "Decide whether ADR-042 — the record of which planning gates get a
    mechanical backstop — should be marked Accepted."
    The author said what it means, in the same
    clause. Its metadata.formWarningDisposition records the rebuttal contemporaneously, including
    the second half: ADR-042 is the decision's SUBJECT, so the "move it" branch was unavailable too.
  • unlinkified-reference warned "the reader cannot open them from the ask. Supply the full
    URL"
    on ask#10647, which cited Notion 34f937f0 in the body and carried
    https://app.notion.com/p/34f937f03cb48108a95bdf3813f5ca84 in contextRefs — the full URL for
    that exact id. The two checks gave contradictory advice because neither could see what the other
    recommended.

Key changes

domain-jargon credits an inline gloss at first use (form-lint.ts). The rule is structural
and punctuation-anchored — a parenthetical, or a matched dash-pair apposition — and requires
CLOSURE
. An unclosed opener ("ADR-042 — required measuring its trigger") stays a fire, which is
what stops the rule from suppressing the shape the check exists to catch. Deliberately not a
semantic gloss detector: form-lint.ts:170-176 already records that an unbounded natural-language
surface is the axis ADR-024 assigns to embedding rather than regex.

The ask's own contextRefs become a resolution source (external-refs.ts, asks.ts). A
truncated cued id that is a unique prefix of a Notion URL the ask carries is resolved and the URL
appended, so the persisted body gains a working link.

Two design constraints worth calling out, because they were the whole planning finding:

  • The fix is at the TEXT, not at the check. asks.ts:1239-1254 (mt#2918, PR feat(mt#2918): Make external artifact citations in ask bodies reachable #2755 R1) already
    decided this: "any present or future check that reads for a URL inherits the same mismatch, so
    the fix belongs at the text, not at the check."
    FormLintInput therefore gains no field, and
    the unlinkified-reference branch itself is unmodified. Teaching the check to look away would
    have silenced the warning while leaving the body unreadable — defeating mt#2918's stated goal that
    "every downstream reader … carries the URL rather than a bare page id."
  • Harvesting is scoped to Notion-hosted URLs. A Notion page id and a Minsky ask/memory/workspace
    id are the same shape, so a candidate set built from every id-shaped run could resolve a truncated
    cue against a Minsky entity and append a dead URL. An ambiguous prefix resolves to nothing rather
    than guessing.

Idempotence across both call paths is carried by harvesting candidates from the text itself as well
as from contextRefs: computeFormLintMatches re-runs the transform with no options over the
already-normalized question, and without that second source it would re-report a reference the
normalization step had just fixed.

scripts/replay-ask-form-lint-calibration.ts replays the corpus against the current matchers. It
labels each row with body provenance (as-filed / original-content / edited-since) because an
ask body is mutable — mt#3584 lost a real false positive to exactly that conflation.

Testing

Execution evidence:

SC1 + SC2 + SC3 — 17 new tests, 100 pass / 0 fail across the three suites (bun test packages/domain/src/ask/external-refs.test.ts packages/domain/src/ask/form-lint.test.ts src/adapters/shared/commands/asks.external-refs.test.ts):

(pass) domain-jargon — inline gloss at first use (mt#4901) > AT1: a dash-pair gloss at first use suppresses the ADR/RFC class
(pass) domain-jargon — inline gloss at first use (mt#4901) > a parenthetical gloss suppresses it the same way
(pass) domain-jargon — inline gloss at first use (mt#4901) > AT3: a bare ADR reference with no gloss still fires
(pass) domain-jargon — inline gloss at first use (mt#4901) > AT3: a bare ADR reference mid-body still fires
(pass) domain-jargon — inline gloss at first use (mt#4901) > AT4: a possessive use is not a gloss
(pass) domain-jargon — inline gloss at first use (mt#4901) > an UNCLOSED dash opener is not a gloss — closure is what bounds the rule
(pass) domain-jargon — inline gloss at first use (mt#4901) > the gloss must sit at the FIRST use, not a later one
(pass) domain-jargon — inline gloss at first use (mt#4901) > a later bare use is ordinary prose once the term is glossed on introduction
(pass) linkifyExternalRefs — resolving a truncated cue ... (mt#4901) > AT2: the prefix resolves against a contextRefs URL and the URL lands in the text
(pass) linkifyExternalRefs — resolving a truncated cue ... (mt#4901) > AT2 negative control: with no refs supplied, the same body still reports it
(pass) linkifyExternalRefs — resolving a truncated cue ... (mt#4901) > idempotent across call paths: a second pass with NO options reports nothing
(pass) linkifyExternalRefs — resolving a truncated cue ... (mt#4901) > an AMBIGUOUS prefix resolves to nothing rather than guessing between two pages
(pass) linkifyExternalRefs — resolving a truncated cue ... (mt#4901) > only a NOTION-hosted URL is a resolution source — a bare id elsewhere is not
(pass) linkifyExternalRefs — resolving a truncated cue ... (mt#4901) > collectNotionIdsFromUrls reads app.notion.com and notion.so, and nothing else
(pass) createAskWithFormLint — contextRefs as a resolution source (mt#4901) > a short id prefix in the body resolves against a contextRefs URL
(pass) createAskWithFormLint — contextRefs as a resolution source (mt#4901) > negative control: the same body with NO contextRefs still warns
(pass) createAskWithFormLint — contextRefs as a resolution source (mt#4901) > a contextRef that is not a Notion URL is not a resolution source

 100 pass
 0 fail
Ran 100 tests across 3 files.

AT1 = the first row; AT2 = the linkifyExternalRefs rows plus the createAskWithFormLint
rows (which read the PERSISTED question back out of the repository, so they assert the URL is in the
body rather than only that the warning fell silent — the rejected fix shape would pass the weaker
assertion); AT3 = the two "still fires" rows; AT4 = the possessive row.

No regression across the ask family — 950 pass / 0 fail across 52 files, including
form-lint.jargon-and-lede.test.ts (the mt#4516 suite that owns this check):

950 pass
 0 fail
Ran 950 tests across 52 files. [11.66s]

Typecheck — 0 errors across 8 projects (., packages/domain, packages/shared,
services/reviewer, services/site, src/cockpit/web, tsconfig.hooks.json,
tsconfig.scripts.json). Lint — 0 errors, 0 warnings over 4324 files.

Negative control: reverted all three source files (external-refs.ts, form-lint.ts, asks.ts) via git stash push, leaving the tests in place, and observed the new tests FAIL.

(fail) createAskWithFormLint — contextRefs as a resolution source (mt#4901) > a short id prefix in the body resolves against a contextRefs URL
(fail) domain-jargon — inline gloss at first use (mt#4901) > AT1: a dash-pair gloss at first use suppresses the ADR/RFC class
(fail) domain-jargon — inline gloss at first use (mt#4901) > a parenthetical gloss suppresses it the same way
(fail) domain-jargon — inline gloss at first use (mt#4901) > a later bare use is ordinary prose once the term is glossed on introduction
SyntaxError: Export named 'collectNotionIdsFromUrls' not found in module '.../external-refs.ts'

 69 pass
 5 fail
Ran 74 tests across 3 files.

Reported honestly rather than as a clean 17-for-17: the external-refs.test.ts file does not LOAD
against the pre-fix tree (it imports a symbol the fix introduces), so its 6 tests are unrunnable
there rather than failing. The 4 named failures are the real control. Note also that the
recall-floor tests (AT3, AT4) pass both before and after — correct, since they must not change.
This is a full revert of the fix, not a one-line revert (mt#4512).

Live verification

AT5 / SC4 — replayed over the real 51-record corpus at
~/.local/state/minsky/projects/a0809beec3ba7e98/ask-form-lint-calibration.jsonl, against the live
ask store:

Corpus: 51 records; 5 carry domain-jargon / unlinkified-reference

2026-08-27T15:11:32.568Z  ask#10647   [as-filed]          domain-jargon: STILL FIRES; unlinkified-reference: cleared
2026-08-27T15:12:28.035Z  ask#10650   [edited-since]      domain-jargon: cleared
2026-08-27T15:23:43.123Z  ask#10657   [original-content]  domain-jargon: STILL FIRES
2026-08-27T15:29:45.684Z  ask#10662   [original-content]  domain-jargon: STILL FIRES
2026-08-31T02:06:38.515Z  ask#11095   [as-filed]          domain-jargon: STILL FIRES

cleared: 2   still fires: 4   unfetchable: 0

cleared: 2 are exactly the two matches classified false in the calibration window. still
fires: 4
are the three true positives plus ask#10647's ask-kind name class, which the pass
classified uncertain and this change deliberately does not touch. 0 unfetchable, so every record
was actually re-judged.

[edited-since] on ask#10650 is the honest label: that ask was edited after its record, and the
edit touched metadata only (its editHistory records fields: ["metadata"]), so the replayed
question is the judged text.

One thing the replay could not do from this session: resolveCalibrationLogDir keys on the
current working directory, so run from a session workspace it resolves to that workspace's own empty
project key. That is mt#4885, which already owns the general fix; the script takes an explicit
--log override rather than re-solving it here, and refuses to silently report a clean zero.

Deploy verification

isDeploySurfaceFile returns true for 6 of the 7 changed files — run over this diff, not

true  packages/domain/src/ask/external-refs.ts
true  packages/domain/src/ask/form-lint.ts
true  src/adapters/shared/commands/asks.ts
true  packages/domain/src/ask/external-refs.test.ts
true  packages/domain/src/ask/form-lint.test.ts
true  src/adapters/shared/commands/asks.external-refs.test.ts
false scripts/replay-ask-form-lint-calibration.ts

So this is a deploy-surface PR and no [no-deploy-impact] claim is made. Post-merge I will run
deployment_wait-for-latest with notBefore set to the merge timestamp and expectCommitSha set
to the merge commit, and read buildIdentity rather than treating SUCCESS alone as verification.

No new external-system integration: no new permission, scope, credential, outbound host, or webhook.

Notes

  • Coordinates with mt#4389, which owns missing-force-immediate in the same file — adjacent
    branches, different causes. Not merged into this change; that task additionally carries a reserved
    SEVERITY_TRANSPORT_CHECK_KINDS question for the operator.
  • Open PR feat(mt#4639): Convert 614 log sites to getLoggableErrorSummary and flip the rule to error #3412 also touches src/adapters/shared/commands/asks.ts. Verified line-disjoint
    against its real diff: 8 single-line edits (an import plus seven catch-block error: lines), none
    inside normalizeQuestionForLint, validateFormLintNotViolated, or createAskWithFormLint.
    Whoever lands second rebases.
  • Unrelated flake observed: the first push was blocked by
    src/cockpit/sweepers.test.ts > an overrunning tick is not run concurrently with the next tick (mt#4335) (maxInFlight 2 vs 1). It passes 3/3 in isolation and passed on the gated re-run that
    let this push through; it fails only under the 64-file parallel partition. Not caused by this diff
    — no file it touches is in this change — and no task currently owns it.

🤖 Generated with Claude Code

https://claude.ai/code/session_017FKnD9tAMUFGYWg36keBvz

edobry and others added 2 commits September 2, 2026 14:27
…m contextRefs

Two advisory ask-form-lint checks judged the question body in isolation and
fired on asks that already carried the remedy the warning asked for.

domain-jargon: a term GLOSSED inline at its first use now suppresses its class.
The rule is structural and punctuation-anchored — a parenthetical, or a matched
dash-pair apposition, both requiring CLOSURE. An unclosed opener stays a fire,
which is what keeps this from suppressing the shape the check exists to catch.
Deliberately not a semantic gloss detector: that is the unbounded
natural-language axis form-lint.ts:170-176 already records as belonging to
embedding rather than regex.

unlinkified-reference: the ask's own contextRefs are now a resolution source.
A body citing a page by a short id prefix, with the full URL in contextRefs, is
reachable — resolving it makes the PERSISTED body carry the URL, which is
mt#2918's actual goal, and the warning then falls silent as a consequence
rather than by exemption. The fix is at the TEXT, not at the check, per the
decision recorded at asks.ts:1239-1254; FormLintInput gains no field.
Harvesting is scoped to Notion-hosted URLs so a truncated cue can never resolve
against a Minsky entity id of the same shape, and an ambiguous prefix resolves
to nothing rather than guessing.

Replayed over the real 51-record corpus: 2 cleared (exactly the two measured
false positives), 4 still firing (the true positives preserved), 0 unfetchable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017FKnD9tAMUFGYWg36keBvz
@minsky-ai minsky-ai Bot added the authorship/co-authored Co-authored by human and AI agent label Sep 2, 2026
@minsky-reviewer

minsky-reviewer Bot commented Sep 2, 2026 •

Copy link
Copy Markdown

Minsky Reviewer Status

Verdict: APPROVED — no blocking findings
Review: View review
Model: openai/gpt-5 | Tokens: 468K prompt, 5K completion | Duration: 174s
Mode: normal

Commands

  • /review — request a fresh review

@minsky-reviewer minsky-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


One blocking inconsistency remains. The PR correctly adds inline-gloss suppression for domain-jargon and extends linkifyExternalRefs to resolve truncated Notion ids against contextRefs; create-time normalization is wired through normalizeQuestionForLint and tests are comprehensive. However, validateFormLintNotViolated still normalizes with linkifyExternalRefs(question) without supplying knownRefs/contextRefs, so the validation path can still judge against an un-normalized body (particularly via asks.edit), violating SC2’s requirement that the fix live at the TEXT across both call paths. Please update validateFormLintNotViolated to use the same normalization (e.g., call normalizeQuestionForLint or pass knownRefs) so behavior is consistent. Other aspects look solid and tests preserve recall as required; live replay evidence is out-of-repo and was noted as unverifiable.

Findings

  • [BLOCKING] src/adapters/shared/commands/asks.ts:1320 — Normalization in validateFormLintNotViolated ignores contextRefs, violating SC2 and creating inconsistent behavior across call paths
    validateFormLintNotViolated still normalizes via linkifyExternalRefs(params.question) without supplying the ask's contextRefs as a resolution source. See the code block under the mt#2918 comment where it assigns const { text: normalizedQuestion } = linkifyExternalRefs(params.question); — this path never consults contextRefs.

Per the spec (SC2), the normalization step must resolve a truncated Notion id in question against the ask’s own contextRefs and do so at the TEXT layer in both call paths. While normalizeQuestionForLint was updated to pass knownRefs (and callers like asks.create correctly use it), the validation path here remains out of sync, meaning:

  • In asks.create’s validate hook (which calls validateFormLintNotViolated(normalizeQuestionForLint(params))), the missing knownRefs is masked by the prior normalization — but this function’s own contract (“normalizes on its own path too”) remains unfulfilled.
  • In other call sites that invoke validateFormLintNotViolated directly (e.g., validateEditFormLintAgainstExistingAsk for asks.edit), the same truncated-id-in-body + URL-in-contextRefs case will still be judged against an un-normalized body, reintroducing the false-positive the PR claims to fix.

Fix: thread contextRefs (or precomputed knownRefs) into this normalization path — either by calling normalizeQuestionForLint here (recommended for parity) or by passing { knownRefs } to linkifyExternalRefs so this function’s normalization is consistent with the create path. This also aligns the implementation with the spec’s explicit requirement that the fix live at the text, not in the checker, across both call paths.

Spec verification

Criterion Status Evidence
SC1 — domain-jargon does not fire when the term is glossed inline at first use via a punctuation-anchored apposition (parenthetical or matched dash-pair). The rule is structural, not semantic. Met packages/domain/src/ask/form-lint.ts:170-214 adds INLINE_GLOSS_PATTERN and firesUnglossed(), and computeFormLintMatches() now uses firesUnglossed(question, ...) for all three jargon classes instead of bare .test(...) (packages/domain/src/ask/form-lint.ts:733-744 → 768+). New tests in packages/domain/src/ask/form-lint.test.ts add AT1 and related negatives (lines ~700-795 in diff) demonstrating suppression on first-use gloss and preserved recall on unglossed cases.
SC2 — A bare artifact id in question whose URL already exists in contextRefs is resolved and linkified BEFORE persistence. The fix is at the TEXT (normalization) not at the check; do NOT add contextRefs to FormLintInput; do NOT modify the unlinkified-reference matcher itself. Also, both normalization call sites (create path and validation path) should apply the same transform. Not Met Met in part: (a) linkifyExternalRefs now accepts knownRefs and can resolve truncated Notion ids against Notion URLs in contextRefs (packages/domain/src/ask/external-refs.ts:113-176, 214-247), and (b) the create path uses normalizeQuestionForLint to pass contextRefs (src/adapters/shared/commands/asks.ts:1058-1090 and 2062-2075). Not met for the validation path: validateFormLintNotViolated still normalizes via linkifyExternalRefs(params.question) with NO knownRefs (src/adapters/shared/commands/asks.ts around the const { text: normalizedQuestion } = linkifyExternalRefs(params.question); line). Per the spec, this call site should use the same normalization (either normalizeQuestionForLint or linkifyExternalRefs(..., {knownRefs})) so the persisted body and the validated body match. The unlinkified-reference branch remained unmodified and FormLintInput was not extended — both as required.
SC3 — Recall preserved: a bare decision-record reference with no gloss still fires, and a bare artifact id with no matching contextRefs still fires. Met Negative-control tests were added and pass: packages/domain/src/ask/form-lint.test.ts includes two AT3 cases where unglossed ADR references still fire, and packages/domain/src/ask/external-refs.test.ts includes an AT2 negative control showing unlinkified still reported when no refs are supplied. The unlinkified-reference matcher itself is unchanged (packages/domain/src/ask/form-lint.ts near the linkifyExternalRefs(question) call), preserving recall absent normalization.
SC4 — Replay over the real calibration corpus: all 5 lifetime domain-jargon and the 1 unlinkified-reference record re-judged, with before/after verdicts recorded. Unverifiable A new script scripts/replay-ask-form-lint-calibration.ts was added to perform the replay, but the criterion requires the re-judged results, which live outside the repo. The PR body references an out-of-repo path ~/.local/state/minsky/.../ask-form-lint-calibration.jsonl and shows sample output; this reviewer cannot fetch or verify those live-run results from the diff alone.

Documentation impact

  • no-update-needed — Internal behavior changes to form-lint normalization and linkification; no public CLI surface or documented contract appears to change. I checked only code comments and in-file docs; no separate docs/ files were modified by this PR. If there is a doc describing ask-form-lint semantics, it was not included in the diff and I did not scan the entire docs tree in this round.

…hared seam

The reviewer was right, and the finding is stronger than "inconsistent": it was
a false HARD-REJECT on the asks.edit surface.

validateFormLintNotViolated called linkifyExternalRefs(params.question) bare, so
it resolved nothing against contextRefs. The create path masked that — its caller
already normalizes — and the edit path did not: validateEditFormLintAgainstExistingAsk
fetches the existing ask (contextRefs included) and passed everything BUT them.

That surface is where it bites, because unlinkified-reference is advisory but
portal-no-link BLOCKS: an authorization.approve question naming a portal action
with no URL is exactly a question whose only citation is a page id the transform
was about to resolve. mt#2918's originating case, reintroduced on the one call
site it had not been checked on.

- validateFormLintNotViolated takes contextRefs and normalizes via
  normalizeQuestionForLint, fulfilling its own docblock's "normalizes on its own
  path too" rather than only claiming it.
- validateEditFormLintAgainstExistingAsk passes existing.contextRefs.

Negative control: with the bare call restored, the new edit test is rejected with
"portal-no-link: portal action with no direct link"; with the seam, it passes.

Class scan: these were the only two remaining sites. createAskWithFormLint and the
asks.create validate hook already route through normalizeQuestionForLint, and
form-lint's own re-run is covered by harvesting candidates from the text.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017FKnD9tAMUFGYWg36keBvz

@minsky-reviewer minsky-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


R2 verification: the prior blocking issue remains. The create path now threads contextRefs into normalization via normalizeQuestionForLint and tests look solid, and SC1/SC3 are met. However, validateFormLintNotViolated still normalizes with linkifyExternalRefs(question) without supplying knownRefs/contextRefs, so the validation/edit path can still misjudge a truncated Notion id whose full URL is present in contextRefs. This violates SC2’s requirement that the fix live at the text across both call paths. Please update validateFormLintNotViolated to use the same normalization (e.g., call normalizeQuestionForLint or pass { knownRefs }) to restore parity. Live-replay evidence (SC4) remains unverifiable from the diff but is not blocking. No new defects beyond this inconsistency were found.

Findings

  • [BLOCKING] src/adapters/shared/commands/asks.ts:1239 — Validation path still normalizes without contextRefs, leaving SC2 unmet and creating inconsistent behavior across call paths
    In validateFormLintNotViolated, normalization still calls linkifyExternalRefs(params.question) without passing knownRefs derived from the ask’s contextRefs (src/adapters/shared/commands/asks.ts:1233-1248; specifically const { text: normalizedQuestion } = linkifyExternalRefs(params.question);).

Per SC2, the normalization must resolve a truncated Notion id in question against the ask’s own contextRefs at the TEXT layer, and crucially it must do so across BOTH call paths (create and validation) to avoid false positives on edits and any other direct consumer of this validator. While normalizeQuestionForLint was updated to pass knownRefs and the asks.create path now uses it, the validation path remains out-of-sync, so:

  • asks.edit (which invokes validateEditFormLintAgainstExistingAsk → validateFormLintNotViolated) can still judge against an un-normalized body when the body contains a truncated Notion id and the URL is present in contextRefs.
  • This silently reintroduces the exact unlinkified false-positive the PR intends to eliminate.

Fix: Either (a) have validateFormLintNotViolated call normalizeQuestionForLint({question, contextRefs}) so both paths share identical normalization logic, or (b) pass { knownRefs } to linkifyExternalRefs here by harvesting contextRefs alongside question from the same input. This preserves the recorded decision to fix at the text, not in the checker, and aligns both call paths.

Spec verification

Criterion Status Evidence
SC1 — domain-jargon does not fire on a term that is glossed inline at its first use in question via a punctuation-anchored apposition (parenthetical or matched dash-pair). Rule is structural, not semantic. Met packages/domain/src/ask/form-lint.ts:154-214 adds INLINE_GLOSS_PATTERN and firesUnglossed(), and compute path at packages/domain/src/ask/form-lint.ts:768-777 switches the three jargon checks to firesUnglossed(...). Tests in packages/domain/src/ask/form-lint.test.ts:696-790 cover AT1/AT3/AT4 and related negatives, demonstrating suppression on first-use gloss and preserved recall.
SC2 — A bare artifact id in question whose URL already exists in contextRefs is resolved and linkified before persistence, at the TEXT layer; no FormLintInput changes; do not modify the unlinkified-reference matcher; both normalization call paths (create and validation) apply the same transform. Not Met Create path: Met — normalizeQuestionForLint now harvests contextRefs and passes { knownRefs } to linkifyExternalRefs (src/adapters/shared/commands/asks.ts:1102-1114), and asks.create validate uses normalizeQuestionForLint(params) (asks.ts:2506-2515). Validation path: Not Met — validateFormLintNotViolated still re-normalizes with linkifyExternalRefs(params.question) WITHOUT knownRefs (asks.ts:1233-1248 region; specifically the line const { text: normalizedQuestion } = linkifyExternalRefs(params.question);). asks.edit relies on this function (asks.ts:1705-1731), so truncated-id + URL-in-contextRefs remains misjudged on that path.
SC3 — Recall preserved: a bare decision-record reference with no gloss still fires; a bare artifact id with no matching contextRefs still fires. Met unlinkified-reference matcher remains unchanged in packages/domain/src/ask/form-lint.ts (call site still delegates to linkifier; no suppression). Negative controls in packages/domain/src/ask/external-refs.test.ts ("AT2 negative control") and packages/domain/src/ask/form-lint.test.ts (two AT3 tests around lines 730-760) confirm recall is preserved when no gloss/refs are present.
SC4 — Replay over the calibration corpus: all 5 domain-jargon and the 1 unlinkified-reference lifetime records re-judged with before/after verdicts recorded. Unverifiable scripts/replay-ask-form-lint-calibration.ts was added and emits the required replay, but the results are produced from an out-of-repo corpus path (see PR body: ~/.local/state/minsky/.../ask-form-lint-calibration.jsonl). This review cannot fetch or run the live replay; evidence remains the script implementation rather than recorded outputs.

Documentation impact

  • no-update-needed — This PR changes internal normalization behavior and advisory lint behavior for asks; no new public CLI flags or command contracts were added, and existing command shapes remain the same. No docs/ files were modified in the diff, and the changes align with an existing recorded decision (mt#2918) rather than introducing new user-visible options. I did not find any specific docs describing ask-form-lint normalization semantics to update.

@minsky-reviewer minsky-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


Re-verified the prior BLOCKING finding: the edit path was normalizing without considering contextRefs, leading to a false hard-reject. The latest commit threads contextRefs through the shared normalization seam and updates validateFormLintNotViolated to use normalizeQuestionForLint, aligning create and edit. Tests were added to assert the non-reject when a truncated Notion id is resolvable via contextRefs and reject otherwise. I found no new critical defects introduced by this fix. Spec criteria SC1–SC3 are met; SC4’s live-replay evidence is out-of-repo and thus unverifiable here. Documentation impact is none. Verdict: APPROVE.

Spec verification

Criterion Status Evidence
domain-jargon does not fire on a term that is glossed inline at its first use in question. The rule must be structural and punctuation-anchored — an em-dash or parenthetical apposition immediately following the term — and must be written down rather than left to the matcher's incidental behavior. Do NOT build a semantic gloss detector. Met The structural-gloss handling shipped earlier in this PR (packages/domain/src/ask/form-lint.ts and its tests) remains in place; no regressions introduced by this fix commit. New changes do not touch that branch. Live tests referenced in the PR body show AT1/AT3/AT4 passing; this fix commit is edit-surface normalization only.
A bare artifact id in question whose URL the ask already carries in contextRefs is resolved and linkified before the body is persisted, so unlinkified-reference stops firing as a consequence rather than by being taught to look away. The fix belongs at the TEXT, not at the check; extend linkifyExternalRefs with the ask's own refs as a resolution source, and thread it through normalizeQuestionForLint and both create/edit validation paths. Do NOT add a contextRefs field to FormLintInput. Met src/adapters/shared/commands/asks.ts:1061-1072 extends normalizeQuestionForLint to accept contextRefs and passes knownRefs into linkifyExternalRefs. src/adapters/shared/commands/asks.ts:1269-1287 now normalizes via normalizeQuestionForLint(params) instead of calling linkifyExternalRefs bare. src/adapters/shared/commands/asks.ts:2064-2071 threads existing.contextRefs into validateFormLintNotViolated for the edit path. Tests at src/adapters/shared/commands/asks-edit-form-lint.test.ts:219-261 assert non-reject when URL is carried in contextRefs and reject when absent. No changes were made to FormLintInput (out of file scope) or to the unlinkified-reference check itself.
Recall preserved, load-bearing: a bare decision-record reference with no gloss still fires, and a bare artifact id with no matching contextRefs entry still fires. Met Negative-control tests are present per PR body; this fix commit adds only normalization plumbing and does not relax filterBlockingFormLintMatches nor computeFormLintMatches. The added negative-control test at src/adapters/shared/commands/asks-edit-form-lint.test.ts:247-261 explicitly expects rejection when no contextRefs URL is available.
Every one of the 5 lifetime domain-jargon records and the 1 unlinkified-reference record is re-judged against the change, with the before/after verdict recorded. Unverifiable Live replay evidence is described in the PR body under “Live verification,” but the replay script output and calibration log path are out-of-repo (~/.local/state/...). The diff adds no repo artifact proving the live run. Per constitution, out-of-repo claims are unverifiable here.

Documentation impact

  • no-update-needed — Change is a bugfix to internal normalization and validation paths for asks.edit and ask form-lint plumbing. No new commands, flags, or user-facing contract changes; behavior aligns both paths with existing documented intent (normalize text before lint). No docs in-repo appear to describe the prior inconsistent edit-surface behavior.

@edobry
edobry merged commit fe1f896 into main Sep 2, 2026
20 checks passed
@edobry
edobry deleted the task/mt-4901 branch September 2, 2026 18:56

@minsky-reviewer minsky-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent adversarial review (Chinese-wall)
Reviewer: minsky-reviewer[bot] via openai:gpt-5
Tier: 2


Verification pass: the prior BLOCKING issue (validation path normalizing without contextRefs) is addressed. validateFormLintNotViolated now normalizes via the shared normalizeQuestionForLint(params) seam and validateEditFormLintAgainstExistingAsk threads existing.contextRefs, aligning create and edit semantics. SC1 and SC3 remain satisfied; SC2 is now satisfied across both call paths; SC4 remains unverifiable from the diff due to out-of-repo corpus. I found no new critical defects introduced by the fix. Tests cover the added behaviors, and the unlinkified check remains unmodified per spec. Approving this iteration.

Spec verification

Criterion Status Evidence
SC1 — domain-jargon does not fire on a term that is glossed inline at its first use in question. The rule must be structural and punctuation-anchored (parenthetical or matched dash-pair), not semantic. Met packages/domain/src/ask/form-lint.ts:154-214 adds INLINE_GLOSS_PATTERN and firesUnglossed(question, pattern); packages/domain/src/ask/form-lint.ts:768-777 switches the three jargon checks to firesUnglossed(...). Tests covering AT1/AT3/AT4 and negatives are added at packages/domain/src/ask/form-lint.test.ts:696-790.
SC2 — A bare artifact id in question whose URL already exists in contextRefs is resolved and linkified before persistence at the TEXT layer; no FormLintInput changes; do not modify the unlinkified-reference matcher; both normalization call paths (create and validation) apply the same transform. Met src/adapters/shared/commands/asks.ts:1058-1114 updates normalizeQuestionForLint to harvest contextRefs and pass { knownRefs } to linkifyExternalRefs. src/adapters/shared/commands/asks.ts:1217-1271 updates validateFormLintNotViolated to normalize via normalizeQuestionForLint(params) (threading contextRefs) instead of calling linkifyExternalRefs bare. src/adapters/shared/commands/asks.ts:2034-2064 threads existing.contextRefs into validateEditFormLintAgainstExistingAsk. unlinkified-reference branch in packages/domain/src/ask/form-lint.ts remains unmodified. Tests asserting create/edit behavior added at src/adapters/shared/commands/asks.external-refs.test.ts:131-207 and src/adapters/shared/commands/asks-edit-form-lint.test.ts:178-240.
SC3 — Recall preserved: a bare decision-record reference with no gloss still fires; a bare artifact id with no matching contextRefs still fires. Met The domain-jargon checks now use firesUnglossed(...) which still fires when there is no gloss (packages/domain/src/ask/form-lint.ts:768-777). Negative-control tests in packages/domain/src/ask/form-lint.test.ts around lines 731-760 assert bare ADR still fires; packages/domain/src/ask/external-refs.test.ts adds “AT2 negative control” confirming that without knownRefs the body still reports unlinkified.
SC4 — Replay over the calibration corpus re-judges all 5 domain-jargon and the 1 unlinkified-reference lifetime records with before/after verdicts recorded. Unverifiable scripts/replay-ask-form-lint-calibration.ts implements the replay, but it reads from an out-of-repo path resolved via resolveCalibrationLogDir or --log. The corpus path cited in the PR body is ~/.local/state/minsky/.../ask-form-lint-calibration.jsonl, which this review cannot access; live outputs are not present in the diff.

Adoption sweep

Symbol Kind Consumers found Classification Notes
collectNotionIdsFromUrls function packages/domain/src/ask/external-refs.test.ts:line ~222 — imports and asserts extraction from Notion URLs Adopted New helper exported from packages/domain/src/ask/external-refs.ts; currently used in tests to verify behavior. No production consumers required by spec.

Documentation impact

  • no-update-needed — The PR refines internal lint behavior and text normalization for asks without changing public command shapes, flags, or user-facing APIs. No docs files were modified, and the behavior aligns with an existing recorded design (normalize at the text layer per mt#2918). I checked for docs describing ask-form-lint or asks.create/asks.edit contracts in-repo and found no specific prose that would be invalidated by these internal adjustments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

authorship/co-authored Co-authored by human and AI agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant