pi-monofold 0.7.0 → 0.8.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/focus-preset.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type MonofoldRouteType, parseDefaultRouteOverride } from "./focus-route-override.js";
1
2
  import { parseFocusSkills, resetFocusSkillsWarningState } from "./focus-skills.js";
2
3
  import { assertKnownKeys, asStringArray, isRecord, uniqueStrings } from "./validation.js";
3
4
 
@@ -10,13 +11,14 @@ export type FocusPreset = {
10
11
  label: string;
11
12
  targets: FocusPresetTarget[];
12
13
  focusSkills?: string[];
14
+ defaultRouteOverride?: MonofoldRouteType;
13
15
  };
14
16
 
15
17
  export type FocusMatchableWorkspace = {
16
18
  tags: string[];
17
19
  };
18
20
 
19
- const FOCUS_PRESET_KEYS = new Set(["id", "label", "targets", "focusSkills"]);
21
+ const FOCUS_PRESET_KEYS = new Set(["id", "label", "targets", "focusSkills", "defaultRouteOverride"]);
20
22
  const FOCUS_PRESET_TARGET_KEYS = new Set(["targetTags"]);
21
23
 
22
24
  /** Parses and validates focus preset configuration from YAML/JSON input. */
@@ -52,6 +54,7 @@ export function parseFocusPresets(value: unknown, label = "focusPresets"): Focus
52
54
  targets.push({ targetTags });
53
55
  }
54
56
  const focusSkills = parseFocusSkills(itemLabel, item.focusSkills);
57
+ const defaultRouteOverride = parseDefaultRouteOverride(itemLabel, item.defaultRouteOverride);
55
58
  if (seenIds.has(item.id)) throw new Error(`${label} has duplicate preset id: ${item.id}`);
56
59
  seenIds.add(item.id);
57
60
  presets.push({
@@ -59,6 +62,7 @@ export function parseFocusPresets(value: unknown, label = "focusPresets"): Focus
59
62
  label: item.label,
60
63
  targets,
61
64
  ...(focusSkills !== undefined ? { focusSkills } : {}),
65
+ ...(defaultRouteOverride !== undefined ? { defaultRouteOverride } : {}),
62
66
  });
63
67
  }
64
68
  return presets;
@@ -0,0 +1,44 @@
1
+ /** Monofold route types available for routed Markdown writes. */
2
+ export const MONOFOLD_ROUTE_TYPES = [
3
+ "default",
4
+ "prd",
5
+ "design",
6
+ "progress",
7
+ "issue",
8
+ "research",
9
+ "decision",
10
+ ] as const;
11
+
12
+ export type MonofoldRouteType = (typeof MONOFOLD_ROUTE_TYPES)[number];
13
+
14
+ const ROUTE_TYPE_SET = new Set<string>(MONOFOLD_ROUTE_TYPES);
15
+
16
+ /** Validates a route type string and returns the typed value. */
17
+ export function assertMonofoldRouteType(label: string, value: string): MonofoldRouteType {
18
+ if (!ROUTE_TYPE_SET.has(value)) {
19
+ throw new Error(
20
+ `${label} must be one of: ${MONOFOLD_ROUTE_TYPES.join(", ")} (got: ${value})`,
21
+ );
22
+ }
23
+ return value as MonofoldRouteType;
24
+ }
25
+
26
+ /** Parses an optional defaultRouteOverride from preset config. */
27
+ export function parseDefaultRouteOverride(itemLabel: string, value: unknown): MonofoldRouteType | undefined {
28
+ if (value === undefined) return undefined;
29
+ if (typeof value !== "string" || value.trim() === "") {
30
+ throw new Error(`${itemLabel}.defaultRouteOverride must be a non-empty string`);
31
+ }
32
+ return assertMonofoldRouteType(`${itemLabel}.defaultRouteOverride`, value.trim());
33
+ }
34
+
35
+ /** Resolves the effective write route: explicit selection wins over Focus default, then "default". */
36
+ export function resolveWriteRouteType(
37
+ explicitRoute: string | undefined,
38
+ defaultRouteOverride: MonofoldRouteType | undefined,
39
+ ): MonofoldRouteType {
40
+ if (explicitRoute !== undefined && explicitRoute !== "") {
41
+ return assertMonofoldRouteType("routeType", explicitRoute);
42
+ }
43
+ return defaultRouteOverride ?? "default";
44
+ }
package/index.ts CHANGED
@@ -20,6 +20,10 @@ import {
20
20
  setActiveFocusPresetId,
21
21
  warnZeroTargetMatchesForPreset,
22
22
  } from "./focus-preset.js";
23
+ import {
24
+ resolveWriteRouteType,
25
+ type MonofoldRouteType,
26
+ } from "./focus-route-override.js";
23
27
  import {
24
28
  applyFocusSkillsToSystemPrompt,
25
29
  warnMissingFocusSkills,
@@ -41,7 +45,7 @@ import { assertKnownKeys, asStringArray, isRecord, uniqueStrings } from "./valid
41
45
  type CapabilityTag = "read" | "writeDocs" | "editCode" | "runCommands" | "git";
42
46
  type LegacyCapabilityTag = CapabilityTag | "gitCommit" | "gitPush";
43
47
  type IntentCategory = "Explore" | "Write" | "Config" | "Git";
44
- type RouteType = "default" | "prd" | "design" | "progress" | "issue" | "research" | "decision";
48
+ type RouteType = MonofoldRouteType;
45
49
 
46
50
  type RouteConfig = {
47
51
  path: string;
@@ -892,7 +896,11 @@ function sendCommandError(pi: ExtensionAPI, command: string, error: unknown, usa
892
896
  }
893
897
 
894
898
  function formatFocusStatus(position: ActiveFocusPresetPosition): string {
895
- return `focus: ${position.preset.label} (${position.index + 1}/${position.total}) ${FOCUS_CYCLE_SHORTCUT}`;
899
+ const base = `focus: ${position.preset.label} (${position.index + 1}/${position.total}) ${FOCUS_CYCLE_SHORTCUT}`;
900
+ if (position.preset.defaultRouteOverride) {
901
+ return `${base} route:${position.preset.defaultRouteOverride}`;
902
+ }
903
+ return base;
896
904
  }
897
905
 
898
906
  function updateFocusStatus(ctx: ExtensionContext | ExtensionCommandContext, loaded: LoadedConfig): void {
@@ -1026,6 +1034,9 @@ async function buildManifest(loaded: LoadedConfig): Promise<string> {
1026
1034
  const activeTargetIds = new Set(activeWorkspaces.map(({ workspace }) => workspace.targetId));
1027
1035
  if (activeWorkspaces.length > 0) {
1028
1036
  lines.push(`Active Focus: ${activePreset.label} (${activePreset.id})`);
1037
+ if (activePreset.defaultRouteOverride) {
1038
+ lines.push(`Default write route override: ${activePreset.defaultRouteOverride} (explicit routeType/--route still wins)`);
1039
+ }
1029
1040
  for (const { workspace } of activeWorkspaces) {
1030
1041
  lines.push(await buildFullWorkspaceManifestEntry(workspace, " (active)"));
1031
1042
  }
@@ -1046,6 +1057,9 @@ async function buildManifest(loaded: LoadedConfig): Promise<string> {
1046
1057
  async function buildFocusContextInjection(loaded: LoadedConfig, preset: FocusPreset): Promise<FocusContextInjection> {
1047
1058
  const activeWorkspaces = getActiveFocusWorkspaces(loaded, preset);
1048
1059
  const lines = ["## Focus Context Injection", "", `Active Focus: ${preset.label} (${preset.id})`];
1060
+ if (preset.defaultRouteOverride) {
1061
+ lines.push(`Default write route override: ${preset.defaultRouteOverride} (explicit routeType/--route still wins)`);
1062
+ }
1049
1063
  const notices: string[] = [];
1050
1064
  const seenFiles = new Set<string>();
1051
1065
  let injectedFileCount = 0;
@@ -1386,7 +1400,7 @@ ${focusInjection.text}` : ""}
1386
1400
  label: "Workspace Write",
1387
1401
  description: "Write a Markdown document to a routed Workspace destination using routeType, title, body, filename, and metadata.",
1388
1402
  parameters: Type.Object({
1389
- routeType: Type.String({ description: "default, prd, design, progress, issue, research, or decision" }),
1403
+ routeType: Type.Optional(Type.String({ description: "default, prd, design, progress, issue, research, or decision; omitted uses active Focus defaultRouteOverride or default" })),
1390
1404
  title: Type.String(),
1391
1405
  body: Type.String(),
1392
1406
  filename: Type.Optional(Type.String()),
@@ -1397,9 +1411,9 @@ ${focusInjection.text}` : ""}
1397
1411
  workspaceName: Type.Optional(Type.String()),
1398
1412
  }),
1399
1413
  async execute(_id, params, _signal, _onUpdate, ctx) {
1400
- const routeType = params.routeType as RouteType;
1401
- if (!ROUTE_TYPES.includes(routeType)) throw new Error(`Unknown routeType: ${params.routeType}`);
1402
1414
  const loaded = await loadConfig(ctx.cwd);
1415
+ const activePreset = getActiveFocusPreset(loaded);
1416
+ const routeType = resolveWriteRouteType(params.routeType, activePreset?.defaultRouteOverride);
1403
1417
  const workspace = await resolveWorkspace(ctx, loaded, {
1404
1418
  targetTags: params.targetTags,
1405
1419
  targetName: params.targetName,
@@ -1613,14 +1627,15 @@ ${focusInjection.text}` : ""}
1613
1627
  const writeCommand = async (args: string, ctx: ExtensionCommandContext) => {
1614
1628
  try {
1615
1629
  const parsed = parseCommandArgs(args);
1616
- const routeType = (stringFlag(parsed.flags, "route", "r") ?? parsed.positional[0] ?? "default") as RouteType;
1617
- if (!ROUTE_TYPES.includes(routeType)) throw new Error(`Unknown routeType: ${routeType}`);
1618
1630
  const title = stringFlag(parsed.flags, "title", "t");
1619
1631
  const body = stringFlag(parsed.flags, "body", "b");
1620
1632
  if (!title) throw new Error("--title is required");
1621
1633
  if (!body) throw new Error("--body is required");
1622
1634
 
1623
1635
  const loaded = await loadConfig(ctx.cwd);
1636
+ const activePreset = getActiveFocusPreset(loaded);
1637
+ const explicitRoute = stringFlag(parsed.flags, "route", "r") ?? parsed.positional[0];
1638
+ const routeType = resolveWriteRouteType(explicitRoute, activePreset?.defaultRouteOverride);
1624
1639
  const workspace = await resolveWorkspace(ctx, loaded, commandTarget(parsed.flags, ["writeDocs"]));
1625
1640
  const route = workspace.normalizedRoutes[routeType] ?? workspace.normalizedRoutes.default;
1626
1641
  if (!route) throw new Error(`Workspace has no route for ${routeType} and no default route`);
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.7.0",
14
+ "version": "0.8.0",
15
15
  "pi": {
16
16
  "extensions": [
17
17
  "./index.ts"
@@ -25,6 +25,7 @@
25
25
  "unknown-path-allows.ts",
26
26
  "file-read-preview.ts",
27
27
  "focus-preset.ts",
28
+ "focus-route-override.ts",
28
29
  "focus-skills.ts",
29
30
  "monofold-read-ops.ts",
30
31
  "read-caps.ts",