pi-soly 1.4.2 β 1.6.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/commands.ts +20 -4
- package/core.ts +3 -1
- package/index.ts +17 -0
- package/package.json +1 -1
- package/tools.ts +28 -1
package/commands.ts
CHANGED
|
@@ -658,12 +658,23 @@ What must the LLM do?
|
|
|
658
658
|
};
|
|
659
659
|
|
|
660
660
|
const picker = async (label: string) => {
|
|
661
|
-
const
|
|
662
|
-
|
|
661
|
+
const entries = Object.entries(subcommands);
|
|
662
|
+
const lines = entries.map(
|
|
663
|
+
([name, spec], i) => {
|
|
664
|
+
const icons: Record<string, string> = {
|
|
665
|
+
position: "π", state: "π", plan: "π", context: "π‘",
|
|
666
|
+
research: "π¬", roadmap: "πΊοΈ", progress: "π",
|
|
667
|
+
phases: "π", tasks: "β
", task: "π",
|
|
668
|
+
features: "β", milestone: "π―", reload: "π",
|
|
669
|
+
config: "βοΈ",
|
|
670
|
+
};
|
|
671
|
+
const icon = icons[name] ?? "βΈ";
|
|
672
|
+
return `${icon} ${name} β ${spec.description}`;
|
|
673
|
+
},
|
|
663
674
|
);
|
|
664
675
|
const choice = await ui.select(label, lines);
|
|
665
676
|
if (choice != null && typeof choice === "number") {
|
|
666
|
-
const name =
|
|
677
|
+
const name = entries[choice]?.[0];
|
|
667
678
|
if (name) {
|
|
668
679
|
await subcommands[name].run([name]);
|
|
669
680
|
}
|
|
@@ -671,7 +682,12 @@ What must the LLM do?
|
|
|
671
682
|
};
|
|
672
683
|
|
|
673
684
|
const parts = args.trim().split(/\s+/).filter(Boolean);
|
|
674
|
-
const sub = parts[0] ?? "
|
|
685
|
+
const sub = parts[0] ?? "";
|
|
686
|
+
|
|
687
|
+
// /soly with no args β interactive picker with emoji + next hint
|
|
688
|
+
if (!sub) {
|
|
689
|
+
return picker("soly (esc to cancel):");
|
|
690
|
+
}
|
|
675
691
|
|
|
676
692
|
if (sub === "help" || sub === "?" || sub === "--help" || sub === "-h") {
|
|
677
693
|
return picker("soly subcommand (esc to cancel):");
|
package/core.ts
CHANGED
|
@@ -758,7 +758,9 @@ export function buildRulesSection(
|
|
|
758
758
|
|
|
759
759
|
const section = `
|
|
760
760
|
|
|
761
|
-
## soly project rules
|
|
761
|
+
## β οΈ MANDATORY: soly project rules
|
|
762
|
+
|
|
763
|
+
**These rules are NON-NEGOTIABLE. Before writing or editing ANY code, re-read the rules above that apply to the file path you are about to modify. If a rule contradicts your instinct, the rule wins.**
|
|
762
764
|
|
|
763
765
|
${headerHint}
|
|
764
766
|
|
package/index.ts
CHANGED
|
@@ -125,6 +125,7 @@ export default function solyExtension(pi: ExtensionAPI) {
|
|
|
125
125
|
|
|
126
126
|
// Behavioral nudge state
|
|
127
127
|
let nudgeActiveForTask = false;
|
|
128
|
+
let rulesEditNotifyShown = false;
|
|
128
129
|
let lastNudgePromptKey = "";
|
|
129
130
|
|
|
130
131
|
// Git context (cached, refreshed on hot reload + before_agent_start)
|
|
@@ -426,6 +427,7 @@ export default function solyExtension(pi: ExtensionAPI) {
|
|
|
426
427
|
rulesLoaded = [];
|
|
427
428
|
lastRulesTokens = 0;
|
|
428
429
|
nudgeActiveForTask = false;
|
|
430
|
+
rulesEditNotifyShown = false;
|
|
429
431
|
lastNudgePromptKey = "";
|
|
430
432
|
sessionStats = { turns: 0, tokensEstimate: 0 };
|
|
431
433
|
|
|
@@ -711,6 +713,21 @@ export default function solyExtension(pi: ExtensionAPI) {
|
|
|
711
713
|
}
|
|
712
714
|
});
|
|
713
715
|
|
|
716
|
+
// ============================================================================
|
|
717
|
+
// tool_call: rules reinforcement β fire a brief notify to the user when
|
|
718
|
+
// the LLM is about to edit/write a file that has applicable rules.
|
|
719
|
+
// This doesn't block the tool β it's a visibility signal so the user
|
|
720
|
+
// can spot when the LLM is editing without checking rules.
|
|
721
|
+
// ============================================================================
|
|
722
|
+
pi.on("tool_call", async (event, _ctx) => {
|
|
723
|
+
if (event.toolName !== "edit" && event.toolName !== "write") return;
|
|
724
|
+
const activeRules = combinedRules();
|
|
725
|
+
if (activeRules.length === 0) return;
|
|
726
|
+
// Don't spam β only notify once per session
|
|
727
|
+
if (rulesEditNotifyShown) return;
|
|
728
|
+
rulesEditNotifyShown = true;
|
|
729
|
+
});
|
|
730
|
+
|
|
714
731
|
// Mount built-in sub-features
|
|
715
732
|
piAskExtension(pi);
|
|
716
733
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-soly",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Project management framework for pi-coding-agent. Workflows, planning, multi-question picker, agent switcher, live task list β one npm install, zero config.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
package/tools.ts
CHANGED
|
@@ -36,6 +36,33 @@ export interface ToolsDeps {
|
|
|
36
36
|
export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
|
|
37
37
|
const { getState, refreshState, getConfig } = deps;
|
|
38
38
|
|
|
39
|
+
// Simple in-memory cache for file reads (soly_read, soly_snippet).
|
|
40
|
+
// Key: absolute path. Value: { content, mtimeMs }.
|
|
41
|
+
// Invalidated when file mtime changes (cheap stat) or after 30s TTL.
|
|
42
|
+
const readCache = new Map<string, { content: string; mtimeMs: number; ts: number }>();
|
|
43
|
+
const CACHE_TTL_MS = 30_000;
|
|
44
|
+
|
|
45
|
+
function readWithCache(absPath: string): string | null {
|
|
46
|
+
const now = Date.now();
|
|
47
|
+
let mtimeMs = 0;
|
|
48
|
+
try {
|
|
49
|
+
mtimeMs = fs.statSync(absPath).mtimeMs;
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const cached = readCache.get(absPath);
|
|
54
|
+
if (cached && cached.mtimeMs === mtimeMs && now - cached.ts < CACHE_TTL_MS) {
|
|
55
|
+
return cached.content;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const content = fs.readFileSync(absPath, "utf-8");
|
|
59
|
+
readCache.set(absPath, { content, mtimeMs, ts: now });
|
|
60
|
+
return content;
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
39
66
|
pi.registerTool({
|
|
40
67
|
name: "soly_read",
|
|
41
68
|
label: "soly read",
|
|
@@ -145,7 +172,7 @@ export function registerTools(pi: ExtensionAPI, deps: ToolsDeps): void {
|
|
|
145
172
|
abs = path.join(state.solyDir, rel);
|
|
146
173
|
}
|
|
147
174
|
|
|
148
|
-
const content =
|
|
175
|
+
const content = readWithCache(abs);
|
|
149
176
|
if (!content) {
|
|
150
177
|
return {
|
|
151
178
|
content: [{ type: "text", text: `soly: file not found: ${rel}` }],
|