@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.
- package/README.md +24 -41
- package/dist/hooks/memory-hook.d.ts +21 -0
- package/dist/hooks/memory-hook.d.ts.map +1 -0
- package/dist/hooks/orchestrator-guard-hook.d.ts.map +1 -1
- package/dist/hooks/todo-hook.d.ts +1 -7
- package/dist/hooks/todo-hook.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +709 -420
- package/dist/services/memory-store.d.ts +40 -0
- package/dist/services/memory-store.d.ts.map +1 -0
- package/dist/tools/memory-search.d.ts +3 -0
- package/dist/tools/memory-search.d.ts.map +1 -0
- package/docs/commands/fd-doctor.md +21 -0
- package/docs/commands/fd-quick.md +33 -0
- package/docs/commands/fd-reflect.md +23 -0
- package/docs/commands/fd-status.md +31 -0
- package/docs/commands/fd-translate-intent.md +17 -0
- package/docs/commands.md +209 -271
- package/docs/configuration.md +1 -2
- package/docs/index.md +22 -28
- package/docs/memory.md +69 -0
- package/docs/quick-start.md +1 -1
- package/package.json +1 -1
- package/src/commands/fd-deploy-check.md +131 -11
- package/src/commands/fd-new-project.md +14 -1
- package/src/commands/fd-quick.md +60 -0
- package/src/commands/fd-reflect.md +41 -2
- package/src/commands/fd-status.md +84 -0
- package/src/rules/README.md +8 -7
- package/src/skills/agent-harness-construction/SKILL.md +227 -0
- package/src/skills/api-design/SKILL.md +5 -0
- package/src/skills/backend-patterns/SKILL.md +105 -0
- package/src/skills/clean-architecture/SKILL.md +85 -0
- package/src/skills/cqrs/SKILL.md +230 -0
- package/src/skills/ddd-architecture/SKILL.md +104 -0
- package/src/skills/django-patterns/SKILL.md +304 -0
- package/src/skills/django-tdd/SKILL.md +297 -0
- package/src/skills/event-driven-architecture/SKILL.md +152 -0
- package/src/skills/frontend-pattern/SKILL.md +159 -0
- package/src/skills/hexagonal-architecture/SKILL.md +80 -0
- package/src/skills/layered-architecture/SKILL.md +64 -0
- package/src/skills/postgres-patterns/SKILL.md +74 -0
- package/src/skills/python-patterns/SKILL.md +5 -0
- package/src/skills/saga-architecture/SKILL.md +113 -0
- package/dist/tools/run-parallel.d.ts +0 -4
- package/dist/tools/run-parallel.d.ts.map +0 -1
- package/docs/command-migration.md +0 -175
- package/docs/commands/fd-analyze-change.md +0 -107
- package/docs/commands/fd-dashboard.md +0 -11
- package/docs/commands/fd-evaluate-risk.md +0 -134
- package/docs/commands/fd-guarded-edit.md +0 -105
- package/docs/commands/fd-progress.md +0 -11
- package/docs/commands/fd-review-code.md +0 -29
- package/docs/commands/fd-roadmap.md +0 -10
- package/docs/commands/fd-settings.md +0 -10
- package/docs/parallel-execution.md +0 -255
- package/src/commands/fd-analyze-change.md +0 -57
- package/src/commands/fd-approve.md +0 -64
- package/src/commands/fd-blast-radius.md +0 -49
- package/src/commands/fd-dashboard.md +0 -57
- package/src/commands/fd-evaluate-risk.md +0 -62
- package/src/commands/fd-guarded-edit.md +0 -69
- package/src/commands/fd-impact-radar.md +0 -51
- package/src/commands/fd-learn.md +0 -36
- package/src/commands/fd-progress.md +0 -50
- package/src/commands/fd-regression-predict.md +0 -57
- package/src/commands/fd-review-code.md +0 -96
- package/src/commands/fd-review-route.md +0 -54
- package/src/commands/fd-roadmap.md +0 -46
- package/src/commands/fd-settings.md +0 -57
- package/src/commands/fd-test-gap.md +0 -54
- package/src/commands/fd-volatility-map.md +0 -64
- package/src/commands/fd-workspace-status.md +0 -34
- 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
|
|
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-
|
|
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
|
|
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:
|
|
697
|
-
agent:
|
|
698
|
-
prompt:
|
|
520
|
+
steps: tool4.schema.array(tool4.schema.object({
|
|
521
|
+
agent: tool4.schema.string(),
|
|
522
|
+
prompt: tool4.schema.string()
|
|
699
523
|
})),
|
|
700
|
-
initial_context:
|
|
701
|
-
abort_on_failure:
|
|
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 =
|
|
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
|
|
792
|
-
function
|
|
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
|
|
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:
|
|
801
|
-
prompt:
|
|
802
|
-
context:
|
|
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 =
|
|
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
|
|
872
|
-
import { readFileSync as
|
|
873
|
-
import { join as
|
|
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
|
|
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 (!
|
|
707
|
+
if (!existsSync5(p))
|
|
884
708
|
return emptyMemory();
|
|
885
709
|
try {
|
|
886
|
-
return JSON.parse(
|
|
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 (!
|
|
894
|
-
|
|
717
|
+
if (!existsSync5(base))
|
|
718
|
+
mkdirSync2(base, { recursive: true });
|
|
895
719
|
memory.last_updated = new Date().toISOString();
|
|
896
|
-
|
|
720
|
+
writeFileSync5(memoryPath(directory), JSON.stringify(memory, null, 2), "utf-8");
|
|
897
721
|
}
|
|
898
|
-
var repoMemoryTool =
|
|
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:
|
|
902
|
-
node_id:
|
|
903
|
-
node:
|
|
904
|
-
type:
|
|
905
|
-
path:
|
|
906
|
-
owner:
|
|
907
|
-
tags:
|
|
908
|
-
dependencies:
|
|
909
|
-
dependents:
|
|
910
|
-
bug_history:
|
|
911
|
-
conventions:
|
|
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:
|
|
914
|
-
type:
|
|
915
|
-
owner:
|
|
916
|
-
tag:
|
|
917
|
-
path_prefix:
|
|
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
|
|
973
|
-
import { readFileSync as
|
|
974
|
-
import { join as
|
|
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
|
|
801
|
+
return join6(codebaseDir(directory), FAILURES_FILE);
|
|
978
802
|
}
|
|
979
803
|
function readStore(directory) {
|
|
980
804
|
const p = failuresPath(directory);
|
|
981
|
-
if (!
|
|
805
|
+
if (!existsSync6(p))
|
|
982
806
|
return { version: "1.0", last_updated: new Date().toISOString(), entries: [] };
|
|
983
807
|
try {
|
|
984
|
-
return JSON.parse(
|
|
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 (!
|
|
992
|
-
|
|
815
|
+
if (!existsSync6(base))
|
|
816
|
+
mkdirSync3(base, { recursive: true });
|
|
993
817
|
store.last_updated = new Date().toISOString();
|
|
994
|
-
|
|
818
|
+
writeFileSync6(failuresPath(directory), JSON.stringify(store, null, 2), "utf-8");
|
|
995
819
|
}
|
|
996
|
-
var failureReplayTool =
|
|
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:
|
|
1000
|
-
entry:
|
|
1001
|
-
id:
|
|
1002
|
-
type:
|
|
1003
|
-
description:
|
|
1004
|
-
affected_paths:
|
|
1005
|
-
root_cause:
|
|
1006
|
-
fix_applied:
|
|
1007
|
-
tags:
|
|
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:
|
|
1010
|
-
type:
|
|
1011
|
-
path_prefix:
|
|
1012
|
-
tag:
|
|
1013
|
-
limit:
|
|
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:
|
|
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
|
|
1078
|
-
import { readFileSync as
|
|
1079
|
-
import { join as
|
|
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
|
|
906
|
+
return join7(codebaseDir(directory), DECISIONS_FILE);
|
|
1083
907
|
}
|
|
1084
908
|
function readDecisions(directory) {
|
|
1085
909
|
const p = decisionsPath(directory);
|
|
1086
|
-
if (!
|
|
910
|
+
if (!existsSync7(p))
|
|
1087
911
|
return [];
|
|
1088
|
-
return
|
|
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 =
|
|
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:
|
|
1101
|
-
entry:
|
|
1102
|
-
id:
|
|
1103
|
-
file_path:
|
|
1104
|
-
change_type:
|
|
1105
|
-
rationale:
|
|
1106
|
-
evidence:
|
|
1107
|
-
assumptions:
|
|
1108
|
-
alternatives_considered:
|
|
1109
|
-
risk_level:
|
|
1110
|
-
agent:
|
|
1111
|
-
session_id:
|
|
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:
|
|
1114
|
-
file_path:
|
|
1115
|
-
change_type:
|
|
1116
|
-
risk_level:
|
|
1117
|
-
limit:
|
|
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:
|
|
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 (!
|
|
1129
|
-
|
|
952
|
+
if (!existsSync7(base))
|
|
953
|
+
mkdirSync4(base, { recursive: true });
|
|
1130
954
|
const entry = { ...args.entry, timestamp: new Date().toISOString() };
|
|
1131
|
-
|
|
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
|
|
1163
|
-
import { readFileSync as
|
|
1164
|
-
import { join as
|
|
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
|
|
991
|
+
return join8(codebaseDir(directory), VOLATILITY_FILE);
|
|
1168
992
|
}
|
|
1169
993
|
function readStore2(directory) {
|
|
1170
994
|
const p = volatilityPath(directory);
|
|
1171
|
-
if (!
|
|
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(
|
|
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 (!
|
|
1182
|
-
|
|
1005
|
+
if (!existsSync8(base))
|
|
1006
|
+
mkdirSync5(base, { recursive: true });
|
|
1183
1007
|
store.last_updated = new Date().toISOString();
|
|
1184
|
-
|
|
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 =
|
|
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:
|
|
1200
|
-
entries:
|
|
1201
|
-
path:
|
|
1202
|
-
churn_score:
|
|
1203
|
-
hotfix_count:
|
|
1204
|
-
todo_count:
|
|
1205
|
-
last_breakage:
|
|
1206
|
-
notes:
|
|
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:
|
|
1209
|
-
path:
|
|
1210
|
-
churn_score:
|
|
1211
|
-
hotfix_count:
|
|
1212
|
-
todo_count:
|
|
1213
|
-
last_breakage:
|
|
1214
|
-
notes:
|
|
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:
|
|
1217
|
-
path_prefix:
|
|
1218
|
-
limit:
|
|
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
|
|
1271
|
-
import { readFileSync as
|
|
1272
|
-
import { join as
|
|
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
|
|
1099
|
+
return join9(codebaseDir(directory), POLICIES_FILE);
|
|
1276
1100
|
}
|
|
1277
1101
|
function readStore3(directory) {
|
|
1278
1102
|
const p = policiesPath(directory);
|
|
1279
|
-
if (!
|
|
1103
|
+
if (!existsSync9(p))
|
|
1280
1104
|
return { version: "1.0", last_updated: new Date().toISOString(), policies: [] };
|
|
1281
1105
|
try {
|
|
1282
|
-
return JSON.parse(
|
|
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 (!
|
|
1290
|
-
|
|
1113
|
+
if (!existsSync9(base))
|
|
1114
|
+
mkdirSync6(base, { recursive: true });
|
|
1291
1115
|
store.last_updated = new Date().toISOString();
|
|
1292
|
-
|
|
1116
|
+
writeFileSync9(policiesPath(directory), JSON.stringify(store, null, 2), "utf-8");
|
|
1293
1117
|
}
|
|
1294
|
-
var policyEngineTool =
|
|
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:
|
|
1298
|
-
policy:
|
|
1299
|
-
id:
|
|
1300
|
-
name:
|
|
1301
|
-
trigger:
|
|
1302
|
-
rule:
|
|
1303
|
-
source:
|
|
1304
|
-
failure_count:
|
|
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:
|
|
1307
|
-
active:
|
|
1308
|
-
query:
|
|
1309
|
-
source:
|
|
1310
|
-
active_only:
|
|
1311
|
-
trigger_contains:
|
|
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
|
|
1374
|
-
import { readFileSync as
|
|
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 =
|
|
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:
|
|
1380
|
-
targetContent:
|
|
1381
|
-
expectedHash:
|
|
1382
|
-
replacementContent:
|
|
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 =
|
|
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
|
-
|
|
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
|
|
1232
|
+
import { tool as tool12 } from "@opencode-ai/plugin";
|
|
1409
1233
|
function createCouncilTool(client) {
|
|
1410
|
-
return
|
|
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:
|
|
1414
|
-
agents:
|
|
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
|
|
1471
|
-
import { writeFileSync as
|
|
1472
|
-
import { join as
|
|
1473
|
-
var contextGeneratorTool =
|
|
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:
|
|
1477
|
-
force:
|
|
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 ?
|
|
1482
|
-
if (!
|
|
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 =
|
|
1486
|
-
if (
|
|
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 =
|
|
1313
|
+
const pkgPath = join10(root, "package.json");
|
|
1490
1314
|
let projectName = "Project";
|
|
1491
1315
|
let techStack = "Unknown";
|
|
1492
|
-
if (
|
|
1316
|
+
if (existsSync10(pkgPath)) {
|
|
1493
1317
|
try {
|
|
1494
|
-
const pkg = JSON.parse(
|
|
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(
|
|
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
|
-
|
|
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
|
|
1527
|
-
import { mkdirSync as
|
|
1528
|
-
import { join as
|
|
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 =
|
|
1531
|
-
var createSkillTool =
|
|
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:
|
|
1535
|
-
description:
|
|
1536
|
-
content:
|
|
1537
|
-
tags:
|
|
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 =
|
|
1541
|
-
const skillFile =
|
|
1542
|
-
if (
|
|
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
|
-
|
|
1558
|
-
|
|
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
|
|
1570
|
-
import { existsSync as
|
|
1571
|
-
import { join as
|
|
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 =
|
|
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:
|
|
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 =
|
|
1601
|
-
if (!
|
|
1424
|
+
const full = join12(root, rel);
|
|
1425
|
+
if (!existsSync12(full))
|
|
1602
1426
|
continue;
|
|
1603
1427
|
try {
|
|
1604
|
-
const raw =
|
|
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
|
|
1625
|
-
import { join as
|
|
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(
|
|
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 =
|
|
1892
|
+
const planningDirPath = join14(dir, PLANNING_DIR2);
|
|
1684
1893
|
const codebaseDirectory = codebaseDir(dir);
|
|
1685
|
-
const configPath =
|
|
1686
|
-
const statePath2 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
|
1776
|
-
import { join as
|
|
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 =
|
|
2031
|
+
const constraintsPath = join15(codebaseDir(directory), "CONSTRAINTS.md");
|
|
1823
2032
|
if (!existsSync15(constraintsPath))
|
|
1824
2033
|
return null;
|
|
1825
2034
|
try {
|
|
1826
|
-
const content =
|
|
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
|
|
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 =
|
|
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
|
|
1983
|
-
import { join as
|
|
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 =
|
|
2214
|
+
const p = join16(codebaseDir(directory), "VOLATILITY.json");
|
|
2006
2215
|
if (!existsSync17(p))
|
|
2007
2216
|
return {};
|
|
2008
2217
|
try {
|
|
2009
|
-
const data = JSON.parse(
|
|
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 =
|
|
2228
|
+
const p = join16(codebaseDir(directory), "FAILURES.json");
|
|
2020
2229
|
if (!existsSync17(p))
|
|
2021
2230
|
return [];
|
|
2022
2231
|
try {
|
|
2023
|
-
const data = JSON.parse(
|
|
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
|
|
2081
|
-
import { join as
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
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
|
|
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 (
|
|
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) &&
|
|
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 (!
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
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(
|
|
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 (
|
|
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 (!
|
|
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 (
|
|
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 === "
|
|
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
|
};
|