Skip to content

feat(core,adapter): assemble sourcePatch for say→change-rule (#566)#593

Merged
laofahai merged 2 commits into
mainfrom
feat/say-change-rule-sourcepatch
Jun 13, 2026
Merged

feat(core,adapter): assemble sourcePatch for say→change-rule (#566)#593
laofahai merged 2 commits into
mainfrom
feat/say-change-rule-sourcepatch

Conversation

@laofahai

Copy link
Copy Markdown
Owner

What

Wires the natural-language "say→change an existing code-condition rule's threshold" path to a structured sourcePatch (#566). This is the core assembly brick that turns an utterance like "把经理审批阈值改成 20000" into a governed Proposal whose change carries the exact { filePath, constantName, newValueLiteral } the ProposalFileWriter's injected SourcePatcher (#590 / #591) needs to rewrite the real export const MANAGER_APPROVAL_THRESHOLD in source.

How

  • schema-intent-prompt.tsParsedSchemaIntent.newValueLiteral, the prompt instruction to emit it, and the strict isSafeValueLiteral validator (number / boolean / null / double-quoted JSON string only — no expressions, identifiers, Infinity/NaN, multi-token, or whitespace). Unsafe values are dropped at parse time.
  • types/rule.ts — opt-in RuleDefinition.patchTarget { sourcePath, constantName }. A code-condition rule declares the constant it owns; without it, no sourcePatch is built (existing fail-loud guard from fix(core): fail loud on un-graduatable code-condition rule updates (#566) #587 still applies).
  • schema-intent-rule-updater.tsdraftRuleUpdate assembles sourcePatch only when conditionKind === "code" + patchTarget present + newValueLiteral safe (re-validated as defence-in-depth).
  • proposal-engine.ts / schema-intent-types.tsProposalDiff.sourcePatch + SchemaIntentRule.patchTarget so the ai single-change model can carry it.
  • adapter-server / adapter-mcptoSchemaIntentRule() projects patchTarget; the adapter-server resolve-schema-intent route copies outcome.sourcePatch onto the governed ProposalChange.sourcePatch.
  • demo — the manager-approval-threshold rule opts in.

Scope

Runtime injection of the real TS-AST patcher (patchNamedConstant from @linchkit/devtools, merged in #591) is a separate capstone PRproposal-graduate-api.ts is intentionally untouched here. The e2e graduation test proves assembly→patch with an injected fake patcher.

Tests

New schema-intent-value-literal.test.ts (validator + parse gating, incl. malicious inputs) and schema-intent-rule-updater.test.ts (safe→patch; absent / unsafe / no-patchTarget / declarative → no patch), plus an extended proposal-file-writer.test.ts assembly→graduation loop. bun run check + bun run typecheck clean; targeted core/adapter/demo suites green.

The local full bun run test batched runner currently hangs in an unrelated packages/cli batch (leftover dev server on :3001/:3002 + proxy) — not this diff. CI clean-install is authoritative.

Related: #566, #587, #590, #591

…hange-rule (#566)

When an utterance asks to change a constant a code-condition rule owns ("raise
the manager-approval threshold to 20000"), the resolver emits a strictly
validated newValueLiteral and draftRuleUpdate assembles a
ProposalDiff.sourcePatch {filePath, constantName, newValueLiteral} when the rule
opts in via the new RuleDefinition.patchTarget. The adapter-server route copies
it onto the governed ProposalChange.sourcePatch consumed by ProposalFileWriter;
both adapter-server and adapter-mcp project patchTarget into the AI rule snapshot.

Security: isSafeValueLiteral admits only number / boolean / null / JSON-string
literals (no expressions, identifiers, or multi-token input), re-validated at
assembly time. The demo manager-approval-threshold rule opts in.

Runtime injection of the real TS-AST patcher is a separate capstone PR; tests
here prove assembly + graduation with an injected fake patcher.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@laofahai, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 36 minutes and 1 second. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 15a84aee-c375-4100-a815-62db2ed2a067

📥 Commits

Reviewing files that changed from the base of the PR and between 14f8391 and d00977d.

📒 Files selected for processing (14)
  • .changeset/say-change-rule-sourcepatch.md
  • addons/adapter-mcp/cap-adapter-mcp/src/schema-intent-ontology.ts
  • addons/adapter-server/cap-adapter-server/src/routes/ai-resolve-schema-intent.ts
  • addons/adapter-server/cap-adapter-server/src/routes/ai-schema-intent-ontology.ts
  • addons/demo/cap-purchase-demo/src/rules/manager-approval-threshold.ts
  • packages/core/__tests__/proposal-file-writer.test.ts
  • packages/core/src/ai/__tests__/schema-intent-rule-updater.test.ts
  • packages/core/src/ai/__tests__/schema-intent-value-literal.test.ts
  • packages/core/src/ai/index.ts
  • packages/core/src/ai/proposal-engine.ts
  • packages/core/src/ai/schema-intent-prompt.ts
  • packages/core/src/ai/schema-intent-rule-updater.ts
  • packages/core/src/ai/schema-intent-types.ts
  • packages/core/src/types/rule.ts
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/say-change-rule-sourcepatch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request implements the natural-language path to change an existing code-condition rule's threshold via a structured sourcePatch (#566). It introduces isSafeValueLiteral to validate the new value literal, adds patchTarget to RuleDefinition to opt into this behavior, and updates the adapter-server and adapter-mcp to propagate these patches to the ProposalFileWriter. Feedback on the changes suggests refining the regular expression in isSafeValueLiteral to disallow number literals with leading zeros (e.g., 0123), which would otherwise trigger strict-mode compile-time syntax errors regarding octal literals.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread packages/core/src/ai/schema-intent-prompt.ts Outdated
…593 review)

A leading-zero integer (`007`, `0123`) splices into source as an octal literal —
a syntax error in strict-mode TypeScript. Tighten the number regex to disallow
leading zeros on non-zero integers (gemini), and add 007/0123/00 regression
cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@laofahai laofahai merged commit ebc2cec into main Jun 13, 2026
10 checks passed
@laofahai laofahai deleted the feat/say-change-rule-sourcepatch branch June 13, 2026 14:18
constantName: string;
/** Already-validated literal to write as the new value, e.g. "20000". */
newValueLiteral: string;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Type-drift risk: SchemaIntentProposalDraft.sourcePatch (this block) and ProposalDiff["sourcePatch"] in proposal-engine.ts declare the same three-field shape independently. Because TypeScript structural typing allows assignability in either direction, if ProposalDiff["sourcePatch"] gains or drops a required field the assignment in draftRuleUpdate will still compile silently — the new field would disappear from the outcome object without a compile error at the declaration site.

buildSourcePatch already anchors its return type as NonNullable<ProposalDiff["sourcePatch"]>, so the fix is to derive this field's type from that same anchor:

Suggested change
};
};
  sourcePatch?: NonNullable<ProposalDiff["sourcePatch"]>;

(Add import type { ProposalDiff } from "./proposal-engine"; at the top of this file.) Future shape changes to ProposalDiff["sourcePatch"] would then propagate automatically.

effectType: "warn",
conditionKind: "declarative",
condition: { field: "amount", operator: "gt", value: 5000 },
roundTrippable: true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Test doesn't exercise buildSourcePatch at all. The title says "builds NO sourcePatch for a declarative (non-code) rule", but roundTrippable: true means the outer condition in draftRuleUpdate

if (existing.conditionKind === "code" || existing.roundTrippable === false)

is false, so execution takes the declarative rebuild path and buildSourcePatch is never called. The test therefore provides zero coverage for the guard inside buildSourcePatch:

if (existing.conditionKind !== "code") return undefined;

Mutation: changing that guard to conditionKind !== "declarative" would pass every existing test while silently allowing a declarative-rule diff-only path to emit a sourcePatch.

The missing case is a rule where both the outer condition and buildSourcePatch's guard are relevant:

// roundTrippable: false → enters the diff-only branch in draftRuleUpdate
// conditionKind: "declarative" → buildSourcePatch must return undefined despite having a patchTarget + safe literal
const nonRoundTrippableDeclarative: SchemaIntentRule = {
  ...codeRuleWithPatchTarget({ conditionKind: "declarative", roundTrippable: false }),
};
const outcome = runDraft({
  parsed: parsedUpdate({ newValueLiteral: "20000" }),   // no rule: field → noMatch guard
  rule: nonRoundTrippableDeclarative,
});
// Diff-only path, buildSourcePatch called but conditionKind !== "code" → sourcePatch absent
expect(outcome.sourcePatch).toBeUndefined();

This test would fail under the above mutation, providing the missing coverage.

@claude

claude Bot commented Jun 13, 2026

Copy link
Copy Markdown

Code review summary — two inline findings posted.

The core security design (isSafeValueLiteral + defence-in-depth re-validation in buildSourcePatch + path-traversal check in applySourcePatch) is solid, and the validator test battery is thorough.

  1. Type-drift risk (inline on schema-intent-types.ts:103): SchemaIntentProposalDraft.sourcePatch redeclares the same three-field shape as ProposalDiff["sourcePatch"] independently. A future shape change to ProposalDiff["sourcePatch"] won't propagate here — it will compile silently while the documented contract diverges. Using NonNullable<ProposalDiff["sourcePatch"]> as the field type instead ties the two together and makes drift a compile error.

  2. Missing mutation-resistant test (inline on schema-intent-rule-updater.test.ts:145): The test 'builds NO sourcePatch for a declarative (non-code) rule' uses roundTrippable: true, which takes the declarative rebuild path and never calls buildSourcePatch. A one-line mutation of the guard inside buildSourcePatch (conditionKind !== 'code' to conditionKind !== 'declarative') passes every existing test. Adding a case with { conditionKind: 'declarative', roundTrippable: false, patchTarget } + safe literal exercises the guard directly.

laofahai added a commit that referenced this pull request Jun 13, 2026
…duates to real source (#566 capstone) (#595)

* feat(adapter-server): inject patchNamedConstant — say→change-rule graduates to real source (#566 capstone)

The proposal graduate API gains an injectable `sourcePatcher` option, threaded
into its DEFAULT writer; server.ts (the composition root) wires in
`patchNamedConstant` from @linchkit/devtools — the TS-AST patcher behind core's
typescript-free SourcePatcher seam. An approved code-condition rule update
carrying a `sourcePatch` now rewrites the named constant in REAL source during
graduation, then opens the usual human-reviewed PR.

This closes the natural-language → real-code arm: with the resolver assembly
(#593) + the patcher (#591), "raise the manager-approval threshold to 20000"
now graduates by editing `export const MANAGER_APPROVAL_THRESHOLD`.

Adds @linchkit/devtools as a dependency (capability seam: core stays
typescript-free; the heavy patcher lives in devtools, injected here). New
integration test drives the full route → default writer → real file patch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* test(adapter-server): assert status before parsing body (#595 review)

Move the `res.status` assertion ahead of `res.json()` so a non-JSON error body
surfaces as a clear status-code mismatch instead of a confusing SyntaxError
(gemini).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant