pi-soly 1.4.2 β†’ 1.5.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.
Files changed (3) hide show
  1. package/commands.ts +20 -4
  2. package/package.json +1 -1
  3. 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 lines = Object.entries(subcommands).map(
662
- ([name, spec]) => `${name} - ${spec.description}`,
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 = Object.keys(subcommands)[choice];
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] ?? "position";
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-soly",
3
- "version": "1.4.2",
3
+ "version": "1.5.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 = readIfExists(abs);
175
+ const content = readWithCache(abs);
149
176
  if (!content) {
150
177
  return {
151
178
  content: [{ type: "text", text: `soly: file not found: ${rel}` }],