@dv.nghiem/flowdeck 0.4.4 → 0.4.5

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 (145) hide show
  1. package/dist/agents/risk-analyst.d.ts.map +1 -1
  2. package/dist/dashboard/lib/state-reader.d.ts.map +1 -1
  3. package/dist/dashboard/server.mjs +22 -73
  4. package/dist/hooks/approval-hook.d.ts.map +1 -1
  5. package/dist/hooks/event-log-hook.d.ts +12 -0
  6. package/dist/hooks/event-log-hook.d.ts.map +1 -0
  7. package/dist/hooks/orchestrator-guard-hook.d.ts.map +1 -1
  8. package/dist/hooks/patch-trust.d.ts +0 -1
  9. package/dist/hooks/patch-trust.d.ts.map +1 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +622 -1145
  12. package/dist/lib/impact-radar.d.ts.map +1 -1
  13. package/dist/services/agent-validator.d.ts +1 -1
  14. package/dist/services/agent-validator.d.ts.map +1 -1
  15. package/dist/services/event-logger.d.ts +18 -0
  16. package/dist/services/event-logger.d.ts.map +1 -0
  17. package/dist/services/supervisor-binding.d.ts.map +1 -1
  18. package/dist/services/token-metrics.d.ts +1 -1
  19. package/dist/services/workflow-scorecard.d.ts.map +1 -1
  20. package/dist/tools/delegate.d.ts +1 -2
  21. package/dist/tools/delegate.d.ts.map +1 -1
  22. package/dist/tools/run-pipeline.d.ts +1 -2
  23. package/dist/tools/run-pipeline.d.ts.map +1 -1
  24. package/docs/commands/fd-deploy-check.md +1 -5
  25. package/docs/commands/fd-reflect.md +8 -9
  26. package/docs/commands/fd-suggest.md +3 -4
  27. package/docs/concepts/architecture.md +0 -3
  28. package/docs/concepts/intelligence.md +2 -36
  29. package/docs/skills/index.md +0 -2
  30. package/package.json +2 -2
  31. package/src/commands/fd-deploy-check.md +1 -5
  32. package/src/commands/fd-reflect.md +4 -6
  33. package/src/commands/fd-suggest.md +3 -4
  34. package/src/skills/change-impact-radar/SKILL.md +3 -4
  35. package/src/skills/confidence-aware-planning/SKILL.md +0 -1
  36. package/src/skills/patch-trust-score/SKILL.md +3 -5
  37. package/dist/dashboard/lib/port-finder.test.d.ts +0 -2
  38. package/dist/dashboard/lib/port-finder.test.d.ts.map +0 -1
  39. package/dist/hooks/notifications.test.d.ts +0 -14
  40. package/dist/hooks/notifications.test.d.ts.map +0 -1
  41. package/dist/hooks/patch-trust.test.d.ts +0 -2
  42. package/dist/hooks/patch-trust.test.d.ts.map +0 -1
  43. package/dist/hooks/telemetry-hook.d.ts +0 -33
  44. package/dist/hooks/telemetry-hook.d.ts.map +0 -1
  45. package/dist/hooks/telemetry-hook.test.d.ts +0 -2
  46. package/dist/hooks/telemetry-hook.test.d.ts.map +0 -1
  47. package/dist/hooks/tool-guard.test.d.ts +0 -2
  48. package/dist/hooks/tool-guard.test.d.ts.map +0 -1
  49. package/dist/lib/research-gate.test.d.ts +0 -2
  50. package/dist/lib/research-gate.test.d.ts.map +0 -1
  51. package/dist/services/activity-reporter.d.ts +0 -96
  52. package/dist/services/activity-reporter.d.ts.map +0 -1
  53. package/dist/services/activity-reporter.test.d.ts +0 -2
  54. package/dist/services/activity-reporter.test.d.ts.map +0 -1
  55. package/dist/services/artifact-store.d.ts +0 -39
  56. package/dist/services/artifact-store.d.ts.map +0 -1
  57. package/dist/services/artifact-store.test.d.ts +0 -2
  58. package/dist/services/artifact-store.test.d.ts.map +0 -1
  59. package/dist/services/codegraph.test.d.ts +0 -2
  60. package/dist/services/codegraph.test.d.ts.map +0 -1
  61. package/dist/services/command-validator.test.d.ts +0 -2
  62. package/dist/services/command-validator.test.d.ts.map +0 -1
  63. package/dist/services/context-assembler.d.ts +0 -29
  64. package/dist/services/context-assembler.d.ts.map +0 -1
  65. package/dist/services/context-assembler.test.d.ts +0 -2
  66. package/dist/services/context-assembler.test.d.ts.map +0 -1
  67. package/dist/services/cost-budget.d.ts +0 -53
  68. package/dist/services/cost-budget.d.ts.map +0 -1
  69. package/dist/services/cost-budget.test.d.ts +0 -2
  70. package/dist/services/cost-budget.test.d.ts.map +0 -1
  71. package/dist/services/cost-estimator.test.d.ts +0 -2
  72. package/dist/services/cost-estimator.test.d.ts.map +0 -1
  73. package/dist/services/draft-verifier.d.ts +0 -48
  74. package/dist/services/draft-verifier.d.ts.map +0 -1
  75. package/dist/services/draft-verifier.test.d.ts +0 -2
  76. package/dist/services/draft-verifier.test.d.ts.map +0 -1
  77. package/dist/services/governance.test.d.ts +0 -11
  78. package/dist/services/governance.test.d.ts.map +0 -1
  79. package/dist/services/index.d.ts +0 -25
  80. package/dist/services/index.d.ts.map +0 -1
  81. package/dist/services/lazy-rule-loader.test.d.ts +0 -23
  82. package/dist/services/lazy-rule-loader.test.d.ts.map +0 -1
  83. package/dist/services/model-router-ext.test.d.ts +0 -2
  84. package/dist/services/model-router-ext.test.d.ts.map +0 -1
  85. package/dist/services/model-router.test.d.ts +0 -2
  86. package/dist/services/model-router.test.d.ts.map +0 -1
  87. package/dist/services/policy-compiler.d.ts +0 -27
  88. package/dist/services/policy-compiler.d.ts.map +0 -1
  89. package/dist/services/preflight-explorer.test.d.ts +0 -25
  90. package/dist/services/preflight-explorer.test.d.ts.map +0 -1
  91. package/dist/services/prompt-cache-ext.test.d.ts +0 -2
  92. package/dist/services/prompt-cache-ext.test.d.ts.map +0 -1
  93. package/dist/services/prompt-cache.test.d.ts +0 -2
  94. package/dist/services/prompt-cache.test.d.ts.map +0 -1
  95. package/dist/services/quick-router.test.d.ts +0 -13
  96. package/dist/services/quick-router.test.d.ts.map +0 -1
  97. package/dist/services/recommended-question.test.d.ts +0 -2
  98. package/dist/services/recommended-question.test.d.ts.map +0 -1
  99. package/dist/services/rtk-manager.test.d.ts +0 -2
  100. package/dist/services/rtk-manager.test.d.ts.map +0 -1
  101. package/dist/services/rtk-policy.test.d.ts +0 -2
  102. package/dist/services/rtk-policy.test.d.ts.map +0 -1
  103. package/dist/services/rule-engine.d.ts +0 -29
  104. package/dist/services/rule-engine.d.ts.map +0 -1
  105. package/dist/services/rule-engine.test.d.ts +0 -2
  106. package/dist/services/rule-engine.test.d.ts.map +0 -1
  107. package/dist/services/services.test.d.ts +0 -2
  108. package/dist/services/services.test.d.ts.map +0 -1
  109. package/dist/services/supervisor.test.d.ts +0 -14
  110. package/dist/services/supervisor.test.d.ts.map +0 -1
  111. package/dist/services/task-batcher.d.ts +0 -48
  112. package/dist/services/task-batcher.d.ts.map +0 -1
  113. package/dist/services/task-batcher.test.d.ts +0 -2
  114. package/dist/services/task-batcher.test.d.ts.map +0 -1
  115. package/dist/services/telemetry.d.ts +0 -48
  116. package/dist/services/telemetry.d.ts.map +0 -1
  117. package/dist/services/token-budget.d.ts +0 -44
  118. package/dist/services/token-budget.d.ts.map +0 -1
  119. package/dist/services/token-budget.test.d.ts +0 -2
  120. package/dist/services/token-budget.test.d.ts.map +0 -1
  121. package/dist/services/token-metrics-ext.test.d.ts +0 -2
  122. package/dist/services/token-metrics-ext.test.d.ts.map +0 -1
  123. package/dist/services/token-metrics.test.d.ts +0 -2
  124. package/dist/services/token-metrics.test.d.ts.map +0 -1
  125. package/dist/tools/agent-dispatch.test.d.ts +0 -2
  126. package/dist/tools/agent-dispatch.test.d.ts.map +0 -1
  127. package/dist/tools/codebase-index.test.d.ts +0 -2
  128. package/dist/tools/codebase-index.test.d.ts.map +0 -1
  129. package/dist/tools/context-generator.d.ts +0 -3
  130. package/dist/tools/context-generator.d.ts.map +0 -1
  131. package/dist/tools/create-skill.d.ts +0 -3
  132. package/dist/tools/create-skill.d.ts.map +0 -1
  133. package/dist/tools/dispatch-routing.test.d.ts +0 -2
  134. package/dist/tools/dispatch-routing.test.d.ts.map +0 -1
  135. package/dist/tools/failure-replay.test.d.ts +0 -2
  136. package/dist/tools/failure-replay.test.d.ts.map +0 -1
  137. package/dist/tools/repo-memory.test.d.ts +0 -2
  138. package/dist/tools/repo-memory.test.d.ts.map +0 -1
  139. package/dist/tools/volatility-map.d.ts +0 -18
  140. package/dist/tools/volatility-map.d.ts.map +0 -1
  141. package/dist/tools/volatility-map.test.d.ts +0 -2
  142. package/dist/tools/volatility-map.test.d.ts.map +0 -1
  143. package/dist/tools/workspace-state.d.ts +0 -3
  144. package/dist/tools/workspace-state.d.ts.map +0 -1
  145. 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,23 +1137,23 @@ 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
1158
  } catch {
1532
1159
  console.warn(`[flowdeck] Failed to load config from ${configPath}`);
@@ -1611,19 +1238,19 @@ function extractText2(parts) {
1611
1238
  return parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join(`
1612
1239
  `);
1613
1240
  }
1614
- function createDelegateTool(client, reporter) {
1615
- return tool5({
1241
+ function createDelegateTool(client) {
1242
+ return tool4({
1616
1243
  description: "Delegate a task to a single agent via a child session. Returns the agent's output.",
1617
1244
  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()
1245
+ agent: tool4.schema.string(),
1246
+ prompt: tool4.schema.string(),
1247
+ context: tool4.schema.string().optional(),
1248
+ task_type: tool4.schema.string().optional(),
1249
+ retry_attempts: tool4.schema.number().optional().default(1),
1250
+ safe_to_cache: tool4.schema.boolean().optional().default(false),
1251
+ cache_ttl_ms: tool4.schema.number().optional(),
1252
+ workflow_id: tool4.schema.string().optional(),
1253
+ stage: tool4.schema.string().optional()
1627
1254
  },
1628
1255
  async execute(args, context) {
1629
1256
  const startTime = Date.now();
@@ -1642,17 +1269,13 @@ function createDelegateTool(client, reporter) {
1642
1269
  ---
1643
1270
 
1644
1271
  ${args.prompt}` : args.prompt;
1645
- reporter?.reportToolStarted("delegate", summarize(args.prompt, 100), {
1646
- agent: args.agent,
1647
- stage: args.stage
1648
- });
1649
1272
  const safe_to_cache = args.safe_to_cache === true && CACHEABLE_AGENTS.has(args.agent);
1650
1273
  let stateVersion = 0;
1651
1274
  let indexVersion = 0;
1652
1275
  if (safe_to_cache) {
1653
1276
  const index = readCodebaseIndex(context.directory);
1654
1277
  const sp = statePath(context.directory);
1655
- const rawState = existsSync11(sp) ? readFileSync11(sp, "utf-8") : "";
1278
+ const rawState = existsSync10(sp) ? readFileSync10(sp, "utf-8") : "";
1656
1279
  const state = rawState ? parseState(rawState) : {};
1657
1280
  stateVersion = typeof state.summaryVersion === "number" ? state.summaryVersion : 0;
1658
1281
  indexVersion = typeof index.summaryVersion === "number" ? index.summaryVersion : 0;
@@ -1661,7 +1284,6 @@ ${args.prompt}` : args.prompt;
1661
1284
  if (metricsWorkflowId) {
1662
1285
  recordCacheHit(context.directory, metricsWorkflowId, metricsStage, fullPrompt, args.agent, agentModel);
1663
1286
  }
1664
- reporter?.reportCacheHit("delegate", args.agent);
1665
1287
  return JSON.stringify({
1666
1288
  agent: args.agent,
1667
1289
  success: true,
@@ -1719,15 +1341,10 @@ ${args.prompt}` : args.prompt;
1719
1341
  recordRetryCall(context.directory, metricsWorkflowId, metricsStage, fullPromptForSession, "", args.agent, Date.now() - attemptStart, agentModel, retryCostUsd);
1720
1342
  }
1721
1343
  retriesUsed++;
1722
- reporter?.reportToolRetried("delegate", retriesUsed, "prompt response indicated retry", { agent: args.agent });
1723
1344
  }
1724
1345
  if (!promptRes || promptRes.error) {
1725
1346
  const errMsg = `Prompt failed: ${promptRes?.error?.detail ?? "unknown"}`;
1726
1347
  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
1348
  return JSON.stringify({
1732
1349
  agent: args.agent,
1733
1350
  session_id: childId,
@@ -1743,10 +1360,6 @@ ${args.prompt}` : args.prompt;
1743
1360
  if (info?.error) {
1744
1361
  const errMsg = `Agent error: ${JSON.stringify(info.error)}`;
1745
1362
  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
1363
  return JSON.stringify({
1751
1364
  agent: args.agent,
1752
1365
  session_id: childId,
@@ -1766,10 +1379,6 @@ ${args.prompt}` : args.prompt;
1766
1379
  const costUsd = agentModel ? estimateCostUSD(agentModel, inputTokens, outputTokens) : undefined;
1767
1380
  recordModelCall(context.directory, metricsWorkflowId, metricsStage, fullPromptForSession, output, args.agent, Date.now() - startTime, agentModel, costUsd);
1768
1381
  }
1769
- reporter?.reportToolCompleted("delegate", Date.now() - startTime, summarize(output, 80), {
1770
- agent: args.agent,
1771
- retry_count: retriesUsed
1772
- });
1773
1382
  if (safe_to_cache && output) {
1774
1383
  setCached(context.directory, args.agent, fullPromptForSession, args.context ?? "", stateVersion, indexVersion, output, true, args.cache_ttl_ms);
1775
1384
  }
@@ -1788,53 +1397,53 @@ ${args.prompt}` : args.prompt;
1788
1397
  }
1789
1398
 
1790
1399
  // 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";
1400
+ import { tool as tool5 } from "@opencode-ai/plugin";
1401
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, existsSync as existsSync11, mkdirSync as mkdirSync6 } from "fs";
1402
+ import { join as join10 } from "path";
1794
1403
  var MEMORY_FILE = "MEMORY.json";
1795
1404
  function memoryPath(directory) {
1796
- return join11(codebaseDir(directory), MEMORY_FILE);
1405
+ return join10(codebaseDir(directory), MEMORY_FILE);
1797
1406
  }
1798
1407
  function emptyMemory() {
1799
1408
  return { version: "1.0", last_updated: new Date().toISOString(), nodes: {} };
1800
1409
  }
1801
1410
  function readMemory(directory) {
1802
1411
  const p = memoryPath(directory);
1803
- if (!existsSync12(p))
1412
+ if (!existsSync11(p))
1804
1413
  return emptyMemory();
1805
1414
  try {
1806
- return JSON.parse(readFileSync12(p, "utf-8"));
1415
+ return JSON.parse(readFileSync11(p, "utf-8"));
1807
1416
  } catch {
1808
1417
  return emptyMemory();
1809
1418
  }
1810
1419
  }
1811
1420
  function writeMemory(directory, memory) {
1812
1421
  const base = codebaseDir(directory);
1813
- if (!existsSync12(base))
1422
+ if (!existsSync11(base))
1814
1423
  mkdirSync6(base, { recursive: true });
1815
1424
  memory.last_updated = new Date().toISOString();
1816
- writeFileSync8(memoryPath(directory), JSON.stringify(memory, null, 2), "utf-8");
1425
+ writeFileSync7(memoryPath(directory), JSON.stringify(memory, null, 2), "utf-8");
1817
1426
  }
1818
- var repoMemoryTool = tool6({
1427
+ var repoMemoryTool = tool5({
1819
1428
  description: "Repo Memory Graph: read/write/query persistent architecture graph in .codebase/MEMORY.json (modules, dependencies, ownership, bug history, conventions)",
1820
1429
  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())
1430
+ action: tool5.schema.enum(["read", "write_node", "query", "delete_node"]),
1431
+ node_id: tool5.schema.string().optional(),
1432
+ node: tool5.schema.object({
1433
+ type: tool5.schema.enum(["module", "service", "api", "schema", "config"]),
1434
+ path: tool5.schema.string(),
1435
+ owner: tool5.schema.string().optional(),
1436
+ tags: tool5.schema.array(tool5.schema.string()),
1437
+ dependencies: tool5.schema.array(tool5.schema.string()),
1438
+ dependents: tool5.schema.array(tool5.schema.string()),
1439
+ bug_history: tool5.schema.array(tool5.schema.string()),
1440
+ conventions: tool5.schema.array(tool5.schema.string())
1832
1441
  }).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()
1442
+ query: tool5.schema.object({
1443
+ type: tool5.schema.enum(["module", "service", "api", "schema", "config"]).optional(),
1444
+ owner: tool5.schema.string().optional(),
1445
+ tag: tool5.schema.string().optional(),
1446
+ path_prefix: tool5.schema.string().optional()
1838
1447
  }).optional()
1839
1448
  },
1840
1449
  async execute(args, context) {
@@ -1889,50 +1498,50 @@ var repoMemoryTool = tool6({
1889
1498
  });
1890
1499
 
1891
1500
  // 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";
1501
+ import { tool as tool6 } from "@opencode-ai/plugin";
1502
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, existsSync as existsSync12, mkdirSync as mkdirSync7 } from "fs";
1503
+ import { join as join11 } from "path";
1895
1504
  var FAILURES_FILE = "FAILURES.json";
1896
1505
  function failuresPath(directory) {
1897
- return join12(codebaseDir(directory), FAILURES_FILE);
1506
+ return join11(codebaseDir(directory), FAILURES_FILE);
1898
1507
  }
1899
1508
  function readStore(directory) {
1900
1509
  const p = failuresPath(directory);
1901
- if (!existsSync13(p))
1510
+ if (!existsSync12(p))
1902
1511
  return { version: "1.0", last_updated: new Date().toISOString(), entries: [] };
1903
1512
  try {
1904
- return JSON.parse(readFileSync13(p, "utf-8"));
1513
+ return JSON.parse(readFileSync12(p, "utf-8"));
1905
1514
  } catch {
1906
1515
  return { version: "1.0", last_updated: new Date().toISOString(), entries: [] };
1907
1516
  }
1908
1517
  }
1909
1518
  function writeStore(directory, store) {
1910
1519
  const base = codebaseDir(directory);
1911
- if (!existsSync13(base))
1520
+ if (!existsSync12(base))
1912
1521
  mkdirSync7(base, { recursive: true });
1913
1522
  store.last_updated = new Date().toISOString();
1914
- writeFileSync9(failuresPath(directory), JSON.stringify(store, null, 2), "utf-8");
1523
+ writeFileSync8(failuresPath(directory), JSON.stringify(store, null, 2), "utf-8");
1915
1524
  }
1916
- var failureReplayTool = tool7({
1525
+ var failureReplayTool = tool6({
1917
1526
  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
1527
  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())
1528
+ action: tool6.schema.enum(["record", "query", "list", "mark_resolved"]),
1529
+ entry: tool6.schema.object({
1530
+ id: tool6.schema.string(),
1531
+ type: tool6.schema.enum(["reverted_commit", "failed_deployment", "flaky_test", "bug_fix", "build_failure"]),
1532
+ description: tool6.schema.string(),
1533
+ affected_paths: tool6.schema.array(tool6.schema.string()),
1534
+ root_cause: tool6.schema.string().optional(),
1535
+ fix_applied: tool6.schema.string().optional(),
1536
+ tags: tool6.schema.array(tool6.schema.string())
1928
1537
  }).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()
1538
+ query: tool6.schema.object({
1539
+ type: tool6.schema.enum(["reverted_commit", "failed_deployment", "flaky_test", "bug_fix", "build_failure"]).optional(),
1540
+ path_prefix: tool6.schema.string().optional(),
1541
+ tag: tool6.schema.string().optional(),
1542
+ limit: tool6.schema.number().optional()
1934
1543
  }).optional(),
1935
- entry_id: tool7.schema.string().optional()
1544
+ entry_id: tool6.schema.string().optional()
1936
1545
  },
1937
1546
  async execute(args, context) {
1938
1547
  const dir = context.directory ?? process.cwd();
@@ -1994,18 +1603,18 @@ var failureReplayTool = tool7({
1994
1603
  });
1995
1604
 
1996
1605
  // 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";
1606
+ import { tool as tool7 } from "@opencode-ai/plugin";
1607
+ import { readFileSync as readFileSync13, existsSync as existsSync13, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2 } from "fs";
1608
+ import { join as join12 } from "path";
2000
1609
  var DECISIONS_FILE = "DECISIONS.jsonl";
2001
1610
  function decisionsPath(directory) {
2002
- return join13(codebaseDir(directory), DECISIONS_FILE);
1611
+ return join12(codebaseDir(directory), DECISIONS_FILE);
2003
1612
  }
2004
1613
  function readDecisions(directory) {
2005
1614
  const p = decisionsPath(directory);
2006
- if (!existsSync14(p))
1615
+ if (!existsSync13(p))
2007
1616
  return [];
2008
- return readFileSync14(p, "utf-8").split(`
1617
+ return readFileSync13(p, "utf-8").split(`
2009
1618
  `).filter((l) => l.trim()).map((l) => {
2010
1619
  try {
2011
1620
  return JSON.parse(l);
@@ -2014,29 +1623,29 @@ function readDecisions(directory) {
2014
1623
  }
2015
1624
  }).filter(Boolean);
2016
1625
  }
2017
- var decisionTraceTool = tool8({
1626
+ var decisionTraceTool = tool7({
2018
1627
  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
1628
  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()
1629
+ action: tool7.schema.enum(["record", "query", "get_for_file"]),
1630
+ entry: tool7.schema.object({
1631
+ id: tool7.schema.string(),
1632
+ file_path: tool7.schema.string(),
1633
+ change_type: tool7.schema.enum(["create", "edit", "delete", "refactor"]),
1634
+ rationale: tool7.schema.string(),
1635
+ evidence: tool7.schema.array(tool7.schema.string()),
1636
+ assumptions: tool7.schema.array(tool7.schema.string()),
1637
+ alternatives_considered: tool7.schema.array(tool7.schema.string()),
1638
+ risk_level: tool7.schema.enum(["low", "medium", "high"]),
1639
+ agent: tool7.schema.string().optional(),
1640
+ session_id: tool7.schema.string().optional()
2032
1641
  }).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()
1642
+ query: tool7.schema.object({
1643
+ file_path: tool7.schema.string().optional(),
1644
+ change_type: tool7.schema.enum(["create", "edit", "delete", "refactor"]).optional(),
1645
+ risk_level: tool7.schema.enum(["low", "medium", "high"]).optional(),
1646
+ limit: tool7.schema.number().optional()
2038
1647
  }).optional(),
2039
- file_path: tool8.schema.string().optional()
1648
+ file_path: tool7.schema.string().optional()
2040
1649
  },
2041
1650
  async execute(args, context) {
2042
1651
  const dir = context.directory ?? process.cwd();
@@ -2045,7 +1654,7 @@ var decisionTraceTool = tool8({
2045
1654
  case "record": {
2046
1655
  if (!args.entry)
2047
1656
  return JSON.stringify({ error: "entry required" });
2048
- if (!existsSync14(base))
1657
+ if (!existsSync13(base))
2049
1658
  mkdirSync8(base, { recursive: true });
2050
1659
  const entry = { ...args.entry, timestamp: new Date().toISOString() };
2051
1660
  appendFileSync2(decisionsPath(dir), JSON.stringify(entry) + `
@@ -2078,162 +1687,54 @@ var decisionTraceTool = tool8({
2078
1687
  }
2079
1688
  });
2080
1689
 
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
1690
  // 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";
1691
+ import { tool as tool8 } from "@opencode-ai/plugin";
1692
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync10, existsSync as existsSync14, mkdirSync as mkdirSync9 } from "fs";
1693
+ import { join as join13 } from "path";
2193
1694
  var POLICIES_FILE = "POLICIES.json";
2194
1695
  function policiesPath(directory) {
2195
- return join15(codebaseDir(directory), POLICIES_FILE);
1696
+ return join13(codebaseDir(directory), POLICIES_FILE);
2196
1697
  }
2197
- function readStore3(directory) {
1698
+ function readStore2(directory) {
2198
1699
  const p = policiesPath(directory);
2199
- if (!existsSync16(p))
1700
+ if (!existsSync14(p))
2200
1701
  return { version: "1.0", last_updated: new Date().toISOString(), policies: [] };
2201
1702
  try {
2202
- return JSON.parse(readFileSync16(p, "utf-8"));
1703
+ return JSON.parse(readFileSync14(p, "utf-8"));
2203
1704
  } catch {
2204
1705
  return { version: "1.0", last_updated: new Date().toISOString(), policies: [] };
2205
1706
  }
2206
1707
  }
2207
- function writeStore3(directory, store) {
1708
+ function writeStore2(directory, store) {
2208
1709
  const base = codebaseDir(directory);
2209
- if (!existsSync16(base))
2210
- mkdirSync10(base, { recursive: true });
1710
+ if (!existsSync14(base))
1711
+ mkdirSync9(base, { recursive: true });
2211
1712
  store.last_updated = new Date().toISOString();
2212
- writeFileSync12(policiesPath(directory), JSON.stringify(store, null, 2), "utf-8");
1713
+ writeFileSync10(policiesPath(directory), JSON.stringify(store, null, 2), "utf-8");
2213
1714
  }
2214
- var policyEngineTool = tool10({
1715
+ var policyEngineTool = tool8({
2215
1716
  description: "Self-Healing Policy Engine: manage .codebase/POLICIES.json — add, list, query, toggle, and record violations of editing policies learned from past failures",
2216
1717
  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()
1718
+ action: tool8.schema.enum(["list", "add", "record_violation", "toggle", "query"]),
1719
+ policy: tool8.schema.object({
1720
+ id: tool8.schema.string(),
1721
+ name: tool8.schema.string(),
1722
+ trigger: tool8.schema.string(),
1723
+ rule: tool8.schema.string(),
1724
+ source: tool8.schema.enum(["manual", "learned"]),
1725
+ failure_count: tool8.schema.number()
2225
1726
  }).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()
1727
+ policy_id: tool8.schema.string().optional(),
1728
+ active: tool8.schema.boolean().optional(),
1729
+ query: tool8.schema.object({
1730
+ source: tool8.schema.enum(["manual", "learned"]).optional(),
1731
+ active_only: tool8.schema.boolean().optional(),
1732
+ trigger_contains: tool8.schema.string().optional()
2232
1733
  }).optional()
2233
1734
  },
2234
1735
  async execute(args, context) {
2235
1736
  const dir = context.directory ?? process.cwd();
2236
- const store = readStore3(dir);
1737
+ const store = readStore2(dir);
2237
1738
  switch (args.action) {
2238
1739
  case "list": {
2239
1740
  const active = store.policies.filter((p) => p.active);
@@ -2248,7 +1749,7 @@ var policyEngineTool = tool10({
2248
1749
  } else {
2249
1750
  store.policies.push({ ...args.policy, created_at: new Date().toISOString(), active: true });
2250
1751
  }
2251
- writeStore3(dir, store);
1752
+ writeStore2(dir, store);
2252
1753
  return JSON.stringify({ success: true, id: args.policy.id });
2253
1754
  }
2254
1755
  case "record_violation": {
@@ -2259,7 +1760,7 @@ var policyEngineTool = tool10({
2259
1760
  return JSON.stringify({ error: `Policy not found: ${args.policy_id}` });
2260
1761
  policy.failure_count++;
2261
1762
  policy.last_violated = new Date().toISOString();
2262
- writeStore3(dir, store);
1763
+ writeStore2(dir, store);
2263
1764
  return JSON.stringify({ success: true, policy_id: args.policy_id, failure_count: policy.failure_count });
2264
1765
  }
2265
1766
  case "toggle": {
@@ -2269,7 +1770,7 @@ var policyEngineTool = tool10({
2269
1770
  if (!policy)
2270
1771
  return JSON.stringify({ error: `Policy not found: ${args.policy_id}` });
2271
1772
  policy.active = args.active !== undefined ? args.active : !policy.active;
2272
- writeStore3(dir, store);
1773
+ writeStore2(dir, store);
2273
1774
  return JSON.stringify({ success: true, policy_id: args.policy_id, active: policy.active });
2274
1775
  }
2275
1776
  case "query": {
@@ -2290,22 +1791,22 @@ var policyEngineTool = tool10({
2290
1791
  });
2291
1792
 
2292
1793
  // src/tools/hash-edit.ts
2293
- import { tool as tool11 } from "@opencode-ai/plugin";
2294
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync13 } from "fs";
1794
+ import { tool as tool9 } from "@opencode-ai/plugin";
1795
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync11 } from "fs";
2295
1796
  import { createHash as createHash2 } from "crypto";
2296
- var hashEditTool = tool11({
1797
+ var hashEditTool = tool9({
2297
1798
  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
1799
  args: {
2299
- filePath: tool11.schema.string(),
2300
- targetContent: tool11.schema.string(),
2301
- expectedHash: tool11.schema.string().optional(),
2302
- replacementContent: tool11.schema.string()
1800
+ filePath: tool9.schema.string(),
1801
+ targetContent: tool9.schema.string(),
1802
+ expectedHash: tool9.schema.string().optional(),
1803
+ replacementContent: tool9.schema.string()
2303
1804
  },
2304
1805
  async execute(args, context) {
2305
1806
  const fullPath = args.filePath.startsWith("/") ? args.filePath : `${context.directory}/${args.filePath}`;
2306
1807
  let content;
2307
1808
  try {
2308
- content = readFileSync17(fullPath, "utf-8");
1809
+ content = readFileSync15(fullPath, "utf-8");
2309
1810
  } catch (e) {
2310
1811
  return `Error: Could not read file ${args.filePath}`;
2311
1812
  }
@@ -2319,17 +1820,17 @@ var hashEditTool = tool11({
2319
1820
  }
2320
1821
  }
2321
1822
  const newContent = content.replace(args.targetContent, args.replacementContent);
2322
- writeFileSync13(fullPath, newContent, "utf-8");
1823
+ writeFileSync11(fullPath, newContent, "utf-8");
2323
1824
  return `Successfully updated ${args.filePath} using hash-anchored edit.`;
2324
1825
  }
2325
1826
  });
2326
1827
 
2327
1828
  // 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";
1829
+ import { tool as tool10 } from "@opencode-ai/plugin";
1830
+ import { appendFileSync as appendFileSync3, existsSync as existsSync15, mkdirSync as mkdirSync10 } from "fs";
1831
+ import { join as join14 } from "path";
2331
1832
  import { createHash as createHash3 } from "crypto";
2332
- import { readFileSync as readFileSync18 } from "fs";
1833
+ import { readFileSync as readFileSync16 } from "fs";
2333
1834
  var _councilCache = new Map;
2334
1835
  var COUNCIL_CACHE_TTL_MS = 20 * 60 * 1000;
2335
1836
  function councilCacheKey(task, agents, stateVersion, indexVersion) {
@@ -2350,20 +1851,20 @@ async function runWithConcurrencyLimit(tasks, limit) {
2350
1851
  return results;
2351
1852
  }
2352
1853
  function createCouncilTool(client) {
2353
- return tool12({
1854
+ return tool10({
2354
1855
  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
1856
  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)
1857
+ task: tool10.schema.string(),
1858
+ agents: tool10.schema.array(tool10.schema.string()).optional(),
1859
+ force_fresh: tool10.schema.boolean().optional().default(false),
1860
+ max_concurrency: tool10.schema.number().optional().default(3)
2360
1861
  },
2361
1862
  async execute(args, context) {
2362
1863
  const agents = args.agents || ["architect", "reviewer", "backend-coder"];
2363
1864
  const concurrencyLimit = Math.max(1, Math.min(5, typeof args.max_concurrency === "number" ? args.max_concurrency : 3));
2364
1865
  const index = readCodebaseIndex(context.directory);
2365
1866
  const sp = statePath(context.directory);
2366
- const rawState = existsSync17(sp) ? readFileSync18(sp, "utf-8") : "";
1867
+ const rawState = existsSync15(sp) ? readFileSync16(sp, "utf-8") : "";
2367
1868
  const state = rawState ? parseState(rawState) : {};
2368
1869
  const stateVersion = typeof state.summaryVersion === "number" ? state.summaryVersion : 0;
2369
1870
  const indexVersion = typeof index.summaryVersion === "number" ? index.summaryVersion : 0;
@@ -2439,117 +1940,18 @@ Please synthesize these results. Identify areas of agreement, resolve conflicts,
2439
1940
  function persistCouncilResult(directory, payload) {
2440
1941
  try {
2441
1942
  const base = codebaseDir(directory);
2442
- if (!existsSync17(base))
2443
- mkdirSync11(base, { recursive: true });
2444
- const path = join16(base, "COUNCILS.jsonl");
1943
+ if (!existsSync15(base))
1944
+ mkdirSync10(base, { recursive: true });
1945
+ const path = join14(base, "COUNCILS.jsonl");
2445
1946
  appendFileSync3(path, JSON.stringify(payload) + `
2446
1947
  `, "utf-8");
2447
1948
  } catch {}
2448
1949
  }
2449
1950
 
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
1951
  // 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";
1952
+ import { tool as tool11 } from "@opencode-ai/plugin";
1953
+ import { existsSync as existsSync16, readFileSync as readFileSync17 } from "fs";
1954
+ import { join as join15 } from "path";
2553
1955
  var MAX_ARTIFACT_BYTES = 4000;
2554
1956
  function tail(text, maxBytes) {
2555
1957
  if (text.length <= maxBytes)
@@ -2557,10 +1959,10 @@ function tail(text, maxBytes) {
2557
1959
  return `... (truncated) ...
2558
1960
  ` + text.slice(-maxBytes);
2559
1961
  }
2560
- var reflectTool = tool15({
1962
+ var reflectTool = tool11({
2561
1963
  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
1964
  args: {
2563
- scope: tool15.schema.enum(["session", "project"]).optional().describe("'session' (default) uses only recent artifacts; 'project' includes all historical data")
1965
+ scope: tool11.schema.enum(["session", "project"]).optional().describe("'session' (default) uses only recent artifacts; 'project' includes all historical data")
2564
1966
  },
2565
1967
  async execute(args, context) {
2566
1968
  const root = context.directory;
@@ -2578,11 +1980,11 @@ var reflectTool = tool15({
2578
1980
  ];
2579
1981
  let found = 0;
2580
1982
  for (const [rel, label] of ARTIFACT_PATHS) {
2581
- const full = join19(root, rel);
2582
- if (!existsSync20(full))
1983
+ const full = join15(root, rel);
1984
+ if (!existsSync16(full))
2583
1985
  continue;
2584
1986
  try {
2585
- const raw = readFileSync20(full, "utf-8").trim();
1987
+ const raw = readFileSync17(full, "utf-8").trim();
2586
1988
  if (!raw)
2587
1989
  continue;
2588
1990
  const count = raw.split(`
@@ -2595,23 +1997,23 @@ var reflectTool = tool15({
2595
1997
  return `No FlowDeck artifacts found under .codebase/.
2596
1998
  ` + "Run some tasks first so decisions, telemetry, and failures are recorded.";
2597
1999
  }
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");
2000
+ 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
2001
  return sections.join(`
2600
2002
  `);
2601
2003
  }
2602
2004
  });
2603
2005
 
2604
2006
  // src/tools/codegraph-tool.ts
2605
- import { tool as tool16 } from "@opencode-ai/plugin";
2007
+ import { tool as tool12 } from "@opencode-ai/plugin";
2606
2008
 
2607
2009
  // src/services/codegraph.ts
2608
2010
  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";
2011
+ import { existsSync as existsSync17, readFileSync as readFileSync18, writeFileSync as writeFileSync12, mkdirSync as mkdirSync11 } from "fs";
2012
+ import { join as join16 } from "path";
2611
2013
  var CODEGRAPH_META_FILE = "CODEGRAPH.md";
2612
2014
  var MAX_FRESHNESS_MS = 30 * 60 * 1000;
2613
2015
  function metaPath(dir) {
2614
- return join20(codebaseDir(dir), CODEGRAPH_META_FILE);
2016
+ return join16(codebaseDir(dir), CODEGRAPH_META_FILE);
2615
2017
  }
2616
2018
  function isCodegraphInstalled() {
2617
2019
  try {
@@ -2626,11 +2028,11 @@ function isCodegraphInstalled() {
2626
2028
  }
2627
2029
  }
2628
2030
  function isCodegraphIndexed(dir) {
2629
- return existsSync21(join20(dir, ".codegraph", "codegraph.db"));
2031
+ return existsSync17(join16(dir, ".codegraph", "codegraph.db"));
2630
2032
  }
2631
2033
  function readCodegraphMeta(dir) {
2632
2034
  const path = metaPath(dir);
2633
- if (!existsSync21(path)) {
2035
+ if (!existsSync17(path)) {
2634
2036
  return {
2635
2037
  installed: false,
2636
2038
  indexed: false,
@@ -2643,7 +2045,7 @@ function readCodegraphMeta(dir) {
2643
2045
  };
2644
2046
  }
2645
2047
  try {
2646
- const content = readFileSync21(path, "utf-8");
2048
+ const content = readFileSync18(path, "utf-8");
2647
2049
  return parseCodegraphMeta(content);
2648
2050
  } catch {
2649
2051
  return {
@@ -2710,8 +2112,8 @@ function parseCodegraphMeta(content) {
2710
2112
  }
2711
2113
  function writeCodegraphMeta(dir, meta) {
2712
2114
  const base = codebaseDir(dir);
2713
- if (!existsSync21(base))
2714
- mkdirSync13(base, { recursive: true });
2115
+ if (!existsSync17(base))
2116
+ mkdirSync11(base, { recursive: true });
2715
2117
  const lines = [
2716
2118
  "# Codegraph Metadata",
2717
2119
  "",
@@ -2724,7 +2126,7 @@ function writeCodegraphMeta(dir, meta) {
2724
2126
  `**installLog:** ${meta.installLog}`,
2725
2127
  `**indexLog:** ${meta.indexLog}`
2726
2128
  ];
2727
- writeFileSync16(metaPath(dir), lines.join(`
2129
+ writeFileSync12(metaPath(dir), lines.join(`
2728
2130
  `), "utf-8");
2729
2131
  }
2730
2132
  function isCodegraphFresh(dir, maxAgeMs = MAX_FRESHNESS_MS) {
@@ -2935,11 +2337,11 @@ function markCodegraphStale(dir) {
2935
2337
  }
2936
2338
 
2937
2339
  // src/tools/codegraph-tool.ts
2938
- var codegraphTool = tool16({
2340
+ var codegraphTool = tool12({
2939
2341
  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
2342
  args: {
2941
- action: tool16.schema.enum(["check", "install", "init", "refresh", "status", "mark-stale"]),
2942
- agent: tool16.schema.string().optional()
2343
+ action: tool12.schema.enum(["check", "install", "init", "refresh", "status", "mark-stale"]),
2344
+ agent: tool12.schema.string().optional()
2943
2345
  },
2944
2346
  async execute(args, context) {
2945
2347
  const dir = context.directory ?? process.cwd();
@@ -3028,21 +2430,21 @@ var codegraphTool = tool16({
3028
2430
  });
3029
2431
 
3030
2432
  // 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");
2433
+ import { tool as tool13 } from "@opencode-ai/plugin";
2434
+ import { existsSync as existsSync18, readFileSync as readFileSync19 } from "fs";
2435
+ import { join as join17, dirname as dirname2 } from "path";
2436
+ import { fileURLToPath } from "url";
2437
+ var RULES_DIR = join17(dirname2(fileURLToPath(import.meta.url)), "..", "rules");
3036
2438
  var _loadedPaths = new Set;
3037
- var loadRulesTool = tool17({
2439
+ var loadRulesTool = tool13({
3038
2440
  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
2441
  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.")
2442
+ stage: tool13.schema.string().optional().describe("Current workflow stage: discuss | plan | execute | verify | fix-bug | write-docs"),
2443
+ 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)."),
2444
+ 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
2445
  },
3044
2446
  async execute(args) {
3045
- const rulesDir = existsSync22(RULES_DIR) ? RULES_DIR : null;
2447
+ const rulesDir = existsSync18(RULES_DIR) ? RULES_DIR : null;
3046
2448
  if (!rulesDir) {
3047
2449
  return JSON.stringify({
3048
2450
  loaded: [],
@@ -3068,7 +2470,7 @@ var loadRulesTool = tool17({
3068
2470
  continue;
3069
2471
  }
3070
2472
  try {
3071
- const text = readFileSync22(rule.path, "utf-8");
2473
+ const text = readFileSync19(rule.path, "utf-8");
3072
2474
  contents.push(`## ${name}
3073
2475
 
3074
2476
  ${text}`);
@@ -3099,11 +2501,11 @@ ${text}`);
3099
2501
  function ruleShortName(rule) {
3100
2502
  return rule.path.replace(RULES_DIR + "/", "").replace(/\.md$/, "");
3101
2503
  }
3102
- var listRulesTool = tool17({
2504
+ var listRulesTool = tool13({
3103
2505
  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
2506
  args: {},
3105
2507
  async execute() {
3106
- const rulesDir = existsSync22(RULES_DIR) ? RULES_DIR : null;
2508
+ const rulesDir = existsSync18(RULES_DIR) ? RULES_DIR : null;
3107
2509
  if (!rulesDir) {
3108
2510
  return JSON.stringify({ rules: [], error: `Rules directory not found at ${RULES_DIR}` });
3109
2511
  }
@@ -3123,13 +2525,13 @@ var listRulesTool = tool17({
3123
2525
  });
3124
2526
 
3125
2527
  // src/tools/rtk-setup.ts
3126
- import { tool as tool18 } from "@opencode-ai/plugin";
2528
+ import { tool as tool14 } from "@opencode-ai/plugin";
3127
2529
 
3128
2530
  // src/services/rtk-manager.ts
3129
2531
  import { spawnSync as spawnSync2 } from "child_process";
3130
- import { existsSync as existsSync23 } from "fs";
2532
+ import { existsSync as existsSync19 } from "fs";
3131
2533
  import { homedir as homedir2 } from "os";
3132
- import { join as join22 } from "path";
2534
+ import { join as join18 } from "path";
3133
2535
 
3134
2536
  // src/services/rtk-policy.ts
3135
2537
  var SUPPORTED_COMMANDS = new Set([
@@ -3175,7 +2577,7 @@ var INSTALL_INSTRUCTIONS = [
3175
2577
  "After installation, call rtk-setup again to verify detection."
3176
2578
  ].join(`
3177
2579
  `);
3178
- var CANDIDATE_PATHS = [join22(homedir2(), ".local", "bin", "rtk"), "/usr/local/bin/rtk", "/usr/bin/rtk"];
2580
+ var CANDIDATE_PATHS = [join18(homedir2(), ".local", "bin", "rtk"), "/usr/local/bin/rtk", "/usr/bin/rtk"];
3179
2581
  function detectRtk() {
3180
2582
  const fromPath = spawnSync2("rtk", ["--version"], { encoding: "utf-8", timeout: 5000 });
3181
2583
  if (fromPath.status === 0) {
@@ -3184,7 +2586,7 @@ function detectRtk() {
3184
2586
  return { installed: true, binPath: "rtk", version };
3185
2587
  }
3186
2588
  for (const candidate of CANDIDATE_PATHS) {
3187
- if (!existsSync23(candidate))
2589
+ if (!existsSync19(candidate))
3188
2590
  continue;
3189
2591
  const result = spawnSync2(candidate, ["--version"], { encoding: "utf-8", timeout: 5000 });
3190
2592
  if (result.status === 0) {
@@ -3262,7 +2664,7 @@ function getRtkStatus(opts) {
3262
2664
  }
3263
2665
 
3264
2666
  // src/tools/rtk-setup.ts
3265
- var rtkSetupTool = tool18({
2667
+ var rtkSetupTool = tool14({
3266
2668
  description: [
3267
2669
  "Detect, initialize, and report status of rtk (output compression proxy for CLI commands).",
3268
2670
  "rtk reduces noisy CLI output (git, npm, test runners, linters, docker) by 60-90%.",
@@ -3270,7 +2672,7 @@ var rtkSetupTool = tool18({
3270
2672
  "When RTK_INSTALLED=true in the environment, use `$RTK_BIN git status` for compressed output."
3271
2673
  ].join(" "),
3272
2674
  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.")
2675
+ 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
2676
  },
3275
2677
  async execute(args) {
3276
2678
  const action = args.action ?? "status";
@@ -3310,15 +2712,15 @@ var rtkSetupTool = tool18({
3310
2712
  });
3311
2713
 
3312
2714
  // src/hooks/guard-rails.ts
3313
- import { existsSync as existsSync24, readFileSync as readFileSync23 } from "fs";
3314
- import { join as join23 } from "path";
2715
+ import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
2716
+ import { join as join19 } from "path";
3315
2717
  var PLANNING_DIR2 = ".planning";
3316
2718
  var CONFIG_FILE = "config.json";
3317
2719
  var STATE_FILE2 = "STATE.md";
3318
2720
  function resolveExecutionMode(configPath, trustScore, volatility) {
3319
- if (existsSync24(configPath)) {
2721
+ if (existsSync20(configPath)) {
3320
2722
  try {
3321
- const config = JSON.parse(readFileSync23(configPath, "utf-8"));
2723
+ const config = JSON.parse(readFileSync20(configPath, "utf-8"));
3322
2724
  if (config.execution_mode === "review-only")
3323
2725
  return "review-only";
3324
2726
  if (config.execution_mode === "guarded")
@@ -3372,22 +2774,22 @@ async function guardRailsHook(ctx, input, _output) {
3372
2774
  if (!ENABLED)
3373
2775
  return;
3374
2776
  const dir = ctx.directory;
3375
- const planningDirPath = join23(dir, PLANNING_DIR2);
2777
+ const planningDirPath = join19(dir, PLANNING_DIR2);
3376
2778
  const codebaseDirectory = codebaseDir(dir);
3377
- const configPath = join23(planningDirPath, CONFIG_FILE);
3378
- const statePath2 = join23(planningDirPath, STATE_FILE2);
2779
+ const configPath = join19(planningDirPath, CONFIG_FILE);
2780
+ const statePath2 = join19(planningDirPath, STATE_FILE2);
3379
2781
  const workspaceRoot = findWorkspaceRoot(dir);
3380
2782
  if (workspaceRoot && dir !== workspaceRoot) {
3381
2783
  const config = getWorkspaceConfig(dir);
3382
- if (config && config.workspace_mode === "shared" && !existsSync24(planningDirPath)) {
2784
+ if (config && config.workspace_mode === "shared" && !existsSync20(planningDirPath)) {
3383
2785
  const msg = `No .planning/ in this sub-repo. Switch to workspace root: cd ${workspaceRoot}`;
3384
2786
  throw new Error(`[flowdeck] BLOCK: ${msg}`);
3385
2787
  }
3386
2788
  }
3387
2789
  if (input.tool === "write" || input.tool === "edit") {
3388
- if (!existsSync24(planningDirPath))
2790
+ if (!existsSync20(planningDirPath))
3389
2791
  return;
3390
- if (!existsSync24(codebaseDirectory)) {
2792
+ if (!existsSync20(codebaseDirectory)) {
3391
2793
  throw new Error(`[flowdeck] WARNING: .codebase/ not found. Run /fd-map-codebase to map the codebase.`);
3392
2794
  }
3393
2795
  const execMode = resolveExecutionMode(configPath, null);
@@ -3443,15 +2845,15 @@ function getDesignGateMessage(dir) {
3443
2845
  }
3444
2846
  function planSuggestsUiHeavy(dir, phase) {
3445
2847
  const planPath = phasePlanPath(dir, phase);
3446
- if (!existsSync24(planPath))
2848
+ if (!existsSync20(planPath))
3447
2849
  return false;
3448
- const planContent = readFileSync23(planPath, "utf-8");
2850
+ const planContent = readFileSync20(planPath, "utf-8");
3449
2851
  return isUiHeavyTask(planContent);
3450
2852
  }
3451
2853
  function effectiveSeverity(configPath, statePath2) {
3452
- if (existsSync24(configPath)) {
2854
+ if (existsSync20(configPath)) {
3453
2855
  try {
3454
- const configContent = readFileSync23(configPath, "utf-8");
2856
+ const configContent = readFileSync20(configPath, "utf-8");
3455
2857
  const config = JSON.parse(configContent);
3456
2858
  if (config.guard_enforcement === "warn")
3457
2859
  return "warn";
@@ -3467,10 +2869,10 @@ function getEffectiveSeverity(configPath, statePath2) {
3467
2869
  return effectiveSeverity(configPath, statePath2);
3468
2870
  }
3469
2871
  function getPlanConfirmed(statePath2) {
3470
- if (!existsSync24(statePath2))
2872
+ if (!existsSync20(statePath2))
3471
2873
  return false;
3472
2874
  try {
3473
- const content = readFileSync23(statePath2, "utf-8");
2875
+ const content = readFileSync20(statePath2, "utf-8");
3474
2876
  const match = content.match(/plan_confirmed:\s*(true|false)/i);
3475
2877
  return match ? match[1].toLowerCase() === "true" : false;
3476
2878
  } catch {
@@ -3478,32 +2880,32 @@ function getPlanConfirmed(statePath2) {
3478
2880
  }
3479
2881
  }
3480
2882
  function getWarningMessage(planningDir2) {
3481
- if (!existsSync24(join23(planningDir2, STATE_FILE2))) {
2883
+ if (!existsSync20(join19(planningDir2, STATE_FILE2))) {
3482
2884
  return "No STATE.md found. Run /fd-map-codebase then /fd-new-feature to start a feature.";
3483
2885
  }
3484
2886
  return "Plan not confirmed. Run /fd-plan and confirm to enable execution.";
3485
2887
  }
3486
2888
  function getBlockMessage(planningDir2) {
3487
- if (!existsSync24(join23(planningDir2, STATE_FILE2))) {
2889
+ if (!existsSync20(join19(planningDir2, STATE_FILE2))) {
3488
2890
  return "No STATE.md found. Run /fd-map-codebase then /fd-new-feature to start a feature.";
3489
2891
  }
3490
2892
  return "Plan not confirmed. Run /fd-plan and confirm to enable execution.";
3491
2893
  }
3492
2894
 
3493
2895
  // src/hooks/tool-guard.ts
3494
- import { existsSync as existsSync25, readFileSync as readFileSync24 } from "fs";
3495
- import { join as join24 } from "path";
2896
+ import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
2897
+ import { join as join20 } from "path";
3496
2898
  var IS_ENABLED = () => process.env.FLOWDECK_TOOL_GUARD_ENABLED === "on";
3497
2899
  var BLOCKED_PATTERNS = {
3498
2900
  read: [".env", ".pem", ".key", ".secret"],
3499
2901
  write: ["node_modules"],
3500
2902
  bash: ["rm -rf"]
3501
2903
  };
3502
- function isBlocked(tool19, args) {
3503
- const patterns = BLOCKED_PATTERNS[tool19];
2904
+ function isBlocked(tool15, args) {
2905
+ const patterns = BLOCKED_PATTERNS[tool15];
3504
2906
  if (!patterns)
3505
2907
  return null;
3506
- if (tool19 === "bash") {
2908
+ if (tool15 === "bash") {
3507
2909
  const cmd = args.command;
3508
2910
  if (!cmd)
3509
2911
  return null;
@@ -3514,7 +2916,7 @@ function isBlocked(tool19, args) {
3514
2916
  }
3515
2917
  return null;
3516
2918
  }
3517
- if (tool19 === "read") {
2919
+ if (tool15 === "read") {
3518
2920
  const filePath = args.filePath;
3519
2921
  if (!filePath)
3520
2922
  return null;
@@ -3525,7 +2927,7 @@ function isBlocked(tool19, args) {
3525
2927
  }
3526
2928
  return null;
3527
2929
  }
3528
- if (tool19 === "write") {
2930
+ if (tool15 === "write") {
3529
2931
  const filePath = args.filePath;
3530
2932
  if (!filePath)
3531
2933
  return null;
@@ -3539,11 +2941,11 @@ function isBlocked(tool19, args) {
3539
2941
  return null;
3540
2942
  }
3541
2943
  function checkArchConstraint(directory, filePath) {
3542
- const constraintsPath = join24(codebaseDir(directory), "CONSTRAINTS.md");
3543
- if (!existsSync25(constraintsPath))
2944
+ const constraintsPath = join20(codebaseDir(directory), "CONSTRAINTS.md");
2945
+ if (!existsSync21(constraintsPath))
3544
2946
  return null;
3545
2947
  try {
3546
- const content = readFileSync24(constraintsPath, "utf-8");
2948
+ const content = readFileSync21(constraintsPath, "utf-8");
3547
2949
  const match = content.match(/## Forbidden Paths\n([\s\S]*?)(?:\n##|$)/);
3548
2950
  if (!match)
3549
2951
  return null;
@@ -3584,9 +2986,9 @@ function isUiDesignApprovalRequired(directory) {
3584
2986
  return !(state.design_stage === "handoff_complete" && state.design_approved);
3585
2987
  }
3586
2988
  const planPath = phasePlanPath(directory, state.phase || 1);
3587
- if (!existsSync25(planPath))
2989
+ if (!existsSync21(planPath))
3588
2990
  return false;
3589
- const planContent = readFileSync24(planPath, "utf-8");
2991
+ const planContent = readFileSync21(planPath, "utf-8");
3590
2992
  if (!isUiHeavyTask(planContent))
3591
2993
  return false;
3592
2994
  return !(state.design_stage === "handoff_complete" && state.design_approved);
@@ -3615,18 +3017,18 @@ async function toolGuardHook(ctx, input, output) {
3615
3017
  }
3616
3018
 
3617
3019
  // src/hooks/session-start.ts
3618
- import { existsSync as existsSync26, readFileSync as readFileSync25 } from "fs";
3020
+ import { existsSync as existsSync22, readFileSync as readFileSync22 } from "fs";
3619
3021
  async function sessionStartHook(ctx) {
3620
3022
  const planningDir2 = ctx.directory + "/.planning";
3621
3023
  const codebaseDirectory = codebaseDir(ctx.directory);
3622
3024
  const workspaceRoot = findWorkspaceRoot(ctx.directory);
3623
3025
  const config = workspaceRoot ? getWorkspaceConfig(ctx.directory) : null;
3624
- if (!existsSync26(planningDir2)) {
3026
+ if (!existsSync22(planningDir2)) {
3625
3027
  return {
3626
3028
  flowdeck_phase: null,
3627
3029
  flowdeck_status: "no_plan",
3628
3030
  flowdeck_warning: "Run /fd-map-codebase to index the codebase, then /fd-new-feature to start a feature.",
3629
- flowdeck_has_codebase: existsSync26(codebaseDirectory),
3031
+ flowdeck_has_codebase: existsSync22(codebaseDirectory),
3630
3032
  ...workspaceRoot && config?.sub_repos ? {
3631
3033
  flowdeck_workspace_root: workspaceRoot,
3632
3034
  flowdeck_sub_repos: config.sub_repos,
@@ -3637,7 +3039,7 @@ async function sessionStartHook(ctx) {
3637
3039
  }
3638
3040
  try {
3639
3041
  const stateFilePath = statePath(ctx.directory);
3640
- const content = readFileSync25(stateFilePath, "utf-8");
3042
+ const content = readFileSync22(stateFilePath, "utf-8");
3641
3043
  const state = parseState(content);
3642
3044
  const currentPhase = state["current_phase"] || {};
3643
3045
  const result = {
@@ -3645,7 +3047,7 @@ async function sessionStartHook(ctx) {
3645
3047
  flowdeck_status: currentPhase["status"] ?? null,
3646
3048
  flowdeck_steps_pending: currentPhase["steps_pending"] ?? null,
3647
3049
  flowdeck_last_action: currentPhase["last_action"] ?? null,
3648
- flowdeck_has_codebase: existsSync26(codebaseDirectory)
3050
+ flowdeck_has_codebase: existsSync22(codebaseDirectory)
3649
3051
  };
3650
3052
  if (workspaceRoot && config?.sub_repos && config.sub_repos.length > 0) {
3651
3053
  result.flowdeck_workspace_root = workspaceRoot;
@@ -3660,7 +3062,7 @@ async function sessionStartHook(ctx) {
3660
3062
  flowdeck_phase: null,
3661
3063
  flowdeck_status: "error",
3662
3064
  flowdeck_warning: "State file unreadable. Continuing without flowdeck context.",
3663
- flowdeck_has_codebase: existsSync26(codebaseDirectory)
3065
+ flowdeck_has_codebase: existsSync22(codebaseDirectory)
3664
3066
  };
3665
3067
  if (workspaceRoot && config?.sub_repos && config.sub_repos.length > 0) {
3666
3068
  result.flowdeck_workspace_root = workspaceRoot;
@@ -3690,7 +3092,7 @@ var COMPLETION_COMMANDS = new Set([
3690
3092
  "execute",
3691
3093
  "verify"
3692
3094
  ]);
3693
- function normalizeCommandName2(raw) {
3095
+ function normalizeCommandName(raw) {
3694
3096
  return raw.replace(/^\//, "").replace(/^fd-/, "");
3695
3097
  }
3696
3098
  function notify(title, body, level = "info") {
@@ -3735,7 +3137,7 @@ class NotificationController {
3735
3137
  this.log = log;
3736
3138
  }
3737
3139
  onCommandExecuted(rawCommand) {
3738
- const name = normalizeCommandName2(rawCommand);
3140
+ const name = normalizeCommandName(rawCommand);
3739
3141
  if (!INTERACTIVE_COMMANDS.has(name) && !COMPLETION_COMMANDS.has(name)) {
3740
3142
  this.log(`[notify] command.executed: "${name}" — not a tracked command, skipping`);
3741
3143
  return;
@@ -3799,13 +3201,13 @@ class NotificationController {
3799
3201
  return this.lastNotifiedKey;
3800
3202
  }
3801
3203
  }
3802
- function notifyPermissionNeeded(tool19) {
3803
- notify("FlowDeck Permission Required", `Agent needs approval to use tool: ${tool19}`, "critical");
3204
+ function notifyPermissionNeeded(tool15) {
3205
+ notify("FlowDeck Permission Required", `Agent needs approval to use tool: ${tool15}`, "critical");
3804
3206
  }
3805
3207
 
3806
3208
  // src/hooks/patch-trust.ts
3807
- import { existsSync as existsSync27, readFileSync as readFileSync26 } from "fs";
3808
- import { join as join25 } from "path";
3209
+ import { existsSync as existsSync23, readFileSync as readFileSync23 } from "fs";
3210
+ import { join as join21 } from "path";
3809
3211
  var HIGH_RISK_KEYWORDS = [
3810
3212
  "password",
3811
3213
  "secret",
@@ -3826,26 +3228,12 @@ var HIGH_RISK_KEYWORDS = [
3826
3228
  "root",
3827
3229
  "privilege"
3828
3230
  ];
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
3231
  function loadFailedPaths(directory) {
3844
- const p = join25(codebaseDir(directory), "FAILURES.json");
3845
- if (!existsSync27(p))
3232
+ const p = join21(codebaseDir(directory), "FAILURES.json");
3233
+ if (!existsSync23(p))
3846
3234
  return [];
3847
3235
  try {
3848
- const data = JSON.parse(readFileSync26(p, "utf-8"));
3236
+ const data = JSON.parse(readFileSync23(p, "utf-8"));
3849
3237
  return (data.entries ?? []).flatMap((e) => e.affected_paths ?? []);
3850
3238
  } catch {
3851
3239
  return [];
@@ -3854,18 +3242,6 @@ function loadFailedPaths(directory) {
3854
3242
  function scorePatch(directory, filePath, content) {
3855
3243
  let score = 100;
3856
3244
  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
3245
  const failedPaths = loadFailedPaths(directory);
3870
3246
  if (failedPaths.some((p) => filePath.includes(p))) {
3871
3247
  score -= 20;
@@ -3910,8 +3286,8 @@ async function patchTrustHook(ctx, input, output) {
3910
3286
  }
3911
3287
 
3912
3288
  // 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";
3289
+ import { existsSync as existsSync24, mkdirSync as mkdirSync12, appendFileSync as appendFileSync4 } from "fs";
3290
+ import { join as join22 } from "path";
3915
3291
  async function decisionTraceHook(ctx, input, output) {
3916
3292
  if (input.tool !== "write" && input.tool !== "edit")
3917
3293
  return;
@@ -3920,8 +3296,8 @@ async function decisionTraceHook(ctx, input, output) {
3920
3296
  return;
3921
3297
  const base = codebaseDir(ctx.directory);
3922
3298
  try {
3923
- if (!existsSync28(base))
3924
- mkdirSync14(base, { recursive: true });
3299
+ if (!existsSync24(base))
3300
+ mkdirSync12(base, { recursive: true });
3925
3301
  const entry = {
3926
3302
  timestamp: new Date().toISOString(),
3927
3303
  file_path: filePath,
@@ -3933,164 +3309,14 @@ async function decisionTraceHook(ctx, input, output) {
3933
3309
  risk_level: "unknown",
3934
3310
  auto_recorded: true
3935
3311
  };
3936
- appendFileSync4(join26(base, "DECISIONS.jsonl"), JSON.stringify(entry) + `
3312
+ appendFileSync4(join22(base, "DECISIONS.jsonl"), JSON.stringify(entry) + `
3937
3313
  `, "utf-8");
3938
3314
  } catch {}
3939
3315
  }
3940
3316
 
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
3317
  // 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";
3318
+ import { existsSync as existsSync25, readFileSync as readFileSync24, writeFileSync as writeFileSync13, mkdirSync as mkdirSync13 } from "fs";
3319
+ import { join as join23 } from "path";
4094
3320
  var APPROVAL_TTL_MS = 30 * 60 * 1000;
4095
3321
  var SENSITIVE_PATTERNS = [
4096
3322
  /auth/i,
@@ -4127,14 +3353,14 @@ function isSensitivePath(filePath) {
4127
3353
  return SENSITIVE_PATTERNS.some((p) => p.test(filePath));
4128
3354
  }
4129
3355
  function approvalsPath(dir) {
4130
- return join28(codebaseDir(dir), "APPROVALS.json");
3356
+ return join23(codebaseDir(dir), "APPROVALS.json");
4131
3357
  }
4132
3358
  function loadStore2(dir) {
4133
3359
  const p = approvalsPath(dir);
4134
- if (!existsSync30(p))
3360
+ if (!existsSync25(p))
4135
3361
  return { requests: [] };
4136
3362
  try {
4137
- return JSON.parse(readFileSync28(p, "utf-8"));
3363
+ return JSON.parse(readFileSync24(p, "utf-8"));
4138
3364
  } catch {
4139
3365
  return { requests: [] };
4140
3366
  }
@@ -4152,8 +3378,8 @@ async function approvalHook(context, toolInput, output) {
4152
3378
  if (!ENABLED2)
4153
3379
  return;
4154
3380
  const dir = context.directory ?? process.cwd();
4155
- const tool19 = toolInput.name ?? toolInput.tool ?? "";
4156
- if (!WRITE_TOOLS.has(tool19))
3381
+ const tool15 = toolInput.name ?? toolInput.tool ?? "";
3382
+ if (!WRITE_TOOLS.has(tool15))
4157
3383
  return;
4158
3384
  const args = output.args ?? {};
4159
3385
  const filePath = String(args.path ?? args.file_path ?? args.filename ?? "");
@@ -4164,20 +3390,305 @@ async function approvalHook(context, toolInput, output) {
4164
3390
  const approval = checkApproval(dir, filePath, "");
4165
3391
  if (approval)
4166
3392
  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
3393
  throw new Error(`APPROVAL_REQUIRED: "${filePath}" is a sensitive file (auth/payment/secrets/infra).
4177
3394
  ` + `Risk level: HIGH — manual approval needed before editing.
4178
3395
  ` + `To proceed: run /fd-guarded-edit --file "${filePath}" to review and approve this change.`);
4179
3396
  }
4180
3397
 
3398
+ // src/services/event-logger.ts
3399
+ import { existsSync as existsSync26, mkdirSync as mkdirSync14, appendFileSync as appendFileSync5, readFileSync as readFileSync25, writeFileSync as writeFileSync14, renameSync, unlinkSync, statSync as statSync2 } from "fs";
3400
+ import { join as join24, resolve as resolve2, sep } from "path";
3401
+ var SENSITIVE_KEYS = [
3402
+ "password",
3403
+ "token",
3404
+ "apikey",
3405
+ "api_key",
3406
+ "secret",
3407
+ "authorization",
3408
+ "auth",
3409
+ "key",
3410
+ "credential",
3411
+ "privatekey",
3412
+ "private_key",
3413
+ "accesstoken",
3414
+ "access_token",
3415
+ "refreshtoken",
3416
+ "refresh_token"
3417
+ ];
3418
+ var currentAgent = null;
3419
+ function getCurrentAgent() {
3420
+ return currentAgent;
3421
+ }
3422
+ function setCurrentAgent(agent) {
3423
+ currentAgent = agent;
3424
+ }
3425
+ function sanitizeArgs(args) {
3426
+ if (!args || typeof args !== "object")
3427
+ return {};
3428
+ const result = {};
3429
+ for (const [key, value] of Object.entries(args)) {
3430
+ const lowerKey = key.toLowerCase();
3431
+ if (SENSITIVE_KEYS.some((sk) => lowerKey.includes(sk))) {
3432
+ result[key] = "[REDACTED]";
3433
+ } else if (key === "content" || key === "newString" || key === "oldString" || key === "template") {
3434
+ if (typeof value === "string" && value.length > 100) {
3435
+ result[key] = `[${value.length} chars truncated]`;
3436
+ } else {
3437
+ result[key] = value;
3438
+ }
3439
+ } else {
3440
+ result[key] = value;
3441
+ }
3442
+ }
3443
+ return result;
3444
+ }
3445
+ function isValidDirectory(directory) {
3446
+ const normalized = resolve2(directory);
3447
+ if (normalized !== directory && !directory.startsWith(sep)) {
3448
+ return false;
3449
+ }
3450
+ if (directory.includes("..") || directory.includes(".." + sep)) {
3451
+ return false;
3452
+ }
3453
+ try {
3454
+ const stats = statSync2(directory);
3455
+ return stats.isDirectory();
3456
+ } catch {
3457
+ return false;
3458
+ }
3459
+ }
3460
+ function logEvent(directory, event) {
3461
+ if (process.env.FLOWDECK_EVENT_LOG === "off")
3462
+ return;
3463
+ if (!isValidDirectory(directory)) {
3464
+ process.stderr.write(`[FlowDeck] Invalid log directory: ${directory}
3465
+ `);
3466
+ return;
3467
+ }
3468
+ const logDir = join24(directory, ".opencode");
3469
+ const logPath = join24(logDir, "flowdeck-events.jsonl");
3470
+ try {
3471
+ if (!existsSync26(logDir)) {
3472
+ mkdirSync14(logDir, { recursive: true });
3473
+ }
3474
+ appendFileSync5(logPath, JSON.stringify(event) + `
3475
+ `, "utf-8");
3476
+ rotateLogFile(logPath);
3477
+ const line = formatEventForStderr(event);
3478
+ process.stderr.write(line + `
3479
+ `);
3480
+ } catch {}
3481
+ }
3482
+ function rotateLogFile(logPath) {
3483
+ try {
3484
+ const stats = statSync2(logPath);
3485
+ if (stats.size < 5000)
3486
+ return;
3487
+ const content = readFileSync25(logPath, "utf-8");
3488
+ const lines = content.split(`
3489
+ `).filter((l) => l.trim());
3490
+ if (lines.length > 1000) {
3491
+ const backupPath = logPath + ".backup";
3492
+ renameSync(logPath, backupPath);
3493
+ const keep = lines.slice(-1000);
3494
+ writeFileSync14(logPath, keep.join(`
3495
+ `) + `
3496
+ `, "utf-8");
3497
+ try {
3498
+ unlinkSync(backupPath);
3499
+ } catch {}
3500
+ }
3501
+ } catch {}
3502
+ }
3503
+ function formatEventForStderr(event) {
3504
+ const time = event.timestamp.slice(11, 23);
3505
+ const agent = event.agent ?? "unknown";
3506
+ const dim = "\x1B[2m";
3507
+ const reset = "\x1B[0m";
3508
+ const cyan = "\x1B[36m";
3509
+ switch (event.type) {
3510
+ case "tool.before": {
3511
+ let icon;
3512
+ if (event.tool === "write" || event.tool === "edit")
3513
+ icon = "✏️ ";
3514
+ else if (event.tool === "read")
3515
+ icon = "\uD83D\uDD0D";
3516
+ else if (event.tool === "bash" || event.tool === "shell")
3517
+ icon = "\uD83C\uDFC3";
3518
+ else if (event.tool === "delegate")
3519
+ icon = "\uD83E\uDD16";
3520
+ else
3521
+ icon = "\uD83D\uDD27";
3522
+ const argStr = formatArgs(event.args);
3523
+ const thinking = event.thinking ? ` "${event.thinking}"` : "";
3524
+ return `${dim}[${time}]${reset} ${icon} ${cyan}${agent}${reset} → ${event.tool}(${argStr})${thinking}`;
3525
+ }
3526
+ case "tool.after": {
3527
+ let icon;
3528
+ let statusColor;
3529
+ if (event.status === "success") {
3530
+ icon = "✅";
3531
+ statusColor = "\x1B[32m";
3532
+ } else if (event.status === "error") {
3533
+ icon = "❌";
3534
+ statusColor = "\x1B[31m";
3535
+ } else if (event.status === "blocked") {
3536
+ icon = "⛔";
3537
+ statusColor = "\x1B[33m";
3538
+ } else {
3539
+ icon = "✅";
3540
+ statusColor = "\x1B[32m";
3541
+ }
3542
+ const argStr = formatArgs(event.args);
3543
+ const duration = event.duration_ms ? ` done in ${event.duration_ms}ms` : "";
3544
+ const error = event.error ? ` error: ${event.error}` : "";
3545
+ return `${dim}[${time}]${reset} ${icon} ${cyan}${agent}${reset} → ${event.tool}(${argStr})${statusColor}${duration}${error}${reset}`;
3546
+ }
3547
+ case "agent.delegated": {
3548
+ const thinking = event.thinking ? ` "${event.thinking}"` : "";
3549
+ return `${dim}[${time}]${reset} \uD83E\uDD16 ${cyan}${agent}${reset} → delegate(${thinking})`;
3550
+ }
3551
+ case "session.created":
3552
+ return `${dim}[${time}]${reset} \uD83D\uDCC2 session created${event.session_id ? ` (${event.session_id})` : ""}`;
3553
+ case "session.idle":
3554
+ return `${dim}[${time}]${reset} \uD83D\uDCA4 session idle${event.session_id ? ` (${event.session_id})` : ""}`;
3555
+ case "session.error":
3556
+ return `${dim}[${time}]${reset} ❌ session error${event.error ? `: ${event.error}` : ""}`;
3557
+ default:
3558
+ return `${dim}[${time}]${reset} ${event.type}`;
3559
+ }
3560
+ }
3561
+ function formatArgs(args) {
3562
+ if (!args)
3563
+ return "";
3564
+ const parts = [];
3565
+ for (const [key, value] of Object.entries(args)) {
3566
+ if (key === "filePath" || key === "path" || key === "file") {
3567
+ parts.push(String(value));
3568
+ } else if (key === "agent") {
3569
+ parts.push(`@${String(value)}`);
3570
+ }
3571
+ }
3572
+ return parts.join(", ");
3573
+ }
3574
+
3575
+ // src/hooks/event-log-hook.ts
3576
+ var toolStartTimes = new Map;
3577
+ var staleThresholdMs = 5 * 60 * 1000;
3578
+ var CLEANUP_INTERVAL = 50;
3579
+ var beforeHookCallCount = 0;
3580
+ function cleanupStaleToolStartTimes() {
3581
+ const now = Date.now();
3582
+ for (const [key, startTime] of toolStartTimes.entries()) {
3583
+ if (now - startTime > staleThresholdMs) {
3584
+ toolStartTimes.delete(key);
3585
+ }
3586
+ }
3587
+ }
3588
+ async function eventLogBeforeHook(ctx, toolInput, toolOutput) {
3589
+ const toolName = toolInput.tool ?? toolInput.name ?? "unknown";
3590
+ const sessionId = toolInput.sessionID ?? toolInput.sessionId ?? "unknown";
3591
+ const args = toolOutput?.args ?? toolInput?.args ?? {};
3592
+ const startKey = `${sessionId}:${toolName}`;
3593
+ beforeHookCallCount++;
3594
+ if (beforeHookCallCount >= CLEANUP_INTERVAL) {
3595
+ beforeHookCallCount = 0;
3596
+ cleanupStaleToolStartTimes();
3597
+ }
3598
+ toolStartTimes.set(startKey, Date.now());
3599
+ const event = {
3600
+ timestamp: new Date().toISOString(),
3601
+ type: "tool.before",
3602
+ agent: getCurrentAgent() ?? undefined,
3603
+ tool: toolName,
3604
+ args: sanitizeArgs(args),
3605
+ session_id: sessionId
3606
+ };
3607
+ logEvent(ctx.directory, event);
3608
+ }
3609
+ async function eventLogAfterHook(ctx, toolInput, toolOutput) {
3610
+ const toolName = toolInput.tool ?? toolInput.name ?? "unknown";
3611
+ const sessionId = toolInput.sessionID ?? toolInput.sessionId ?? "unknown";
3612
+ const args = toolOutput?.args ?? toolInput?.args ?? {};
3613
+ const startKey = `${sessionId}:${toolName}`;
3614
+ const startTime = toolStartTimes.get(startKey);
3615
+ const durationMs = startTime ? Date.now() - startTime : undefined;
3616
+ toolStartTimes.delete(startKey);
3617
+ let status = "success";
3618
+ let error;
3619
+ if (toolOutput?.error != null) {
3620
+ status = "error";
3621
+ error = typeof toolOutput.error === "string" ? toolOutput.error : String(toolOutput.error);
3622
+ } else if (toolOutput?.status === "error") {
3623
+ status = "error";
3624
+ error = typeof toolOutput.error === "string" ? toolOutput.error : "Unknown error";
3625
+ } else if (toolOutput?.status === "blocked") {
3626
+ status = "blocked";
3627
+ }
3628
+ const event = {
3629
+ timestamp: new Date().toISOString(),
3630
+ type: "tool.after",
3631
+ agent: getCurrentAgent() ?? undefined,
3632
+ tool: toolName,
3633
+ args: sanitizeArgs(args),
3634
+ duration_ms: durationMs,
3635
+ status,
3636
+ error,
3637
+ session_id: sessionId
3638
+ };
3639
+ logEvent(ctx.directory, event);
3640
+ }
3641
+ async function eventLogSessionHook(ctx, event) {
3642
+ const type = event?.type ?? "";
3643
+ const props = event?.properties ?? {};
3644
+ if (type === "session.created") {
3645
+ if (props.parentID) {
3646
+ const agentName = extractAgentFromEvent(props);
3647
+ setCurrentAgent(agentName);
3648
+ }
3649
+ const toolEvent = {
3650
+ timestamp: new Date().toISOString(),
3651
+ type: "session.created",
3652
+ session_id: props.id ?? props.sessionId ?? undefined
3653
+ };
3654
+ logEvent(ctx.directory, toolEvent);
3655
+ } else if (type === "session.idle") {
3656
+ if (props.parentID) {
3657
+ setCurrentAgent(null);
3658
+ }
3659
+ const toolEvent = {
3660
+ timestamp: new Date().toISOString(),
3661
+ type: "session.idle",
3662
+ session_id: props.id ?? props.sessionId ?? undefined
3663
+ };
3664
+ logEvent(ctx.directory, toolEvent);
3665
+ } else if (type === "session.error") {
3666
+ if (props.parentID) {
3667
+ setCurrentAgent(null);
3668
+ }
3669
+ const err = props.error;
3670
+ const errorMsg = (err && typeof err === "object" && "message" in err ? String(err.message) : undefined) ?? (typeof err === "string" ? err : undefined) ?? undefined;
3671
+ const toolEvent = {
3672
+ timestamp: new Date().toISOString(),
3673
+ type: "session.error",
3674
+ session_id: props.id ?? props.sessionId ?? undefined,
3675
+ error: errorMsg
3676
+ };
3677
+ logEvent(ctx.directory, toolEvent);
3678
+ }
3679
+ }
3680
+ function extractAgentFromEvent(props) {
3681
+ if (typeof props.agent === "string")
3682
+ return props.agent;
3683
+ if (typeof props.name === "string")
3684
+ return props.name;
3685
+ const title = typeof props.title === "string" ? props.title : "";
3686
+ const match = title.match(/^(.+)-delegate$/);
3687
+ if (match)
3688
+ return match[1];
3689
+ return "unknown";
3690
+ }
3691
+
4181
3692
  // src/hooks/context-window-monitor.ts
4182
3693
  var CONTEXT_WARNING_THRESHOLD = 0.7;
4183
3694
  var DEFAULT_CONTEXT_LIMIT = Number(process.env.FLOWDECK_CONTEXT_LIMIT) || 200000;
@@ -4229,8 +3740,8 @@ function createContextWindowMonitorHook() {
4229
3740
  }
4230
3741
 
4231
3742
  // src/hooks/shell-env-hook.ts
4232
- import { existsSync as existsSync31, readFileSync as readFileSync29 } from "fs";
4233
- import { join as join29 } from "path";
3743
+ import { existsSync as existsSync27, readFileSync as readFileSync26 } from "fs";
3744
+ import { join as join25 } from "path";
4234
3745
  import { createRequire as createRequire2 } from "module";
4235
3746
  var _version;
4236
3747
  function getVersion() {
@@ -4266,7 +3777,7 @@ var MARKER_TO_LANG = {
4266
3777
  };
4267
3778
  function detectPackageManager(root) {
4268
3779
  for (const [lockfile, pm] of Object.entries(LOCKFILE_TO_PM)) {
4269
- if (existsSync31(join29(root, lockfile)))
3780
+ if (existsSync27(join25(root, lockfile)))
4270
3781
  return pm;
4271
3782
  }
4272
3783
  return;
@@ -4275,7 +3786,7 @@ function detectLanguages(root) {
4275
3786
  const langs = [];
4276
3787
  const seen = new Set;
4277
3788
  for (const [marker, lang] of Object.entries(MARKER_TO_LANG)) {
4278
- if (!seen.has(lang) && existsSync31(join29(root, marker))) {
3789
+ if (!seen.has(lang) && existsSync27(join25(root, marker))) {
4279
3790
  langs.push(lang);
4280
3791
  seen.add(lang);
4281
3792
  }
@@ -4283,11 +3794,11 @@ function detectLanguages(root) {
4283
3794
  return langs;
4284
3795
  }
4285
3796
  function readCurrentPhase(root) {
4286
- const statePath2 = join29(root, ".planning", "STATE.md");
4287
- if (!existsSync31(statePath2))
3797
+ const statePath2 = join25(root, ".planning", "STATE.md");
3798
+ if (!existsSync27(statePath2))
4288
3799
  return;
4289
3800
  try {
4290
- const content = readFileSync29(statePath2, "utf-8");
3801
+ const content = readFileSync26(statePath2, "utf-8");
4291
3802
  const match = content.match(/phase:\s*(\S+)/i);
4292
3803
  return match?.[1];
4293
3804
  } catch {
@@ -4412,8 +3923,8 @@ function createSessionIdleHook(client, tracker) {
4412
3923
  }
4413
3924
 
4414
3925
  // src/hooks/compaction-hook.ts
4415
- import { existsSync as existsSync32, readFileSync as readFileSync30 } from "fs";
4416
- import { join as join30 } from "path";
3926
+ import { existsSync as existsSync28, readFileSync as readFileSync27 } from "fs";
3927
+ import { join as join26 } from "path";
4417
3928
  var STRUCTURED_SUMMARY_PROMPT = `
4418
3929
  When summarizing this session, you MUST include the following sections:
4419
3930
 
@@ -4454,10 +3965,10 @@ For each: agent name, status, description, session_id.
4454
3965
  var _lastInjected = new Map;
4455
3966
  function readPlanningState2(directory) {
4456
3967
  const sp = statePath(directory);
4457
- if (!existsSync32(sp))
3968
+ if (!existsSync28(sp))
4458
3969
  return null;
4459
3970
  try {
4460
- const content = readFileSync30(sp, "utf-8");
3971
+ const content = readFileSync27(sp, "utf-8");
4461
3972
  const parsed = parseState(content);
4462
3973
  const version = typeof parsed.summaryVersion === "number" ? parsed.summaryVersion : 0;
4463
3974
  return { content: content.slice(0, 1500), version };
@@ -4486,15 +3997,15 @@ function createCompactionHook(ctx, tracker) {
4486
3997
  sections.push(`_State unchanged since last compaction. summaryVersion=${currentStateVersion}_`);
4487
3998
  sections.push("");
4488
3999
  }
4489
- const indexPath2 = join30(ctx.directory, ".planning", "CODEBASE_INDEX.md");
4490
- if (indexChanged && existsSync32(indexPath2)) {
4000
+ const indexPath2 = join26(ctx.directory, ".planning", "CODEBASE_INDEX.md");
4001
+ if (indexChanged && existsSync28(indexPath2)) {
4491
4002
  try {
4492
- const indexContent = readFileSync30(indexPath2, "utf-8");
4003
+ const indexContent = readFileSync27(indexPath2, "utf-8");
4493
4004
  const indexSummary = "\n## Codebase Index\n```\n" + indexContent.slice(0, 800) + "\n```\n";
4494
4005
  sections.push(indexSummary);
4495
4006
  sections.push("");
4496
4007
  } catch {}
4497
- } else if (existsSync32(indexPath2)) {
4008
+ } else if (existsSync28(indexPath2)) {
4498
4009
  sections.push(`## Codebase Index (unchanged, v${currentIndexVersion})`);
4499
4010
  sections.push(`_Index unchanged since last compaction. summaryVersion=${currentIndexVersion}_`);
4500
4011
  sections.push("");
@@ -4545,12 +4056,9 @@ var ALWAYS_ALLOWED = new Set([
4545
4056
  "council",
4546
4057
  "planning-state",
4547
4058
  "codebase-state",
4548
- "workspace-state",
4549
4059
  "repo-memory",
4550
4060
  "decision-trace",
4551
4061
  "policy-engine",
4552
- "context-generator",
4553
- "create-skill",
4554
4062
  "reflect"
4555
4063
  ]);
4556
4064
  function isDelegationTool(name) {
@@ -4661,7 +4169,7 @@ async function runAutoLearner(client, directory, appLog) {
4661
4169
  parts: [
4662
4170
  {
4663
4171
  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."
4172
+ 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
4173
  }
4666
4174
  ],
4667
4175
  tools: { question: false }
@@ -4895,8 +4403,8 @@ Please advise.
4895
4403
  ## Self-Learning
4896
4404
 
4897
4405
  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
4406
+ 1. After the task completes successfully, write a new skill markdown file under \`src/skills/<name>/SKILL.md\` to capture the pattern
4407
+ 2. Use a descriptive kebab-case name for the directory, a one-sentence description in the frontmatter, and structured Markdown content
4900
4408
  3. Include: When to Activate, Steps, Examples, and Pitfalls sections
4901
4409
 
4902
4410
  Do NOT create a skill for routine tasks. Only capture genuinely novel or reusable patterns.`;
@@ -7042,7 +6550,6 @@ You receive a structured context with:
7042
6550
  - \`file_path\`: optional specific file being changed
7043
6551
  - \`trust_score\`: patch trust score (0–100; 80+ = safe, 40–79 = review-required, <40 = high-risk)
7044
6552
  - \`trust_signals\`: list of risk signals from the patch trust scorer
7045
- - \`volatile_zones\`: paths marked as volatile or critical in VOLATILITY.json
7046
6553
  - \`prior_failures\`: failure entries from FAILURES.json that match this change
7047
6554
  - \`regression_categories\`: predicted regression categories for this change
7048
6555
  - \`confidence\`: system confidence score (0–100; based on how much codebase context data exists)
@@ -7501,7 +7008,7 @@ var AUTO_LEARNER_PROMPT = `You run automatically after a coding session to captu
7501
7008
  - Novel solutions that took non-obvious reasoning
7502
7009
  - Recurring tool sequences that indicate a reusable workflow
7503
7010
  - Knowledge gaps that had to be worked out from scratch
7504
- 3. For each valuable pattern, call \`create-skill\` immediately.
7011
+ 3. For each valuable pattern, write a skill markdown file under \`src/skills/<name>/SKILL.md\` immediately.
7505
7012
  4. If nothing is worth capturing, output exactly: "No new skills identified."
7506
7013
  5. End with a one-line summary: "Auto-learn complete: N skill(s) created."
7507
7014
 
@@ -7866,12 +7373,9 @@ var CONTRACTS = [
7866
7373
  "council",
7867
7374
  "planning-state",
7868
7375
  "codebase-state",
7869
- "workspace-state",
7870
7376
  "repo-memory",
7871
7377
  "decision-trace",
7872
7378
  "policy-engine",
7873
- "context-generator",
7874
- "create-skill",
7875
7379
  "reflect"
7876
7380
  ],
7877
7381
  forbiddenActions: [
@@ -7906,7 +7410,7 @@ var CONTRACTS = [
7906
7410
  allowedTaskTypes: ["planning", "task-breakdown", "step-decomposition"],
7907
7411
  requiredInputs: ["task description or STATE.md"],
7908
7412
  expectedOutputFields: ["steps", "phase"],
7909
- allowedTools: ["read", "glob", "grep", "planning-state", "workspace-state"],
7413
+ allowedTools: ["read", "glob", "grep", "planning-state"],
7910
7414
  forbiddenActions: [
7911
7415
  "write source files",
7912
7416
  "run bash commands",
@@ -8422,7 +7926,6 @@ function runSupervisorReview(directory, targetName, ctx = {}, clarificationQuest
8422
7926
  reviewPhase,
8423
7927
  timestamp: timestamp2
8424
7928
  };
8425
- _emitTelemetry(directory, decision2, ctx);
8426
7929
  return decision2;
8427
7930
  }
8428
7931
  const policyResult = targetType === "command" ? checkCommandPolicy(targetName, ctx) : checkAgentPolicy(targetName, ctx);
@@ -8444,7 +7947,6 @@ function runSupervisorReview(directory, targetName, ctx = {}, clarificationQuest
8444
7947
  timestamp: timestamp2,
8445
7948
  ...escalationQuestion ? { clarificationQuestion: escalationQuestion } : {}
8446
7949
  };
8447
- _emitTelemetry(directory, supervisorDecision, ctx);
8448
7950
  return supervisorDecision;
8449
7951
  }
8450
7952
  function shouldProceed(decision, mode, canBlock) {
@@ -8457,33 +7959,12 @@ function shouldProceed(decision, mode, canBlock) {
8457
7959
  }
8458
7960
  return decision.decision !== "block" || decision.confidenceScore > 0.3;
8459
7961
  }
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
7962
 
8482
7963
  // src/index.ts
8483
7964
  function lazyLoadRulePaths(projectRoot) {
8484
- const __dir = dirname5(fileURLToPath3(import.meta.url));
8485
- const rulesDir = join31(__dir, "..", "src", "rules");
8486
- if (!existsSync33(rulesDir))
7965
+ const __dir = dirname3(fileURLToPath2(import.meta.url));
7966
+ const rulesDir = join27(__dir, "..", "src", "rules");
7967
+ if (!existsSync29(rulesDir))
8487
7968
  return { paths: [], diagnostics: "[LazyRuleLoader] rules directory not found" };
8488
7969
  const detectedLanguages = detectProjectLanguages(projectRoot);
8489
7970
  const paths = getStartupRulePaths(rulesDir, detectedLanguages);
@@ -8492,17 +7973,17 @@ function lazyLoadRulePaths(projectRoot) {
8492
7973
  return { paths, diagnostics };
8493
7974
  }
8494
7975
  function loadCommands() {
8495
- const __dir = dirname5(fileURLToPath3(import.meta.url));
8496
- const commandsDir = join31(__dir, "..", "src", "commands");
8497
- if (!existsSync33(commandsDir))
7976
+ const __dir = dirname3(fileURLToPath2(import.meta.url));
7977
+ const commandsDir = join27(__dir, "..", "src", "commands");
7978
+ if (!existsSync29(commandsDir))
8498
7979
  return {};
8499
7980
  const commands = {};
8500
7981
  try {
8501
- for (const file of readdirSync5(commandsDir)) {
7982
+ for (const file of readdirSync4(commandsDir)) {
8502
7983
  if (!file.endsWith(".md"))
8503
7984
  continue;
8504
7985
  const name = basename2(file, ".md");
8505
- const raw = readFileSync31(join31(commandsDir, file), "utf-8");
7986
+ const raw = readFileSync28(join27(commandsDir, file), "utf-8");
8506
7987
  let description;
8507
7988
  let template = raw;
8508
7989
  const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
@@ -8520,10 +8001,8 @@ function loadCommands() {
8520
8001
  var plugin = async (input, _options) => {
8521
8002
  const { directory, client, worktree } = input;
8522
8003
  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);
8004
+ const runPipelineTool = createRunPipelineTool(client);
8005
+ const delegateTool = createDelegateTool(client);
8527
8006
  const councilTool = createCouncilTool(client);
8528
8007
  const fileTracker = new SessionFileTracker;
8529
8008
  const { fileEdited, fileWatcherUpdated } = createFileTrackerHooks(fileTracker);
@@ -8587,8 +8066,8 @@ var plugin = async (input, _options) => {
8587
8066
  }
8588
8067
  }
8589
8068
  }
8590
- const skillsDir = join31(dirname5(fileURLToPath3(import.meta.url)), "..", "src", "skills");
8591
- if (existsSync33(skillsDir)) {
8069
+ const skillsDir = join27(dirname3(fileURLToPath2(import.meta.url)), "..", "src", "skills");
8070
+ if (existsSync29(skillsDir)) {
8592
8071
  const cfgAny = cfg;
8593
8072
  if (!cfgAny.skills || typeof cfgAny.skills !== "object") {
8594
8073
  cfgAny.skills = { paths: [] };
@@ -8617,18 +8096,14 @@ var plugin = async (input, _options) => {
8617
8096
  tool: {
8618
8097
  "planning-state": planningStateTool,
8619
8098
  "codebase-state": codebaseStateTool,
8620
- "workspace-state": workspaceStateTool,
8621
8099
  "run-pipeline": runPipelineTool,
8622
8100
  delegate: delegateTool,
8623
8101
  "repo-memory": repoMemoryTool,
8624
8102
  "failure-replay": failureReplayTool,
8625
8103
  "decision-trace": decisionTraceTool,
8626
- "volatility-map": volatilityMapTool,
8627
8104
  "policy-engine": policyEngineTool,
8628
8105
  "hash-edit": hashEditTool,
8629
8106
  council: councilTool,
8630
- "context-generator": contextGeneratorTool,
8631
- "create-skill": createSkillTool,
8632
8107
  reflect: reflectTool,
8633
8108
  codegraph: codegraphTool,
8634
8109
  "load-rules": loadRulesTool,
@@ -8641,17 +8116,18 @@ var plugin = async (input, _options) => {
8641
8116
  "file.watcher.updated": fileWatcherUpdated,
8642
8117
  "experimental.session.compacting": compactionHook,
8643
8118
  "command.execute.before": async (input2, _output) => {
8644
- activityReporter.reportCommandStarted(input2.command);
8645
8119
  lastExecutedCommand = input2.command;
8646
8120
  },
8647
8121
  "permission.ask": async (input2, _output) => {
8648
8122
  notifyPermissionNeeded(input2.title);
8649
- activityReporter.reportWaitingForApproval(input2.title);
8650
8123
  },
8651
8124
  event: async ({ event }) => {
8652
8125
  const type = event?.type ?? "";
8653
8126
  if (type === "session.created" || type === "session.started") {
8654
8127
  await sessionStartHook({ directory });
8128
+ if (type === "session.created") {
8129
+ await eventLogSessionHook({ directory }, event);
8130
+ }
8655
8131
  }
8656
8132
  if (type === "command.executed") {
8657
8133
  const commandName = event?.properties?.name ?? "";
@@ -8662,9 +8138,9 @@ var plugin = async (input, _options) => {
8662
8138
  await contextMonitor.event({ event });
8663
8139
  orchestratorGuard.onEvent(event);
8664
8140
  if (type === "session.idle") {
8141
+ await eventLogSessionHook({ directory }, event);
8665
8142
  const hasEdits = fileTracker.getEditedPaths().length > 0;
8666
8143
  if (lastExecutedCommand) {
8667
- activityReporter.reportCommandCompleted(lastExecutedCommand, hasEdits);
8668
8144
  lastExecutedCommand = null;
8669
8145
  }
8670
8146
  notifCtrl.onSessionIdle(hasEdits);
@@ -8676,6 +8152,7 @@ var plugin = async (input, _options) => {
8676
8152
  }
8677
8153
  }
8678
8154
  if (type === "session.error") {
8155
+ await eventLogSessionHook({ directory }, event);
8679
8156
  lastExecutedCommand = null;
8680
8157
  const err = event?.properties?.error;
8681
8158
  const errorMsg = (err && typeof err === "object" && "message" in err ? String(err.message) : undefined) ?? (typeof err === "string" ? err : undefined) ?? "An unexpected error occurred";
@@ -8721,15 +8198,15 @@ var plugin = async (input, _options) => {
8721
8198
  }
8722
8199
  }
8723
8200
  }
8724
- await telemetryHook({ directory }, toolInput, toolOutput, activityReporter);
8725
8201
  await approvalHook({ directory }, toolInput, toolOutput);
8726
8202
  await guardRailsHook({ directory }, toolInput, toolOutput);
8727
8203
  await toolGuardHook({ directory }, toolInput, toolOutput);
8728
8204
  await patchTrustHook({ directory }, toolInput, toolOutput);
8729
8205
  await decisionTraceHook({ directory }, toolInput, toolOutput);
8206
+ await eventLogBeforeHook({ directory }, toolInput, toolOutput);
8730
8207
  },
8731
8208
  "tool.execute.after": async (toolInput, toolOutput) => {
8732
- await telemetryAfterHook({ directory }, toolInput, toolOutput, activityReporter);
8209
+ await eventLogAfterHook({ directory }, toolInput, toolOutput);
8733
8210
  const afterToolName = toolInput.tool ?? toolInput.name ?? "";
8734
8211
  if (afterToolName === "delegate" || afterToolName === "run-pipeline") {
8735
8212
  try {