pi-monofold 0.6.0 → 0.6.2

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/README.md CHANGED
@@ -106,7 +106,7 @@ Example command flows: [docs/examples.md](./docs/examples.md).
106
106
 
107
107
  Default focus shortcut: `ctrl+shift+m` cycles Active Focus forward through `focusPresets` YAML order. No backward focus shortcut ships in the MVP.
108
108
 
109
- When Active Focus is set, Pi Monofold injects the active preset's `contextFiles` into each agent turn under **Focus Context Injection** and recomposes the manifest so active Workspace Targets are shown first while non-active targets are collapsed to one-line summaries. The MVP uses provisional context-injection caps that are intentionally temporary and exposed as constants for future tuning:
109
+ When Active Focus is set, Pi Monofold injects the active preset's `contextFiles` into each agent turn under **Focus Context Injection** and recomposes the manifest so active Workspace Targets are shown first while non-active targets are collapsed to one-line summaries. Tag-based target inference in `monofold_read`, `monofold_write`, and `monofold_git` also prefers Workspace Targets that belong to the active preset when a tag query would otherwise match multiple candidates; explicit `targetId` / workspace name selectors and uniquely matching targets are unchanged. If multiple in-focus targets still tie, the existing workspace selection flow applies. The MVP uses provisional context-injection caps that are intentionally temporary and exposed as constants for future tuning:
110
110
 
111
111
  - Max **6** context files per active preset.
112
112
  - Max **6,000** characters per file, with `… [truncated]` appended when a file is cut.
package/focus-preset.ts CHANGED
@@ -184,3 +184,31 @@ export function resetActiveFocusSessionState(): void {
184
184
  activeFocusPresetId = null;
185
185
  activeFocusInitialized = false;
186
186
  }
187
+
188
+ export type TagBasedTargetInput = {
189
+ targetTags?: string[];
190
+ targetId?: string;
191
+ targetName?: string;
192
+ workspaceName?: string;
193
+ workspaceIndex?: number;
194
+ };
195
+
196
+ /** Returns true when workspace resolution relies on tag query without an explicit selector. */
197
+ export function isTagBasedTargetInference(target: TagBasedTargetInput): boolean {
198
+ if (!target.targetTags?.length) return false;
199
+ if (target.targetId) return false;
200
+ const targetName = target.targetName ?? target.workspaceName;
201
+ if (targetName) return false;
202
+ if (target.workspaceIndex !== undefined) return false;
203
+ return true;
204
+ }
205
+
206
+ /** Narrows ambiguous tag matches to active-focus targets when at least one in-focus candidate exists. */
207
+ export function biasMatchesTowardActiveFocus<T extends { targetId: string }>(
208
+ matches: T[],
209
+ activeTargetIds: ReadonlySet<string>,
210
+ ): T[] {
211
+ if (matches.length <= 1 || activeTargetIds.size === 0) return matches;
212
+ const inFocus = matches.filter((match) => activeTargetIds.has(match.targetId));
213
+ return inFocus.length > 0 ? inFocus : matches;
214
+ }
package/index.ts CHANGED
@@ -12,6 +12,8 @@ import {
12
12
  findFocusPresetById,
13
13
  getActiveFocusPresetId,
14
14
  getActiveFocusPresetPosition,
15
+ biasMatchesTowardActiveFocus,
16
+ isTagBasedTargetInference,
15
17
  matchesFocusTarget,
16
18
  parseFocusPresets,
17
19
  setActiveFocusPresetByLabel,
@@ -917,9 +919,20 @@ function notifyNoFocusPresets(ctx: ExtensionContext | ExtensionCommandContext, p
917
919
  if (pi) sendCommandOutput(pi, "monofold:focus", message, { focusPresets: 0 });
918
920
  }
919
921
 
922
+ function biasWorkspaceMatchesWithActiveFocus(loaded: LoadedConfig, matches: ResolvedWorkspace[]): ResolvedWorkspace[] {
923
+ const activePreset = getActiveFocusPreset(loaded);
924
+ if (!activePreset) return matches;
925
+ const activeTargetIds = new Set(getActiveFocusWorkspaces(loaded, activePreset).map(({ workspace }) => workspace.targetId));
926
+ return biasMatchesTowardActiveFocus(matches, activeTargetIds);
927
+ }
928
+
920
929
  async function resolveWorkspace(ctx: ExtensionContext | ExtensionCommandContext, loaded: LoadedConfig, target: TargetInput): Promise<ResolvedWorkspace> {
921
- const matches = loaded.workspaces.filter((workspace) => matchesTarget(workspace, target));
930
+ ensureActiveFocusInitialized(loaded.raw.focusPresets);
931
+ let matches = loaded.workspaces.filter((workspace) => matchesTarget(workspace, target));
922
932
  if (matches.length === 0) throw new Error(`No workspace matches target: ${JSON.stringify(target)}`);
933
+ if (matches.length > 1 && isTagBasedTargetInference(target)) {
934
+ matches = biasWorkspaceMatchesWithActiveFocus(loaded, matches);
935
+ }
923
936
  if (matches.length === 1) return matches[0];
924
937
  if (!ctx.hasUI) {
925
938
  throw new Error(`Multiple workspaces match target in non-interactive mode: ${matches.map(formatWorkspaceLabel).join(", ")}`);
package/package.json CHANGED
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "description": "Pi extension that folds multiple repositories and folders into a guarded virtual monorepo for AI agents.",
13
13
  "type": "module",
14
- "version": "0.6.0",
14
+ "version": "0.6.2",
15
15
  "pi": {
16
16
  "extensions": [
17
17
  "./index.ts"