@dv.nghiem/flowdeck 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/README.md +24 -41
  2. package/dist/hooks/memory-hook.d.ts +21 -0
  3. package/dist/hooks/memory-hook.d.ts.map +1 -0
  4. package/dist/hooks/orchestrator-guard-hook.d.ts +2 -1
  5. package/dist/hooks/orchestrator-guard-hook.d.ts.map +1 -1
  6. package/dist/hooks/todo-hook.d.ts +1 -7
  7. package/dist/hooks/todo-hook.d.ts.map +1 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +649 -310
  10. package/dist/services/memory-store.d.ts +40 -0
  11. package/dist/services/memory-store.d.ts.map +1 -0
  12. package/dist/services/telemetry.d.ts +1 -1
  13. package/dist/services/telemetry.d.ts.map +1 -1
  14. package/dist/tools/memory-search.d.ts +3 -0
  15. package/dist/tools/memory-search.d.ts.map +1 -0
  16. package/docs/commands/fd-doctor.md +21 -0
  17. package/docs/commands/fd-quick.md +33 -0
  18. package/docs/commands/fd-reflect.md +23 -0
  19. package/docs/commands/fd-status.md +31 -0
  20. package/docs/commands/fd-translate-intent.md +17 -0
  21. package/docs/commands.md +209 -271
  22. package/docs/configuration.md +2 -1
  23. package/docs/index.md +22 -28
  24. package/docs/memory.md +69 -0
  25. package/docs/quick-start.md +1 -1
  26. package/docs/workflows.md +72 -320
  27. package/package.json +1 -2
  28. package/src/commands/fd-deploy-check.md +189 -34
  29. package/src/commands/fd-discuss.md +44 -6
  30. package/src/commands/fd-fix-bug.md +47 -20
  31. package/src/commands/fd-map-codebase.md +66 -18
  32. package/src/commands/fd-multi-repo.md +130 -6
  33. package/src/commands/fd-new-feature.md +164 -21
  34. package/src/commands/fd-new-project.md +14 -1
  35. package/src/commands/fd-plan.md +66 -44
  36. package/src/commands/fd-quick.md +60 -0
  37. package/src/commands/fd-reflect.md +41 -2
  38. package/src/commands/fd-status.md +84 -0
  39. package/src/commands/fd-write-docs.md +55 -23
  40. package/src/rules/README.md +8 -7
  41. package/src/skills/agent-harness-construction/SKILL.md +227 -0
  42. package/src/skills/api-design/SKILL.md +5 -0
  43. package/src/skills/backend-patterns/SKILL.md +105 -0
  44. package/src/skills/clean-architecture/SKILL.md +85 -0
  45. package/src/skills/cqrs/SKILL.md +230 -0
  46. package/src/skills/ddd-architecture/SKILL.md +104 -0
  47. package/src/skills/django-patterns/SKILL.md +304 -0
  48. package/src/skills/django-tdd/SKILL.md +297 -0
  49. package/src/skills/event-driven-architecture/SKILL.md +152 -0
  50. package/src/skills/frontend-pattern/SKILL.md +159 -0
  51. package/src/skills/hexagonal-architecture/SKILL.md +80 -0
  52. package/src/skills/layered-architecture/SKILL.md +64 -0
  53. package/src/skills/postgres-patterns/SKILL.md +74 -0
  54. package/src/skills/python-patterns/SKILL.md +5 -0
  55. package/src/skills/saga-architecture/SKILL.md +113 -0
  56. package/dist/tools/run-parallel.d.ts +0 -4
  57. package/dist/tools/run-parallel.d.ts.map +0 -1
  58. package/docs/command-migration.md +0 -175
  59. package/docs/commands/fd-analyze-change.md +0 -107
  60. package/docs/commands/fd-dashboard.md +0 -11
  61. package/docs/commands/fd-evaluate-risk.md +0 -134
  62. package/docs/commands/fd-guarded-edit.md +0 -105
  63. package/docs/commands/fd-progress.md +0 -11
  64. package/docs/commands/fd-review-code.md +0 -29
  65. package/docs/commands/fd-roadmap.md +0 -10
  66. package/docs/commands/fd-settings.md +0 -10
  67. package/docs/parallel-execution.md +0 -227
  68. package/src/commands/fd-analyze-change.md +0 -57
  69. package/src/commands/fd-approve.md +0 -64
  70. package/src/commands/fd-blast-radius.md +0 -49
  71. package/src/commands/fd-dashboard.md +0 -57
  72. package/src/commands/fd-evaluate-risk.md +0 -62
  73. package/src/commands/fd-guarded-edit.md +0 -69
  74. package/src/commands/fd-impact-radar.md +0 -51
  75. package/src/commands/fd-learn.md +0 -36
  76. package/src/commands/fd-progress.md +0 -50
  77. package/src/commands/fd-regression-predict.md +0 -57
  78. package/src/commands/fd-review-code.md +0 -62
  79. package/src/commands/fd-review-route.md +0 -54
  80. package/src/commands/fd-roadmap.md +0 -46
  81. package/src/commands/fd-settings.md +0 -57
  82. package/src/commands/fd-test-gap.md +0 -54
  83. package/src/commands/fd-volatility-map.md +0 -64
  84. package/src/commands/fd-workspace-status.md +0 -34
  85. package/src/skills/parallel-execute/SKILL.md +0 -92
  86. package/src/workflows/debug-flow.md +0 -119
  87. package/src/workflows/deploy-check-flow.md +0 -98
  88. package/src/workflows/discuss-flow.md +0 -97
  89. package/src/workflows/execute-flow.md +0 -233
  90. package/src/workflows/execute-phase.md +0 -145
  91. package/src/workflows/fix-bug-flow.md +0 -210
  92. package/src/workflows/map-codebase-flow.md +0 -92
  93. package/src/workflows/multi-repo-flow.md +0 -226
  94. package/src/workflows/parallel-execution-flow.md +0 -236
  95. package/src/workflows/plan-flow.md +0 -126
  96. package/src/workflows/plan-phase.md +0 -101
  97. package/src/workflows/refactor-flow.md +0 -122
  98. package/src/workflows/review-code-flow.md +0 -105
  99. package/src/workflows/spec-driven-flow.md +0 -43
  100. package/src/workflows/write-docs-flow.md +0 -95
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
- import { readdirSync as readdirSync3, readFileSync as readFileSync22, existsSync as existsSync23 } from "fs";
3
- import { join as join22, basename } from "path";
2
+ import { readdirSync as readdirSync3, readFileSync as readFileSync22, existsSync as existsSync24 } from "fs";
3
+ import { join as join23, basename } from "path";
4
4
  import { dirname as dirname4 } from "path";
5
5
  import { fileURLToPath as fileURLToPath2 } from "url";
6
6
 
@@ -507,127 +507,22 @@ var workspaceStateTool = tool3({
507
507
  }
508
508
  });
509
509
 
510
- // src/tools/run-parallel.ts
510
+ // src/tools/run-pipeline.ts
511
511
  import { tool as tool4 } from "@opencode-ai/plugin";
512
512
  function extractText(parts) {
513
513
  return parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join(`
514
514
  `);
515
515
  }
516
- function createRunParallelTool(client) {
517
- return tool4({
518
- description: "Run multiple agents in parallel. All tasks execute simultaneously via child sessions. Returns combined results with per-agent wall time. Partial results returned on failure.",
519
- args: {
520
- tasks: tool4.schema.array(tool4.schema.object({
521
- agent: tool4.schema.string(),
522
- prompt: tool4.schema.string(),
523
- context: tool4.schema.string().optional()
524
- }))
525
- },
526
- async execute(args, context) {
527
- const startTime = Date.now();
528
- const childSessionIds = [];
529
- context.abort.addEventListener("abort", () => {
530
- for (const id of childSessionIds) {
531
- client.session.abort({
532
- path: { id },
533
- query: { directory: context.directory }
534
- }).catch(() => {});
535
- }
536
- });
537
- const promises = args.tasks.map(async (task) => {
538
- const taskStart = Date.now();
539
- const createRes = await client.session.create({
540
- body: { parentID: context.sessionID, title: `${task.agent}-subtask` },
541
- query: { directory: context.directory }
542
- });
543
- if (createRes.error || !createRes.data?.id) {
544
- return {
545
- agent: task.agent,
546
- success: false,
547
- error: `Failed to create session: ${createRes.error?.detail ?? "unknown"}`,
548
- duration_ms: Date.now() - taskStart
549
- };
550
- }
551
- const childId = createRes.data.id;
552
- childSessionIds.push(childId);
553
- const fullPrompt = task.context ? `${task.context}
554
-
555
- ---
556
-
557
- ${task.prompt}` : task.prompt;
558
- const promptRes = await client.session.prompt({
559
- path: { id: childId },
560
- body: {
561
- agent: task.agent,
562
- parts: [{ type: "text", text: fullPrompt }],
563
- tools: { question: false }
564
- },
565
- query: { directory: context.directory }
566
- });
567
- if (promptRes.error) {
568
- return {
569
- agent: task.agent,
570
- session_id: childId,
571
- success: false,
572
- error: `Prompt failed: ${promptRes.error?.detail ?? "unknown"}`,
573
- duration_ms: Date.now() - taskStart
574
- };
575
- }
576
- const info = promptRes.data?.info;
577
- if (info?.error) {
578
- return {
579
- agent: task.agent,
580
- session_id: childId,
581
- success: false,
582
- error: `Agent error: ${JSON.stringify(info.error)}`,
583
- duration_ms: Date.now() - taskStart
584
- };
585
- }
586
- const output = extractText(promptRes.data?.parts ?? []);
587
- return {
588
- agent: task.agent,
589
- session_id: childId,
590
- success: true,
591
- output: output || "(no text output)",
592
- duration_ms: Date.now() - taskStart
593
- };
594
- });
595
- const settled = await Promise.allSettled(promises);
596
- const results = settled.map((result, i) => {
597
- if (result.status === "fulfilled")
598
- return result.value;
599
- return {
600
- agent: args.tasks[i].agent,
601
- success: false,
602
- error: result.reason?.message || String(result.reason),
603
- duration_ms: Date.now() - startTime
604
- };
605
- });
606
- return JSON.stringify({
607
- results,
608
- total_duration_ms: Date.now() - startTime,
609
- failures: results.filter((r) => !r.success).map((r) => r.agent)
610
- });
611
- }
612
- });
613
- }
614
-
615
- // src/tools/run-pipeline.ts
616
- import { tool as tool5 } from "@opencode-ai/plugin";
617
- function extractText2(parts) {
618
- return parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join(`
619
- `);
620
- }
621
516
  function createRunPipelineTool(client) {
622
- return tool5({
517
+ return tool4({
623
518
  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.",
624
519
  args: {
625
- steps: tool5.schema.array(tool5.schema.object({
626
- agent: tool5.schema.string(),
627
- prompt: tool5.schema.string()
520
+ steps: tool4.schema.array(tool4.schema.object({
521
+ agent: tool4.schema.string(),
522
+ prompt: tool4.schema.string()
628
523
  })),
629
- initial_context: tool5.schema.string().optional(),
630
- abort_on_failure: tool5.schema.boolean().optional().default(true)
524
+ initial_context: tool4.schema.string().optional(),
525
+ abort_on_failure: tool4.schema.boolean().optional().default(true)
631
526
  },
632
527
  async execute(args, context) {
633
528
  const startTime = Date.now();
@@ -700,7 +595,7 @@ ${step.prompt}` : step.prompt;
700
595
  }
701
596
  continue;
702
597
  }
703
- const output = extractText2(promptRes.data?.parts ?? []);
598
+ const output = extractText(promptRes.data?.parts ?? []);
704
599
  trace.push({ agent: step.agent, session_id: createRes.data.id, input: stepInput, output: output || "(no text output)", duration_ms: Date.now() - stepStart, success: true });
705
600
  carryContext = output;
706
601
  }
@@ -717,18 +612,18 @@ ${step.prompt}` : step.prompt;
717
612
  }
718
613
 
719
614
  // src/tools/delegate.ts
720
- import { tool as tool6 } from "@opencode-ai/plugin";
721
- function extractText3(parts) {
615
+ import { tool as tool5 } from "@opencode-ai/plugin";
616
+ function extractText2(parts) {
722
617
  return parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join(`
723
618
  `);
724
619
  }
725
620
  function createDelegateTool(client) {
726
- return tool6({
621
+ return tool5({
727
622
  description: "Delegate a task to a single agent via a child session. Returns the agent's output.",
728
623
  args: {
729
- agent: tool6.schema.string(),
730
- prompt: tool6.schema.string(),
731
- context: tool6.schema.string().optional()
624
+ agent: tool5.schema.string(),
625
+ prompt: tool5.schema.string(),
626
+ context: tool5.schema.string().optional()
732
627
  },
733
628
  async execute(args, context) {
734
629
  const startTime = Date.now();
@@ -784,7 +679,7 @@ ${args.prompt}` : args.prompt;
784
679
  duration_ms: Date.now() - startTime
785
680
  });
786
681
  }
787
- const output = extractText3(promptRes.data?.parts ?? []);
682
+ const output = extractText2(promptRes.data?.parts ?? []);
788
683
  return JSON.stringify({
789
684
  agent: args.agent,
790
685
  session_id: childId,
@@ -797,7 +692,7 @@ ${args.prompt}` : args.prompt;
797
692
  }
798
693
 
799
694
  // src/tools/repo-memory.ts
800
- import { tool as tool7 } from "@opencode-ai/plugin";
695
+ import { tool as tool6 } from "@opencode-ai/plugin";
801
696
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
802
697
  import { join as join5 } from "path";
803
698
  var MEMORY_FILE = "MEMORY.json";
@@ -824,26 +719,26 @@ function writeMemory(directory, memory) {
824
719
  memory.last_updated = new Date().toISOString();
825
720
  writeFileSync5(memoryPath(directory), JSON.stringify(memory, null, 2), "utf-8");
826
721
  }
827
- var repoMemoryTool = tool7({
722
+ var repoMemoryTool = tool6({
828
723
  description: "Repo Memory Graph: read/write/query persistent architecture graph in .codebase/MEMORY.json (modules, dependencies, ownership, bug history, conventions)",
829
724
  args: {
830
- action: tool7.schema.enum(["read", "write_node", "query", "delete_node"]),
831
- node_id: tool7.schema.string().optional(),
832
- node: tool7.schema.object({
833
- type: tool7.schema.enum(["module", "service", "api", "schema", "config"]),
834
- path: tool7.schema.string(),
835
- owner: tool7.schema.string().optional(),
836
- tags: tool7.schema.array(tool7.schema.string()),
837
- dependencies: tool7.schema.array(tool7.schema.string()),
838
- dependents: tool7.schema.array(tool7.schema.string()),
839
- bug_history: tool7.schema.array(tool7.schema.string()),
840
- conventions: tool7.schema.array(tool7.schema.string())
725
+ action: tool6.schema.enum(["read", "write_node", "query", "delete_node"]),
726
+ node_id: tool6.schema.string().optional(),
727
+ node: tool6.schema.object({
728
+ type: tool6.schema.enum(["module", "service", "api", "schema", "config"]),
729
+ path: tool6.schema.string(),
730
+ owner: tool6.schema.string().optional(),
731
+ tags: tool6.schema.array(tool6.schema.string()),
732
+ dependencies: tool6.schema.array(tool6.schema.string()),
733
+ dependents: tool6.schema.array(tool6.schema.string()),
734
+ bug_history: tool6.schema.array(tool6.schema.string()),
735
+ conventions: tool6.schema.array(tool6.schema.string())
841
736
  }).optional(),
842
- query: tool7.schema.object({
843
- type: tool7.schema.enum(["module", "service", "api", "schema", "config"]).optional(),
844
- owner: tool7.schema.string().optional(),
845
- tag: tool7.schema.string().optional(),
846
- path_prefix: tool7.schema.string().optional()
737
+ query: tool6.schema.object({
738
+ type: tool6.schema.enum(["module", "service", "api", "schema", "config"]).optional(),
739
+ owner: tool6.schema.string().optional(),
740
+ tag: tool6.schema.string().optional(),
741
+ path_prefix: tool6.schema.string().optional()
847
742
  }).optional()
848
743
  },
849
744
  async execute(args, context) {
@@ -898,7 +793,7 @@ var repoMemoryTool = tool7({
898
793
  });
899
794
 
900
795
  // src/tools/failure-replay.ts
901
- import { tool as tool8 } from "@opencode-ai/plugin";
796
+ import { tool as tool7 } from "@opencode-ai/plugin";
902
797
  import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
903
798
  import { join as join6 } from "path";
904
799
  var FAILURES_FILE = "FAILURES.json";
@@ -922,26 +817,26 @@ function writeStore(directory, store) {
922
817
  store.last_updated = new Date().toISOString();
923
818
  writeFileSync6(failuresPath(directory), JSON.stringify(store, null, 2), "utf-8");
924
819
  }
925
- var failureReplayTool = tool8({
820
+ var failureReplayTool = tool7({
926
821
  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",
927
822
  args: {
928
- action: tool8.schema.enum(["record", "query", "list", "mark_resolved"]),
929
- entry: tool8.schema.object({
930
- id: tool8.schema.string(),
931
- type: tool8.schema.enum(["reverted_commit", "failed_deployment", "flaky_test", "bug_fix", "build_failure"]),
932
- description: tool8.schema.string(),
933
- affected_paths: tool8.schema.array(tool8.schema.string()),
934
- root_cause: tool8.schema.string().optional(),
935
- fix_applied: tool8.schema.string().optional(),
936
- tags: tool8.schema.array(tool8.schema.string())
823
+ action: tool7.schema.enum(["record", "query", "list", "mark_resolved"]),
824
+ entry: tool7.schema.object({
825
+ id: tool7.schema.string(),
826
+ type: tool7.schema.enum(["reverted_commit", "failed_deployment", "flaky_test", "bug_fix", "build_failure"]),
827
+ description: tool7.schema.string(),
828
+ affected_paths: tool7.schema.array(tool7.schema.string()),
829
+ root_cause: tool7.schema.string().optional(),
830
+ fix_applied: tool7.schema.string().optional(),
831
+ tags: tool7.schema.array(tool7.schema.string())
937
832
  }).optional(),
938
- query: tool8.schema.object({
939
- type: tool8.schema.enum(["reverted_commit", "failed_deployment", "flaky_test", "bug_fix", "build_failure"]).optional(),
940
- path_prefix: tool8.schema.string().optional(),
941
- tag: tool8.schema.string().optional(),
942
- limit: tool8.schema.number().optional()
833
+ query: tool7.schema.object({
834
+ type: tool7.schema.enum(["reverted_commit", "failed_deployment", "flaky_test", "bug_fix", "build_failure"]).optional(),
835
+ path_prefix: tool7.schema.string().optional(),
836
+ tag: tool7.schema.string().optional(),
837
+ limit: tool7.schema.number().optional()
943
838
  }).optional(),
944
- entry_id: tool8.schema.string().optional()
839
+ entry_id: tool7.schema.string().optional()
945
840
  },
946
841
  async execute(args, context) {
947
842
  const dir = context.directory ?? process.cwd();
@@ -1003,7 +898,7 @@ var failureReplayTool = tool8({
1003
898
  });
1004
899
 
1005
900
  // src/tools/decision-trace.ts
1006
- import { tool as tool9 } from "@opencode-ai/plugin";
901
+ import { tool as tool8 } from "@opencode-ai/plugin";
1007
902
  import { readFileSync as readFileSync7, existsSync as existsSync7, mkdirSync as mkdirSync4, appendFileSync } from "fs";
1008
903
  import { join as join7 } from "path";
1009
904
  var DECISIONS_FILE = "DECISIONS.jsonl";
@@ -1023,29 +918,29 @@ function readDecisions(directory) {
1023
918
  }
1024
919
  }).filter(Boolean);
1025
920
  }
1026
- var decisionTraceTool = tool9({
921
+ var decisionTraceTool = tool8({
1027
922
  description: "Decision Trace: record why the agent changed something, what evidence was used, and assumptions made. Stored in .codebase/DECISIONS.jsonl for fast review.",
1028
923
  args: {
1029
- action: tool9.schema.enum(["record", "query", "get_for_file"]),
1030
- entry: tool9.schema.object({
1031
- id: tool9.schema.string(),
1032
- file_path: tool9.schema.string(),
1033
- change_type: tool9.schema.enum(["create", "edit", "delete", "refactor"]),
1034
- rationale: tool9.schema.string(),
1035
- evidence: tool9.schema.array(tool9.schema.string()),
1036
- assumptions: tool9.schema.array(tool9.schema.string()),
1037
- alternatives_considered: tool9.schema.array(tool9.schema.string()),
1038
- risk_level: tool9.schema.enum(["low", "medium", "high"]),
1039
- agent: tool9.schema.string().optional(),
1040
- session_id: tool9.schema.string().optional()
924
+ action: tool8.schema.enum(["record", "query", "get_for_file"]),
925
+ entry: tool8.schema.object({
926
+ id: tool8.schema.string(),
927
+ file_path: tool8.schema.string(),
928
+ change_type: tool8.schema.enum(["create", "edit", "delete", "refactor"]),
929
+ rationale: tool8.schema.string(),
930
+ evidence: tool8.schema.array(tool8.schema.string()),
931
+ assumptions: tool8.schema.array(tool8.schema.string()),
932
+ alternatives_considered: tool8.schema.array(tool8.schema.string()),
933
+ risk_level: tool8.schema.enum(["low", "medium", "high"]),
934
+ agent: tool8.schema.string().optional(),
935
+ session_id: tool8.schema.string().optional()
1041
936
  }).optional(),
1042
- query: tool9.schema.object({
1043
- file_path: tool9.schema.string().optional(),
1044
- change_type: tool9.schema.enum(["create", "edit", "delete", "refactor"]).optional(),
1045
- risk_level: tool9.schema.enum(["low", "medium", "high"]).optional(),
1046
- limit: tool9.schema.number().optional()
937
+ query: tool8.schema.object({
938
+ file_path: tool8.schema.string().optional(),
939
+ change_type: tool8.schema.enum(["create", "edit", "delete", "refactor"]).optional(),
940
+ risk_level: tool8.schema.enum(["low", "medium", "high"]).optional(),
941
+ limit: tool8.schema.number().optional()
1047
942
  }).optional(),
1048
- file_path: tool9.schema.string().optional()
943
+ file_path: tool8.schema.string().optional()
1049
944
  },
1050
945
  async execute(args, context) {
1051
946
  const dir = context.directory ?? process.cwd();
@@ -1088,7 +983,7 @@ var decisionTraceTool = tool9({
1088
983
  });
1089
984
 
1090
985
  // src/tools/volatility-map.ts
1091
- import { tool as tool10 } from "@opencode-ai/plugin";
986
+ import { tool as tool9 } from "@opencode-ai/plugin";
1092
987
  import { readFileSync as readFileSync8, writeFileSync as writeFileSync8, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
1093
988
  import { join as join8 } from "path";
1094
989
  var VOLATILITY_FILE = "VOLATILITY.json";
@@ -1122,29 +1017,29 @@ function stabilityLabel(churn, hotfixes, todos) {
1122
1017
  return "moderate";
1123
1018
  return "stable";
1124
1019
  }
1125
- var volatilityMapTool = tool10({
1020
+ var volatilityMapTool = tool9({
1126
1021
  description: "Codebase Volatility Map: read/write/query .codebase/VOLATILITY.json — highlights unstable zones based on churn, hotfix frequency, and TODO clusters",
1127
1022
  args: {
1128
- action: tool10.schema.enum(["read", "write", "query_hotspots", "update_entry"]),
1129
- entries: tool10.schema.array(tool10.schema.object({
1130
- path: tool10.schema.string(),
1131
- churn_score: tool10.schema.number(),
1132
- hotfix_count: tool10.schema.number(),
1133
- todo_count: tool10.schema.number(),
1134
- last_breakage: tool10.schema.string().optional(),
1135
- notes: tool10.schema.array(tool10.schema.string())
1023
+ action: tool9.schema.enum(["read", "write", "query_hotspots", "update_entry"]),
1024
+ entries: tool9.schema.array(tool9.schema.object({
1025
+ path: tool9.schema.string(),
1026
+ churn_score: tool9.schema.number(),
1027
+ hotfix_count: tool9.schema.number(),
1028
+ todo_count: tool9.schema.number(),
1029
+ last_breakage: tool9.schema.string().optional(),
1030
+ notes: tool9.schema.array(tool9.schema.string())
1136
1031
  })).optional(),
1137
- entry: tool10.schema.object({
1138
- path: tool10.schema.string(),
1139
- churn_score: tool10.schema.number(),
1140
- hotfix_count: tool10.schema.number(),
1141
- todo_count: tool10.schema.number(),
1142
- last_breakage: tool10.schema.string().optional(),
1143
- notes: tool10.schema.array(tool10.schema.string())
1032
+ entry: tool9.schema.object({
1033
+ path: tool9.schema.string(),
1034
+ churn_score: tool9.schema.number(),
1035
+ hotfix_count: tool9.schema.number(),
1036
+ todo_count: tool9.schema.number(),
1037
+ last_breakage: tool9.schema.string().optional(),
1038
+ notes: tool9.schema.array(tool9.schema.string())
1144
1039
  }).optional(),
1145
- threshold: tool10.schema.enum(["stable", "moderate", "volatile", "critical"]).optional(),
1146
- path_prefix: tool10.schema.string().optional(),
1147
- limit: tool10.schema.number().optional()
1040
+ threshold: tool9.schema.enum(["stable", "moderate", "volatile", "critical"]).optional(),
1041
+ path_prefix: tool9.schema.string().optional(),
1042
+ limit: tool9.schema.number().optional()
1148
1043
  },
1149
1044
  async execute(args, context) {
1150
1045
  const dir = context.directory ?? process.cwd();
@@ -1196,7 +1091,7 @@ var volatilityMapTool = tool10({
1196
1091
  });
1197
1092
 
1198
1093
  // src/tools/policy-engine.ts
1199
- import { tool as tool11 } from "@opencode-ai/plugin";
1094
+ import { tool as tool10 } from "@opencode-ai/plugin";
1200
1095
  import { readFileSync as readFileSync9, writeFileSync as writeFileSync9, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
1201
1096
  import { join as join9 } from "path";
1202
1097
  var POLICIES_FILE = "POLICIES.json";
@@ -1220,24 +1115,24 @@ function writeStore3(directory, store) {
1220
1115
  store.last_updated = new Date().toISOString();
1221
1116
  writeFileSync9(policiesPath(directory), JSON.stringify(store, null, 2), "utf-8");
1222
1117
  }
1223
- var policyEngineTool = tool11({
1118
+ var policyEngineTool = tool10({
1224
1119
  description: "Self-Healing Policy Engine: manage .codebase/POLICIES.json — add, list, query, toggle, and record violations of editing policies learned from past failures",
1225
1120
  args: {
1226
- action: tool11.schema.enum(["list", "add", "record_violation", "toggle", "query"]),
1227
- policy: tool11.schema.object({
1228
- id: tool11.schema.string(),
1229
- name: tool11.schema.string(),
1230
- trigger: tool11.schema.string(),
1231
- rule: tool11.schema.string(),
1232
- source: tool11.schema.enum(["manual", "learned"]),
1233
- failure_count: tool11.schema.number()
1121
+ action: tool10.schema.enum(["list", "add", "record_violation", "toggle", "query"]),
1122
+ policy: tool10.schema.object({
1123
+ id: tool10.schema.string(),
1124
+ name: tool10.schema.string(),
1125
+ trigger: tool10.schema.string(),
1126
+ rule: tool10.schema.string(),
1127
+ source: tool10.schema.enum(["manual", "learned"]),
1128
+ failure_count: tool10.schema.number()
1234
1129
  }).optional(),
1235
- policy_id: tool11.schema.string().optional(),
1236
- active: tool11.schema.boolean().optional(),
1237
- query: tool11.schema.object({
1238
- source: tool11.schema.enum(["manual", "learned"]).optional(),
1239
- active_only: tool11.schema.boolean().optional(),
1240
- trigger_contains: tool11.schema.string().optional()
1130
+ policy_id: tool10.schema.string().optional(),
1131
+ active: tool10.schema.boolean().optional(),
1132
+ query: tool10.schema.object({
1133
+ source: tool10.schema.enum(["manual", "learned"]).optional(),
1134
+ active_only: tool10.schema.boolean().optional(),
1135
+ trigger_contains: tool10.schema.string().optional()
1241
1136
  }).optional()
1242
1137
  },
1243
1138
  async execute(args, context) {
@@ -1299,16 +1194,16 @@ var policyEngineTool = tool11({
1299
1194
  });
1300
1195
 
1301
1196
  // src/tools/hash-edit.ts
1302
- import { tool as tool12 } from "@opencode-ai/plugin";
1197
+ import { tool as tool11 } from "@opencode-ai/plugin";
1303
1198
  import { readFileSync as readFileSync10, writeFileSync as writeFileSync10 } from "fs";
1304
1199
  import { createHash } from "crypto";
1305
- var hashEditTool = tool12({
1200
+ var hashEditTool = tool11({
1306
1201
  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.",
1307
1202
  args: {
1308
- filePath: tool12.schema.string(),
1309
- targetContent: tool12.schema.string(),
1310
- expectedHash: tool12.schema.string().optional(),
1311
- replacementContent: tool12.schema.string()
1203
+ filePath: tool11.schema.string(),
1204
+ targetContent: tool11.schema.string(),
1205
+ expectedHash: tool11.schema.string().optional(),
1206
+ replacementContent: tool11.schema.string()
1312
1207
  },
1313
1208
  async execute(args, context) {
1314
1209
  const fullPath = args.filePath.startsWith("/") ? args.filePath : `${context.directory}/${args.filePath}`;
@@ -1334,13 +1229,13 @@ var hashEditTool = tool12({
1334
1229
  });
1335
1230
 
1336
1231
  // src/tools/council.ts
1337
- import { tool as tool13 } from "@opencode-ai/plugin";
1232
+ import { tool as tool12 } from "@opencode-ai/plugin";
1338
1233
  function createCouncilTool(client) {
1339
- return tool13({
1234
+ return tool12({
1340
1235
  description: "Run an ensemble of agents (Council) on the same task to reach consensus or compare approaches. Runs 3 specialized agents in parallel and returns their synthesized outputs.",
1341
1236
  args: {
1342
- task: tool13.schema.string(),
1343
- agents: tool13.schema.array(tool13.schema.string()).optional()
1237
+ task: tool12.schema.string(),
1238
+ agents: tool12.schema.array(tool12.schema.string()).optional()
1344
1239
  },
1345
1240
  async execute(args, context) {
1346
1241
  const agents = args.agents || ["architect", "reviewer", "coder"];
@@ -1396,14 +1291,14 @@ Please synthesize these results. Identify areas of agreement, resolve conflicts,
1396
1291
  }
1397
1292
 
1398
1293
  // src/tools/context-generator.ts
1399
- import { tool as tool14 } from "@opencode-ai/plugin";
1294
+ import { tool as tool13 } from "@opencode-ai/plugin";
1400
1295
  import { writeFileSync as writeFileSync11, existsSync as existsSync10, readFileSync as readFileSync11, readdirSync as readdirSync2, statSync } from "fs";
1401
1296
  import { join as join10 } from "path";
1402
- var contextGeneratorTool = tool14({
1297
+ var contextGeneratorTool = tool13({
1403
1298
  description: "Auto-generate or update hierarchical context files (AGENTS.md, CLAUDE.md) throughout the project. These files provide critical grounding for AI agents.",
1404
1299
  args: {
1405
- targetDir: tool14.schema.string().optional(),
1406
- force: tool14.schema.boolean().optional()
1300
+ targetDir: tool13.schema.string().optional(),
1301
+ force: tool13.schema.boolean().optional()
1407
1302
  },
1408
1303
  async execute(args, context) {
1409
1304
  const root = context.directory;
@@ -1452,18 +1347,18 @@ Generated by FlowDeck Context Generator.
1452
1347
  });
1453
1348
 
1454
1349
  // src/tools/create-skill.ts
1455
- import { tool as tool15 } from "@opencode-ai/plugin";
1350
+ import { tool as tool14 } from "@opencode-ai/plugin";
1456
1351
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync12, existsSync as existsSync11 } from "fs";
1457
1352
  import { join as join11, dirname as dirname3 } from "path";
1458
1353
  import { fileURLToPath } from "url";
1459
1354
  var SKILLS_DIR = join11(dirname3(fileURLToPath(import.meta.url)), "..", "skills");
1460
- var createSkillTool = tool15({
1355
+ var createSkillTool = tool14({
1461
1356
  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.",
1462
1357
  args: {
1463
- name: tool15.schema.string().describe("Unique kebab-case skill name, e.g. 'api-rate-limiting'"),
1464
- description: tool15.schema.string().describe("One-sentence description of what this skill does"),
1465
- content: tool15.schema.string().describe("Full skill body in Markdown. Must include: ## When to Activate, ## Steps, and ## Examples sections."),
1466
- tags: tool15.schema.array(tool15.schema.string()).optional().describe("Optional tags for categorisation, e.g. ['performance', 'typescript']")
1358
+ name: tool14.schema.string().describe("Unique kebab-case skill name, e.g. 'api-rate-limiting'"),
1359
+ description: tool14.schema.string().describe("One-sentence description of what this skill does"),
1360
+ content: tool14.schema.string().describe("Full skill body in Markdown. Must include: ## When to Activate, ## Steps, and ## Examples sections."),
1361
+ tags: tool14.schema.array(tool14.schema.string()).optional().describe("Optional tags for categorisation, e.g. ['performance', 'typescript']")
1467
1362
  },
1468
1363
  async execute(args) {
1469
1364
  const skillDir = join11(SKILLS_DIR, args.name);
@@ -1495,7 +1390,7 @@ origin: FlowDeck (self-learned)${tagLine}
1495
1390
  });
1496
1391
 
1497
1392
  // src/tools/reflect.ts
1498
- import { tool as tool16 } from "@opencode-ai/plugin";
1393
+ import { tool as tool15 } from "@opencode-ai/plugin";
1499
1394
  import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
1500
1395
  import { join as join12 } from "path";
1501
1396
  var MAX_ARTIFACT_BYTES = 4000;
@@ -1505,10 +1400,10 @@ function tail(text, maxBytes) {
1505
1400
  return `... (truncated) ...
1506
1401
  ` + text.slice(-maxBytes);
1507
1402
  }
1508
- var reflectTool = tool16({
1403
+ var reflectTool = tool15({
1509
1404
  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.",
1510
1405
  args: {
1511
- scope: tool16.schema.enum(["session", "project"]).optional().describe("'session' (default) uses only recent artifacts; 'project' includes all historical data")
1406
+ scope: tool15.schema.enum(["session", "project"]).optional().describe("'session' (default) uses only recent artifacts; 'project' includes all historical data")
1512
1407
  },
1513
1408
  async execute(args, context) {
1514
1409
  const root = context.directory;
@@ -1549,14 +1444,399 @@ var reflectTool = tool16({
1549
1444
  }
1550
1445
  });
1551
1446
 
1552
- // src/hooks/guard-rails.ts
1553
- import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
1447
+ // src/tools/memory-search.ts
1448
+ import { tool as tool16 } from "@opencode-ai/plugin";
1449
+
1450
+ // src/services/memory-store.ts
1451
+ import { Database } from "bun:sqlite";
1452
+ import { existsSync as existsSync13, mkdirSync as mkdirSync8 } from "fs";
1554
1453
  import { join as join13 } from "path";
1454
+ import { homedir } from "os";
1455
+ var MEMORY_DIR = join13(homedir(), ".flowdeck-memory");
1456
+ var DB_PATH = join13(MEMORY_DIR, "memory.db");
1457
+ function ensureDir() {
1458
+ if (!existsSync13(MEMORY_DIR)) {
1459
+ mkdirSync8(MEMORY_DIR, { recursive: true });
1460
+ }
1461
+ }
1462
+ var db = null;
1463
+ function getDb() {
1464
+ if (!db) {
1465
+ ensureDir();
1466
+ db = new Database(DB_PATH);
1467
+ initializeSchema(db);
1468
+ }
1469
+ return db;
1470
+ }
1471
+ function initializeSchema(database) {
1472
+ const schema = `
1473
+ CREATE TABLE IF NOT EXISTS sessions (
1474
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1475
+ content_session_id TEXT NOT NULL UNIQUE,
1476
+ project TEXT NOT NULL,
1477
+ directory TEXT NOT NULL,
1478
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1479
+ last_active_at TEXT NOT NULL DEFAULT (datetime('now')),
1480
+ summary TEXT,
1481
+ prompt_count INTEGER DEFAULT 0
1482
+ );
1483
+
1484
+ CREATE TABLE IF NOT EXISTS observations (
1485
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1486
+ session_id INTEGER NOT NULL,
1487
+ tool_name TEXT NOT NULL,
1488
+ tool_input TEXT,
1489
+ tool_response TEXT,
1490
+ directory TEXT NOT NULL,
1491
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1492
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
1493
+ );
1494
+
1495
+ CREATE TABLE IF NOT EXISTS summaries (
1496
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1497
+ session_id INTEGER NOT NULL UNIQUE,
1498
+ content TEXT NOT NULL,
1499
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
1500
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
1501
+ );
1502
+
1503
+ CREATE INDEX IF NOT EXISTS idx_observations_session ON observations(session_id);
1504
+ CREATE INDEX IF NOT EXISTS idx_observations_directory ON observations(directory);
1505
+ CREATE INDEX IF NOT EXISTS idx_observations_tool ON observations(tool_name);
1506
+ CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project);
1507
+ CREATE INDEX IF NOT EXISTS idx_sessions_directory ON sessions(directory);
1508
+ `;
1509
+ database.run(schema);
1510
+ }
1511
+ function serializeToolInput(input) {
1512
+ if (!input)
1513
+ return null;
1514
+ try {
1515
+ return JSON.stringify(input);
1516
+ } catch {
1517
+ return String(input);
1518
+ }
1519
+ }
1520
+ function parseToolInput(input) {
1521
+ if (!input)
1522
+ return null;
1523
+ try {
1524
+ return JSON.parse(input);
1525
+ } catch {
1526
+ return null;
1527
+ }
1528
+ }
1529
+ function initSession(contentSessionId, project, directory) {
1530
+ const database = getDb();
1531
+ const now = new Date().toISOString();
1532
+ const existing = database.prepare("SELECT * FROM sessions WHERE content_session_id = ?").get(contentSessionId);
1533
+ if (existing) {
1534
+ database.prepare("UPDATE sessions SET last_active_at = ?, prompt_count = prompt_count + 1 WHERE id = ?").run(now, existing.id);
1535
+ return { ...existing, last_active_at: now, prompt_count: (existing.prompt_count || 0) + 1 };
1536
+ }
1537
+ const result = database.prepare("INSERT INTO sessions (content_session_id, project, directory, created_at, last_active_at) VALUES (?, ?, ?, ?, ?)").run(contentSessionId, project, directory, now, now);
1538
+ return {
1539
+ id: result.lastInsertRowid,
1540
+ content_session_id: contentSessionId,
1541
+ project,
1542
+ directory,
1543
+ created_at: now,
1544
+ last_active_at: now,
1545
+ prompt_count: 1
1546
+ };
1547
+ }
1548
+ function storeObservation(sessionId, toolName, toolInput, toolResponse, directory) {
1549
+ const database = getDb();
1550
+ const now = new Date().toISOString();
1551
+ const result = database.prepare("INSERT INTO observations (session_id, tool_name, tool_input, tool_response, directory, created_at) VALUES (?, ?, ?, ?, ?, ?)").run(sessionId, toolName, serializeToolInput(toolInput), toolResponse ? toolResponse.slice(0, 1e4) : null, directory, now);
1552
+ database.prepare("UPDATE sessions SET last_active_at = ? WHERE id = ?").run(now, sessionId);
1553
+ return {
1554
+ id: result.lastInsertRowid,
1555
+ session_id: sessionId,
1556
+ tool_name: toolName,
1557
+ tool_input: parseToolInput(serializeToolInput(toolInput)),
1558
+ tool_response: toolResponse ? toolResponse.slice(0, 1e4) : null,
1559
+ directory,
1560
+ created_at: now
1561
+ };
1562
+ }
1563
+ function storeSummary(sessionId, content) {
1564
+ const database = getDb();
1565
+ const now = new Date().toISOString();
1566
+ database.prepare("INSERT OR REPLACE INTO summaries (session_id, content, created_at) VALUES (?, ?, ?)").run(sessionId, content, now);
1567
+ database.prepare("UPDATE sessions SET summary = ? WHERE id = ?").run(content, sessionId);
1568
+ return {
1569
+ id: database.prepare("SELECT last_insert_rowid() as id").get().id,
1570
+ session_id: sessionId,
1571
+ content,
1572
+ created_at: now
1573
+ };
1574
+ }
1575
+ function getRecentSessions(directory, limit = 5) {
1576
+ const database = getDb();
1577
+ return database.prepare(`SELECT * FROM sessions
1578
+ WHERE directory = ?
1579
+ ORDER BY last_active_at DESC
1580
+ LIMIT ?`).all(directory, limit);
1581
+ }
1582
+ function getObservationsForSession(sessionId) {
1583
+ const database = getDb();
1584
+ const observations = database.prepare("SELECT * FROM observations WHERE session_id = ? ORDER BY created_at ASC").all(sessionId);
1585
+ return observations.map((obs) => ({
1586
+ ...obs,
1587
+ tool_input: parseToolInput(obs.tool_input)
1588
+ }));
1589
+ }
1590
+ function getRecentObservations(directory, limit = 50) {
1591
+ const database = getDb();
1592
+ const rows = database.prepare(`SELECT o.*, s.project, s.content_session_id, s.created_at as session_created
1593
+ FROM observations o
1594
+ JOIN sessions s ON o.session_id = s.id
1595
+ WHERE o.directory = ?
1596
+ ORDER BY o.created_at DESC
1597
+ LIMIT ?`).all(directory, limit);
1598
+ return rows.map((row) => ({
1599
+ observation: {
1600
+ ...row,
1601
+ tool_input: parseToolInput(row.tool_input)
1602
+ },
1603
+ session: {
1604
+ content_session_id: row.content_session_id,
1605
+ project: row.project,
1606
+ directory,
1607
+ created_at: row.session_created
1608
+ }
1609
+ }));
1610
+ }
1611
+ function searchObservations(directory, query, limit = 10) {
1612
+ const database = getDb();
1613
+ const pattern = `%${query}%`;
1614
+ const rows = database.prepare(`SELECT o.*, s.project, s.content_session_id, s.created_at as session_created
1615
+ FROM observations o
1616
+ JOIN sessions s ON o.session_id = s.id
1617
+ WHERE o.directory = ? AND (o.tool_name LIKE ? OR o.tool_input LIKE ? OR o.tool_response LIKE ?)
1618
+ ORDER BY o.created_at DESC
1619
+ LIMIT ?`).all(directory, pattern, pattern, pattern, limit);
1620
+ return rows.map((row) => ({
1621
+ observation: {
1622
+ ...row,
1623
+ tool_input: parseToolInput(row.tool_input)
1624
+ },
1625
+ session: {
1626
+ content_session_id: row.content_session_id,
1627
+ project: row.project,
1628
+ directory,
1629
+ created_at: row.session_created
1630
+ }
1631
+ }));
1632
+ }
1633
+ function getContextForDirectory(directory, maxObservations = 20) {
1634
+ const recentObs = getRecentObservations(directory, maxObservations);
1635
+ if (recentObs.length === 0)
1636
+ return "";
1637
+ const lines = ["## Recent Context"];
1638
+ for (const { observation, session } of recentObs) {
1639
+ const date = observation.created_at ? new Date(observation.created_at).toLocaleDateString() : "unknown";
1640
+ lines.push(`
1641
+ ### [${date}] ${session.project} - ${observation.tool_name}`);
1642
+ if (observation.tool_input && Object.keys(observation.tool_input).length > 0) {
1643
+ const preview = JSON.stringify(observation.tool_input).slice(0, 200);
1644
+ lines.push(`Input: ${preview}${preview.length >= 200 ? "..." : ""}`);
1645
+ }
1646
+ if (observation.tool_response) {
1647
+ const preview = observation.tool_response.slice(0, 300);
1648
+ lines.push(`Output: ${preview}${observation.tool_response.length > 300 ? "..." : ""}`);
1649
+ }
1650
+ }
1651
+ const summaries = getDb().prepare(`SELECT su.* FROM summaries su
1652
+ JOIN sessions s ON su.session_id = s.id
1653
+ WHERE s.directory = ?
1654
+ ORDER BY su.created_at DESC
1655
+ LIMIT 3`).all(directory);
1656
+ if (summaries.length > 0) {
1657
+ lines.push(`
1658
+ ## Session Summaries`);
1659
+ for (const sum of summaries) {
1660
+ const date = sum.created_at ? new Date(sum.created_at).toLocaleDateString() : "unknown";
1661
+ lines.push(`
1662
+ ### [${date}]`);
1663
+ lines.push(sum.content.slice(0, 500));
1664
+ }
1665
+ }
1666
+ return lines.join(`
1667
+ `);
1668
+ }
1669
+
1670
+ // src/tools/memory-search.ts
1671
+ var memorySearchTool = tool16({
1672
+ description: "Search FlowDeck memory for past observations, sessions, and context. Use to recall what was worked on previously.",
1673
+ args: {
1674
+ query: tool16.schema.string().optional().describe("Search query for memory (searches tool names, inputs, and outputs)"),
1675
+ session_id: tool16.schema.string().optional().describe("Specific session ID to retrieve observations from"),
1676
+ limit: tool16.schema.number().optional().describe("Maximum number of results (default: 10)")
1677
+ },
1678
+ async execute(args, context) {
1679
+ const directory = context.directory ?? process.cwd();
1680
+ const limit = args.limit ?? 10;
1681
+ if (args.session_id) {
1682
+ const sessions2 = getRecentSessions(directory, 100);
1683
+ const targetSession = sessions2.find((s) => String(s.id) === args.session_id || s.content_session_id === args.session_id);
1684
+ if (!targetSession) {
1685
+ return JSON.stringify({ error: "Session not found", session_id: args.session_id });
1686
+ }
1687
+ const observations = getObservationsForSession(targetSession.id);
1688
+ return JSON.stringify({
1689
+ session: targetSession,
1690
+ observations: observations.map((o) => ({
1691
+ tool_name: o.tool_name,
1692
+ tool_input: o.tool_input,
1693
+ tool_response: o.tool_response ? o.tool_response.slice(0, 500) + (o.tool_response.length > 500 ? "..." : "") : null,
1694
+ created_at: o.created_at
1695
+ }))
1696
+ });
1697
+ }
1698
+ if (args.query) {
1699
+ const results = searchObservations(directory, args.query, limit);
1700
+ if (results.length === 0) {
1701
+ return JSON.stringify({ message: `No results found for "${args.query}"`, results: [] });
1702
+ }
1703
+ return JSON.stringify({
1704
+ query: args.query,
1705
+ count: results.length,
1706
+ results: results.map(({ observation, session }) => ({
1707
+ tool_name: observation.tool_name,
1708
+ tool_input: observation.tool_input,
1709
+ tool_response: observation.tool_response ? observation.tool_response.slice(0, 300) + (observation.tool_response.length > 300 ? "..." : "") : null,
1710
+ project: session.project,
1711
+ date: observation.created_at
1712
+ }))
1713
+ });
1714
+ }
1715
+ const sessions = getRecentSessions(directory, limit);
1716
+ if (sessions.length === 0) {
1717
+ return JSON.stringify({ message: "No previous sessions found in this directory", sessions: [] });
1718
+ }
1719
+ return JSON.stringify({
1720
+ message: "Recent sessions",
1721
+ count: sessions.length,
1722
+ sessions: sessions.map((s) => ({
1723
+ id: s.id,
1724
+ content_session_id: s.content_session_id,
1725
+ project: s.project,
1726
+ created_at: s.created_at,
1727
+ last_active_at: s.last_active_at,
1728
+ summary: s.summary
1729
+ }))
1730
+ });
1731
+ }
1732
+ });
1733
+
1734
+ // src/hooks/memory-hook.ts
1735
+ var MAX_TOOL_RESPONSE = 1e4;
1736
+ var MAX_PROMPT_LENGTH = 2000;
1737
+ var activeSessions = new Map;
1738
+ function extractProjectFromDirectory(directory) {
1739
+ const parts = directory.split("/");
1740
+ return parts[parts.length - 1] || "unknown";
1741
+ }
1742
+ function truncate(str, max) {
1743
+ if (!str || str.length <= max)
1744
+ return str || "";
1745
+ return str.slice(0, max);
1746
+ }
1747
+ function onSessionCreated(directory, contentSessionId, prompt) {
1748
+ const project = extractProjectFromDirectory(directory);
1749
+ const session = initSession(contentSessionId, project, directory);
1750
+ activeSessions.set(contentSessionId, {
1751
+ sessionId: session.id,
1752
+ contentSessionId,
1753
+ project,
1754
+ directory
1755
+ });
1756
+ return session;
1757
+ }
1758
+ function onToolExecuted(contentSessionId, toolName, toolInput, toolResponse, directory) {
1759
+ let ctx = activeSessions.get(contentSessionId);
1760
+ if (!ctx) {
1761
+ const project = extractProjectFromDirectory(directory);
1762
+ const session = initSession(contentSessionId, project, directory);
1763
+ ctx = {
1764
+ sessionId: session.id,
1765
+ contentSessionId,
1766
+ project,
1767
+ directory
1768
+ };
1769
+ activeSessions.set(contentSessionId, ctx);
1770
+ }
1771
+ storeObservation(ctx.sessionId, truncate(toolName, 200), toolInput, toolResponse ? truncate(toolResponse, MAX_TOOL_RESPONSE) : null, directory);
1772
+ }
1773
+ function onMessageUpdated(contentSessionId, role, content, directory) {
1774
+ if (role !== "assistant")
1775
+ return;
1776
+ if (!content || !content.trim())
1777
+ return;
1778
+ let ctx = activeSessions.get(contentSessionId);
1779
+ if (!ctx) {
1780
+ const project = extractProjectFromDirectory(directory);
1781
+ const session = initSession(contentSessionId, project, directory);
1782
+ ctx = {
1783
+ sessionId: session.id,
1784
+ contentSessionId,
1785
+ project,
1786
+ directory
1787
+ };
1788
+ activeSessions.set(contentSessionId, ctx);
1789
+ }
1790
+ storeObservation(ctx.sessionId, "assistant_message", { role }, truncate(content, MAX_TOOL_RESPONSE), directory);
1791
+ }
1792
+ function onSessionCompact(contentSessionId, summary) {
1793
+ const ctx = activeSessions.get(contentSessionId);
1794
+ if (!ctx)
1795
+ return;
1796
+ storeSummary(ctx.sessionId, truncate(summary, MAX_PROMPT_LENGTH));
1797
+ }
1798
+ function onSessionEnd(contentSessionId, lastMessage) {
1799
+ const ctx = activeSessions.get(contentSessionId);
1800
+ if (!ctx)
1801
+ return;
1802
+ if (lastMessage && lastMessage.trim()) {
1803
+ storeSummary(ctx.sessionId, truncate(lastMessage, MAX_PROMPT_LENGTH));
1804
+ }
1805
+ activeSessions.delete(contentSessionId);
1806
+ }
1807
+ function getSessionContext(directory, contentSessionId) {
1808
+ const context = getContextForDirectory(directory, 30);
1809
+ const previousSessions = getRecentSessions(directory, 5);
1810
+ if (previousSessions.length > 0 && activeSessions.has(contentSessionId)) {
1811
+ const ctx = activeSessions.get(contentSessionId);
1812
+ for (const prev of previousSessions) {
1813
+ if (prev.content_session_id === contentSessionId)
1814
+ continue;
1815
+ }
1816
+ }
1817
+ return { context, previousSessions };
1818
+ }
1819
+ function clearSession(contentSessionId) {
1820
+ activeSessions.delete(contentSessionId);
1821
+ }
1822
+ var memoryHook = {
1823
+ onSessionCreated,
1824
+ onToolExecuted,
1825
+ onMessageUpdated,
1826
+ onSessionCompact,
1827
+ onSessionEnd,
1828
+ getSessionContext,
1829
+ clearSession
1830
+ };
1831
+
1832
+ // src/hooks/guard-rails.ts
1833
+ import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
1834
+ import { join as join14 } from "path";
1555
1835
  var PLANNING_DIR2 = ".planning";
1556
1836
  var CONFIG_FILE = "config.json";
1557
1837
  var STATE_FILE2 = "STATE.md";
1558
1838
  function resolveExecutionMode(configPath, trustScore, volatility) {
1559
- if (existsSync13(configPath)) {
1839
+ if (existsSync14(configPath)) {
1560
1840
  try {
1561
1841
  const config = JSON.parse(readFileSync13(configPath, "utf-8"));
1562
1842
  if (config.execution_mode === "review-only")
@@ -1609,22 +1889,22 @@ var BUILD_DEPLOY_PATTERNS = [
1609
1889
  ];
1610
1890
  async function guardRailsHook(ctx, input, _output) {
1611
1891
  const dir = ctx.directory;
1612
- const planningDirPath = join13(dir, PLANNING_DIR2);
1892
+ const planningDirPath = join14(dir, PLANNING_DIR2);
1613
1893
  const codebaseDirectory = codebaseDir(dir);
1614
- const configPath = join13(planningDirPath, CONFIG_FILE);
1615
- const statePath2 = join13(planningDirPath, STATE_FILE2);
1894
+ const configPath = join14(planningDirPath, CONFIG_FILE);
1895
+ const statePath2 = join14(planningDirPath, STATE_FILE2);
1616
1896
  const workspaceRoot = findWorkspaceRoot(dir);
1617
1897
  if (workspaceRoot && dir !== workspaceRoot) {
1618
1898
  const config = getWorkspaceConfig(dir);
1619
- if (config && config.workspace_mode === "shared" && !existsSync13(planningDirPath)) {
1899
+ if (config && config.workspace_mode === "shared" && !existsSync14(planningDirPath)) {
1620
1900
  const msg = `No .planning/ in this sub-repo. Switch to workspace root: cd ${workspaceRoot}`;
1621
1901
  throw new Error(`[flowdeck] BLOCK: ${msg}`);
1622
1902
  }
1623
1903
  }
1624
1904
  if (input.tool === "write" || input.tool === "edit") {
1625
- if (!existsSync13(planningDirPath))
1905
+ if (!existsSync14(planningDirPath))
1626
1906
  return;
1627
- if (!existsSync13(codebaseDirectory)) {
1907
+ if (!existsSync14(codebaseDirectory)) {
1628
1908
  throw new Error(`[flowdeck] WARNING: .codebase/ not found. Run /map-codebase to map the codebase.`);
1629
1909
  }
1630
1910
  const execMode = resolveExecutionMode(configPath, null);
@@ -1659,7 +1939,7 @@ async function guardRailsHook(ctx, input, _output) {
1659
1939
  }
1660
1940
  }
1661
1941
  function effectiveSeverity(configPath, statePath2) {
1662
- if (existsSync13(configPath)) {
1942
+ if (existsSync14(configPath)) {
1663
1943
  try {
1664
1944
  const configContent = readFileSync13(configPath, "utf-8");
1665
1945
  const config = JSON.parse(configContent);
@@ -1677,7 +1957,7 @@ function getEffectiveSeverity(configPath, statePath2) {
1677
1957
  return effectiveSeverity(configPath, statePath2);
1678
1958
  }
1679
1959
  function getPlanConfirmed(statePath2) {
1680
- if (!existsSync13(statePath2))
1960
+ if (!existsSync14(statePath2))
1681
1961
  return false;
1682
1962
  try {
1683
1963
  const content = readFileSync13(statePath2, "utf-8");
@@ -1688,21 +1968,21 @@ function getPlanConfirmed(statePath2) {
1688
1968
  }
1689
1969
  }
1690
1970
  function getWarningMessage(statePath2, planningDir3) {
1691
- if (!existsSync13(join13(planningDir3, STATE_FILE2))) {
1971
+ if (!existsSync14(join14(planningDir3, STATE_FILE2))) {
1692
1972
  return "No .planning/ found. Run /new-project first.";
1693
1973
  }
1694
1974
  return "Plan not confirmed. Run /plan and confirm to enable execution.";
1695
1975
  }
1696
1976
  function getBlockMessage(statePath2, planningDir3) {
1697
- if (!existsSync13(join13(planningDir3, STATE_FILE2))) {
1977
+ if (!existsSync14(join14(planningDir3, STATE_FILE2))) {
1698
1978
  return "No .planning/ found. Run /new-project first.";
1699
1979
  }
1700
1980
  return "Plan not confirmed. Run /plan and confirm to enable execution.";
1701
1981
  }
1702
1982
 
1703
1983
  // src/hooks/tool-guard.ts
1704
- import { existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
1705
- import { join as join14 } from "path";
1984
+ import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
1985
+ import { join as join15 } from "path";
1706
1986
  var BLOCKED_PATTERNS = {
1707
1987
  read: [".env", ".pem", ".key", ".secret"],
1708
1988
  write: ["node_modules"],
@@ -1748,8 +2028,8 @@ function isBlocked(tool17, args) {
1748
2028
  return null;
1749
2029
  }
1750
2030
  function checkArchConstraint(directory, filePath) {
1751
- const constraintsPath = join14(codebaseDir(directory), "CONSTRAINTS.md");
1752
- if (!existsSync14(constraintsPath))
2031
+ const constraintsPath = join15(codebaseDir(directory), "CONSTRAINTS.md");
2032
+ if (!existsSync15(constraintsPath))
1753
2033
  return null;
1754
2034
  try {
1755
2035
  const content = readFileSync14(constraintsPath, "utf-8");
@@ -1797,18 +2077,18 @@ async function toolGuardHook(ctx, input, output) {
1797
2077
  }
1798
2078
 
1799
2079
  // src/hooks/session-start.ts
1800
- import { existsSync as existsSync15, readFileSync as readFileSync15 } from "fs";
2080
+ import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
1801
2081
  async function sessionStartHook(ctx) {
1802
2082
  const planningDir3 = ctx.directory + "/.planning";
1803
2083
  const codebaseDirectory = codebaseDir(ctx.directory);
1804
2084
  const workspaceRoot = findWorkspaceRoot(ctx.directory);
1805
2085
  const config = workspaceRoot ? getWorkspaceConfig(ctx.directory) : null;
1806
- if (!existsSync15(planningDir3)) {
2086
+ if (!existsSync16(planningDir3)) {
1807
2087
  return {
1808
2088
  flowdeck_phase: null,
1809
2089
  flowdeck_status: "no_plan",
1810
2090
  flowdeck_warning: "Run /new-project or /map-codebase to initialize.",
1811
- flowdeck_has_codebase: existsSync15(codebaseDirectory),
2091
+ flowdeck_has_codebase: existsSync16(codebaseDirectory),
1812
2092
  ...workspaceRoot && config?.sub_repos ? {
1813
2093
  flowdeck_workspace_root: workspaceRoot,
1814
2094
  flowdeck_sub_repos: config.sub_repos,
@@ -1827,7 +2107,7 @@ async function sessionStartHook(ctx) {
1827
2107
  flowdeck_status: currentPhase["status"] ?? null,
1828
2108
  flowdeck_steps_pending: currentPhase["steps_pending"] ?? null,
1829
2109
  flowdeck_last_action: currentPhase["last_action"] ?? null,
1830
- flowdeck_has_codebase: existsSync15(codebaseDirectory)
2110
+ flowdeck_has_codebase: existsSync16(codebaseDirectory)
1831
2111
  };
1832
2112
  if (workspaceRoot && config?.sub_repos && config.sub_repos.length > 0) {
1833
2113
  result.flowdeck_workspace_root = workspaceRoot;
@@ -1842,7 +2122,7 @@ async function sessionStartHook(ctx) {
1842
2122
  flowdeck_phase: null,
1843
2123
  flowdeck_status: "error",
1844
2124
  flowdeck_warning: "State file unreadable. Continuing without flowdeck context.",
1845
- flowdeck_has_codebase: existsSync15(codebaseDirectory)
2125
+ flowdeck_has_codebase: existsSync16(codebaseDirectory)
1846
2126
  };
1847
2127
  if (workspaceRoot && config?.sub_repos && config.sub_repos.length > 0) {
1848
2128
  result.flowdeck_workspace_root = workspaceRoot;
@@ -1908,8 +2188,8 @@ function notifyPermissionNeeded(tool17) {
1908
2188
  }
1909
2189
 
1910
2190
  // src/hooks/patch-trust.ts
1911
- import { existsSync as existsSync16, readFileSync as readFileSync16 } from "fs";
1912
- import { join as join15 } from "path";
2191
+ import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
2192
+ import { join as join16 } from "path";
1913
2193
  var HIGH_RISK_KEYWORDS = [
1914
2194
  "password",
1915
2195
  "secret",
@@ -1931,8 +2211,8 @@ var HIGH_RISK_KEYWORDS = [
1931
2211
  "privilege"
1932
2212
  ];
1933
2213
  function loadVolatility(directory) {
1934
- const p = join15(codebaseDir(directory), "VOLATILITY.json");
1935
- if (!existsSync16(p))
2214
+ const p = join16(codebaseDir(directory), "VOLATILITY.json");
2215
+ if (!existsSync17(p))
1936
2216
  return {};
1937
2217
  try {
1938
2218
  const data = JSON.parse(readFileSync16(p, "utf-8"));
@@ -1945,8 +2225,8 @@ function loadVolatility(directory) {
1945
2225
  }
1946
2226
  }
1947
2227
  function loadFailedPaths(directory) {
1948
- const p = join15(codebaseDir(directory), "FAILURES.json");
1949
- if (!existsSync16(p))
2228
+ const p = join16(codebaseDir(directory), "FAILURES.json");
2229
+ if (!existsSync17(p))
1950
2230
  return [];
1951
2231
  try {
1952
2232
  const data = JSON.parse(readFileSync16(p, "utf-8"));
@@ -2006,8 +2286,8 @@ async function patchTrustHook(ctx, input, output) {
2006
2286
  }
2007
2287
 
2008
2288
  // src/hooks/decision-trace-hook.ts
2009
- import { existsSync as existsSync17, mkdirSync as mkdirSync8, appendFileSync as appendFileSync2 } from "fs";
2010
- import { join as join16 } from "path";
2289
+ import { existsSync as existsSync18, mkdirSync as mkdirSync9, appendFileSync as appendFileSync2 } from "fs";
2290
+ import { join as join17 } from "path";
2011
2291
  async function decisionTraceHook(ctx, input, output) {
2012
2292
  if (input.tool !== "write" && input.tool !== "edit")
2013
2293
  return;
@@ -2016,8 +2296,8 @@ async function decisionTraceHook(ctx, input, output) {
2016
2296
  return;
2017
2297
  const base = codebaseDir(ctx.directory);
2018
2298
  try {
2019
- if (!existsSync17(base))
2020
- mkdirSync8(base, { recursive: true });
2299
+ if (!existsSync18(base))
2300
+ mkdirSync9(base, { recursive: true });
2021
2301
  const entry = {
2022
2302
  timestamp: new Date().toISOString(),
2023
2303
  file_path: filePath,
@@ -2029,22 +2309,24 @@ async function decisionTraceHook(ctx, input, output) {
2029
2309
  risk_level: "unknown",
2030
2310
  auto_recorded: true
2031
2311
  };
2032
- appendFileSync2(join16(base, "DECISIONS.jsonl"), JSON.stringify(entry) + `
2312
+ appendFileSync2(join17(base, "DECISIONS.jsonl"), JSON.stringify(entry) + `
2033
2313
  `, "utf-8");
2034
2314
  } catch {}
2035
2315
  }
2036
2316
 
2037
2317
  // src/services/telemetry.ts
2038
- import { existsSync as existsSync18, readFileSync as readFileSync17, appendFileSync as appendFileSync3, mkdirSync as mkdirSync9 } from "fs";
2039
- import { join as join17 } from "path";
2318
+ import { existsSync as existsSync19, readFileSync as readFileSync17, appendFileSync as appendFileSync3, mkdirSync as mkdirSync10 } from "fs";
2319
+ import { join as join18 } from "path";
2040
2320
  import { randomUUID } from "crypto";
2041
2321
  function telemetryPath(dir) {
2042
- return join17(codebaseDir(dir), "TELEMETRY.jsonl");
2322
+ return join18(codebaseDir(dir), "TELEMETRY.jsonl");
2043
2323
  }
2044
2324
  function appendEvent(dir, partial) {
2325
+ if (process.env.TELEMETRY_ENABLED !== "true")
2326
+ return null;
2045
2327
  const cd = codebaseDir(dir);
2046
- if (!existsSync18(cd))
2047
- mkdirSync9(cd, { recursive: true });
2328
+ if (!existsSync19(cd))
2329
+ mkdirSync10(cd, { recursive: true });
2048
2330
  const event = {
2049
2331
  id: randomUUID(),
2050
2332
  ts: new Date().toISOString(),
@@ -2081,8 +2363,8 @@ async function telemetryAfterHook(context, toolInput, _output) {
2081
2363
  }
2082
2364
 
2083
2365
  // src/services/approval-manager.ts
2084
- import { existsSync as existsSync19, readFileSync as readFileSync18, writeFileSync as writeFileSync13, mkdirSync as mkdirSync10 } from "fs";
2085
- import { join as join18 } from "path";
2366
+ import { existsSync as existsSync20, readFileSync as readFileSync18, writeFileSync as writeFileSync13, mkdirSync as mkdirSync11 } from "fs";
2367
+ import { join as join19 } from "path";
2086
2368
  var APPROVAL_TTL_MS = 30 * 60 * 1000;
2087
2369
  var SENSITIVE_PATTERNS = [
2088
2370
  /auth/i,
@@ -2119,11 +2401,11 @@ function isSensitivePath(filePath) {
2119
2401
  return SENSITIVE_PATTERNS.some((p) => p.test(filePath));
2120
2402
  }
2121
2403
  function approvalsPath(dir) {
2122
- return join18(codebaseDir(dir), "APPROVALS.json");
2404
+ return join19(codebaseDir(dir), "APPROVALS.json");
2123
2405
  }
2124
2406
  function loadStore(dir) {
2125
2407
  const p = approvalsPath(dir);
2126
- if (!existsSync19(p))
2408
+ if (!existsSync20(p))
2127
2409
  return { requests: [] };
2128
2410
  try {
2129
2411
  return JSON.parse(readFileSync18(p, "utf-8"));
@@ -2218,8 +2500,8 @@ function createContextWindowMonitorHook() {
2218
2500
  }
2219
2501
 
2220
2502
  // src/hooks/shell-env-hook.ts
2221
- import { existsSync as existsSync20, readFileSync as readFileSync19 } from "fs";
2222
- import { join as join19 } from "path";
2503
+ import { existsSync as existsSync21, readFileSync as readFileSync19 } from "fs";
2504
+ import { join as join20 } from "path";
2223
2505
  import { createRequire } from "module";
2224
2506
  var _version;
2225
2507
  function getVersion() {
@@ -2255,7 +2537,7 @@ var MARKER_TO_LANG = {
2255
2537
  };
2256
2538
  function detectPackageManager(root) {
2257
2539
  for (const [lockfile, pm] of Object.entries(LOCKFILE_TO_PM)) {
2258
- if (existsSync20(join19(root, lockfile)))
2540
+ if (existsSync21(join20(root, lockfile)))
2259
2541
  return pm;
2260
2542
  }
2261
2543
  return;
@@ -2264,7 +2546,7 @@ function detectLanguages(root) {
2264
2546
  const langs = [];
2265
2547
  const seen = new Set;
2266
2548
  for (const [marker, lang] of Object.entries(MARKER_TO_LANG)) {
2267
- if (!seen.has(lang) && existsSync20(join19(root, marker))) {
2549
+ if (!seen.has(lang) && existsSync21(join20(root, marker))) {
2268
2550
  langs.push(lang);
2269
2551
  seen.add(lang);
2270
2552
  }
@@ -2272,8 +2554,8 @@ function detectLanguages(root) {
2272
2554
  return langs;
2273
2555
  }
2274
2556
  function readCurrentPhase(root) {
2275
- const statePath2 = join19(root, ".planning", "STATE.md");
2276
- if (!existsSync20(statePath2))
2557
+ const statePath2 = join20(root, ".planning", "STATE.md");
2558
+ if (!existsSync21(statePath2))
2277
2559
  return;
2278
2560
  try {
2279
2561
  const content = readFileSync19(statePath2, "utf-8");
@@ -2306,7 +2588,8 @@ function createShellEnvHook(ctx) {
2306
2588
  // src/hooks/todo-hook.ts
2307
2589
  function createTodoHook(client) {
2308
2590
  return async (event) => {
2309
- const todos = event.todos ?? [];
2591
+ const rawTodos = event.todos;
2592
+ const todos = Array.isArray(rawTodos) ? rawTodos : [];
2310
2593
  const completed = todos.filter((t) => t.done || t.status === "completed").length;
2311
2594
  const total = todos.length;
2312
2595
  if (total === 0)
@@ -2375,8 +2658,8 @@ function createSessionIdleHook(client, tracker) {
2375
2658
  }
2376
2659
 
2377
2660
  // src/hooks/compaction-hook.ts
2378
- import { existsSync as existsSync21, readFileSync as readFileSync20 } from "fs";
2379
- import { join as join20 } from "path";
2661
+ import { existsSync as existsSync22, readFileSync as readFileSync20 } from "fs";
2662
+ import { join as join21 } from "path";
2380
2663
  var STRUCTURED_SUMMARY_PROMPT = `
2381
2664
  When summarizing this session, you MUST include the following sections:
2382
2665
 
@@ -2415,8 +2698,8 @@ For each: agent name, status, description, session_id.
2415
2698
  **RESUME, DON'T RESTART.** Use session_id to continue existing sessions.
2416
2699
  `;
2417
2700
  function readPlanningState2(directory) {
2418
- const statePath2 = join20(directory, ".planning", "STATE.md");
2419
- if (!existsSync21(statePath2))
2701
+ const statePath2 = join21(directory, ".planning", "STATE.md");
2702
+ if (!existsSync22(statePath2))
2420
2703
  return null;
2421
2704
  try {
2422
2705
  const content = readFileSync20(statePath2, "utf-8");
@@ -2453,7 +2736,7 @@ function createCompactionHook(ctx, tracker) {
2453
2736
  }
2454
2737
 
2455
2738
  // src/hooks/orchestrator-guard-hook.ts
2456
- var DISABLED = process.env.FLOWDECK_ORCHESTRATOR_GUARD === "off";
2739
+ var DISABLED = process.env.FLOWDECK_ORCHESTRATOR_GUARD !== "on";
2457
2740
  var BLOCKED_TOOLS = new Set([
2458
2741
  "write_file",
2459
2742
  "write",
@@ -2474,7 +2757,6 @@ var BLOCKED_TOOLS = new Set([
2474
2757
  ]);
2475
2758
  var ALWAYS_ALLOWED = new Set([
2476
2759
  "delegate",
2477
- "run-parallel",
2478
2760
  "run-pipeline",
2479
2761
  "council",
2480
2762
  "planning-state",
@@ -2509,7 +2791,7 @@ function blockMessage(toolName) {
2509
2791
  ` + ` delegate({ agent: "@researcher", prompt: "..." }) — research / file analysis
2510
2792
  ` + ` delegate({ agent: "@tester", prompt: "..." }) — tests / commands
2511
2793
 
2512
- ` + `To disable this guard: set FLOWDECK_ORCHESTRATOR_GUARD=off`;
2794
+ ` + `To enable this guard: set FLOWDECK_ORCHESTRATOR_GUARD=on`;
2513
2795
  }
2514
2796
 
2515
2797
  class OrchestratorGuard {
@@ -5513,21 +5795,21 @@ function getAgentConfigs(agentModels) {
5513
5795
  }
5514
5796
 
5515
5797
  // src/config/loader.ts
5516
- import { existsSync as existsSync22, readFileSync as readFileSync21 } from "fs";
5517
- import { join as join21 } from "path";
5518
- import { homedir } from "os";
5798
+ import { existsSync as existsSync23, readFileSync as readFileSync21 } from "fs";
5799
+ import { join as join22 } from "path";
5800
+ import { homedir as homedir2 } from "os";
5519
5801
  var CONFIG_FILENAME = "flowdeck.json";
5520
5802
  function getGlobalConfigDir() {
5521
- return process.env.OPENCODE_CONFIG_DIR || (process.env.XDG_CONFIG_HOME ? join21(process.env.XDG_CONFIG_HOME, "opencode") : join21(homedir(), ".config", "opencode"));
5803
+ return process.env.OPENCODE_CONFIG_DIR || (process.env.XDG_CONFIG_HOME ? join22(process.env.XDG_CONFIG_HOME, "opencode") : join22(homedir2(), ".config", "opencode"));
5522
5804
  }
5523
5805
  function loadFlowDeckConfig(directory) {
5524
5806
  const candidates = [];
5525
5807
  if (directory) {
5526
- candidates.push(join21(directory, ".opencode", CONFIG_FILENAME));
5808
+ candidates.push(join22(directory, ".opencode", CONFIG_FILENAME));
5527
5809
  }
5528
- candidates.push(join21(getGlobalConfigDir(), CONFIG_FILENAME));
5810
+ candidates.push(join22(getGlobalConfigDir(), CONFIG_FILENAME));
5529
5811
  for (const configPath of candidates) {
5530
- if (existsSync22(configPath)) {
5812
+ if (existsSync23(configPath)) {
5531
5813
  try {
5532
5814
  const content = readFileSync21(configPath, "utf-8");
5533
5815
  return JSON.parse(content);
@@ -5539,10 +5821,29 @@ function loadFlowDeckConfig(directory) {
5539
5821
  return {};
5540
5822
  }
5541
5823
  // src/index.ts
5824
+ function loadRulePaths() {
5825
+ const __dir = dirname4(fileURLToPath2(import.meta.url));
5826
+ const rulesDir = join23(__dir, "..", "src", "rules");
5827
+ if (!existsSync24(rulesDir))
5828
+ return [];
5829
+ const paths = [];
5830
+ function walk(dir) {
5831
+ for (const entry of readdirSync3(dir, { withFileTypes: true })) {
5832
+ const full = join23(dir, entry.name);
5833
+ if (entry.isDirectory()) {
5834
+ walk(full);
5835
+ } else if (entry.isFile() && entry.name.endsWith(".md") && entry.name !== "README.md") {
5836
+ paths.push(full);
5837
+ }
5838
+ }
5839
+ }
5840
+ walk(rulesDir);
5841
+ return paths;
5842
+ }
5542
5843
  function loadCommands() {
5543
5844
  const __dir = dirname4(fileURLToPath2(import.meta.url));
5544
- const commandsDir = join22(__dir, "..", "src", "commands");
5545
- if (!existsSync23(commandsDir))
5845
+ const commandsDir = join23(__dir, "..", "src", "commands");
5846
+ if (!existsSync24(commandsDir))
5546
5847
  return {};
5547
5848
  const commands = {};
5548
5849
  try {
@@ -5550,7 +5851,7 @@ function loadCommands() {
5550
5851
  if (!file.endsWith(".md"))
5551
5852
  continue;
5552
5853
  const name = basename(file, ".md");
5553
- const raw = readFileSync22(join22(commandsDir, file), "utf-8");
5854
+ const raw = readFileSync22(join23(commandsDir, file), "utf-8");
5554
5855
  let description;
5555
5856
  let template = raw;
5556
5857
  const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
@@ -5567,7 +5868,6 @@ function loadCommands() {
5567
5868
  }
5568
5869
  var plugin = async (input, _options) => {
5569
5870
  const { directory, client, worktree } = input;
5570
- const runParallelTool = createRunParallelTool(client);
5571
5871
  const runPipelineTool = createRunPipelineTool(client);
5572
5872
  const delegateTool = createDelegateTool(client);
5573
5873
  const councilTool = createCouncilTool(client);
@@ -5628,8 +5928,8 @@ var plugin = async (input, _options) => {
5628
5928
  }
5629
5929
  }
5630
5930
  }
5631
- const skillsDir = join22(dirname4(fileURLToPath2(import.meta.url)), "..", "src", "skills");
5632
- if (existsSync23(skillsDir)) {
5931
+ const skillsDir = join23(dirname4(fileURLToPath2(import.meta.url)), "..", "src", "skills");
5932
+ if (existsSync24(skillsDir)) {
5633
5933
  const cfgAny = cfg;
5634
5934
  if (!cfgAny.skills || typeof cfgAny.skills !== "object") {
5635
5935
  cfgAny.skills = { paths: [] };
@@ -5641,12 +5941,23 @@ var plugin = async (input, _options) => {
5641
5941
  cfgSkills.paths.push(skillsDir);
5642
5942
  }
5643
5943
  }
5944
+ const rulePaths = loadRulePaths();
5945
+ if (rulePaths.length > 0) {
5946
+ if (!Array.isArray(cfg.instructions)) {
5947
+ cfg.instructions = [];
5948
+ }
5949
+ const existing = new Set(cfg.instructions);
5950
+ for (const p of rulePaths) {
5951
+ if (!existing.has(p)) {
5952
+ cfg.instructions.push(p);
5953
+ }
5954
+ }
5955
+ }
5644
5956
  },
5645
5957
  tool: {
5646
5958
  "planning-state": planningStateTool,
5647
5959
  "codebase-state": codebaseStateTool,
5648
5960
  "workspace-state": workspaceStateTool,
5649
- "run-parallel": runParallelTool,
5650
5961
  "run-pipeline": runPipelineTool,
5651
5962
  delegate: delegateTool,
5652
5963
  "repo-memory": repoMemoryTool,
@@ -5658,7 +5969,8 @@ var plugin = async (input, _options) => {
5658
5969
  council: councilTool,
5659
5970
  "context-generator": contextGeneratorTool,
5660
5971
  "create-skill": createSkillTool,
5661
- reflect: reflectTool
5972
+ reflect: reflectTool,
5973
+ "memory-search": memorySearchTool
5662
5974
  },
5663
5975
  "shell.env": shellEnvHook,
5664
5976
  "todo.updated": todoHook,
@@ -5670,11 +5982,34 @@ var plugin = async (input, _options) => {
5670
5982
  },
5671
5983
  event: async ({ event }) => {
5672
5984
  const type = event?.type ?? "";
5673
- await contextMonitor.event({ event });
5674
- orchestratorGuard.onEvent(event);
5675
5985
  if (type === "session.created" || type === "session.started") {
5986
+ const sessionId = event?.sessionID ?? event?.sessionId ?? "";
5987
+ if (sessionId) {
5988
+ memoryHook.onSessionCreated(directory, sessionId, event?.prompt);
5989
+ }
5676
5990
  await sessionStartHook({ directory });
5677
- } else if (type === "session.idle") {
5991
+ } else if (type === "message.updated" && event?.event) {
5992
+ const msgEvent = event.event;
5993
+ const sessionId = msgEvent?.sessionID ?? msgEvent?.sessionId ?? "";
5994
+ if (sessionId) {
5995
+ memoryHook.onMessageUpdated(sessionId, msgEvent.role, msgEvent.content, directory);
5996
+ }
5997
+ } else if (type === "session.compacted" && event?.event) {
5998
+ const compactEvent = event.event;
5999
+ const sessionId = compactEvent?.sessionID ?? compactEvent?.sessionId ?? "";
6000
+ if (sessionId) {
6001
+ memoryHook.onSessionCompact(sessionId, compactEvent.summary ?? "");
6002
+ }
6003
+ } else if (type === "session.deleted" && event?.event) {
6004
+ const delEvent = event.event;
6005
+ const sessionId = delEvent?.sessionID ?? delEvent?.sessionId ?? "";
6006
+ if (sessionId) {
6007
+ memoryHook.clearSession(sessionId);
6008
+ }
6009
+ }
6010
+ await contextMonitor.event({ event });
6011
+ orchestratorGuard.onEvent(event);
6012
+ if (type === "session.idle") {
5678
6013
  await sessionIdleHook();
5679
6014
  await autoLearnHook();
5680
6015
  }
@@ -5690,6 +6025,10 @@ var plugin = async (input, _options) => {
5690
6025
  },
5691
6026
  "tool.execute.after": async (toolInput, toolOutput) => {
5692
6027
  await telemetryAfterHook({ directory }, toolInput, toolOutput);
6028
+ const sessionId = toolInput?.sessionID ?? toolInput?.sessionId ?? "";
6029
+ if (sessionId && toolInput?.tool) {
6030
+ memoryHook.onToolExecuted(sessionId, toolInput.tool, toolInput, toolOutput?.output ?? null, directory);
6031
+ }
5693
6032
  await contextMonitor["tool.execute.after"](toolInput, toolOutput);
5694
6033
  }
5695
6034
  };