@dv.nghiem/flowdeck 0.2.3 → 0.2.4
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/dist/hooks/orchestrator-guard-hook.d.ts +2 -1
- package/dist/hooks/orchestrator-guard-hook.d.ts.map +1 -1
- package/dist/index.js +197 -147
- package/dist/services/telemetry.d.ts +1 -1
- package/dist/services/telemetry.d.ts.map +1 -1
- package/dist/tools/run-parallel.d.ts.map +1 -1
- package/docs/configuration.md +2 -0
- package/docs/parallel-execution.md +28 -0
- package/docs/workflows.md +72 -320
- package/package.json +1 -2
- package/src/commands/fd-deploy-check.md +67 -32
- package/src/commands/fd-discuss.md +44 -6
- package/src/commands/fd-fix-bug.md +47 -20
- package/src/commands/fd-map-codebase.md +66 -18
- package/src/commands/fd-multi-repo.md +130 -6
- package/src/commands/fd-new-feature.md +164 -21
- package/src/commands/fd-plan.md +66 -44
- package/src/commands/fd-review-code.md +69 -35
- package/src/commands/fd-write-docs.md +55 -23
- package/src/workflows/debug-flow.md +0 -119
- package/src/workflows/deploy-check-flow.md +0 -98
- package/src/workflows/discuss-flow.md +0 -97
- package/src/workflows/execute-flow.md +0 -233
- package/src/workflows/execute-phase.md +0 -145
- package/src/workflows/fix-bug-flow.md +0 -210
- package/src/workflows/map-codebase-flow.md +0 -92
- package/src/workflows/multi-repo-flow.md +0 -226
- package/src/workflows/parallel-execution-flow.md +0 -236
- package/src/workflows/plan-flow.md +0 -126
- package/src/workflows/plan-phase.md +0 -101
- package/src/workflows/refactor-flow.md +0 -122
- package/src/workflows/review-code-flow.md +0 -105
- package/src/workflows/spec-driven-flow.md +0 -43
- package/src/workflows/write-docs-flow.md +0 -95
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { readdirSync as readdirSync3, readFileSync as readFileSync22, existsSync as existsSync23 } from "fs";
|
|
3
|
-
import { join as
|
|
3
|
+
import { join as join23, basename } from "path";
|
|
4
4
|
import { dirname as dirname4 } from "path";
|
|
5
5
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6
6
|
|
|
@@ -509,6 +509,33 @@ var workspaceStateTool = tool3({
|
|
|
509
509
|
|
|
510
510
|
// src/tools/run-parallel.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";
|
|
512
539
|
function extractText(parts) {
|
|
513
540
|
return parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join(`
|
|
514
541
|
`);
|
|
@@ -534,6 +561,7 @@ function createRunParallelTool(client) {
|
|
|
534
561
|
}).catch(() => {});
|
|
535
562
|
}
|
|
536
563
|
});
|
|
564
|
+
const dir = context.directory ?? process.cwd();
|
|
537
565
|
const promises = args.tasks.map(async (task) => {
|
|
538
566
|
const taskStart = Date.now();
|
|
539
567
|
const createRes = await client.session.create({
|
|
@@ -550,6 +578,14 @@ function createRunParallelTool(client) {
|
|
|
550
578
|
}
|
|
551
579
|
const childId = createRes.data.id;
|
|
552
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
|
+
});
|
|
553
589
|
const fullPrompt = task.context ? `${task.context}
|
|
554
590
|
|
|
555
591
|
---
|
|
@@ -565,6 +601,15 @@ ${task.prompt}` : task.prompt;
|
|
|
565
601
|
query: { directory: context.directory }
|
|
566
602
|
});
|
|
567
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
|
+
});
|
|
568
613
|
return {
|
|
569
614
|
agent: task.agent,
|
|
570
615
|
session_id: childId,
|
|
@@ -575,6 +620,15 @@ ${task.prompt}` : task.prompt;
|
|
|
575
620
|
}
|
|
576
621
|
const info = promptRes.data?.info;
|
|
577
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
|
+
});
|
|
578
632
|
return {
|
|
579
633
|
agent: task.agent,
|
|
580
634
|
session_id: childId,
|
|
@@ -584,6 +638,15 @@ ${task.prompt}` : task.prompt;
|
|
|
584
638
|
};
|
|
585
639
|
}
|
|
586
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
|
+
});
|
|
587
650
|
return {
|
|
588
651
|
agent: task.agent,
|
|
589
652
|
session_id: childId,
|
|
@@ -603,6 +666,14 @@ ${task.prompt}` : task.prompt;
|
|
|
603
666
|
duration_ms: Date.now() - startTime
|
|
604
667
|
};
|
|
605
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));
|
|
606
677
|
return JSON.stringify({
|
|
607
678
|
results,
|
|
608
679
|
total_duration_ms: Date.now() - startTime,
|
|
@@ -798,31 +869,31 @@ ${args.prompt}` : args.prompt;
|
|
|
798
869
|
|
|
799
870
|
// src/tools/repo-memory.ts
|
|
800
871
|
import { tool as tool7 } from "@opencode-ai/plugin";
|
|
801
|
-
import { readFileSync as
|
|
802
|
-
import { join as
|
|
872
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
873
|
+
import { join as join7 } from "path";
|
|
803
874
|
var MEMORY_FILE = "MEMORY.json";
|
|
804
875
|
function memoryPath(directory) {
|
|
805
|
-
return
|
|
876
|
+
return join7(codebaseDir(directory), MEMORY_FILE);
|
|
806
877
|
}
|
|
807
878
|
function emptyMemory() {
|
|
808
879
|
return { version: "1.0", last_updated: new Date().toISOString(), nodes: {} };
|
|
809
880
|
}
|
|
810
881
|
function readMemory(directory) {
|
|
811
882
|
const p = memoryPath(directory);
|
|
812
|
-
if (!
|
|
883
|
+
if (!existsSync6(p))
|
|
813
884
|
return emptyMemory();
|
|
814
885
|
try {
|
|
815
|
-
return JSON.parse(
|
|
886
|
+
return JSON.parse(readFileSync6(p, "utf-8"));
|
|
816
887
|
} catch {
|
|
817
888
|
return emptyMemory();
|
|
818
889
|
}
|
|
819
890
|
}
|
|
820
891
|
function writeMemory(directory, memory) {
|
|
821
892
|
const base = codebaseDir(directory);
|
|
822
|
-
if (!
|
|
823
|
-
|
|
893
|
+
if (!existsSync6(base))
|
|
894
|
+
mkdirSync3(base, { recursive: true });
|
|
824
895
|
memory.last_updated = new Date().toISOString();
|
|
825
|
-
|
|
896
|
+
writeFileSync6(memoryPath(directory), JSON.stringify(memory, null, 2), "utf-8");
|
|
826
897
|
}
|
|
827
898
|
var repoMemoryTool = tool7({
|
|
828
899
|
description: "Repo Memory Graph: read/write/query persistent architecture graph in .codebase/MEMORY.json (modules, dependencies, ownership, bug history, conventions)",
|
|
@@ -899,28 +970,28 @@ var repoMemoryTool = tool7({
|
|
|
899
970
|
|
|
900
971
|
// src/tools/failure-replay.ts
|
|
901
972
|
import { tool as tool8 } from "@opencode-ai/plugin";
|
|
902
|
-
import { readFileSync as
|
|
903
|
-
import { join as
|
|
973
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync7, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
|
|
974
|
+
import { join as join8 } from "path";
|
|
904
975
|
var FAILURES_FILE = "FAILURES.json";
|
|
905
976
|
function failuresPath(directory) {
|
|
906
|
-
return
|
|
977
|
+
return join8(codebaseDir(directory), FAILURES_FILE);
|
|
907
978
|
}
|
|
908
979
|
function readStore(directory) {
|
|
909
980
|
const p = failuresPath(directory);
|
|
910
|
-
if (!
|
|
981
|
+
if (!existsSync7(p))
|
|
911
982
|
return { version: "1.0", last_updated: new Date().toISOString(), entries: [] };
|
|
912
983
|
try {
|
|
913
|
-
return JSON.parse(
|
|
984
|
+
return JSON.parse(readFileSync7(p, "utf-8"));
|
|
914
985
|
} catch {
|
|
915
986
|
return { version: "1.0", last_updated: new Date().toISOString(), entries: [] };
|
|
916
987
|
}
|
|
917
988
|
}
|
|
918
989
|
function writeStore(directory, store) {
|
|
919
990
|
const base = codebaseDir(directory);
|
|
920
|
-
if (!
|
|
921
|
-
|
|
991
|
+
if (!existsSync7(base))
|
|
992
|
+
mkdirSync4(base, { recursive: true });
|
|
922
993
|
store.last_updated = new Date().toISOString();
|
|
923
|
-
|
|
994
|
+
writeFileSync7(failuresPath(directory), JSON.stringify(store, null, 2), "utf-8");
|
|
924
995
|
}
|
|
925
996
|
var failureReplayTool = tool8({
|
|
926
997
|
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",
|
|
@@ -1004,17 +1075,17 @@ var failureReplayTool = tool8({
|
|
|
1004
1075
|
|
|
1005
1076
|
// src/tools/decision-trace.ts
|
|
1006
1077
|
import { tool as tool9 } from "@opencode-ai/plugin";
|
|
1007
|
-
import { readFileSync as
|
|
1008
|
-
import { join as
|
|
1078
|
+
import { readFileSync as readFileSync8, existsSync as existsSync8, mkdirSync as mkdirSync5, appendFileSync as appendFileSync2 } from "fs";
|
|
1079
|
+
import { join as join9 } from "path";
|
|
1009
1080
|
var DECISIONS_FILE = "DECISIONS.jsonl";
|
|
1010
1081
|
function decisionsPath(directory) {
|
|
1011
|
-
return
|
|
1082
|
+
return join9(codebaseDir(directory), DECISIONS_FILE);
|
|
1012
1083
|
}
|
|
1013
1084
|
function readDecisions(directory) {
|
|
1014
1085
|
const p = decisionsPath(directory);
|
|
1015
|
-
if (!
|
|
1086
|
+
if (!existsSync8(p))
|
|
1016
1087
|
return [];
|
|
1017
|
-
return
|
|
1088
|
+
return readFileSync8(p, "utf-8").split(`
|
|
1018
1089
|
`).filter((l) => l.trim()).map((l) => {
|
|
1019
1090
|
try {
|
|
1020
1091
|
return JSON.parse(l);
|
|
@@ -1054,10 +1125,10 @@ var decisionTraceTool = tool9({
|
|
|
1054
1125
|
case "record": {
|
|
1055
1126
|
if (!args.entry)
|
|
1056
1127
|
return JSON.stringify({ error: "entry required" });
|
|
1057
|
-
if (!
|
|
1058
|
-
|
|
1128
|
+
if (!existsSync8(base))
|
|
1129
|
+
mkdirSync5(base, { recursive: true });
|
|
1059
1130
|
const entry = { ...args.entry, timestamp: new Date().toISOString() };
|
|
1060
|
-
|
|
1131
|
+
appendFileSync2(decisionsPath(dir), JSON.stringify(entry) + `
|
|
1061
1132
|
`, "utf-8");
|
|
1062
1133
|
return JSON.stringify({ success: true, id: args.entry.id });
|
|
1063
1134
|
}
|
|
@@ -1089,28 +1160,28 @@ var decisionTraceTool = tool9({
|
|
|
1089
1160
|
|
|
1090
1161
|
// src/tools/volatility-map.ts
|
|
1091
1162
|
import { tool as tool10 } from "@opencode-ai/plugin";
|
|
1092
|
-
import { readFileSync as
|
|
1093
|
-
import { join as
|
|
1163
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync9, existsSync as existsSync9, mkdirSync as mkdirSync6 } from "fs";
|
|
1164
|
+
import { join as join10 } from "path";
|
|
1094
1165
|
var VOLATILITY_FILE = "VOLATILITY.json";
|
|
1095
1166
|
function volatilityPath(directory) {
|
|
1096
|
-
return
|
|
1167
|
+
return join10(codebaseDir(directory), VOLATILITY_FILE);
|
|
1097
1168
|
}
|
|
1098
1169
|
function readStore2(directory) {
|
|
1099
1170
|
const p = volatilityPath(directory);
|
|
1100
|
-
if (!
|
|
1171
|
+
if (!existsSync9(p))
|
|
1101
1172
|
return { version: "1.0", last_updated: new Date().toISOString(), generated_at: new Date().toISOString(), entries: [] };
|
|
1102
1173
|
try {
|
|
1103
|
-
return JSON.parse(
|
|
1174
|
+
return JSON.parse(readFileSync9(p, "utf-8"));
|
|
1104
1175
|
} catch {
|
|
1105
1176
|
return { version: "1.0", last_updated: new Date().toISOString(), generated_at: new Date().toISOString(), entries: [] };
|
|
1106
1177
|
}
|
|
1107
1178
|
}
|
|
1108
1179
|
function writeStore2(directory, store) {
|
|
1109
1180
|
const base = codebaseDir(directory);
|
|
1110
|
-
if (!
|
|
1111
|
-
|
|
1181
|
+
if (!existsSync9(base))
|
|
1182
|
+
mkdirSync6(base, { recursive: true });
|
|
1112
1183
|
store.last_updated = new Date().toISOString();
|
|
1113
|
-
|
|
1184
|
+
writeFileSync9(volatilityPath(directory), JSON.stringify(store, null, 2), "utf-8");
|
|
1114
1185
|
}
|
|
1115
1186
|
function stabilityLabel(churn, hotfixes, todos) {
|
|
1116
1187
|
const score = churn + hotfixes * 10 + todos * 2;
|
|
@@ -1197,28 +1268,28 @@ var volatilityMapTool = tool10({
|
|
|
1197
1268
|
|
|
1198
1269
|
// src/tools/policy-engine.ts
|
|
1199
1270
|
import { tool as tool11 } from "@opencode-ai/plugin";
|
|
1200
|
-
import { readFileSync as
|
|
1201
|
-
import { join as
|
|
1271
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync10, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
|
|
1272
|
+
import { join as join11 } from "path";
|
|
1202
1273
|
var POLICIES_FILE = "POLICIES.json";
|
|
1203
1274
|
function policiesPath(directory) {
|
|
1204
|
-
return
|
|
1275
|
+
return join11(codebaseDir(directory), POLICIES_FILE);
|
|
1205
1276
|
}
|
|
1206
1277
|
function readStore3(directory) {
|
|
1207
1278
|
const p = policiesPath(directory);
|
|
1208
|
-
if (!
|
|
1279
|
+
if (!existsSync10(p))
|
|
1209
1280
|
return { version: "1.0", last_updated: new Date().toISOString(), policies: [] };
|
|
1210
1281
|
try {
|
|
1211
|
-
return JSON.parse(
|
|
1282
|
+
return JSON.parse(readFileSync10(p, "utf-8"));
|
|
1212
1283
|
} catch {
|
|
1213
1284
|
return { version: "1.0", last_updated: new Date().toISOString(), policies: [] };
|
|
1214
1285
|
}
|
|
1215
1286
|
}
|
|
1216
1287
|
function writeStore3(directory, store) {
|
|
1217
1288
|
const base = codebaseDir(directory);
|
|
1218
|
-
if (!
|
|
1219
|
-
|
|
1289
|
+
if (!existsSync10(base))
|
|
1290
|
+
mkdirSync7(base, { recursive: true });
|
|
1220
1291
|
store.last_updated = new Date().toISOString();
|
|
1221
|
-
|
|
1292
|
+
writeFileSync10(policiesPath(directory), JSON.stringify(store, null, 2), "utf-8");
|
|
1222
1293
|
}
|
|
1223
1294
|
var policyEngineTool = tool11({
|
|
1224
1295
|
description: "Self-Healing Policy Engine: manage .codebase/POLICIES.json — add, list, query, toggle, and record violations of editing policies learned from past failures",
|
|
@@ -1300,7 +1371,7 @@ var policyEngineTool = tool11({
|
|
|
1300
1371
|
|
|
1301
1372
|
// src/tools/hash-edit.ts
|
|
1302
1373
|
import { tool as tool12 } from "@opencode-ai/plugin";
|
|
1303
|
-
import { readFileSync as
|
|
1374
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
1304
1375
|
import { createHash } from "crypto";
|
|
1305
1376
|
var hashEditTool = tool12({
|
|
1306
1377
|
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.",
|
|
@@ -1314,7 +1385,7 @@ var hashEditTool = tool12({
|
|
|
1314
1385
|
const fullPath = args.filePath.startsWith("/") ? args.filePath : `${context.directory}/${args.filePath}`;
|
|
1315
1386
|
let content;
|
|
1316
1387
|
try {
|
|
1317
|
-
content =
|
|
1388
|
+
content = readFileSync11(fullPath, "utf-8");
|
|
1318
1389
|
} catch (e) {
|
|
1319
1390
|
return `Error: Could not read file ${args.filePath}`;
|
|
1320
1391
|
}
|
|
@@ -1328,7 +1399,7 @@ var hashEditTool = tool12({
|
|
|
1328
1399
|
}
|
|
1329
1400
|
}
|
|
1330
1401
|
const newContent = content.replace(args.targetContent, args.replacementContent);
|
|
1331
|
-
|
|
1402
|
+
writeFileSync11(fullPath, newContent, "utf-8");
|
|
1332
1403
|
return `Successfully updated ${args.filePath} using hash-anchored edit.`;
|
|
1333
1404
|
}
|
|
1334
1405
|
});
|
|
@@ -1397,8 +1468,8 @@ Please synthesize these results. Identify areas of agreement, resolve conflicts,
|
|
|
1397
1468
|
|
|
1398
1469
|
// src/tools/context-generator.ts
|
|
1399
1470
|
import { tool as tool14 } from "@opencode-ai/plugin";
|
|
1400
|
-
import { writeFileSync as
|
|
1401
|
-
import { join as
|
|
1471
|
+
import { writeFileSync as writeFileSync12, existsSync as existsSync11, readFileSync as readFileSync12, readdirSync as readdirSync2, statSync } from "fs";
|
|
1472
|
+
import { join as join12 } from "path";
|
|
1402
1473
|
var contextGeneratorTool = tool14({
|
|
1403
1474
|
description: "Auto-generate or update hierarchical context files (AGENTS.md, CLAUDE.md) throughout the project. These files provide critical grounding for AI agents.",
|
|
1404
1475
|
args: {
|
|
@@ -1407,20 +1478,20 @@ var contextGeneratorTool = tool14({
|
|
|
1407
1478
|
},
|
|
1408
1479
|
async execute(args, context) {
|
|
1409
1480
|
const root = context.directory;
|
|
1410
|
-
const target = args.targetDir ?
|
|
1411
|
-
if (!
|
|
1481
|
+
const target = args.targetDir ? join12(root, args.targetDir) : root;
|
|
1482
|
+
if (!existsSync11(target)) {
|
|
1412
1483
|
return `Error: Directory ${target} does not exist.`;
|
|
1413
1484
|
}
|
|
1414
|
-
const agentsMdPath =
|
|
1415
|
-
if (
|
|
1485
|
+
const agentsMdPath = join12(target, "AGENTS.md");
|
|
1486
|
+
if (existsSync11(agentsMdPath) && !args.force) {
|
|
1416
1487
|
return `AGENTS.md already exists in ${target}. Use force: true to overwrite.`;
|
|
1417
1488
|
}
|
|
1418
|
-
const pkgPath =
|
|
1489
|
+
const pkgPath = join12(root, "package.json");
|
|
1419
1490
|
let projectName = "Project";
|
|
1420
1491
|
let techStack = "Unknown";
|
|
1421
|
-
if (
|
|
1492
|
+
if (existsSync11(pkgPath)) {
|
|
1422
1493
|
try {
|
|
1423
|
-
const pkg = JSON.parse(
|
|
1494
|
+
const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
1424
1495
|
projectName = pkg.name || projectName;
|
|
1425
1496
|
techStack = Object.keys(pkg.dependencies || {}).slice(0, 5).join(", ");
|
|
1426
1497
|
} catch {}
|
|
@@ -1438,7 +1509,7 @@ var contextGeneratorTool = tool14({
|
|
|
1438
1509
|
|
|
1439
1510
|
## Directory Map
|
|
1440
1511
|
${readdirSync2(target).slice(0, 10).map((f) => {
|
|
1441
|
-
const s = statSync(
|
|
1512
|
+
const s = statSync(join12(target, f));
|
|
1442
1513
|
return `- \`${f}\`${s.isDirectory() ? "/" : ""} : [Description]`;
|
|
1443
1514
|
}).join(`
|
|
1444
1515
|
`)}
|
|
@@ -1446,17 +1517,17 @@ ${readdirSync2(target).slice(0, 10).map((f) => {
|
|
|
1446
1517
|
---
|
|
1447
1518
|
Generated by FlowDeck Context Generator.
|
|
1448
1519
|
`;
|
|
1449
|
-
|
|
1520
|
+
writeFileSync12(agentsMdPath, content, "utf-8");
|
|
1450
1521
|
return `Successfully generated AGENTS.md in ${target}.`;
|
|
1451
1522
|
}
|
|
1452
1523
|
});
|
|
1453
1524
|
|
|
1454
1525
|
// src/tools/create-skill.ts
|
|
1455
1526
|
import { tool as tool15 } from "@opencode-ai/plugin";
|
|
1456
|
-
import { mkdirSync as
|
|
1457
|
-
import { join as
|
|
1527
|
+
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync13, existsSync as existsSync12 } from "fs";
|
|
1528
|
+
import { join as join13, dirname as dirname3 } from "path";
|
|
1458
1529
|
import { fileURLToPath } from "url";
|
|
1459
|
-
var SKILLS_DIR =
|
|
1530
|
+
var SKILLS_DIR = join13(dirname3(fileURLToPath(import.meta.url)), "..", "skills");
|
|
1460
1531
|
var createSkillTool = tool15({
|
|
1461
1532
|
description: "Create a new reusable skill in the FlowDeck skill library (src/skills/). " + "Use this when you discover a repeatable pattern, solve a novel problem with human guidance, " + "or want to capture domain knowledge for future sessions.",
|
|
1462
1533
|
args: {
|
|
@@ -1466,9 +1537,9 @@ var createSkillTool = tool15({
|
|
|
1466
1537
|
tags: tool15.schema.array(tool15.schema.string()).optional().describe("Optional tags for categorisation, e.g. ['performance', 'typescript']")
|
|
1467
1538
|
},
|
|
1468
1539
|
async execute(args) {
|
|
1469
|
-
const skillDir =
|
|
1470
|
-
const skillFile =
|
|
1471
|
-
if (
|
|
1540
|
+
const skillDir = join13(SKILLS_DIR, args.name);
|
|
1541
|
+
const skillFile = join13(skillDir, "SKILL.md");
|
|
1542
|
+
if (existsSync12(skillFile)) {
|
|
1472
1543
|
return `Skill '${args.name}' already exists at ${skillFile}.
|
|
1473
1544
|
` + `Use a different name or delete the existing skill directory first.`;
|
|
1474
1545
|
}
|
|
@@ -1483,8 +1554,8 @@ origin: FlowDeck (self-learned)${tagLine}
|
|
|
1483
1554
|
`;
|
|
1484
1555
|
const fullContent = frontmatter + args.content.trimStart();
|
|
1485
1556
|
try {
|
|
1486
|
-
|
|
1487
|
-
|
|
1557
|
+
mkdirSync8(skillDir, { recursive: true });
|
|
1558
|
+
writeFileSync13(skillFile, fullContent, "utf-8");
|
|
1488
1559
|
return `✓ Skill '${args.name}' created at ${skillFile}
|
|
1489
1560
|
|
|
1490
1561
|
` + `The skill is now part of the FlowDeck library. Restart OpenCode to load it into the active session.`;
|
|
@@ -1496,8 +1567,8 @@ origin: FlowDeck (self-learned)${tagLine}
|
|
|
1496
1567
|
|
|
1497
1568
|
// src/tools/reflect.ts
|
|
1498
1569
|
import { tool as tool16 } from "@opencode-ai/plugin";
|
|
1499
|
-
import { existsSync as
|
|
1500
|
-
import { join as
|
|
1570
|
+
import { existsSync as existsSync13, readFileSync as readFileSync13 } from "fs";
|
|
1571
|
+
import { join as join14 } from "path";
|
|
1501
1572
|
var MAX_ARTIFACT_BYTES = 4000;
|
|
1502
1573
|
function tail(text, maxBytes) {
|
|
1503
1574
|
if (text.length <= maxBytes)
|
|
@@ -1526,11 +1597,11 @@ var reflectTool = tool16({
|
|
|
1526
1597
|
];
|
|
1527
1598
|
let found = 0;
|
|
1528
1599
|
for (const [rel, label] of ARTIFACT_PATHS) {
|
|
1529
|
-
const full =
|
|
1530
|
-
if (!
|
|
1600
|
+
const full = join14(root, rel);
|
|
1601
|
+
if (!existsSync13(full))
|
|
1531
1602
|
continue;
|
|
1532
1603
|
try {
|
|
1533
|
-
const raw =
|
|
1604
|
+
const raw = readFileSync13(full, "utf-8").trim();
|
|
1534
1605
|
if (!raw)
|
|
1535
1606
|
continue;
|
|
1536
1607
|
const count = raw.split(`
|
|
@@ -1550,15 +1621,15 @@ var reflectTool = tool16({
|
|
|
1550
1621
|
});
|
|
1551
1622
|
|
|
1552
1623
|
// src/hooks/guard-rails.ts
|
|
1553
|
-
import { existsSync as
|
|
1554
|
-
import { join as
|
|
1624
|
+
import { existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
|
|
1625
|
+
import { join as join15 } from "path";
|
|
1555
1626
|
var PLANNING_DIR2 = ".planning";
|
|
1556
1627
|
var CONFIG_FILE = "config.json";
|
|
1557
1628
|
var STATE_FILE2 = "STATE.md";
|
|
1558
1629
|
function resolveExecutionMode(configPath, trustScore, volatility) {
|
|
1559
|
-
if (
|
|
1630
|
+
if (existsSync14(configPath)) {
|
|
1560
1631
|
try {
|
|
1561
|
-
const config = JSON.parse(
|
|
1632
|
+
const config = JSON.parse(readFileSync14(configPath, "utf-8"));
|
|
1562
1633
|
if (config.execution_mode === "review-only")
|
|
1563
1634
|
return "review-only";
|
|
1564
1635
|
if (config.execution_mode === "guarded")
|
|
@@ -1609,22 +1680,22 @@ var BUILD_DEPLOY_PATTERNS = [
|
|
|
1609
1680
|
];
|
|
1610
1681
|
async function guardRailsHook(ctx, input, _output) {
|
|
1611
1682
|
const dir = ctx.directory;
|
|
1612
|
-
const planningDirPath =
|
|
1683
|
+
const planningDirPath = join15(dir, PLANNING_DIR2);
|
|
1613
1684
|
const codebaseDirectory = codebaseDir(dir);
|
|
1614
|
-
const configPath =
|
|
1615
|
-
const statePath2 =
|
|
1685
|
+
const configPath = join15(planningDirPath, CONFIG_FILE);
|
|
1686
|
+
const statePath2 = join15(planningDirPath, STATE_FILE2);
|
|
1616
1687
|
const workspaceRoot = findWorkspaceRoot(dir);
|
|
1617
1688
|
if (workspaceRoot && dir !== workspaceRoot) {
|
|
1618
1689
|
const config = getWorkspaceConfig(dir);
|
|
1619
|
-
if (config && config.workspace_mode === "shared" && !
|
|
1690
|
+
if (config && config.workspace_mode === "shared" && !existsSync14(planningDirPath)) {
|
|
1620
1691
|
const msg = `No .planning/ in this sub-repo. Switch to workspace root: cd ${workspaceRoot}`;
|
|
1621
1692
|
throw new Error(`[flowdeck] BLOCK: ${msg}`);
|
|
1622
1693
|
}
|
|
1623
1694
|
}
|
|
1624
1695
|
if (input.tool === "write" || input.tool === "edit") {
|
|
1625
|
-
if (!
|
|
1696
|
+
if (!existsSync14(planningDirPath))
|
|
1626
1697
|
return;
|
|
1627
|
-
if (!
|
|
1698
|
+
if (!existsSync14(codebaseDirectory)) {
|
|
1628
1699
|
throw new Error(`[flowdeck] WARNING: .codebase/ not found. Run /map-codebase to map the codebase.`);
|
|
1629
1700
|
}
|
|
1630
1701
|
const execMode = resolveExecutionMode(configPath, null);
|
|
@@ -1659,9 +1730,9 @@ async function guardRailsHook(ctx, input, _output) {
|
|
|
1659
1730
|
}
|
|
1660
1731
|
}
|
|
1661
1732
|
function effectiveSeverity(configPath, statePath2) {
|
|
1662
|
-
if (
|
|
1733
|
+
if (existsSync14(configPath)) {
|
|
1663
1734
|
try {
|
|
1664
|
-
const configContent =
|
|
1735
|
+
const configContent = readFileSync14(configPath, "utf-8");
|
|
1665
1736
|
const config = JSON.parse(configContent);
|
|
1666
1737
|
if (config.guard_enforcement === "warn")
|
|
1667
1738
|
return "warn";
|
|
@@ -1677,10 +1748,10 @@ function getEffectiveSeverity(configPath, statePath2) {
|
|
|
1677
1748
|
return effectiveSeverity(configPath, statePath2);
|
|
1678
1749
|
}
|
|
1679
1750
|
function getPlanConfirmed(statePath2) {
|
|
1680
|
-
if (!
|
|
1751
|
+
if (!existsSync14(statePath2))
|
|
1681
1752
|
return false;
|
|
1682
1753
|
try {
|
|
1683
|
-
const content =
|
|
1754
|
+
const content = readFileSync14(statePath2, "utf-8");
|
|
1684
1755
|
const match = content.match(/plan_confirmed:\s*(true|false)/i);
|
|
1685
1756
|
return match ? match[1].toLowerCase() === "true" : false;
|
|
1686
1757
|
} catch {
|
|
@@ -1688,21 +1759,21 @@ function getPlanConfirmed(statePath2) {
|
|
|
1688
1759
|
}
|
|
1689
1760
|
}
|
|
1690
1761
|
function getWarningMessage(statePath2, planningDir3) {
|
|
1691
|
-
if (!
|
|
1762
|
+
if (!existsSync14(join15(planningDir3, STATE_FILE2))) {
|
|
1692
1763
|
return "No .planning/ found. Run /new-project first.";
|
|
1693
1764
|
}
|
|
1694
1765
|
return "Plan not confirmed. Run /plan and confirm to enable execution.";
|
|
1695
1766
|
}
|
|
1696
1767
|
function getBlockMessage(statePath2, planningDir3) {
|
|
1697
|
-
if (!
|
|
1768
|
+
if (!existsSync14(join15(planningDir3, STATE_FILE2))) {
|
|
1698
1769
|
return "No .planning/ found. Run /new-project first.";
|
|
1699
1770
|
}
|
|
1700
1771
|
return "Plan not confirmed. Run /plan and confirm to enable execution.";
|
|
1701
1772
|
}
|
|
1702
1773
|
|
|
1703
1774
|
// src/hooks/tool-guard.ts
|
|
1704
|
-
import { existsSync as
|
|
1705
|
-
import { join as
|
|
1775
|
+
import { existsSync as existsSync15, readFileSync as readFileSync15 } from "fs";
|
|
1776
|
+
import { join as join16 } from "path";
|
|
1706
1777
|
var BLOCKED_PATTERNS = {
|
|
1707
1778
|
read: [".env", ".pem", ".key", ".secret"],
|
|
1708
1779
|
write: ["node_modules"],
|
|
@@ -1748,11 +1819,11 @@ function isBlocked(tool17, args) {
|
|
|
1748
1819
|
return null;
|
|
1749
1820
|
}
|
|
1750
1821
|
function checkArchConstraint(directory, filePath) {
|
|
1751
|
-
const constraintsPath =
|
|
1752
|
-
if (!
|
|
1822
|
+
const constraintsPath = join16(codebaseDir(directory), "CONSTRAINTS.md");
|
|
1823
|
+
if (!existsSync15(constraintsPath))
|
|
1753
1824
|
return null;
|
|
1754
1825
|
try {
|
|
1755
|
-
const content =
|
|
1826
|
+
const content = readFileSync15(constraintsPath, "utf-8");
|
|
1756
1827
|
const match = content.match(/## Forbidden Paths\n([\s\S]*?)(?:\n##|$)/);
|
|
1757
1828
|
if (!match)
|
|
1758
1829
|
return null;
|
|
@@ -1797,18 +1868,18 @@ async function toolGuardHook(ctx, input, output) {
|
|
|
1797
1868
|
}
|
|
1798
1869
|
|
|
1799
1870
|
// src/hooks/session-start.ts
|
|
1800
|
-
import { existsSync as
|
|
1871
|
+
import { existsSync as existsSync16, readFileSync as readFileSync16 } from "fs";
|
|
1801
1872
|
async function sessionStartHook(ctx) {
|
|
1802
1873
|
const planningDir3 = ctx.directory + "/.planning";
|
|
1803
1874
|
const codebaseDirectory = codebaseDir(ctx.directory);
|
|
1804
1875
|
const workspaceRoot = findWorkspaceRoot(ctx.directory);
|
|
1805
1876
|
const config = workspaceRoot ? getWorkspaceConfig(ctx.directory) : null;
|
|
1806
|
-
if (!
|
|
1877
|
+
if (!existsSync16(planningDir3)) {
|
|
1807
1878
|
return {
|
|
1808
1879
|
flowdeck_phase: null,
|
|
1809
1880
|
flowdeck_status: "no_plan",
|
|
1810
1881
|
flowdeck_warning: "Run /new-project or /map-codebase to initialize.",
|
|
1811
|
-
flowdeck_has_codebase:
|
|
1882
|
+
flowdeck_has_codebase: existsSync16(codebaseDirectory),
|
|
1812
1883
|
...workspaceRoot && config?.sub_repos ? {
|
|
1813
1884
|
flowdeck_workspace_root: workspaceRoot,
|
|
1814
1885
|
flowdeck_sub_repos: config.sub_repos,
|
|
@@ -1819,7 +1890,7 @@ async function sessionStartHook(ctx) {
|
|
|
1819
1890
|
}
|
|
1820
1891
|
try {
|
|
1821
1892
|
const stateFilePath = statePath(ctx.directory);
|
|
1822
|
-
const content =
|
|
1893
|
+
const content = readFileSync16(stateFilePath, "utf-8");
|
|
1823
1894
|
const state = parseState(content);
|
|
1824
1895
|
const currentPhase = state["current_phase"] || {};
|
|
1825
1896
|
const result = {
|
|
@@ -1827,7 +1898,7 @@ async function sessionStartHook(ctx) {
|
|
|
1827
1898
|
flowdeck_status: currentPhase["status"] ?? null,
|
|
1828
1899
|
flowdeck_steps_pending: currentPhase["steps_pending"] ?? null,
|
|
1829
1900
|
flowdeck_last_action: currentPhase["last_action"] ?? null,
|
|
1830
|
-
flowdeck_has_codebase:
|
|
1901
|
+
flowdeck_has_codebase: existsSync16(codebaseDirectory)
|
|
1831
1902
|
};
|
|
1832
1903
|
if (workspaceRoot && config?.sub_repos && config.sub_repos.length > 0) {
|
|
1833
1904
|
result.flowdeck_workspace_root = workspaceRoot;
|
|
@@ -1842,7 +1913,7 @@ async function sessionStartHook(ctx) {
|
|
|
1842
1913
|
flowdeck_phase: null,
|
|
1843
1914
|
flowdeck_status: "error",
|
|
1844
1915
|
flowdeck_warning: "State file unreadable. Continuing without flowdeck context.",
|
|
1845
|
-
flowdeck_has_codebase:
|
|
1916
|
+
flowdeck_has_codebase: existsSync16(codebaseDirectory)
|
|
1846
1917
|
};
|
|
1847
1918
|
if (workspaceRoot && config?.sub_repos && config.sub_repos.length > 0) {
|
|
1848
1919
|
result.flowdeck_workspace_root = workspaceRoot;
|
|
@@ -1908,8 +1979,8 @@ function notifyPermissionNeeded(tool17) {
|
|
|
1908
1979
|
}
|
|
1909
1980
|
|
|
1910
1981
|
// src/hooks/patch-trust.ts
|
|
1911
|
-
import { existsSync as
|
|
1912
|
-
import { join as
|
|
1982
|
+
import { existsSync as existsSync17, readFileSync as readFileSync17 } from "fs";
|
|
1983
|
+
import { join as join17 } from "path";
|
|
1913
1984
|
var HIGH_RISK_KEYWORDS = [
|
|
1914
1985
|
"password",
|
|
1915
1986
|
"secret",
|
|
@@ -1931,11 +2002,11 @@ var HIGH_RISK_KEYWORDS = [
|
|
|
1931
2002
|
"privilege"
|
|
1932
2003
|
];
|
|
1933
2004
|
function loadVolatility(directory) {
|
|
1934
|
-
const p =
|
|
1935
|
-
if (!
|
|
2005
|
+
const p = join17(codebaseDir(directory), "VOLATILITY.json");
|
|
2006
|
+
if (!existsSync17(p))
|
|
1936
2007
|
return {};
|
|
1937
2008
|
try {
|
|
1938
|
-
const data = JSON.parse(
|
|
2009
|
+
const data = JSON.parse(readFileSync17(p, "utf-8"));
|
|
1939
2010
|
const map = {};
|
|
1940
2011
|
for (const entry of data.entries ?? [])
|
|
1941
2012
|
map[entry.path] = entry.stability;
|
|
@@ -1945,11 +2016,11 @@ function loadVolatility(directory) {
|
|
|
1945
2016
|
}
|
|
1946
2017
|
}
|
|
1947
2018
|
function loadFailedPaths(directory) {
|
|
1948
|
-
const p =
|
|
1949
|
-
if (!
|
|
2019
|
+
const p = join17(codebaseDir(directory), "FAILURES.json");
|
|
2020
|
+
if (!existsSync17(p))
|
|
1950
2021
|
return [];
|
|
1951
2022
|
try {
|
|
1952
|
-
const data = JSON.parse(
|
|
2023
|
+
const data = JSON.parse(readFileSync17(p, "utf-8"));
|
|
1953
2024
|
return (data.entries ?? []).flatMap((e) => e.affected_paths ?? []);
|
|
1954
2025
|
} catch {
|
|
1955
2026
|
return [];
|
|
@@ -2006,8 +2077,8 @@ async function patchTrustHook(ctx, input, output) {
|
|
|
2006
2077
|
}
|
|
2007
2078
|
|
|
2008
2079
|
// src/hooks/decision-trace-hook.ts
|
|
2009
|
-
import { existsSync as
|
|
2010
|
-
import { join as
|
|
2080
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync9, appendFileSync as appendFileSync3 } from "fs";
|
|
2081
|
+
import { join as join18 } from "path";
|
|
2011
2082
|
async function decisionTraceHook(ctx, input, output) {
|
|
2012
2083
|
if (input.tool !== "write" && input.tool !== "edit")
|
|
2013
2084
|
return;
|
|
@@ -2016,8 +2087,8 @@ async function decisionTraceHook(ctx, input, output) {
|
|
|
2016
2087
|
return;
|
|
2017
2088
|
const base = codebaseDir(ctx.directory);
|
|
2018
2089
|
try {
|
|
2019
|
-
if (!
|
|
2020
|
-
|
|
2090
|
+
if (!existsSync18(base))
|
|
2091
|
+
mkdirSync9(base, { recursive: true });
|
|
2021
2092
|
const entry = {
|
|
2022
2093
|
timestamp: new Date().toISOString(),
|
|
2023
2094
|
file_path: filePath,
|
|
@@ -2029,32 +2100,11 @@ async function decisionTraceHook(ctx, input, output) {
|
|
|
2029
2100
|
risk_level: "unknown",
|
|
2030
2101
|
auto_recorded: true
|
|
2031
2102
|
};
|
|
2032
|
-
|
|
2103
|
+
appendFileSync3(join18(base, "DECISIONS.jsonl"), JSON.stringify(entry) + `
|
|
2033
2104
|
`, "utf-8");
|
|
2034
2105
|
} catch {}
|
|
2035
2106
|
}
|
|
2036
2107
|
|
|
2037
|
-
// src/services/telemetry.ts
|
|
2038
|
-
import { existsSync as existsSync18, readFileSync as readFileSync17, appendFileSync as appendFileSync3, mkdirSync as mkdirSync9 } from "fs";
|
|
2039
|
-
import { join as join17 } from "path";
|
|
2040
|
-
import { randomUUID } from "crypto";
|
|
2041
|
-
function telemetryPath(dir) {
|
|
2042
|
-
return join17(codebaseDir(dir), "TELEMETRY.jsonl");
|
|
2043
|
-
}
|
|
2044
|
-
function appendEvent(dir, partial) {
|
|
2045
|
-
const cd = codebaseDir(dir);
|
|
2046
|
-
if (!existsSync18(cd))
|
|
2047
|
-
mkdirSync9(cd, { recursive: true });
|
|
2048
|
-
const event = {
|
|
2049
|
-
id: randomUUID(),
|
|
2050
|
-
ts: new Date().toISOString(),
|
|
2051
|
-
...partial
|
|
2052
|
-
};
|
|
2053
|
-
appendFileSync3(telemetryPath(dir), JSON.stringify(event) + `
|
|
2054
|
-
`, "utf-8");
|
|
2055
|
-
return event;
|
|
2056
|
-
}
|
|
2057
|
-
|
|
2058
2108
|
// src/hooks/telemetry-hook.ts
|
|
2059
2109
|
async function telemetryHook(context, toolInput, output) {
|
|
2060
2110
|
const dir = context.directory ?? process.cwd();
|
|
@@ -2081,8 +2131,8 @@ async function telemetryAfterHook(context, toolInput, _output) {
|
|
|
2081
2131
|
}
|
|
2082
2132
|
|
|
2083
2133
|
// src/services/approval-manager.ts
|
|
2084
|
-
import { existsSync as existsSync19, readFileSync as readFileSync18, writeFileSync as
|
|
2085
|
-
import { join as
|
|
2134
|
+
import { existsSync as existsSync19, readFileSync as readFileSync18, writeFileSync as writeFileSync14, mkdirSync as mkdirSync10 } from "fs";
|
|
2135
|
+
import { join as join19 } from "path";
|
|
2086
2136
|
var APPROVAL_TTL_MS = 30 * 60 * 1000;
|
|
2087
2137
|
var SENSITIVE_PATTERNS = [
|
|
2088
2138
|
/auth/i,
|
|
@@ -2119,7 +2169,7 @@ function isSensitivePath(filePath) {
|
|
|
2119
2169
|
return SENSITIVE_PATTERNS.some((p) => p.test(filePath));
|
|
2120
2170
|
}
|
|
2121
2171
|
function approvalsPath(dir) {
|
|
2122
|
-
return
|
|
2172
|
+
return join19(codebaseDir(dir), "APPROVALS.json");
|
|
2123
2173
|
}
|
|
2124
2174
|
function loadStore(dir) {
|
|
2125
2175
|
const p = approvalsPath(dir);
|
|
@@ -2219,7 +2269,7 @@ function createContextWindowMonitorHook() {
|
|
|
2219
2269
|
|
|
2220
2270
|
// src/hooks/shell-env-hook.ts
|
|
2221
2271
|
import { existsSync as existsSync20, readFileSync as readFileSync19 } from "fs";
|
|
2222
|
-
import { join as
|
|
2272
|
+
import { join as join20 } from "path";
|
|
2223
2273
|
import { createRequire } from "module";
|
|
2224
2274
|
var _version;
|
|
2225
2275
|
function getVersion() {
|
|
@@ -2255,7 +2305,7 @@ var MARKER_TO_LANG = {
|
|
|
2255
2305
|
};
|
|
2256
2306
|
function detectPackageManager(root) {
|
|
2257
2307
|
for (const [lockfile, pm] of Object.entries(LOCKFILE_TO_PM)) {
|
|
2258
|
-
if (existsSync20(
|
|
2308
|
+
if (existsSync20(join20(root, lockfile)))
|
|
2259
2309
|
return pm;
|
|
2260
2310
|
}
|
|
2261
2311
|
return;
|
|
@@ -2264,7 +2314,7 @@ function detectLanguages(root) {
|
|
|
2264
2314
|
const langs = [];
|
|
2265
2315
|
const seen = new Set;
|
|
2266
2316
|
for (const [marker, lang] of Object.entries(MARKER_TO_LANG)) {
|
|
2267
|
-
if (!seen.has(lang) && existsSync20(
|
|
2317
|
+
if (!seen.has(lang) && existsSync20(join20(root, marker))) {
|
|
2268
2318
|
langs.push(lang);
|
|
2269
2319
|
seen.add(lang);
|
|
2270
2320
|
}
|
|
@@ -2272,7 +2322,7 @@ function detectLanguages(root) {
|
|
|
2272
2322
|
return langs;
|
|
2273
2323
|
}
|
|
2274
2324
|
function readCurrentPhase(root) {
|
|
2275
|
-
const statePath2 =
|
|
2325
|
+
const statePath2 = join20(root, ".planning", "STATE.md");
|
|
2276
2326
|
if (!existsSync20(statePath2))
|
|
2277
2327
|
return;
|
|
2278
2328
|
try {
|
|
@@ -2376,7 +2426,7 @@ function createSessionIdleHook(client, tracker) {
|
|
|
2376
2426
|
|
|
2377
2427
|
// src/hooks/compaction-hook.ts
|
|
2378
2428
|
import { existsSync as existsSync21, readFileSync as readFileSync20 } from "fs";
|
|
2379
|
-
import { join as
|
|
2429
|
+
import { join as join21 } from "path";
|
|
2380
2430
|
var STRUCTURED_SUMMARY_PROMPT = `
|
|
2381
2431
|
When summarizing this session, you MUST include the following sections:
|
|
2382
2432
|
|
|
@@ -2415,7 +2465,7 @@ For each: agent name, status, description, session_id.
|
|
|
2415
2465
|
**RESUME, DON'T RESTART.** Use session_id to continue existing sessions.
|
|
2416
2466
|
`;
|
|
2417
2467
|
function readPlanningState2(directory) {
|
|
2418
|
-
const statePath2 =
|
|
2468
|
+
const statePath2 = join21(directory, ".planning", "STATE.md");
|
|
2419
2469
|
if (!existsSync21(statePath2))
|
|
2420
2470
|
return null;
|
|
2421
2471
|
try {
|
|
@@ -2453,7 +2503,7 @@ function createCompactionHook(ctx, tracker) {
|
|
|
2453
2503
|
}
|
|
2454
2504
|
|
|
2455
2505
|
// src/hooks/orchestrator-guard-hook.ts
|
|
2456
|
-
var DISABLED = process.env.FLOWDECK_ORCHESTRATOR_GUARD
|
|
2506
|
+
var DISABLED = process.env.FLOWDECK_ORCHESTRATOR_GUARD !== "on";
|
|
2457
2507
|
var BLOCKED_TOOLS = new Set([
|
|
2458
2508
|
"write_file",
|
|
2459
2509
|
"write",
|
|
@@ -2509,7 +2559,7 @@ function blockMessage(toolName) {
|
|
|
2509
2559
|
` + ` delegate({ agent: "@researcher", prompt: "..." }) — research / file analysis
|
|
2510
2560
|
` + ` delegate({ agent: "@tester", prompt: "..." }) — tests / commands
|
|
2511
2561
|
|
|
2512
|
-
` + `To
|
|
2562
|
+
` + `To enable this guard: set FLOWDECK_ORCHESTRATOR_GUARD=on`;
|
|
2513
2563
|
}
|
|
2514
2564
|
|
|
2515
2565
|
class OrchestratorGuard {
|
|
@@ -5514,18 +5564,18 @@ function getAgentConfigs(agentModels) {
|
|
|
5514
5564
|
|
|
5515
5565
|
// src/config/loader.ts
|
|
5516
5566
|
import { existsSync as existsSync22, readFileSync as readFileSync21 } from "fs";
|
|
5517
|
-
import { join as
|
|
5567
|
+
import { join as join22 } from "path";
|
|
5518
5568
|
import { homedir } from "os";
|
|
5519
5569
|
var CONFIG_FILENAME = "flowdeck.json";
|
|
5520
5570
|
function getGlobalConfigDir() {
|
|
5521
|
-
return process.env.OPENCODE_CONFIG_DIR || (process.env.XDG_CONFIG_HOME ?
|
|
5571
|
+
return process.env.OPENCODE_CONFIG_DIR || (process.env.XDG_CONFIG_HOME ? join22(process.env.XDG_CONFIG_HOME, "opencode") : join22(homedir(), ".config", "opencode"));
|
|
5522
5572
|
}
|
|
5523
5573
|
function loadFlowDeckConfig(directory) {
|
|
5524
5574
|
const candidates = [];
|
|
5525
5575
|
if (directory) {
|
|
5526
|
-
candidates.push(
|
|
5576
|
+
candidates.push(join22(directory, ".opencode", CONFIG_FILENAME));
|
|
5527
5577
|
}
|
|
5528
|
-
candidates.push(
|
|
5578
|
+
candidates.push(join22(getGlobalConfigDir(), CONFIG_FILENAME));
|
|
5529
5579
|
for (const configPath of candidates) {
|
|
5530
5580
|
if (existsSync22(configPath)) {
|
|
5531
5581
|
try {
|
|
@@ -5541,7 +5591,7 @@ function loadFlowDeckConfig(directory) {
|
|
|
5541
5591
|
// src/index.ts
|
|
5542
5592
|
function loadCommands() {
|
|
5543
5593
|
const __dir = dirname4(fileURLToPath2(import.meta.url));
|
|
5544
|
-
const commandsDir =
|
|
5594
|
+
const commandsDir = join23(__dir, "..", "src", "commands");
|
|
5545
5595
|
if (!existsSync23(commandsDir))
|
|
5546
5596
|
return {};
|
|
5547
5597
|
const commands = {};
|
|
@@ -5550,7 +5600,7 @@ function loadCommands() {
|
|
|
5550
5600
|
if (!file.endsWith(".md"))
|
|
5551
5601
|
continue;
|
|
5552
5602
|
const name = basename(file, ".md");
|
|
5553
|
-
const raw = readFileSync22(
|
|
5603
|
+
const raw = readFileSync22(join23(commandsDir, file), "utf-8");
|
|
5554
5604
|
let description;
|
|
5555
5605
|
let template = raw;
|
|
5556
5606
|
const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
@@ -5628,7 +5678,7 @@ var plugin = async (input, _options) => {
|
|
|
5628
5678
|
}
|
|
5629
5679
|
}
|
|
5630
5680
|
}
|
|
5631
|
-
const skillsDir =
|
|
5681
|
+
const skillsDir = join23(dirname4(fileURLToPath2(import.meta.url)), "..", "src", "skills");
|
|
5632
5682
|
if (existsSync23(skillsDir)) {
|
|
5633
5683
|
const cfgAny = cfg;
|
|
5634
5684
|
if (!cfgAny.skills || typeof cfgAny.skills !== "object") {
|