@dv.nghiem/flowdeck 0.4.3 → 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 (139) 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 +634 -823
  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.map +1 -1
  21. package/dist/tools/run-pipeline.d.ts.map +1 -1
  22. package/docs/commands/fd-deploy-check.md +1 -5
  23. package/docs/commands/fd-reflect.md +8 -9
  24. package/docs/commands/fd-suggest.md +3 -4
  25. package/docs/concepts/architecture.md +0 -3
  26. package/docs/concepts/intelligence.md +2 -36
  27. package/docs/skills/index.md +0 -2
  28. package/package.json +2 -2
  29. package/src/commands/fd-deploy-check.md +1 -5
  30. package/src/commands/fd-reflect.md +4 -6
  31. package/src/commands/fd-suggest.md +3 -4
  32. package/src/skills/change-impact-radar/SKILL.md +3 -4
  33. package/src/skills/confidence-aware-planning/SKILL.md +0 -1
  34. package/src/skills/patch-trust-score/SKILL.md +3 -5
  35. package/dist/dashboard/lib/port-finder.test.d.ts +0 -2
  36. package/dist/dashboard/lib/port-finder.test.d.ts.map +0 -1
  37. package/dist/hooks/notifications.test.d.ts +0 -14
  38. package/dist/hooks/notifications.test.d.ts.map +0 -1
  39. package/dist/hooks/patch-trust.test.d.ts +0 -2
  40. package/dist/hooks/patch-trust.test.d.ts.map +0 -1
  41. package/dist/hooks/telemetry-hook.d.ts +0 -32
  42. package/dist/hooks/telemetry-hook.d.ts.map +0 -1
  43. package/dist/hooks/telemetry-hook.test.d.ts +0 -2
  44. package/dist/hooks/telemetry-hook.test.d.ts.map +0 -1
  45. package/dist/hooks/tool-guard.test.d.ts +0 -2
  46. package/dist/hooks/tool-guard.test.d.ts.map +0 -1
  47. package/dist/lib/research-gate.test.d.ts +0 -2
  48. package/dist/lib/research-gate.test.d.ts.map +0 -1
  49. package/dist/services/artifact-store.d.ts +0 -39
  50. package/dist/services/artifact-store.d.ts.map +0 -1
  51. package/dist/services/artifact-store.test.d.ts +0 -2
  52. package/dist/services/artifact-store.test.d.ts.map +0 -1
  53. package/dist/services/codegraph.test.d.ts +0 -2
  54. package/dist/services/codegraph.test.d.ts.map +0 -1
  55. package/dist/services/command-validator.test.d.ts +0 -2
  56. package/dist/services/command-validator.test.d.ts.map +0 -1
  57. package/dist/services/context-assembler.d.ts +0 -29
  58. package/dist/services/context-assembler.d.ts.map +0 -1
  59. package/dist/services/context-assembler.test.d.ts +0 -2
  60. package/dist/services/context-assembler.test.d.ts.map +0 -1
  61. package/dist/services/cost-budget.d.ts +0 -53
  62. package/dist/services/cost-budget.d.ts.map +0 -1
  63. package/dist/services/cost-budget.test.d.ts +0 -2
  64. package/dist/services/cost-budget.test.d.ts.map +0 -1
  65. package/dist/services/cost-estimator.test.d.ts +0 -2
  66. package/dist/services/cost-estimator.test.d.ts.map +0 -1
  67. package/dist/services/draft-verifier.d.ts +0 -48
  68. package/dist/services/draft-verifier.d.ts.map +0 -1
  69. package/dist/services/draft-verifier.test.d.ts +0 -2
  70. package/dist/services/draft-verifier.test.d.ts.map +0 -1
  71. package/dist/services/governance.test.d.ts +0 -11
  72. package/dist/services/governance.test.d.ts.map +0 -1
  73. package/dist/services/index.d.ts +0 -25
  74. package/dist/services/index.d.ts.map +0 -1
  75. package/dist/services/lazy-rule-loader.test.d.ts +0 -23
  76. package/dist/services/lazy-rule-loader.test.d.ts.map +0 -1
  77. package/dist/services/model-router-ext.test.d.ts +0 -2
  78. package/dist/services/model-router-ext.test.d.ts.map +0 -1
  79. package/dist/services/model-router.test.d.ts +0 -2
  80. package/dist/services/model-router.test.d.ts.map +0 -1
  81. package/dist/services/policy-compiler.d.ts +0 -27
  82. package/dist/services/policy-compiler.d.ts.map +0 -1
  83. package/dist/services/preflight-explorer.test.d.ts +0 -25
  84. package/dist/services/preflight-explorer.test.d.ts.map +0 -1
  85. package/dist/services/prompt-cache-ext.test.d.ts +0 -2
  86. package/dist/services/prompt-cache-ext.test.d.ts.map +0 -1
  87. package/dist/services/prompt-cache.test.d.ts +0 -2
  88. package/dist/services/prompt-cache.test.d.ts.map +0 -1
  89. package/dist/services/quick-router.test.d.ts +0 -13
  90. package/dist/services/quick-router.test.d.ts.map +0 -1
  91. package/dist/services/recommended-question.test.d.ts +0 -2
  92. package/dist/services/recommended-question.test.d.ts.map +0 -1
  93. package/dist/services/rtk-manager.test.d.ts +0 -2
  94. package/dist/services/rtk-manager.test.d.ts.map +0 -1
  95. package/dist/services/rtk-policy.test.d.ts +0 -2
  96. package/dist/services/rtk-policy.test.d.ts.map +0 -1
  97. package/dist/services/rule-engine.d.ts +0 -29
  98. package/dist/services/rule-engine.d.ts.map +0 -1
  99. package/dist/services/rule-engine.test.d.ts +0 -2
  100. package/dist/services/rule-engine.test.d.ts.map +0 -1
  101. package/dist/services/services.test.d.ts +0 -2
  102. package/dist/services/services.test.d.ts.map +0 -1
  103. package/dist/services/supervisor.test.d.ts +0 -14
  104. package/dist/services/supervisor.test.d.ts.map +0 -1
  105. package/dist/services/task-batcher.d.ts +0 -48
  106. package/dist/services/task-batcher.d.ts.map +0 -1
  107. package/dist/services/task-batcher.test.d.ts +0 -2
  108. package/dist/services/task-batcher.test.d.ts.map +0 -1
  109. package/dist/services/telemetry.d.ts +0 -40
  110. package/dist/services/telemetry.d.ts.map +0 -1
  111. package/dist/services/token-budget.d.ts +0 -44
  112. package/dist/services/token-budget.d.ts.map +0 -1
  113. package/dist/services/token-budget.test.d.ts +0 -2
  114. package/dist/services/token-budget.test.d.ts.map +0 -1
  115. package/dist/services/token-metrics-ext.test.d.ts +0 -2
  116. package/dist/services/token-metrics-ext.test.d.ts.map +0 -1
  117. package/dist/services/token-metrics.test.d.ts +0 -2
  118. package/dist/services/token-metrics.test.d.ts.map +0 -1
  119. package/dist/tools/agent-dispatch.test.d.ts +0 -2
  120. package/dist/tools/agent-dispatch.test.d.ts.map +0 -1
  121. package/dist/tools/codebase-index.test.d.ts +0 -2
  122. package/dist/tools/codebase-index.test.d.ts.map +0 -1
  123. package/dist/tools/context-generator.d.ts +0 -3
  124. package/dist/tools/context-generator.d.ts.map +0 -1
  125. package/dist/tools/create-skill.d.ts +0 -3
  126. package/dist/tools/create-skill.d.ts.map +0 -1
  127. package/dist/tools/dispatch-routing.test.d.ts +0 -2
  128. package/dist/tools/dispatch-routing.test.d.ts.map +0 -1
  129. package/dist/tools/failure-replay.test.d.ts +0 -2
  130. package/dist/tools/failure-replay.test.d.ts.map +0 -1
  131. package/dist/tools/repo-memory.test.d.ts +0 -2
  132. package/dist/tools/repo-memory.test.d.ts.map +0 -1
  133. package/dist/tools/volatility-map.d.ts +0 -18
  134. package/dist/tools/volatility-map.d.ts.map +0 -1
  135. package/dist/tools/volatility-map.test.d.ts +0 -2
  136. package/dist/tools/volatility-map.test.d.ts.map +0 -1
  137. package/dist/tools/workspace-state.d.ts +0 -3
  138. package/dist/tools/workspace-state.d.ts.map +0 -1
  139. 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}`;
@@ -873,18 +722,18 @@ function extractText(parts) {
873
722
  `);
874
723
  }
875
724
  function createRunPipelineTool(client) {
876
- return tool4({
725
+ return tool3({
877
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.",
878
727
  args: {
879
- steps: tool4.schema.array(tool4.schema.object({
880
- agent: tool4.schema.string(),
881
- prompt: tool4.schema.string(),
882
- 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()
883
732
  })),
884
- initial_context: tool4.schema.string().optional(),
885
- abort_on_failure: tool4.schema.boolean().optional().default(true),
886
- retry_attempts: tool4.schema.number().optional().default(1),
887
- 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()
888
737
  },
889
738
  async execute(args, context) {
890
739
  const startTime = Date.now();
@@ -893,6 +742,7 @@ function createRunPipelineTool(client) {
893
742
  let aborted = false;
894
743
  const retryAttempts = typeof args.retry_attempts === "number" ? args.retry_attempts : 1;
895
744
  const maxRetries = Math.max(0, Math.floor(retryAttempts));
745
+ const totalSteps = args.steps.length;
896
746
  let inflightChildId = null;
897
747
  const abortHandler = () => {
898
748
  if (inflightChildId) {
@@ -904,7 +754,8 @@ function createRunPipelineTool(client) {
904
754
  };
905
755
  context.abort.addEventListener("abort", abortHandler);
906
756
  try {
907
- for (const step of args.steps) {
757
+ for (let stepIdx = 0;stepIdx < args.steps.length; stepIdx++) {
758
+ const step = args.steps[stepIdx];
908
759
  if (context.abort.aborted) {
909
760
  aborted = true;
910
761
  break;
@@ -978,9 +829,10 @@ ${step.prompt}` : step.prompt;
978
829
  } finally {
979
830
  context.abort.removeEventListener("abort", abortHandler);
980
831
  }
832
+ const totalDuration = Date.now() - startTime;
981
833
  return JSON.stringify({
982
834
  steps: trace,
983
- total_duration_ms: Date.now() - startTime,
835
+ total_duration_ms: totalDuration,
984
836
  aborted
985
837
  });
986
838
  }
@@ -988,13 +840,13 @@ ${step.prompt}` : step.prompt;
988
840
  }
989
841
 
990
842
  // src/tools/delegate.ts
991
- import { tool as tool5 } from "@opencode-ai/plugin";
992
- 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";
993
845
 
994
846
  // src/services/prompt-cache.ts
995
847
  import { createHash } from "crypto";
996
- import { existsSync as existsSync7, readFileSync as readFileSync7, writeFileSync as writeFileSync6, readdirSync as readdirSync3, statSync, mkdirSync as mkdirSync3 } from "fs";
997
- 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";
998
850
  var CACHEABLE_AGENTS = new Set([
999
851
  "researcher",
1000
852
  "code-explorer",
@@ -1008,17 +860,17 @@ var CACHE_DIR_NAME = "prompt-cache";
1008
860
  var MAX_CACHE_ENTRIES = 200;
1009
861
  var DEFAULT_TTL_MS = 30 * 60 * 1000;
1010
862
  function cacheDir(dir) {
1011
- return join7(codebaseDir(dir), CACHE_DIR_NAME);
863
+ return join6(codebaseDir(dir), CACHE_DIR_NAME);
1012
864
  }
1013
865
  function entryPath(dir, key) {
1014
- return join7(cacheDir(dir), `${key}.json`);
866
+ return join6(cacheDir(dir), `${key}.json`);
1015
867
  }
1016
868
  function readEntry(dir, key, stateVersion, indexVersion) {
1017
869
  const path = entryPath(dir, key);
1018
- if (!existsSync7(path))
870
+ if (!existsSync6(path))
1019
871
  return null;
1020
872
  try {
1021
- const entry = JSON.parse(readFileSync7(path, "utf-8"));
873
+ const entry = JSON.parse(readFileSync6(path, "utf-8"));
1022
874
  const age = Date.now() - new Date(entry.created_at).getTime();
1023
875
  if (age > entry.ttl_ms)
1024
876
  return null;
@@ -1066,7 +918,7 @@ function setCached(dir, agent, prompt, context, stateVersion, indexVersion, resp
1066
918
  if (!CACHEABLE_AGENTS.has(agent))
1067
919
  return;
1068
920
  const cd = cacheDir(dir);
1069
- if (!existsSync7(cd))
921
+ if (!existsSync6(cd))
1070
922
  mkdirSync3(cd, { recursive: true });
1071
923
  const key = hashKey(agent, prompt, context, stateVersion, indexVersion);
1072
924
  const entry = {
@@ -1078,21 +930,21 @@ function setCached(dir, agent, prompt, context, stateVersion, indexVersion, resp
1078
930
  ttl_ms,
1079
931
  response
1080
932
  };
1081
- writeFileSync6(entryPath(dir, key), JSON.stringify(entry, null, 2), "utf-8");
933
+ writeFileSync5(entryPath(dir, key), JSON.stringify(entry, null, 2), "utf-8");
1082
934
  pruneExpired(dir);
1083
935
  }
1084
936
  function pruneExpired(dir) {
1085
937
  const cd = cacheDir(dir);
1086
- if (!existsSync7(cd))
938
+ if (!existsSync6(cd))
1087
939
  return;
1088
940
  try {
1089
941
  const files = readdirSync3(cd).filter((f) => f.endsWith(".json"));
1090
942
  const now = Date.now();
1091
943
  const entries = [];
1092
944
  for (const f of files) {
1093
- const p = join7(cd, f);
945
+ const p = join6(cd, f);
1094
946
  try {
1095
- const entry = JSON.parse(readFileSync7(p, "utf-8"));
947
+ const entry = JSON.parse(readFileSync6(p, "utf-8"));
1096
948
  const age = now - new Date(entry.created_at).getTime();
1097
949
  entries.push({ path: p, created_at: new Date(entry.created_at).getTime(), expired: age > entry.ttl_ms });
1098
950
  } catch {
@@ -1119,15 +971,15 @@ function pruneExpired(dir) {
1119
971
  }
1120
972
 
1121
973
  // src/tools/codebase-index.ts
1122
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as existsSync8, mkdirSync as mkdirSync4 } from "fs";
1123
- 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";
1124
976
  var CODEBASE_INDEX_FILE = "CODEBASE_INDEX.md";
1125
977
  function indexPath(dir) {
1126
- return join8(planningDir(dir), CODEBASE_INDEX_FILE);
978
+ return join7(planningDir(dir), CODEBASE_INDEX_FILE);
1127
979
  }
1128
980
  function readCodebaseIndex(dir) {
1129
981
  const path = indexPath(dir);
1130
- if (!existsSync8(path)) {
982
+ if (!existsSync7(path)) {
1131
983
  return {
1132
984
  exists: false,
1133
985
  lastUpdatedAt: "",
@@ -1141,7 +993,7 @@ function readCodebaseIndex(dir) {
1141
993
  };
1142
994
  }
1143
995
  try {
1144
- const content = readFileSync8(path, "utf-8");
996
+ const content = readFileSync7(path, "utf-8");
1145
997
  return parseCodebaseIndexContent(content);
1146
998
  } catch {
1147
999
  return {
@@ -1217,17 +1069,17 @@ function parseCodebaseIndexContent(content) {
1217
1069
  }
1218
1070
 
1219
1071
  // src/services/token-metrics.ts
1220
- import { existsSync as existsSync9, readFileSync as readFileSync9, appendFileSync, mkdirSync as mkdirSync5 } from "fs";
1221
- 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";
1222
1074
  function estimateTokens(text) {
1223
1075
  return Math.ceil(text.length / 4);
1224
1076
  }
1225
1077
  function metricsPath(dir) {
1226
- return join9(codebaseDir(dir), "TOKEN_METRICS.jsonl");
1078
+ return join8(codebaseDir(dir), "TOKEN_METRICS.jsonl");
1227
1079
  }
1228
1080
  function appendEvent(dir, event) {
1229
1081
  const cd = codebaseDir(dir);
1230
- if (!existsSync9(cd))
1082
+ if (!existsSync8(cd))
1231
1083
  mkdirSync5(cd, { recursive: true });
1232
1084
  appendFileSync(metricsPath(dir), JSON.stringify(event) + `
1233
1085
  `, "utf-8");
@@ -1285,23 +1137,23 @@ function recordRetryCall(dir, workflow_id, stage, inputText, outputText, agent,
1285
1137
  var _workflowTimers = new Map;
1286
1138
 
1287
1139
  // src/config/loader.ts
1288
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
1289
- import { join as join10 } from "path";
1140
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
1141
+ import { join as join9 } from "path";
1290
1142
  import { homedir } from "os";
1291
1143
  var CONFIG_FILENAME = "flowdeck.json";
1292
1144
  function getGlobalConfigDir() {
1293
- 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"));
1294
1146
  }
1295
1147
  function loadFlowDeckConfig(directory) {
1296
1148
  const candidates = [];
1297
1149
  if (directory) {
1298
- candidates.push(join10(directory, ".opencode", CONFIG_FILENAME));
1150
+ candidates.push(join9(directory, ".opencode", CONFIG_FILENAME));
1299
1151
  }
1300
- candidates.push(join10(getGlobalConfigDir(), CONFIG_FILENAME));
1152
+ candidates.push(join9(getGlobalConfigDir(), CONFIG_FILENAME));
1301
1153
  for (const configPath of candidates) {
1302
- if (existsSync10(configPath)) {
1154
+ if (existsSync9(configPath)) {
1303
1155
  try {
1304
- const content = readFileSync10(configPath, "utf-8");
1156
+ const content = readFileSync9(configPath, "utf-8");
1305
1157
  return JSON.parse(content);
1306
1158
  } catch {
1307
1159
  console.warn(`[flowdeck] Failed to load config from ${configPath}`);
@@ -1387,18 +1239,18 @@ function extractText2(parts) {
1387
1239
  `);
1388
1240
  }
1389
1241
  function createDelegateTool(client) {
1390
- return tool5({
1242
+ return tool4({
1391
1243
  description: "Delegate a task to a single agent via a child session. Returns the agent's output.",
1392
1244
  args: {
1393
- agent: tool5.schema.string(),
1394
- prompt: tool5.schema.string(),
1395
- context: tool5.schema.string().optional(),
1396
- task_type: tool5.schema.string().optional(),
1397
- retry_attempts: tool5.schema.number().optional().default(1),
1398
- safe_to_cache: tool5.schema.boolean().optional().default(false),
1399
- cache_ttl_ms: tool5.schema.number().optional(),
1400
- workflow_id: tool5.schema.string().optional(),
1401
- 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()
1402
1254
  },
1403
1255
  async execute(args, context) {
1404
1256
  const startTime = Date.now();
@@ -1423,7 +1275,7 @@ ${args.prompt}` : args.prompt;
1423
1275
  if (safe_to_cache) {
1424
1276
  const index = readCodebaseIndex(context.directory);
1425
1277
  const sp = statePath(context.directory);
1426
- const rawState = existsSync11(sp) ? readFileSync11(sp, "utf-8") : "";
1278
+ const rawState = existsSync10(sp) ? readFileSync10(sp, "utf-8") : "";
1427
1279
  const state = rawState ? parseState(rawState) : {};
1428
1280
  stateVersion = typeof state.summaryVersion === "number" ? state.summaryVersion : 0;
1429
1281
  indexVersion = typeof index.summaryVersion === "number" ? index.summaryVersion : 0;
@@ -1491,12 +1343,13 @@ ${args.prompt}` : args.prompt;
1491
1343
  retriesUsed++;
1492
1344
  }
1493
1345
  if (!promptRes || promptRes.error) {
1346
+ const errMsg = `Prompt failed: ${promptRes?.error?.detail ?? "unknown"}`;
1494
1347
  recordRun(context.directory, args.agent, "", taskType, false, Date.now() - startTime);
1495
1348
  return JSON.stringify({
1496
1349
  agent: args.agent,
1497
1350
  session_id: childId,
1498
1351
  success: false,
1499
- error: `Prompt failed: ${promptRes?.error?.detail ?? "unknown"}`,
1352
+ error: errMsg,
1500
1353
  task_type: taskType,
1501
1354
  model: "",
1502
1355
  retries_used: retriesUsed,
@@ -1505,12 +1358,13 @@ ${args.prompt}` : args.prompt;
1505
1358
  }
1506
1359
  const info = promptRes.data?.info;
1507
1360
  if (info?.error) {
1361
+ const errMsg = `Agent error: ${JSON.stringify(info.error)}`;
1508
1362
  recordRun(context.directory, args.agent, "", taskType, false, Date.now() - startTime);
1509
1363
  return JSON.stringify({
1510
1364
  agent: args.agent,
1511
1365
  session_id: childId,
1512
1366
  success: false,
1513
- error: `Agent error: ${JSON.stringify(info.error)}`,
1367
+ error: errMsg,
1514
1368
  task_type: taskType,
1515
1369
  model: "",
1516
1370
  retries_used: retriesUsed,
@@ -1543,53 +1397,53 @@ ${args.prompt}` : args.prompt;
1543
1397
  }
1544
1398
 
1545
1399
  // src/tools/repo-memory.ts
1546
- import { tool as tool6 } from "@opencode-ai/plugin";
1547
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync8, existsSync as existsSync12, mkdirSync as mkdirSync6 } from "fs";
1548
- 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";
1549
1403
  var MEMORY_FILE = "MEMORY.json";
1550
1404
  function memoryPath(directory) {
1551
- return join11(codebaseDir(directory), MEMORY_FILE);
1405
+ return join10(codebaseDir(directory), MEMORY_FILE);
1552
1406
  }
1553
1407
  function emptyMemory() {
1554
1408
  return { version: "1.0", last_updated: new Date().toISOString(), nodes: {} };
1555
1409
  }
1556
1410
  function readMemory(directory) {
1557
1411
  const p = memoryPath(directory);
1558
- if (!existsSync12(p))
1412
+ if (!existsSync11(p))
1559
1413
  return emptyMemory();
1560
1414
  try {
1561
- return JSON.parse(readFileSync12(p, "utf-8"));
1415
+ return JSON.parse(readFileSync11(p, "utf-8"));
1562
1416
  } catch {
1563
1417
  return emptyMemory();
1564
1418
  }
1565
1419
  }
1566
1420
  function writeMemory(directory, memory) {
1567
1421
  const base = codebaseDir(directory);
1568
- if (!existsSync12(base))
1422
+ if (!existsSync11(base))
1569
1423
  mkdirSync6(base, { recursive: true });
1570
1424
  memory.last_updated = new Date().toISOString();
1571
- writeFileSync8(memoryPath(directory), JSON.stringify(memory, null, 2), "utf-8");
1425
+ writeFileSync7(memoryPath(directory), JSON.stringify(memory, null, 2), "utf-8");
1572
1426
  }
1573
- var repoMemoryTool = tool6({
1427
+ var repoMemoryTool = tool5({
1574
1428
  description: "Repo Memory Graph: read/write/query persistent architecture graph in .codebase/MEMORY.json (modules, dependencies, ownership, bug history, conventions)",
1575
1429
  args: {
1576
- action: tool6.schema.enum(["read", "write_node", "query", "delete_node"]),
1577
- node_id: tool6.schema.string().optional(),
1578
- node: tool6.schema.object({
1579
- type: tool6.schema.enum(["module", "service", "api", "schema", "config"]),
1580
- path: tool6.schema.string(),
1581
- owner: tool6.schema.string().optional(),
1582
- tags: tool6.schema.array(tool6.schema.string()),
1583
- dependencies: tool6.schema.array(tool6.schema.string()),
1584
- dependents: tool6.schema.array(tool6.schema.string()),
1585
- bug_history: tool6.schema.array(tool6.schema.string()),
1586
- 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())
1587
1441
  }).optional(),
1588
- query: tool6.schema.object({
1589
- type: tool6.schema.enum(["module", "service", "api", "schema", "config"]).optional(),
1590
- owner: tool6.schema.string().optional(),
1591
- tag: tool6.schema.string().optional(),
1592
- 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()
1593
1447
  }).optional()
1594
1448
  },
1595
1449
  async execute(args, context) {
@@ -1644,50 +1498,50 @@ var repoMemoryTool = tool6({
1644
1498
  });
1645
1499
 
1646
1500
  // src/tools/failure-replay.ts
1647
- import { tool as tool7 } from "@opencode-ai/plugin";
1648
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, existsSync as existsSync13, mkdirSync as mkdirSync7 } from "fs";
1649
- 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";
1650
1504
  var FAILURES_FILE = "FAILURES.json";
1651
1505
  function failuresPath(directory) {
1652
- return join12(codebaseDir(directory), FAILURES_FILE);
1506
+ return join11(codebaseDir(directory), FAILURES_FILE);
1653
1507
  }
1654
1508
  function readStore(directory) {
1655
1509
  const p = failuresPath(directory);
1656
- if (!existsSync13(p))
1510
+ if (!existsSync12(p))
1657
1511
  return { version: "1.0", last_updated: new Date().toISOString(), entries: [] };
1658
1512
  try {
1659
- return JSON.parse(readFileSync13(p, "utf-8"));
1513
+ return JSON.parse(readFileSync12(p, "utf-8"));
1660
1514
  } catch {
1661
1515
  return { version: "1.0", last_updated: new Date().toISOString(), entries: [] };
1662
1516
  }
1663
1517
  }
1664
1518
  function writeStore(directory, store) {
1665
1519
  const base = codebaseDir(directory);
1666
- if (!existsSync13(base))
1520
+ if (!existsSync12(base))
1667
1521
  mkdirSync7(base, { recursive: true });
1668
1522
  store.last_updated = new Date().toISOString();
1669
- writeFileSync9(failuresPath(directory), JSON.stringify(store, null, 2), "utf-8");
1523
+ writeFileSync8(failuresPath(directory), JSON.stringify(store, null, 2), "utf-8");
1670
1524
  }
1671
- var failureReplayTool = tool7({
1525
+ var failureReplayTool = tool6({
1672
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",
1673
1527
  args: {
1674
- action: tool7.schema.enum(["record", "query", "list", "mark_resolved"]),
1675
- entry: tool7.schema.object({
1676
- id: tool7.schema.string(),
1677
- type: tool7.schema.enum(["reverted_commit", "failed_deployment", "flaky_test", "bug_fix", "build_failure"]),
1678
- description: tool7.schema.string(),
1679
- affected_paths: tool7.schema.array(tool7.schema.string()),
1680
- root_cause: tool7.schema.string().optional(),
1681
- fix_applied: tool7.schema.string().optional(),
1682
- 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())
1683
1537
  }).optional(),
1684
- query: tool7.schema.object({
1685
- type: tool7.schema.enum(["reverted_commit", "failed_deployment", "flaky_test", "bug_fix", "build_failure"]).optional(),
1686
- path_prefix: tool7.schema.string().optional(),
1687
- tag: tool7.schema.string().optional(),
1688
- 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()
1689
1543
  }).optional(),
1690
- entry_id: tool7.schema.string().optional()
1544
+ entry_id: tool6.schema.string().optional()
1691
1545
  },
1692
1546
  async execute(args, context) {
1693
1547
  const dir = context.directory ?? process.cwd();
@@ -1749,18 +1603,18 @@ var failureReplayTool = tool7({
1749
1603
  });
1750
1604
 
1751
1605
  // src/tools/decision-trace.ts
1752
- import { tool as tool8 } from "@opencode-ai/plugin";
1753
- import { readFileSync as readFileSync14, existsSync as existsSync14, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2 } from "fs";
1754
- 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";
1755
1609
  var DECISIONS_FILE = "DECISIONS.jsonl";
1756
1610
  function decisionsPath(directory) {
1757
- return join13(codebaseDir(directory), DECISIONS_FILE);
1611
+ return join12(codebaseDir(directory), DECISIONS_FILE);
1758
1612
  }
1759
1613
  function readDecisions(directory) {
1760
1614
  const p = decisionsPath(directory);
1761
- if (!existsSync14(p))
1615
+ if (!existsSync13(p))
1762
1616
  return [];
1763
- return readFileSync14(p, "utf-8").split(`
1617
+ return readFileSync13(p, "utf-8").split(`
1764
1618
  `).filter((l) => l.trim()).map((l) => {
1765
1619
  try {
1766
1620
  return JSON.parse(l);
@@ -1769,29 +1623,29 @@ function readDecisions(directory) {
1769
1623
  }
1770
1624
  }).filter(Boolean);
1771
1625
  }
1772
- var decisionTraceTool = tool8({
1626
+ var decisionTraceTool = tool7({
1773
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.",
1774
1628
  args: {
1775
- action: tool8.schema.enum(["record", "query", "get_for_file"]),
1776
- entry: tool8.schema.object({
1777
- id: tool8.schema.string(),
1778
- file_path: tool8.schema.string(),
1779
- change_type: tool8.schema.enum(["create", "edit", "delete", "refactor"]),
1780
- rationale: tool8.schema.string(),
1781
- evidence: tool8.schema.array(tool8.schema.string()),
1782
- assumptions: tool8.schema.array(tool8.schema.string()),
1783
- alternatives_considered: tool8.schema.array(tool8.schema.string()),
1784
- risk_level: tool8.schema.enum(["low", "medium", "high"]),
1785
- agent: tool8.schema.string().optional(),
1786
- 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()
1787
1641
  }).optional(),
1788
- query: tool8.schema.object({
1789
- file_path: tool8.schema.string().optional(),
1790
- change_type: tool8.schema.enum(["create", "edit", "delete", "refactor"]).optional(),
1791
- risk_level: tool8.schema.enum(["low", "medium", "high"]).optional(),
1792
- 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()
1793
1647
  }).optional(),
1794
- file_path: tool8.schema.string().optional()
1648
+ file_path: tool7.schema.string().optional()
1795
1649
  },
1796
1650
  async execute(args, context) {
1797
1651
  const dir = context.directory ?? process.cwd();
@@ -1800,7 +1654,7 @@ var decisionTraceTool = tool8({
1800
1654
  case "record": {
1801
1655
  if (!args.entry)
1802
1656
  return JSON.stringify({ error: "entry required" });
1803
- if (!existsSync14(base))
1657
+ if (!existsSync13(base))
1804
1658
  mkdirSync8(base, { recursive: true });
1805
1659
  const entry = { ...args.entry, timestamp: new Date().toISOString() };
1806
1660
  appendFileSync2(decisionsPath(dir), JSON.stringify(entry) + `
@@ -1833,162 +1687,54 @@ var decisionTraceTool = tool8({
1833
1687
  }
1834
1688
  });
1835
1689
 
1836
- // src/tools/volatility-map.ts
1837
- import { tool as tool9 } from "@opencode-ai/plugin";
1838
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync11, existsSync as existsSync15, mkdirSync as mkdirSync9 } from "fs";
1839
- import { join as join14 } from "path";
1840
- var VOLATILITY_FILE = "VOLATILITY.json";
1841
- function volatilityPath(directory) {
1842
- return join14(codebaseDir(directory), VOLATILITY_FILE);
1843
- }
1844
- function readStore2(directory) {
1845
- const p = volatilityPath(directory);
1846
- if (!existsSync15(p))
1847
- return { version: "1.0", last_updated: new Date().toISOString(), generated_at: new Date().toISOString(), entries: [] };
1848
- try {
1849
- return JSON.parse(readFileSync15(p, "utf-8"));
1850
- } catch {
1851
- return { version: "1.0", last_updated: new Date().toISOString(), generated_at: new Date().toISOString(), entries: [] };
1852
- }
1853
- }
1854
- function writeStore2(directory, store) {
1855
- const base = codebaseDir(directory);
1856
- if (!existsSync15(base))
1857
- mkdirSync9(base, { recursive: true });
1858
- store.last_updated = new Date().toISOString();
1859
- writeFileSync11(volatilityPath(directory), JSON.stringify(store, null, 2), "utf-8");
1860
- }
1861
- function stabilityLabel(churn, hotfixes, todos) {
1862
- const score = churn + hotfixes * 10 + todos * 2;
1863
- if (score >= 80)
1864
- return "critical";
1865
- if (score >= 50)
1866
- return "volatile";
1867
- if (score >= 20)
1868
- return "moderate";
1869
- return "stable";
1870
- }
1871
- var volatilityMapTool = tool9({
1872
- description: "Codebase Volatility Map: read/write/query .codebase/VOLATILITY.json — highlights unstable zones based on churn, hotfix frequency, and TODO clusters",
1873
- args: {
1874
- action: tool9.schema.enum(["read", "write", "query_hotspots", "update_entry"]),
1875
- entries: tool9.schema.array(tool9.schema.object({
1876
- path: tool9.schema.string(),
1877
- churn_score: tool9.schema.number(),
1878
- hotfix_count: tool9.schema.number(),
1879
- todo_count: tool9.schema.number(),
1880
- last_breakage: tool9.schema.string().optional(),
1881
- notes: tool9.schema.array(tool9.schema.string())
1882
- })).optional(),
1883
- entry: tool9.schema.object({
1884
- path: tool9.schema.string(),
1885
- churn_score: tool9.schema.number(),
1886
- hotfix_count: tool9.schema.number(),
1887
- todo_count: tool9.schema.number(),
1888
- last_breakage: tool9.schema.string().optional(),
1889
- notes: tool9.schema.array(tool9.schema.string())
1890
- }).optional(),
1891
- threshold: tool9.schema.enum(["stable", "moderate", "volatile", "critical"]).optional(),
1892
- path_prefix: tool9.schema.string().optional(),
1893
- limit: tool9.schema.number().optional()
1894
- },
1895
- async execute(args, context) {
1896
- const dir = context.directory ?? process.cwd();
1897
- const store = readStore2(dir);
1898
- switch (args.action) {
1899
- case "read": {
1900
- return JSON.stringify({ last_updated: store.last_updated, count: store.entries.length, entries: store.entries });
1901
- }
1902
- case "write": {
1903
- if (!args.entries)
1904
- return JSON.stringify({ error: "entries required" });
1905
- store.entries = args.entries.map((e) => ({
1906
- ...e,
1907
- stability: stabilityLabel(e.churn_score, e.hotfix_count, e.todo_count)
1908
- }));
1909
- store.generated_at = new Date().toISOString();
1910
- writeStore2(dir, store);
1911
- return JSON.stringify({ success: true, count: store.entries.length });
1912
- }
1913
- case "update_entry": {
1914
- if (!args.entry)
1915
- return JSON.stringify({ error: "entry required" });
1916
- const idx = store.entries.findIndex((e) => e.path === args.entry.path);
1917
- const updated = {
1918
- ...args.entry,
1919
- stability: stabilityLabel(args.entry.churn_score, args.entry.hotfix_count, args.entry.todo_count)
1920
- };
1921
- if (idx >= 0) {
1922
- store.entries[idx] = updated;
1923
- } else {
1924
- store.entries.push(updated);
1925
- }
1926
- writeStore2(dir, store);
1927
- return JSON.stringify({ success: true, path: args.entry.path, stability: updated.stability });
1928
- }
1929
- case "query_hotspots": {
1930
- const levels = { stable: 0, moderate: 1, volatile: 2, critical: 3 };
1931
- const minLevel = levels[args.threshold ?? "volatile"] ?? 2;
1932
- let results = store.entries.filter((e) => (levels[e.stability] ?? 0) >= minLevel);
1933
- if (args.path_prefix)
1934
- results = results.filter((e) => e.path.startsWith(args.path_prefix));
1935
- results.sort((a, b) => levels[b.stability] - levels[a.stability] || b.churn_score - a.churn_score);
1936
- if (args.limit)
1937
- results = results.slice(0, args.limit);
1938
- return JSON.stringify({ count: results.length, hotspots: results });
1939
- }
1940
- }
1941
- }
1942
- });
1943
-
1944
1690
  // src/tools/policy-engine.ts
1945
- import { tool as tool10 } from "@opencode-ai/plugin";
1946
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync12, existsSync as existsSync16, mkdirSync as mkdirSync10 } from "fs";
1947
- 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";
1948
1694
  var POLICIES_FILE = "POLICIES.json";
1949
1695
  function policiesPath(directory) {
1950
- return join15(codebaseDir(directory), POLICIES_FILE);
1696
+ return join13(codebaseDir(directory), POLICIES_FILE);
1951
1697
  }
1952
- function readStore3(directory) {
1698
+ function readStore2(directory) {
1953
1699
  const p = policiesPath(directory);
1954
- if (!existsSync16(p))
1700
+ if (!existsSync14(p))
1955
1701
  return { version: "1.0", last_updated: new Date().toISOString(), policies: [] };
1956
1702
  try {
1957
- return JSON.parse(readFileSync16(p, "utf-8"));
1703
+ return JSON.parse(readFileSync14(p, "utf-8"));
1958
1704
  } catch {
1959
1705
  return { version: "1.0", last_updated: new Date().toISOString(), policies: [] };
1960
1706
  }
1961
1707
  }
1962
- function writeStore3(directory, store) {
1708
+ function writeStore2(directory, store) {
1963
1709
  const base = codebaseDir(directory);
1964
- if (!existsSync16(base))
1965
- mkdirSync10(base, { recursive: true });
1710
+ if (!existsSync14(base))
1711
+ mkdirSync9(base, { recursive: true });
1966
1712
  store.last_updated = new Date().toISOString();
1967
- writeFileSync12(policiesPath(directory), JSON.stringify(store, null, 2), "utf-8");
1713
+ writeFileSync10(policiesPath(directory), JSON.stringify(store, null, 2), "utf-8");
1968
1714
  }
1969
- var policyEngineTool = tool10({
1715
+ var policyEngineTool = tool8({
1970
1716
  description: "Self-Healing Policy Engine: manage .codebase/POLICIES.json — add, list, query, toggle, and record violations of editing policies learned from past failures",
1971
1717
  args: {
1972
- action: tool10.schema.enum(["list", "add", "record_violation", "toggle", "query"]),
1973
- policy: tool10.schema.object({
1974
- id: tool10.schema.string(),
1975
- name: tool10.schema.string(),
1976
- trigger: tool10.schema.string(),
1977
- rule: tool10.schema.string(),
1978
- source: tool10.schema.enum(["manual", "learned"]),
1979
- 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()
1980
1726
  }).optional(),
1981
- policy_id: tool10.schema.string().optional(),
1982
- active: tool10.schema.boolean().optional(),
1983
- query: tool10.schema.object({
1984
- source: tool10.schema.enum(["manual", "learned"]).optional(),
1985
- active_only: tool10.schema.boolean().optional(),
1986
- 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()
1987
1733
  }).optional()
1988
1734
  },
1989
1735
  async execute(args, context) {
1990
1736
  const dir = context.directory ?? process.cwd();
1991
- const store = readStore3(dir);
1737
+ const store = readStore2(dir);
1992
1738
  switch (args.action) {
1993
1739
  case "list": {
1994
1740
  const active = store.policies.filter((p) => p.active);
@@ -2003,7 +1749,7 @@ var policyEngineTool = tool10({
2003
1749
  } else {
2004
1750
  store.policies.push({ ...args.policy, created_at: new Date().toISOString(), active: true });
2005
1751
  }
2006
- writeStore3(dir, store);
1752
+ writeStore2(dir, store);
2007
1753
  return JSON.stringify({ success: true, id: args.policy.id });
2008
1754
  }
2009
1755
  case "record_violation": {
@@ -2014,7 +1760,7 @@ var policyEngineTool = tool10({
2014
1760
  return JSON.stringify({ error: `Policy not found: ${args.policy_id}` });
2015
1761
  policy.failure_count++;
2016
1762
  policy.last_violated = new Date().toISOString();
2017
- writeStore3(dir, store);
1763
+ writeStore2(dir, store);
2018
1764
  return JSON.stringify({ success: true, policy_id: args.policy_id, failure_count: policy.failure_count });
2019
1765
  }
2020
1766
  case "toggle": {
@@ -2024,7 +1770,7 @@ var policyEngineTool = tool10({
2024
1770
  if (!policy)
2025
1771
  return JSON.stringify({ error: `Policy not found: ${args.policy_id}` });
2026
1772
  policy.active = args.active !== undefined ? args.active : !policy.active;
2027
- writeStore3(dir, store);
1773
+ writeStore2(dir, store);
2028
1774
  return JSON.stringify({ success: true, policy_id: args.policy_id, active: policy.active });
2029
1775
  }
2030
1776
  case "query": {
@@ -2045,22 +1791,22 @@ var policyEngineTool = tool10({
2045
1791
  });
2046
1792
 
2047
1793
  // src/tools/hash-edit.ts
2048
- import { tool as tool11 } from "@opencode-ai/plugin";
2049
- 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";
2050
1796
  import { createHash as createHash2 } from "crypto";
2051
- var hashEditTool = tool11({
1797
+ var hashEditTool = tool9({
2052
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.",
2053
1799
  args: {
2054
- filePath: tool11.schema.string(),
2055
- targetContent: tool11.schema.string(),
2056
- expectedHash: tool11.schema.string().optional(),
2057
- 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()
2058
1804
  },
2059
1805
  async execute(args, context) {
2060
1806
  const fullPath = args.filePath.startsWith("/") ? args.filePath : `${context.directory}/${args.filePath}`;
2061
1807
  let content;
2062
1808
  try {
2063
- content = readFileSync17(fullPath, "utf-8");
1809
+ content = readFileSync15(fullPath, "utf-8");
2064
1810
  } catch (e) {
2065
1811
  return `Error: Could not read file ${args.filePath}`;
2066
1812
  }
@@ -2074,17 +1820,17 @@ var hashEditTool = tool11({
2074
1820
  }
2075
1821
  }
2076
1822
  const newContent = content.replace(args.targetContent, args.replacementContent);
2077
- writeFileSync13(fullPath, newContent, "utf-8");
1823
+ writeFileSync11(fullPath, newContent, "utf-8");
2078
1824
  return `Successfully updated ${args.filePath} using hash-anchored edit.`;
2079
1825
  }
2080
1826
  });
2081
1827
 
2082
1828
  // src/tools/council.ts
2083
- import { tool as tool12 } from "@opencode-ai/plugin";
2084
- import { appendFileSync as appendFileSync3, existsSync as existsSync17, mkdirSync as mkdirSync11 } from "fs";
2085
- 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";
2086
1832
  import { createHash as createHash3 } from "crypto";
2087
- import { readFileSync as readFileSync18 } from "fs";
1833
+ import { readFileSync as readFileSync16 } from "fs";
2088
1834
  var _councilCache = new Map;
2089
1835
  var COUNCIL_CACHE_TTL_MS = 20 * 60 * 1000;
2090
1836
  function councilCacheKey(task, agents, stateVersion, indexVersion) {
@@ -2105,20 +1851,20 @@ async function runWithConcurrencyLimit(tasks, limit) {
2105
1851
  return results;
2106
1852
  }
2107
1853
  function createCouncilTool(client) {
2108
- return tool12({
1854
+ return tool10({
2109
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.",
2110
1856
  args: {
2111
- task: tool12.schema.string(),
2112
- agents: tool12.schema.array(tool12.schema.string()).optional(),
2113
- force_fresh: tool12.schema.boolean().optional().default(false),
2114
- 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)
2115
1861
  },
2116
1862
  async execute(args, context) {
2117
1863
  const agents = args.agents || ["architect", "reviewer", "backend-coder"];
2118
1864
  const concurrencyLimit = Math.max(1, Math.min(5, typeof args.max_concurrency === "number" ? args.max_concurrency : 3));
2119
1865
  const index = readCodebaseIndex(context.directory);
2120
1866
  const sp = statePath(context.directory);
2121
- const rawState = existsSync17(sp) ? readFileSync18(sp, "utf-8") : "";
1867
+ const rawState = existsSync15(sp) ? readFileSync16(sp, "utf-8") : "";
2122
1868
  const state = rawState ? parseState(rawState) : {};
2123
1869
  const stateVersion = typeof state.summaryVersion === "number" ? state.summaryVersion : 0;
2124
1870
  const indexVersion = typeof index.summaryVersion === "number" ? index.summaryVersion : 0;
@@ -2194,117 +1940,18 @@ Please synthesize these results. Identify areas of agreement, resolve conflicts,
2194
1940
  function persistCouncilResult(directory, payload) {
2195
1941
  try {
2196
1942
  const base = codebaseDir(directory);
2197
- if (!existsSync17(base))
2198
- mkdirSync11(base, { recursive: true });
2199
- const path = join16(base, "COUNCILS.jsonl");
1943
+ if (!existsSync15(base))
1944
+ mkdirSync10(base, { recursive: true });
1945
+ const path = join14(base, "COUNCILS.jsonl");
2200
1946
  appendFileSync3(path, JSON.stringify(payload) + `
2201
1947
  `, "utf-8");
2202
1948
  } catch {}
2203
1949
  }
2204
1950
 
2205
- // src/tools/context-generator.ts
2206
- import { tool as tool13 } from "@opencode-ai/plugin";
2207
- import { writeFileSync as writeFileSync14, existsSync as existsSync18, readFileSync as readFileSync19, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
2208
- import { join as join17 } from "path";
2209
- var contextGeneratorTool = tool13({
2210
- description: "Auto-generate or update hierarchical context files (AGENTS.md, CLAUDE.md) throughout the project. These files provide critical grounding for AI agents.",
2211
- args: {
2212
- targetDir: tool13.schema.string().optional(),
2213
- force: tool13.schema.boolean().optional()
2214
- },
2215
- async execute(args, context) {
2216
- const root = context.directory;
2217
- const target = args.targetDir ? join17(root, args.targetDir) : root;
2218
- if (!existsSync18(target)) {
2219
- return `Error: Directory ${target} does not exist.`;
2220
- }
2221
- const agentsMdPath = join17(target, "AGENTS.md");
2222
- if (existsSync18(agentsMdPath) && !args.force) {
2223
- return `AGENTS.md already exists in ${target}. Use force: true to overwrite.`;
2224
- }
2225
- const pkgPath = join17(root, "package.json");
2226
- let projectName = "Project";
2227
- let techStack = "Unknown";
2228
- if (existsSync18(pkgPath)) {
2229
- try {
2230
- const pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
2231
- projectName = pkg.name || projectName;
2232
- techStack = Object.keys(pkg.dependencies || {}).slice(0, 5).join(", ");
2233
- } catch {}
2234
- }
2235
- const content = `# AGENTS.md for ${projectName}
2236
-
2237
- ## Context
2238
- - **Tech Stack**: ${techStack}
2239
- - **Primary Goal**: [Explain the main purpose of this directory/project]
2240
-
2241
- ## Rules for Agents
2242
- 1. **Consistency**: Follow existing patterns in this directory.
2243
- 2. **Safety**: Do not modify files in \`node_modules\` or other sensitive areas.
2244
- 3. **Planning**: Always check \`.planning/STATE.md\` before executing major changes.
2245
-
2246
- ## Directory Map
2247
- ${readdirSync4(target).slice(0, 10).map((f) => {
2248
- const s = statSync2(join17(target, f));
2249
- return `- \`${f}\`${s.isDirectory() ? "/" : ""} : [Description]`;
2250
- }).join(`
2251
- `)}
2252
-
2253
- ---
2254
- Generated by FlowDeck Context Generator.
2255
- `;
2256
- writeFileSync14(agentsMdPath, content, "utf-8");
2257
- return `Successfully generated AGENTS.md in ${target}.`;
2258
- }
2259
- });
2260
-
2261
- // src/tools/create-skill.ts
2262
- import { tool as tool14 } from "@opencode-ai/plugin";
2263
- import { mkdirSync as mkdirSync12, writeFileSync as writeFileSync15, existsSync as existsSync19 } from "fs";
2264
- import { join as join18, dirname as dirname3 } from "path";
2265
- import { fileURLToPath } from "url";
2266
- var SKILLS_DIR = join18(dirname3(fileURLToPath(import.meta.url)), "..", "skills");
2267
- var createSkillTool = tool14({
2268
- 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.",
2269
- args: {
2270
- name: tool14.schema.string().describe("Unique kebab-case skill name, e.g. 'api-rate-limiting'"),
2271
- description: tool14.schema.string().describe("One-sentence description of what this skill does"),
2272
- content: tool14.schema.string().describe("Full skill body in Markdown. Must include: ## When to Activate, ## Steps, and ## Examples sections."),
2273
- tags: tool14.schema.array(tool14.schema.string()).optional().describe("Optional tags for categorisation, e.g. ['performance', 'typescript']")
2274
- },
2275
- async execute(args) {
2276
- const skillDir = join18(SKILLS_DIR, args.name);
2277
- const skillFile = join18(skillDir, "SKILL.md");
2278
- if (existsSync19(skillFile)) {
2279
- return `Skill '${args.name}' already exists at ${skillFile}.
2280
- ` + `Use a different name or delete the existing skill directory first.`;
2281
- }
2282
- const tagLine = args.tags?.length ? `
2283
- tags: [${args.tags.join(", ")}]` : "";
2284
- const frontmatter = `---
2285
- name: ${args.name}
2286
- description: ${args.description}
2287
- origin: FlowDeck (self-learned)${tagLine}
2288
- ---
2289
-
2290
- `;
2291
- const fullContent = frontmatter + args.content.trimStart();
2292
- try {
2293
- mkdirSync12(skillDir, { recursive: true });
2294
- writeFileSync15(skillFile, fullContent, "utf-8");
2295
- return `✓ Skill '${args.name}' created at ${skillFile}
2296
-
2297
- ` + `The skill is now part of the FlowDeck library. Restart OpenCode to load it into the active session.`;
2298
- } catch (err) {
2299
- return `Error creating skill '${args.name}': ${err.message}`;
2300
- }
2301
- }
2302
- });
2303
-
2304
1951
  // src/tools/reflect.ts
2305
- import { tool as tool15 } from "@opencode-ai/plugin";
2306
- import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
2307
- 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";
2308
1955
  var MAX_ARTIFACT_BYTES = 4000;
2309
1956
  function tail(text, maxBytes) {
2310
1957
  if (text.length <= maxBytes)
@@ -2312,10 +1959,10 @@ function tail(text, maxBytes) {
2312
1959
  return `... (truncated) ...
2313
1960
  ` + text.slice(-maxBytes);
2314
1961
  }
2315
- var reflectTool = tool15({
1962
+ var reflectTool = tool11({
2316
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.",
2317
1964
  args: {
2318
- 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")
2319
1966
  },
2320
1967
  async execute(args, context) {
2321
1968
  const root = context.directory;
@@ -2333,11 +1980,11 @@ var reflectTool = tool15({
2333
1980
  ];
2334
1981
  let found = 0;
2335
1982
  for (const [rel, label] of ARTIFACT_PATHS) {
2336
- const full = join19(root, rel);
2337
- if (!existsSync20(full))
1983
+ const full = join15(root, rel);
1984
+ if (!existsSync16(full))
2338
1985
  continue;
2339
1986
  try {
2340
- const raw = readFileSync20(full, "utf-8").trim();
1987
+ const raw = readFileSync17(full, "utf-8").trim();
2341
1988
  if (!raw)
2342
1989
  continue;
2343
1990
  const count = raw.split(`
@@ -2350,23 +1997,23 @@ var reflectTool = tool15({
2350
1997
  return `No FlowDeck artifacts found under .codebase/.
2351
1998
  ` + "Run some tasks first so decisions, telemetry, and failures are recorded.";
2352
1999
  }
2353
- 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");
2354
2001
  return sections.join(`
2355
2002
  `);
2356
2003
  }
2357
2004
  });
2358
2005
 
2359
2006
  // src/tools/codegraph-tool.ts
2360
- import { tool as tool16 } from "@opencode-ai/plugin";
2007
+ import { tool as tool12 } from "@opencode-ai/plugin";
2361
2008
 
2362
2009
  // src/services/codegraph.ts
2363
2010
  import { spawnSync } from "child_process";
2364
- import { existsSync as existsSync21, readFileSync as readFileSync21, writeFileSync as writeFileSync16, mkdirSync as mkdirSync13 } from "fs";
2365
- 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";
2366
2013
  var CODEGRAPH_META_FILE = "CODEGRAPH.md";
2367
2014
  var MAX_FRESHNESS_MS = 30 * 60 * 1000;
2368
2015
  function metaPath(dir) {
2369
- return join20(codebaseDir(dir), CODEGRAPH_META_FILE);
2016
+ return join16(codebaseDir(dir), CODEGRAPH_META_FILE);
2370
2017
  }
2371
2018
  function isCodegraphInstalled() {
2372
2019
  try {
@@ -2381,11 +2028,11 @@ function isCodegraphInstalled() {
2381
2028
  }
2382
2029
  }
2383
2030
  function isCodegraphIndexed(dir) {
2384
- return existsSync21(join20(dir, ".codegraph", "codegraph.db"));
2031
+ return existsSync17(join16(dir, ".codegraph", "codegraph.db"));
2385
2032
  }
2386
2033
  function readCodegraphMeta(dir) {
2387
2034
  const path = metaPath(dir);
2388
- if (!existsSync21(path)) {
2035
+ if (!existsSync17(path)) {
2389
2036
  return {
2390
2037
  installed: false,
2391
2038
  indexed: false,
@@ -2398,7 +2045,7 @@ function readCodegraphMeta(dir) {
2398
2045
  };
2399
2046
  }
2400
2047
  try {
2401
- const content = readFileSync21(path, "utf-8");
2048
+ const content = readFileSync18(path, "utf-8");
2402
2049
  return parseCodegraphMeta(content);
2403
2050
  } catch {
2404
2051
  return {
@@ -2465,8 +2112,8 @@ function parseCodegraphMeta(content) {
2465
2112
  }
2466
2113
  function writeCodegraphMeta(dir, meta) {
2467
2114
  const base = codebaseDir(dir);
2468
- if (!existsSync21(base))
2469
- mkdirSync13(base, { recursive: true });
2115
+ if (!existsSync17(base))
2116
+ mkdirSync11(base, { recursive: true });
2470
2117
  const lines = [
2471
2118
  "# Codegraph Metadata",
2472
2119
  "",
@@ -2479,7 +2126,7 @@ function writeCodegraphMeta(dir, meta) {
2479
2126
  `**installLog:** ${meta.installLog}`,
2480
2127
  `**indexLog:** ${meta.indexLog}`
2481
2128
  ];
2482
- writeFileSync16(metaPath(dir), lines.join(`
2129
+ writeFileSync12(metaPath(dir), lines.join(`
2483
2130
  `), "utf-8");
2484
2131
  }
2485
2132
  function isCodegraphFresh(dir, maxAgeMs = MAX_FRESHNESS_MS) {
@@ -2690,11 +2337,11 @@ function markCodegraphStale(dir) {
2690
2337
  }
2691
2338
 
2692
2339
  // src/tools/codegraph-tool.ts
2693
- var codegraphTool = tool16({
2340
+ var codegraphTool = tool12({
2694
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.",
2695
2342
  args: {
2696
- action: tool16.schema.enum(["check", "install", "init", "refresh", "status", "mark-stale"]),
2697
- agent: tool16.schema.string().optional()
2343
+ action: tool12.schema.enum(["check", "install", "init", "refresh", "status", "mark-stale"]),
2344
+ agent: tool12.schema.string().optional()
2698
2345
  },
2699
2346
  async execute(args, context) {
2700
2347
  const dir = context.directory ?? process.cwd();
@@ -2783,21 +2430,21 @@ var codegraphTool = tool16({
2783
2430
  });
2784
2431
 
2785
2432
  // src/tools/load-rules.ts
2786
- import { tool as tool17 } from "@opencode-ai/plugin";
2787
- import { existsSync as existsSync22, readFileSync as readFileSync22 } from "fs";
2788
- import { join as join21, dirname as dirname4 } from "path";
2789
- import { fileURLToPath as fileURLToPath2 } from "url";
2790
- 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");
2791
2438
  var _loadedPaths = new Set;
2792
- var loadRulesTool = tool17({
2439
+ var loadRulesTool = tool13({
2793
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).",
2794
2441
  args: {
2795
- stage: tool17.schema.string().optional().describe("Current workflow stage: discuss | plan | execute | verify | fix-bug | write-docs"),
2796
- 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)."),
2797
- 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.")
2798
2445
  },
2799
2446
  async execute(args) {
2800
- const rulesDir = existsSync22(RULES_DIR) ? RULES_DIR : null;
2447
+ const rulesDir = existsSync18(RULES_DIR) ? RULES_DIR : null;
2801
2448
  if (!rulesDir) {
2802
2449
  return JSON.stringify({
2803
2450
  loaded: [],
@@ -2823,7 +2470,7 @@ var loadRulesTool = tool17({
2823
2470
  continue;
2824
2471
  }
2825
2472
  try {
2826
- const text = readFileSync22(rule.path, "utf-8");
2473
+ const text = readFileSync19(rule.path, "utf-8");
2827
2474
  contents.push(`## ${name}
2828
2475
 
2829
2476
  ${text}`);
@@ -2854,11 +2501,11 @@ ${text}`);
2854
2501
  function ruleShortName(rule) {
2855
2502
  return rule.path.replace(RULES_DIR + "/", "").replace(/\.md$/, "");
2856
2503
  }
2857
- var listRulesTool = tool17({
2504
+ var listRulesTool = tool13({
2858
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.",
2859
2506
  args: {},
2860
2507
  async execute() {
2861
- const rulesDir = existsSync22(RULES_DIR) ? RULES_DIR : null;
2508
+ const rulesDir = existsSync18(RULES_DIR) ? RULES_DIR : null;
2862
2509
  if (!rulesDir) {
2863
2510
  return JSON.stringify({ rules: [], error: `Rules directory not found at ${RULES_DIR}` });
2864
2511
  }
@@ -2878,13 +2525,13 @@ var listRulesTool = tool17({
2878
2525
  });
2879
2526
 
2880
2527
  // src/tools/rtk-setup.ts
2881
- import { tool as tool18 } from "@opencode-ai/plugin";
2528
+ import { tool as tool14 } from "@opencode-ai/plugin";
2882
2529
 
2883
2530
  // src/services/rtk-manager.ts
2884
2531
  import { spawnSync as spawnSync2 } from "child_process";
2885
- import { existsSync as existsSync23 } from "fs";
2532
+ import { existsSync as existsSync19 } from "fs";
2886
2533
  import { homedir as homedir2 } from "os";
2887
- import { join as join22 } from "path";
2534
+ import { join as join18 } from "path";
2888
2535
 
2889
2536
  // src/services/rtk-policy.ts
2890
2537
  var SUPPORTED_COMMANDS = new Set([
@@ -2930,7 +2577,7 @@ var INSTALL_INSTRUCTIONS = [
2930
2577
  "After installation, call rtk-setup again to verify detection."
2931
2578
  ].join(`
2932
2579
  `);
2933
- 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"];
2934
2581
  function detectRtk() {
2935
2582
  const fromPath = spawnSync2("rtk", ["--version"], { encoding: "utf-8", timeout: 5000 });
2936
2583
  if (fromPath.status === 0) {
@@ -2939,7 +2586,7 @@ function detectRtk() {
2939
2586
  return { installed: true, binPath: "rtk", version };
2940
2587
  }
2941
2588
  for (const candidate of CANDIDATE_PATHS) {
2942
- if (!existsSync23(candidate))
2589
+ if (!existsSync19(candidate))
2943
2590
  continue;
2944
2591
  const result = spawnSync2(candidate, ["--version"], { encoding: "utf-8", timeout: 5000 });
2945
2592
  if (result.status === 0) {
@@ -3017,7 +2664,7 @@ function getRtkStatus(opts) {
3017
2664
  }
3018
2665
 
3019
2666
  // src/tools/rtk-setup.ts
3020
- var rtkSetupTool = tool18({
2667
+ var rtkSetupTool = tool14({
3021
2668
  description: [
3022
2669
  "Detect, initialize, and report status of rtk (output compression proxy for CLI commands).",
3023
2670
  "rtk reduces noisy CLI output (git, npm, test runners, linters, docker) by 60-90%.",
@@ -3025,7 +2672,7 @@ var rtkSetupTool = tool18({
3025
2672
  "When RTK_INSTALLED=true in the environment, use `$RTK_BIN git status` for compressed output."
3026
2673
  ].join(" "),
3027
2674
  args: {
3028
- 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.")
3029
2676
  },
3030
2677
  async execute(args) {
3031
2678
  const action = args.action ?? "status";
@@ -3065,15 +2712,15 @@ var rtkSetupTool = tool18({
3065
2712
  });
3066
2713
 
3067
2714
  // src/hooks/guard-rails.ts
3068
- import { existsSync as existsSync24, readFileSync as readFileSync23 } from "fs";
3069
- import { join as join23 } from "path";
2715
+ import { existsSync as existsSync20, readFileSync as readFileSync20 } from "fs";
2716
+ import { join as join19 } from "path";
3070
2717
  var PLANNING_DIR2 = ".planning";
3071
2718
  var CONFIG_FILE = "config.json";
3072
2719
  var STATE_FILE2 = "STATE.md";
3073
2720
  function resolveExecutionMode(configPath, trustScore, volatility) {
3074
- if (existsSync24(configPath)) {
2721
+ if (existsSync20(configPath)) {
3075
2722
  try {
3076
- const config = JSON.parse(readFileSync23(configPath, "utf-8"));
2723
+ const config = JSON.parse(readFileSync20(configPath, "utf-8"));
3077
2724
  if (config.execution_mode === "review-only")
3078
2725
  return "review-only";
3079
2726
  if (config.execution_mode === "guarded")
@@ -3127,22 +2774,22 @@ async function guardRailsHook(ctx, input, _output) {
3127
2774
  if (!ENABLED)
3128
2775
  return;
3129
2776
  const dir = ctx.directory;
3130
- const planningDirPath = join23(dir, PLANNING_DIR2);
2777
+ const planningDirPath = join19(dir, PLANNING_DIR2);
3131
2778
  const codebaseDirectory = codebaseDir(dir);
3132
- const configPath = join23(planningDirPath, CONFIG_FILE);
3133
- const statePath2 = join23(planningDirPath, STATE_FILE2);
2779
+ const configPath = join19(planningDirPath, CONFIG_FILE);
2780
+ const statePath2 = join19(planningDirPath, STATE_FILE2);
3134
2781
  const workspaceRoot = findWorkspaceRoot(dir);
3135
2782
  if (workspaceRoot && dir !== workspaceRoot) {
3136
2783
  const config = getWorkspaceConfig(dir);
3137
- if (config && config.workspace_mode === "shared" && !existsSync24(planningDirPath)) {
2784
+ if (config && config.workspace_mode === "shared" && !existsSync20(planningDirPath)) {
3138
2785
  const msg = `No .planning/ in this sub-repo. Switch to workspace root: cd ${workspaceRoot}`;
3139
2786
  throw new Error(`[flowdeck] BLOCK: ${msg}`);
3140
2787
  }
3141
2788
  }
3142
2789
  if (input.tool === "write" || input.tool === "edit") {
3143
- if (!existsSync24(planningDirPath))
2790
+ if (!existsSync20(planningDirPath))
3144
2791
  return;
3145
- if (!existsSync24(codebaseDirectory)) {
2792
+ if (!existsSync20(codebaseDirectory)) {
3146
2793
  throw new Error(`[flowdeck] WARNING: .codebase/ not found. Run /fd-map-codebase to map the codebase.`);
3147
2794
  }
3148
2795
  const execMode = resolveExecutionMode(configPath, null);
@@ -3198,15 +2845,15 @@ function getDesignGateMessage(dir) {
3198
2845
  }
3199
2846
  function planSuggestsUiHeavy(dir, phase) {
3200
2847
  const planPath = phasePlanPath(dir, phase);
3201
- if (!existsSync24(planPath))
2848
+ if (!existsSync20(planPath))
3202
2849
  return false;
3203
- const planContent = readFileSync23(planPath, "utf-8");
2850
+ const planContent = readFileSync20(planPath, "utf-8");
3204
2851
  return isUiHeavyTask(planContent);
3205
2852
  }
3206
2853
  function effectiveSeverity(configPath, statePath2) {
3207
- if (existsSync24(configPath)) {
2854
+ if (existsSync20(configPath)) {
3208
2855
  try {
3209
- const configContent = readFileSync23(configPath, "utf-8");
2856
+ const configContent = readFileSync20(configPath, "utf-8");
3210
2857
  const config = JSON.parse(configContent);
3211
2858
  if (config.guard_enforcement === "warn")
3212
2859
  return "warn";
@@ -3222,10 +2869,10 @@ function getEffectiveSeverity(configPath, statePath2) {
3222
2869
  return effectiveSeverity(configPath, statePath2);
3223
2870
  }
3224
2871
  function getPlanConfirmed(statePath2) {
3225
- if (!existsSync24(statePath2))
2872
+ if (!existsSync20(statePath2))
3226
2873
  return false;
3227
2874
  try {
3228
- const content = readFileSync23(statePath2, "utf-8");
2875
+ const content = readFileSync20(statePath2, "utf-8");
3229
2876
  const match = content.match(/plan_confirmed:\s*(true|false)/i);
3230
2877
  return match ? match[1].toLowerCase() === "true" : false;
3231
2878
  } catch {
@@ -3233,32 +2880,32 @@ function getPlanConfirmed(statePath2) {
3233
2880
  }
3234
2881
  }
3235
2882
  function getWarningMessage(planningDir2) {
3236
- if (!existsSync24(join23(planningDir2, STATE_FILE2))) {
2883
+ if (!existsSync20(join19(planningDir2, STATE_FILE2))) {
3237
2884
  return "No STATE.md found. Run /fd-map-codebase then /fd-new-feature to start a feature.";
3238
2885
  }
3239
2886
  return "Plan not confirmed. Run /fd-plan and confirm to enable execution.";
3240
2887
  }
3241
2888
  function getBlockMessage(planningDir2) {
3242
- if (!existsSync24(join23(planningDir2, STATE_FILE2))) {
2889
+ if (!existsSync20(join19(planningDir2, STATE_FILE2))) {
3243
2890
  return "No STATE.md found. Run /fd-map-codebase then /fd-new-feature to start a feature.";
3244
2891
  }
3245
2892
  return "Plan not confirmed. Run /fd-plan and confirm to enable execution.";
3246
2893
  }
3247
2894
 
3248
2895
  // src/hooks/tool-guard.ts
3249
- import { existsSync as existsSync25, readFileSync as readFileSync24 } from "fs";
3250
- import { join as join24 } from "path";
2896
+ import { existsSync as existsSync21, readFileSync as readFileSync21 } from "fs";
2897
+ import { join as join20 } from "path";
3251
2898
  var IS_ENABLED = () => process.env.FLOWDECK_TOOL_GUARD_ENABLED === "on";
3252
2899
  var BLOCKED_PATTERNS = {
3253
2900
  read: [".env", ".pem", ".key", ".secret"],
3254
2901
  write: ["node_modules"],
3255
2902
  bash: ["rm -rf"]
3256
2903
  };
3257
- function isBlocked(tool19, args) {
3258
- const patterns = BLOCKED_PATTERNS[tool19];
2904
+ function isBlocked(tool15, args) {
2905
+ const patterns = BLOCKED_PATTERNS[tool15];
3259
2906
  if (!patterns)
3260
2907
  return null;
3261
- if (tool19 === "bash") {
2908
+ if (tool15 === "bash") {
3262
2909
  const cmd = args.command;
3263
2910
  if (!cmd)
3264
2911
  return null;
@@ -3269,7 +2916,7 @@ function isBlocked(tool19, args) {
3269
2916
  }
3270
2917
  return null;
3271
2918
  }
3272
- if (tool19 === "read") {
2919
+ if (tool15 === "read") {
3273
2920
  const filePath = args.filePath;
3274
2921
  if (!filePath)
3275
2922
  return null;
@@ -3280,7 +2927,7 @@ function isBlocked(tool19, args) {
3280
2927
  }
3281
2928
  return null;
3282
2929
  }
3283
- if (tool19 === "write") {
2930
+ if (tool15 === "write") {
3284
2931
  const filePath = args.filePath;
3285
2932
  if (!filePath)
3286
2933
  return null;
@@ -3294,11 +2941,11 @@ function isBlocked(tool19, args) {
3294
2941
  return null;
3295
2942
  }
3296
2943
  function checkArchConstraint(directory, filePath) {
3297
- const constraintsPath = join24(codebaseDir(directory), "CONSTRAINTS.md");
3298
- if (!existsSync25(constraintsPath))
2944
+ const constraintsPath = join20(codebaseDir(directory), "CONSTRAINTS.md");
2945
+ if (!existsSync21(constraintsPath))
3299
2946
  return null;
3300
2947
  try {
3301
- const content = readFileSync24(constraintsPath, "utf-8");
2948
+ const content = readFileSync21(constraintsPath, "utf-8");
3302
2949
  const match = content.match(/## Forbidden Paths\n([\s\S]*?)(?:\n##|$)/);
3303
2950
  if (!match)
3304
2951
  return null;
@@ -3339,9 +2986,9 @@ function isUiDesignApprovalRequired(directory) {
3339
2986
  return !(state.design_stage === "handoff_complete" && state.design_approved);
3340
2987
  }
3341
2988
  const planPath = phasePlanPath(directory, state.phase || 1);
3342
- if (!existsSync25(planPath))
2989
+ if (!existsSync21(planPath))
3343
2990
  return false;
3344
- const planContent = readFileSync24(planPath, "utf-8");
2991
+ const planContent = readFileSync21(planPath, "utf-8");
3345
2992
  if (!isUiHeavyTask(planContent))
3346
2993
  return false;
3347
2994
  return !(state.design_stage === "handoff_complete" && state.design_approved);
@@ -3370,18 +3017,18 @@ async function toolGuardHook(ctx, input, output) {
3370
3017
  }
3371
3018
 
3372
3019
  // src/hooks/session-start.ts
3373
- import { existsSync as existsSync26, readFileSync as readFileSync25 } from "fs";
3020
+ import { existsSync as existsSync22, readFileSync as readFileSync22 } from "fs";
3374
3021
  async function sessionStartHook(ctx) {
3375
3022
  const planningDir2 = ctx.directory + "/.planning";
3376
3023
  const codebaseDirectory = codebaseDir(ctx.directory);
3377
3024
  const workspaceRoot = findWorkspaceRoot(ctx.directory);
3378
3025
  const config = workspaceRoot ? getWorkspaceConfig(ctx.directory) : null;
3379
- if (!existsSync26(planningDir2)) {
3026
+ if (!existsSync22(planningDir2)) {
3380
3027
  return {
3381
3028
  flowdeck_phase: null,
3382
3029
  flowdeck_status: "no_plan",
3383
3030
  flowdeck_warning: "Run /fd-map-codebase to index the codebase, then /fd-new-feature to start a feature.",
3384
- flowdeck_has_codebase: existsSync26(codebaseDirectory),
3031
+ flowdeck_has_codebase: existsSync22(codebaseDirectory),
3385
3032
  ...workspaceRoot && config?.sub_repos ? {
3386
3033
  flowdeck_workspace_root: workspaceRoot,
3387
3034
  flowdeck_sub_repos: config.sub_repos,
@@ -3392,7 +3039,7 @@ async function sessionStartHook(ctx) {
3392
3039
  }
3393
3040
  try {
3394
3041
  const stateFilePath = statePath(ctx.directory);
3395
- const content = readFileSync25(stateFilePath, "utf-8");
3042
+ const content = readFileSync22(stateFilePath, "utf-8");
3396
3043
  const state = parseState(content);
3397
3044
  const currentPhase = state["current_phase"] || {};
3398
3045
  const result = {
@@ -3400,7 +3047,7 @@ async function sessionStartHook(ctx) {
3400
3047
  flowdeck_status: currentPhase["status"] ?? null,
3401
3048
  flowdeck_steps_pending: currentPhase["steps_pending"] ?? null,
3402
3049
  flowdeck_last_action: currentPhase["last_action"] ?? null,
3403
- flowdeck_has_codebase: existsSync26(codebaseDirectory)
3050
+ flowdeck_has_codebase: existsSync22(codebaseDirectory)
3404
3051
  };
3405
3052
  if (workspaceRoot && config?.sub_repos && config.sub_repos.length > 0) {
3406
3053
  result.flowdeck_workspace_root = workspaceRoot;
@@ -3415,7 +3062,7 @@ async function sessionStartHook(ctx) {
3415
3062
  flowdeck_phase: null,
3416
3063
  flowdeck_status: "error",
3417
3064
  flowdeck_warning: "State file unreadable. Continuing without flowdeck context.",
3418
- flowdeck_has_codebase: existsSync26(codebaseDirectory)
3065
+ flowdeck_has_codebase: existsSync22(codebaseDirectory)
3419
3066
  };
3420
3067
  if (workspaceRoot && config?.sub_repos && config.sub_repos.length > 0) {
3421
3068
  result.flowdeck_workspace_root = workspaceRoot;
@@ -3554,13 +3201,13 @@ class NotificationController {
3554
3201
  return this.lastNotifiedKey;
3555
3202
  }
3556
3203
  }
3557
- function notifyPermissionNeeded(tool19) {
3558
- 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");
3559
3206
  }
3560
3207
 
3561
3208
  // src/hooks/patch-trust.ts
3562
- import { existsSync as existsSync27, readFileSync as readFileSync26 } from "fs";
3563
- import { join as join25 } from "path";
3209
+ import { existsSync as existsSync23, readFileSync as readFileSync23 } from "fs";
3210
+ import { join as join21 } from "path";
3564
3211
  var HIGH_RISK_KEYWORDS = [
3565
3212
  "password",
3566
3213
  "secret",
@@ -3581,26 +3228,12 @@ var HIGH_RISK_KEYWORDS = [
3581
3228
  "root",
3582
3229
  "privilege"
3583
3230
  ];
3584
- function loadVolatility(directory) {
3585
- const p = join25(codebaseDir(directory), "VOLATILITY.json");
3586
- if (!existsSync27(p))
3587
- return {};
3588
- try {
3589
- const data = JSON.parse(readFileSync26(p, "utf-8"));
3590
- const map = {};
3591
- for (const entry of data.entries ?? [])
3592
- map[entry.path] = entry.stability;
3593
- return map;
3594
- } catch {
3595
- return {};
3596
- }
3597
- }
3598
3231
  function loadFailedPaths(directory) {
3599
- const p = join25(codebaseDir(directory), "FAILURES.json");
3600
- if (!existsSync27(p))
3232
+ const p = join21(codebaseDir(directory), "FAILURES.json");
3233
+ if (!existsSync23(p))
3601
3234
  return [];
3602
3235
  try {
3603
- const data = JSON.parse(readFileSync26(p, "utf-8"));
3236
+ const data = JSON.parse(readFileSync23(p, "utf-8"));
3604
3237
  return (data.entries ?? []).flatMap((e) => e.affected_paths ?? []);
3605
3238
  } catch {
3606
3239
  return [];
@@ -3609,18 +3242,6 @@ function loadFailedPaths(directory) {
3609
3242
  function scorePatch(directory, filePath, content) {
3610
3243
  let score = 100;
3611
3244
  const signals = [];
3612
- const volatility = loadVolatility(directory);
3613
- const stability = Object.entries(volatility).find(([path]) => filePath.includes(path))?.[1];
3614
- if (stability === "critical") {
3615
- score -= 40;
3616
- signals.push("file is in critical volatility zone");
3617
- } else if (stability === "volatile") {
3618
- score -= 25;
3619
- signals.push("file is in volatile zone");
3620
- } else if (stability === "moderate") {
3621
- score -= 10;
3622
- signals.push("file has moderate churn");
3623
- }
3624
3245
  const failedPaths = loadFailedPaths(directory);
3625
3246
  if (failedPaths.some((p) => filePath.includes(p))) {
3626
3247
  score -= 20;
@@ -3665,8 +3286,8 @@ async function patchTrustHook(ctx, input, output) {
3665
3286
  }
3666
3287
 
3667
3288
  // src/hooks/decision-trace-hook.ts
3668
- import { existsSync as existsSync28, mkdirSync as mkdirSync14, appendFileSync as appendFileSync4 } from "fs";
3669
- 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";
3670
3291
  async function decisionTraceHook(ctx, input, output) {
3671
3292
  if (input.tool !== "write" && input.tool !== "edit")
3672
3293
  return;
@@ -3675,8 +3296,8 @@ async function decisionTraceHook(ctx, input, output) {
3675
3296
  return;
3676
3297
  const base = codebaseDir(ctx.directory);
3677
3298
  try {
3678
- if (!existsSync28(base))
3679
- mkdirSync14(base, { recursive: true });
3299
+ if (!existsSync24(base))
3300
+ mkdirSync12(base, { recursive: true });
3680
3301
  const entry = {
3681
3302
  timestamp: new Date().toISOString(),
3682
3303
  file_path: filePath,
@@ -3688,87 +3309,14 @@ async function decisionTraceHook(ctx, input, output) {
3688
3309
  risk_level: "unknown",
3689
3310
  auto_recorded: true
3690
3311
  };
3691
- appendFileSync4(join26(base, "DECISIONS.jsonl"), JSON.stringify(entry) + `
3312
+ appendFileSync4(join22(base, "DECISIONS.jsonl"), JSON.stringify(entry) + `
3692
3313
  `, "utf-8");
3693
3314
  } catch {}
3694
3315
  }
3695
3316
 
3696
- // src/services/telemetry.ts
3697
- import { existsSync as existsSync29, readFileSync as readFileSync27, appendFileSync as appendFileSync5, mkdirSync as mkdirSync15 } from "fs";
3698
- import { join as join27 } from "path";
3699
- import { randomUUID } from "crypto";
3700
- function telemetryPath(dir) {
3701
- return join27(codebaseDir(dir), "TELEMETRY.jsonl");
3702
- }
3703
- function appendEvent2(dir, partial) {
3704
- if (process.env.TELEMETRY_ENABLED !== "true")
3705
- return null;
3706
- const cd = codebaseDir(dir);
3707
- if (!existsSync29(cd))
3708
- mkdirSync15(cd, { recursive: true });
3709
- const event = {
3710
- id: randomUUID(),
3711
- ts: new Date().toISOString(),
3712
- ...partial
3713
- };
3714
- appendFileSync5(telemetryPath(dir), JSON.stringify(event) + `
3715
- `, "utf-8");
3716
- return event;
3717
- }
3718
-
3719
- // src/hooks/telemetry-hook.ts
3720
- function resolveIds(toolInput) {
3721
- const session_id = toolInput.sessionID ?? toolInput.sessionId ?? process.env.OPENCODE_SESSION_ID ?? "session-0";
3722
- const run_id = toolInput.messageID ?? toolInput.messageId ?? toolInput.runID ?? toolInput.runId ?? process.env.OPENCODE_RUN_ID ?? "run-0";
3723
- return { session_id, run_id };
3724
- }
3725
- function inferStatus(output) {
3726
- if (output.error)
3727
- return "error";
3728
- if (typeof output.output !== "string")
3729
- return "ok";
3730
- const text = output.output.trim();
3731
- if (!text)
3732
- return "ok";
3733
- try {
3734
- const parsed = JSON.parse(text);
3735
- if (parsed.success === false || parsed.error || parsed.status === "error")
3736
- return "error";
3737
- return "ok";
3738
- } catch {
3739
- return "ok";
3740
- }
3741
- }
3742
- async function telemetryHook(context, toolInput, output) {
3743
- const dir = context.directory ?? process.cwd();
3744
- const tool19 = toolInput.name ?? toolInput.tool ?? "unknown";
3745
- const ids = resolveIds(toolInput);
3746
- appendEvent2(dir, {
3747
- session_id: ids.session_id,
3748
- run_id: ids.run_id,
3749
- event: "tool.call",
3750
- tool: tool19,
3751
- status: "ok",
3752
- meta: { parameters: output.args ?? {} }
3753
- });
3754
- }
3755
- async function telemetryAfterHook(context, toolInput, output) {
3756
- const dir = context.directory ?? process.cwd();
3757
- const tool19 = toolInput.name ?? toolInput.tool ?? "unknown";
3758
- const ids = resolveIds(toolInput);
3759
- const status = inferStatus(output);
3760
- appendEvent2(dir, {
3761
- session_id: ids.session_id,
3762
- run_id: ids.run_id,
3763
- event: "tool.complete",
3764
- tool: tool19,
3765
- status
3766
- });
3767
- }
3768
-
3769
3317
  // src/services/approval-manager.ts
3770
- import { existsSync as existsSync30, readFileSync as readFileSync28, writeFileSync as writeFileSync17, mkdirSync as mkdirSync16 } from "fs";
3771
- 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";
3772
3320
  var APPROVAL_TTL_MS = 30 * 60 * 1000;
3773
3321
  var SENSITIVE_PATTERNS = [
3774
3322
  /auth/i,
@@ -3805,14 +3353,14 @@ function isSensitivePath(filePath) {
3805
3353
  return SENSITIVE_PATTERNS.some((p) => p.test(filePath));
3806
3354
  }
3807
3355
  function approvalsPath(dir) {
3808
- return join28(codebaseDir(dir), "APPROVALS.json");
3356
+ return join23(codebaseDir(dir), "APPROVALS.json");
3809
3357
  }
3810
3358
  function loadStore2(dir) {
3811
3359
  const p = approvalsPath(dir);
3812
- if (!existsSync30(p))
3360
+ if (!existsSync25(p))
3813
3361
  return { requests: [] };
3814
3362
  try {
3815
- return JSON.parse(readFileSync28(p, "utf-8"));
3363
+ return JSON.parse(readFileSync24(p, "utf-8"));
3816
3364
  } catch {
3817
3365
  return { requests: [] };
3818
3366
  }
@@ -3830,8 +3378,8 @@ async function approvalHook(context, toolInput, output) {
3830
3378
  if (!ENABLED2)
3831
3379
  return;
3832
3380
  const dir = context.directory ?? process.cwd();
3833
- const tool19 = toolInput.name ?? toolInput.tool ?? "";
3834
- if (!WRITE_TOOLS.has(tool19))
3381
+ const tool15 = toolInput.name ?? toolInput.tool ?? "";
3382
+ if (!WRITE_TOOLS.has(tool15))
3835
3383
  return;
3836
3384
  const args = output.args ?? {};
3837
3385
  const filePath = String(args.path ?? args.file_path ?? args.filename ?? "");
@@ -3842,20 +3390,305 @@ async function approvalHook(context, toolInput, output) {
3842
3390
  const approval = checkApproval(dir, filePath, "");
3843
3391
  if (approval)
3844
3392
  return;
3845
- appendEvent2(dir, {
3846
- session_id: process.env.OPENCODE_SESSION_ID ?? "session-0",
3847
- run_id: process.env.OPENCODE_RUN_ID ?? "run-0",
3848
- event: "approval.request",
3849
- tool: tool19,
3850
- status: "blocked",
3851
- files: [filePath],
3852
- meta: { trigger: "sensitive_file", file: filePath }
3853
- });
3854
3393
  throw new Error(`APPROVAL_REQUIRED: "${filePath}" is a sensitive file (auth/payment/secrets/infra).
3855
3394
  ` + `Risk level: HIGH — manual approval needed before editing.
3856
3395
  ` + `To proceed: run /fd-guarded-edit --file "${filePath}" to review and approve this change.`);
3857
3396
  }
3858
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
+
3859
3692
  // src/hooks/context-window-monitor.ts
3860
3693
  var CONTEXT_WARNING_THRESHOLD = 0.7;
3861
3694
  var DEFAULT_CONTEXT_LIMIT = Number(process.env.FLOWDECK_CONTEXT_LIMIT) || 200000;
@@ -3907,8 +3740,8 @@ function createContextWindowMonitorHook() {
3907
3740
  }
3908
3741
 
3909
3742
  // src/hooks/shell-env-hook.ts
3910
- import { existsSync as existsSync31, readFileSync as readFileSync29 } from "fs";
3911
- import { join as join29 } from "path";
3743
+ import { existsSync as existsSync27, readFileSync as readFileSync26 } from "fs";
3744
+ import { join as join25 } from "path";
3912
3745
  import { createRequire as createRequire2 } from "module";
3913
3746
  var _version;
3914
3747
  function getVersion() {
@@ -3944,7 +3777,7 @@ var MARKER_TO_LANG = {
3944
3777
  };
3945
3778
  function detectPackageManager(root) {
3946
3779
  for (const [lockfile, pm] of Object.entries(LOCKFILE_TO_PM)) {
3947
- if (existsSync31(join29(root, lockfile)))
3780
+ if (existsSync27(join25(root, lockfile)))
3948
3781
  return pm;
3949
3782
  }
3950
3783
  return;
@@ -3953,7 +3786,7 @@ function detectLanguages(root) {
3953
3786
  const langs = [];
3954
3787
  const seen = new Set;
3955
3788
  for (const [marker, lang] of Object.entries(MARKER_TO_LANG)) {
3956
- if (!seen.has(lang) && existsSync31(join29(root, marker))) {
3789
+ if (!seen.has(lang) && existsSync27(join25(root, marker))) {
3957
3790
  langs.push(lang);
3958
3791
  seen.add(lang);
3959
3792
  }
@@ -3961,11 +3794,11 @@ function detectLanguages(root) {
3961
3794
  return langs;
3962
3795
  }
3963
3796
  function readCurrentPhase(root) {
3964
- const statePath2 = join29(root, ".planning", "STATE.md");
3965
- if (!existsSync31(statePath2))
3797
+ const statePath2 = join25(root, ".planning", "STATE.md");
3798
+ if (!existsSync27(statePath2))
3966
3799
  return;
3967
3800
  try {
3968
- const content = readFileSync29(statePath2, "utf-8");
3801
+ const content = readFileSync26(statePath2, "utf-8");
3969
3802
  const match = content.match(/phase:\s*(\S+)/i);
3970
3803
  return match?.[1];
3971
3804
  } catch {
@@ -4090,8 +3923,8 @@ function createSessionIdleHook(client, tracker) {
4090
3923
  }
4091
3924
 
4092
3925
  // src/hooks/compaction-hook.ts
4093
- import { existsSync as existsSync32, readFileSync as readFileSync30 } from "fs";
4094
- import { join as join30 } from "path";
3926
+ import { existsSync as existsSync28, readFileSync as readFileSync27 } from "fs";
3927
+ import { join as join26 } from "path";
4095
3928
  var STRUCTURED_SUMMARY_PROMPT = `
4096
3929
  When summarizing this session, you MUST include the following sections:
4097
3930
 
@@ -4132,10 +3965,10 @@ For each: agent name, status, description, session_id.
4132
3965
  var _lastInjected = new Map;
4133
3966
  function readPlanningState2(directory) {
4134
3967
  const sp = statePath(directory);
4135
- if (!existsSync32(sp))
3968
+ if (!existsSync28(sp))
4136
3969
  return null;
4137
3970
  try {
4138
- const content = readFileSync30(sp, "utf-8");
3971
+ const content = readFileSync27(sp, "utf-8");
4139
3972
  const parsed = parseState(content);
4140
3973
  const version = typeof parsed.summaryVersion === "number" ? parsed.summaryVersion : 0;
4141
3974
  return { content: content.slice(0, 1500), version };
@@ -4164,15 +3997,15 @@ function createCompactionHook(ctx, tracker) {
4164
3997
  sections.push(`_State unchanged since last compaction. summaryVersion=${currentStateVersion}_`);
4165
3998
  sections.push("");
4166
3999
  }
4167
- const indexPath2 = join30(ctx.directory, ".planning", "CODEBASE_INDEX.md");
4168
- if (indexChanged && existsSync32(indexPath2)) {
4000
+ const indexPath2 = join26(ctx.directory, ".planning", "CODEBASE_INDEX.md");
4001
+ if (indexChanged && existsSync28(indexPath2)) {
4169
4002
  try {
4170
- const indexContent = readFileSync30(indexPath2, "utf-8");
4003
+ const indexContent = readFileSync27(indexPath2, "utf-8");
4171
4004
  const indexSummary = "\n## Codebase Index\n```\n" + indexContent.slice(0, 800) + "\n```\n";
4172
4005
  sections.push(indexSummary);
4173
4006
  sections.push("");
4174
4007
  } catch {}
4175
- } else if (existsSync32(indexPath2)) {
4008
+ } else if (existsSync28(indexPath2)) {
4176
4009
  sections.push(`## Codebase Index (unchanged, v${currentIndexVersion})`);
4177
4010
  sections.push(`_Index unchanged since last compaction. summaryVersion=${currentIndexVersion}_`);
4178
4011
  sections.push("");
@@ -4223,12 +4056,9 @@ var ALWAYS_ALLOWED = new Set([
4223
4056
  "council",
4224
4057
  "planning-state",
4225
4058
  "codebase-state",
4226
- "workspace-state",
4227
4059
  "repo-memory",
4228
4060
  "decision-trace",
4229
4061
  "policy-engine",
4230
- "context-generator",
4231
- "create-skill",
4232
4062
  "reflect"
4233
4063
  ]);
4234
4064
  function isDelegationTool(name) {
@@ -4339,7 +4169,7 @@ async function runAutoLearner(client, directory, appLog) {
4339
4169
  parts: [
4340
4170
  {
4341
4171
  type: "text",
4342
- 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."
4343
4173
  }
4344
4174
  ],
4345
4175
  tools: { question: false }
@@ -4573,8 +4403,8 @@ Please advise.
4573
4403
  ## Self-Learning
4574
4404
 
4575
4405
  When a task required unusual human guidance, a novel solution strategy, or exposed a knowledge gap:
4576
- 1. After the task completes successfully, call the \`create-skill\` tool to capture the pattern
4577
- 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
4578
4408
  3. Include: When to Activate, Steps, Examples, and Pitfalls sections
4579
4409
 
4580
4410
  Do NOT create a skill for routine tasks. Only capture genuinely novel or reusable patterns.`;
@@ -6720,7 +6550,6 @@ You receive a structured context with:
6720
6550
  - \`file_path\`: optional specific file being changed
6721
6551
  - \`trust_score\`: patch trust score (0–100; 80+ = safe, 40–79 = review-required, <40 = high-risk)
6722
6552
  - \`trust_signals\`: list of risk signals from the patch trust scorer
6723
- - \`volatile_zones\`: paths marked as volatile or critical in VOLATILITY.json
6724
6553
  - \`prior_failures\`: failure entries from FAILURES.json that match this change
6725
6554
  - \`regression_categories\`: predicted regression categories for this change
6726
6555
  - \`confidence\`: system confidence score (0–100; based on how much codebase context data exists)
@@ -7179,7 +7008,7 @@ var AUTO_LEARNER_PROMPT = `You run automatically after a coding session to captu
7179
7008
  - Novel solutions that took non-obvious reasoning
7180
7009
  - Recurring tool sequences that indicate a reusable workflow
7181
7010
  - Knowledge gaps that had to be worked out from scratch
7182
- 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.
7183
7012
  4. If nothing is worth capturing, output exactly: "No new skills identified."
7184
7013
  5. End with a one-line summary: "Auto-learn complete: N skill(s) created."
7185
7014
 
@@ -7544,12 +7373,9 @@ var CONTRACTS = [
7544
7373
  "council",
7545
7374
  "planning-state",
7546
7375
  "codebase-state",
7547
- "workspace-state",
7548
7376
  "repo-memory",
7549
7377
  "decision-trace",
7550
7378
  "policy-engine",
7551
- "context-generator",
7552
- "create-skill",
7553
7379
  "reflect"
7554
7380
  ],
7555
7381
  forbiddenActions: [
@@ -7584,7 +7410,7 @@ var CONTRACTS = [
7584
7410
  allowedTaskTypes: ["planning", "task-breakdown", "step-decomposition"],
7585
7411
  requiredInputs: ["task description or STATE.md"],
7586
7412
  expectedOutputFields: ["steps", "phase"],
7587
- allowedTools: ["read", "glob", "grep", "planning-state", "workspace-state"],
7413
+ allowedTools: ["read", "glob", "grep", "planning-state"],
7588
7414
  forbiddenActions: [
7589
7415
  "write source files",
7590
7416
  "run bash commands",
@@ -8100,7 +7926,6 @@ function runSupervisorReview(directory, targetName, ctx = {}, clarificationQuest
8100
7926
  reviewPhase,
8101
7927
  timestamp: timestamp2
8102
7928
  };
8103
- _emitTelemetry(directory, decision2, ctx);
8104
7929
  return decision2;
8105
7930
  }
8106
7931
  const policyResult = targetType === "command" ? checkCommandPolicy(targetName, ctx) : checkAgentPolicy(targetName, ctx);
@@ -8122,7 +7947,6 @@ function runSupervisorReview(directory, targetName, ctx = {}, clarificationQuest
8122
7947
  timestamp: timestamp2,
8123
7948
  ...escalationQuestion ? { clarificationQuestion: escalationQuestion } : {}
8124
7949
  };
8125
- _emitTelemetry(directory, supervisorDecision, ctx);
8126
7950
  return supervisorDecision;
8127
7951
  }
8128
7952
  function shouldProceed(decision, mode, canBlock) {
@@ -8135,33 +7959,12 @@ function shouldProceed(decision, mode, canBlock) {
8135
7959
  }
8136
7960
  return decision.decision !== "block" || decision.confidenceScore > 0.3;
8137
7961
  }
8138
- function _emitTelemetry(directory, decision, ctx) {
8139
- try {
8140
- appendEvent2(directory, {
8141
- session_id: ctx.session_id ?? "session-0",
8142
- run_id: ctx.run_id ?? "unknown",
8143
- event: "supervisor.review",
8144
- agent: "supervisor",
8145
- status: decision.decision === "approve" ? "ok" : decision.decision === "block" ? "blocked" : decision.decision === "escalate" ? "approved" : "ok",
8146
- meta: {
8147
- targetName: decision.targetName,
8148
- targetType: decision.targetType,
8149
- exists: decision.exists,
8150
- decision: decision.decision,
8151
- confidenceScore: decision.confidenceScore,
8152
- riskFlags: decision.riskFlags,
8153
- missingRequirements: decision.missingRequirements,
8154
- reviewPhase: decision.reviewPhase
8155
- }
8156
- });
8157
- } catch {}
8158
- }
8159
7962
 
8160
7963
  // src/index.ts
8161
7964
  function lazyLoadRulePaths(projectRoot) {
8162
- const __dir = dirname5(fileURLToPath3(import.meta.url));
8163
- const rulesDir = join31(__dir, "..", "src", "rules");
8164
- if (!existsSync33(rulesDir))
7965
+ const __dir = dirname3(fileURLToPath2(import.meta.url));
7966
+ const rulesDir = join27(__dir, "..", "src", "rules");
7967
+ if (!existsSync29(rulesDir))
8165
7968
  return { paths: [], diagnostics: "[LazyRuleLoader] rules directory not found" };
8166
7969
  const detectedLanguages = detectProjectLanguages(projectRoot);
8167
7970
  const paths = getStartupRulePaths(rulesDir, detectedLanguages);
@@ -8170,17 +7973,17 @@ function lazyLoadRulePaths(projectRoot) {
8170
7973
  return { paths, diagnostics };
8171
7974
  }
8172
7975
  function loadCommands() {
8173
- const __dir = dirname5(fileURLToPath3(import.meta.url));
8174
- const commandsDir = join31(__dir, "..", "src", "commands");
8175
- if (!existsSync33(commandsDir))
7976
+ const __dir = dirname3(fileURLToPath2(import.meta.url));
7977
+ const commandsDir = join27(__dir, "..", "src", "commands");
7978
+ if (!existsSync29(commandsDir))
8176
7979
  return {};
8177
7980
  const commands = {};
8178
7981
  try {
8179
- for (const file of readdirSync5(commandsDir)) {
7982
+ for (const file of readdirSync4(commandsDir)) {
8180
7983
  if (!file.endsWith(".md"))
8181
7984
  continue;
8182
7985
  const name = basename2(file, ".md");
8183
- const raw = readFileSync31(join31(commandsDir, file), "utf-8");
7986
+ const raw = readFileSync28(join27(commandsDir, file), "utf-8");
8184
7987
  let description;
8185
7988
  let template = raw;
8186
7989
  const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
@@ -8197,6 +8000,7 @@ function loadCommands() {
8197
8000
  }
8198
8001
  var plugin = async (input, _options) => {
8199
8002
  const { directory, client, worktree } = input;
8003
+ const appLog = (msg) => client.app.log({ body: { service: "flowdeck", level: "info", message: msg } }).catch(() => {});
8200
8004
  const runPipelineTool = createRunPipelineTool(client);
8201
8005
  const delegateTool = createDelegateTool(client);
8202
8006
  const councilTool = createCouncilTool(client);
@@ -8208,11 +8012,11 @@ var plugin = async (input, _options) => {
8208
8012
  const sessionIdleHook = createSessionIdleHook(client, fileTracker);
8209
8013
  const compactionHook = createCompactionHook({ directory }, fileTracker);
8210
8014
  const orchestratorGuard = new OrchestratorGuard;
8211
- const appLog = (msg) => client.app.log({ body: { service: "flowdeck", level: "info", message: msg } }).catch(() => {});
8212
8015
  const autoLearnHook = createAutoLearnHook(client, fileTracker, directory, appLog);
8213
8016
  const notifCtrl = new NotificationController(undefined, appLog);
8214
8017
  const agentConfigs = getAgentConfigs({});
8215
8018
  const mcps = createFlowDeckMcps();
8019
+ let lastExecutedCommand = null;
8216
8020
  return {
8217
8021
  name: "@dv.nghiem/flowdeck",
8218
8022
  agent: agentConfigs,
@@ -8262,8 +8066,8 @@ var plugin = async (input, _options) => {
8262
8066
  }
8263
8067
  }
8264
8068
  }
8265
- const skillsDir = join31(dirname5(fileURLToPath3(import.meta.url)), "..", "src", "skills");
8266
- if (existsSync33(skillsDir)) {
8069
+ const skillsDir = join27(dirname3(fileURLToPath2(import.meta.url)), "..", "src", "skills");
8070
+ if (existsSync29(skillsDir)) {
8267
8071
  const cfgAny = cfg;
8268
8072
  if (!cfgAny.skills || typeof cfgAny.skills !== "object") {
8269
8073
  cfgAny.skills = { paths: [] };
@@ -8292,18 +8096,14 @@ var plugin = async (input, _options) => {
8292
8096
  tool: {
8293
8097
  "planning-state": planningStateTool,
8294
8098
  "codebase-state": codebaseStateTool,
8295
- "workspace-state": workspaceStateTool,
8296
8099
  "run-pipeline": runPipelineTool,
8297
8100
  delegate: delegateTool,
8298
8101
  "repo-memory": repoMemoryTool,
8299
8102
  "failure-replay": failureReplayTool,
8300
8103
  "decision-trace": decisionTraceTool,
8301
- "volatility-map": volatilityMapTool,
8302
8104
  "policy-engine": policyEngineTool,
8303
8105
  "hash-edit": hashEditTool,
8304
8106
  council: councilTool,
8305
- "context-generator": contextGeneratorTool,
8306
- "create-skill": createSkillTool,
8307
8107
  reflect: reflectTool,
8308
8108
  codegraph: codegraphTool,
8309
8109
  "load-rules": loadRulesTool,
@@ -8315,7 +8115,9 @@ var plugin = async (input, _options) => {
8315
8115
  "file.edited": fileEdited,
8316
8116
  "file.watcher.updated": fileWatcherUpdated,
8317
8117
  "experimental.session.compacting": compactionHook,
8318
- "command.execute.before": async (_input, _output) => {},
8118
+ "command.execute.before": async (input2, _output) => {
8119
+ lastExecutedCommand = input2.command;
8120
+ },
8319
8121
  "permission.ask": async (input2, _output) => {
8320
8122
  notifyPermissionNeeded(input2.title);
8321
8123
  },
@@ -8323,6 +8125,9 @@ var plugin = async (input, _options) => {
8323
8125
  const type = event?.type ?? "";
8324
8126
  if (type === "session.created" || type === "session.started") {
8325
8127
  await sessionStartHook({ directory });
8128
+ if (type === "session.created") {
8129
+ await eventLogSessionHook({ directory }, event);
8130
+ }
8326
8131
  }
8327
8132
  if (type === "command.executed") {
8328
8133
  const commandName = event?.properties?.name ?? "";
@@ -8333,7 +8138,11 @@ var plugin = async (input, _options) => {
8333
8138
  await contextMonitor.event({ event });
8334
8139
  orchestratorGuard.onEvent(event);
8335
8140
  if (type === "session.idle") {
8141
+ await eventLogSessionHook({ directory }, event);
8336
8142
  const hasEdits = fileTracker.getEditedPaths().length > 0;
8143
+ if (lastExecutedCommand) {
8144
+ lastExecutedCommand = null;
8145
+ }
8337
8146
  notifCtrl.onSessionIdle(hasEdits);
8338
8147
  try {
8339
8148
  await sessionIdleHook();
@@ -8343,6 +8152,8 @@ var plugin = async (input, _options) => {
8343
8152
  }
8344
8153
  }
8345
8154
  if (type === "session.error") {
8155
+ await eventLogSessionHook({ directory }, event);
8156
+ lastExecutedCommand = null;
8346
8157
  const err = event?.properties?.error;
8347
8158
  const errorMsg = (err && typeof err === "object" && "message" in err ? String(err.message) : undefined) ?? (typeof err === "string" ? err : undefined) ?? "An unexpected error occurred";
8348
8159
  notifCtrl.onSessionError(errorMsg);
@@ -8387,15 +8198,15 @@ var plugin = async (input, _options) => {
8387
8198
  }
8388
8199
  }
8389
8200
  }
8390
- await telemetryHook({ directory }, toolInput, toolOutput);
8391
8201
  await approvalHook({ directory }, toolInput, toolOutput);
8392
8202
  await guardRailsHook({ directory }, toolInput, toolOutput);
8393
8203
  await toolGuardHook({ directory }, toolInput, toolOutput);
8394
8204
  await patchTrustHook({ directory }, toolInput, toolOutput);
8395
8205
  await decisionTraceHook({ directory }, toolInput, toolOutput);
8206
+ await eventLogBeforeHook({ directory }, toolInput, toolOutput);
8396
8207
  },
8397
8208
  "tool.execute.after": async (toolInput, toolOutput) => {
8398
- await telemetryAfterHook({ directory }, toolInput, toolOutput);
8209
+ await eventLogAfterHook({ directory }, toolInput, toolOutput);
8399
8210
  const afterToolName = toolInput.tool ?? toolInput.name ?? "";
8400
8211
  if (afterToolName === "delegate" || afterToolName === "run-pipeline") {
8401
8212
  try {