@dv.nghiem/flowdeck 0.3.2 → 0.3.3
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 +11 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +187 -108
- package/dist/tools/memory-status.d.ts +3 -0
- package/dist/tools/memory-status.d.ts.map +1 -0
- package/docs/commands.md +102 -9
- package/docs/quick-start.md +44 -23
- package/docs/workflows.md +10 -8
- package/package.json +1 -1
- package/src/commands/fd-execute.md +192 -0
- package/src/commands/fd-new-feature.md +44 -157
- package/src/commands/fd-new-project.md +1 -2
- package/src/commands/fd-plan.md +1 -1
- package/src/commands/fd-suggest.md +84 -0
- package/src/commands/fd-verify.md +126 -0
- package/src/rules/common/agent-orchestration.md +5 -5
- package/src/rules/common/coding-style.md +17 -0
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { readdirSync as readdirSync3, readFileSync as readFileSync22, existsSync as
|
|
3
|
-
import { join as
|
|
2
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync22, existsSync as existsSync25 } from "fs";
|
|
3
|
+
import { join as join24, basename } from "path";
|
|
4
4
|
import { dirname as dirname4 } from "path";
|
|
5
5
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6
6
|
|
|
@@ -1731,6 +1731,76 @@ var memorySearchTool = tool16({
|
|
|
1731
1731
|
}
|
|
1732
1732
|
});
|
|
1733
1733
|
|
|
1734
|
+
// src/tools/memory-status.ts
|
|
1735
|
+
import { tool as tool17 } from "@opencode-ai/plugin";
|
|
1736
|
+
import { Database as Database2 } from "bun:sqlite";
|
|
1737
|
+
import { existsSync as existsSync14 } from "fs";
|
|
1738
|
+
import { join as join14 } from "path";
|
|
1739
|
+
import { homedir as homedir2 } from "os";
|
|
1740
|
+
var DB_PATH2 = join14(homedir2(), ".flowdeck-memory", "memory.db");
|
|
1741
|
+
var memoryStatusTool = tool17({
|
|
1742
|
+
description: "Check FlowDeck memory database status, statistics, and recent sessions",
|
|
1743
|
+
args: {},
|
|
1744
|
+
async execute(_args, _context) {
|
|
1745
|
+
try {
|
|
1746
|
+
const exists = existsSync14(DB_PATH2);
|
|
1747
|
+
const result = {
|
|
1748
|
+
database_exists: exists,
|
|
1749
|
+
path: DB_PATH2,
|
|
1750
|
+
status: exists ? "ACTIVE" : "NOT_INITIALIZED",
|
|
1751
|
+
statistics: null
|
|
1752
|
+
};
|
|
1753
|
+
if (exists) {
|
|
1754
|
+
try {
|
|
1755
|
+
const db2 = new Database2(DB_PATH2);
|
|
1756
|
+
const sessions = db2.prepare("SELECT COUNT(*) as count FROM sessions").get();
|
|
1757
|
+
const observations = db2.prepare("SELECT COUNT(*) as count FROM observations").get();
|
|
1758
|
+
const summaries = db2.prepare("SELECT COUNT(*) as count FROM summaries").get();
|
|
1759
|
+
const recentSessions = db2.prepare(`
|
|
1760
|
+
SELECT
|
|
1761
|
+
id,
|
|
1762
|
+
content_session_id,
|
|
1763
|
+
project,
|
|
1764
|
+
directory,
|
|
1765
|
+
created_at,
|
|
1766
|
+
last_active_at,
|
|
1767
|
+
prompt_count
|
|
1768
|
+
FROM sessions
|
|
1769
|
+
ORDER BY last_active_at DESC
|
|
1770
|
+
LIMIT 5
|
|
1771
|
+
`).all();
|
|
1772
|
+
result.statistics = {
|
|
1773
|
+
sessions: sessions.count,
|
|
1774
|
+
observations: observations.count,
|
|
1775
|
+
summaries: summaries.count,
|
|
1776
|
+
recent_sessions: recentSessions.map((s) => {
|
|
1777
|
+
const obsCount = db2.prepare("SELECT COUNT(*) as count FROM observations WHERE session_id = ?").get(s.id);
|
|
1778
|
+
return {
|
|
1779
|
+
project: s.project,
|
|
1780
|
+
directory: s.directory,
|
|
1781
|
+
observations_in_session: obsCount.count,
|
|
1782
|
+
last_active: s.last_active_at,
|
|
1783
|
+
prompt_count: s.prompt_count
|
|
1784
|
+
};
|
|
1785
|
+
})
|
|
1786
|
+
};
|
|
1787
|
+
db2.close();
|
|
1788
|
+
} catch (err) {
|
|
1789
|
+
result.status = "ERROR";
|
|
1790
|
+
result.statistics = { error: String(err) };
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
return JSON.stringify(result, null, 2);
|
|
1794
|
+
} catch (err) {
|
|
1795
|
+
return JSON.stringify({
|
|
1796
|
+
status: "ERROR",
|
|
1797
|
+
error: String(err),
|
|
1798
|
+
path: DB_PATH2
|
|
1799
|
+
}, null, 2);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
});
|
|
1803
|
+
|
|
1734
1804
|
// src/hooks/memory-hook.ts
|
|
1735
1805
|
var MAX_TOOL_RESPONSE = 1e4;
|
|
1736
1806
|
var MAX_PROMPT_LENGTH = 2000;
|
|
@@ -1830,13 +1900,13 @@ var memoryHook = {
|
|
|
1830
1900
|
};
|
|
1831
1901
|
|
|
1832
1902
|
// src/hooks/guard-rails.ts
|
|
1833
|
-
import { existsSync as
|
|
1834
|
-
import { join as
|
|
1903
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13 } from "fs";
|
|
1904
|
+
import { join as join15 } from "path";
|
|
1835
1905
|
var PLANNING_DIR2 = ".planning";
|
|
1836
1906
|
var CONFIG_FILE = "config.json";
|
|
1837
1907
|
var STATE_FILE2 = "STATE.md";
|
|
1838
1908
|
function resolveExecutionMode(configPath, trustScore, volatility) {
|
|
1839
|
-
if (
|
|
1909
|
+
if (existsSync15(configPath)) {
|
|
1840
1910
|
try {
|
|
1841
1911
|
const config = JSON.parse(readFileSync13(configPath, "utf-8"));
|
|
1842
1912
|
if (config.execution_mode === "review-only")
|
|
@@ -1892,22 +1962,22 @@ async function guardRailsHook(ctx, input, _output) {
|
|
|
1892
1962
|
if (!ENABLED)
|
|
1893
1963
|
return;
|
|
1894
1964
|
const dir = ctx.directory;
|
|
1895
|
-
const planningDirPath =
|
|
1965
|
+
const planningDirPath = join15(dir, PLANNING_DIR2);
|
|
1896
1966
|
const codebaseDirectory = codebaseDir(dir);
|
|
1897
|
-
const configPath =
|
|
1898
|
-
const statePath2 =
|
|
1967
|
+
const configPath = join15(planningDirPath, CONFIG_FILE);
|
|
1968
|
+
const statePath2 = join15(planningDirPath, STATE_FILE2);
|
|
1899
1969
|
const workspaceRoot = findWorkspaceRoot(dir);
|
|
1900
1970
|
if (workspaceRoot && dir !== workspaceRoot) {
|
|
1901
1971
|
const config = getWorkspaceConfig(dir);
|
|
1902
|
-
if (config && config.workspace_mode === "shared" && !
|
|
1972
|
+
if (config && config.workspace_mode === "shared" && !existsSync15(planningDirPath)) {
|
|
1903
1973
|
const msg = `No .planning/ in this sub-repo. Switch to workspace root: cd ${workspaceRoot}`;
|
|
1904
1974
|
throw new Error(`[flowdeck] BLOCK: ${msg}`);
|
|
1905
1975
|
}
|
|
1906
1976
|
}
|
|
1907
1977
|
if (input.tool === "write" || input.tool === "edit") {
|
|
1908
|
-
if (!
|
|
1978
|
+
if (!existsSync15(planningDirPath))
|
|
1909
1979
|
return;
|
|
1910
|
-
if (!
|
|
1980
|
+
if (!existsSync15(codebaseDirectory)) {
|
|
1911
1981
|
throw new Error(`[flowdeck] WARNING: .codebase/ not found. Run /map-codebase to map the codebase.`);
|
|
1912
1982
|
}
|
|
1913
1983
|
const execMode = resolveExecutionMode(configPath, null);
|
|
@@ -1940,7 +2010,7 @@ async function guardRailsHook(ctx, input, _output) {
|
|
|
1940
2010
|
}
|
|
1941
2011
|
}
|
|
1942
2012
|
function effectiveSeverity(configPath, statePath2) {
|
|
1943
|
-
if (
|
|
2013
|
+
if (existsSync15(configPath)) {
|
|
1944
2014
|
try {
|
|
1945
2015
|
const configContent = readFileSync13(configPath, "utf-8");
|
|
1946
2016
|
const config = JSON.parse(configContent);
|
|
@@ -1958,7 +2028,7 @@ function getEffectiveSeverity(configPath, statePath2) {
|
|
|
1958
2028
|
return effectiveSeverity(configPath, statePath2);
|
|
1959
2029
|
}
|
|
1960
2030
|
function getPlanConfirmed(statePath2) {
|
|
1961
|
-
if (!
|
|
2031
|
+
if (!existsSync15(statePath2))
|
|
1962
2032
|
return false;
|
|
1963
2033
|
try {
|
|
1964
2034
|
const content = readFileSync13(statePath2, "utf-8");
|
|
@@ -1969,32 +2039,32 @@ function getPlanConfirmed(statePath2) {
|
|
|
1969
2039
|
}
|
|
1970
2040
|
}
|
|
1971
2041
|
function getWarningMessage(planningDir2) {
|
|
1972
|
-
if (!
|
|
2042
|
+
if (!existsSync15(join15(planningDir2, STATE_FILE2))) {
|
|
1973
2043
|
return "No .planning/ found. Run /new-project first.";
|
|
1974
2044
|
}
|
|
1975
2045
|
return "Plan not confirmed. Run /plan and confirm to enable execution.";
|
|
1976
2046
|
}
|
|
1977
2047
|
function getBlockMessage(planningDir2) {
|
|
1978
|
-
if (!
|
|
2048
|
+
if (!existsSync15(join15(planningDir2, STATE_FILE2))) {
|
|
1979
2049
|
return "No .planning/ found. Run /new-project first.";
|
|
1980
2050
|
}
|
|
1981
2051
|
return "Plan not confirmed. Run /plan and confirm to enable execution.";
|
|
1982
2052
|
}
|
|
1983
2053
|
|
|
1984
2054
|
// src/hooks/tool-guard.ts
|
|
1985
|
-
import { existsSync as
|
|
1986
|
-
import { join as
|
|
2055
|
+
import { existsSync as existsSync16, readFileSync as readFileSync14 } from "fs";
|
|
2056
|
+
import { join as join16 } from "path";
|
|
1987
2057
|
var IS_ENABLED = () => process.env.FLOWDECK_TOOL_GUARD_ENABLED === "on";
|
|
1988
2058
|
var BLOCKED_PATTERNS = {
|
|
1989
2059
|
read: [".env", ".pem", ".key", ".secret"],
|
|
1990
2060
|
write: ["node_modules"],
|
|
1991
2061
|
bash: ["rm -rf"]
|
|
1992
2062
|
};
|
|
1993
|
-
function isBlocked(
|
|
1994
|
-
const patterns = BLOCKED_PATTERNS[
|
|
2063
|
+
function isBlocked(tool18, args) {
|
|
2064
|
+
const patterns = BLOCKED_PATTERNS[tool18];
|
|
1995
2065
|
if (!patterns)
|
|
1996
2066
|
return null;
|
|
1997
|
-
if (
|
|
2067
|
+
if (tool18 === "bash") {
|
|
1998
2068
|
const cmd = args.command;
|
|
1999
2069
|
if (!cmd)
|
|
2000
2070
|
return null;
|
|
@@ -2005,7 +2075,7 @@ function isBlocked(tool17, args) {
|
|
|
2005
2075
|
}
|
|
2006
2076
|
return null;
|
|
2007
2077
|
}
|
|
2008
|
-
if (
|
|
2078
|
+
if (tool18 === "read") {
|
|
2009
2079
|
const filePath = args.filePath;
|
|
2010
2080
|
if (!filePath)
|
|
2011
2081
|
return null;
|
|
@@ -2016,7 +2086,7 @@ function isBlocked(tool17, args) {
|
|
|
2016
2086
|
}
|
|
2017
2087
|
return null;
|
|
2018
2088
|
}
|
|
2019
|
-
if (
|
|
2089
|
+
if (tool18 === "write") {
|
|
2020
2090
|
const filePath = args.filePath;
|
|
2021
2091
|
if (!filePath)
|
|
2022
2092
|
return null;
|
|
@@ -2030,8 +2100,8 @@ function isBlocked(tool17, args) {
|
|
|
2030
2100
|
return null;
|
|
2031
2101
|
}
|
|
2032
2102
|
function checkArchConstraint(directory, filePath) {
|
|
2033
|
-
const constraintsPath =
|
|
2034
|
-
if (!
|
|
2103
|
+
const constraintsPath = join16(codebaseDir(directory), "CONSTRAINTS.md");
|
|
2104
|
+
if (!existsSync16(constraintsPath))
|
|
2035
2105
|
return null;
|
|
2036
2106
|
try {
|
|
2037
2107
|
const content = readFileSync14(constraintsPath, "utf-8");
|
|
@@ -2081,18 +2151,18 @@ async function toolGuardHook(ctx, input, output) {
|
|
|
2081
2151
|
}
|
|
2082
2152
|
|
|
2083
2153
|
// src/hooks/session-start.ts
|
|
2084
|
-
import { existsSync as
|
|
2154
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
|
|
2085
2155
|
async function sessionStartHook(ctx) {
|
|
2086
2156
|
const planningDir2 = ctx.directory + "/.planning";
|
|
2087
2157
|
const codebaseDirectory = codebaseDir(ctx.directory);
|
|
2088
2158
|
const workspaceRoot = findWorkspaceRoot(ctx.directory);
|
|
2089
2159
|
const config = workspaceRoot ? getWorkspaceConfig(ctx.directory) : null;
|
|
2090
|
-
if (!
|
|
2160
|
+
if (!existsSync17(planningDir2)) {
|
|
2091
2161
|
return {
|
|
2092
2162
|
flowdeck_phase: null,
|
|
2093
2163
|
flowdeck_status: "no_plan",
|
|
2094
2164
|
flowdeck_warning: "Run /new-project or /map-codebase to initialize.",
|
|
2095
|
-
flowdeck_has_codebase:
|
|
2165
|
+
flowdeck_has_codebase: existsSync17(codebaseDirectory),
|
|
2096
2166
|
...workspaceRoot && config?.sub_repos ? {
|
|
2097
2167
|
flowdeck_workspace_root: workspaceRoot,
|
|
2098
2168
|
flowdeck_sub_repos: config.sub_repos,
|
|
@@ -2111,7 +2181,7 @@ async function sessionStartHook(ctx) {
|
|
|
2111
2181
|
flowdeck_status: currentPhase["status"] ?? null,
|
|
2112
2182
|
flowdeck_steps_pending: currentPhase["steps_pending"] ?? null,
|
|
2113
2183
|
flowdeck_last_action: currentPhase["last_action"] ?? null,
|
|
2114
|
-
flowdeck_has_codebase:
|
|
2184
|
+
flowdeck_has_codebase: existsSync17(codebaseDirectory)
|
|
2115
2185
|
};
|
|
2116
2186
|
if (workspaceRoot && config?.sub_repos && config.sub_repos.length > 0) {
|
|
2117
2187
|
result.flowdeck_workspace_root = workspaceRoot;
|
|
@@ -2126,7 +2196,7 @@ async function sessionStartHook(ctx) {
|
|
|
2126
2196
|
flowdeck_phase: null,
|
|
2127
2197
|
flowdeck_status: "error",
|
|
2128
2198
|
flowdeck_warning: "State file unreadable. Continuing without flowdeck context.",
|
|
2129
|
-
flowdeck_has_codebase:
|
|
2199
|
+
flowdeck_has_codebase: existsSync17(codebaseDirectory)
|
|
2130
2200
|
};
|
|
2131
2201
|
if (workspaceRoot && config?.sub_repos && config.sub_repos.length > 0) {
|
|
2132
2202
|
result.flowdeck_workspace_root = workspaceRoot;
|
|
@@ -2187,13 +2257,13 @@ function tryTerminalBell() {
|
|
|
2187
2257
|
function notifySessionIdle() {
|
|
2188
2258
|
notify("FlowDeck Task Completed", "Agent is idle and waiting for your next instruction", "info");
|
|
2189
2259
|
}
|
|
2190
|
-
function notifyPermissionNeeded(
|
|
2191
|
-
notify("FlowDeck Permission Required", `Agent needs approval to use tool: ${
|
|
2260
|
+
function notifyPermissionNeeded(tool18) {
|
|
2261
|
+
notify("FlowDeck Permission Required", `Agent needs approval to use tool: ${tool18}`, "critical");
|
|
2192
2262
|
}
|
|
2193
2263
|
|
|
2194
2264
|
// src/hooks/patch-trust.ts
|
|
2195
|
-
import { existsSync as
|
|
2196
|
-
import { join as
|
|
2265
|
+
import { existsSync as existsSync18, readFileSync as readFileSync16 } from "fs";
|
|
2266
|
+
import { join as join17 } from "path";
|
|
2197
2267
|
var HIGH_RISK_KEYWORDS = [
|
|
2198
2268
|
"password",
|
|
2199
2269
|
"secret",
|
|
@@ -2215,8 +2285,8 @@ var HIGH_RISK_KEYWORDS = [
|
|
|
2215
2285
|
"privilege"
|
|
2216
2286
|
];
|
|
2217
2287
|
function loadVolatility(directory) {
|
|
2218
|
-
const p =
|
|
2219
|
-
if (!
|
|
2288
|
+
const p = join17(codebaseDir(directory), "VOLATILITY.json");
|
|
2289
|
+
if (!existsSync18(p))
|
|
2220
2290
|
return {};
|
|
2221
2291
|
try {
|
|
2222
2292
|
const data = JSON.parse(readFileSync16(p, "utf-8"));
|
|
@@ -2229,8 +2299,8 @@ function loadVolatility(directory) {
|
|
|
2229
2299
|
}
|
|
2230
2300
|
}
|
|
2231
2301
|
function loadFailedPaths(directory) {
|
|
2232
|
-
const p =
|
|
2233
|
-
if (!
|
|
2302
|
+
const p = join17(codebaseDir(directory), "FAILURES.json");
|
|
2303
|
+
if (!existsSync18(p))
|
|
2234
2304
|
return [];
|
|
2235
2305
|
try {
|
|
2236
2306
|
const data = JSON.parse(readFileSync16(p, "utf-8"));
|
|
@@ -2298,8 +2368,8 @@ async function patchTrustHook(ctx, input, output) {
|
|
|
2298
2368
|
}
|
|
2299
2369
|
|
|
2300
2370
|
// src/hooks/decision-trace-hook.ts
|
|
2301
|
-
import { existsSync as
|
|
2302
|
-
import { join as
|
|
2371
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync9, appendFileSync as appendFileSync2 } from "fs";
|
|
2372
|
+
import { join as join18 } from "path";
|
|
2303
2373
|
async function decisionTraceHook(ctx, input, output) {
|
|
2304
2374
|
if (input.tool !== "write" && input.tool !== "edit")
|
|
2305
2375
|
return;
|
|
@@ -2308,7 +2378,7 @@ async function decisionTraceHook(ctx, input, output) {
|
|
|
2308
2378
|
return;
|
|
2309
2379
|
const base = codebaseDir(ctx.directory);
|
|
2310
2380
|
try {
|
|
2311
|
-
if (!
|
|
2381
|
+
if (!existsSync19(base))
|
|
2312
2382
|
mkdirSync9(base, { recursive: true });
|
|
2313
2383
|
const entry = {
|
|
2314
2384
|
timestamp: new Date().toISOString(),
|
|
@@ -2321,23 +2391,23 @@ async function decisionTraceHook(ctx, input, output) {
|
|
|
2321
2391
|
risk_level: "unknown",
|
|
2322
2392
|
auto_recorded: true
|
|
2323
2393
|
};
|
|
2324
|
-
appendFileSync2(
|
|
2394
|
+
appendFileSync2(join18(base, "DECISIONS.jsonl"), JSON.stringify(entry) + `
|
|
2325
2395
|
`, "utf-8");
|
|
2326
2396
|
} catch {}
|
|
2327
2397
|
}
|
|
2328
2398
|
|
|
2329
2399
|
// src/services/telemetry.ts
|
|
2330
|
-
import { existsSync as
|
|
2331
|
-
import { join as
|
|
2400
|
+
import { existsSync as existsSync20, readFileSync as readFileSync17, appendFileSync as appendFileSync3, mkdirSync as mkdirSync10 } from "fs";
|
|
2401
|
+
import { join as join19 } from "path";
|
|
2332
2402
|
import { randomUUID } from "crypto";
|
|
2333
2403
|
function telemetryPath(dir) {
|
|
2334
|
-
return
|
|
2404
|
+
return join19(codebaseDir(dir), "TELEMETRY.jsonl");
|
|
2335
2405
|
}
|
|
2336
2406
|
function appendEvent(dir, partial) {
|
|
2337
2407
|
if (process.env.TELEMETRY_ENABLED !== "true")
|
|
2338
2408
|
return null;
|
|
2339
2409
|
const cd = codebaseDir(dir);
|
|
2340
|
-
if (!
|
|
2410
|
+
if (!existsSync20(cd))
|
|
2341
2411
|
mkdirSync10(cd, { recursive: true });
|
|
2342
2412
|
const event = {
|
|
2343
2413
|
id: randomUUID(),
|
|
@@ -2352,31 +2422,31 @@ function appendEvent(dir, partial) {
|
|
|
2352
2422
|
// src/hooks/telemetry-hook.ts
|
|
2353
2423
|
async function telemetryHook(context, toolInput, output) {
|
|
2354
2424
|
const dir = context.directory ?? process.cwd();
|
|
2355
|
-
const
|
|
2425
|
+
const tool18 = toolInput.name ?? toolInput.tool ?? "unknown";
|
|
2356
2426
|
appendEvent(dir, {
|
|
2357
2427
|
session_id: process.env.OPENCODE_SESSION_ID ?? "session-0",
|
|
2358
2428
|
run_id: process.env.OPENCODE_RUN_ID ?? "run-0",
|
|
2359
2429
|
event: "tool.call",
|
|
2360
|
-
tool:
|
|
2430
|
+
tool: tool18,
|
|
2361
2431
|
status: "ok",
|
|
2362
2432
|
meta: { parameters: output.args ?? {} }
|
|
2363
2433
|
});
|
|
2364
2434
|
}
|
|
2365
2435
|
async function telemetryAfterHook(context, toolInput, _output) {
|
|
2366
2436
|
const dir = context.directory ?? process.cwd();
|
|
2367
|
-
const
|
|
2437
|
+
const tool18 = toolInput.name ?? toolInput.tool ?? "unknown";
|
|
2368
2438
|
appendEvent(dir, {
|
|
2369
2439
|
session_id: process.env.OPENCODE_SESSION_ID ?? "session-0",
|
|
2370
2440
|
run_id: process.env.OPENCODE_RUN_ID ?? "run-0",
|
|
2371
2441
|
event: "tool.complete",
|
|
2372
|
-
tool:
|
|
2442
|
+
tool: tool18,
|
|
2373
2443
|
status: "ok"
|
|
2374
2444
|
});
|
|
2375
2445
|
}
|
|
2376
2446
|
|
|
2377
2447
|
// src/services/approval-manager.ts
|
|
2378
|
-
import { existsSync as
|
|
2379
|
-
import { join as
|
|
2448
|
+
import { existsSync as existsSync21, readFileSync as readFileSync18, writeFileSync as writeFileSync13, mkdirSync as mkdirSync11 } from "fs";
|
|
2449
|
+
import { join as join20 } from "path";
|
|
2380
2450
|
var APPROVAL_TTL_MS = 30 * 60 * 1000;
|
|
2381
2451
|
var SENSITIVE_PATTERNS = [
|
|
2382
2452
|
/auth/i,
|
|
@@ -2413,11 +2483,11 @@ function isSensitivePath(filePath) {
|
|
|
2413
2483
|
return SENSITIVE_PATTERNS.some((p) => p.test(filePath));
|
|
2414
2484
|
}
|
|
2415
2485
|
function approvalsPath(dir) {
|
|
2416
|
-
return
|
|
2486
|
+
return join20(codebaseDir(dir), "APPROVALS.json");
|
|
2417
2487
|
}
|
|
2418
2488
|
function loadStore(dir) {
|
|
2419
2489
|
const p = approvalsPath(dir);
|
|
2420
|
-
if (!
|
|
2490
|
+
if (!existsSync21(p))
|
|
2421
2491
|
return { requests: [] };
|
|
2422
2492
|
try {
|
|
2423
2493
|
return JSON.parse(readFileSync18(p, "utf-8"));
|
|
@@ -2438,8 +2508,8 @@ async function approvalHook(context, toolInput, output) {
|
|
|
2438
2508
|
if (!ENABLED2)
|
|
2439
2509
|
return;
|
|
2440
2510
|
const dir = context.directory ?? process.cwd();
|
|
2441
|
-
const
|
|
2442
|
-
if (!WRITE_TOOLS.has(
|
|
2511
|
+
const tool18 = toolInput.name ?? toolInput.tool ?? "";
|
|
2512
|
+
if (!WRITE_TOOLS.has(tool18))
|
|
2443
2513
|
return;
|
|
2444
2514
|
const args = output.args ?? {};
|
|
2445
2515
|
const filePath = String(args.path ?? args.file_path ?? args.filename ?? "");
|
|
@@ -2454,7 +2524,7 @@ async function approvalHook(context, toolInput, output) {
|
|
|
2454
2524
|
session_id: process.env.OPENCODE_SESSION_ID ?? "session-0",
|
|
2455
2525
|
run_id: process.env.OPENCODE_RUN_ID ?? "run-0",
|
|
2456
2526
|
event: "approval.request",
|
|
2457
|
-
tool:
|
|
2527
|
+
tool: tool18,
|
|
2458
2528
|
status: "blocked",
|
|
2459
2529
|
files: [filePath],
|
|
2460
2530
|
meta: { trigger: "sensitive_file", file: filePath }
|
|
@@ -2515,8 +2585,8 @@ function createContextWindowMonitorHook() {
|
|
|
2515
2585
|
}
|
|
2516
2586
|
|
|
2517
2587
|
// src/hooks/shell-env-hook.ts
|
|
2518
|
-
import { existsSync as
|
|
2519
|
-
import { join as
|
|
2588
|
+
import { existsSync as existsSync22, readFileSync as readFileSync19 } from "fs";
|
|
2589
|
+
import { join as join21 } from "path";
|
|
2520
2590
|
import { createRequire } from "module";
|
|
2521
2591
|
var _version;
|
|
2522
2592
|
function getVersion() {
|
|
@@ -2552,7 +2622,7 @@ var MARKER_TO_LANG = {
|
|
|
2552
2622
|
};
|
|
2553
2623
|
function detectPackageManager(root) {
|
|
2554
2624
|
for (const [lockfile, pm] of Object.entries(LOCKFILE_TO_PM)) {
|
|
2555
|
-
if (
|
|
2625
|
+
if (existsSync22(join21(root, lockfile)))
|
|
2556
2626
|
return pm;
|
|
2557
2627
|
}
|
|
2558
2628
|
return;
|
|
@@ -2561,7 +2631,7 @@ function detectLanguages(root) {
|
|
|
2561
2631
|
const langs = [];
|
|
2562
2632
|
const seen = new Set;
|
|
2563
2633
|
for (const [marker, lang] of Object.entries(MARKER_TO_LANG)) {
|
|
2564
|
-
if (!seen.has(lang) &&
|
|
2634
|
+
if (!seen.has(lang) && existsSync22(join21(root, marker))) {
|
|
2565
2635
|
langs.push(lang);
|
|
2566
2636
|
seen.add(lang);
|
|
2567
2637
|
}
|
|
@@ -2569,8 +2639,8 @@ function detectLanguages(root) {
|
|
|
2569
2639
|
return langs;
|
|
2570
2640
|
}
|
|
2571
2641
|
function readCurrentPhase(root) {
|
|
2572
|
-
const statePath2 =
|
|
2573
|
-
if (!
|
|
2642
|
+
const statePath2 = join21(root, ".planning", "STATE.md");
|
|
2643
|
+
if (!existsSync22(statePath2))
|
|
2574
2644
|
return;
|
|
2575
2645
|
try {
|
|
2576
2646
|
const content = readFileSync19(statePath2, "utf-8");
|
|
@@ -2673,8 +2743,8 @@ function createSessionIdleHook(client, tracker) {
|
|
|
2673
2743
|
}
|
|
2674
2744
|
|
|
2675
2745
|
// src/hooks/compaction-hook.ts
|
|
2676
|
-
import { existsSync as
|
|
2677
|
-
import { join as
|
|
2746
|
+
import { existsSync as existsSync23, readFileSync as readFileSync20 } from "fs";
|
|
2747
|
+
import { join as join22 } from "path";
|
|
2678
2748
|
var STRUCTURED_SUMMARY_PROMPT = `
|
|
2679
2749
|
When summarizing this session, you MUST include the following sections:
|
|
2680
2750
|
|
|
@@ -2713,8 +2783,8 @@ For each: agent name, status, description, session_id.
|
|
|
2713
2783
|
**RESUME, DON'T RESTART.** Use session_id to continue existing sessions.
|
|
2714
2784
|
`;
|
|
2715
2785
|
function readPlanningState2(directory) {
|
|
2716
|
-
const statePath2 =
|
|
2717
|
-
if (!
|
|
2786
|
+
const statePath2 = join22(directory, ".planning", "STATE.md");
|
|
2787
|
+
if (!existsSync23(statePath2))
|
|
2718
2788
|
return null;
|
|
2719
2789
|
try {
|
|
2720
2790
|
const content = readFileSync20(statePath2, "utf-8");
|
|
@@ -5810,21 +5880,21 @@ function getAgentConfigs(agentModels) {
|
|
|
5810
5880
|
}
|
|
5811
5881
|
|
|
5812
5882
|
// src/config/loader.ts
|
|
5813
|
-
import { existsSync as
|
|
5814
|
-
import { join as
|
|
5815
|
-
import { homedir as
|
|
5883
|
+
import { existsSync as existsSync24, readFileSync as readFileSync21 } from "fs";
|
|
5884
|
+
import { join as join23 } from "path";
|
|
5885
|
+
import { homedir as homedir3 } from "os";
|
|
5816
5886
|
var CONFIG_FILENAME = "flowdeck.json";
|
|
5817
5887
|
function getGlobalConfigDir() {
|
|
5818
|
-
return process.env.OPENCODE_CONFIG_DIR || (process.env.XDG_CONFIG_HOME ?
|
|
5888
|
+
return process.env.OPENCODE_CONFIG_DIR || (process.env.XDG_CONFIG_HOME ? join23(process.env.XDG_CONFIG_HOME, "opencode") : join23(homedir3(), ".config", "opencode"));
|
|
5819
5889
|
}
|
|
5820
5890
|
function loadFlowDeckConfig(directory) {
|
|
5821
5891
|
const candidates = [];
|
|
5822
5892
|
if (directory) {
|
|
5823
|
-
candidates.push(
|
|
5893
|
+
candidates.push(join23(directory, ".opencode", CONFIG_FILENAME));
|
|
5824
5894
|
}
|
|
5825
|
-
candidates.push(
|
|
5895
|
+
candidates.push(join23(getGlobalConfigDir(), CONFIG_FILENAME));
|
|
5826
5896
|
for (const configPath of candidates) {
|
|
5827
|
-
if (
|
|
5897
|
+
if (existsSync24(configPath)) {
|
|
5828
5898
|
try {
|
|
5829
5899
|
const content = readFileSync21(configPath, "utf-8");
|
|
5830
5900
|
return JSON.parse(content);
|
|
@@ -5838,13 +5908,13 @@ function loadFlowDeckConfig(directory) {
|
|
|
5838
5908
|
// src/index.ts
|
|
5839
5909
|
function loadRulePaths() {
|
|
5840
5910
|
const __dir = dirname4(fileURLToPath2(import.meta.url));
|
|
5841
|
-
const rulesDir =
|
|
5842
|
-
if (!
|
|
5911
|
+
const rulesDir = join24(__dir, "..", "src", "rules");
|
|
5912
|
+
if (!existsSync25(rulesDir))
|
|
5843
5913
|
return [];
|
|
5844
5914
|
const paths = [];
|
|
5845
5915
|
function walk(dir) {
|
|
5846
5916
|
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
5847
|
-
const full =
|
|
5917
|
+
const full = join24(dir, entry.name);
|
|
5848
5918
|
if (entry.isDirectory()) {
|
|
5849
5919
|
walk(full);
|
|
5850
5920
|
} else if (entry.isFile() && entry.name.endsWith(".md") && entry.name !== "README.md") {
|
|
@@ -5857,8 +5927,8 @@ function loadRulePaths() {
|
|
|
5857
5927
|
}
|
|
5858
5928
|
function loadCommands() {
|
|
5859
5929
|
const __dir = dirname4(fileURLToPath2(import.meta.url));
|
|
5860
|
-
const commandsDir =
|
|
5861
|
-
if (!
|
|
5930
|
+
const commandsDir = join24(__dir, "..", "src", "commands");
|
|
5931
|
+
if (!existsSync25(commandsDir))
|
|
5862
5932
|
return {};
|
|
5863
5933
|
const commands = {};
|
|
5864
5934
|
try {
|
|
@@ -5866,7 +5936,7 @@ function loadCommands() {
|
|
|
5866
5936
|
if (!file.endsWith(".md"))
|
|
5867
5937
|
continue;
|
|
5868
5938
|
const name = basename(file, ".md");
|
|
5869
|
-
const raw = readFileSync22(
|
|
5939
|
+
const raw = readFileSync22(join24(commandsDir, file), "utf-8");
|
|
5870
5940
|
let description;
|
|
5871
5941
|
let template = raw;
|
|
5872
5942
|
const fmMatch = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/);
|
|
@@ -5943,8 +6013,8 @@ var plugin = async (input, _options) => {
|
|
|
5943
6013
|
}
|
|
5944
6014
|
}
|
|
5945
6015
|
}
|
|
5946
|
-
const skillsDir =
|
|
5947
|
-
if (
|
|
6016
|
+
const skillsDir = join24(dirname4(fileURLToPath2(import.meta.url)), "..", "src", "skills");
|
|
6017
|
+
if (existsSync25(skillsDir)) {
|
|
5948
6018
|
const cfgAny = cfg;
|
|
5949
6019
|
if (!cfgAny.skills || typeof cfgAny.skills !== "object") {
|
|
5950
6020
|
cfgAny.skills = { paths: [] };
|
|
@@ -5985,7 +6055,8 @@ var plugin = async (input, _options) => {
|
|
|
5985
6055
|
"context-generator": contextGeneratorTool,
|
|
5986
6056
|
"create-skill": createSkillTool,
|
|
5987
6057
|
reflect: reflectTool,
|
|
5988
|
-
"memory-search": memorySearchTool
|
|
6058
|
+
"memory-search": memorySearchTool,
|
|
6059
|
+
"memory-status": memoryStatusTool
|
|
5989
6060
|
},
|
|
5990
6061
|
"shell.env": shellEnvHook,
|
|
5991
6062
|
"todo.updated": todoHook,
|
|
@@ -5997,30 +6068,34 @@ var plugin = async (input, _options) => {
|
|
|
5997
6068
|
},
|
|
5998
6069
|
event: async ({ event }) => {
|
|
5999
6070
|
const type = event?.type ?? "";
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
|
|
6071
|
+
try {
|
|
6072
|
+
if (type === "session.created" || type === "session.started") {
|
|
6073
|
+
const sessionId = event?.sessionID ?? event?.sessionId ?? "";
|
|
6074
|
+
if (sessionId) {
|
|
6075
|
+
memoryHook.onSessionCreated(directory, sessionId, event?.prompt);
|
|
6076
|
+
}
|
|
6077
|
+
await sessionStartHook({ directory });
|
|
6078
|
+
} else if (type === "message.updated") {
|
|
6079
|
+
const msgEvent = event?.event ?? event;
|
|
6080
|
+
const sessionId = msgEvent?.sessionID ?? msgEvent?.sessionId ?? "";
|
|
6081
|
+
if (sessionId) {
|
|
6082
|
+
memoryHook.onMessageUpdated(sessionId, msgEvent.role, msgEvent.content, directory);
|
|
6083
|
+
}
|
|
6084
|
+
} else if (type === "session.compacted") {
|
|
6085
|
+
const compactEvent = event?.event ?? event;
|
|
6086
|
+
const sessionId = compactEvent?.sessionID ?? compactEvent?.sessionId ?? "";
|
|
6087
|
+
if (sessionId) {
|
|
6088
|
+
memoryHook.onSessionCompact(sessionId, compactEvent.summary ?? "");
|
|
6089
|
+
}
|
|
6090
|
+
} else if (type === "session.deleted") {
|
|
6091
|
+
const delEvent = event?.event ?? event;
|
|
6092
|
+
const sessionId = delEvent?.sessionID ?? delEvent?.sessionId ?? "";
|
|
6093
|
+
if (sessionId) {
|
|
6094
|
+
memoryHook.clearSession(sessionId);
|
|
6095
|
+
}
|
|
6023
6096
|
}
|
|
6097
|
+
} catch (err) {
|
|
6098
|
+
console.error("[FlowDeck Memory] Event handler error:", err);
|
|
6024
6099
|
}
|
|
6025
6100
|
await contextMonitor.event({ event });
|
|
6026
6101
|
orchestratorGuard.onEvent(event);
|
|
@@ -6050,9 +6125,13 @@ var plugin = async (input, _options) => {
|
|
|
6050
6125
|
},
|
|
6051
6126
|
"tool.execute.after": async (toolInput, toolOutput) => {
|
|
6052
6127
|
await telemetryAfterHook({ directory }, toolInput, toolOutput);
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6128
|
+
try {
|
|
6129
|
+
const sessionId = toolInput?.sessionID ?? toolInput?.sessionId ?? "";
|
|
6130
|
+
if (sessionId && toolInput?.tool) {
|
|
6131
|
+
memoryHook.onToolExecuted(sessionId, toolInput.tool, toolInput, toolOutput?.output ?? null, directory);
|
|
6132
|
+
}
|
|
6133
|
+
} catch (err) {
|
|
6134
|
+
console.error("[FlowDeck Memory] Tool execution error:", err);
|
|
6056
6135
|
}
|
|
6057
6136
|
await contextMonitor["tool.execute.after"](toolInput, toolOutput);
|
|
6058
6137
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-status.d.ts","sourceRoot":"","sources":["../../src/tools/memory-status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAQ/D,eAAO,MAAM,gBAAgB,EAAE,cAoE7B,CAAA"}
|