pi-soly 1.12.2 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config.ts +10 -0
- package/core.ts +62 -0
- package/index.ts +25 -0
- package/package.json +1 -1
package/config.ts
CHANGED
|
@@ -45,6 +45,13 @@ export interface SolyConfig {
|
|
|
45
45
|
* user (ask "ready to implement, or discuss first?") before writing code.
|
|
46
46
|
* Default on. */
|
|
47
47
|
confirmBeforeCode: boolean;
|
|
48
|
+
/** Inject a compact list of rules applicable to the file being
|
|
49
|
+
* edited/written, appended to the tool's result. Forces the LLM to
|
|
50
|
+
* confirm in the next message which rules were applied — closes the
|
|
51
|
+
* "rules read at start of turn, forgotten by edit time" gap.
|
|
52
|
+
* Default on. Disable for verbose rule sets that would otherwise
|
|
53
|
+
* flood the per-edit context. */
|
|
54
|
+
preActionRuleReminder: boolean;
|
|
48
55
|
};
|
|
49
56
|
display: {
|
|
50
57
|
/** Always show the recommended (⭐) option as the first row. */
|
|
@@ -125,6 +132,7 @@ export const DEFAULT_CONFIG: SolyConfig = {
|
|
|
125
132
|
nudgeNotify: false,
|
|
126
133
|
toolHints: true,
|
|
127
134
|
confirmBeforeCode: true,
|
|
135
|
+
preActionRuleReminder: true,
|
|
128
136
|
},
|
|
129
137
|
display: {
|
|
130
138
|
defaultRecommendedFirst: true,
|
|
@@ -216,6 +224,8 @@ function deepMerge(base: SolyConfig, over: RawConfig): SolyConfig {
|
|
|
216
224
|
merged.agent.toolHints = over.agent.toolHints;
|
|
217
225
|
if (typeof over.agent.confirmBeforeCode === "boolean")
|
|
218
226
|
merged.agent.confirmBeforeCode = over.agent.confirmBeforeCode;
|
|
227
|
+
if (typeof over.agent.preActionRuleReminder === "boolean")
|
|
228
|
+
merged.agent.preActionRuleReminder = over.agent.preActionRuleReminder;
|
|
219
229
|
}
|
|
220
230
|
if (over.display) {
|
|
221
231
|
if (typeof over.display.defaultRecommendedFirst === "boolean")
|
package/core.ts
CHANGED
|
@@ -571,6 +571,68 @@ ${blocks.join("\n\n---\n\n")}${skippedNote}
|
|
|
571
571
|
return { section, loaded: applicable.map(ruleKey), interactive };
|
|
572
572
|
}
|
|
573
573
|
|
|
574
|
+
// ============================================================================
|
|
575
|
+
// Per-file rule lookup (used by pre-action reminder in tool_result hook)
|
|
576
|
+
// ============================================================================
|
|
577
|
+
//
|
|
578
|
+
// Returns the subset of `rules` that apply to a single file path. Mirrors the
|
|
579
|
+
// glob logic in `buildRulesSection` (always/no-globs = universal; otherwise
|
|
580
|
+
// match `filePath` against `meta.globs`). Excludes disabled rules. Use this
|
|
581
|
+
// to feed the pre-action reminder — LLM should see applicable rules AT THE
|
|
582
|
+
// MOMENT of edit, not just at the start of the turn (where they decay).
|
|
583
|
+
export function getApplicableRulesForFile(
|
|
584
|
+
filePath: string,
|
|
585
|
+
rules: RuleFile[],
|
|
586
|
+
): RuleFile[] {
|
|
587
|
+
return rules.filter((r) => {
|
|
588
|
+
if (!r.enabled) return false;
|
|
589
|
+
if (r.meta.always === true) return true;
|
|
590
|
+
const globs = r.meta.globs;
|
|
591
|
+
if (!globs || globs.length === 0) return true;
|
|
592
|
+
return globs.some((g) => matchesGlob(filePath, g));
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Formats a compact reminder block for the LLM to see right after editing
|
|
597
|
+
// `filePath`. Sorted by priority (high → medium → low → undefined), capped
|
|
598
|
+
// at `cap` rules (default 3) so the LLM isn't flooded. Returns "" when no
|
|
599
|
+
// rules apply — caller should treat that as "don't inject anything".
|
|
600
|
+
export function formatRuleReminder(
|
|
601
|
+
rules: RuleFile[],
|
|
602
|
+
filePath: string,
|
|
603
|
+
cap = 3,
|
|
604
|
+
): string {
|
|
605
|
+
if (rules.length === 0) return "";
|
|
606
|
+
|
|
607
|
+
const priorityOrder: Record<string, number> = { high: 0, medium: 1, low: 2 };
|
|
608
|
+
const sorted = [...rules].sort((a, b) => {
|
|
609
|
+
const pa = priorityOrder[a.meta.priority ?? ""] ?? 1;
|
|
610
|
+
const pb = priorityOrder[b.meta.priority ?? ""] ?? 1;
|
|
611
|
+
if (pa !== pb) return pa - pb;
|
|
612
|
+
return a.relPath.localeCompare(b.relPath);
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
const shown = sorted.slice(0, cap);
|
|
616
|
+
const remaining = rules.length - shown.length;
|
|
617
|
+
|
|
618
|
+
const lines: string[] = [];
|
|
619
|
+
lines.push(`\n\n---\n\n📋 **Applicable rules for \`${filePath}\`:**\n`);
|
|
620
|
+
shown.forEach((r, i) => {
|
|
621
|
+
const desc = r.meta.description ? ` — ${r.meta.description}` : "";
|
|
622
|
+
lines.push(`${i + 1}. [\`${r.relPath}\`]${desc}`);
|
|
623
|
+
});
|
|
624
|
+
if (remaining > 0) {
|
|
625
|
+
lines.push(
|
|
626
|
+
`\n_…and ${remaining} more — use \`/why ${filePath}\` to see all._`,
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
lines.push(
|
|
630
|
+
`\nIn your next message, briefly confirm which were applied (or note why not).`,
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
return lines.join("\n");
|
|
634
|
+
}
|
|
635
|
+
|
|
574
636
|
// ============================================================================
|
|
575
637
|
// Phase-scoped rules loader
|
|
576
638
|
// ============================================================================
|
package/index.ts
CHANGED
|
@@ -32,7 +32,9 @@ import {
|
|
|
32
32
|
CONTEXT_WINDOW_TOKENS,
|
|
33
33
|
DEFAULT_PROGRESS,
|
|
34
34
|
extractFilePathsFromPrompt,
|
|
35
|
+
formatRuleReminder,
|
|
35
36
|
formatTok,
|
|
37
|
+
getApplicableRulesForFile,
|
|
36
38
|
loadAllRules,
|
|
37
39
|
loadPhaseRules,
|
|
38
40
|
loadProjectState,
|
|
@@ -834,6 +836,29 @@ export default function solyExtension(pi: ExtensionAPI) {
|
|
|
834
836
|
}
|
|
835
837
|
});
|
|
836
838
|
|
|
839
|
+
// ============================================================================
|
|
840
|
+
// tool_result: inject a compact reminder of rules applicable to the file
|
|
841
|
+
// just edited/written, so the LLM sees them AT THE MOMENT of action (not
|
|
842
|
+
// 30 turns later when they've decayed in attention). The LLM is asked to
|
|
843
|
+
// confirm in its next message which rules were applied — closing the gap
|
|
844
|
+
// where rules loaded at the start of a turn are forgotten by the time the
|
|
845
|
+
// agent actually edits something.
|
|
846
|
+
// ============================================================================
|
|
847
|
+
pi.on("tool_result", async (event, _ctx) => {
|
|
848
|
+
if (!getActiveConfig().agent.preActionRuleReminder) return;
|
|
849
|
+
if (event.toolName !== "edit" && event.toolName !== "write") return;
|
|
850
|
+
if (event.isError) return; // tool failed — don't pile on
|
|
851
|
+
const input = event.input as { path?: string };
|
|
852
|
+
const filePath = input?.path;
|
|
853
|
+
if (!filePath) return;
|
|
854
|
+
const applicable = getApplicableRulesForFile(filePath, combinedRules());
|
|
855
|
+
const reminder = formatRuleReminder(applicable, filePath);
|
|
856
|
+
if (!reminder) return;
|
|
857
|
+
return {
|
|
858
|
+
content: [...event.content, { type: "text" as const, text: reminder }],
|
|
859
|
+
};
|
|
860
|
+
});
|
|
861
|
+
|
|
837
862
|
// ============================================================================
|
|
838
863
|
// Chrome: working-indicator telemetry lifecycle + reactive data refresh
|
|
839
864
|
// ============================================================================
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-soly",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Project management + workflow framework for pi-coding-agent. Plans, state, MANDATORY rules, self-review, multi-question picker, native footer + welcome chrome — one npm install, zero config. The LLM drives execution and delegates to a worker subagent when available.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|