Skip to content

fix(coding-agent): honor PI_RULES environment settings - #670

Merged
code-yeongyu merged 1 commit into
mainfrom
fix/rules-env-config
Aug 3, 2026
Merged

fix(coding-agent): honor PI_RULES environment settings#670
code-yeongyu merged 1 commit into
mainfrom
fix/rules-env-config

Conversation

@code-yeongyu

@code-yeongyu code-yeongyu commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • back-port the accepted pi-rules#25 environment configuration behavior into
    Senpi's built-in rules extension
  • honor PI_RULES_DISABLED, PI_RULES_MAX_RULE_CHARS, and
    PI_RULES_MAX_RESULT_CHARS with strict positive-integer parsing and
    presence-only flag composition
  • include Senpi's project-rules envelope and source headers in the total result
    budget so the complete static/dynamic block respects the configured limit
  • document the variables and record the fork-specific manual adaptation

Upstream source: code-yeongyu/pi-rules#25
Upstream merge commit: 12ad906f0b29e949ebbd1f89d8f85789578aa6e6

RED -> GREEN evidence

  • focused RED on unchanged production code: 4 failed / 3 passed
    • env disable still injected static and dynamic rules
    • a requested 50-character rule remained 5,000 characters
    • a requested 300-character result remained 14,774 characters
  • focused GREEN after the fix: 8/8
  • strict-parser mutation (parseInt) failed exactly the malformed-value test;
    restored strict parser returned 8/8 GREEN
  • reverting only dynamic complete-result budgeting produced 893 characters
    against a 600-character cap; restoring it returned GREEN

Validation

  • changed-file LSP diagnostics: 0 errors
  • npx vitest --run test/rules-env-config.test.ts test/rules-before-agent-start.test.ts:
    11/11 passed
  • npm run check: passed
  • npm run build: passed after syncing current origin/main
  • full coding-agent suite: 6,709 tests passed; 3 unrelated concurrent-session
    tests failed from a pre-existing unhandled No API key found for anthropic
    rejection. The implicated agent-session-concurrent.test.ts passed 7/7 in
    isolation in the same built worktree; this change does not touch session,
    model, provider, or auth code.

Real CLI QA

Driven through the real source CLI and local fake model server with isolated
HOME/session/config directories and a 5,000-character AGENTS.md:

Scenario Complete block Rule payload Result
baseline 5,242 5,000 injected
PI_RULES_DISABLED=1 0 0 absent
PI_RULES_MAX_RULE_CHARS=50 295 50 truncated
PI_RULES_MAX_RESULT_CHARS=300 298 54 truncated
combined 50 / 300 298 49 truncated within total cap
malformed 50abc 5,249 5,000 defaults
malformed 1.5 5,247 5,000 defaults
malformed 1e3 5,247 5,000 defaults
  • standard Senpi QA self-check: 9/9
  • standard mock-loop self-test: passed
  • all fake-server ports closed
  • all sandboxes removed
  • real auth unchanged

Local evidence: local-ignore/qa-evidence/20260803-pi-rules-env/


Summary by cubic

Honors PI_RULES_DISABLED, PI_RULES_MAX_RULE_CHARS, and PI_RULES_MAX_RESULT_CHARS in the built-in rules extension and fixes result-length budgeting so static and dynamic blocks respect configured limits. Backports behavior from code-yeongyu/pi-rules PR #25.

  • Bug Fixes
    • Resolve env config at extension registration with strict whole-string positive-integer parsing; defaults are kept on malformed values. Truthy PI_RULES_DISABLED accepts 1, true, yes, on (case-insensitive) and composes with the runtime flag instead of being overwritten.
    • Include the <project_rules> envelope, headers, and sentinels in maxResultChars; re-render until the complete block fits, returning no envelope if none can fit. Applies to both static and dynamic blocks.
    • Docs: README now documents these env vars. Tests: added rules-env-config.test.ts and cleared env in test setup.

Written for commit 7525d38. Summary will update on new commits.

Review in cubic

@code-yeongyu
code-yeongyu enabled auto-merge August 3, 2026 07:54
@code-yeongyu
code-yeongyu merged commit f665907 into main Aug 3, 2026
14 checks passed
@code-yeongyu
code-yeongyu deleted the fix/rules-env-config branch August 3, 2026 08:00

@code-yeongyu code-yeongyu left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[sisyphus-bot] brutal review of PR #670

Reviewed 8 changed files at commit 7525d38.

Findings: 0 critical, 0 major, 4 minor.

The implementation is solid — the strict positive-integer parser correctly rejects all malformed inputs (empty, whitespace, zero, negative, decimal, scientific, hex, signed, non-ASCII digits, overflow). The formatWithinResultBudget loop correctly converges and handles the edge case where the envelope alone exceeds the budget. The disabled || envDisabled composition is correct for a one-way disable flag. Test coverage is strong for the happy path and malformed-value cases.

The 4 minor findings are: (1) silent malformed-value fallback with no diagnostic, (2) missing test for env-disabled + flag-false sticky composition, (3) missing multi-rule test for budget convergence, (4) re-truncation performance in the budget loop. None are blocking — the code is correct, these are hardening and coverage improvements.

See inline comments for details.

Comment on lines +20 to +26
function parsePositiveInteger(value: string | undefined): number | undefined {
if (value === undefined) return undefined;
const normalized = value.trim();
if (!/^\d+$/.test(normalized)) return undefined;
const parsed = Number(normalized);
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[sisyphus-bot] minor: parsePositiveInteger silently returns undefined (→ default) on malformed values with no diagnostic, log, or warning. A user who typos PI_RULES_MAX_RULE_CHARS=50abc silently gets 12000-char rules instead of 50. The RuleDiagnostic type (types.ts:137-140) exists with severity warning/error and could carry this. While the PR body documents 'defaults are kept on malformed values', the complete absence of any feedback makes troubleshooting unexpected behavior very difficult — the user has no way to know their env var was rejected.

Fix: Emit a console.warn (or capture malformed values in a diagnostics array on PiRulesConfig / SessionState) when parsePositiveInteger rejects a non-empty value, including the env var name and the rejected value.

Comment on lines +196 to +200
it("keeps the presence-only disabled flag working", async () => {
const runner = await createRunner();
runner.setFlagValue("pi-rules-disabled", true);
expect(await emitStatic(runner)).toBe("");
});

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[sisyphus-bot] minor: Missing test for env-disabled + flag-false sticky composition. The disabled || envDisabled composition in index.ts:50 means that when PI_RULES_DISABLED=1 is set, the runtime flag pi-rules-disabled=false CANNOT re-enable rules (false || true = true). This is designed per the changes.md, but no test verifies it. If someone refactors || to && or removes envDisabled, no test would catch the regression — the 'keeps the presence-only disabled flag working' test only tests flag-true with env unset.

Fix: Add a test: set PI_RULES_DISABLED=1, create a runner, set flag pi-rules-disabled to false, emit static, and assert the result is still empty (rules stay disabled).

Comment on lines +220 to +225
it("caps the complete dynamic tool-result block", async () => {
process.env.PI_RULES_MAX_RESULT_CHARS = "600";
const block = await emitDynamic();
expect(block.length).toBeLessThanOrEqual(600);
expect(block).toContain("Additional project instructions matched for src/index.ts:");
});

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[sisyphus-bot] minor: Missing multi-rule test for formatWithinResultBudget convergence. All budget tests use a single static rule (AGENTS.md, 5000 chars) and a single dynamic rule. The re-render loop in formatter.ts:26-42 is only exercised with one rule, where convergence is trivial (one truncation step). With multiple rules, the loop may iterate multiple times as truncateBudget reduces different rules, and no test verifies the loop correctly handles the multi-rule case where reducing one rule's body isn't enough and the budget must be further reduced.

Fix: Add a test with 3+ static rules (different sizes) and a tight PI_RULES_MAX_RESULT_CHARS (e.g., 200) that forces the loop to iterate 2+ times. Assert block.length <= maxResultChars and that at least one rule is truncated.

Comment on lines +26 to +42
function formatWithinResultBudget(
rules: ReadonlyArray<LoadedRule>,
options: FormatOptions,
render: (truncatedRules: ReadonlyArray<TruncatedRule>) => string,
): string {
let bodyBudget = options.maxResultChars;
while (bodyBudget > 0) {
const truncatedRules = truncateRules(rules, { ...options, maxResultChars: bodyBudget });
if (truncatedRules.length === 0) return "";

const block = render(truncatedRules);
const overflow = block.length - options.maxResultChars;
if (overflow <= 0) return block;
bodyBudget = Math.max(0, bodyBudget - overflow);
}
return "";
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

[sisyphus-bot] minor: formatWithinResultBudget re-truncates ALL rules from scratch on every iteration (truncateRules(rules, {...options, maxResultChars: bodyBudget})). When the overflow is small (e.g., 1 char over budget due to envelope overhead), the loop re-truncates every rule body and re-runs truncateBudget. For a large number of rules with a tight budget, this is O(n_rules * iterations). The loop terminates correctly (bodyBudget strictly decreases), but the performance is unnecessarily poor for the multi-rule case. Consider binary-searching bodyBudget or caching per-rule truncation and only trimming the last rule that exceeds the budget.

Fix: Binary-search bodyBudget between 0 and maxResultChars instead of linear decrement. Or: truncate each rule once to maxRuleChars, then iteratively trim the last rule by the overflow amount, avoiding full re-truncation.

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