taskplane 0.23.13 → 0.23.14

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.
@@ -2938,6 +2938,83 @@ export function buildAgentIdFromLane(
2938
2938
  *
2939
2939
  * @since TP-102
2940
2940
  */
2941
+ /**
2942
+ * Parse an agent .md file: extract frontmatter and body.
2943
+ * Returns null if file doesn't exist or is malformed.
2944
+ * @since TP-117
2945
+ */
2946
+ function parseAgentFile(filePath: string): { fm: Record<string, string>; body: string } | null {
2947
+ try {
2948
+ if (!existsSync(filePath)) return null;
2949
+ const raw = readFileSync(filePath, "utf-8");
2950
+ const fmEnd = raw.indexOf("---", 4);
2951
+ if (fmEnd < 0) return { fm: {}, body: raw.trim() };
2952
+ const fmBlock = raw.slice(4, fmEnd).trim();
2953
+ const fm: Record<string, string> = {};
2954
+ for (const line of fmBlock.split("\n")) {
2955
+ const m = line.match(/^([\w-]+)\s*:\s*(.+)/);
2956
+ if (m) fm[m[1]] = m[2].trim();
2957
+ }
2958
+ return { fm, body: raw.slice(fmEnd + 3).trim() };
2959
+ } catch { return null; }
2960
+ }
2961
+
2962
+ /**
2963
+ * Load the base agent prompt from the taskplane package's templates/ directory.
2964
+ * Resolves the package root via well-known npm global paths.
2965
+ * @since TP-117
2966
+ */
2967
+ function loadBaseAgentPrompt(agentName: string): string {
2968
+ const relPath = join("node_modules", "taskplane", "templates", "agents", `${agentName}.md`);
2969
+ const candidates: string[] = [];
2970
+
2971
+ // Global npm paths
2972
+ const home = process.env.HOME || process.env.USERPROFILE || "";
2973
+ if (process.env.APPDATA) candidates.push(join(process.env.APPDATA, "npm", relPath));
2974
+ if (home) {
2975
+ candidates.push(join(home, "AppData", "Roaming", "npm", relPath));
2976
+ candidates.push(join(home, ".npm-global", "lib", relPath));
2977
+ }
2978
+ candidates.push(join("/usr", "local", "lib", relPath));
2979
+ candidates.push(join("/opt", "homebrew", "lib", relPath));
2980
+
2981
+ // Dynamic: npm root -g
2982
+ try {
2983
+ const result = spawnSync("npm", ["root", "-g"], { encoding: "utf-8", timeout: 5000, shell: true });
2984
+ if (result.stdout?.trim()) {
2985
+ candidates.push(join(result.stdout.trim(), "taskplane", "templates", "agents", `${agentName}.md`));
2986
+ }
2987
+ } catch { /* ignore */ }
2988
+
2989
+ for (const p of candidates) {
2990
+ const def = parseAgentFile(p);
2991
+ if (def?.body) return def.body;
2992
+ }
2993
+ return "";
2994
+ }
2995
+
2996
+ /**
2997
+ * Load local project agent prompt from .pi/agents/ or agents/ directory.
2998
+ * Supports standalone mode (local replaces base entirely).
2999
+ * @since TP-117
3000
+ */
3001
+ function loadLocalAgentPrompt(stateRoot: string, agentName: string): string {
3002
+ const paths = [
3003
+ join(stateRoot, ".pi", "agents", `${agentName}.md`),
3004
+ join(stateRoot, "agents", `${agentName}.md`),
3005
+ ];
3006
+ for (const p of paths) {
3007
+ const def = parseAgentFile(p);
3008
+ if (def) {
3009
+ // standalone: true → use local as-is (body only, replaces base)
3010
+ if (def.fm.standalone === "true") return def.body;
3011
+ // Otherwise return body as project-specific guidance to append
3012
+ if (def.body) return def.body;
3013
+ }
3014
+ }
3015
+ return "";
3016
+ }
3017
+
2941
3018
  export function resolveRuntimeStateRoot(
2942
3019
  repoRoot: string,
2943
3020
  workspaceRoot?: string,
@@ -2987,16 +3064,20 @@ export async function executeLaneV2(
2987
3064
  const opId = resolveOperatorId(config);
2988
3065
  const agentIdPrefix = `${tmuxPrefix}-${opId}`;
2989
3066
 
2990
- // Load worker agent definition for system prompt
3067
+ // Load worker agent definition: compose base template + local project guidance.
3068
+ // The base template (templates/agents/task-worker.md) contains critical behavioral
3069
+ // rules: checkpoint discipline, STATUS.md resume algorithm, review_step instructions.
3070
+ // The local file (.pi/agents/task-worker.md) adds project-specific guidance.
2991
3071
  let workerSystemPrompt = "You are a task execution agent. Read STATUS.md first, find unchecked items, work on them, checkpoint after each.";
2992
3072
  try {
2993
- const agentPath = join(stateRoot, ".pi", "agents", "task-worker.md");
2994
- if (existsSync(agentPath)) {
2995
- const raw = readFileSync(agentPath, "utf-8");
2996
- const fmEnd = raw.indexOf("---", 4);
2997
- if (fmEnd > 0) {
2998
- workerSystemPrompt = raw.slice(fmEnd + 3).trim();
2999
- }
3073
+ const basePrompt = loadBaseAgentPrompt("task-worker");
3074
+ const localPrompt = loadLocalAgentPrompt(stateRoot, "task-worker");
3075
+ if (basePrompt && localPrompt) {
3076
+ workerSystemPrompt = basePrompt + "\n\n---\n\n## Project-Specific Guidance\n\n" + localPrompt;
3077
+ } else if (basePrompt) {
3078
+ workerSystemPrompt = basePrompt;
3079
+ } else if (localPrompt) {
3080
+ workerSystemPrompt = localPrompt;
3000
3081
  }
3001
3082
  } catch { /* use default */ }
3002
3083
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.23.13",
3
+ "version": "0.23.14",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",