@dv.nghiem/flowdeck 0.2.4 → 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 (74) 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.map +1 -1
  5. package/dist/hooks/todo-hook.d.ts +1 -7
  6. package/dist/hooks/todo-hook.d.ts.map +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +709 -420
  9. package/dist/services/memory-store.d.ts +40 -0
  10. package/dist/services/memory-store.d.ts.map +1 -0
  11. package/dist/tools/memory-search.d.ts +3 -0
  12. package/dist/tools/memory-search.d.ts.map +1 -0
  13. package/docs/commands/fd-doctor.md +21 -0
  14. package/docs/commands/fd-quick.md +33 -0
  15. package/docs/commands/fd-reflect.md +23 -0
  16. package/docs/commands/fd-status.md +31 -0
  17. package/docs/commands/fd-translate-intent.md +17 -0
  18. package/docs/commands.md +209 -271
  19. package/docs/configuration.md +1 -2
  20. package/docs/index.md +22 -28
  21. package/docs/memory.md +69 -0
  22. package/docs/quick-start.md +1 -1
  23. package/package.json +1 -1
  24. package/src/commands/fd-deploy-check.md +131 -11
  25. package/src/commands/fd-new-project.md +14 -1
  26. package/src/commands/fd-quick.md +60 -0
  27. package/src/commands/fd-reflect.md +41 -2
  28. package/src/commands/fd-status.md +84 -0
  29. package/src/rules/README.md +8 -7
  30. package/src/skills/agent-harness-construction/SKILL.md +227 -0
  31. package/src/skills/api-design/SKILL.md +5 -0
  32. package/src/skills/backend-patterns/SKILL.md +105 -0
  33. package/src/skills/clean-architecture/SKILL.md +85 -0
  34. package/src/skills/cqrs/SKILL.md +230 -0
  35. package/src/skills/ddd-architecture/SKILL.md +104 -0
  36. package/src/skills/django-patterns/SKILL.md +304 -0
  37. package/src/skills/django-tdd/SKILL.md +297 -0
  38. package/src/skills/event-driven-architecture/SKILL.md +152 -0
  39. package/src/skills/frontend-pattern/SKILL.md +159 -0
  40. package/src/skills/hexagonal-architecture/SKILL.md +80 -0
  41. package/src/skills/layered-architecture/SKILL.md +64 -0
  42. package/src/skills/postgres-patterns/SKILL.md +74 -0
  43. package/src/skills/python-patterns/SKILL.md +5 -0
  44. package/src/skills/saga-architecture/SKILL.md +113 -0
  45. package/dist/tools/run-parallel.d.ts +0 -4
  46. package/dist/tools/run-parallel.d.ts.map +0 -1
  47. package/docs/command-migration.md +0 -175
  48. package/docs/commands/fd-analyze-change.md +0 -107
  49. package/docs/commands/fd-dashboard.md +0 -11
  50. package/docs/commands/fd-evaluate-risk.md +0 -134
  51. package/docs/commands/fd-guarded-edit.md +0 -105
  52. package/docs/commands/fd-progress.md +0 -11
  53. package/docs/commands/fd-review-code.md +0 -29
  54. package/docs/commands/fd-roadmap.md +0 -10
  55. package/docs/commands/fd-settings.md +0 -10
  56. package/docs/parallel-execution.md +0 -255
  57. package/src/commands/fd-analyze-change.md +0 -57
  58. package/src/commands/fd-approve.md +0 -64
  59. package/src/commands/fd-blast-radius.md +0 -49
  60. package/src/commands/fd-dashboard.md +0 -57
  61. package/src/commands/fd-evaluate-risk.md +0 -62
  62. package/src/commands/fd-guarded-edit.md +0 -69
  63. package/src/commands/fd-impact-radar.md +0 -51
  64. package/src/commands/fd-learn.md +0 -36
  65. package/src/commands/fd-progress.md +0 -50
  66. package/src/commands/fd-regression-predict.md +0 -57
  67. package/src/commands/fd-review-code.md +0 -96
  68. package/src/commands/fd-review-route.md +0 -54
  69. package/src/commands/fd-roadmap.md +0 -46
  70. package/src/commands/fd-settings.md +0 -57
  71. package/src/commands/fd-test-gap.md +0 -54
  72. package/src/commands/fd-volatility-map.md +0 -64
  73. package/src/commands/fd-workspace-status.md +0 -34
  74. package/src/skills/parallel-execute/SKILL.md +0 -92
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { readdirSync as readdirSync3, readFileSync as readFileSync22, existsSync as existsSync23 } from "fs";
2
+ import { readdirSync as readdirSync3, readFileSync as readFileSync22, existsSync as existsSync24 } from "fs";
3
3
  import { join as join23, basename } from "path";
4
4
  import { dirname as dirname4 } from "path";
5
5
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -507,198 +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
-
513
- // src/services/telemetry.ts
514
- import { existsSync as existsSync5, readFileSync as readFileSync5, appendFileSync, mkdirSync as mkdirSync2 } from "fs";
515
- import { join as join5 } from "path";
516
- import { randomUUID } from "crypto";
517
- function telemetryPath(dir) {
518
- return join5(codebaseDir(dir), "TELEMETRY.jsonl");
519
- }
520
- function appendEvent(dir, partial) {
521
- if (process.env.TELEMETRY_ENABLED !== "true")
522
- return null;
523
- const cd = codebaseDir(dir);
524
- if (!existsSync5(cd))
525
- mkdirSync2(cd, { recursive: true });
526
- const event = {
527
- id: randomUUID(),
528
- ts: new Date().toISOString(),
529
- ...partial
530
- };
531
- appendFileSync(telemetryPath(dir), JSON.stringify(event) + `
532
- `, "utf-8");
533
- return event;
534
- }
535
-
536
- // src/tools/run-parallel.ts
537
- import { writeFileSync as writeFileSync5 } from "fs";
538
- import { join as join6 } from "path";
539
512
  function extractText(parts) {
540
513
  return parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join(`
541
514
  `);
542
515
  }
543
- function createRunParallelTool(client) {
544
- return tool4({
545
- 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.",
546
- args: {
547
- tasks: tool4.schema.array(tool4.schema.object({
548
- agent: tool4.schema.string(),
549
- prompt: tool4.schema.string(),
550
- context: tool4.schema.string().optional()
551
- }))
552
- },
553
- async execute(args, context) {
554
- const startTime = Date.now();
555
- const childSessionIds = [];
556
- context.abort.addEventListener("abort", () => {
557
- for (const id of childSessionIds) {
558
- client.session.abort({
559
- path: { id },
560
- query: { directory: context.directory }
561
- }).catch(() => {});
562
- }
563
- });
564
- const dir = context.directory ?? process.cwd();
565
- const promises = args.tasks.map(async (task) => {
566
- const taskStart = Date.now();
567
- const createRes = await client.session.create({
568
- body: { parentID: context.sessionID, title: `${task.agent}-subtask` },
569
- query: { directory: context.directory }
570
- });
571
- if (createRes.error || !createRes.data?.id) {
572
- return {
573
- agent: task.agent,
574
- success: false,
575
- error: `Failed to create session: ${createRes.error?.detail ?? "unknown"}`,
576
- duration_ms: Date.now() - taskStart
577
- };
578
- }
579
- const childId = createRes.data.id;
580
- childSessionIds.push(childId);
581
- appendEvent(dir, {
582
- session_id: context.sessionID,
583
- run_id: process.env.OPENCODE_RUN_ID ?? "run-0",
584
- event: "agent.dispatch",
585
- agent: task.agent,
586
- status: "ok",
587
- meta: { child_session_id: childId, task_index: args.tasks.findIndex((t) => t.agent === task.agent) }
588
- });
589
- const fullPrompt = task.context ? `${task.context}
590
-
591
- ---
592
-
593
- ${task.prompt}` : task.prompt;
594
- const promptRes = await client.session.prompt({
595
- path: { id: childId },
596
- body: {
597
- agent: task.agent,
598
- parts: [{ type: "text", text: fullPrompt }],
599
- tools: { question: false }
600
- },
601
- query: { directory: context.directory }
602
- });
603
- if (promptRes.error) {
604
- appendEvent(dir, {
605
- session_id: context.sessionID,
606
- run_id: process.env.OPENCODE_RUN_ID ?? "run-0",
607
- event: "agent.complete",
608
- agent: task.agent,
609
- status: "error",
610
- duration_ms: Date.now() - taskStart,
611
- meta: { child_session_id: childId, error: `Prompt failed: ${promptRes.error?.detail ?? "unknown"}` }
612
- });
613
- return {
614
- agent: task.agent,
615
- session_id: childId,
616
- success: false,
617
- error: `Prompt failed: ${promptRes.error?.detail ?? "unknown"}`,
618
- duration_ms: Date.now() - taskStart
619
- };
620
- }
621
- const info = promptRes.data?.info;
622
- if (info?.error) {
623
- appendEvent(dir, {
624
- session_id: context.sessionID,
625
- run_id: process.env.OPENCODE_RUN_ID ?? "run-0",
626
- event: "agent.complete",
627
- agent: task.agent,
628
- status: "error",
629
- duration_ms: Date.now() - taskStart,
630
- meta: { child_session_id: childId, error: JSON.stringify(info.error) }
631
- });
632
- return {
633
- agent: task.agent,
634
- session_id: childId,
635
- success: false,
636
- error: `Agent error: ${JSON.stringify(info.error)}`,
637
- duration_ms: Date.now() - taskStart
638
- };
639
- }
640
- const output = extractText(promptRes.data?.parts ?? []);
641
- appendEvent(dir, {
642
- session_id: context.sessionID,
643
- run_id: process.env.OPENCODE_RUN_ID ?? "run-0",
644
- event: "agent.complete",
645
- agent: task.agent,
646
- status: "ok",
647
- duration_ms: Date.now() - taskStart,
648
- meta: { child_session_id: childId, output_length: output?.length ?? 0 }
649
- });
650
- return {
651
- agent: task.agent,
652
- session_id: childId,
653
- success: true,
654
- output: output || "(no text output)",
655
- duration_ms: Date.now() - taskStart
656
- };
657
- });
658
- const settled = await Promise.allSettled(promises);
659
- const results = settled.map((result, i) => {
660
- if (result.status === "fulfilled")
661
- return result.value;
662
- return {
663
- agent: args.tasks[i].agent,
664
- success: false,
665
- error: result.reason?.message || String(result.reason),
666
- duration_ms: Date.now() - startTime
667
- };
668
- });
669
- const progress = {
670
- total: args.tasks.length,
671
- completed: results.filter((r) => r.success || r.error).length,
672
- in_progress: childSessionIds.length,
673
- results: results.map((r) => ({ agent: r.agent, success: r.success, duration_ms: r.duration_ms })),
674
- total_duration_ms: Date.now() - startTime
675
- };
676
- writeFileSync5(join6(codebaseDir(dir), "parallel-progress.json"), JSON.stringify(progress, null, 2));
677
- return JSON.stringify({
678
- results,
679
- total_duration_ms: Date.now() - startTime,
680
- failures: results.filter((r) => !r.success).map((r) => r.agent)
681
- });
682
- }
683
- });
684
- }
685
-
686
- // src/tools/run-pipeline.ts
687
- import { tool as tool5 } from "@opencode-ai/plugin";
688
- function extractText2(parts) {
689
- return parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join(`
690
- `);
691
- }
692
516
  function createRunPipelineTool(client) {
693
- return tool5({
517
+ return tool4({
694
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.",
695
519
  args: {
696
- steps: tool5.schema.array(tool5.schema.object({
697
- agent: tool5.schema.string(),
698
- prompt: tool5.schema.string()
520
+ steps: tool4.schema.array(tool4.schema.object({
521
+ agent: tool4.schema.string(),
522
+ prompt: tool4.schema.string()
699
523
  })),
700
- initial_context: tool5.schema.string().optional(),
701
- 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)
702
526
  },
703
527
  async execute(args, context) {
704
528
  const startTime = Date.now();
@@ -771,7 +595,7 @@ ${step.prompt}` : step.prompt;
771
595
  }
772
596
  continue;
773
597
  }
774
- const output = extractText2(promptRes.data?.parts ?? []);
598
+ const output = extractText(promptRes.data?.parts ?? []);
775
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 });
776
600
  carryContext = output;
777
601
  }
@@ -788,18 +612,18 @@ ${step.prompt}` : step.prompt;
788
612
  }
789
613
 
790
614
  // src/tools/delegate.ts
791
- import { tool as tool6 } from "@opencode-ai/plugin";
792
- function extractText3(parts) {
615
+ import { tool as tool5 } from "@opencode-ai/plugin";
616
+ function extractText2(parts) {
793
617
  return parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join(`
794
618
  `);
795
619
  }
796
620
  function createDelegateTool(client) {
797
- return tool6({
621
+ return tool5({
798
622
  description: "Delegate a task to a single agent via a child session. Returns the agent's output.",
799
623
  args: {
800
- agent: tool6.schema.string(),
801
- prompt: tool6.schema.string(),
802
- context: tool6.schema.string().optional()
624
+ agent: tool5.schema.string(),
625
+ prompt: tool5.schema.string(),
626
+ context: tool5.schema.string().optional()
803
627
  },
804
628
  async execute(args, context) {
805
629
  const startTime = Date.now();
@@ -855,7 +679,7 @@ ${args.prompt}` : args.prompt;
855
679
  duration_ms: Date.now() - startTime
856
680
  });
857
681
  }
858
- const output = extractText3(promptRes.data?.parts ?? []);
682
+ const output = extractText2(promptRes.data?.parts ?? []);
859
683
  return JSON.stringify({
860
684
  agent: args.agent,
861
685
  session_id: childId,
@@ -868,53 +692,53 @@ ${args.prompt}` : args.prompt;
868
692
  }
869
693
 
870
694
  // src/tools/repo-memory.ts
871
- import { tool as tool7 } from "@opencode-ai/plugin";
872
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
873
- import { join as join7 } from "path";
695
+ import { tool as tool6 } from "@opencode-ai/plugin";
696
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
697
+ import { join as join5 } from "path";
874
698
  var MEMORY_FILE = "MEMORY.json";
875
699
  function memoryPath(directory) {
876
- return join7(codebaseDir(directory), MEMORY_FILE);
700
+ return join5(codebaseDir(directory), MEMORY_FILE);
877
701
  }
878
702
  function emptyMemory() {
879
703
  return { version: "1.0", last_updated: new Date().toISOString(), nodes: {} };
880
704
  }
881
705
  function readMemory(directory) {
882
706
  const p = memoryPath(directory);
883
- if (!existsSync6(p))
707
+ if (!existsSync5(p))
884
708
  return emptyMemory();
885
709
  try {
886
- return JSON.parse(readFileSync6(p, "utf-8"));
710
+ return JSON.parse(readFileSync5(p, "utf-8"));
887
711
  } catch {
888
712
  return emptyMemory();
889
713
  }
890
714
  }
891
715
  function writeMemory(directory, memory) {
892
716
  const base = codebaseDir(directory);
893
- if (!existsSync6(base))
894
- mkdirSync3(base, { recursive: true });
717
+ if (!existsSync5(base))
718
+ mkdirSync2(base, { recursive: true });
895
719
  memory.last_updated = new Date().toISOString();
896
- writeFileSync6(memoryPath(directory), JSON.stringify(memory, null, 2), "utf-8");
720
+ writeFileSync5(memoryPath(directory), JSON.stringify(memory, null, 2), "utf-8");
897
721
  }
898
- var repoMemoryTool = tool7({
722
+ var repoMemoryTool = tool6({
899
723
  description: "Repo Memory Graph: read/write/query persistent architecture graph in .codebase/MEMORY.json (modules, dependencies, ownership, bug history, conventions)",
900
724
  args: {
901
- action: tool7.schema.enum(["read", "write_node", "query", "delete_node"]),
902
- node_id: tool7.schema.string().optional(),
903
- node: tool7.schema.object({
904
- type: tool7.schema.enum(["module", "service", "api", "schema", "config"]),
905
- path: tool7.schema.string(),
906
- owner: tool7.schema.string().optional(),
907
- tags: tool7.schema.array(tool7.schema.string()),
908
- dependencies: tool7.schema.array(tool7.schema.string()),
909
- dependents: tool7.schema.array(tool7.schema.string()),
910
- bug_history: tool7.schema.array(tool7.schema.string()),
911
- 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())
912
736
  }).optional(),
913
- query: tool7.schema.object({
914
- type: tool7.schema.enum(["module", "service", "api", "schema", "config"]).optional(),
915
- owner: tool7.schema.string().optional(),
916
- tag: tool7.schema.string().optional(),
917
- 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()
918
742
  }).optional()
919
743
  },
920
744
  async execute(args, context) {
@@ -969,50 +793,50 @@ var repoMemoryTool = tool7({
969
793
  });
970
794
 
971
795
  // src/tools/failure-replay.ts
972
- import { tool as tool8 } from "@opencode-ai/plugin";
973
- import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
974
- import { join as join8 } from "path";
796
+ import { tool as tool7 } from "@opencode-ai/plugin";
797
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
798
+ import { join as join6 } from "path";
975
799
  var FAILURES_FILE = "FAILURES.json";
976
800
  function failuresPath(directory) {
977
- return join8(codebaseDir(directory), FAILURES_FILE);
801
+ return join6(codebaseDir(directory), FAILURES_FILE);
978
802
  }
979
803
  function readStore(directory) {
980
804
  const p = failuresPath(directory);
981
- if (!existsSync7(p))
805
+ if (!existsSync6(p))
982
806
  return { version: "1.0", last_updated: new Date().toISOString(), entries: [] };
983
807
  try {
984
- return JSON.parse(readFileSync7(p, "utf-8"));
808
+ return JSON.parse(readFileSync6(p, "utf-8"));
985
809
  } catch {
986
810
  return { version: "1.0", last_updated: new Date().toISOString(), entries: [] };
987
811
  }
988
812
  }
989
813
  function writeStore(directory, store) {
990
814
  const base = codebaseDir(directory);
991
- if (!existsSync7(base))
992
- mkdirSync4(base, { recursive: true });
815
+ if (!existsSync6(base))
816
+ mkdirSync3(base, { recursive: true });
993
817
  store.last_updated = new Date().toISOString();
994
- writeFileSync7(failuresPath(directory), JSON.stringify(store, null, 2), "utf-8");
818
+ writeFileSync6(failuresPath(directory), JSON.stringify(store, null, 2), "utf-8");
995
819
  }
996
- var failureReplayTool = tool8({
820
+ var failureReplayTool = tool7({
997
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",
998
822
  args: {
999
- action: tool8.schema.enum(["record", "query", "list", "mark_resolved"]),
1000
- entry: tool8.schema.object({
1001
- id: tool8.schema.string(),
1002
- type: tool8.schema.enum(["reverted_commit", "failed_deployment", "flaky_test", "bug_fix", "build_failure"]),
1003
- description: tool8.schema.string(),
1004
- affected_paths: tool8.schema.array(tool8.schema.string()),
1005
- root_cause: tool8.schema.string().optional(),
1006
- fix_applied: tool8.schema.string().optional(),
1007
- 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())
1008
832
  }).optional(),
1009
- query: tool8.schema.object({
1010
- type: tool8.schema.enum(["reverted_commit", "failed_deployment", "flaky_test", "bug_fix", "build_failure"]).optional(),
1011
- path_prefix: tool8.schema.string().optional(),
1012
- tag: tool8.schema.string().optional(),
1013
- 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()
1014
838
  }).optional(),
1015
- entry_id: tool8.schema.string().optional()
839
+ entry_id: tool7.schema.string().optional()
1016
840
  },
1017
841
  async execute(args, context) {
1018
842
  const dir = context.directory ?? process.cwd();
@@ -1074,18 +898,18 @@ var failureReplayTool = tool8({
1074
898
  });
1075
899
 
1076
900
  // src/tools/decision-trace.ts
1077
- import { tool as tool9 } from "@opencode-ai/plugin";
1078
- import { readFileSync as readFileSync8, existsSync as existsSync8, mkdirSync as mkdirSync5, appendFileSync as appendFileSync2 } from "fs";
1079
- import { join as join9 } from "path";
901
+ import { tool as tool8 } from "@opencode-ai/plugin";
902
+ import { readFileSync as readFileSync7, existsSync as existsSync7, mkdirSync as mkdirSync4, appendFileSync } from "fs";
903
+ import { join as join7 } from "path";
1080
904
  var DECISIONS_FILE = "DECISIONS.jsonl";
1081
905
  function decisionsPath(directory) {
1082
- return join9(codebaseDir(directory), DECISIONS_FILE);
906
+ return join7(codebaseDir(directory), DECISIONS_FILE);
1083
907
  }
1084
908
  function readDecisions(directory) {
1085
909
  const p = decisionsPath(directory);
1086
- if (!existsSync8(p))
910
+ if (!existsSync7(p))
1087
911
  return [];
1088
- return readFileSync8(p, "utf-8").split(`
912
+ return readFileSync7(p, "utf-8").split(`
1089
913
  `).filter((l) => l.trim()).map((l) => {
1090
914
  try {
1091
915
  return JSON.parse(l);
@@ -1094,29 +918,29 @@ function readDecisions(directory) {
1094
918
  }
1095
919
  }).filter(Boolean);
1096
920
  }
1097
- var decisionTraceTool = tool9({
921
+ var decisionTraceTool = tool8({
1098
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.",
1099
923
  args: {
1100
- action: tool9.schema.enum(["record", "query", "get_for_file"]),
1101
- entry: tool9.schema.object({
1102
- id: tool9.schema.string(),
1103
- file_path: tool9.schema.string(),
1104
- change_type: tool9.schema.enum(["create", "edit", "delete", "refactor"]),
1105
- rationale: tool9.schema.string(),
1106
- evidence: tool9.schema.array(tool9.schema.string()),
1107
- assumptions: tool9.schema.array(tool9.schema.string()),
1108
- alternatives_considered: tool9.schema.array(tool9.schema.string()),
1109
- risk_level: tool9.schema.enum(["low", "medium", "high"]),
1110
- agent: tool9.schema.string().optional(),
1111
- 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()
1112
936
  }).optional(),
1113
- query: tool9.schema.object({
1114
- file_path: tool9.schema.string().optional(),
1115
- change_type: tool9.schema.enum(["create", "edit", "delete", "refactor"]).optional(),
1116
- risk_level: tool9.schema.enum(["low", "medium", "high"]).optional(),
1117
- 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()
1118
942
  }).optional(),
1119
- file_path: tool9.schema.string().optional()
943
+ file_path: tool8.schema.string().optional()
1120
944
  },
1121
945
  async execute(args, context) {
1122
946
  const dir = context.directory ?? process.cwd();
@@ -1125,10 +949,10 @@ var decisionTraceTool = tool9({
1125
949
  case "record": {
1126
950
  if (!args.entry)
1127
951
  return JSON.stringify({ error: "entry required" });
1128
- if (!existsSync8(base))
1129
- mkdirSync5(base, { recursive: true });
952
+ if (!existsSync7(base))
953
+ mkdirSync4(base, { recursive: true });
1130
954
  const entry = { ...args.entry, timestamp: new Date().toISOString() };
1131
- appendFileSync2(decisionsPath(dir), JSON.stringify(entry) + `
955
+ appendFileSync(decisionsPath(dir), JSON.stringify(entry) + `
1132
956
  `, "utf-8");
1133
957
  return JSON.stringify({ success: true, id: args.entry.id });
1134
958
  }
@@ -1159,29 +983,29 @@ var decisionTraceTool = tool9({
1159
983
  });
1160
984
 
1161
985
  // src/tools/volatility-map.ts
1162
- import { tool as tool10 } from "@opencode-ai/plugin";
1163
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync9, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
1164
- import { join as join10 } from "path";
986
+ import { tool as tool9 } from "@opencode-ai/plugin";
987
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync8, existsSync as existsSync8, mkdirSync as mkdirSync5 } from "fs";
988
+ import { join as join8 } from "path";
1165
989
  var VOLATILITY_FILE = "VOLATILITY.json";
1166
990
  function volatilityPath(directory) {
1167
- return join10(codebaseDir(directory), VOLATILITY_FILE);
991
+ return join8(codebaseDir(directory), VOLATILITY_FILE);
1168
992
  }
1169
993
  function readStore2(directory) {
1170
994
  const p = volatilityPath(directory);
1171
- if (!existsSync9(p))
995
+ if (!existsSync8(p))
1172
996
  return { version: "1.0", last_updated: new Date().toISOString(), generated_at: new Date().toISOString(), entries: [] };
1173
997
  try {
1174
- return JSON.parse(readFileSync9(p, "utf-8"));
998
+ return JSON.parse(readFileSync8(p, "utf-8"));
1175
999
  } catch {
1176
1000
  return { version: "1.0", last_updated: new Date().toISOString(), generated_at: new Date().toISOString(), entries: [] };
1177
1001
  }
1178
1002
  }
1179
1003
  function writeStore2(directory, store) {
1180
1004
  const base = codebaseDir(directory);
1181
- if (!existsSync9(base))
1182
- mkdirSync6(base, { recursive: true });
1005
+ if (!existsSync8(base))
1006
+ mkdirSync5(base, { recursive: true });
1183
1007
  store.last_updated = new Date().toISOString();
1184
- writeFileSync9(volatilityPath(directory), JSON.stringify(store, null, 2), "utf-8");
1008
+ writeFileSync8(volatilityPath(directory), JSON.stringify(store, null, 2), "utf-8");
1185
1009
  }
1186
1010
  function stabilityLabel(churn, hotfixes, todos) {
1187
1011
  const score = churn + hotfixes * 10 + todos * 2;
@@ -1193,29 +1017,29 @@ function stabilityLabel(churn, hotfixes, todos) {
1193
1017
  return "moderate";
1194
1018
  return "stable";
1195
1019
  }
1196
- var volatilityMapTool = tool10({
1020
+ var volatilityMapTool = tool9({
1197
1021
  description: "Codebase Volatility Map: read/write/query .codebase/VOLATILITY.json — highlights unstable zones based on churn, hotfix frequency, and TODO clusters",
1198
1022
  args: {
1199
- action: tool10.schema.enum(["read", "write", "query_hotspots", "update_entry"]),
1200
- entries: tool10.schema.array(tool10.schema.object({
1201
- path: tool10.schema.string(),
1202
- churn_score: tool10.schema.number(),
1203
- hotfix_count: tool10.schema.number(),
1204
- todo_count: tool10.schema.number(),
1205
- last_breakage: tool10.schema.string().optional(),
1206
- 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())
1207
1031
  })).optional(),
1208
- entry: tool10.schema.object({
1209
- path: tool10.schema.string(),
1210
- churn_score: tool10.schema.number(),
1211
- hotfix_count: tool10.schema.number(),
1212
- todo_count: tool10.schema.number(),
1213
- last_breakage: tool10.schema.string().optional(),
1214
- 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())
1215
1039
  }).optional(),
1216
- threshold: tool10.schema.enum(["stable", "moderate", "volatile", "critical"]).optional(),
1217
- path_prefix: tool10.schema.string().optional(),
1218
- 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()
1219
1043
  },
1220
1044
  async execute(args, context) {
1221
1045
  const dir = context.directory ?? process.cwd();
@@ -1267,48 +1091,48 @@ var volatilityMapTool = tool10({
1267
1091
  });
1268
1092
 
1269
1093
  // src/tools/policy-engine.ts
1270
- import { tool as tool11 } from "@opencode-ai/plugin";
1271
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync10, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
1272
- import { join as join11 } from "path";
1094
+ import { tool as tool10 } from "@opencode-ai/plugin";
1095
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync9, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
1096
+ import { join as join9 } from "path";
1273
1097
  var POLICIES_FILE = "POLICIES.json";
1274
1098
  function policiesPath(directory) {
1275
- return join11(codebaseDir(directory), POLICIES_FILE);
1099
+ return join9(codebaseDir(directory), POLICIES_FILE);
1276
1100
  }
1277
1101
  function readStore3(directory) {
1278
1102
  const p = policiesPath(directory);
1279
- if (!existsSync10(p))
1103
+ if (!existsSync9(p))
1280
1104
  return { version: "1.0", last_updated: new Date().toISOString(), policies: [] };
1281
1105
  try {
1282
- return JSON.parse(readFileSync10(p, "utf-8"));
1106
+ return JSON.parse(readFileSync9(p, "utf-8"));
1283
1107
  } catch {
1284
1108
  return { version: "1.0", last_updated: new Date().toISOString(), policies: [] };
1285
1109
  }
1286
1110
  }
1287
1111
  function writeStore3(directory, store) {
1288
1112
  const base = codebaseDir(directory);
1289
- if (!existsSync10(base))
1290
- mkdirSync7(base, { recursive: true });
1113
+ if (!existsSync9(base))
1114
+ mkdirSync6(base, { recursive: true });
1291
1115
  store.last_updated = new Date().toISOString();
1292
- writeFileSync10(policiesPath(directory), JSON.stringify(store, null, 2), "utf-8");
1116
+ writeFileSync9(policiesPath(directory), JSON.stringify(store, null, 2), "utf-8");
1293
1117
  }
1294
- var policyEngineTool = tool11({
1118
+ var policyEngineTool = tool10({
1295
1119
  description: "Self-Healing Policy Engine: manage .codebase/POLICIES.json — add, list, query, toggle, and record violations of editing policies learned from past failures",
1296
1120
  args: {
1297
- action: tool11.schema.enum(["list", "add", "record_violation", "toggle", "query"]),
1298
- policy: tool11.schema.object({
1299
- id: tool11.schema.string(),
1300
- name: tool11.schema.string(),
1301
- trigger: tool11.schema.string(),
1302
- rule: tool11.schema.string(),
1303
- source: tool11.schema.enum(["manual", "learned"]),
1304
- 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()
1305
1129
  }).optional(),
1306
- policy_id: tool11.schema.string().optional(),
1307
- active: tool11.schema.boolean().optional(),
1308
- query: tool11.schema.object({
1309
- source: tool11.schema.enum(["manual", "learned"]).optional(),
1310
- active_only: tool11.schema.boolean().optional(),
1311
- 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()
1312
1136
  }).optional()
1313
1137
  },
1314
1138
  async execute(args, context) {
@@ -1370,22 +1194,22 @@ var policyEngineTool = tool11({
1370
1194
  });
1371
1195
 
1372
1196
  // src/tools/hash-edit.ts
1373
- import { tool as tool12 } from "@opencode-ai/plugin";
1374
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "fs";
1197
+ import { tool as tool11 } from "@opencode-ai/plugin";
1198
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync10 } from "fs";
1375
1199
  import { createHash } from "crypto";
1376
- var hashEditTool = tool12({
1200
+ var hashEditTool = tool11({
1377
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.",
1378
1202
  args: {
1379
- filePath: tool12.schema.string(),
1380
- targetContent: tool12.schema.string(),
1381
- expectedHash: tool12.schema.string().optional(),
1382
- 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()
1383
1207
  },
1384
1208
  async execute(args, context) {
1385
1209
  const fullPath = args.filePath.startsWith("/") ? args.filePath : `${context.directory}/${args.filePath}`;
1386
1210
  let content;
1387
1211
  try {
1388
- content = readFileSync11(fullPath, "utf-8");
1212
+ content = readFileSync10(fullPath, "utf-8");
1389
1213
  } catch (e) {
1390
1214
  return `Error: Could not read file ${args.filePath}`;
1391
1215
  }
@@ -1399,19 +1223,19 @@ var hashEditTool = tool12({
1399
1223
  }
1400
1224
  }
1401
1225
  const newContent = content.replace(args.targetContent, args.replacementContent);
1402
- writeFileSync11(fullPath, newContent, "utf-8");
1226
+ writeFileSync10(fullPath, newContent, "utf-8");
1403
1227
  return `Successfully updated ${args.filePath} using hash-anchored edit.`;
1404
1228
  }
1405
1229
  });
1406
1230
 
1407
1231
  // src/tools/council.ts
1408
- import { tool as tool13 } from "@opencode-ai/plugin";
1232
+ import { tool as tool12 } from "@opencode-ai/plugin";
1409
1233
  function createCouncilTool(client) {
1410
- return tool13({
1234
+ return tool12({
1411
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.",
1412
1236
  args: {
1413
- task: tool13.schema.string(),
1414
- agents: tool13.schema.array(tool13.schema.string()).optional()
1237
+ task: tool12.schema.string(),
1238
+ agents: tool12.schema.array(tool12.schema.string()).optional()
1415
1239
  },
1416
1240
  async execute(args, context) {
1417
1241
  const agents = args.agents || ["architect", "reviewer", "coder"];
@@ -1467,31 +1291,31 @@ Please synthesize these results. Identify areas of agreement, resolve conflicts,
1467
1291
  }
1468
1292
 
1469
1293
  // src/tools/context-generator.ts
1470
- import { tool as tool14 } from "@opencode-ai/plugin";
1471
- import { writeFileSync as writeFileSync12, existsSync as existsSync11, readFileSync as readFileSync12, readdirSync as readdirSync2, statSync } from "fs";
1472
- import { join as join12 } from "path";
1473
- var contextGeneratorTool = tool14({
1294
+ import { tool as tool13 } from "@opencode-ai/plugin";
1295
+ import { writeFileSync as writeFileSync11, existsSync as existsSync10, readFileSync as readFileSync11, readdirSync as readdirSync2, statSync } from "fs";
1296
+ import { join as join10 } from "path";
1297
+ var contextGeneratorTool = tool13({
1474
1298
  description: "Auto-generate or update hierarchical context files (AGENTS.md, CLAUDE.md) throughout the project. These files provide critical grounding for AI agents.",
1475
1299
  args: {
1476
- targetDir: tool14.schema.string().optional(),
1477
- force: tool14.schema.boolean().optional()
1300
+ targetDir: tool13.schema.string().optional(),
1301
+ force: tool13.schema.boolean().optional()
1478
1302
  },
1479
1303
  async execute(args, context) {
1480
1304
  const root = context.directory;
1481
- const target = args.targetDir ? join12(root, args.targetDir) : root;
1482
- if (!existsSync11(target)) {
1305
+ const target = args.targetDir ? join10(root, args.targetDir) : root;
1306
+ if (!existsSync10(target)) {
1483
1307
  return `Error: Directory ${target} does not exist.`;
1484
1308
  }
1485
- const agentsMdPath = join12(target, "AGENTS.md");
1486
- if (existsSync11(agentsMdPath) && !args.force) {
1309
+ const agentsMdPath = join10(target, "AGENTS.md");
1310
+ if (existsSync10(agentsMdPath) && !args.force) {
1487
1311
  return `AGENTS.md already exists in ${target}. Use force: true to overwrite.`;
1488
1312
  }
1489
- const pkgPath = join12(root, "package.json");
1313
+ const pkgPath = join10(root, "package.json");
1490
1314
  let projectName = "Project";
1491
1315
  let techStack = "Unknown";
1492
- if (existsSync11(pkgPath)) {
1316
+ if (existsSync10(pkgPath)) {
1493
1317
  try {
1494
- const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
1318
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
1495
1319
  projectName = pkg.name || projectName;
1496
1320
  techStack = Object.keys(pkg.dependencies || {}).slice(0, 5).join(", ");
1497
1321
  } catch {}
@@ -1509,7 +1333,7 @@ var contextGeneratorTool = tool14({
1509
1333
 
1510
1334
  ## Directory Map
1511
1335
  ${readdirSync2(target).slice(0, 10).map((f) => {
1512
- const s = statSync(join12(target, f));
1336
+ const s = statSync(join10(target, f));
1513
1337
  return `- \`${f}\`${s.isDirectory() ? "/" : ""} : [Description]`;
1514
1338
  }).join(`
1515
1339
  `)}
@@ -1517,29 +1341,29 @@ ${readdirSync2(target).slice(0, 10).map((f) => {
1517
1341
  ---
1518
1342
  Generated by FlowDeck Context Generator.
1519
1343
  `;
1520
- writeFileSync12(agentsMdPath, content, "utf-8");
1344
+ writeFileSync11(agentsMdPath, content, "utf-8");
1521
1345
  return `Successfully generated AGENTS.md in ${target}.`;
1522
1346
  }
1523
1347
  });
1524
1348
 
1525
1349
  // src/tools/create-skill.ts
1526
- import { tool as tool15 } from "@opencode-ai/plugin";
1527
- import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync13, existsSync as existsSync12 } from "fs";
1528
- import { join as join13, dirname as dirname3 } from "path";
1350
+ import { tool as tool14 } from "@opencode-ai/plugin";
1351
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync12, existsSync as existsSync11 } from "fs";
1352
+ import { join as join11, dirname as dirname3 } from "path";
1529
1353
  import { fileURLToPath } from "url";
1530
- var SKILLS_DIR = join13(dirname3(fileURLToPath(import.meta.url)), "..", "skills");
1531
- var createSkillTool = tool15({
1354
+ var SKILLS_DIR = join11(dirname3(fileURLToPath(import.meta.url)), "..", "skills");
1355
+ var createSkillTool = tool14({
1532
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.",
1533
1357
  args: {
1534
- name: tool15.schema.string().describe("Unique kebab-case skill name, e.g. 'api-rate-limiting'"),
1535
- description: tool15.schema.string().describe("One-sentence description of what this skill does"),
1536
- content: tool15.schema.string().describe("Full skill body in Markdown. Must include: ## When to Activate, ## Steps, and ## Examples sections."),
1537
- 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']")
1538
1362
  },
1539
1363
  async execute(args) {
1540
- const skillDir = join13(SKILLS_DIR, args.name);
1541
- const skillFile = join13(skillDir, "SKILL.md");
1542
- if (existsSync12(skillFile)) {
1364
+ const skillDir = join11(SKILLS_DIR, args.name);
1365
+ const skillFile = join11(skillDir, "SKILL.md");
1366
+ if (existsSync11(skillFile)) {
1543
1367
  return `Skill '${args.name}' already exists at ${skillFile}.
1544
1368
  ` + `Use a different name or delete the existing skill directory first.`;
1545
1369
  }
@@ -1554,8 +1378,8 @@ origin: FlowDeck (self-learned)${tagLine}
1554
1378
  `;
1555
1379
  const fullContent = frontmatter + args.content.trimStart();
1556
1380
  try {
1557
- mkdirSync8(skillDir, { recursive: true });
1558
- writeFileSync13(skillFile, fullContent, "utf-8");
1381
+ mkdirSync7(skillDir, { recursive: true });
1382
+ writeFileSync12(skillFile, fullContent, "utf-8");
1559
1383
  return `✓ Skill '${args.name}' created at ${skillFile}
1560
1384
 
1561
1385
  ` + `The skill is now part of the FlowDeck library. Restart OpenCode to load it into the active session.`;
@@ -1566,9 +1390,9 @@ origin: FlowDeck (self-learned)${tagLine}
1566
1390
  });
1567
1391
 
1568
1392
  // src/tools/reflect.ts
1569
- import { tool as tool16 } from "@opencode-ai/plugin";
1570
- import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
1571
- import { join as join14 } from "path";
1393
+ import { tool as tool15 } from "@opencode-ai/plugin";
1394
+ import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
1395
+ import { join as join12 } from "path";
1572
1396
  var MAX_ARTIFACT_BYTES = 4000;
1573
1397
  function tail(text, maxBytes) {
1574
1398
  if (text.length <= maxBytes)
@@ -1576,10 +1400,10 @@ function tail(text, maxBytes) {
1576
1400
  return `... (truncated) ...
1577
1401
  ` + text.slice(-maxBytes);
1578
1402
  }
1579
- var reflectTool = tool16({
1403
+ var reflectTool = tool15({
1580
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.",
1581
1405
  args: {
1582
- 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")
1583
1407
  },
1584
1408
  async execute(args, context) {
1585
1409
  const root = context.directory;
@@ -1597,11 +1421,11 @@ var reflectTool = tool16({
1597
1421
  ];
1598
1422
  let found = 0;
1599
1423
  for (const [rel, label] of ARTIFACT_PATHS) {
1600
- const full = join14(root, rel);
1601
- if (!existsSync13(full))
1424
+ const full = join12(root, rel);
1425
+ if (!existsSync12(full))
1602
1426
  continue;
1603
1427
  try {
1604
- const raw = readFileSync13(full, "utf-8").trim();
1428
+ const raw = readFileSync12(full, "utf-8").trim();
1605
1429
  if (!raw)
1606
1430
  continue;
1607
1431
  const count = raw.split(`
@@ -1620,16 +1444,401 @@ var reflectTool = tool16({
1620
1444
  }
1621
1445
  });
1622
1446
 
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";
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
+
1623
1832
  // src/hooks/guard-rails.ts
1624
- import { existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
1625
- import { join as join15 } from "path";
1833
+ import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
1834
+ import { join as join14 } from "path";
1626
1835
  var PLANNING_DIR2 = ".planning";
1627
1836
  var CONFIG_FILE = "config.json";
1628
1837
  var STATE_FILE2 = "STATE.md";
1629
1838
  function resolveExecutionMode(configPath, trustScore, volatility) {
1630
1839
  if (existsSync14(configPath)) {
1631
1840
  try {
1632
- const config = JSON.parse(readFileSync14(configPath, "utf-8"));
1841
+ const config = JSON.parse(readFileSync13(configPath, "utf-8"));
1633
1842
  if (config.execution_mode === "review-only")
1634
1843
  return "review-only";
1635
1844
  if (config.execution_mode === "guarded")
@@ -1680,10 +1889,10 @@ var BUILD_DEPLOY_PATTERNS = [
1680
1889
  ];
1681
1890
  async function guardRailsHook(ctx, input, _output) {
1682
1891
  const dir = ctx.directory;
1683
- const planningDirPath = join15(dir, PLANNING_DIR2);
1892
+ const planningDirPath = join14(dir, PLANNING_DIR2);
1684
1893
  const codebaseDirectory = codebaseDir(dir);
1685
- const configPath = join15(planningDirPath, CONFIG_FILE);
1686
- const statePath2 = join15(planningDirPath, STATE_FILE2);
1894
+ const configPath = join14(planningDirPath, CONFIG_FILE);
1895
+ const statePath2 = join14(planningDirPath, STATE_FILE2);
1687
1896
  const workspaceRoot = findWorkspaceRoot(dir);
1688
1897
  if (workspaceRoot && dir !== workspaceRoot) {
1689
1898
  const config = getWorkspaceConfig(dir);
@@ -1732,7 +1941,7 @@ async function guardRailsHook(ctx, input, _output) {
1732
1941
  function effectiveSeverity(configPath, statePath2) {
1733
1942
  if (existsSync14(configPath)) {
1734
1943
  try {
1735
- const configContent = readFileSync14(configPath, "utf-8");
1944
+ const configContent = readFileSync13(configPath, "utf-8");
1736
1945
  const config = JSON.parse(configContent);
1737
1946
  if (config.guard_enforcement === "warn")
1738
1947
  return "warn";
@@ -1751,7 +1960,7 @@ function getPlanConfirmed(statePath2) {
1751
1960
  if (!existsSync14(statePath2))
1752
1961
  return false;
1753
1962
  try {
1754
- const content = readFileSync14(statePath2, "utf-8");
1963
+ const content = readFileSync13(statePath2, "utf-8");
1755
1964
  const match = content.match(/plan_confirmed:\s*(true|false)/i);
1756
1965
  return match ? match[1].toLowerCase() === "true" : false;
1757
1966
  } catch {
@@ -1759,21 +1968,21 @@ function getPlanConfirmed(statePath2) {
1759
1968
  }
1760
1969
  }
1761
1970
  function getWarningMessage(statePath2, planningDir3) {
1762
- if (!existsSync14(join15(planningDir3, STATE_FILE2))) {
1971
+ if (!existsSync14(join14(planningDir3, STATE_FILE2))) {
1763
1972
  return "No .planning/ found. Run /new-project first.";
1764
1973
  }
1765
1974
  return "Plan not confirmed. Run /plan and confirm to enable execution.";
1766
1975
  }
1767
1976
  function getBlockMessage(statePath2, planningDir3) {
1768
- if (!existsSync14(join15(planningDir3, STATE_FILE2))) {
1977
+ if (!existsSync14(join14(planningDir3, STATE_FILE2))) {
1769
1978
  return "No .planning/ found. Run /new-project first.";
1770
1979
  }
1771
1980
  return "Plan not confirmed. Run /plan and confirm to enable execution.";
1772
1981
  }
1773
1982
 
1774
1983
  // src/hooks/tool-guard.ts
1775
- import { existsSync as existsSync15, readFileSync as readFileSync15 } from "fs";
1776
- import { join as join16 } from "path";
1984
+ import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
1985
+ import { join as join15 } from "path";
1777
1986
  var BLOCKED_PATTERNS = {
1778
1987
  read: [".env", ".pem", ".key", ".secret"],
1779
1988
  write: ["node_modules"],
@@ -1819,11 +2028,11 @@ function isBlocked(tool17, args) {
1819
2028
  return null;
1820
2029
  }
1821
2030
  function checkArchConstraint(directory, filePath) {
1822
- const constraintsPath = join16(codebaseDir(directory), "CONSTRAINTS.md");
2031
+ const constraintsPath = join15(codebaseDir(directory), "CONSTRAINTS.md");
1823
2032
  if (!existsSync15(constraintsPath))
1824
2033
  return null;
1825
2034
  try {
1826
- const content = readFileSync15(constraintsPath, "utf-8");
2035
+ const content = readFileSync14(constraintsPath, "utf-8");
1827
2036
  const match = content.match(/## Forbidden Paths\n([\s\S]*?)(?:\n##|$)/);
1828
2037
  if (!match)
1829
2038
  return null;
@@ -1868,7 +2077,7 @@ async function toolGuardHook(ctx, input, output) {
1868
2077
  }
1869
2078
 
1870
2079
  // src/hooks/session-start.ts
1871
- import { existsSync as existsSync16, readFileSync as readFileSync16 } from "fs";
2080
+ import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
1872
2081
  async function sessionStartHook(ctx) {
1873
2082
  const planningDir3 = ctx.directory + "/.planning";
1874
2083
  const codebaseDirectory = codebaseDir(ctx.directory);
@@ -1890,7 +2099,7 @@ async function sessionStartHook(ctx) {
1890
2099
  }
1891
2100
  try {
1892
2101
  const stateFilePath = statePath(ctx.directory);
1893
- const content = readFileSync16(stateFilePath, "utf-8");
2102
+ const content = readFileSync15(stateFilePath, "utf-8");
1894
2103
  const state = parseState(content);
1895
2104
  const currentPhase = state["current_phase"] || {};
1896
2105
  const result = {
@@ -1979,8 +2188,8 @@ function notifyPermissionNeeded(tool17) {
1979
2188
  }
1980
2189
 
1981
2190
  // src/hooks/patch-trust.ts
1982
- import { existsSync as existsSync17, readFileSync as readFileSync17 } from "fs";
1983
- import { join as join17 } from "path";
2191
+ import { existsSync as existsSync17, readFileSync as readFileSync16 } from "fs";
2192
+ import { join as join16 } from "path";
1984
2193
  var HIGH_RISK_KEYWORDS = [
1985
2194
  "password",
1986
2195
  "secret",
@@ -2002,11 +2211,11 @@ var HIGH_RISK_KEYWORDS = [
2002
2211
  "privilege"
2003
2212
  ];
2004
2213
  function loadVolatility(directory) {
2005
- const p = join17(codebaseDir(directory), "VOLATILITY.json");
2214
+ const p = join16(codebaseDir(directory), "VOLATILITY.json");
2006
2215
  if (!existsSync17(p))
2007
2216
  return {};
2008
2217
  try {
2009
- const data = JSON.parse(readFileSync17(p, "utf-8"));
2218
+ const data = JSON.parse(readFileSync16(p, "utf-8"));
2010
2219
  const map = {};
2011
2220
  for (const entry of data.entries ?? [])
2012
2221
  map[entry.path] = entry.stability;
@@ -2016,11 +2225,11 @@ function loadVolatility(directory) {
2016
2225
  }
2017
2226
  }
2018
2227
  function loadFailedPaths(directory) {
2019
- const p = join17(codebaseDir(directory), "FAILURES.json");
2228
+ const p = join16(codebaseDir(directory), "FAILURES.json");
2020
2229
  if (!existsSync17(p))
2021
2230
  return [];
2022
2231
  try {
2023
- const data = JSON.parse(readFileSync17(p, "utf-8"));
2232
+ const data = JSON.parse(readFileSync16(p, "utf-8"));
2024
2233
  return (data.entries ?? []).flatMap((e) => e.affected_paths ?? []);
2025
2234
  } catch {
2026
2235
  return [];
@@ -2077,8 +2286,8 @@ async function patchTrustHook(ctx, input, output) {
2077
2286
  }
2078
2287
 
2079
2288
  // src/hooks/decision-trace-hook.ts
2080
- import { existsSync as existsSync18, mkdirSync as mkdirSync9, appendFileSync as appendFileSync3 } from "fs";
2081
- import { join as join18 } from "path";
2289
+ import { existsSync as existsSync18, mkdirSync as mkdirSync9, appendFileSync as appendFileSync2 } from "fs";
2290
+ import { join as join17 } from "path";
2082
2291
  async function decisionTraceHook(ctx, input, output) {
2083
2292
  if (input.tool !== "write" && input.tool !== "edit")
2084
2293
  return;
@@ -2100,11 +2309,34 @@ async function decisionTraceHook(ctx, input, output) {
2100
2309
  risk_level: "unknown",
2101
2310
  auto_recorded: true
2102
2311
  };
2103
- appendFileSync3(join18(base, "DECISIONS.jsonl"), JSON.stringify(entry) + `
2312
+ appendFileSync2(join17(base, "DECISIONS.jsonl"), JSON.stringify(entry) + `
2104
2313
  `, "utf-8");
2105
2314
  } catch {}
2106
2315
  }
2107
2316
 
2317
+ // src/services/telemetry.ts
2318
+ import { existsSync as existsSync19, readFileSync as readFileSync17, appendFileSync as appendFileSync3, mkdirSync as mkdirSync10 } from "fs";
2319
+ import { join as join18 } from "path";
2320
+ import { randomUUID } from "crypto";
2321
+ function telemetryPath(dir) {
2322
+ return join18(codebaseDir(dir), "TELEMETRY.jsonl");
2323
+ }
2324
+ function appendEvent(dir, partial) {
2325
+ if (process.env.TELEMETRY_ENABLED !== "true")
2326
+ return null;
2327
+ const cd = codebaseDir(dir);
2328
+ if (!existsSync19(cd))
2329
+ mkdirSync10(cd, { recursive: true });
2330
+ const event = {
2331
+ id: randomUUID(),
2332
+ ts: new Date().toISOString(),
2333
+ ...partial
2334
+ };
2335
+ appendFileSync3(telemetryPath(dir), JSON.stringify(event) + `
2336
+ `, "utf-8");
2337
+ return event;
2338
+ }
2339
+
2108
2340
  // src/hooks/telemetry-hook.ts
2109
2341
  async function telemetryHook(context, toolInput, output) {
2110
2342
  const dir = context.directory ?? process.cwd();
@@ -2131,7 +2363,7 @@ async function telemetryAfterHook(context, toolInput, _output) {
2131
2363
  }
2132
2364
 
2133
2365
  // src/services/approval-manager.ts
2134
- import { existsSync as existsSync19, readFileSync as readFileSync18, writeFileSync as writeFileSync14, mkdirSync as mkdirSync10 } from "fs";
2366
+ import { existsSync as existsSync20, readFileSync as readFileSync18, writeFileSync as writeFileSync13, mkdirSync as mkdirSync11 } from "fs";
2135
2367
  import { join as join19 } from "path";
2136
2368
  var APPROVAL_TTL_MS = 30 * 60 * 1000;
2137
2369
  var SENSITIVE_PATTERNS = [
@@ -2173,7 +2405,7 @@ function approvalsPath(dir) {
2173
2405
  }
2174
2406
  function loadStore(dir) {
2175
2407
  const p = approvalsPath(dir);
2176
- if (!existsSync19(p))
2408
+ if (!existsSync20(p))
2177
2409
  return { requests: [] };
2178
2410
  try {
2179
2411
  return JSON.parse(readFileSync18(p, "utf-8"));
@@ -2268,7 +2500,7 @@ function createContextWindowMonitorHook() {
2268
2500
  }
2269
2501
 
2270
2502
  // src/hooks/shell-env-hook.ts
2271
- import { existsSync as existsSync20, readFileSync as readFileSync19 } from "fs";
2503
+ import { existsSync as existsSync21, readFileSync as readFileSync19 } from "fs";
2272
2504
  import { join as join20 } from "path";
2273
2505
  import { createRequire } from "module";
2274
2506
  var _version;
@@ -2305,7 +2537,7 @@ var MARKER_TO_LANG = {
2305
2537
  };
2306
2538
  function detectPackageManager(root) {
2307
2539
  for (const [lockfile, pm] of Object.entries(LOCKFILE_TO_PM)) {
2308
- if (existsSync20(join20(root, lockfile)))
2540
+ if (existsSync21(join20(root, lockfile)))
2309
2541
  return pm;
2310
2542
  }
2311
2543
  return;
@@ -2314,7 +2546,7 @@ function detectLanguages(root) {
2314
2546
  const langs = [];
2315
2547
  const seen = new Set;
2316
2548
  for (const [marker, lang] of Object.entries(MARKER_TO_LANG)) {
2317
- if (!seen.has(lang) && existsSync20(join20(root, marker))) {
2549
+ if (!seen.has(lang) && existsSync21(join20(root, marker))) {
2318
2550
  langs.push(lang);
2319
2551
  seen.add(lang);
2320
2552
  }
@@ -2323,7 +2555,7 @@ function detectLanguages(root) {
2323
2555
  }
2324
2556
  function readCurrentPhase(root) {
2325
2557
  const statePath2 = join20(root, ".planning", "STATE.md");
2326
- if (!existsSync20(statePath2))
2558
+ if (!existsSync21(statePath2))
2327
2559
  return;
2328
2560
  try {
2329
2561
  const content = readFileSync19(statePath2, "utf-8");
@@ -2356,7 +2588,8 @@ function createShellEnvHook(ctx) {
2356
2588
  // src/hooks/todo-hook.ts
2357
2589
  function createTodoHook(client) {
2358
2590
  return async (event) => {
2359
- const todos = event.todos ?? [];
2591
+ const rawTodos = event.todos;
2592
+ const todos = Array.isArray(rawTodos) ? rawTodos : [];
2360
2593
  const completed = todos.filter((t) => t.done || t.status === "completed").length;
2361
2594
  const total = todos.length;
2362
2595
  if (total === 0)
@@ -2425,7 +2658,7 @@ function createSessionIdleHook(client, tracker) {
2425
2658
  }
2426
2659
 
2427
2660
  // src/hooks/compaction-hook.ts
2428
- import { existsSync as existsSync21, readFileSync as readFileSync20 } from "fs";
2661
+ import { existsSync as existsSync22, readFileSync as readFileSync20 } from "fs";
2429
2662
  import { join as join21 } from "path";
2430
2663
  var STRUCTURED_SUMMARY_PROMPT = `
2431
2664
  When summarizing this session, you MUST include the following sections:
@@ -2466,7 +2699,7 @@ For each: agent name, status, description, session_id.
2466
2699
  `;
2467
2700
  function readPlanningState2(directory) {
2468
2701
  const statePath2 = join21(directory, ".planning", "STATE.md");
2469
- if (!existsSync21(statePath2))
2702
+ if (!existsSync22(statePath2))
2470
2703
  return null;
2471
2704
  try {
2472
2705
  const content = readFileSync20(statePath2, "utf-8");
@@ -2524,7 +2757,6 @@ var BLOCKED_TOOLS = new Set([
2524
2757
  ]);
2525
2758
  var ALWAYS_ALLOWED = new Set([
2526
2759
  "delegate",
2527
- "run-parallel",
2528
2760
  "run-pipeline",
2529
2761
  "council",
2530
2762
  "planning-state",
@@ -5563,12 +5795,12 @@ function getAgentConfigs(agentModels) {
5563
5795
  }
5564
5796
 
5565
5797
  // src/config/loader.ts
5566
- import { existsSync as existsSync22, readFileSync as readFileSync21 } from "fs";
5798
+ import { existsSync as existsSync23, readFileSync as readFileSync21 } from "fs";
5567
5799
  import { join as join22 } from "path";
5568
- import { homedir } from "os";
5800
+ import { homedir as homedir2 } from "os";
5569
5801
  var CONFIG_FILENAME = "flowdeck.json";
5570
5802
  function getGlobalConfigDir() {
5571
- return process.env.OPENCODE_CONFIG_DIR || (process.env.XDG_CONFIG_HOME ? join22(process.env.XDG_CONFIG_HOME, "opencode") : join22(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"));
5572
5804
  }
5573
5805
  function loadFlowDeckConfig(directory) {
5574
5806
  const candidates = [];
@@ -5577,7 +5809,7 @@ function loadFlowDeckConfig(directory) {
5577
5809
  }
5578
5810
  candidates.push(join22(getGlobalConfigDir(), CONFIG_FILENAME));
5579
5811
  for (const configPath of candidates) {
5580
- if (existsSync22(configPath)) {
5812
+ if (existsSync23(configPath)) {
5581
5813
  try {
5582
5814
  const content = readFileSync21(configPath, "utf-8");
5583
5815
  return JSON.parse(content);
@@ -5589,10 +5821,29 @@ function loadFlowDeckConfig(directory) {
5589
5821
  return {};
5590
5822
  }
5591
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
+ }
5592
5843
  function loadCommands() {
5593
5844
  const __dir = dirname4(fileURLToPath2(import.meta.url));
5594
5845
  const commandsDir = join23(__dir, "..", "src", "commands");
5595
- if (!existsSync23(commandsDir))
5846
+ if (!existsSync24(commandsDir))
5596
5847
  return {};
5597
5848
  const commands = {};
5598
5849
  try {
@@ -5617,7 +5868,6 @@ function loadCommands() {
5617
5868
  }
5618
5869
  var plugin = async (input, _options) => {
5619
5870
  const { directory, client, worktree } = input;
5620
- const runParallelTool = createRunParallelTool(client);
5621
5871
  const runPipelineTool = createRunPipelineTool(client);
5622
5872
  const delegateTool = createDelegateTool(client);
5623
5873
  const councilTool = createCouncilTool(client);
@@ -5679,7 +5929,7 @@ var plugin = async (input, _options) => {
5679
5929
  }
5680
5930
  }
5681
5931
  const skillsDir = join23(dirname4(fileURLToPath2(import.meta.url)), "..", "src", "skills");
5682
- if (existsSync23(skillsDir)) {
5932
+ if (existsSync24(skillsDir)) {
5683
5933
  const cfgAny = cfg;
5684
5934
  if (!cfgAny.skills || typeof cfgAny.skills !== "object") {
5685
5935
  cfgAny.skills = { paths: [] };
@@ -5691,12 +5941,23 @@ var plugin = async (input, _options) => {
5691
5941
  cfgSkills.paths.push(skillsDir);
5692
5942
  }
5693
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
+ }
5694
5956
  },
5695
5957
  tool: {
5696
5958
  "planning-state": planningStateTool,
5697
5959
  "codebase-state": codebaseStateTool,
5698
5960
  "workspace-state": workspaceStateTool,
5699
- "run-parallel": runParallelTool,
5700
5961
  "run-pipeline": runPipelineTool,
5701
5962
  delegate: delegateTool,
5702
5963
  "repo-memory": repoMemoryTool,
@@ -5708,7 +5969,8 @@ var plugin = async (input, _options) => {
5708
5969
  council: councilTool,
5709
5970
  "context-generator": contextGeneratorTool,
5710
5971
  "create-skill": createSkillTool,
5711
- reflect: reflectTool
5972
+ reflect: reflectTool,
5973
+ "memory-search": memorySearchTool
5712
5974
  },
5713
5975
  "shell.env": shellEnvHook,
5714
5976
  "todo.updated": todoHook,
@@ -5720,11 +5982,34 @@ var plugin = async (input, _options) => {
5720
5982
  },
5721
5983
  event: async ({ event }) => {
5722
5984
  const type = event?.type ?? "";
5723
- await contextMonitor.event({ event });
5724
- orchestratorGuard.onEvent(event);
5725
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
+ }
5726
5990
  await sessionStartHook({ directory });
5727
- } 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") {
5728
6013
  await sessionIdleHook();
5729
6014
  await autoLearnHook();
5730
6015
  }
@@ -5740,6 +6025,10 @@ var plugin = async (input, _options) => {
5740
6025
  },
5741
6026
  "tool.execute.after": async (toolInput, toolOutput) => {
5742
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
+ }
5743
6032
  await contextMonitor["tool.execute.after"](toolInput, toolOutput);
5744
6033
  }
5745
6034
  };