@dv.nghiem/flowdeck 0.4.4 → 0.4.6

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 (149) hide show
  1. package/dist/agents/index.d.ts.map +1 -1
  2. package/dist/agents/risk-analyst.d.ts.map +1 -1
  3. package/dist/dashboard/lib/state-reader.d.ts.map +1 -1
  4. package/dist/dashboard/server.mjs +22 -73
  5. package/dist/hooks/approval-hook.d.ts.map +1 -1
  6. package/dist/hooks/event-log-hook.d.ts +30 -0
  7. package/dist/hooks/event-log-hook.d.ts.map +1 -0
  8. package/dist/hooks/notifications.d.ts.map +1 -1
  9. package/dist/hooks/orchestrator-guard-hook.d.ts.map +1 -1
  10. package/dist/hooks/patch-trust.d.ts +0 -1
  11. package/dist/hooks/patch-trust.d.ts.map +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +627 -1159
  14. package/dist/lib/impact-radar.d.ts.map +1 -1
  15. package/dist/lib/research-gate.d.ts +6 -2
  16. package/dist/lib/research-gate.d.ts.map +1 -1
  17. package/dist/services/agent-validator.d.ts +1 -1
  18. package/dist/services/agent-validator.d.ts.map +1 -1
  19. package/dist/services/event-logger.d.ts +18 -0
  20. package/dist/services/event-logger.d.ts.map +1 -0
  21. package/dist/services/supervisor-binding.d.ts.map +1 -1
  22. package/dist/services/token-metrics.d.ts +1 -1
  23. package/dist/services/workflow-scorecard.d.ts.map +1 -1
  24. package/dist/tools/delegate.d.ts +1 -2
  25. package/dist/tools/delegate.d.ts.map +1 -1
  26. package/dist/tools/run-pipeline.d.ts +1 -2
  27. package/dist/tools/run-pipeline.d.ts.map +1 -1
  28. package/docs/commands/fd-deploy-check.md +1 -5
  29. package/docs/commands/fd-reflect.md +8 -9
  30. package/docs/commands/fd-suggest.md +3 -4
  31. package/docs/concepts/architecture.md +0 -3
  32. package/docs/concepts/intelligence.md +2 -36
  33. package/docs/skills/index.md +0 -2
  34. package/package.json +2 -2
  35. package/src/commands/fd-deploy-check.md +1 -5
  36. package/src/commands/fd-reflect.md +4 -6
  37. package/src/commands/fd-suggest.md +3 -4
  38. package/src/skills/change-impact-radar/SKILL.md +3 -4
  39. package/src/skills/confidence-aware-planning/SKILL.md +0 -1
  40. package/src/skills/patch-trust-score/SKILL.md +3 -5
  41. package/dist/dashboard/lib/port-finder.test.d.ts +0 -2
  42. package/dist/dashboard/lib/port-finder.test.d.ts.map +0 -1
  43. package/dist/hooks/notifications.test.d.ts +0 -14
  44. package/dist/hooks/notifications.test.d.ts.map +0 -1
  45. package/dist/hooks/patch-trust.test.d.ts +0 -2
  46. package/dist/hooks/patch-trust.test.d.ts.map +0 -1
  47. package/dist/hooks/telemetry-hook.d.ts +0 -33
  48. package/dist/hooks/telemetry-hook.d.ts.map +0 -1
  49. package/dist/hooks/telemetry-hook.test.d.ts +0 -2
  50. package/dist/hooks/telemetry-hook.test.d.ts.map +0 -1
  51. package/dist/hooks/tool-guard.test.d.ts +0 -2
  52. package/dist/hooks/tool-guard.test.d.ts.map +0 -1
  53. package/dist/lib/research-gate.test.d.ts +0 -2
  54. package/dist/lib/research-gate.test.d.ts.map +0 -1
  55. package/dist/services/activity-reporter.d.ts +0 -96
  56. package/dist/services/activity-reporter.d.ts.map +0 -1
  57. package/dist/services/activity-reporter.test.d.ts +0 -2
  58. package/dist/services/activity-reporter.test.d.ts.map +0 -1
  59. package/dist/services/artifact-store.d.ts +0 -39
  60. package/dist/services/artifact-store.d.ts.map +0 -1
  61. package/dist/services/artifact-store.test.d.ts +0 -2
  62. package/dist/services/artifact-store.test.d.ts.map +0 -1
  63. package/dist/services/codegraph.test.d.ts +0 -2
  64. package/dist/services/codegraph.test.d.ts.map +0 -1
  65. package/dist/services/command-validator.test.d.ts +0 -2
  66. package/dist/services/command-validator.test.d.ts.map +0 -1
  67. package/dist/services/context-assembler.d.ts +0 -29
  68. package/dist/services/context-assembler.d.ts.map +0 -1
  69. package/dist/services/context-assembler.test.d.ts +0 -2
  70. package/dist/services/context-assembler.test.d.ts.map +0 -1
  71. package/dist/services/cost-budget.d.ts +0 -53
  72. package/dist/services/cost-budget.d.ts.map +0 -1
  73. package/dist/services/cost-budget.test.d.ts +0 -2
  74. package/dist/services/cost-budget.test.d.ts.map +0 -1
  75. package/dist/services/cost-estimator.test.d.ts +0 -2
  76. package/dist/services/cost-estimator.test.d.ts.map +0 -1
  77. package/dist/services/draft-verifier.d.ts +0 -48
  78. package/dist/services/draft-verifier.d.ts.map +0 -1
  79. package/dist/services/draft-verifier.test.d.ts +0 -2
  80. package/dist/services/draft-verifier.test.d.ts.map +0 -1
  81. package/dist/services/governance.test.d.ts +0 -11
  82. package/dist/services/governance.test.d.ts.map +0 -1
  83. package/dist/services/index.d.ts +0 -25
  84. package/dist/services/index.d.ts.map +0 -1
  85. package/dist/services/lazy-rule-loader.test.d.ts +0 -23
  86. package/dist/services/lazy-rule-loader.test.d.ts.map +0 -1
  87. package/dist/services/model-router-ext.test.d.ts +0 -2
  88. package/dist/services/model-router-ext.test.d.ts.map +0 -1
  89. package/dist/services/model-router.test.d.ts +0 -2
  90. package/dist/services/model-router.test.d.ts.map +0 -1
  91. package/dist/services/policy-compiler.d.ts +0 -27
  92. package/dist/services/policy-compiler.d.ts.map +0 -1
  93. package/dist/services/preflight-explorer.test.d.ts +0 -25
  94. package/dist/services/preflight-explorer.test.d.ts.map +0 -1
  95. package/dist/services/prompt-cache-ext.test.d.ts +0 -2
  96. package/dist/services/prompt-cache-ext.test.d.ts.map +0 -1
  97. package/dist/services/prompt-cache.test.d.ts +0 -2
  98. package/dist/services/prompt-cache.test.d.ts.map +0 -1
  99. package/dist/services/quick-router.test.d.ts +0 -13
  100. package/dist/services/quick-router.test.d.ts.map +0 -1
  101. package/dist/services/recommended-question.test.d.ts +0 -2
  102. package/dist/services/recommended-question.test.d.ts.map +0 -1
  103. package/dist/services/rtk-manager.test.d.ts +0 -2
  104. package/dist/services/rtk-manager.test.d.ts.map +0 -1
  105. package/dist/services/rtk-policy.test.d.ts +0 -2
  106. package/dist/services/rtk-policy.test.d.ts.map +0 -1
  107. package/dist/services/rule-engine.d.ts +0 -29
  108. package/dist/services/rule-engine.d.ts.map +0 -1
  109. package/dist/services/rule-engine.test.d.ts +0 -2
  110. package/dist/services/rule-engine.test.d.ts.map +0 -1
  111. package/dist/services/services.test.d.ts +0 -2
  112. package/dist/services/services.test.d.ts.map +0 -1
  113. package/dist/services/supervisor.test.d.ts +0 -14
  114. package/dist/services/supervisor.test.d.ts.map +0 -1
  115. package/dist/services/task-batcher.d.ts +0 -48
  116. package/dist/services/task-batcher.d.ts.map +0 -1
  117. package/dist/services/task-batcher.test.d.ts +0 -2
  118. package/dist/services/task-batcher.test.d.ts.map +0 -1
  119. package/dist/services/telemetry.d.ts +0 -48
  120. package/dist/services/telemetry.d.ts.map +0 -1
  121. package/dist/services/token-budget.d.ts +0 -44
  122. package/dist/services/token-budget.d.ts.map +0 -1
  123. package/dist/services/token-budget.test.d.ts +0 -2
  124. package/dist/services/token-budget.test.d.ts.map +0 -1
  125. package/dist/services/token-metrics-ext.test.d.ts +0 -2
  126. package/dist/services/token-metrics-ext.test.d.ts.map +0 -1
  127. package/dist/services/token-metrics.test.d.ts +0 -2
  128. package/dist/services/token-metrics.test.d.ts.map +0 -1
  129. package/dist/tools/agent-dispatch.test.d.ts +0 -2
  130. package/dist/tools/agent-dispatch.test.d.ts.map +0 -1
  131. package/dist/tools/codebase-index.test.d.ts +0 -2
  132. package/dist/tools/codebase-index.test.d.ts.map +0 -1
  133. package/dist/tools/context-generator.d.ts +0 -3
  134. package/dist/tools/context-generator.d.ts.map +0 -1
  135. package/dist/tools/create-skill.d.ts +0 -3
  136. package/dist/tools/create-skill.d.ts.map +0 -1
  137. package/dist/tools/dispatch-routing.test.d.ts +0 -2
  138. package/dist/tools/dispatch-routing.test.d.ts.map +0 -1
  139. package/dist/tools/failure-replay.test.d.ts +0 -2
  140. package/dist/tools/failure-replay.test.d.ts.map +0 -1
  141. package/dist/tools/repo-memory.test.d.ts +0 -2
  142. package/dist/tools/repo-memory.test.d.ts.map +0 -1
  143. package/dist/tools/volatility-map.d.ts +0 -18
  144. package/dist/tools/volatility-map.d.ts.map +0 -1
  145. package/dist/tools/volatility-map.test.d.ts +0 -2
  146. package/dist/tools/volatility-map.test.d.ts.map +0 -1
  147. package/dist/tools/workspace-state.d.ts +0 -3
  148. package/dist/tools/workspace-state.d.ts.map +0 -1
  149. package/src/skills/volatility-map/SKILL.md +0 -52
package/dist/index.js CHANGED
@@ -2,10 +2,10 @@ import { createRequire } from "node:module";
2
2
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
3
 
4
4
  // src/index.ts
5
- import { readFileSync as readFileSync31, readdirSync as readdirSync5, existsSync as existsSync33 } from "fs";
6
- import { join as join31, basename as basename2 } from "path";
7
- import { dirname as dirname5 } from "path";
8
- import { fileURLToPath as fileURLToPath3 } from "url";
5
+ import { readFileSync as readFileSync28, readdirSync as readdirSync4, existsSync as existsSync29 } from "fs";
6
+ import { join as join27, basename as basename2 } from "path";
7
+ import { dirname as dirname3 } from "path";
8
+ import { fileURLToPath as fileURLToPath2 } from "url";
9
9
 
10
10
  // src/services/lazy-rule-loader.ts
11
11
  import { existsSync, readFileSync, readdirSync } from "fs";
@@ -393,14 +393,6 @@ function findWorkspaceRoot(startDir) {
393
393
  }
394
394
  return null;
395
395
  }
396
- function resolveSubRepos(configPath, subRepos) {
397
- const configDir = dirname(configPath);
398
- return subRepos.map((r) => {
399
- if (resolve(r) === r)
400
- return r;
401
- return resolve(configDir, r);
402
- });
403
- }
404
396
  function getWorkspaceConfig(dir) {
405
397
  const root = findWorkspaceRoot(dir);
406
398
  if (!root)
@@ -577,173 +569,30 @@ ${entry}`, "utf-8");
577
569
  }
578
570
  });
579
571
 
580
- // src/tools/workspace-state.ts
581
- import { tool as tool3 } from "@opencode-ai/plugin";
582
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
583
- import { join as join5 } from "path";
584
- function getRepoName(repoPath) {
585
- return repoPath.split(/[/\\]/).pop() || repoPath;
586
- }
587
- function readWorkspaceStateFile(workspaceRoot) {
588
- const sp = join5(planningDir(workspaceRoot), "STATE.md");
589
- if (!existsSync5(sp))
590
- return null;
591
- return readFileSync5(sp, "utf-8");
592
- }
593
- function writeWorkspaceStateFile(workspaceRoot, content) {
594
- const sp = join5(planningDir(workspaceRoot), "STATE.md");
595
- writeFileSync4(sp, content, "utf-8");
596
- }
597
- function parseWorkspaceState(content) {
598
- const result = {};
599
- const lines = content.split(`
600
- `);
601
- for (const line of lines) {
602
- const kvMatch = line.match(/^\*\*([^:]+):\*\*\s*(.+)/);
603
- if (kvMatch) {
604
- result[kvMatch[1].trim()] = kvMatch[2].trim();
605
- }
606
- }
607
- return result;
608
- }
609
- async function readWorkspaceContextAction(dir, mode, workspaceRoot) {
610
- const stateContent = readWorkspaceStateFile(workspaceRoot);
611
- if (!stateContent) {
612
- return { error: "Workspace STATE.md not found. Initialize workspace first." };
613
- }
614
- const parsed = parseWorkspaceState(stateContent);
615
- return { exists: true, workspace_root: workspaceRoot, workspace_mode: mode, ...parsed };
616
- }
617
- async function updateWorkspaceContextAction(dir, mode, workspaceRoot, updates) {
618
- if (!updates)
619
- return { error: "No updates provided" };
620
- let content = readWorkspaceStateFile(workspaceRoot);
621
- if (!content) {
622
- content = `# Workspace State
623
-
624
- ---
625
-
626
- `;
627
- }
628
- if (updates.current_repo !== undefined) {
629
- const regex = /^\*\*current_repo:\*\*.*/m;
630
- if (regex.test(content)) {
631
- content = content.replace(regex, `**current_repo:** ${updates.current_repo}`);
632
- } else {
633
- content = content.replace(/^---\n/, `---
634
-
635
- **current_repo:** ${updates.current_repo}
636
- `);
637
- }
638
- }
639
- if (updates.status !== undefined) {
640
- const regex = /^\*\*status:\*\*.*/m;
641
- if (regex.test(content)) {
642
- content = content.replace(regex, `**status:** ${updates.status}`);
643
- }
644
- }
645
- writeWorkspaceStateFile(workspaceRoot, content);
646
- return { success: true, updated_at: timestamp() };
647
- }
648
- async function listSubReposAction(dir, subRepos, workspaceRoot) {
649
- const resolved = resolveSubRepos(join5(workspaceRoot, ".planning", "config.json"), subRepos);
650
- const repos = [];
651
- for (const repoPath of resolved) {
652
- const repoName = getRepoName(repoPath);
653
- const planningPath = planningDir(repoPath);
654
- const hasPlanning = existsSync5(planningPath);
655
- repos.push({
656
- name: repoName,
657
- path: repoPath,
658
- status: hasPlanning ? "active" : "not_initialized"
659
- });
660
- }
661
- return { repos };
662
- }
663
- async function getSubRepoStateAction(dir, repoName, subRepos, workspaceRoot, mode) {
664
- if (!repoName)
665
- return { error: "repo name is required" };
666
- const resolved = resolveSubRepos(join5(workspaceRoot, ".planning", "config.json"), subRepos);
667
- const targetPath = resolved.find((p) => getRepoName(p) === repoName);
668
- if (!targetPath) {
669
- return { error: "not_found", path: repoName, message: `Repo '${repoName}' not found in sub_repos` };
670
- }
671
- const planningPath = planningDir(targetPath);
672
- if (!existsSync5(planningPath)) {
673
- return { error: "not_found", path: targetPath };
674
- }
675
- const sp = statePath(targetPath);
676
- if (!existsSync5(sp)) {
677
- return { error: "not_found", path: targetPath, message: `.planning/STATE.md not found in ${repoName}` };
678
- }
679
- const stateContent = readFileSync5(sp, "utf-8");
680
- const parsed = parseWorkspaceState(stateContent);
681
- return {
682
- repo_path: targetPath,
683
- repo_name: repoName,
684
- ...parsed
685
- };
686
- }
687
- var workspaceStateTool = tool3({
688
- description: "Manage workspace state across multiple repos: read workspace context, update context, list sub-repos, get sub-repo state",
689
- args: {
690
- action: tool3.schema.enum(["read_context", "update_context", "list_repos", "get_repo_state"]),
691
- updates: tool3.schema.object({
692
- current_repo: tool3.schema.string().optional(),
693
- status: tool3.schema.string().optional(),
694
- phase: tool3.schema.number().optional()
695
- }).optional(),
696
- repo: tool3.schema.string().optional()
697
- },
698
- async execute(args, context) {
699
- const dir = context.directory ?? process.cwd();
700
- const workspaceRoot = findWorkspaceRoot(dir);
701
- if (!workspaceRoot) {
702
- return JSON.stringify({ error: "Workspace root not found. No config.json with sub_repos found." });
703
- }
704
- const config = getWorkspaceConfig(dir);
705
- if (!config) {
706
- return JSON.stringify({ error: "Could not read workspace config." });
707
- }
708
- const mode = config.workspace_mode;
709
- const subRepos = config.sub_repos || [];
710
- switch (args.action) {
711
- case "read_context":
712
- return JSON.stringify(await readWorkspaceContextAction(dir, mode, workspaceRoot));
713
- case "update_context":
714
- return JSON.stringify(await updateWorkspaceContextAction(dir, mode, workspaceRoot, args.updates));
715
- case "list_repos":
716
- return JSON.stringify(await listSubReposAction(dir, subRepos, workspaceRoot));
717
- case "get_repo_state":
718
- return JSON.stringify(await getSubRepoStateAction(dir, args.repo, subRepos, workspaceRoot, mode));
719
- }
720
- }
721
- });
722
-
723
572
  // src/tools/run-pipeline.ts
724
- import { tool as tool4 } from "@opencode-ai/plugin";
573
+ import { tool as tool3 } from "@opencode-ai/plugin";
725
574
 
726
575
  // src/services/agent-performance.ts
727
- import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync2 } from "fs";
728
- import { join as join6 } from "path";
576
+ import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4, mkdirSync as mkdirSync2 } from "fs";
577
+ import { join as join5 } from "path";
729
578
  function perfPath(dir) {
730
- return join6(codebaseDir(dir), "AGENT_PERF.json");
579
+ return join5(codebaseDir(dir), "AGENT_PERF.json");
731
580
  }
732
581
  function loadStore(dir) {
733
582
  const p = perfPath(dir);
734
- if (!existsSync6(p))
583
+ if (!existsSync5(p))
735
584
  return { entries: [], updated_at: new Date().toISOString() };
736
585
  try {
737
- return JSON.parse(readFileSync6(p, "utf-8"));
586
+ return JSON.parse(readFileSync5(p, "utf-8"));
738
587
  } catch {
739
588
  return { entries: [], updated_at: new Date().toISOString() };
740
589
  }
741
590
  }
742
591
  function saveStore(dir, store) {
743
592
  const cd = codebaseDir(dir);
744
- if (!existsSync6(cd))
593
+ if (!existsSync5(cd))
745
594
  mkdirSync2(cd, { recursive: true });
746
- writeFileSync5(perfPath(dir), JSON.stringify(store, null, 2), "utf-8");
595
+ writeFileSync4(perfPath(dir), JSON.stringify(store, null, 2), "utf-8");
747
596
  }
748
597
  function makeKey(agent, model, task_type) {
749
598
  return `${agent}::${model}::${task_type}`;
@@ -867,227 +716,24 @@ function isUiHeavyTask(input) {
867
716
  return !hasOnlyNonUiSignals;
868
717
  }
869
718
 
870
- // src/services/activity-reporter.ts
871
- var SUMMARY_MAX_NORMAL = 120;
872
- var SUMMARY_MAX_DEBUG = 600;
873
- var HEARTBEAT_INTERVAL_MS = 15000;
874
- var TOAST_ON_START_TOOLS = new Set(["delegate", "run-pipeline"]);
875
- function isDebugMode() {
876
- return process.env.FLOWDECK_DEBUG === "true" || process.env.FLOWDECK_DEBUG === "1";
877
- }
878
- function summarize(text, maxLen = SUMMARY_MAX_NORMAL) {
879
- if (!text)
880
- return "";
881
- const s = text.trim().replace(/\s+/g, " ");
882
- return s.length <= maxLen ? s : s.slice(0, maxLen - 1) + "…";
883
- }
884
- function fmtDuration(ms) {
885
- if (ms < 1000)
886
- return `${ms}ms`;
887
- return `${(ms / 1000).toFixed(1)}s`;
888
- }
889
- function normalizeCommandName(raw) {
890
- return raw.replace(/^\//, "").replace(/^fd-/, "");
891
- }
892
-
893
- class ActivityReporter {
894
- log;
895
- toastFn;
896
- startTimes = new Map;
897
- heartbeats = new Map;
898
- constructor(log, toast) {
899
- this.log = log;
900
- this.toastFn = toast;
901
- }
902
- emit(msg) {
903
- try {
904
- this.log(msg);
905
- } catch {}
906
- }
907
- toastNow(msg, variant, duration) {
908
- if (!this.toastFn)
909
- return;
910
- try {
911
- this.toastFn(msg, variant, duration);
912
- } catch {}
913
- }
914
- trackStart(key) {
915
- this.startTimes.set(key, Date.now());
916
- const toolName = key.split(":").pop() ?? key;
917
- const interval = setInterval(() => {
918
- const startMs = this.startTimes.get(key);
919
- if (startMs === undefined)
920
- return;
921
- const elapsed = Date.now() - startMs;
922
- const msg = `[⋯ ${toolName}] still running (${fmtDuration(elapsed)})`;
923
- this.emit(msg);
924
- this.toastNow(msg, "info", 8000);
925
- }, HEARTBEAT_INTERVAL_MS);
926
- if (typeof interval.unref === "function") {
927
- interval.unref();
928
- }
929
- this.heartbeats.set(key, interval);
930
- }
931
- elapsedMs(key) {
932
- const interval = this.heartbeats.get(key);
933
- if (interval !== undefined) {
934
- clearInterval(interval);
935
- this.heartbeats.delete(key);
936
- }
937
- const t = this.startTimes.get(key);
938
- if (t === undefined)
939
- return;
940
- this.startTimes.delete(key);
941
- return Date.now() - t;
942
- }
943
- reportToolStarted(tool4, inputSummary, meta = {}) {
944
- const maxLen = isDebugMode() ? SUMMARY_MAX_DEBUG : SUMMARY_MAX_NORMAL;
945
- const parts = [`[→ ${tool4}]`];
946
- if (meta.agent)
947
- parts.push(`agent=${meta.agent}`);
948
- if (inputSummary)
949
- parts.push(summarize(inputSummary, maxLen));
950
- if (isDebugMode()) {
951
- if (meta.session_id)
952
- parts.push(`session=${meta.session_id}`);
953
- if (meta.stage)
954
- parts.push(`stage=${meta.stage}`);
955
- if (meta.run_id)
956
- parts.push(`run=${meta.run_id}`);
957
- }
958
- this.emit(parts.join(" "));
959
- if (TOAST_ON_START_TOOLS.has(tool4)) {
960
- const agentPart = meta.agent ? ` @${meta.agent}` : "";
961
- const inputPart = inputSummary ? `: ${summarize(inputSummary, 60)}` : "";
962
- this.toastNow(`→ ${tool4}${agentPart}${inputPart}`, "info", 3000);
963
- }
964
- }
965
- reportToolCompleted(tool4, durationMs, resultSummary, meta = {}) {
966
- const maxLen = isDebugMode() ? SUMMARY_MAX_DEBUG : SUMMARY_MAX_NORMAL;
967
- const dur = durationMs !== undefined ? ` (${fmtDuration(durationMs)})` : "";
968
- const parts = [`[✓ ${tool4}]${dur}`];
969
- if (meta.agent)
970
- parts.push(`agent=${meta.agent}`);
971
- if (resultSummary)
972
- parts.push(summarize(resultSummary, maxLen));
973
- if (isDebugMode() && meta.retry_count && meta.retry_count > 0) {
974
- parts.push(`retries=${meta.retry_count}`);
975
- }
976
- this.emit(parts.join(" "));
977
- }
978
- reportToolFailed(tool4, durationMs, error, meta = {}) {
979
- const dur = durationMs !== undefined ? ` (${fmtDuration(durationMs)})` : "";
980
- const parts = [`[✗ ${tool4}]${dur}`];
981
- if (meta.agent)
982
- parts.push(`agent=${meta.agent}`);
983
- parts.push(`error=${summarize(error, isDebugMode() ? SUMMARY_MAX_DEBUG : 200)}`);
984
- if (isDebugMode() && meta.retry_count && meta.retry_count > 0) {
985
- parts.push(`retries=${meta.retry_count}`);
986
- }
987
- this.emit(parts.join(" "));
988
- this.toastNow(`✗ ${tool4}${dur}: ${summarize(error, 80)}`, "error", 8000);
989
- }
990
- reportToolRetried(tool4, attempt, reason, meta = {}) {
991
- const parts = [`[↺ ${tool4}] retry attempt=${attempt}`];
992
- if (meta.agent)
993
- parts.push(`agent=${meta.agent}`);
994
- if (reason)
995
- parts.push(`reason=${summarize(reason, 80)}`);
996
- this.emit(parts.join(" "));
997
- this.toastNow(`↺ ${tool4} retry #${attempt}${meta.agent ? ` @${meta.agent}` : ""}`, "warning", 5000);
998
- }
999
- reportToolFallback(fromTool, toTool, reason, meta = {}) {
1000
- const parts = [`[⇢ fallback] ${fromTool} → ${toTool}`];
1001
- if (reason)
1002
- parts.push(`reason=${summarize(reason, 80)}`);
1003
- if (meta.agent)
1004
- parts.push(`agent=${meta.agent}`);
1005
- this.emit(parts.join(" "));
1006
- this.toastNow(`⇢ fallback: ${fromTool} → ${toTool}`, "info", 4000);
1007
- }
1008
- reportCacheHit(tool4, agent, meta = {}) {
1009
- const parts = [`[≡ ${tool4}] cache hit agent=${agent}`];
1010
- if (isDebugMode() && meta.session_id)
1011
- parts.push(`session=${meta.session_id}`);
1012
- this.emit(parts.join(" "));
1013
- }
1014
- reportSkipped(tool4, reason, meta = {}) {
1015
- const parts = [`[⊘ ${tool4}] skipped`];
1016
- if (reason)
1017
- parts.push(`reason=${summarize(reason, 80)}`);
1018
- if (meta.agent)
1019
- parts.push(`agent=${meta.agent}`);
1020
- this.emit(parts.join(" "));
1021
- }
1022
- reportStageProgress(stage, status, detail, meta = {}) {
1023
- const icon = {
1024
- started: "▶",
1025
- running: "⋯",
1026
- complete: "●",
1027
- failed: "✗",
1028
- waiting: "⌛"
1029
- };
1030
- const sym = icon[status] ?? "·";
1031
- const parts = [`[${sym} ${stage}] ${status}`];
1032
- if (detail)
1033
- parts.push(summarize(detail));
1034
- if (isDebugMode() && meta.workflow_id)
1035
- parts.push(`workflow=${meta.workflow_id}`);
1036
- this.emit(parts.join(" "));
1037
- const detailPart = detail ? `: ${summarize(detail, 60)}` : "";
1038
- switch (status) {
1039
- case "started":
1040
- this.toastNow(`▶ ${stage} started${detailPart}`, "info", 3000);
1041
- break;
1042
- case "complete":
1043
- this.toastNow(`● ${stage} complete${detailPart}`, "success", 4000);
1044
- break;
1045
- case "failed":
1046
- this.toastNow(`✗ ${stage} failed${detailPart}`, "error", 8000);
1047
- break;
1048
- case "waiting":
1049
- this.toastNow(`⌛ ${stage}: waiting for input${detailPart}`, "warning", 30000);
1050
- break;
1051
- }
1052
- }
1053
- reportWaitingForApproval(tool4, _meta = {}) {
1054
- const msg = `⌛ Approval required: ${tool4}`;
1055
- this.emit(msg);
1056
- this.toastNow(msg, "warning", 30000);
1057
- }
1058
- reportCommandStarted(command) {
1059
- const cmd = normalizeCommandName(command);
1060
- const msg = `▶ /${cmd} started`;
1061
- this.emit(msg);
1062
- this.toastNow(msg, "info", 2500);
1063
- }
1064
- reportCommandCompleted(command, hasEdits) {
1065
- const cmd = normalizeCommandName(command);
1066
- const detail = hasEdits ? " (files modified)" : "";
1067
- const msg = `● /${cmd} complete${detail}`;
1068
- this.emit(msg);
1069
- this.toastNow(msg, "success", 5000);
1070
- }
1071
- }
1072
-
1073
719
  // src/tools/run-pipeline.ts
1074
720
  function extractText(parts) {
1075
721
  return parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join(`
1076
722
  `);
1077
723
  }
1078
- function createRunPipelineTool(client, reporter) {
1079
- return tool4({
724
+ function createRunPipelineTool(client) {
725
+ return tool3({
1080
726
  description: "Run agents in sequential pipeline. Each step's output is appended to the next step's context. One fresh child session per step. Returns full trace with session ID, input/output/duration per step.",
1081
727
  args: {
1082
- steps: tool4.schema.array(tool4.schema.object({
1083
- agent: tool4.schema.string(),
1084
- prompt: tool4.schema.string(),
1085
- task_type: tool4.schema.string().optional()
728
+ steps: tool3.schema.array(tool3.schema.object({
729
+ agent: tool3.schema.string(),
730
+ prompt: tool3.schema.string(),
731
+ task_type: tool3.schema.string().optional()
1086
732
  })),
1087
- initial_context: tool4.schema.string().optional(),
1088
- abort_on_failure: tool4.schema.boolean().optional().default(true),
1089
- retry_attempts: tool4.schema.number().optional().default(1),
1090
- max_carry_chars: tool4.schema.number().optional()
733
+ initial_context: tool3.schema.string().optional(),
734
+ abort_on_failure: tool3.schema.boolean().optional().default(true),
735
+ retry_attempts: tool3.schema.number().optional().default(1),
736
+ max_carry_chars: tool3.schema.number().optional()
1091
737
  },
1092
738
  async execute(args, context) {
1093
739
  const startTime = Date.now();
@@ -1097,7 +743,6 @@ function createRunPipelineTool(client, reporter) {
1097
743
  const retryAttempts = typeof args.retry_attempts === "number" ? args.retry_attempts : 1;
1098
744
  const maxRetries = Math.max(0, Math.floor(retryAttempts));
1099
745
  const totalSteps = args.steps.length;
1100
- reporter?.reportStageProgress("pipeline", "started", `${totalSteps} step(s)`);
1101
746
  let inflightChildId = null;
1102
747
  const abortHandler = () => {
1103
748
  if (inflightChildId) {
@@ -1122,10 +767,6 @@ function createRunPipelineTool(client, reporter) {
1122
767
  ---
1123
768
 
1124
769
  ${step.prompt}` : step.prompt;
1125
- reporter?.reportToolStarted("run-pipeline", summarize(step.prompt, 80), {
1126
- agent: step.agent,
1127
- stage: `step ${stepIdx + 1}/${totalSteps}`
1128
- });
1129
770
  const createRes = await client.session.create({
1130
771
  body: { parentID: context.sessionID, title: `${step.agent}-pipeline` },
1131
772
  query: { directory: context.directory }
@@ -1133,7 +774,6 @@ ${step.prompt}` : step.prompt;
1133
774
  if (createRes.error || !createRes.data?.id) {
1134
775
  const errMsg = `Failed to create session: ${createRes.error?.detail ?? "unknown"}`;
1135
776
  trace.push({ agent: step.agent, task_type: taskType, model: "", input: stepInput, output: errMsg, duration_ms: Date.now() - stepStart, success: false });
1136
- reporter?.reportToolFailed("run-pipeline", Date.now() - stepStart, errMsg, { agent: step.agent });
1137
777
  aborted = true;
1138
778
  break;
1139
779
  }
@@ -1153,7 +793,6 @@ ${step.prompt}` : step.prompt;
1153
793
  if (!shouldRetry(promptRes) || attempt === maxRetries)
1154
794
  break;
1155
795
  retriesUsed++;
1156
- reporter?.reportToolRetried("run-pipeline", retriesUsed, "prompt response indicated retry", { agent: step.agent });
1157
796
  }
1158
797
  inflightChildId = null;
1159
798
  if (context.abort.aborted) {
@@ -1164,7 +803,6 @@ ${step.prompt}` : step.prompt;
1164
803
  const errMsg = `Prompt failed: ${promptRes?.error?.detail ?? "unknown"}`;
1165
804
  trace.push({ agent: step.agent, session_id: createRes.data.id, task_type: taskType, model: "", input: stepInput, output: `${errMsg}${retriesUsed > 0 ? ` (retries: ${retriesUsed})` : ""}`, duration_ms: Date.now() - stepStart, success: false });
1166
805
  recordRun(context.directory, step.agent, "", taskType, false, Date.now() - stepStart);
1167
- reporter?.reportToolFailed("run-pipeline", Date.now() - stepStart, errMsg, { agent: step.agent, retry_count: retriesUsed });
1168
806
  if (args.abort_on_failure) {
1169
807
  aborted = true;
1170
808
  break;
@@ -1176,7 +814,6 @@ ${step.prompt}` : step.prompt;
1176
814
  const errMsg = `Agent error: ${JSON.stringify(info.error)}`;
1177
815
  trace.push({ agent: step.agent, session_id: createRes.data.id, task_type: taskType, model: "", input: stepInput, output: `${errMsg}${retriesUsed > 0 ? ` (retries: ${retriesUsed})` : ""}`, duration_ms: Date.now() - stepStart, success: false });
1178
816
  recordRun(context.directory, step.agent, "", taskType, false, Date.now() - stepStart);
1179
- reporter?.reportToolFailed("run-pipeline", Date.now() - stepStart, errMsg, { agent: step.agent, retry_count: retriesUsed });
1180
817
  if (args.abort_on_failure) {
1181
818
  aborted = true;
1182
819
  break;
@@ -1186,11 +823,6 @@ ${step.prompt}` : step.prompt;
1186
823
  const output = extractText(promptRes.data?.parts ?? []);
1187
824
  trace.push({ agent: step.agent, session_id: createRes.data.id, task_type: taskType, model: "", input: stepInput, output: output || "(no text output)", duration_ms: Date.now() - stepStart, success: true, context_chars: carryContext.length });
1188
825
  recordRun(context.directory, step.agent, "", taskType, true, Date.now() - stepStart);
1189
- reporter?.reportToolCompleted("run-pipeline", Date.now() - stepStart, summarize(output, 80), {
1190
- agent: step.agent,
1191
- retry_count: retriesUsed,
1192
- stage: `step ${stepIdx + 1}/${totalSteps}`
1193
- });
1194
826
  const rawOutput = output || "";
1195
827
  carryContext = typeof args.max_carry_chars === "number" && rawOutput.length > args.max_carry_chars ? rawOutput.slice(rawOutput.length - args.max_carry_chars) : rawOutput;
1196
828
  }
@@ -1198,11 +830,6 @@ ${step.prompt}` : step.prompt;
1198
830
  context.abort.removeEventListener("abort", abortHandler);
1199
831
  }
1200
832
  const totalDuration = Date.now() - startTime;
1201
- if (aborted) {
1202
- reporter?.reportStageProgress("pipeline", "failed", `aborted after ${trace.length}/${totalSteps} steps`);
1203
- } else {
1204
- reporter?.reportStageProgress("pipeline", "complete", `${totalSteps} step(s) in ${totalDuration}ms`);
1205
- }
1206
833
  return JSON.stringify({
1207
834
  steps: trace,
1208
835
  total_duration_ms: totalDuration,
@@ -1213,13 +840,13 @@ ${step.prompt}` : step.prompt;
1213
840
  }
1214
841
 
1215
842
  // src/tools/delegate.ts
1216
- import { tool as tool5 } from "@opencode-ai/plugin";
1217
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
843
+ import { tool as tool4 } from "@opencode-ai/plugin";
844
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
1218
845
 
1219
846
  // src/services/prompt-cache.ts
1220
847
  import { createHash } from "crypto";
1221
- import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6, readdirSync as readdirSync3, statSync, mkdirSync as mkdirSync3 } from "fs";
1222
- import { join as join7 } from "path";
848
+ import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync5, readdirSync as readdirSync3, statSync, mkdirSync as mkdirSync3 } from "fs";
849
+ import { join as join6 } from "path";
1223
850
  var CACHEABLE_AGENTS = new Set([
1224
851
  "researcher",
1225
852
  "code-explorer",
@@ -1233,17 +860,17 @@ var CACHE_DIR_NAME = "prompt-cache";
1233
860
  var MAX_CACHE_ENTRIES = 200;
1234
861
  var DEFAULT_TTL_MS = 30 * 60 * 1000;
1235
862
  function cacheDir(dir) {
1236
- return join7(codebaseDir(dir), CACHE_DIR_NAME);
863
+ return join6(codebaseDir(dir), CACHE_DIR_NAME);
1237
864
  }
1238
865
  function entryPath(dir, key) {
1239
- return join7(cacheDir(dir), `${key}.json`);
866
+ return join6(cacheDir(dir), `${key}.json`);
1240
867
  }
1241
868
  function readEntry(dir, key, stateVersion, indexVersion) {
1242
869
  const path = entryPath(dir, key);
1243
- if (!existsSync7(path))
870
+ if (!existsSync6(path))
1244
871
  return null;
1245
872
  try {
1246
- const entry = JSON.parse(readFileSync7(path, "utf-8"));
873
+ const entry = JSON.parse(readFileSync6(path, "utf-8"));
1247
874
  const age = Date.now() - new Date(entry.created_at).getTime();
1248
875
  if (age > entry.ttl_ms)
1249
876
  return null;
@@ -1291,7 +918,7 @@ function setCached(dir, agent, prompt, context, stateVersion, indexVersion, resp
1291
918
  if (!CACHEABLE_AGENTS.has(agent))
1292
919
  return;
1293
920
  const cd = cacheDir(dir);
1294
- if (!existsSync7(cd))
921
+ if (!existsSync6(cd))
1295
922
  mkdirSync3(cd, { recursive: true });
1296
923
  const key = hashKey(agent, prompt, context, stateVersion, indexVersion);
1297
924
  const entry = {
@@ -1303,21 +930,21 @@ function setCached(dir, agent, prompt, context, stateVersion, indexVersion, resp
1303
930
  ttl_ms,
1304
931
  response
1305
932
  };
1306
- writeFileSync6(entryPath(dir, key), JSON.stringify(entry, null, 2), "utf-8");
933
+ writeFileSync5(entryPath(dir, key), JSON.stringify(entry, null, 2), "utf-8");
1307
934
  pruneExpired(dir);
1308
935
  }
1309
936
  function pruneExpired(dir) {
1310
937
  const cd = cacheDir(dir);
1311
- if (!existsSync7(cd))
938
+ if (!existsSync6(cd))
1312
939
  return;
1313
940
  try {
1314
941
  const files = readdirSync3(cd).filter((f) => f.endsWith(".json"));
1315
942
  const now = Date.now();
1316
943
  const entries = [];
1317
944
  for (const f of files) {
1318
- const p = join7(cd, f);
945
+ const p = join6(cd, f);
1319
946
  try {
1320
- const entry = JSON.parse(readFileSync7(p, "utf-8"));
947
+ const entry = JSON.parse(readFileSync6(p, "utf-8"));
1321
948
  const age = now - new Date(entry.created_at).getTime();
1322
949
  entries.push({ path: p, created_at: new Date(entry.created_at).getTime(), expired: age > entry.ttl_ms });
1323
950
  } catch {
@@ -1344,15 +971,15 @@ function pruneExpired(dir) {
1344
971
  }
1345
972
 
1346
973
  // src/tools/codebase-index.ts
1347
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
1348
- import { join as join8 } from "path";
974
+ import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
975
+ import { join as join7 } from "path";
1349
976
  var CODEBASE_INDEX_FILE = "CODEBASE_INDEX.md";
1350
977
  function indexPath(dir) {
1351
- return join8(planningDir(dir), CODEBASE_INDEX_FILE);
978
+ return join7(planningDir(dir), CODEBASE_INDEX_FILE);
1352
979
  }
1353
980
  function readCodebaseIndex(dir) {
1354
981
  const path = indexPath(dir);
1355
- if (!existsSync8(path)) {
982
+ if (!existsSync7(path)) {
1356
983
  return {
1357
984
  exists: false,
1358
985
  lastUpdatedAt: "",
@@ -1366,7 +993,7 @@ function readCodebaseIndex(dir) {
1366
993
  };
1367
994
  }
1368
995
  try {
1369
- const content = readFileSync8(path, "utf-8");
996
+ const content = readFileSync7(path, "utf-8");
1370
997
  return parseCodebaseIndexContent(content);
1371
998
  } catch {
1372
999
  return {
@@ -1442,17 +1069,17 @@ function parseCodebaseIndexContent(content) {
1442
1069
  }
1443
1070
 
1444
1071
  // src/services/token-metrics.ts
1445
- import { existsSync as existsSync9, readFileSync as readFileSync9, appendFileSync, mkdirSync as mkdirSync5 } from "fs";
1446
- import { join as join9 } from "path";
1072
+ import { existsSync as existsSync8, readFileSync as readFileSync8, appendFileSync, mkdirSync as mkdirSync5 } from "fs";
1073
+ import { join as join8 } from "path";
1447
1074
  function estimateTokens(text) {
1448
1075
  return Math.ceil(text.length / 4);
1449
1076
  }
1450
1077
  function metricsPath(dir) {
1451
- return join9(codebaseDir(dir), "TOKEN_METRICS.jsonl");
1078
+ return join8(codebaseDir(dir), "TOKEN_METRICS.jsonl");
1452
1079
  }
1453
1080
  function appendEvent(dir, event) {
1454
1081
  const cd = codebaseDir(dir);
1455
- if (!existsSync9(cd))
1082
+ if (!existsSync8(cd))
1456
1083
  mkdirSync5(cd, { recursive: true });
1457
1084
  appendFileSync(metricsPath(dir), JSON.stringify(event) + `
1458
1085
  `, "utf-8");
@@ -1510,27 +1137,25 @@ function recordRetryCall(dir, workflow_id, stage, inputText, outputText, agent,
1510
1137
  var _workflowTimers = new Map;
1511
1138
 
1512
1139
  // src/config/loader.ts
1513
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
1514
- import { join as join10 } from "path";
1140
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
1141
+ import { join as join9 } from "path";
1515
1142
  import { homedir } from "os";
1516
1143
  var CONFIG_FILENAME = "flowdeck.json";
1517
1144
  function getGlobalConfigDir() {
1518
- return process.env.OPENCODE_CONFIG_DIR || (process.env.XDG_CONFIG_HOME ? join10(process.env.XDG_CONFIG_HOME, "opencode") : join10(homedir(), ".config", "opencode"));
1145
+ return process.env.OPENCODE_CONFIG_DIR || (process.env.XDG_CONFIG_HOME ? join9(process.env.XDG_CONFIG_HOME, "opencode") : join9(homedir(), ".config", "opencode"));
1519
1146
  }
1520
1147
  function loadFlowDeckConfig(directory) {
1521
1148
  const candidates = [];
1522
1149
  if (directory) {
1523
- candidates.push(join10(directory, ".opencode", CONFIG_FILENAME));
1150
+ candidates.push(join9(directory, ".opencode", CONFIG_FILENAME));
1524
1151
  }
1525
- candidates.push(join10(getGlobalConfigDir(), CONFIG_FILENAME));
1152
+ candidates.push(join9(getGlobalConfigDir(), CONFIG_FILENAME));
1526
1153
  for (const configPath of candidates) {
1527
- if (existsSync10(configPath)) {
1154
+ if (existsSync9(configPath)) {
1528
1155
  try {
1529
- const content = readFileSync10(configPath, "utf-8");
1156
+ const content = readFileSync9(configPath, "utf-8");
1530
1157
  return JSON.parse(content);
1531
- } catch {
1532
- console.warn(`[flowdeck] Failed to load config from ${configPath}`);
1533
- }
1158
+ } catch {}
1534
1159
  }
1535
1160
  }
1536
1161
  return {};
@@ -1611,19 +1236,19 @@ function extractText2(parts) {
1611
1236
  return parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join(`
1612
1237
  `);
1613
1238
  }
1614
- function createDelegateTool(client, reporter) {
1615
- return tool5({
1239
+ function createDelegateTool(client) {
1240
+ return tool4({
1616
1241
  description: "Delegate a task to a single agent via a child session. Returns the agent's output.",
1617
1242
  args: {
1618
- agent: tool5.schema.string(),
1619
- prompt: tool5.schema.string(),
1620
- context: tool5.schema.string().optional(),
1621
- task_type: tool5.schema.string().optional(),
1622
- retry_attempts: tool5.schema.number().optional().default(1),
1623
- safe_to_cache: tool5.schema.boolean().optional().default(false),
1624
- cache_ttl_ms: tool5.schema.number().optional(),
1625
- workflow_id: tool5.schema.string().optional(),
1626
- stage: tool5.schema.string().optional()
1243
+ agent: tool4.schema.string(),
1244
+ prompt: tool4.schema.string(),
1245
+ context: tool4.schema.string().optional(),
1246
+ task_type: tool4.schema.string().optional(),
1247
+ retry_attempts: tool4.schema.number().optional().default(1),
1248
+ safe_to_cache: tool4.schema.boolean().optional().default(false),
1249
+ cache_ttl_ms: tool4.schema.number().optional(),
1250
+ workflow_id: tool4.schema.string().optional(),
1251
+ stage: tool4.schema.string().optional()
1627
1252
  },
1628
1253
  async execute(args, context) {
1629
1254
  const startTime = Date.now();
@@ -1642,17 +1267,13 @@ function createDelegateTool(client, reporter) {
1642
1267
  ---
1643
1268
 
1644
1269
  ${args.prompt}` : args.prompt;
1645
- reporter?.reportToolStarted("delegate", summarize(args.prompt, 100), {
1646
- agent: args.agent,
1647
- stage: args.stage
1648
- });
1649
1270
  const safe_to_cache = args.safe_to_cache === true && CACHEABLE_AGENTS.has(args.agent);
1650
1271
  let stateVersion = 0;
1651
1272
  let indexVersion = 0;
1652
1273
  if (safe_to_cache) {
1653
1274
  const index = readCodebaseIndex(context.directory);
1654
1275
  const sp = statePath(context.directory);
1655
- const rawState = existsSync11(sp) ? readFileSync11(sp, "utf-8") : "";
1276
+ const rawState = existsSync10(sp) ? readFileSync10(sp, "utf-8") : "";
1656
1277
  const state = rawState ? parseState(rawState) : {};
1657
1278
  stateVersion = typeof state.summaryVersion === "number" ? state.summaryVersion : 0;
1658
1279
  indexVersion = typeof index.summaryVersion === "number" ? index.summaryVersion : 0;
@@ -1661,7 +1282,6 @@ ${args.prompt}` : args.prompt;
1661
1282
  if (metricsWorkflowId) {
1662
1283
  recordCacheHit(context.directory, metricsWorkflowId, metricsStage, fullPrompt, args.agent, agentModel);
1663
1284
  }
1664
- reporter?.reportCacheHit("delegate", args.agent);
1665
1285
  return JSON.stringify({
1666
1286
  agent: args.agent,
1667
1287
  success: true,
@@ -1719,15 +1339,10 @@ ${args.prompt}` : args.prompt;
1719
1339
  recordRetryCall(context.directory, metricsWorkflowId, metricsStage, fullPromptForSession, "", args.agent, Date.now() - attemptStart, agentModel, retryCostUsd);
1720
1340
  }
1721
1341
  retriesUsed++;
1722
- reporter?.reportToolRetried("delegate", retriesUsed, "prompt response indicated retry", { agent: args.agent });
1723
1342
  }
1724
1343
  if (!promptRes || promptRes.error) {
1725
1344
  const errMsg = `Prompt failed: ${promptRes?.error?.detail ?? "unknown"}`;
1726
1345
  recordRun(context.directory, args.agent, "", taskType, false, Date.now() - startTime);
1727
- reporter?.reportToolFailed("delegate", Date.now() - startTime, errMsg, {
1728
- agent: args.agent,
1729
- retry_count: retriesUsed
1730
- });
1731
1346
  return JSON.stringify({
1732
1347
  agent: args.agent,
1733
1348
  session_id: childId,
@@ -1743,10 +1358,6 @@ ${args.prompt}` : args.prompt;
1743
1358
  if (info?.error) {
1744
1359
  const errMsg = `Agent error: ${JSON.stringify(info.error)}`;
1745
1360
  recordRun(context.directory, args.agent, "", taskType, false, Date.now() - startTime);
1746
- reporter?.reportToolFailed("delegate", Date.now() - startTime, errMsg, {
1747
- agent: args.agent,
1748
- retry_count: retriesUsed
1749
- });
1750
1361
  return JSON.stringify({
1751
1362
  agent: args.agent,
1752
1363
  session_id: childId,
@@ -1766,10 +1377,6 @@ ${args.prompt}` : args.prompt;
1766
1377
  const costUsd = agentModel ? estimateCostUSD(agentModel, inputTokens, outputTokens) : undefined;
1767
1378
  recordModelCall(context.directory, metricsWorkflowId, metricsStage, fullPromptForSession, output, args.agent, Date.now() - startTime, agentModel, costUsd);
1768
1379
  }
1769
- reporter?.reportToolCompleted("delegate", Date.now() - startTime, summarize(output, 80), {
1770
- agent: args.agent,
1771
- retry_count: retriesUsed
1772
- });
1773
1380
  if (safe_to_cache && output) {
1774
1381
  setCached(context.directory, args.agent, fullPromptForSession, args.context ?? "", stateVersion, indexVersion, output, true, args.cache_ttl_ms);
1775
1382
  }
@@ -1788,53 +1395,53 @@ ${args.prompt}` : args.prompt;
1788
1395
  }
1789
1396
 
1790
1397
  // src/tools/repo-memory.ts
1791
- import { tool as tool6 } from "@opencode-ai/plugin";
1792
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, existsSync as existsSync12, mkdirSync as mkdirSync6 } from "fs";
1793
- import { join as join11 } from "path";
1398
+ import { tool as tool5 } from "@opencode-ai/plugin";
1399
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, existsSync as existsSync11, mkdirSync as mkdirSync6 } from "fs";
1400
+ import { join as join10 } from "path";
1794
1401
  var MEMORY_FILE = "MEMORY.json";
1795
1402
  function memoryPath(directory) {
1796
- return join11(codebaseDir(directory), MEMORY_FILE);
1403
+ return join10(codebaseDir(directory), MEMORY_FILE);
1797
1404
  }
1798
1405
  function emptyMemory() {
1799
1406
  return { version: "1.0", last_updated: new Date().toISOString(), nodes: {} };
1800
1407
  }
1801
1408
  function readMemory(directory) {
1802
1409
  const p = memoryPath(directory);
1803
- if (!existsSync12(p))
1410
+ if (!existsSync11(p))
1804
1411
  return emptyMemory();
1805
1412
  try {
1806
- return JSON.parse(readFileSync12(p, "utf-8"));
1413
+ return JSON.parse(readFileSync11(p, "utf-8"));
1807
1414
  } catch {
1808
1415
  return emptyMemory();
1809
1416
  }
1810
1417
  }
1811
1418
  function writeMemory(directory, memory) {
1812
1419
  const base = codebaseDir(directory);
1813
- if (!existsSync12(base))
1420
+ if (!existsSync11(base))
1814
1421
  mkdirSync6(base, { recursive: true });
1815
1422
  memory.last_updated = new Date().toISOString();
1816
- writeFileSync8(memoryPath(directory), JSON.stringify(memory, null, 2), "utf-8");
1423
+ writeFileSync7(memoryPath(directory), JSON.stringify(memory, null, 2), "utf-8");
1817
1424
  }
1818
- var repoMemoryTool = tool6({
1425
+ var repoMemoryTool = tool5({
1819
1426
  description: "Repo Memory Graph: read/write/query persistent architecture graph in .codebase/MEMORY.json (modules, dependencies, ownership, bug history, conventions)",
1820
1427
  args: {
1821
- action: tool6.schema.enum(["read", "write_node", "query", "delete_node"]),
1822
- node_id: tool6.schema.string().optional(),
1823
- node: tool6.schema.object({
1824
- type: tool6.schema.enum(["module", "service", "api", "schema", "config"]),
1825
- path: tool6.schema.string(),
1826
- owner: tool6.schema.string().optional(),
1827
- tags: tool6.schema.array(tool6.schema.string()),
1828
- dependencies: tool6.schema.array(tool6.schema.string()),
1829
- dependents: tool6.schema.array(tool6.schema.string()),
1830
- bug_history: tool6.schema.array(tool6.schema.string()),
1831
- conventions: tool6.schema.array(tool6.schema.string())
1428
+ action: tool5.schema.enum(["read", "write_node", "query", "delete_node"]),
1429
+ node_id: tool5.schema.string().optional(),
1430
+ node: tool5.schema.object({
1431
+ type: tool5.schema.enum(["module", "service", "api", "schema", "config"]),
1432
+ path: tool5.schema.string(),
1433
+ owner: tool5.schema.string().optional(),
1434
+ tags: tool5.schema.array(tool5.schema.string()),
1435
+ dependencies: tool5.schema.array(tool5.schema.string()),
1436
+ dependents: tool5.schema.array(tool5.schema.string()),
1437
+ bug_history: tool5.schema.array(tool5.schema.string()),
1438
+ conventions: tool5.schema.array(tool5.schema.string())
1832
1439
  }).optional(),
1833
- query: tool6.schema.object({
1834
- type: tool6.schema.enum(["module", "service", "api", "schema", "config"]).optional(),
1835
- owner: tool6.schema.string().optional(),
1836
- tag: tool6.schema.string().optional(),
1837
- path_prefix: tool6.schema.string().optional()
1440
+ query: tool5.schema.object({
1441
+ type: tool5.schema.enum(["module", "service", "api", "schema", "config"]).optional(),
1442
+ owner: tool5.schema.string().optional(),
1443
+ tag: tool5.schema.string().optional(),
1444
+ path_prefix: tool5.schema.string().optional()
1838
1445
  }).optional()
1839
1446
  },
1840
1447
  async execute(args, context) {
@@ -1889,50 +1496,50 @@ var repoMemoryTool = tool6({
1889
1496
  });
1890
1497
 
1891
1498
  // src/tools/failure-replay.ts
1892
- import { tool as tool7 } from "@opencode-ai/plugin";
1893
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, existsSync as existsSync13, mkdirSync as mkdirSync7 } from "fs";
1894
- import { join as join12 } from "path";
1499
+ import { tool as tool6 } from "@opencode-ai/plugin";
1500
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, existsSync as existsSync12, mkdirSync as mkdirSync7 } from "fs";
1501
+ import { join as join11 } from "path";
1895
1502
  var FAILURES_FILE = "FAILURES.json";
1896
1503
  function failuresPath(directory) {
1897
- return join12(codebaseDir(directory), FAILURES_FILE);
1504
+ return join11(codebaseDir(directory), FAILURES_FILE);
1898
1505
  }
1899
1506
  function readStore(directory) {
1900
1507
  const p = failuresPath(directory);
1901
- if (!existsSync13(p))
1508
+ if (!existsSync12(p))
1902
1509
  return { version: "1.0", last_updated: new Date().toISOString(), entries: [] };
1903
1510
  try {
1904
- return JSON.parse(readFileSync13(p, "utf-8"));
1511
+ return JSON.parse(readFileSync12(p, "utf-8"));
1905
1512
  } catch {
1906
1513
  return { version: "1.0", last_updated: new Date().toISOString(), entries: [] };
1907
1514
  }
1908
1515
  }
1909
1516
  function writeStore(directory, store) {
1910
1517
  const base = codebaseDir(directory);
1911
- if (!existsSync13(base))
1518
+ if (!existsSync12(base))
1912
1519
  mkdirSync7(base, { recursive: true });
1913
1520
  store.last_updated = new Date().toISOString();
1914
- writeFileSync9(failuresPath(directory), JSON.stringify(store, null, 2), "utf-8");
1521
+ writeFileSync8(failuresPath(directory), JSON.stringify(store, null, 2), "utf-8");
1915
1522
  }
1916
- var failureReplayTool = tool7({
1523
+ var failureReplayTool = tool6({
1917
1524
  description: "Failure Replay Engine: record and query past failures (reverted commits, failed deployments, flaky tests, bug fixes) in .codebase/FAILURES.json so the agent avoids repeating mistakes",
1918
1525
  args: {
1919
- action: tool7.schema.enum(["record", "query", "list", "mark_resolved"]),
1920
- entry: tool7.schema.object({
1921
- id: tool7.schema.string(),
1922
- type: tool7.schema.enum(["reverted_commit", "failed_deployment", "flaky_test", "bug_fix", "build_failure"]),
1923
- description: tool7.schema.string(),
1924
- affected_paths: tool7.schema.array(tool7.schema.string()),
1925
- root_cause: tool7.schema.string().optional(),
1926
- fix_applied: tool7.schema.string().optional(),
1927
- tags: tool7.schema.array(tool7.schema.string())
1526
+ action: tool6.schema.enum(["record", "query", "list", "mark_resolved"]),
1527
+ entry: tool6.schema.object({
1528
+ id: tool6.schema.string(),
1529
+ type: tool6.schema.enum(["reverted_commit", "failed_deployment", "flaky_test", "bug_fix", "build_failure"]),
1530
+ description: tool6.schema.string(),
1531
+ affected_paths: tool6.schema.array(tool6.schema.string()),
1532
+ root_cause: tool6.schema.string().optional(),
1533
+ fix_applied: tool6.schema.string().optional(),
1534
+ tags: tool6.schema.array(tool6.schema.string())
1928
1535
  }).optional(),
1929
- query: tool7.schema.object({
1930
- type: tool7.schema.enum(["reverted_commit", "failed_deployment", "flaky_test", "bug_fix", "build_failure"]).optional(),
1931
- path_prefix: tool7.schema.string().optional(),
1932
- tag: tool7.schema.string().optional(),
1933
- limit: tool7.schema.number().optional()
1536
+ query: tool6.schema.object({
1537
+ type: tool6.schema.enum(["reverted_commit", "failed_deployment", "flaky_test", "bug_fix", "build_failure"]).optional(),
1538
+ path_prefix: tool6.schema.string().optional(),
1539
+ tag: tool6.schema.string().optional(),
1540
+ limit: tool6.schema.number().optional()
1934
1541
  }).optional(),
1935
- entry_id: tool7.schema.string().optional()
1542
+ entry_id: tool6.schema.string().optional()
1936
1543
  },
1937
1544
  async execute(args, context) {
1938
1545
  const dir = context.directory ?? process.cwd();
@@ -1994,18 +1601,18 @@ var failureReplayTool = tool7({
1994
1601
  });
1995
1602
 
1996
1603
  // src/tools/decision-trace.ts
1997
- import { tool as tool8 } from "@opencode-ai/plugin";
1998
- import { readFileSync as readFileSync14, existsSync as existsSync14, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2 } from "fs";
1999
- import { join as join13 } from "path";
1604
+ import { tool as tool7 } from "@opencode-ai/plugin";
1605
+ import { readFileSync as readFileSync13, existsSync as existsSync13, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2 } from "fs";
1606
+ import { join as join12 } from "path";
2000
1607
  var DECISIONS_FILE = "DECISIONS.jsonl";
2001
1608
  function decisionsPath(directory) {
2002
- return join13(codebaseDir(directory), DECISIONS_FILE);
1609
+ return join12(codebaseDir(directory), DECISIONS_FILE);
2003
1610
  }
2004
1611
  function readDecisions(directory) {
2005
1612
  const p = decisionsPath(directory);
2006
- if (!existsSync14(p))
1613
+ if (!existsSync13(p))
2007
1614
  return [];
2008
- return readFileSync14(p, "utf-8").split(`
1615
+ return readFileSync13(p, "utf-8").split(`
2009
1616
  `).filter((l) => l.trim()).map((l) => {
2010
1617
  try {
2011
1618
  return JSON.parse(l);
@@ -2014,29 +1621,29 @@ function readDecisions(directory) {
2014
1621
  }
2015
1622
  }).filter(Boolean);
2016
1623
  }
2017
- var decisionTraceTool = tool8({
1624
+ var decisionTraceTool = tool7({
2018
1625
  description: "Decision Trace: record why the agent changed something, what evidence was used, and assumptions made. Stored in .codebase/DECISIONS.jsonl for fast review.",
2019
1626
  args: {
2020
- action: tool8.schema.enum(["record", "query", "get_for_file"]),
2021
- entry: tool8.schema.object({
2022
- id: tool8.schema.string(),
2023
- file_path: tool8.schema.string(),
2024
- change_type: tool8.schema.enum(["create", "edit", "delete", "refactor"]),
2025
- rationale: tool8.schema.string(),
2026
- evidence: tool8.schema.array(tool8.schema.string()),
2027
- assumptions: tool8.schema.array(tool8.schema.string()),
2028
- alternatives_considered: tool8.schema.array(tool8.schema.string()),
2029
- risk_level: tool8.schema.enum(["low", "medium", "high"]),
2030
- agent: tool8.schema.string().optional(),
2031
- session_id: tool8.schema.string().optional()
1627
+ action: tool7.schema.enum(["record", "query", "get_for_file"]),
1628
+ entry: tool7.schema.object({
1629
+ id: tool7.schema.string(),
1630
+ file_path: tool7.schema.string(),
1631
+ change_type: tool7.schema.enum(["create", "edit", "delete", "refactor"]),
1632
+ rationale: tool7.schema.string(),
1633
+ evidence: tool7.schema.array(tool7.schema.string()),
1634
+ assumptions: tool7.schema.array(tool7.schema.string()),
1635
+ alternatives_considered: tool7.schema.array(tool7.schema.string()),
1636
+ risk_level: tool7.schema.enum(["low", "medium", "high"]),
1637
+ agent: tool7.schema.string().optional(),
1638
+ session_id: tool7.schema.string().optional()
2032
1639
  }).optional(),
2033
- query: tool8.schema.object({
2034
- file_path: tool8.schema.string().optional(),
2035
- change_type: tool8.schema.enum(["create", "edit", "delete", "refactor"]).optional(),
2036
- risk_level: tool8.schema.enum(["low", "medium", "high"]).optional(),
2037
- limit: tool8.schema.number().optional()
1640
+ query: tool7.schema.object({
1641
+ file_path: tool7.schema.string().optional(),
1642
+ change_type: tool7.schema.enum(["create", "edit", "delete", "refactor"]).optional(),
1643
+ risk_level: tool7.schema.enum(["low", "medium", "high"]).optional(),
1644
+ limit: tool7.schema.number().optional()
2038
1645
  }).optional(),
2039
- file_path: tool8.schema.string().optional()
1646
+ file_path: tool7.schema.string().optional()
2040
1647
  },
2041
1648
  async execute(args, context) {
2042
1649
  const dir = context.directory ?? process.cwd();
@@ -2045,7 +1652,7 @@ var decisionTraceTool = tool8({
2045
1652
  case "record": {
2046
1653
  if (!args.entry)
2047
1654
  return JSON.stringify({ error: "entry required" });
2048
- if (!existsSync14(base))
1655
+ if (!existsSync13(base))
2049
1656
  mkdirSync8(base, { recursive: true });
2050
1657
  const entry = { ...args.entry, timestamp: new Date().toISOString() };
2051
1658
  appendFileSync2(decisionsPath(dir), JSON.stringify(entry) + `
@@ -2078,162 +1685,54 @@ var decisionTraceTool = tool8({
2078
1685
  }
2079
1686
  });
2080
1687
 
2081
- // src/tools/volatility-map.ts
2082
- import { tool as tool9 } from "@opencode-ai/plugin";
2083
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
2084
- import { join as join14 } from "path";
2085
- var VOLATILITY_FILE = "VOLATILITY.json";
2086
- function volatilityPath(directory) {
2087
- return join14(codebaseDir(directory), VOLATILITY_FILE);
2088
- }
2089
- function readStore2(directory) {
2090
- const p = volatilityPath(directory);
2091
- if (!existsSync15(p))
2092
- return { version: "1.0", last_updated: new Date().toISOString(), generated_at: new Date().toISOString(), entries: [] };
2093
- try {
2094
- return JSON.parse(readFileSync15(p, "utf-8"));
2095
- } catch {
2096
- return { version: "1.0", last_updated: new Date().toISOString(), generated_at: new Date().toISOString(), entries: [] };
2097
- }
2098
- }
2099
- function writeStore2(directory, store) {
2100
- const base = codebaseDir(directory);
2101
- if (!existsSync15(base))
2102
- mkdirSync9(base, { recursive: true });
2103
- store.last_updated = new Date().toISOString();
2104
- writeFileSync11(volatilityPath(directory), JSON.stringify(store, null, 2), "utf-8");
2105
- }
2106
- function stabilityLabel(churn, hotfixes, todos) {
2107
- const score = churn + hotfixes * 10 + todos * 2;
2108
- if (score >= 80)
2109
- return "critical";
2110
- if (score >= 50)
2111
- return "volatile";
2112
- if (score >= 20)
2113
- return "moderate";
2114
- return "stable";
2115
- }
2116
- var volatilityMapTool = tool9({
2117
- description: "Codebase Volatility Map: read/write/query .codebase/VOLATILITY.json — highlights unstable zones based on churn, hotfix frequency, and TODO clusters",
2118
- args: {
2119
- action: tool9.schema.enum(["read", "write", "query_hotspots", "update_entry"]),
2120
- entries: tool9.schema.array(tool9.schema.object({
2121
- path: tool9.schema.string(),
2122
- churn_score: tool9.schema.number(),
2123
- hotfix_count: tool9.schema.number(),
2124
- todo_count: tool9.schema.number(),
2125
- last_breakage: tool9.schema.string().optional(),
2126
- notes: tool9.schema.array(tool9.schema.string())
2127
- })).optional(),
2128
- entry: tool9.schema.object({
2129
- path: tool9.schema.string(),
2130
- churn_score: tool9.schema.number(),
2131
- hotfix_count: tool9.schema.number(),
2132
- todo_count: tool9.schema.number(),
2133
- last_breakage: tool9.schema.string().optional(),
2134
- notes: tool9.schema.array(tool9.schema.string())
2135
- }).optional(),
2136
- threshold: tool9.schema.enum(["stable", "moderate", "volatile", "critical"]).optional(),
2137
- path_prefix: tool9.schema.string().optional(),
2138
- limit: tool9.schema.number().optional()
2139
- },
2140
- async execute(args, context) {
2141
- const dir = context.directory ?? process.cwd();
2142
- const store = readStore2(dir);
2143
- switch (args.action) {
2144
- case "read": {
2145
- return JSON.stringify({ last_updated: store.last_updated, count: store.entries.length, entries: store.entries });
2146
- }
2147
- case "write": {
2148
- if (!args.entries)
2149
- return JSON.stringify({ error: "entries required" });
2150
- store.entries = args.entries.map((e) => ({
2151
- ...e,
2152
- stability: stabilityLabel(e.churn_score, e.hotfix_count, e.todo_count)
2153
- }));
2154
- store.generated_at = new Date().toISOString();
2155
- writeStore2(dir, store);
2156
- return JSON.stringify({ success: true, count: store.entries.length });
2157
- }
2158
- case "update_entry": {
2159
- if (!args.entry)
2160
- return JSON.stringify({ error: "entry required" });
2161
- const idx = store.entries.findIndex((e) => e.path === args.entry.path);
2162
- const updated = {
2163
- ...args.entry,
2164
- stability: stabilityLabel(args.entry.churn_score, args.entry.hotfix_count, args.entry.todo_count)
2165
- };
2166
- if (idx >= 0) {
2167
- store.entries[idx] = updated;
2168
- } else {
2169
- store.entries.push(updated);
2170
- }
2171
- writeStore2(dir, store);
2172
- return JSON.stringify({ success: true, path: args.entry.path, stability: updated.stability });
2173
- }
2174
- case "query_hotspots": {
2175
- const levels = { stable: 0, moderate: 1, volatile: 2, critical: 3 };
2176
- const minLevel = levels[args.threshold ?? "volatile"] ?? 2;
2177
- let results = store.entries.filter((e) => (levels[e.stability] ?? 0) >= minLevel);
2178
- if (args.path_prefix)
2179
- results = results.filter((e) => e.path.startsWith(args.path_prefix));
2180
- results.sort((a, b) => levels[b.stability] - levels[a.stability] || b.churn_score - a.churn_score);
2181
- if (args.limit)
2182
- results = results.slice(0, args.limit);
2183
- return JSON.stringify({ count: results.length, hotspots: results });
2184
- }
2185
- }
2186
- }
2187
- });
2188
-
2189
1688
  // src/tools/policy-engine.ts
2190
- import { tool as tool10 } from "@opencode-ai/plugin";
2191
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync12, existsSync as existsSync16, mkdirSync as mkdirSync10 } from "fs";
2192
- import { join as join15 } from "path";
1689
+ import { tool as tool8 } from "@opencode-ai/plugin";
1690
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, existsSync as existsSync14, mkdirSync as mkdirSync9 } from "fs";
1691
+ import { join as join13 } from "path";
2193
1692
  var POLICIES_FILE = "POLICIES.json";
2194
1693
  function policiesPath(directory) {
2195
- return join15(codebaseDir(directory), POLICIES_FILE);
1694
+ return join13(codebaseDir(directory), POLICIES_FILE);
2196
1695
  }
2197
- function readStore3(directory) {
1696
+ function readStore2(directory) {
2198
1697
  const p = policiesPath(directory);
2199
- if (!existsSync16(p))
1698
+ if (!existsSync14(p))
2200
1699
  return { version: "1.0", last_updated: new Date().toISOString(), policies: [] };
2201
1700
  try {
2202
- return JSON.parse(readFileSync16(p, "utf-8"));
1701
+ return JSON.parse(readFileSync14(p, "utf-8"));
2203
1702
  } catch {
2204
1703
  return { version: "1.0", last_updated: new Date().toISOString(), policies: [] };
2205
1704
  }
2206
1705
  }
2207
- function writeStore3(directory, store) {
1706
+ function writeStore2(directory, store) {
2208
1707
  const base = codebaseDir(directory);
2209
- if (!existsSync16(base))
2210
- mkdirSync10(base, { recursive: true });
1708
+ if (!existsSync14(base))
1709
+ mkdirSync9(base, { recursive: true });
2211
1710
  store.last_updated = new Date().toISOString();
2212
- writeFileSync12(policiesPath(directory), JSON.stringify(store, null, 2), "utf-8");
1711
+ writeFileSync10(policiesPath(directory), JSON.stringify(store, null, 2), "utf-8");
2213
1712
  }
2214
- var policyEngineTool = tool10({
1713
+ var policyEngineTool = tool8({
2215
1714
  description: "Self-Healing Policy Engine: manage .codebase/POLICIES.json — add, list, query, toggle, and record violations of editing policies learned from past failures",
2216
1715
  args: {
2217
- action: tool10.schema.enum(["list", "add", "record_violation", "toggle", "query"]),
2218
- policy: tool10.schema.object({
2219
- id: tool10.schema.string(),
2220
- name: tool10.schema.string(),
2221
- trigger: tool10.schema.string(),
2222
- rule: tool10.schema.string(),
2223
- source: tool10.schema.enum(["manual", "learned"]),
2224
- failure_count: tool10.schema.number()
1716
+ action: tool8.schema.enum(["list", "add", "record_violation", "toggle", "query"]),
1717
+ policy: tool8.schema.object({
1718
+ id: tool8.schema.string(),
1719
+ name: tool8.schema.string(),
1720
+ trigger: tool8.schema.string(),
1721
+ rule: tool8.schema.string(),
1722
+ source: tool8.schema.enum(["manual", "learned"]),
1723
+ failure_count: tool8.schema.number()
2225
1724
  }).optional(),
2226
- policy_id: tool10.schema.string().optional(),
2227
- active: tool10.schema.boolean().optional(),
2228
- query: tool10.schema.object({
2229
- source: tool10.schema.enum(["manual", "learned"]).optional(),
2230
- active_only: tool10.schema.boolean().optional(),
2231
- trigger_contains: tool10.schema.string().optional()
1725
+ policy_id: tool8.schema.string().optional(),
1726
+ active: tool8.schema.boolean().optional(),
1727
+ query: tool8.schema.object({
1728
+ source: tool8.schema.enum(["manual", "learned"]).optional(),
1729
+ active_only: tool8.schema.boolean().optional(),
1730
+ trigger_contains: tool8.schema.string().optional()
2232
1731
  }).optional()
2233
1732
  },
2234
1733
  async execute(args, context) {
2235
1734
  const dir = context.directory ?? process.cwd();
2236
- const store = readStore3(dir);
1735
+ const store = readStore2(dir);
2237
1736
  switch (args.action) {
2238
1737
  case "list": {
2239
1738
  const active = store.policies.filter((p) => p.active);
@@ -2248,7 +1747,7 @@ var policyEngineTool = tool10({
2248
1747
  } else {
2249
1748
  store.policies.push({ ...args.policy, created_at: new Date().toISOString(), active: true });
2250
1749
  }
2251
- writeStore3(dir, store);
1750
+ writeStore2(dir, store);
2252
1751
  return JSON.stringify({ success: true, id: args.policy.id });
2253
1752
  }
2254
1753
  case "record_violation": {
@@ -2259,7 +1758,7 @@ var policyEngineTool = tool10({
2259
1758
  return JSON.stringify({ error: `Policy not found: ${args.policy_id}` });
2260
1759
  policy.failure_count++;
2261
1760
  policy.last_violated = new Date().toISOString();
2262
- writeStore3(dir, store);
1761
+ writeStore2(dir, store);
2263
1762
  return JSON.stringify({ success: true, policy_id: args.policy_id, failure_count: policy.failure_count });
2264
1763
  }
2265
1764
  case "toggle": {
@@ -2269,7 +1768,7 @@ var policyEngineTool = tool10({
2269
1768
  if (!policy)
2270
1769
  return JSON.stringify({ error: `Policy not found: ${args.policy_id}` });
2271
1770
  policy.active = args.active !== undefined ? args.active : !policy.active;
2272
- writeStore3(dir, store);
1771
+ writeStore2(dir, store);
2273
1772
  return JSON.stringify({ success: true, policy_id: args.policy_id, active: policy.active });
2274
1773
  }
2275
1774
  case "query": {
@@ -2290,22 +1789,22 @@ var policyEngineTool = tool10({
2290
1789
  });
2291
1790
 
2292
1791
  // src/tools/hash-edit.ts
2293
- import { tool as tool11 } from "@opencode-ai/plugin";
2294
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync13 } from "fs";
1792
+ import { tool as tool9 } from "@opencode-ai/plugin";
1793
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync11 } from "fs";
2295
1794
  import { createHash as createHash2 } from "crypto";
2296
- var hashEditTool = tool11({
1795
+ var hashEditTool = tool9({
2297
1796
  description: "Reliable file editing with content verification. Takes a target content, its expected MD5 hash, and replacement content. Only applies if the hash matches, preventing edits on stale file versions.",
2298
1797
  args: {
2299
- filePath: tool11.schema.string(),
2300
- targetContent: tool11.schema.string(),
2301
- expectedHash: tool11.schema.string().optional(),
2302
- replacementContent: tool11.schema.string()
1798
+ filePath: tool9.schema.string(),
1799
+ targetContent: tool9.schema.string(),
1800
+ expectedHash: tool9.schema.string().optional(),
1801
+ replacementContent: tool9.schema.string()
2303
1802
  },
2304
1803
  async execute(args, context) {
2305
1804
  const fullPath = args.filePath.startsWith("/") ? args.filePath : `${context.directory}/${args.filePath}`;
2306
1805
  let content;
2307
1806
  try {
2308
- content = readFileSync17(fullPath, "utf-8");
1807
+ content = readFileSync15(fullPath, "utf-8");
2309
1808
  } catch (e) {
2310
1809
  return `Error: Could not read file ${args.filePath}`;
2311
1810
  }
@@ -2319,17 +1818,17 @@ var hashEditTool = tool11({
2319
1818
  }
2320
1819
  }
2321
1820
  const newContent = content.replace(args.targetContent, args.replacementContent);
2322
- writeFileSync13(fullPath, newContent, "utf-8");
1821
+ writeFileSync11(fullPath, newContent, "utf-8");
2323
1822
  return `Successfully updated ${args.filePath} using hash-anchored edit.`;
2324
1823
  }
2325
1824
  });
2326
1825
 
2327
1826
  // src/tools/council.ts
2328
- import { tool as tool12 } from "@opencode-ai/plugin";
2329
- import { appendFileSync as appendFileSync3, existsSync as existsSync17, mkdirSync as mkdirSync11 } from "fs";
2330
- import { join as join16 } from "path";
1827
+ import { tool as tool10 } from "@opencode-ai/plugin";
1828
+ import { appendFileSync as appendFileSync3, existsSync as existsSync15, mkdirSync as mkdirSync10 } from "fs";
1829
+ import { join as join14 } from "path";
2331
1830
  import { createHash as createHash3 } from "crypto";
2332
- import { readFileSync as readFileSync18 } from "fs";
1831
+ import { readFileSync as readFileSync16 } from "fs";
2333
1832
  var _councilCache = new Map;
2334
1833
  var COUNCIL_CACHE_TTL_MS = 20 * 60 * 1000;
2335
1834
  function councilCacheKey(task, agents, stateVersion, indexVersion) {
@@ -2350,20 +1849,20 @@ async function runWithConcurrencyLimit(tasks, limit) {
2350
1849
  return results;
2351
1850
  }
2352
1851
  function createCouncilTool(client) {
2353
- return tool12({
1852
+ return tool10({
2354
1853
  description: "Run an ensemble of agents (Council) on the same task to reach consensus or compare approaches. Runs specialized agents in parallel (bounded concurrency) and returns their synthesized outputs.",
2355
1854
  args: {
2356
- task: tool12.schema.string(),
2357
- agents: tool12.schema.array(tool12.schema.string()).optional(),
2358
- force_fresh: tool12.schema.boolean().optional().default(false),
2359
- max_concurrency: tool12.schema.number().optional().default(3)
1855
+ task: tool10.schema.string(),
1856
+ agents: tool10.schema.array(tool10.schema.string()).optional(),
1857
+ force_fresh: tool10.schema.boolean().optional().default(false),
1858
+ max_concurrency: tool10.schema.number().optional().default(3)
2360
1859
  },
2361
1860
  async execute(args, context) {
2362
1861
  const agents = args.agents || ["architect", "reviewer", "backend-coder"];
2363
1862
  const concurrencyLimit = Math.max(1, Math.min(5, typeof args.max_concurrency === "number" ? args.max_concurrency : 3));
2364
1863
  const index = readCodebaseIndex(context.directory);
2365
1864
  const sp = statePath(context.directory);
2366
- const rawState = existsSync17(sp) ? readFileSync18(sp, "utf-8") : "";
1865
+ const rawState = existsSync15(sp) ? readFileSync16(sp, "utf-8") : "";
2367
1866
  const state = rawState ? parseState(rawState) : {};
2368
1867
  const stateVersion = typeof state.summaryVersion === "number" ? state.summaryVersion : 0;
2369
1868
  const indexVersion = typeof index.summaryVersion === "number" ? index.summaryVersion : 0;
@@ -2439,117 +1938,18 @@ Please synthesize these results. Identify areas of agreement, resolve conflicts,
2439
1938
  function persistCouncilResult(directory, payload) {
2440
1939
  try {
2441
1940
  const base = codebaseDir(directory);
2442
- if (!existsSync17(base))
2443
- mkdirSync11(base, { recursive: true });
2444
- const path = join16(base, "COUNCILS.jsonl");
1941
+ if (!existsSync15(base))
1942
+ mkdirSync10(base, { recursive: true });
1943
+ const path = join14(base, "COUNCILS.jsonl");
2445
1944
  appendFileSync3(path, JSON.stringify(payload) + `
2446
1945
  `, "utf-8");
2447
1946
  } catch {}
2448
1947
  }
2449
1948
 
2450
- // src/tools/context-generator.ts
2451
- import { tool as tool13 } from "@opencode-ai/plugin";
2452
- import { writeFileSync as writeFileSync14, existsSync as existsSync18, readFileSync as readFileSync19, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
2453
- import { join as join17 } from "path";
2454
- var contextGeneratorTool = tool13({
2455
- description: "Auto-generate or update hierarchical context files (AGENTS.md, CLAUDE.md) throughout the project. These files provide critical grounding for AI agents.",
2456
- args: {
2457
- targetDir: tool13.schema.string().optional(),
2458
- force: tool13.schema.boolean().optional()
2459
- },
2460
- async execute(args, context) {
2461
- const root = context.directory;
2462
- const target = args.targetDir ? join17(root, args.targetDir) : root;
2463
- if (!existsSync18(target)) {
2464
- return `Error: Directory ${target} does not exist.`;
2465
- }
2466
- const agentsMdPath = join17(target, "AGENTS.md");
2467
- if (existsSync18(agentsMdPath) && !args.force) {
2468
- return `AGENTS.md already exists in ${target}. Use force: true to overwrite.`;
2469
- }
2470
- const pkgPath = join17(root, "package.json");
2471
- let projectName = "Project";
2472
- let techStack = "Unknown";
2473
- if (existsSync18(pkgPath)) {
2474
- try {
2475
- const pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
2476
- projectName = pkg.name || projectName;
2477
- techStack = Object.keys(pkg.dependencies || {}).slice(0, 5).join(", ");
2478
- } catch {}
2479
- }
2480
- const content = `# AGENTS.md for ${projectName}
2481
-
2482
- ## Context
2483
- - **Tech Stack**: ${techStack}
2484
- - **Primary Goal**: [Explain the main purpose of this directory/project]
2485
-
2486
- ## Rules for Agents
2487
- 1. **Consistency**: Follow existing patterns in this directory.
2488
- 2. **Safety**: Do not modify files in \`node_modules\` or other sensitive areas.
2489
- 3. **Planning**: Always check \`.planning/STATE.md\` before executing major changes.
2490
-
2491
- ## Directory Map
2492
- ${readdirSync4(target).slice(0, 10).map((f) => {
2493
- const s = statSync2(join17(target, f));
2494
- return `- \`${f}\`${s.isDirectory() ? "/" : ""} : [Description]`;
2495
- }).join(`
2496
- `)}
2497
-
2498
- ---
2499
- Generated by FlowDeck Context Generator.
2500
- `;
2501
- writeFileSync14(agentsMdPath, content, "utf-8");
2502
- return `Successfully generated AGENTS.md in ${target}.`;
2503
- }
2504
- });
2505
-
2506
- // src/tools/create-skill.ts
2507
- import { tool as tool14 } from "@opencode-ai/plugin";
2508
- import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync15, existsSync as existsSync19 } from "fs";
2509
- import { join as join18, dirname as dirname3 } from "path";
2510
- import { fileURLToPath } from "url";
2511
- var SKILLS_DIR = join18(dirname3(fileURLToPath(import.meta.url)), "..", "skills");
2512
- var createSkillTool = tool14({
2513
- description: "Create a new reusable skill in the FlowDeck skill library (src/skills/). " + "Use this when you discover a repeatable pattern, solve a novel problem with human guidance, " + "or want to capture domain knowledge for future sessions.",
2514
- args: {
2515
- name: tool14.schema.string().describe("Unique kebab-case skill name, e.g. 'api-rate-limiting'"),
2516
- description: tool14.schema.string().describe("One-sentence description of what this skill does"),
2517
- content: tool14.schema.string().describe("Full skill body in Markdown. Must include: ## When to Activate, ## Steps, and ## Examples sections."),
2518
- tags: tool14.schema.array(tool14.schema.string()).optional().describe("Optional tags for categorisation, e.g. ['performance', 'typescript']")
2519
- },
2520
- async execute(args) {
2521
- const skillDir = join18(SKILLS_DIR, args.name);
2522
- const skillFile = join18(skillDir, "SKILL.md");
2523
- if (existsSync19(skillFile)) {
2524
- return `Skill '${args.name}' already exists at ${skillFile}.
2525
- ` + `Use a different name or delete the existing skill directory first.`;
2526
- }
2527
- const tagLine = args.tags?.length ? `
2528
- tags: [${args.tags.join(", ")}]` : "";
2529
- const frontmatter = `---
2530
- name: ${args.name}
2531
- description: ${args.description}
2532
- origin: FlowDeck (self-learned)${tagLine}
2533
- ---
2534
-
2535
- `;
2536
- const fullContent = frontmatter + args.content.trimStart();
2537
- try {
2538
- mkdirSync12(skillDir, { recursive: true });
2539
- writeFileSync15(skillFile, fullContent, "utf-8");
2540
- return `✓ Skill '${args.name}' created at ${skillFile}
2541
-
2542
- ` + `The skill is now part of the FlowDeck library. Restart OpenCode to load it into the active session.`;
2543
- } catch (err) {
2544
- return `Error creating skill '${args.name}': ${err.message}`;
2545
- }
2546
- }
2547
- });
2548
-
2549
1949
  // src/tools/reflect.ts
2550
- import { tool as tool15 } from "@opencode-ai/plugin";
2551
- import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
2552
- import { join as join19 } from "path";
1950
+ import { tool as tool11 } from "@opencode-ai/plugin";
1951
+ import { existsSync as existsSync16, readFileSync as readFileSync17 } from "fs";
1952
+ import { join as join15 } from "path";
2553
1953
  var MAX_ARTIFACT_BYTES = 4000;
2554
1954
  function tail(text, maxBytes) {
2555
1955
  if (text.length <= maxBytes)
@@ -2557,10 +1957,10 @@ function tail(text, maxBytes) {
2557
1957
  return `... (truncated) ...
2558
1958
  ` + text.slice(-maxBytes);
2559
1959
  }
2560
- var reflectTool = tool15({
1960
+ var reflectTool = tool11({
2561
1961
  description: "Gather session artifacts (decisions, telemetry, failures, policies) and return a structured " + "reflection context that the agent can reason over to produce self-improvement proposals.",
2562
1962
  args: {
2563
- scope: tool15.schema.enum(["session", "project"]).optional().describe("'session' (default) uses only recent artifacts; 'project' includes all historical data")
1963
+ scope: tool11.schema.enum(["session", "project"]).optional().describe("'session' (default) uses only recent artifacts; 'project' includes all historical data")
2564
1964
  },
2565
1965
  async execute(args, context) {
2566
1966
  const root = context.directory;
@@ -2578,11 +1978,11 @@ var reflectTool = tool15({
2578
1978
  ];
2579
1979
  let found = 0;
2580
1980
  for (const [rel, label] of ARTIFACT_PATHS) {
2581
- const full = join19(root, rel);
2582
- if (!existsSync20(full))
1981
+ const full = join15(root, rel);
1982
+ if (!existsSync16(full))
2583
1983
  continue;
2584
1984
  try {
2585
- const raw = readFileSync20(full, "utf-8").trim();
1985
+ const raw = readFileSync17(full, "utf-8").trim();
2586
1986
  if (!raw)
2587
1987
  continue;
2588
1988
  const count = raw.split(`
@@ -2595,23 +1995,23 @@ var reflectTool = tool15({
2595
1995
  return `No FlowDeck artifacts found under .codebase/.
2596
1996
  ` + "Run some tasks first so decisions, telemetry, and failures are recorded.";
2597
1997
  }
2598
- sections.push("## What to do with this data", "Analyse the artifacts above and:", "1. **Identify patterns** — repeated tool sequences, recurring failure modes", "2. **Surface gaps** — knowledge or skills that were missing and had to be figured out", "3. **Propose improvements** — for each gap or pattern, either:", " - Call `create-skill` to capture it as a reusable skill, OR", " - Propose a new entry in `.codebase/POLICIES.json`", "4. **Summarise** — 3–5 bullet points of the most impactful takeaways");
1998
+ sections.push("## What to do with this data", "Analyse the artifacts above and:", "1. **Identify patterns** — repeated tool sequences, recurring failure modes", "2. **Surface gaps** — knowledge or skills that were missing and had to be figured out", "3. **Propose improvements** — for each gap or pattern, either:", " - Write a new skill markdown file under `src/skills/<name>/SKILL.md`, OR", " - Propose a new entry in `.codebase/POLICIES.json`", "4. **Summarise** — 3–5 bullet points of the most impactful takeaways");
2599
1999
  return sections.join(`
2600
2000
  `);
2601
2001
  }
2602
2002
  });
2603
2003
 
2604
2004
  // src/tools/codegraph-tool.ts
2605
- import { tool as tool16 } from "@opencode-ai/plugin";
2005
+ import { tool as tool12 } from "@opencode-ai/plugin";
2606
2006
 
2607
2007
  // src/services/codegraph.ts
2608
2008
  import { spawnSync } from "child_process";
2609
- import { existsSync as existsSync21, readFileSync as readFileSync21, writeFileSync as writeFileSync16, mkdirSync as mkdirSync13 } from "fs";
2610
- import { join as join20 } from "path";
2009
+ import { existsSync as existsSync17, readFileSync as readFileSync18, writeFileSync as writeFileSync12, mkdirSync as mkdirSync11 } from "fs";
2010
+ import { join as join16 } from "path";
2611
2011
  var CODEGRAPH_META_FILE = "CODEGRAPH.md";
2612
2012
  var MAX_FRESHNESS_MS = 30 * 60 * 1000;
2613
2013
  function metaPath(dir) {
2614
- return join20(codebaseDir(dir), CODEGRAPH_META_FILE);
2014
+ return join16(codebaseDir(dir), CODEGRAPH_META_FILE);
2615
2015
  }
2616
2016
  function isCodegraphInstalled() {
2617
2017
  try {
@@ -2626,11 +2026,11 @@ function isCodegraphInstalled() {
2626
2026
  }
2627
2027
  }
2628
2028
  function isCodegraphIndexed(dir) {
2629
- return existsSync21(join20(dir, ".codegraph", "codegraph.db"));
2029
+ return existsSync17(join16(dir, ".codegraph", "codegraph.db"));
2630
2030
  }
2631
2031
  function readCodegraphMeta(dir) {
2632
2032
  const path = metaPath(dir);
2633
- if (!existsSync21(path)) {
2033
+ if (!existsSync17(path)) {
2634
2034
  return {
2635
2035
  installed: false,
2636
2036
  indexed: false,
@@ -2643,7 +2043,7 @@ function readCodegraphMeta(dir) {
2643
2043
  };
2644
2044
  }
2645
2045
  try {
2646
- const content = readFileSync21(path, "utf-8");
2046
+ const content = readFileSync18(path, "utf-8");
2647
2047
  return parseCodegraphMeta(content);
2648
2048
  } catch {
2649
2049
  return {
@@ -2710,8 +2110,8 @@ function parseCodegraphMeta(content) {
2710
2110
  }
2711
2111
  function writeCodegraphMeta(dir, meta) {
2712
2112
  const base = codebaseDir(dir);
2713
- if (!existsSync21(base))
2714
- mkdirSync13(base, { recursive: true });
2113
+ if (!existsSync17(base))
2114
+ mkdirSync11(base, { recursive: true });
2715
2115
  const lines = [
2716
2116
  "# Codegraph Metadata",
2717
2117
  "",
@@ -2724,7 +2124,7 @@ function writeCodegraphMeta(dir, meta) {
2724
2124
  `**installLog:** ${meta.installLog}`,
2725
2125
  `**indexLog:** ${meta.indexLog}`
2726
2126
  ];
2727
- writeFileSync16(metaPath(dir), lines.join(`
2127
+ writeFileSync12(metaPath(dir), lines.join(`
2728
2128
  `), "utf-8");
2729
2129
  }
2730
2130
  function isCodegraphFresh(dir, maxAgeMs = MAX_FRESHNESS_MS) {
@@ -2935,11 +2335,11 @@ function markCodegraphStale(dir) {
2935
2335
  }
2936
2336
 
2937
2337
  // src/tools/codegraph-tool.ts
2938
- var codegraphTool = tool16({
2338
+ var codegraphTool = tool12({
2939
2339
  description: "Manage codegraph lifecycle only: check installation, install, init/rebuild the index, refresh (incremental sync), " + "query status, or mark-stale. Valid actions: check | install | init | refresh | status | mark-stale. " + "Do NOT use this tool for code intelligence queries (files, search, callers, callees, etc.) — " + "those are available as codegraph MCP tools (codegraph_files, codegraph_search, codegraph_context, " + "codegraph_explore, codegraph_callers, codegraph_callees, codegraph_impact, codegraph_trace) " + "when the index is ready.",
2940
2340
  args: {
2941
- action: tool16.schema.enum(["check", "install", "init", "refresh", "status", "mark-stale"]),
2942
- agent: tool16.schema.string().optional()
2341
+ action: tool12.schema.enum(["check", "install", "init", "refresh", "status", "mark-stale"]),
2342
+ agent: tool12.schema.string().optional()
2943
2343
  },
2944
2344
  async execute(args, context) {
2945
2345
  const dir = context.directory ?? process.cwd();
@@ -3028,21 +2428,21 @@ var codegraphTool = tool16({
3028
2428
  });
3029
2429
 
3030
2430
  // src/tools/load-rules.ts
3031
- import { tool as tool17 } from "@opencode-ai/plugin";
3032
- import { existsSync as existsSync22, readFileSync as readFileSync22 } from "fs";
3033
- import { join as join21, dirname as dirname4 } from "path";
3034
- import { fileURLToPath as fileURLToPath2 } from "url";
3035
- var RULES_DIR = join21(dirname4(fileURLToPath2(import.meta.url)), "..", "rules");
2431
+ import { tool as tool13 } from "@opencode-ai/plugin";
2432
+ import { existsSync as existsSync18, readFileSync as readFileSync19 } from "fs";
2433
+ import { join as join17, dirname as dirname2 } from "path";
2434
+ import { fileURLToPath } from "url";
2435
+ var RULES_DIR = join17(dirname2(fileURLToPath(import.meta.url)), "..", "rules");
3036
2436
  var _loadedPaths = new Set;
3037
- var loadRulesTool = tool17({
2437
+ var loadRulesTool = tool13({
3038
2438
  description: "Load additional rule modules on demand for the current workflow stage. " + "Use this at the start of a new stage (execute, verify, fix-bug) to load " + "coding-style, security, testing, and language-specific rules that were not " + "injected at startup. Returns the full text of selected rules. " + "Already-loaded rules are not returned again (suppressed to avoid duplication).",
3039
2439
  args: {
3040
- stage: tool17.schema.string().optional().describe("Current workflow stage: discuss | plan | execute | verify | fix-bug | write-docs"),
3041
- languages: tool17.schema.array(tool17.schema.string()).optional().describe("Project languages to load rules for, e.g. ['typescript']. " + "Omit to use all languages (returns all matching stage rules)."),
3042
- force_reload: tool17.schema.boolean().optional().default(false).describe("When true, return rules even if they were already loaded in this session. " + "Use only when stage context has changed and you need a fresh load.")
2440
+ stage: tool13.schema.string().optional().describe("Current workflow stage: discuss | plan | execute | verify | fix-bug | write-docs"),
2441
+ languages: tool13.schema.array(tool13.schema.string()).optional().describe("Project languages to load rules for, e.g. ['typescript']. " + "Omit to use all languages (returns all matching stage rules)."),
2442
+ force_reload: tool13.schema.boolean().optional().default(false).describe("When true, return rules even if they were already loaded in this session. " + "Use only when stage context has changed and you need a fresh load.")
3043
2443
  },
3044
2444
  async execute(args) {
3045
- const rulesDir = existsSync22(RULES_DIR) ? RULES_DIR : null;
2445
+ const rulesDir = existsSync18(RULES_DIR) ? RULES_DIR : null;
3046
2446
  if (!rulesDir) {
3047
2447
  return JSON.stringify({
3048
2448
  loaded: [],
@@ -3068,7 +2468,7 @@ var loadRulesTool = tool17({
3068
2468
  continue;
3069
2469
  }
3070
2470
  try {
3071
- const text = readFileSync22(rule.path, "utf-8");
2471
+ const text = readFileSync19(rule.path, "utf-8");
3072
2472
  contents.push(`## ${name}
3073
2473
 
3074
2474
  ${text}`);
@@ -3099,11 +2499,11 @@ ${text}`);
3099
2499
  function ruleShortName(rule) {
3100
2500
  return rule.path.replace(RULES_DIR + "/", "").replace(/\.md$/, "");
3101
2501
  }
3102
- var listRulesTool = tool17({
2502
+ var listRulesTool = tool13({
3103
2503
  description: "List all available FlowDeck rule modules with their metadata (description, always_on, " + "stages, languages). Use this before calling load-rules to see what is available. " + "Does NOT load rule content — only returns metadata for discovery.",
3104
2504
  args: {},
3105
2505
  async execute() {
3106
- const rulesDir = existsSync22(RULES_DIR) ? RULES_DIR : null;
2506
+ const rulesDir = existsSync18(RULES_DIR) ? RULES_DIR : null;
3107
2507
  if (!rulesDir) {
3108
2508
  return JSON.stringify({ rules: [], error: `Rules directory not found at ${RULES_DIR}` });
3109
2509
  }
@@ -3123,13 +2523,13 @@ var listRulesTool = tool17({
3123
2523
  });
3124
2524
 
3125
2525
  // src/tools/rtk-setup.ts
3126
- import { tool as tool18 } from "@opencode-ai/plugin";
2526
+ import { tool as tool14 } from "@opencode-ai/plugin";
3127
2527
 
3128
2528
  // src/services/rtk-manager.ts
3129
2529
  import { spawnSync as spawnSync2 } from "child_process";
3130
- import { existsSync as existsSync23 } from "fs";
2530
+ import { existsSync as existsSync19 } from "fs";
3131
2531
  import { homedir as homedir2 } from "os";
3132
- import { join as join22 } from "path";
2532
+ import { join as join18 } from "path";
3133
2533
 
3134
2534
  // src/services/rtk-policy.ts
3135
2535
  var SUPPORTED_COMMANDS = new Set([
@@ -3175,7 +2575,7 @@ var INSTALL_INSTRUCTIONS = [
3175
2575
  "After installation, call rtk-setup again to verify detection."
3176
2576
  ].join(`
3177
2577
  `);
3178
- var CANDIDATE_PATHS = [join22(homedir2(), ".local", "bin", "rtk"), "/usr/local/bin/rtk", "/usr/bin/rtk"];
2578
+ var CANDIDATE_PATHS = [join18(homedir2(), ".local", "bin", "rtk"), "/usr/local/bin/rtk", "/usr/bin/rtk"];
3179
2579
  function detectRtk() {
3180
2580
  const fromPath = spawnSync2("rtk", ["--version"], { encoding: "utf-8", timeout: 5000 });
3181
2581
  if (fromPath.status === 0) {
@@ -3184,7 +2584,7 @@ function detectRtk() {
3184
2584
  return { installed: true, binPath: "rtk", version };
3185
2585
  }
3186
2586
  for (const candidate of CANDIDATE_PATHS) {
3187
- if (!existsSync23(candidate))
2587
+ if (!existsSync19(candidate))
3188
2588
  continue;
3189
2589
  const result = spawnSync2(candidate, ["--version"], { encoding: "utf-8", timeout: 5000 });
3190
2590
  if (result.status === 0) {
@@ -3262,7 +2662,7 @@ function getRtkStatus(opts) {
3262
2662
  }
3263
2663
 
3264
2664
  // src/tools/rtk-setup.ts
3265
- var rtkSetupTool = tool18({
2665
+ var rtkSetupTool = tool14({
3266
2666
  description: [
3267
2667
  "Detect, initialize, and report status of rtk (output compression proxy for CLI commands).",
3268
2668
  "rtk reduces noisy CLI output (git, npm, test runners, linters, docker) by 60-90%.",
@@ -3270,7 +2670,7 @@ var rtkSetupTool = tool18({
3270
2670
  "When RTK_INSTALLED=true in the environment, use `$RTK_BIN git status` for compressed output."
3271
2671
  ].join(" "),
3272
2672
  args: {
3273
- action: tool18.schema.enum(["status", "init"]).optional().describe("'status' — detect and report rtk state (default). " + "'init' — detect, then run `rtk init -g` to install the bash hook. " + "Use 'init' only once per environment setup.")
2673
+ action: tool14.schema.enum(["status", "init"]).optional().describe("'status' — detect and report rtk state (default). " + "'init' — detect, then run `rtk init -g` to install the bash hook. " + "Use 'init' only once per environment setup.")
3274
2674
  },
3275
2675
  async execute(args) {
3276
2676
  const action = args.action ?? "status";
@@ -3310,15 +2710,15 @@ var rtkSetupTool = tool18({
3310
2710
  });
3311
2711
 
3312
2712
  // src/hooks/guard-rails.ts
3313
- import { existsSync as existsSync24, readFileSync as readFileSync23 } from "fs";
3314
- import { join as join23 } from "path";
2713
+ import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
2714
+ import { join as join19 } from "path";
3315
2715
  var PLANNING_DIR2 = ".planning";
3316
2716
  var CONFIG_FILE = "config.json";
3317
2717
  var STATE_FILE2 = "STATE.md";
3318
2718
  function resolveExecutionMode(configPath, trustScore, volatility) {
3319
- if (existsSync24(configPath)) {
2719
+ if (existsSync20(configPath)) {
3320
2720
  try {
3321
- const config = JSON.parse(readFileSync23(configPath, "utf-8"));
2721
+ const config = JSON.parse(readFileSync20(configPath, "utf-8"));
3322
2722
  if (config.execution_mode === "review-only")
3323
2723
  return "review-only";
3324
2724
  if (config.execution_mode === "guarded")
@@ -3372,22 +2772,22 @@ async function guardRailsHook(ctx, input, _output) {
3372
2772
  if (!ENABLED)
3373
2773
  return;
3374
2774
  const dir = ctx.directory;
3375
- const planningDirPath = join23(dir, PLANNING_DIR2);
2775
+ const planningDirPath = join19(dir, PLANNING_DIR2);
3376
2776
  const codebaseDirectory = codebaseDir(dir);
3377
- const configPath = join23(planningDirPath, CONFIG_FILE);
3378
- const statePath2 = join23(planningDirPath, STATE_FILE2);
2777
+ const configPath = join19(planningDirPath, CONFIG_FILE);
2778
+ const statePath2 = join19(planningDirPath, STATE_FILE2);
3379
2779
  const workspaceRoot = findWorkspaceRoot(dir);
3380
2780
  if (workspaceRoot && dir !== workspaceRoot) {
3381
2781
  const config = getWorkspaceConfig(dir);
3382
- if (config && config.workspace_mode === "shared" && !existsSync24(planningDirPath)) {
2782
+ if (config && config.workspace_mode === "shared" && !existsSync20(planningDirPath)) {
3383
2783
  const msg = `No .planning/ in this sub-repo. Switch to workspace root: cd ${workspaceRoot}`;
3384
2784
  throw new Error(`[flowdeck] BLOCK: ${msg}`);
3385
2785
  }
3386
2786
  }
3387
2787
  if (input.tool === "write" || input.tool === "edit") {
3388
- if (!existsSync24(planningDirPath))
2788
+ if (!existsSync20(planningDirPath))
3389
2789
  return;
3390
- if (!existsSync24(codebaseDirectory)) {
2790
+ if (!existsSync20(codebaseDirectory)) {
3391
2791
  throw new Error(`[flowdeck] WARNING: .codebase/ not found. Run /fd-map-codebase to map the codebase.`);
3392
2792
  }
3393
2793
  const execMode = resolveExecutionMode(configPath, null);
@@ -3443,15 +2843,15 @@ function getDesignGateMessage(dir) {
3443
2843
  }
3444
2844
  function planSuggestsUiHeavy(dir, phase) {
3445
2845
  const planPath = phasePlanPath(dir, phase);
3446
- if (!existsSync24(planPath))
2846
+ if (!existsSync20(planPath))
3447
2847
  return false;
3448
- const planContent = readFileSync23(planPath, "utf-8");
2848
+ const planContent = readFileSync20(planPath, "utf-8");
3449
2849
  return isUiHeavyTask(planContent);
3450
2850
  }
3451
2851
  function effectiveSeverity(configPath, statePath2) {
3452
- if (existsSync24(configPath)) {
2852
+ if (existsSync20(configPath)) {
3453
2853
  try {
3454
- const configContent = readFileSync23(configPath, "utf-8");
2854
+ const configContent = readFileSync20(configPath, "utf-8");
3455
2855
  const config = JSON.parse(configContent);
3456
2856
  if (config.guard_enforcement === "warn")
3457
2857
  return "warn";
@@ -3467,10 +2867,10 @@ function getEffectiveSeverity(configPath, statePath2) {
3467
2867
  return effectiveSeverity(configPath, statePath2);
3468
2868
  }
3469
2869
  function getPlanConfirmed(statePath2) {
3470
- if (!existsSync24(statePath2))
2870
+ if (!existsSync20(statePath2))
3471
2871
  return false;
3472
2872
  try {
3473
- const content = readFileSync23(statePath2, "utf-8");
2873
+ const content = readFileSync20(statePath2, "utf-8");
3474
2874
  const match = content.match(/plan_confirmed:\s*(true|false)/i);
3475
2875
  return match ? match[1].toLowerCase() === "true" : false;
3476
2876
  } catch {
@@ -3478,32 +2878,32 @@ function getPlanConfirmed(statePath2) {
3478
2878
  }
3479
2879
  }
3480
2880
  function getWarningMessage(planningDir2) {
3481
- if (!existsSync24(join23(planningDir2, STATE_FILE2))) {
2881
+ if (!existsSync20(join19(planningDir2, STATE_FILE2))) {
3482
2882
  return "No STATE.md found. Run /fd-map-codebase then /fd-new-feature to start a feature.";
3483
2883
  }
3484
2884
  return "Plan not confirmed. Run /fd-plan and confirm to enable execution.";
3485
2885
  }
3486
2886
  function getBlockMessage(planningDir2) {
3487
- if (!existsSync24(join23(planningDir2, STATE_FILE2))) {
2887
+ if (!existsSync20(join19(planningDir2, STATE_FILE2))) {
3488
2888
  return "No STATE.md found. Run /fd-map-codebase then /fd-new-feature to start a feature.";
3489
2889
  }
3490
2890
  return "Plan not confirmed. Run /fd-plan and confirm to enable execution.";
3491
2891
  }
3492
2892
 
3493
2893
  // src/hooks/tool-guard.ts
3494
- import { existsSync as existsSync25, readFileSync as readFileSync24 } from "fs";
3495
- import { join as join24 } from "path";
2894
+ import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
2895
+ import { join as join20 } from "path";
3496
2896
  var IS_ENABLED = () => process.env.FLOWDECK_TOOL_GUARD_ENABLED === "on";
3497
2897
  var BLOCKED_PATTERNS = {
3498
2898
  read: [".env", ".pem", ".key", ".secret"],
3499
2899
  write: ["node_modules"],
3500
2900
  bash: ["rm -rf"]
3501
2901
  };
3502
- function isBlocked(tool19, args) {
3503
- const patterns = BLOCKED_PATTERNS[tool19];
2902
+ function isBlocked(tool15, args) {
2903
+ const patterns = BLOCKED_PATTERNS[tool15];
3504
2904
  if (!patterns)
3505
2905
  return null;
3506
- if (tool19 === "bash") {
2906
+ if (tool15 === "bash") {
3507
2907
  const cmd = args.command;
3508
2908
  if (!cmd)
3509
2909
  return null;
@@ -3514,7 +2914,7 @@ function isBlocked(tool19, args) {
3514
2914
  }
3515
2915
  return null;
3516
2916
  }
3517
- if (tool19 === "read") {
2917
+ if (tool15 === "read") {
3518
2918
  const filePath = args.filePath;
3519
2919
  if (!filePath)
3520
2920
  return null;
@@ -3525,7 +2925,7 @@ function isBlocked(tool19, args) {
3525
2925
  }
3526
2926
  return null;
3527
2927
  }
3528
- if (tool19 === "write") {
2928
+ if (tool15 === "write") {
3529
2929
  const filePath = args.filePath;
3530
2930
  if (!filePath)
3531
2931
  return null;
@@ -3539,11 +2939,11 @@ function isBlocked(tool19, args) {
3539
2939
  return null;
3540
2940
  }
3541
2941
  function checkArchConstraint(directory, filePath) {
3542
- const constraintsPath = join24(codebaseDir(directory), "CONSTRAINTS.md");
3543
- if (!existsSync25(constraintsPath))
2942
+ const constraintsPath = join20(codebaseDir(directory), "CONSTRAINTS.md");
2943
+ if (!existsSync21(constraintsPath))
3544
2944
  return null;
3545
2945
  try {
3546
- const content = readFileSync24(constraintsPath, "utf-8");
2946
+ const content = readFileSync21(constraintsPath, "utf-8");
3547
2947
  const match = content.match(/## Forbidden Paths\n([\s\S]*?)(?:\n##|$)/);
3548
2948
  if (!match)
3549
2949
  return null;
@@ -3584,9 +2984,9 @@ function isUiDesignApprovalRequired(directory) {
3584
2984
  return !(state.design_stage === "handoff_complete" && state.design_approved);
3585
2985
  }
3586
2986
  const planPath = phasePlanPath(directory, state.phase || 1);
3587
- if (!existsSync25(planPath))
2987
+ if (!existsSync21(planPath))
3588
2988
  return false;
3589
- const planContent = readFileSync24(planPath, "utf-8");
2989
+ const planContent = readFileSync21(planPath, "utf-8");
3590
2990
  if (!isUiHeavyTask(planContent))
3591
2991
  return false;
3592
2992
  return !(state.design_stage === "handoff_complete" && state.design_approved);
@@ -3615,18 +3015,18 @@ async function toolGuardHook(ctx, input, output) {
3615
3015
  }
3616
3016
 
3617
3017
  // src/hooks/session-start.ts
3618
- import { existsSync as existsSync26, readFileSync as readFileSync25 } from "fs";
3018
+ import { existsSync as existsSync22, readFileSync as readFileSync22 } from "fs";
3619
3019
  async function sessionStartHook(ctx) {
3620
3020
  const planningDir2 = ctx.directory + "/.planning";
3621
3021
  const codebaseDirectory = codebaseDir(ctx.directory);
3622
3022
  const workspaceRoot = findWorkspaceRoot(ctx.directory);
3623
3023
  const config = workspaceRoot ? getWorkspaceConfig(ctx.directory) : null;
3624
- if (!existsSync26(planningDir2)) {
3024
+ if (!existsSync22(planningDir2)) {
3625
3025
  return {
3626
3026
  flowdeck_phase: null,
3627
3027
  flowdeck_status: "no_plan",
3628
3028
  flowdeck_warning: "Run /fd-map-codebase to index the codebase, then /fd-new-feature to start a feature.",
3629
- flowdeck_has_codebase: existsSync26(codebaseDirectory),
3029
+ flowdeck_has_codebase: existsSync22(codebaseDirectory),
3630
3030
  ...workspaceRoot && config?.sub_repos ? {
3631
3031
  flowdeck_workspace_root: workspaceRoot,
3632
3032
  flowdeck_sub_repos: config.sub_repos,
@@ -3637,7 +3037,7 @@ async function sessionStartHook(ctx) {
3637
3037
  }
3638
3038
  try {
3639
3039
  const stateFilePath = statePath(ctx.directory);
3640
- const content = readFileSync25(stateFilePath, "utf-8");
3040
+ const content = readFileSync22(stateFilePath, "utf-8");
3641
3041
  const state = parseState(content);
3642
3042
  const currentPhase = state["current_phase"] || {};
3643
3043
  const result = {
@@ -3645,7 +3045,7 @@ async function sessionStartHook(ctx) {
3645
3045
  flowdeck_status: currentPhase["status"] ?? null,
3646
3046
  flowdeck_steps_pending: currentPhase["steps_pending"] ?? null,
3647
3047
  flowdeck_last_action: currentPhase["last_action"] ?? null,
3648
- flowdeck_has_codebase: existsSync26(codebaseDirectory)
3048
+ flowdeck_has_codebase: existsSync22(codebaseDirectory)
3649
3049
  };
3650
3050
  if (workspaceRoot && config?.sub_repos && config.sub_repos.length > 0) {
3651
3051
  result.flowdeck_workspace_root = workspaceRoot;
@@ -3654,13 +3054,12 @@ async function sessionStartHook(ctx) {
3654
3054
  result.flowdeck_is_workspace_root = ctx.directory === workspaceRoot;
3655
3055
  }
3656
3056
  return result;
3657
- } catch (err) {
3658
- console.warn("[flowdeck] Warning: State file unreadable. Continuing without flowdeck context.");
3057
+ } catch {
3659
3058
  const result = {
3660
3059
  flowdeck_phase: null,
3661
3060
  flowdeck_status: "error",
3662
3061
  flowdeck_warning: "State file unreadable. Continuing without flowdeck context.",
3663
- flowdeck_has_codebase: existsSync26(codebaseDirectory)
3062
+ flowdeck_has_codebase: existsSync22(codebaseDirectory)
3664
3063
  };
3665
3064
  if (workspaceRoot && config?.sub_repos && config.sub_repos.length > 0) {
3666
3065
  result.flowdeck_workspace_root = workspaceRoot;
@@ -3690,7 +3089,7 @@ var COMPLETION_COMMANDS = new Set([
3690
3089
  "execute",
3691
3090
  "verify"
3692
3091
  ]);
3693
- function normalizeCommandName2(raw) {
3092
+ function normalizeCommandName(raw) {
3694
3093
  return raw.replace(/^\//, "").replace(/^fd-/, "");
3695
3094
  }
3696
3095
  function notify(title, body, level = "info") {
@@ -3699,9 +3098,7 @@ function notify(title, body, level = "info") {
3699
3098
  if (platform === "linux") {
3700
3099
  const urgency = level === "critical" ? "critical" : "normal";
3701
3100
  const proc = execFile("notify-send", ["--urgency", urgency, "--app-name", "FlowDeck", "--icon", "dialog-information", title, body], { timeout: 3000 });
3702
- proc.on("error", () => {
3703
- tryTerminalBell();
3704
- });
3101
+ proc.on("error", () => {});
3705
3102
  } else if (platform === "darwin") {
3706
3103
  const script = `display notification "${body.replace(/"/g, "\\\"")}" with title "${title.replace(/"/g, "\\\"")}" subtitle "FlowDeck"`;
3707
3104
  const proc = execFile("osascript", ["-e", script], { timeout: 3000 });
@@ -3719,11 +3116,6 @@ function notify(title, body, level = "info") {
3719
3116
  }
3720
3117
  } catch {}
3721
3118
  }
3722
- function tryTerminalBell() {
3723
- try {
3724
- process.stdout.write("\x07");
3725
- } catch {}
3726
- }
3727
3119
 
3728
3120
  class NotificationController {
3729
3121
  pendingCommand = null;
@@ -3735,7 +3127,7 @@ class NotificationController {
3735
3127
  this.log = log;
3736
3128
  }
3737
3129
  onCommandExecuted(rawCommand) {
3738
- const name = normalizeCommandName2(rawCommand);
3130
+ const name = normalizeCommandName(rawCommand);
3739
3131
  if (!INTERACTIVE_COMMANDS.has(name) && !COMPLETION_COMMANDS.has(name)) {
3740
3132
  this.log(`[notify] command.executed: "${name}" — not a tracked command, skipping`);
3741
3133
  return;
@@ -3799,13 +3191,13 @@ class NotificationController {
3799
3191
  return this.lastNotifiedKey;
3800
3192
  }
3801
3193
  }
3802
- function notifyPermissionNeeded(tool19) {
3803
- notify("FlowDeck Permission Required", `Agent needs approval to use tool: ${tool19}`, "critical");
3194
+ function notifyPermissionNeeded(tool15) {
3195
+ notify("FlowDeck Permission Required", `Agent needs approval to use tool: ${tool15}`, "critical");
3804
3196
  }
3805
3197
 
3806
3198
  // src/hooks/patch-trust.ts
3807
- import { existsSync as existsSync27, readFileSync as readFileSync26 } from "fs";
3808
- import { join as join25 } from "path";
3199
+ import { existsSync as existsSync23, readFileSync as readFileSync23 } from "fs";
3200
+ import { join as join21 } from "path";
3809
3201
  var HIGH_RISK_KEYWORDS = [
3810
3202
  "password",
3811
3203
  "secret",
@@ -3826,26 +3218,12 @@ var HIGH_RISK_KEYWORDS = [
3826
3218
  "root",
3827
3219
  "privilege"
3828
3220
  ];
3829
- function loadVolatility(directory) {
3830
- const p = join25(codebaseDir(directory), "VOLATILITY.json");
3831
- if (!existsSync27(p))
3832
- return {};
3833
- try {
3834
- const data = JSON.parse(readFileSync26(p, "utf-8"));
3835
- const map = {};
3836
- for (const entry of data.entries ?? [])
3837
- map[entry.path] = entry.stability;
3838
- return map;
3839
- } catch {
3840
- return {};
3841
- }
3842
- }
3843
3221
  function loadFailedPaths(directory) {
3844
- const p = join25(codebaseDir(directory), "FAILURES.json");
3845
- if (!existsSync27(p))
3222
+ const p = join21(codebaseDir(directory), "FAILURES.json");
3223
+ if (!existsSync23(p))
3846
3224
  return [];
3847
3225
  try {
3848
- const data = JSON.parse(readFileSync26(p, "utf-8"));
3226
+ const data = JSON.parse(readFileSync23(p, "utf-8"));
3849
3227
  return (data.entries ?? []).flatMap((e) => e.affected_paths ?? []);
3850
3228
  } catch {
3851
3229
  return [];
@@ -3854,18 +3232,6 @@ function loadFailedPaths(directory) {
3854
3232
  function scorePatch(directory, filePath, content) {
3855
3233
  let score = 100;
3856
3234
  const signals = [];
3857
- const volatility = loadVolatility(directory);
3858
- const stability = Object.entries(volatility).find(([path]) => filePath.includes(path))?.[1];
3859
- if (stability === "critical") {
3860
- score -= 40;
3861
- signals.push("file is in critical volatility zone");
3862
- } else if (stability === "volatile") {
3863
- score -= 25;
3864
- signals.push("file is in volatile zone");
3865
- } else if (stability === "moderate") {
3866
- score -= 10;
3867
- signals.push("file has moderate churn");
3868
- }
3869
3235
  const failedPaths = loadFailedPaths(directory);
3870
3236
  if (failedPaths.some((p) => filePath.includes(p))) {
3871
3237
  score -= 20;
@@ -3910,8 +3276,8 @@ async function patchTrustHook(ctx, input, output) {
3910
3276
  }
3911
3277
 
3912
3278
  // src/hooks/decision-trace-hook.ts
3913
- import { existsSync as existsSync28, mkdirSync as mkdirSync14, appendFileSync as appendFileSync4 } from "fs";
3914
- import { join as join26 } from "path";
3279
+ import { existsSync as existsSync24, mkdirSync as mkdirSync12, appendFileSync as appendFileSync4 } from "fs";
3280
+ import { join as join22 } from "path";
3915
3281
  async function decisionTraceHook(ctx, input, output) {
3916
3282
  if (input.tool !== "write" && input.tool !== "edit")
3917
3283
  return;
@@ -3920,8 +3286,8 @@ async function decisionTraceHook(ctx, input, output) {
3920
3286
  return;
3921
3287
  const base = codebaseDir(ctx.directory);
3922
3288
  try {
3923
- if (!existsSync28(base))
3924
- mkdirSync14(base, { recursive: true });
3289
+ if (!existsSync24(base))
3290
+ mkdirSync12(base, { recursive: true });
3925
3291
  const entry = {
3926
3292
  timestamp: new Date().toISOString(),
3927
3293
  file_path: filePath,
@@ -3933,164 +3299,14 @@ async function decisionTraceHook(ctx, input, output) {
3933
3299
  risk_level: "unknown",
3934
3300
  auto_recorded: true
3935
3301
  };
3936
- appendFileSync4(join26(base, "DECISIONS.jsonl"), JSON.stringify(entry) + `
3302
+ appendFileSync4(join22(base, "DECISIONS.jsonl"), JSON.stringify(entry) + `
3937
3303
  `, "utf-8");
3938
3304
  } catch {}
3939
3305
  }
3940
3306
 
3941
- // src/services/telemetry.ts
3942
- import { existsSync as existsSync29, readFileSync as readFileSync27, appendFileSync as appendFileSync5, mkdirSync as mkdirSync15 } from "fs";
3943
- import { join as join27 } from "path";
3944
- import { randomUUID } from "crypto";
3945
- function telemetryPath(dir) {
3946
- return join27(codebaseDir(dir), "TELEMETRY.jsonl");
3947
- }
3948
- function appendEvent2(dir, partial) {
3949
- if (process.env.TELEMETRY_ENABLED !== "true")
3950
- return null;
3951
- const cd = codebaseDir(dir);
3952
- if (!existsSync29(cd))
3953
- mkdirSync15(cd, { recursive: true });
3954
- const event = {
3955
- id: randomUUID(),
3956
- ts: new Date().toISOString(),
3957
- ...partial
3958
- };
3959
- appendFileSync5(telemetryPath(dir), JSON.stringify(event) + `
3960
- `, "utf-8");
3961
- return event;
3962
- }
3963
-
3964
- // src/hooks/telemetry-hook.ts
3965
- var toolStartTimes = new Map;
3966
- var REPORTABLE_TOOLS = new Set([
3967
- "delegate",
3968
- "run-pipeline",
3969
- "council",
3970
- "bash",
3971
- "write",
3972
- "edit",
3973
- "read",
3974
- "codegraph",
3975
- "codebase-state",
3976
- "planning-state",
3977
- "workspace-state",
3978
- "repo-memory",
3979
- "hash-edit",
3980
- "context-generator",
3981
- "volatility-map",
3982
- "failure-replay",
3983
- "decision-trace",
3984
- "policy-engine",
3985
- "reflect"
3986
- ]);
3987
- var SELF_LOGGING_TOOLS = new Set(["delegate", "run-pipeline", "council"]);
3988
- function correlationKey(sessionId, runId, tool19) {
3989
- return `${sessionId}:${runId}:${tool19}`;
3990
- }
3991
- function resolveIds(toolInput) {
3992
- const session_id = toolInput.sessionID ?? toolInput.sessionId ?? process.env.OPENCODE_SESSION_ID ?? "session-0";
3993
- const run_id = toolInput.messageID ?? toolInput.messageId ?? toolInput.runID ?? toolInput.runId ?? process.env.OPENCODE_RUN_ID ?? "run-0";
3994
- return { session_id, run_id };
3995
- }
3996
- function inferStatus(output) {
3997
- if (output.error)
3998
- return "error";
3999
- if (typeof output.output !== "string")
4000
- return "ok";
4001
- const text = output.output.trim();
4002
- if (!text)
4003
- return "ok";
4004
- try {
4005
- const parsed = JSON.parse(text);
4006
- if (parsed.success === false || parsed.error || parsed.status === "error")
4007
- return "error";
4008
- return "ok";
4009
- } catch {
4010
- return "ok";
4011
- }
4012
- }
4013
- function buildInputSummary(tool19, args) {
4014
- if (tool19 === "delegate" || tool19 === "run-pipeline" || tool19 === "council") {
4015
- return "";
4016
- }
4017
- if (tool19 === "bash" || tool19 === "write" || tool19 === "edit" || tool19 === "read") {
4018
- const path = args.path ?? args.filePath ?? args.file ?? "";
4019
- const cmd = args.command ?? args.cmd ?? "";
4020
- return path || cmd ? summarize(String(path || cmd), 80) : "";
4021
- }
4022
- const firstStr = Object.values(args).find((v) => typeof v === "string");
4023
- return firstStr ? summarize(firstStr, 80) : "";
4024
- }
4025
- async function telemetryHook(context, toolInput, output, reporter) {
4026
- const dir = context.directory ?? process.cwd();
4027
- const tool19 = toolInput.name ?? toolInput.tool ?? "unknown";
4028
- const ids = resolveIds(toolInput);
4029
- const key = correlationKey(ids.session_id, ids.run_id, tool19);
4030
- toolStartTimes.set(key, Date.now());
4031
- appendEvent2(dir, {
4032
- session_id: ids.session_id,
4033
- run_id: ids.run_id,
4034
- event: "tool.call",
4035
- tool: tool19,
4036
- status: "ok",
4037
- meta: { parameters: output.args ?? {} }
4038
- });
4039
- if (reporter && REPORTABLE_TOOLS.has(tool19) && !SELF_LOGGING_TOOLS.has(tool19)) {
4040
- const inputSummary = buildInputSummary(tool19, output.args ?? {});
4041
- reporter.reportToolStarted(tool19, inputSummary, {
4042
- session_id: ids.session_id,
4043
- run_id: ids.run_id
4044
- });
4045
- }
4046
- if (reporter && REPORTABLE_TOOLS.has(tool19)) {
4047
- reporter.trackStart(key);
4048
- }
4049
- }
4050
- async function telemetryAfterHook(context, toolInput, output, reporter) {
4051
- const dir = context.directory ?? process.cwd();
4052
- const tool19 = toolInput.name ?? toolInput.tool ?? "unknown";
4053
- const ids = resolveIds(toolInput);
4054
- const key = correlationKey(ids.session_id, ids.run_id, tool19);
4055
- const status = inferStatus(output);
4056
- let duration_ms;
4057
- if (reporter && REPORTABLE_TOOLS.has(tool19)) {
4058
- duration_ms = reporter.elapsedMs(key);
4059
- }
4060
- if (duration_ms === undefined) {
4061
- const startMs = toolStartTimes.get(key);
4062
- if (startMs !== undefined)
4063
- duration_ms = Date.now() - startMs;
4064
- }
4065
- toolStartTimes.delete(key);
4066
- let result_summary;
4067
- if (typeof output.output === "string") {
4068
- result_summary = summarize(output.output, 100);
4069
- } else if (output.error) {
4070
- result_summary = summarize(String(output.error), 100);
4071
- }
4072
- appendEvent2(dir, {
4073
- session_id: ids.session_id,
4074
- run_id: ids.run_id,
4075
- event: status === "error" ? "tool.failed" : "tool.complete",
4076
- tool: tool19,
4077
- status,
4078
- duration_ms,
4079
- result_summary
4080
- });
4081
- if (reporter && REPORTABLE_TOOLS.has(tool19) && !SELF_LOGGING_TOOLS.has(tool19)) {
4082
- if (status === "error") {
4083
- const errText = output.error ? String(output.error) : typeof output.output === "string" ? output.output : "unknown error";
4084
- reporter.reportToolFailed(tool19, duration_ms, errText);
4085
- } else {
4086
- reporter.reportToolCompleted(tool19, duration_ms, result_summary ?? "");
4087
- }
4088
- }
4089
- }
4090
-
4091
3307
  // src/services/approval-manager.ts
4092
- import { existsSync as existsSync30, readFileSync as readFileSync28, writeFileSync as writeFileSync17, mkdirSync as mkdirSync16 } from "fs";
4093
- import { join as join28 } from "path";
3308
+ import { existsSync as existsSync25, readFileSync as readFileSync24, writeFileSync as writeFileSync13, mkdirSync as mkdirSync13 } from "fs";
3309
+ import { join as join23 } from "path";
4094
3310
  var APPROVAL_TTL_MS = 30 * 60 * 1000;
4095
3311
  var SENSITIVE_PATTERNS = [
4096
3312
  /auth/i,
@@ -4127,14 +3343,14 @@ function isSensitivePath(filePath) {
4127
3343
  return SENSITIVE_PATTERNS.some((p) => p.test(filePath));
4128
3344
  }
4129
3345
  function approvalsPath(dir) {
4130
- return join28(codebaseDir(dir), "APPROVALS.json");
3346
+ return join23(codebaseDir(dir), "APPROVALS.json");
4131
3347
  }
4132
3348
  function loadStore2(dir) {
4133
3349
  const p = approvalsPath(dir);
4134
- if (!existsSync30(p))
3350
+ if (!existsSync25(p))
4135
3351
  return { requests: [] };
4136
3352
  try {
4137
- return JSON.parse(readFileSync28(p, "utf-8"));
3353
+ return JSON.parse(readFileSync24(p, "utf-8"));
4138
3354
  } catch {
4139
3355
  return { requests: [] };
4140
3356
  }
@@ -4152,8 +3368,8 @@ async function approvalHook(context, toolInput, output) {
4152
3368
  if (!ENABLED2)
4153
3369
  return;
4154
3370
  const dir = context.directory ?? process.cwd();
4155
- const tool19 = toolInput.name ?? toolInput.tool ?? "";
4156
- if (!WRITE_TOOLS.has(tool19))
3371
+ const tool15 = toolInput.name ?? toolInput.tool ?? "";
3372
+ if (!WRITE_TOOLS.has(tool15))
4157
3373
  return;
4158
3374
  const args = output.args ?? {};
4159
3375
  const filePath = String(args.path ?? args.file_path ?? args.filename ?? "");
@@ -4164,20 +3380,306 @@ async function approvalHook(context, toolInput, output) {
4164
3380
  const approval = checkApproval(dir, filePath, "");
4165
3381
  if (approval)
4166
3382
  return;
4167
- appendEvent2(dir, {
4168
- session_id: process.env.OPENCODE_SESSION_ID ?? "session-0",
4169
- run_id: process.env.OPENCODE_RUN_ID ?? "run-0",
4170
- event: "approval.request",
4171
- tool: tool19,
4172
- status: "blocked",
4173
- files: [filePath],
4174
- meta: { trigger: "sensitive_file", file: filePath }
4175
- });
4176
3383
  throw new Error(`APPROVAL_REQUIRED: "${filePath}" is a sensitive file (auth/payment/secrets/infra).
4177
3384
  ` + `Risk level: HIGH — manual approval needed before editing.
4178
3385
  ` + `To proceed: run /fd-guarded-edit --file "${filePath}" to review and approve this change.`);
4179
3386
  }
4180
3387
 
3388
+ // src/services/event-logger.ts
3389
+ import { existsSync as existsSync26, mkdirSync as mkdirSync14, appendFileSync as appendFileSync5, readFileSync as readFileSync25, writeFileSync as writeFileSync14, renameSync, unlinkSync, statSync as statSync2 } from "fs";
3390
+ import { join as join24, resolve as resolve2, sep } from "path";
3391
+ var SENSITIVE_KEYS = [
3392
+ "password",
3393
+ "token",
3394
+ "apikey",
3395
+ "api_key",
3396
+ "secret",
3397
+ "authorization",
3398
+ "auth",
3399
+ "key",
3400
+ "credential",
3401
+ "privatekey",
3402
+ "private_key",
3403
+ "accesstoken",
3404
+ "access_token",
3405
+ "refreshtoken",
3406
+ "refresh_token"
3407
+ ];
3408
+ var currentAgent = null;
3409
+ function getCurrentAgent() {
3410
+ return currentAgent;
3411
+ }
3412
+ function setCurrentAgent(agent) {
3413
+ currentAgent = agent;
3414
+ }
3415
+ function sanitizeArgs(args) {
3416
+ if (!args || typeof args !== "object")
3417
+ return {};
3418
+ const result = {};
3419
+ for (const [key, value] of Object.entries(args)) {
3420
+ const lowerKey = key.toLowerCase();
3421
+ if (SENSITIVE_KEYS.some((sk) => lowerKey.includes(sk))) {
3422
+ result[key] = "[REDACTED]";
3423
+ } else if (key === "content" || key === "newString" || key === "oldString" || key === "template") {
3424
+ if (typeof value === "string" && value.length > 100) {
3425
+ result[key] = `[${value.length} chars truncated]`;
3426
+ } else {
3427
+ result[key] = value;
3428
+ }
3429
+ } else {
3430
+ result[key] = value;
3431
+ }
3432
+ }
3433
+ return result;
3434
+ }
3435
+ function isValidDirectory(directory) {
3436
+ const normalized = resolve2(directory);
3437
+ if (normalized !== directory && !directory.startsWith(sep)) {
3438
+ return false;
3439
+ }
3440
+ if (directory.includes("..") || directory.includes(".." + sep)) {
3441
+ return false;
3442
+ }
3443
+ try {
3444
+ const stats = statSync2(directory);
3445
+ return stats.isDirectory();
3446
+ } catch {
3447
+ return false;
3448
+ }
3449
+ }
3450
+ function logEvent(directory, event, log) {
3451
+ if (process.env.FLOWDECK_EVENT_LOG === "off")
3452
+ return;
3453
+ if (!isValidDirectory(directory))
3454
+ return;
3455
+ const logDir = join24(directory, ".opencode");
3456
+ const logPath = join24(logDir, "flowdeck-events.jsonl");
3457
+ try {
3458
+ if (!existsSync26(logDir)) {
3459
+ mkdirSync14(logDir, { recursive: true });
3460
+ }
3461
+ appendFileSync5(logPath, JSON.stringify(event) + `
3462
+ `, "utf-8");
3463
+ rotateLogFile(logPath);
3464
+ if (log) {
3465
+ log(formatEventForStderr(event));
3466
+ }
3467
+ } catch {}
3468
+ }
3469
+ function rotateLogFile(logPath) {
3470
+ try {
3471
+ const stats = statSync2(logPath);
3472
+ if (stats.size < 5000)
3473
+ return;
3474
+ const content = readFileSync25(logPath, "utf-8");
3475
+ const lines = content.split(`
3476
+ `).filter((l) => l.trim());
3477
+ if (lines.length > 1000) {
3478
+ const backupPath = logPath + ".backup";
3479
+ renameSync(logPath, backupPath);
3480
+ const keep = lines.slice(-1000);
3481
+ writeFileSync14(logPath, keep.join(`
3482
+ `) + `
3483
+ `, "utf-8");
3484
+ try {
3485
+ unlinkSync(backupPath);
3486
+ } catch {}
3487
+ }
3488
+ } catch {}
3489
+ }
3490
+ function formatEventForStderr(event) {
3491
+ const time = event.timestamp.slice(11, 23);
3492
+ const agent = event.agent ?? "unknown";
3493
+ const dim = "\x1B[2m";
3494
+ const reset = "\x1B[0m";
3495
+ const cyan = "\x1B[36m";
3496
+ switch (event.type) {
3497
+ case "tool.before": {
3498
+ let icon;
3499
+ if (event.tool === "write" || event.tool === "edit")
3500
+ icon = "✏️ ";
3501
+ else if (event.tool === "read")
3502
+ icon = "\uD83D\uDD0D";
3503
+ else if (event.tool === "bash" || event.tool === "shell")
3504
+ icon = "\uD83C\uDFC3";
3505
+ else if (event.tool === "delegate")
3506
+ icon = "\uD83E\uDD16";
3507
+ else
3508
+ icon = "\uD83D\uDD27";
3509
+ const argStr = formatArgs(event.args);
3510
+ const thinking = event.thinking ? ` "${event.thinking}"` : "";
3511
+ return `${dim}[${time}]${reset} ${icon} ${cyan}${agent}${reset} → ${event.tool}(${argStr})${thinking}`;
3512
+ }
3513
+ case "tool.after": {
3514
+ let icon;
3515
+ let statusColor;
3516
+ if (event.status === "success") {
3517
+ icon = "✅";
3518
+ statusColor = "\x1B[32m";
3519
+ } else if (event.status === "error") {
3520
+ icon = "❌";
3521
+ statusColor = "\x1B[31m";
3522
+ } else if (event.status === "blocked") {
3523
+ icon = "⛔";
3524
+ statusColor = "\x1B[33m";
3525
+ } else {
3526
+ icon = "✅";
3527
+ statusColor = "\x1B[32m";
3528
+ }
3529
+ const argStr = formatArgs(event.args);
3530
+ const duration = event.duration_ms ? ` done in ${event.duration_ms}ms` : "";
3531
+ const error = event.error ? ` error: ${event.error}` : "";
3532
+ return `${dim}[${time}]${reset} ${icon} ${cyan}${agent}${reset} → ${event.tool}(${argStr})${statusColor}${duration}${error}${reset}`;
3533
+ }
3534
+ case "agent.delegated": {
3535
+ const thinking = event.thinking ? ` "${event.thinking}"` : "";
3536
+ return `${dim}[${time}]${reset} \uD83E\uDD16 ${cyan}${agent}${reset} → delegate(${thinking})`;
3537
+ }
3538
+ case "session.created":
3539
+ return `${dim}[${time}]${reset} \uD83D\uDCC2 session created${event.session_id ? ` (${event.session_id})` : ""}`;
3540
+ case "session.idle":
3541
+ return `${dim}[${time}]${reset} \uD83D\uDCA4 session idle${event.session_id ? ` (${event.session_id})` : ""}`;
3542
+ case "session.error":
3543
+ return `${dim}[${time}]${reset} ❌ session error${event.error ? `: ${event.error}` : ""}`;
3544
+ default:
3545
+ return `${dim}[${time}]${reset} ${event.type}`;
3546
+ }
3547
+ }
3548
+ function formatArgs(args) {
3549
+ if (!args)
3550
+ return "";
3551
+ const parts = [];
3552
+ for (const [key, value] of Object.entries(args)) {
3553
+ if (key === "filePath" || key === "path" || key === "file") {
3554
+ parts.push(String(value));
3555
+ } else if (key === "agent") {
3556
+ parts.push(`@${String(value)}`);
3557
+ }
3558
+ }
3559
+ return parts.join(", ");
3560
+ }
3561
+
3562
+ // src/hooks/event-log-hook.ts
3563
+ var toolStartTimes = new Map;
3564
+ var staleThresholdMs = 5 * 60 * 1000;
3565
+ var CLEANUP_INTERVAL = 50;
3566
+ var beforeHookCallCount = 0;
3567
+ function cleanupStaleToolStartTimes() {
3568
+ const now = Date.now();
3569
+ for (const [key, startTime] of toolStartTimes.entries()) {
3570
+ if (now - startTime > staleThresholdMs) {
3571
+ toolStartTimes.delete(key);
3572
+ }
3573
+ }
3574
+ }
3575
+ function createEventLogHooks(appLog) {
3576
+ return {
3577
+ async before(ctx, toolInput, toolOutput) {
3578
+ const toolName = toolInput.tool ?? toolInput.name ?? "unknown";
3579
+ const sessionId = toolInput.sessionID ?? toolInput.sessionId ?? "unknown";
3580
+ const args = toolOutput?.args ?? toolInput?.args ?? {};
3581
+ const startKey = `${sessionId}:${toolName}`;
3582
+ beforeHookCallCount++;
3583
+ if (beforeHookCallCount >= CLEANUP_INTERVAL) {
3584
+ beforeHookCallCount = 0;
3585
+ cleanupStaleToolStartTimes();
3586
+ }
3587
+ toolStartTimes.set(startKey, Date.now());
3588
+ const event = {
3589
+ timestamp: new Date().toISOString(),
3590
+ type: "tool.before",
3591
+ agent: getCurrentAgent() ?? undefined,
3592
+ tool: toolName,
3593
+ args: sanitizeArgs(args),
3594
+ session_id: sessionId
3595
+ };
3596
+ logEvent(ctx.directory, event, appLog);
3597
+ },
3598
+ async after(ctx, toolInput, toolOutput) {
3599
+ const toolName = toolInput.tool ?? toolInput.name ?? "unknown";
3600
+ const sessionId = toolInput.sessionID ?? toolInput.sessionId ?? "unknown";
3601
+ const args = toolOutput?.args ?? toolInput?.args ?? {};
3602
+ const startKey = `${sessionId}:${toolName}`;
3603
+ const startTime = toolStartTimes.get(startKey);
3604
+ const durationMs = startTime ? Date.now() - startTime : undefined;
3605
+ toolStartTimes.delete(startKey);
3606
+ let status = "success";
3607
+ let error;
3608
+ if (toolOutput?.error != null) {
3609
+ status = "error";
3610
+ error = typeof toolOutput.error === "string" ? toolOutput.error : String(toolOutput.error);
3611
+ } else if (toolOutput?.status === "error") {
3612
+ status = "error";
3613
+ error = typeof toolOutput.error === "string" ? toolOutput.error : "Unknown error";
3614
+ } else if (toolOutput?.status === "blocked") {
3615
+ status = "blocked";
3616
+ }
3617
+ const event = {
3618
+ timestamp: new Date().toISOString(),
3619
+ type: "tool.after",
3620
+ agent: getCurrentAgent() ?? undefined,
3621
+ tool: toolName,
3622
+ args: sanitizeArgs(args),
3623
+ duration_ms: durationMs,
3624
+ status,
3625
+ error,
3626
+ session_id: sessionId
3627
+ };
3628
+ logEvent(ctx.directory, event, appLog);
3629
+ },
3630
+ async session(ctx, event) {
3631
+ const type = event?.type ?? "";
3632
+ const props = event?.properties ?? {};
3633
+ if (type === "session.created") {
3634
+ if (props.parentID) {
3635
+ const agentName = extractAgentFromEvent(props);
3636
+ setCurrentAgent(agentName);
3637
+ }
3638
+ const toolEvent = {
3639
+ timestamp: new Date().toISOString(),
3640
+ type: "session.created",
3641
+ session_id: props.id ?? props.sessionId ?? undefined
3642
+ };
3643
+ logEvent(ctx.directory, toolEvent, appLog);
3644
+ } else if (type === "session.idle") {
3645
+ if (props.parentID) {
3646
+ setCurrentAgent(null);
3647
+ }
3648
+ const toolEvent = {
3649
+ timestamp: new Date().toISOString(),
3650
+ type: "session.idle",
3651
+ session_id: props.id ?? props.sessionId ?? undefined
3652
+ };
3653
+ logEvent(ctx.directory, toolEvent, appLog);
3654
+ } else if (type === "session.error") {
3655
+ if (props.parentID) {
3656
+ setCurrentAgent(null);
3657
+ }
3658
+ const err = props.error;
3659
+ const errorMsg = (err && typeof err === "object" && "message" in err ? String(err.message) : undefined) ?? (typeof err === "string" ? err : undefined) ?? undefined;
3660
+ const toolEvent = {
3661
+ timestamp: new Date().toISOString(),
3662
+ type: "session.error",
3663
+ session_id: props.id ?? props.sessionId ?? undefined,
3664
+ error: errorMsg
3665
+ };
3666
+ logEvent(ctx.directory, toolEvent, appLog);
3667
+ }
3668
+ }
3669
+ };
3670
+ }
3671
+ function extractAgentFromEvent(props) {
3672
+ if (typeof props.agent === "string")
3673
+ return props.agent;
3674
+ if (typeof props.name === "string")
3675
+ return props.name;
3676
+ const title = typeof props.title === "string" ? props.title : "";
3677
+ const match = title.match(/^(.+)-delegate$/);
3678
+ if (match)
3679
+ return match[1];
3680
+ return "unknown";
3681
+ }
3682
+
4181
3683
  // src/hooks/context-window-monitor.ts
4182
3684
  var CONTEXT_WARNING_THRESHOLD = 0.7;
4183
3685
  var DEFAULT_CONTEXT_LIMIT = Number(process.env.FLOWDECK_CONTEXT_LIMIT) || 200000;
@@ -4229,8 +3731,8 @@ function createContextWindowMonitorHook() {
4229
3731
  }
4230
3732
 
4231
3733
  // src/hooks/shell-env-hook.ts
4232
- import { existsSync as existsSync31, readFileSync as readFileSync29 } from "fs";
4233
- import { join as join29 } from "path";
3734
+ import { existsSync as existsSync27, readFileSync as readFileSync26 } from "fs";
3735
+ import { join as join25 } from "path";
4234
3736
  import { createRequire as createRequire2 } from "module";
4235
3737
  var _version;
4236
3738
  function getVersion() {
@@ -4266,7 +3768,7 @@ var MARKER_TO_LANG = {
4266
3768
  };
4267
3769
  function detectPackageManager(root) {
4268
3770
  for (const [lockfile, pm] of Object.entries(LOCKFILE_TO_PM)) {
4269
- if (existsSync31(join29(root, lockfile)))
3771
+ if (existsSync27(join25(root, lockfile)))
4270
3772
  return pm;
4271
3773
  }
4272
3774
  return;
@@ -4275,7 +3777,7 @@ function detectLanguages(root) {
4275
3777
  const langs = [];
4276
3778
  const seen = new Set;
4277
3779
  for (const [marker, lang] of Object.entries(MARKER_TO_LANG)) {
4278
- if (!seen.has(lang) && existsSync31(join29(root, marker))) {
3780
+ if (!seen.has(lang) && existsSync27(join25(root, marker))) {
4279
3781
  langs.push(lang);
4280
3782
  seen.add(lang);
4281
3783
  }
@@ -4283,11 +3785,11 @@ function detectLanguages(root) {
4283
3785
  return langs;
4284
3786
  }
4285
3787
  function readCurrentPhase(root) {
4286
- const statePath2 = join29(root, ".planning", "STATE.md");
4287
- if (!existsSync31(statePath2))
3788
+ const statePath2 = join25(root, ".planning", "STATE.md");
3789
+ if (!existsSync27(statePath2))
4288
3790
  return;
4289
3791
  try {
4290
- const content = readFileSync29(statePath2, "utf-8");
3792
+ const content = readFileSync26(statePath2, "utf-8");
4291
3793
  const match = content.match(/phase:\s*(\S+)/i);
4292
3794
  return match?.[1];
4293
3795
  } catch {
@@ -4412,8 +3914,8 @@ function createSessionIdleHook(client, tracker) {
4412
3914
  }
4413
3915
 
4414
3916
  // src/hooks/compaction-hook.ts
4415
- import { existsSync as existsSync32, readFileSync as readFileSync30 } from "fs";
4416
- import { join as join30 } from "path";
3917
+ import { existsSync as existsSync28, readFileSync as readFileSync27 } from "fs";
3918
+ import { join as join26 } from "path";
4417
3919
  var STRUCTURED_SUMMARY_PROMPT = `
4418
3920
  When summarizing this session, you MUST include the following sections:
4419
3921
 
@@ -4454,10 +3956,10 @@ For each: agent name, status, description, session_id.
4454
3956
  var _lastInjected = new Map;
4455
3957
  function readPlanningState2(directory) {
4456
3958
  const sp = statePath(directory);
4457
- if (!existsSync32(sp))
3959
+ if (!existsSync28(sp))
4458
3960
  return null;
4459
3961
  try {
4460
- const content = readFileSync30(sp, "utf-8");
3962
+ const content = readFileSync27(sp, "utf-8");
4461
3963
  const parsed = parseState(content);
4462
3964
  const version = typeof parsed.summaryVersion === "number" ? parsed.summaryVersion : 0;
4463
3965
  return { content: content.slice(0, 1500), version };
@@ -4486,15 +3988,15 @@ function createCompactionHook(ctx, tracker) {
4486
3988
  sections.push(`_State unchanged since last compaction. summaryVersion=${currentStateVersion}_`);
4487
3989
  sections.push("");
4488
3990
  }
4489
- const indexPath2 = join30(ctx.directory, ".planning", "CODEBASE_INDEX.md");
4490
- if (indexChanged && existsSync32(indexPath2)) {
3991
+ const indexPath2 = join26(ctx.directory, ".planning", "CODEBASE_INDEX.md");
3992
+ if (indexChanged && existsSync28(indexPath2)) {
4491
3993
  try {
4492
- const indexContent = readFileSync30(indexPath2, "utf-8");
3994
+ const indexContent = readFileSync27(indexPath2, "utf-8");
4493
3995
  const indexSummary = "\n## Codebase Index\n```\n" + indexContent.slice(0, 800) + "\n```\n";
4494
3996
  sections.push(indexSummary);
4495
3997
  sections.push("");
4496
3998
  } catch {}
4497
- } else if (existsSync32(indexPath2)) {
3999
+ } else if (existsSync28(indexPath2)) {
4498
4000
  sections.push(`## Codebase Index (unchanged, v${currentIndexVersion})`);
4499
4001
  sections.push(`_Index unchanged since last compaction. summaryVersion=${currentIndexVersion}_`);
4500
4002
  sections.push("");
@@ -4545,12 +4047,9 @@ var ALWAYS_ALLOWED = new Set([
4545
4047
  "council",
4546
4048
  "planning-state",
4547
4049
  "codebase-state",
4548
- "workspace-state",
4549
4050
  "repo-memory",
4550
4051
  "decision-trace",
4551
4052
  "policy-engine",
4552
- "context-generator",
4553
- "create-skill",
4554
4053
  "reflect"
4555
4054
  ]);
4556
4055
  function isDelegationTool(name) {
@@ -4661,7 +4160,7 @@ async function runAutoLearner(client, directory, appLog) {
4661
4160
  parts: [
4662
4161
  {
4663
4162
  type: "text",
4664
- text: "Run your automated self-improvement routine: call `reflect`, " + "identify patterns, and call `create-skill` for each one. " + "Complete silently without asking for input."
4163
+ text: "Run your automated self-improvement routine: call `reflect`, " + "identify patterns, and write a skill markdown file under `src/skills/<name>/SKILL.md` for each one. " + "Complete silently without asking for input."
4665
4164
  }
4666
4165
  ],
4667
4166
  tools: { question: false }
@@ -4895,8 +4394,8 @@ Please advise.
4895
4394
  ## Self-Learning
4896
4395
 
4897
4396
  When a task required unusual human guidance, a novel solution strategy, or exposed a knowledge gap:
4898
- 1. After the task completes successfully, call the \`create-skill\` tool to capture the pattern
4899
- 2. Use a descriptive kebab-case name, a one-sentence description, and structured Markdown content
4397
+ 1. After the task completes successfully, write a new skill markdown file under \`src/skills/<name>/SKILL.md\` to capture the pattern
4398
+ 2. Use a descriptive kebab-case name for the directory, a one-sentence description in the frontmatter, and structured Markdown content
4900
4399
  3. Include: When to Activate, Steps, Examples, and Pitfalls sections
4901
4400
 
4902
4401
  Do NOT create a skill for routine tasks. Only capture genuinely novel or reusable patterns.`;
@@ -7042,7 +6541,6 @@ You receive a structured context with:
7042
6541
  - \`file_path\`: optional specific file being changed
7043
6542
  - \`trust_score\`: patch trust score (0–100; 80+ = safe, 40–79 = review-required, <40 = high-risk)
7044
6543
  - \`trust_signals\`: list of risk signals from the patch trust scorer
7045
- - \`volatile_zones\`: paths marked as volatile or critical in VOLATILITY.json
7046
6544
  - \`prior_failures\`: failure entries from FAILURES.json that match this change
7047
6545
  - \`regression_categories\`: predicted regression categories for this change
7048
6546
  - \`confidence\`: system confidence score (0–100; based on how much codebase context data exists)
@@ -7501,7 +6999,7 @@ var AUTO_LEARNER_PROMPT = `You run automatically after a coding session to captu
7501
6999
  - Novel solutions that took non-obvious reasoning
7502
7000
  - Recurring tool sequences that indicate a reusable workflow
7503
7001
  - Knowledge gaps that had to be worked out from scratch
7504
- 3. For each valuable pattern, call \`create-skill\` immediately.
7002
+ 3. For each valuable pattern, write a skill markdown file under \`src/skills/<name>/SKILL.md\` immediately.
7505
7003
  4. If nothing is worth capturing, output exactly: "No new skills identified."
7506
7004
  5. End with a one-line summary: "Auto-learn complete: N skill(s) created."
7507
7005
 
@@ -7816,7 +7314,6 @@ function createAgent(name, model, customPrompt, customAppendPrompt) {
7816
7314
  case "supervisor":
7817
7315
  return createSupervisorAgent(model, customPrompt, customAppendPrompt);
7818
7316
  default:
7819
- console.warn(`[flowdeck] Unknown agent: ${name}`);
7820
7317
  return;
7821
7318
  }
7822
7319
  }
@@ -7866,12 +7363,9 @@ var CONTRACTS = [
7866
7363
  "council",
7867
7364
  "planning-state",
7868
7365
  "codebase-state",
7869
- "workspace-state",
7870
7366
  "repo-memory",
7871
7367
  "decision-trace",
7872
7368
  "policy-engine",
7873
- "context-generator",
7874
- "create-skill",
7875
7369
  "reflect"
7876
7370
  ],
7877
7371
  forbiddenActions: [
@@ -7906,7 +7400,7 @@ var CONTRACTS = [
7906
7400
  allowedTaskTypes: ["planning", "task-breakdown", "step-decomposition"],
7907
7401
  requiredInputs: ["task description or STATE.md"],
7908
7402
  expectedOutputFields: ["steps", "phase"],
7909
- allowedTools: ["read", "glob", "grep", "planning-state", "workspace-state"],
7403
+ allowedTools: ["read", "glob", "grep", "planning-state"],
7910
7404
  forbiddenActions: [
7911
7405
  "write source files",
7912
7406
  "run bash commands",
@@ -8422,7 +7916,6 @@ function runSupervisorReview(directory, targetName, ctx = {}, clarificationQuest
8422
7916
  reviewPhase,
8423
7917
  timestamp: timestamp2
8424
7918
  };
8425
- _emitTelemetry(directory, decision2, ctx);
8426
7919
  return decision2;
8427
7920
  }
8428
7921
  const policyResult = targetType === "command" ? checkCommandPolicy(targetName, ctx) : checkAgentPolicy(targetName, ctx);
@@ -8444,7 +7937,6 @@ function runSupervisorReview(directory, targetName, ctx = {}, clarificationQuest
8444
7937
  timestamp: timestamp2,
8445
7938
  ...escalationQuestion ? { clarificationQuestion: escalationQuestion } : {}
8446
7939
  };
8447
- _emitTelemetry(directory, supervisorDecision, ctx);
8448
7940
  return supervisorDecision;
8449
7941
  }
8450
7942
  function shouldProceed(decision, mode, canBlock) {
@@ -8457,33 +7949,12 @@ function shouldProceed(decision, mode, canBlock) {
8457
7949
  }
8458
7950
  return decision.decision !== "block" || decision.confidenceScore > 0.3;
8459
7951
  }
8460
- function _emitTelemetry(directory, decision, ctx) {
8461
- try {
8462
- appendEvent2(directory, {
8463
- session_id: ctx.session_id ?? "session-0",
8464
- run_id: ctx.run_id ?? "unknown",
8465
- event: "supervisor.review",
8466
- agent: "supervisor",
8467
- status: decision.decision === "approve" ? "ok" : decision.decision === "block" ? "blocked" : decision.decision === "escalate" ? "approved" : "ok",
8468
- meta: {
8469
- targetName: decision.targetName,
8470
- targetType: decision.targetType,
8471
- exists: decision.exists,
8472
- decision: decision.decision,
8473
- confidenceScore: decision.confidenceScore,
8474
- riskFlags: decision.riskFlags,
8475
- missingRequirements: decision.missingRequirements,
8476
- reviewPhase: decision.reviewPhase
8477
- }
8478
- });
8479
- } catch {}
8480
- }
8481
7952
 
8482
7953
  // src/index.ts
8483
7954
  function lazyLoadRulePaths(projectRoot) {
8484
- const __dir = dirname5(fileURLToPath3(import.meta.url));
8485
- const rulesDir = join31(__dir, "..", "src", "rules");
8486
- if (!existsSync33(rulesDir))
7955
+ const __dir = dirname3(fileURLToPath2(import.meta.url));
7956
+ const rulesDir = join27(__dir, "..", "src", "rules");
7957
+ if (!existsSync29(rulesDir))
8487
7958
  return { paths: [], diagnostics: "[LazyRuleLoader] rules directory not found" };
8488
7959
  const detectedLanguages = detectProjectLanguages(projectRoot);
8489
7960
  const paths = getStartupRulePaths(rulesDir, detectedLanguages);
@@ -8492,17 +7963,17 @@ function lazyLoadRulePaths(projectRoot) {
8492
7963
  return { paths, diagnostics };
8493
7964
  }
8494
7965
  function loadCommands() {
8495
- const __dir = dirname5(fileURLToPath3(import.meta.url));
8496
- const commandsDir = join31(__dir, "..", "src", "commands");
8497
- if (!existsSync33(commandsDir))
7966
+ const __dir = dirname3(fileURLToPath2(import.meta.url));
7967
+ const commandsDir = join27(__dir, "..", "src", "commands");
7968
+ if (!existsSync29(commandsDir))
8498
7969
  return {};
8499
7970
  const commands = {};
8500
7971
  try {
8501
- for (const file of readdirSync5(commandsDir)) {
7972
+ for (const file of readdirSync4(commandsDir)) {
8502
7973
  if (!file.endsWith(".md"))
8503
7974
  continue;
8504
7975
  const name = basename2(file, ".md");
8505
- const raw = readFileSync31(join31(commandsDir, file), "utf-8");
7976
+ const raw = readFileSync28(join27(commandsDir, file), "utf-8");
8506
7977
  let description;
8507
7978
  let template = raw;
8508
7979
  const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
@@ -8520,10 +7991,8 @@ function loadCommands() {
8520
7991
  var plugin = async (input, _options) => {
8521
7992
  const { directory, client, worktree } = input;
8522
7993
  const appLog = (msg) => client.app.log({ body: { service: "flowdeck", level: "info", message: msg } }).catch(() => {});
8523
- const toastFn = (message, variant, duration) => client.tui.showToast({ body: { message, variant, duration }, query: { directory } }).catch(() => {});
8524
- const activityReporter = new ActivityReporter(appLog, toastFn);
8525
- const runPipelineTool = createRunPipelineTool(client, activityReporter);
8526
- const delegateTool = createDelegateTool(client, activityReporter);
7994
+ const runPipelineTool = createRunPipelineTool(client);
7995
+ const delegateTool = createDelegateTool(client);
8527
7996
  const councilTool = createCouncilTool(client);
8528
7997
  const fileTracker = new SessionFileTracker;
8529
7998
  const { fileEdited, fileWatcherUpdated } = createFileTrackerHooks(fileTracker);
@@ -8534,6 +8003,7 @@ var plugin = async (input, _options) => {
8534
8003
  const compactionHook = createCompactionHook({ directory }, fileTracker);
8535
8004
  const orchestratorGuard = new OrchestratorGuard;
8536
8005
  const autoLearnHook = createAutoLearnHook(client, fileTracker, directory, appLog);
8006
+ const eventLog = createEventLogHooks(appLog);
8537
8007
  const notifCtrl = new NotificationController(undefined, appLog);
8538
8008
  const agentConfigs = getAgentConfigs({});
8539
8009
  const mcps = createFlowDeckMcps();
@@ -8587,8 +8057,8 @@ var plugin = async (input, _options) => {
8587
8057
  }
8588
8058
  }
8589
8059
  }
8590
- const skillsDir = join31(dirname5(fileURLToPath3(import.meta.url)), "..", "src", "skills");
8591
- if (existsSync33(skillsDir)) {
8060
+ const skillsDir = join27(dirname3(fileURLToPath2(import.meta.url)), "..", "src", "skills");
8061
+ if (existsSync29(skillsDir)) {
8592
8062
  const cfgAny = cfg;
8593
8063
  if (!cfgAny.skills || typeof cfgAny.skills !== "object") {
8594
8064
  cfgAny.skills = { paths: [] };
@@ -8617,18 +8087,14 @@ var plugin = async (input, _options) => {
8617
8087
  tool: {
8618
8088
  "planning-state": planningStateTool,
8619
8089
  "codebase-state": codebaseStateTool,
8620
- "workspace-state": workspaceStateTool,
8621
8090
  "run-pipeline": runPipelineTool,
8622
8091
  delegate: delegateTool,
8623
8092
  "repo-memory": repoMemoryTool,
8624
8093
  "failure-replay": failureReplayTool,
8625
8094
  "decision-trace": decisionTraceTool,
8626
- "volatility-map": volatilityMapTool,
8627
8095
  "policy-engine": policyEngineTool,
8628
8096
  "hash-edit": hashEditTool,
8629
8097
  council: councilTool,
8630
- "context-generator": contextGeneratorTool,
8631
- "create-skill": createSkillTool,
8632
8098
  reflect: reflectTool,
8633
8099
  codegraph: codegraphTool,
8634
8100
  "load-rules": loadRulesTool,
@@ -8641,17 +8107,18 @@ var plugin = async (input, _options) => {
8641
8107
  "file.watcher.updated": fileWatcherUpdated,
8642
8108
  "experimental.session.compacting": compactionHook,
8643
8109
  "command.execute.before": async (input2, _output) => {
8644
- activityReporter.reportCommandStarted(input2.command);
8645
8110
  lastExecutedCommand = input2.command;
8646
8111
  },
8647
8112
  "permission.ask": async (input2, _output) => {
8648
8113
  notifyPermissionNeeded(input2.title);
8649
- activityReporter.reportWaitingForApproval(input2.title);
8650
8114
  },
8651
8115
  event: async ({ event }) => {
8652
8116
  const type = event?.type ?? "";
8653
8117
  if (type === "session.created" || type === "session.started") {
8654
8118
  await sessionStartHook({ directory });
8119
+ if (type === "session.created") {
8120
+ await eventLog.session({ directory }, event);
8121
+ }
8655
8122
  }
8656
8123
  if (type === "command.executed") {
8657
8124
  const commandName = event?.properties?.name ?? "";
@@ -8662,9 +8129,9 @@ var plugin = async (input, _options) => {
8662
8129
  await contextMonitor.event({ event });
8663
8130
  orchestratorGuard.onEvent(event);
8664
8131
  if (type === "session.idle") {
8132
+ await eventLog.session({ directory }, event);
8665
8133
  const hasEdits = fileTracker.getEditedPaths().length > 0;
8666
8134
  if (lastExecutedCommand) {
8667
- activityReporter.reportCommandCompleted(lastExecutedCommand, hasEdits);
8668
8135
  lastExecutedCommand = null;
8669
8136
  }
8670
8137
  notifCtrl.onSessionIdle(hasEdits);
@@ -8676,6 +8143,7 @@ var plugin = async (input, _options) => {
8676
8143
  }
8677
8144
  }
8678
8145
  if (type === "session.error") {
8146
+ await eventLog.session({ directory }, event);
8679
8147
  lastExecutedCommand = null;
8680
8148
  const err = event?.properties?.error;
8681
8149
  const errorMsg = (err && typeof err === "object" && "message" in err ? String(err.message) : undefined) ?? (typeof err === "string" ? err : undefined) ?? "An unexpected error occurred";
@@ -8721,15 +8189,15 @@ var plugin = async (input, _options) => {
8721
8189
  }
8722
8190
  }
8723
8191
  }
8724
- await telemetryHook({ directory }, toolInput, toolOutput, activityReporter);
8725
8192
  await approvalHook({ directory }, toolInput, toolOutput);
8726
8193
  await guardRailsHook({ directory }, toolInput, toolOutput);
8727
8194
  await toolGuardHook({ directory }, toolInput, toolOutput);
8728
8195
  await patchTrustHook({ directory }, toolInput, toolOutput);
8729
8196
  await decisionTraceHook({ directory }, toolInput, toolOutput);
8197
+ await eventLog.before({ directory }, toolInput, toolOutput);
8730
8198
  },
8731
8199
  "tool.execute.after": async (toolInput, toolOutput) => {
8732
- await telemetryAfterHook({ directory }, toolInput, toolOutput, activityReporter);
8200
+ await eventLog.after({ directory }, toolInput, toolOutput);
8733
8201
  const afterToolName = toolInput.tool ?? toolInput.name ?? "";
8734
8202
  if (afterToolName === "delegate" || afterToolName === "run-pipeline") {
8735
8203
  try {