agendex-cli 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +1371 -178
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1059,13 +1059,13 @@ var init_cleanup = __esm(() => {
1059
1059
 
1060
1060
  // src/cli.ts
1061
1061
  import { spawn as spawn3 } from "node:child_process";
1062
- import { existsSync as existsSync9, statSync as statSync2, writeSync } from "node:fs";
1063
- import { resolve as resolve6 } from "node:path";
1062
+ import { existsSync as existsSync11, statSync as statSync2, writeSync } from "node:fs";
1063
+ import { resolve as resolve7 } from "node:path";
1064
1064
  import { fileURLToPath as fileURLToPath2 } from "node:url";
1065
1065
 
1066
1066
  // ../shared/src/adapters/catalog.ts
1067
- import { homedir as homedir6 } from "node:os";
1068
- import { join as join6 } from "node:path";
1067
+ import { homedir as homedir7 } from "node:os";
1068
+ import { join as join7 } from "node:path";
1069
1069
 
1070
1070
  // ../shared/src/adapters/claude-code.ts
1071
1071
  import { readFile, stat, writeFile } from "node:fs/promises";
@@ -1748,6 +1748,453 @@ var ohMyOpencodeAdapter = {
1748
1748
  }
1749
1749
  };
1750
1750
 
1751
+ // ../shared/src/adapters/plannotator.ts
1752
+ import { existsSync as existsSync3 } from "node:fs";
1753
+ import { readFile as readFile6, stat as stat6 } from "node:fs/promises";
1754
+ import { homedir as homedir6 } from "node:os";
1755
+ import { basename as basename5, dirname, join as join6, resolve as resolve2, sep as sep2 } from "node:path";
1756
+ var REQUEST_TIMEOUT_MS = 5000;
1757
+ var PROJECT_PLANS_DIRNAME = "@plans";
1758
+ function getPlannotatorDir() {
1759
+ return process.env.AGENDEX_PLANNOTATOR_DIR || join6(homedir6(), ".plannotator");
1760
+ }
1761
+ function getPlansDir() {
1762
+ return join6(getPlannotatorDir(), "plans");
1763
+ }
1764
+ function getSessionsDir() {
1765
+ return join6(getPlannotatorDir(), "sessions");
1766
+ }
1767
+ function isRecord3(value) {
1768
+ return typeof value === "object" && value !== null;
1769
+ }
1770
+ function normalizeStatusFromFilename(filePath) {
1771
+ const name = basename5(filePath, ".md");
1772
+ if (name.endsWith("-approved"))
1773
+ return "approved";
1774
+ if (name.endsWith("-denied"))
1775
+ return "denied";
1776
+ return "unknown";
1777
+ }
1778
+ function stripStatusSuffix(title) {
1779
+ return title.replace(/-(approved|denied)$/i, "");
1780
+ }
1781
+ function cleanTitle2(title) {
1782
+ return title.replace(/\d{4}-\d{2}-\d{2}/g, "").replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim();
1783
+ }
1784
+ function extractTitle5(content, filePath) {
1785
+ const heading = content.match(/^#\s+(.+)$/m)?.[1]?.trim();
1786
+ if (heading)
1787
+ return heading.replace(/^Plan:\s*/i, "").trim();
1788
+ const name = stripStatusSuffix(basename5(filePath, ".md"));
1789
+ return cleanTitle2(name) || "Plannotator Plan";
1790
+ }
1791
+ function isAnnotationFile(filePath) {
1792
+ return filePath.endsWith(".annotations.md");
1793
+ }
1794
+ function isSnapshotFile(filePath) {
1795
+ if (!filePath.endsWith(".md"))
1796
+ return false;
1797
+ if (isAnnotationFile(filePath))
1798
+ return false;
1799
+ const status = normalizeStatusFromFilename(filePath);
1800
+ return status === "approved" || status === "denied";
1801
+ }
1802
+ function isProjectPlansPath(filePath) {
1803
+ return resolve2(filePath).split(sep2).includes(PROJECT_PLANS_DIRNAME);
1804
+ }
1805
+ function projectPlansDirForPath(filePath) {
1806
+ const parts = resolve2(filePath).split(sep2);
1807
+ const index = parts.lastIndexOf(PROJECT_PLANS_DIRNAME);
1808
+ if (index === -1)
1809
+ return;
1810
+ return parts.slice(0, index + 1).join(sep2) || sep2;
1811
+ }
1812
+ function isProjectPlanFile(filePath) {
1813
+ return filePath.endsWith(".md") && !isAnnotationFile(filePath) && isProjectPlansPath(filePath);
1814
+ }
1815
+ function isSessionFile2(filePath) {
1816
+ return filePath.endsWith(".json");
1817
+ }
1818
+ function isAlive(pid) {
1819
+ try {
1820
+ process.kill(pid, 0);
1821
+ return true;
1822
+ } catch {
1823
+ return false;
1824
+ }
1825
+ }
1826
+ function isSafePlannotatorUrl(rawUrl) {
1827
+ try {
1828
+ const url = new URL(rawUrl);
1829
+ if (url.protocol !== "http:")
1830
+ return false;
1831
+ if (url.username || url.password)
1832
+ return false;
1833
+ if (url.pathname !== "/" && url.pathname !== "")
1834
+ return false;
1835
+ if (url.search || url.hash)
1836
+ return false;
1837
+ const hostname = url.hostname.toLowerCase();
1838
+ if (hostname === "localhost")
1839
+ return true;
1840
+ if (hostname === "127.0.0.1")
1841
+ return true;
1842
+ if (hostname === "::1" || hostname === "[::1]")
1843
+ return true;
1844
+ return false;
1845
+ } catch {
1846
+ return false;
1847
+ }
1848
+ }
1849
+ function apiUrl(baseUrl, path) {
1850
+ const url = new URL(baseUrl);
1851
+ url.pathname = path;
1852
+ url.search = "";
1853
+ url.hash = "";
1854
+ return url.toString();
1855
+ }
1856
+ async function fetchJson(url, init) {
1857
+ const controller = new AbortController;
1858
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
1859
+ try {
1860
+ const res = await fetch(url, {
1861
+ ...init,
1862
+ redirect: "error",
1863
+ signal: controller.signal
1864
+ });
1865
+ if (!res.ok)
1866
+ return null;
1867
+ return await res.json();
1868
+ } catch {
1869
+ return null;
1870
+ } finally {
1871
+ clearTimeout(timer);
1872
+ }
1873
+ }
1874
+ async function postJson(url, body) {
1875
+ const controller = new AbortController;
1876
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
1877
+ try {
1878
+ const res = await fetch(url, {
1879
+ method: "POST",
1880
+ headers: { "Content-Type": "application/json" },
1881
+ body: JSON.stringify(body),
1882
+ redirect: "error",
1883
+ signal: controller.signal
1884
+ });
1885
+ return res.ok;
1886
+ } catch {
1887
+ return false;
1888
+ } finally {
1889
+ clearTimeout(timer);
1890
+ }
1891
+ }
1892
+ function normalizeMode(value) {
1893
+ if (value === "plan" || value === "review" || value === "annotate" || value === "archive") {
1894
+ return value;
1895
+ }
1896
+ return;
1897
+ }
1898
+ function parseSessionInfo(raw) {
1899
+ if (!isRecord3(raw))
1900
+ return null;
1901
+ const pid = typeof raw.pid === "number" && Number.isFinite(raw.pid) ? raw.pid : undefined;
1902
+ const port = typeof raw.port === "number" && Number.isFinite(raw.port) ? raw.port : undefined;
1903
+ const url = typeof raw.url === "string" ? raw.url : undefined;
1904
+ if (!pid || !url)
1905
+ return null;
1906
+ return {
1907
+ pid,
1908
+ port,
1909
+ url,
1910
+ mode: normalizeMode(raw.mode),
1911
+ project: typeof raw.project === "string" ? raw.project : undefined,
1912
+ startedAt: typeof raw.startedAt === "string" ? raw.startedAt : undefined,
1913
+ label: typeof raw.label === "string" ? raw.label : undefined,
1914
+ origin: typeof raw.origin === "string" ? raw.origin : undefined,
1915
+ reviewId: typeof raw.reviewId === "string" ? raw.reviewId : undefined,
1916
+ sourcePlanPath: typeof raw.sourcePlanPath === "string" ? raw.sourcePlanPath : undefined
1917
+ };
1918
+ }
1919
+ function parseDate2(value) {
1920
+ if (!value)
1921
+ return;
1922
+ const date = new Date(value);
1923
+ if (Number.isNaN(date.getTime()))
1924
+ return;
1925
+ return date;
1926
+ }
1927
+ function metadataRecord(metadata) {
1928
+ return {
1929
+ source: "plannotator",
1930
+ sourceAdapter: "plannotator",
1931
+ plannotator: metadata
1932
+ };
1933
+ }
1934
+ function annotationsPathForSnapshot(filePath) {
1935
+ const snapshotBase = basename5(filePath, ".md").replace(/-(approved|denied)$/i, "");
1936
+ return join6(dirname(filePath), `${snapshotBase}.annotations.md`);
1937
+ }
1938
+ function snapshotPathsForAnnotation(filePath) {
1939
+ const base = basename5(filePath, ".annotations.md");
1940
+ return [
1941
+ join6(dirname(filePath), `${base}-approved.md`),
1942
+ join6(dirname(filePath), `${base}-denied.md`)
1943
+ ];
1944
+ }
1945
+ function projectPlanPathsForAnnotation(filePath) {
1946
+ const base = basename5(filePath, ".annotations.md");
1947
+ return [join6(dirname(filePath), `${base}.md`), ...snapshotPathsForAnnotation(filePath)];
1948
+ }
1949
+ async function countAnnotationHeadings(filePath) {
1950
+ if (!existsSync3(filePath))
1951
+ return;
1952
+ try {
1953
+ const raw = await readFile6(filePath, "utf-8");
1954
+ const headings = raw.match(/^#{1,6}\s+/gm);
1955
+ return headings?.length ?? (raw.trim() ? 1 : 0);
1956
+ } catch {
1957
+ return;
1958
+ }
1959
+ }
1960
+ async function parseSnapshot(filePath) {
1961
+ try {
1962
+ const content = await readFile6(filePath, "utf-8");
1963
+ const stats = await stat6(filePath);
1964
+ const status = normalizeStatusFromFilename(filePath);
1965
+ const annotationsPath = annotationsPathForSnapshot(filePath);
1966
+ const annotationCount = await countAnnotationHeadings(annotationsPath);
1967
+ const metadata = {
1968
+ kind: "snapshot",
1969
+ mode: "plan",
1970
+ status,
1971
+ annotationsPath: existsSync3(annotationsPath) ? annotationsPath : undefined,
1972
+ annotationCount,
1973
+ writebackCapable: false
1974
+ };
1975
+ return [
1976
+ {
1977
+ id: hashPath(filePath),
1978
+ agent: "plannotator",
1979
+ title: extractTitle5(content, filePath),
1980
+ content,
1981
+ filePath,
1982
+ format: "md",
1983
+ createdAt: stats.birthtime,
1984
+ updatedAt: stats.mtime,
1985
+ metadata: metadataRecord(metadata)
1986
+ }
1987
+ ];
1988
+ } catch {
1989
+ return [];
1990
+ }
1991
+ }
1992
+ async function parseAnnotationCompanion(filePath) {
1993
+ const plans = [];
1994
+ for (const snapshotPath of snapshotPathsForAnnotation(filePath)) {
1995
+ if (!existsSync3(snapshotPath))
1996
+ continue;
1997
+ plans.push(...await parseSnapshot(snapshotPath));
1998
+ }
1999
+ return plans;
2000
+ }
2001
+ async function parseProjectPlan(filePath) {
2002
+ try {
2003
+ const content = await readFile6(filePath, "utf-8");
2004
+ const stats = await stat6(filePath);
2005
+ const projectPlansDir = projectPlansDirForPath(filePath);
2006
+ const projectRoot = projectPlansDir ? dirname(projectPlansDir) : undefined;
2007
+ const status = normalizeStatusFromFilename(filePath);
2008
+ const annotationsPath = annotationsPathForSnapshot(filePath);
2009
+ const annotationCount = await countAnnotationHeadings(annotationsPath);
2010
+ const metadata = {
2011
+ kind: "project-plan",
2012
+ mode: "plan",
2013
+ status,
2014
+ annotationsPath: existsSync3(annotationsPath) ? annotationsPath : undefined,
2015
+ annotationCount,
2016
+ sourcePlanPath: filePath,
2017
+ project: projectRoot ? basename5(projectRoot) : undefined,
2018
+ writebackCapable: false
2019
+ };
2020
+ return [
2021
+ {
2022
+ id: hashPath(filePath),
2023
+ agent: "plannotator",
2024
+ title: extractTitle5(content, filePath),
2025
+ content,
2026
+ filePath,
2027
+ format: "md",
2028
+ createdAt: stats.birthtime,
2029
+ updatedAt: stats.mtime,
2030
+ workspace: projectRoot,
2031
+ metadata: metadataRecord(metadata)
2032
+ }
2033
+ ];
2034
+ } catch {
2035
+ return [];
2036
+ }
2037
+ }
2038
+ async function parseProjectAnnotationCompanion(filePath) {
2039
+ const plans = [];
2040
+ const seen = new Set;
2041
+ for (const planPath of projectPlanPathsForAnnotation(filePath)) {
2042
+ const resolved = resolve2(planPath);
2043
+ if (seen.has(resolved))
2044
+ continue;
2045
+ seen.add(resolved);
2046
+ if (!existsSync3(planPath))
2047
+ continue;
2048
+ plans.push(...await parseProjectPlan(planPath));
2049
+ }
2050
+ return plans;
2051
+ }
2052
+ async function parseLiveSession(filePath) {
2053
+ try {
2054
+ const raw = JSON.parse(await readFile6(filePath, "utf-8"));
2055
+ const session = parseSessionInfo(raw);
2056
+ if (!session?.pid || !session.url)
2057
+ return [];
2058
+ if (!isAlive(session.pid))
2059
+ return [];
2060
+ if (!isSafePlannotatorUrl(session.url))
2061
+ return [];
2062
+ const mode = session.mode ?? "plan";
2063
+ if (mode === "archive")
2064
+ return [];
2065
+ const planResponse = await fetchJson(apiUrl(session.url, "/api/plan"));
2066
+ if (!planResponse?.plan)
2067
+ return [];
2068
+ const stats = await stat6(filePath);
2069
+ const createdAt = parseDate2(session.startedAt) ?? stats.birthtime;
2070
+ const origin = planResponse.origin ?? session.origin;
2071
+ const responseMode = normalizeMode(planResponse.mode) ?? mode;
2072
+ const workspace = planResponse.projectRoot;
2073
+ const sourcePlanPath = session.sourcePlanPath ?? planResponse.filePath;
2074
+ const metadata = {
2075
+ kind: "live-session",
2076
+ mode: responseMode,
2077
+ status: "pending",
2078
+ origin,
2079
+ url: session.url,
2080
+ pid: session.pid,
2081
+ port: session.port,
2082
+ project: session.project ?? planResponse.versionInfo?.project,
2083
+ label: session.label,
2084
+ reviewId: session.reviewId,
2085
+ sessionPath: filePath,
2086
+ sourcePlanPath,
2087
+ startedAt: session.startedAt,
2088
+ writebackCapable: true
2089
+ };
2090
+ return [
2091
+ {
2092
+ id: hashPath(filePath),
2093
+ agent: origin ?? "plannotator",
2094
+ title: extractTitle5(planResponse.plan, session.label ?? filePath),
2095
+ content: planResponse.plan,
2096
+ filePath: sourcePlanPath ?? filePath,
2097
+ format: "md",
2098
+ createdAt,
2099
+ updatedAt: stats.mtime,
2100
+ workspace,
2101
+ metadata: metadataRecord(metadata)
2102
+ }
2103
+ ];
2104
+ } catch {
2105
+ return [];
2106
+ }
2107
+ }
2108
+ function getPlannotatorMetadata(plan) {
2109
+ const metadata = plan.metadata.plannotator;
2110
+ if (!isRecord3(metadata))
2111
+ return;
2112
+ return metadata;
2113
+ }
2114
+ function formatWritebackFeedback(_plan, payload) {
2115
+ const sections = ["# Agendex Plan Feedback", "The user reviewed this plan in Agendex Cloud."];
2116
+ if (payload.feedback.trim()) {
2117
+ sections.push("## Feedback", payload.feedback.trim());
2118
+ }
2119
+ if (payload.revisedContent?.trim()) {
2120
+ sections.push("## Requested revision", "Use this revised plan content as the target shape when you update and resubmit the plan:", payload.revisedContent.trim());
2121
+ }
2122
+ if (payload.annotations?.length) {
2123
+ sections.push("## Typed annotations", JSON.stringify(payload.annotations, null, 2));
2124
+ }
2125
+ sections.push("Please revise the plan and resubmit it for Plannotator review.");
2126
+ return sections.join(`
2127
+
2128
+ `);
2129
+ }
2130
+ function annotationsForEndpoint(annotations) {
2131
+ return annotations ?? [];
2132
+ }
2133
+ var plannotatorAdapter = {
2134
+ agent: "plannotator",
2135
+ writable: false,
2136
+ getSearchPaths() {
2137
+ return [getPlansDir(), getSessionsDir()];
2138
+ },
2139
+ getWatchPaths() {
2140
+ return [getPlansDir(), getSessionsDir()];
2141
+ },
2142
+ matches(filePath) {
2143
+ const normalized = resolve2(filePath);
2144
+ const plansDir2 = resolve2(getPlansDir());
2145
+ const sessionsDir2 = resolve2(getSessionsDir());
2146
+ if (normalized.startsWith(plansDir2 + sep2))
2147
+ return isSnapshotFile(filePath) || isAnnotationFile(filePath);
2148
+ if (normalized.startsWith(sessionsDir2 + sep2))
2149
+ return isSessionFile2(filePath);
2150
+ if (isProjectPlansPath(filePath))
2151
+ return isProjectPlanFile(filePath) || isAnnotationFile(filePath);
2152
+ return false;
2153
+ },
2154
+ async parse(filePath) {
2155
+ const normalized = resolve2(filePath);
2156
+ const plansDir2 = resolve2(getPlansDir());
2157
+ const sessionsDir2 = resolve2(getSessionsDir());
2158
+ if (normalized.startsWith(plansDir2 + sep2) && isSnapshotFile(filePath)) {
2159
+ return await parseSnapshot(filePath);
2160
+ }
2161
+ if (normalized.startsWith(plansDir2 + sep2) && isAnnotationFile(filePath)) {
2162
+ return await parseAnnotationCompanion(filePath);
2163
+ }
2164
+ if (normalized.startsWith(sessionsDir2 + sep2) && isSessionFile2(filePath)) {
2165
+ return await parseLiveSession(filePath);
2166
+ }
2167
+ if (isProjectPlanFile(filePath)) {
2168
+ return await parseProjectPlan(filePath);
2169
+ }
2170
+ if (isProjectPlansPath(filePath) && isAnnotationFile(filePath)) {
2171
+ return await parseProjectAnnotationCompanion(filePath);
2172
+ }
2173
+ return [];
2174
+ },
2175
+ async write() {
2176
+ return false;
2177
+ },
2178
+ async requestChanges(plan, payload) {
2179
+ const metadata = getPlannotatorMetadata(plan);
2180
+ if (!metadata?.url || !metadata.writebackCapable)
2181
+ return false;
2182
+ if (!isSafePlannotatorUrl(metadata.url))
2183
+ return false;
2184
+ const feedback = formatWritebackFeedback(plan, payload);
2185
+ if (metadata.mode === "review" || metadata.mode === "annotate") {
2186
+ return await postJson(apiUrl(metadata.url, "/api/feedback"), {
2187
+ feedback,
2188
+ annotations: annotationsForEndpoint(payload.annotations)
2189
+ });
2190
+ }
2191
+ return await postJson(apiUrl(metadata.url, "/api/deny"), {
2192
+ feedback,
2193
+ planSave: { enabled: true }
2194
+ });
2195
+ }
2196
+ };
2197
+
1751
2198
  // ../shared/src/adapters/stub.ts
1752
2199
  function createStubAdapter(agent, searchPaths, matchExt) {
1753
2200
  return {
@@ -1762,7 +2209,7 @@ function createStubAdapter(agent, searchPaths, matchExt) {
1762
2209
  }
1763
2210
 
1764
2211
  // ../shared/src/adapters/catalog.ts
1765
- var home = homedir6();
2212
+ var home = homedir7();
1766
2213
  var ADAPTER_AGENT_ALIASES = {
1767
2214
  amp: "amp",
1768
2215
  antigravity: "antigravity",
@@ -1792,6 +2239,7 @@ var ADAPTER_AGENT_ALIASES = {
1792
2239
  opencode: "oh-my-opencode",
1793
2240
  openhands: "openhands",
1794
2241
  pi: "pi",
2242
+ plannotator: "plannotator",
1795
2243
  qoder: "qoder",
1796
2244
  "qwen-code": "qwen-code",
1797
2245
  replit: "replit",
@@ -1812,7 +2260,7 @@ var CATALOG = [
1812
2260
  group: "universal",
1813
2261
  implemented: false,
1814
2262
  defaultEnabled: true,
1815
- createAdapter: () => createStubAdapter("amp", [join6(home, ".amp")], ".json")
2263
+ createAdapter: () => createStubAdapter("amp", [join7(home, ".amp")], ".json")
1816
2264
  },
1817
2265
  {
1818
2266
  id: "antigravity",
@@ -1820,7 +2268,7 @@ var CATALOG = [
1820
2268
  group: "other",
1821
2269
  implemented: false,
1822
2270
  defaultEnabled: false,
1823
- createAdapter: () => createStubAdapter("antigravity", [join6(home, ".gemini", "antigravity")], ".json")
2271
+ createAdapter: () => createStubAdapter("antigravity", [join7(home, ".gemini", "antigravity")], ".json")
1824
2272
  },
1825
2273
  {
1826
2274
  id: "augment",
@@ -1828,7 +2276,7 @@ var CATALOG = [
1828
2276
  group: "other",
1829
2277
  implemented: false,
1830
2278
  defaultEnabled: false,
1831
- createAdapter: () => createStubAdapter("augment", [join6(home, ".augment")], ".json")
2279
+ createAdapter: () => createStubAdapter("augment", [join7(home, ".augment")], ".json")
1832
2280
  },
1833
2281
  {
1834
2282
  id: "claude-code",
@@ -1844,7 +2292,7 @@ var CATALOG = [
1844
2292
  group: "other",
1845
2293
  implemented: false,
1846
2294
  defaultEnabled: false,
1847
- createAdapter: () => createStubAdapter("openclaw", [join6(home, ".openclaw")], ".md")
2295
+ createAdapter: () => createStubAdapter("openclaw", [join7(home, ".openclaw")], ".md")
1848
2296
  },
1849
2297
  {
1850
2298
  id: "cline",
@@ -1853,8 +2301,8 @@ var CATALOG = [
1853
2301
  implemented: false,
1854
2302
  defaultEnabled: true,
1855
2303
  createAdapter: () => createStubAdapter("cline", [
1856
- join6(home, "AppData", "Roaming", "Code", "User", "globalStorage", "saoudrizwan.claude-dev"),
1857
- join6(home, ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev")
2304
+ join7(home, "AppData", "Roaming", "Code", "User", "globalStorage", "saoudrizwan.claude-dev"),
2305
+ join7(home, ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev")
1858
2306
  ], ".json")
1859
2307
  },
1860
2308
  {
@@ -1863,7 +2311,7 @@ var CATALOG = [
1863
2311
  group: "other",
1864
2312
  implemented: false,
1865
2313
  defaultEnabled: false,
1866
- createAdapter: () => createStubAdapter("codebuddy", [join6(home, ".codebuddy")], ".md")
2314
+ createAdapter: () => createStubAdapter("codebuddy", [join7(home, ".codebuddy")], ".md")
1867
2315
  },
1868
2316
  {
1869
2317
  id: "codex",
@@ -1879,7 +2327,7 @@ var CATALOG = [
1879
2327
  group: "other",
1880
2328
  implemented: false,
1881
2329
  defaultEnabled: false,
1882
- createAdapter: () => createStubAdapter("command-code", [join6(home, ".commandcode")], ".md")
2330
+ createAdapter: () => createStubAdapter("command-code", [join7(home, ".commandcode")], ".md")
1883
2331
  },
1884
2332
  {
1885
2333
  id: "continue",
@@ -1895,7 +2343,7 @@ var CATALOG = [
1895
2343
  group: "other",
1896
2344
  implemented: false,
1897
2345
  defaultEnabled: false,
1898
- createAdapter: () => createStubAdapter("crush", [join6(home, ".config", "crush")], ".json")
2346
+ createAdapter: () => createStubAdapter("crush", [join7(home, ".config", "crush")], ".json")
1899
2347
  },
1900
2348
  {
1901
2349
  id: "cursor",
@@ -1911,7 +2359,7 @@ var CATALOG = [
1911
2359
  group: "other",
1912
2360
  implemented: false,
1913
2361
  defaultEnabled: true,
1914
- createAdapter: () => createStubAdapter("droid", [join6(home, ".factory", "droids")], ".md")
2362
+ createAdapter: () => createStubAdapter("droid", [join7(home, ".factory", "droids")], ".md")
1915
2363
  },
1916
2364
  {
1917
2365
  id: "gemini-cli",
@@ -1919,7 +2367,7 @@ var CATALOG = [
1919
2367
  group: "universal",
1920
2368
  implemented: false,
1921
2369
  defaultEnabled: false,
1922
- createAdapter: () => createStubAdapter("gemini-cli", [join6(home, ".gemini")], ".md")
2370
+ createAdapter: () => createStubAdapter("gemini-cli", [join7(home, ".gemini")], ".md")
1923
2371
  },
1924
2372
  {
1925
2373
  id: "github-copilot",
@@ -1927,7 +2375,7 @@ var CATALOG = [
1927
2375
  group: "universal",
1928
2376
  implemented: false,
1929
2377
  defaultEnabled: true,
1930
- createAdapter: () => createStubAdapter("copilot-chat", [join6(home, ".vscode", "User", "workspaceStorage")], ".json")
2378
+ createAdapter: () => createStubAdapter("copilot-chat", [join7(home, ".vscode", "User", "workspaceStorage")], ".json")
1931
2379
  },
1932
2380
  {
1933
2381
  id: "goose",
@@ -1935,7 +2383,7 @@ var CATALOG = [
1935
2383
  group: "other",
1936
2384
  implemented: false,
1937
2385
  defaultEnabled: false,
1938
- createAdapter: () => createStubAdapter("goose", [join6(home, ".config", "goose")], ".json")
2386
+ createAdapter: () => createStubAdapter("goose", [join7(home, ".config", "goose")], ".json")
1939
2387
  },
1940
2388
  {
1941
2389
  id: "junie",
@@ -1943,7 +2391,7 @@ var CATALOG = [
1943
2391
  group: "other",
1944
2392
  implemented: false,
1945
2393
  defaultEnabled: false,
1946
- createAdapter: () => createStubAdapter("junie", [join6(home, ".junie")], ".json")
2394
+ createAdapter: () => createStubAdapter("junie", [join7(home, ".junie")], ".json")
1947
2395
  },
1948
2396
  {
1949
2397
  id: "iflow-cli",
@@ -1951,7 +2399,7 @@ var CATALOG = [
1951
2399
  group: "other",
1952
2400
  implemented: false,
1953
2401
  defaultEnabled: false,
1954
- createAdapter: () => createStubAdapter("iflow-cli", [join6(home, ".iflow")], ".json")
2402
+ createAdapter: () => createStubAdapter("iflow-cli", [join7(home, ".iflow")], ".json")
1955
2403
  },
1956
2404
  {
1957
2405
  id: "kilo",
@@ -1959,7 +2407,7 @@ var CATALOG = [
1959
2407
  group: "other",
1960
2408
  implemented: false,
1961
2409
  defaultEnabled: true,
1962
- createAdapter: () => createStubAdapter("kilo-cli", [join6(home, ".kilo")], ".md")
2410
+ createAdapter: () => createStubAdapter("kilo-cli", [join7(home, ".kilo")], ".md")
1963
2411
  },
1964
2412
  {
1965
2413
  id: "kimi-cli",
@@ -1967,7 +2415,7 @@ var CATALOG = [
1967
2415
  group: "universal",
1968
2416
  implemented: false,
1969
2417
  defaultEnabled: false,
1970
- createAdapter: () => createStubAdapter("kimi-cli", [join6(home, ".kimi")], ".md")
2418
+ createAdapter: () => createStubAdapter("kimi-cli", [join7(home, ".kimi")], ".md")
1971
2419
  },
1972
2420
  {
1973
2421
  id: "kiro-cli",
@@ -1975,7 +2423,7 @@ var CATALOG = [
1975
2423
  group: "other",
1976
2424
  implemented: false,
1977
2425
  defaultEnabled: false,
1978
- createAdapter: () => createStubAdapter("kiro-cli", [join6(home, ".kiro")], ".json")
2426
+ createAdapter: () => createStubAdapter("kiro-cli", [join7(home, ".kiro")], ".json")
1979
2427
  },
1980
2428
  {
1981
2429
  id: "kode",
@@ -1983,7 +2431,7 @@ var CATALOG = [
1983
2431
  group: "other",
1984
2432
  implemented: false,
1985
2433
  defaultEnabled: false,
1986
- createAdapter: () => createStubAdapter("kode", [join6(home, ".kode")], ".json")
2434
+ createAdapter: () => createStubAdapter("kode", [join7(home, ".kode")], ".json")
1987
2435
  },
1988
2436
  {
1989
2437
  id: "mcpjam",
@@ -1991,7 +2439,7 @@ var CATALOG = [
1991
2439
  group: "other",
1992
2440
  implemented: false,
1993
2441
  defaultEnabled: false,
1994
- createAdapter: () => createStubAdapter("mcpjam", [join6(home, ".mcpjam")], ".json")
2442
+ createAdapter: () => createStubAdapter("mcpjam", [join7(home, ".mcpjam")], ".json")
1995
2443
  },
1996
2444
  {
1997
2445
  id: "mistral-vibe",
@@ -1999,7 +2447,7 @@ var CATALOG = [
1999
2447
  group: "other",
2000
2448
  implemented: false,
2001
2449
  defaultEnabled: false,
2002
- createAdapter: () => createStubAdapter("mistral-vibe", [join6(home, ".vibe")], ".json")
2450
+ createAdapter: () => createStubAdapter("mistral-vibe", [join7(home, ".vibe")], ".json")
2003
2451
  },
2004
2452
  {
2005
2453
  id: "mux",
@@ -2007,7 +2455,7 @@ var CATALOG = [
2007
2455
  group: "other",
2008
2456
  implemented: false,
2009
2457
  defaultEnabled: false,
2010
- createAdapter: () => createStubAdapter("mux", [join6(home, ".mux")], ".json")
2458
+ createAdapter: () => createStubAdapter("mux", [join7(home, ".mux")], ".json")
2011
2459
  },
2012
2460
  {
2013
2461
  id: "opencode",
@@ -2023,7 +2471,7 @@ var CATALOG = [
2023
2471
  group: "other",
2024
2472
  implemented: false,
2025
2473
  defaultEnabled: false,
2026
- createAdapter: () => createStubAdapter("openhands", [join6(home, ".openhands")], ".json")
2474
+ createAdapter: () => createStubAdapter("openhands", [join7(home, ".openhands")], ".json")
2027
2475
  },
2028
2476
  {
2029
2477
  id: "pi",
@@ -2031,7 +2479,15 @@ var CATALOG = [
2031
2479
  group: "other",
2032
2480
  implemented: false,
2033
2481
  defaultEnabled: false,
2034
- createAdapter: () => createStubAdapter("pi", [join6(home, ".pi", "agent")], ".md")
2482
+ createAdapter: () => createStubAdapter("pi", [join7(home, ".pi", "agent")], ".md")
2483
+ },
2484
+ {
2485
+ id: "plannotator",
2486
+ displayName: "Plannotator",
2487
+ group: "universal",
2488
+ implemented: true,
2489
+ defaultEnabled: false,
2490
+ createAdapter: () => plannotatorAdapter
2035
2491
  },
2036
2492
  {
2037
2493
  id: "qoder",
@@ -2039,7 +2495,7 @@ var CATALOG = [
2039
2495
  group: "other",
2040
2496
  implemented: false,
2041
2497
  defaultEnabled: false,
2042
- createAdapter: () => createStubAdapter("qoder", [join6(home, ".qoder")], ".json")
2498
+ createAdapter: () => createStubAdapter("qoder", [join7(home, ".qoder")], ".json")
2043
2499
  },
2044
2500
  {
2045
2501
  id: "qwen-code",
@@ -2047,7 +2503,7 @@ var CATALOG = [
2047
2503
  group: "other",
2048
2504
  implemented: false,
2049
2505
  defaultEnabled: false,
2050
- createAdapter: () => createStubAdapter("qwen-code", [join6(home, ".qwen")], ".json")
2506
+ createAdapter: () => createStubAdapter("qwen-code", [join7(home, ".qwen")], ".json")
2051
2507
  },
2052
2508
  {
2053
2509
  id: "replit",
@@ -2055,7 +2511,7 @@ var CATALOG = [
2055
2511
  group: "other",
2056
2512
  implemented: false,
2057
2513
  defaultEnabled: false,
2058
- createAdapter: () => createStubAdapter("replit", [join6(home, ".replit")], ".md")
2514
+ createAdapter: () => createStubAdapter("replit", [join7(home, ".replit")], ".md")
2059
2515
  },
2060
2516
  {
2061
2517
  id: "roo",
@@ -2063,7 +2519,7 @@ var CATALOG = [
2063
2519
  group: "other",
2064
2520
  implemented: false,
2065
2521
  defaultEnabled: false,
2066
- createAdapter: () => createStubAdapter("roo", [join6(home, ".roo")], ".md")
2522
+ createAdapter: () => createStubAdapter("roo", [join7(home, ".roo")], ".md")
2067
2523
  },
2068
2524
  {
2069
2525
  id: "trae",
@@ -2071,7 +2527,7 @@ var CATALOG = [
2071
2527
  group: "other",
2072
2528
  implemented: false,
2073
2529
  defaultEnabled: false,
2074
- createAdapter: () => createStubAdapter("trae", [join6(home, ".trae")], ".md")
2530
+ createAdapter: () => createStubAdapter("trae", [join7(home, ".trae")], ".md")
2075
2531
  },
2076
2532
  {
2077
2533
  id: "trae-cn",
@@ -2079,7 +2535,7 @@ var CATALOG = [
2079
2535
  group: "other",
2080
2536
  implemented: false,
2081
2537
  defaultEnabled: false,
2082
- createAdapter: () => createStubAdapter("trae-cn", [join6(home, ".trae-cn")], ".md")
2538
+ createAdapter: () => createStubAdapter("trae-cn", [join7(home, ".trae-cn")], ".md")
2083
2539
  },
2084
2540
  {
2085
2541
  id: "windsurf",
@@ -2087,7 +2543,7 @@ var CATALOG = [
2087
2543
  group: "other",
2088
2544
  implemented: false,
2089
2545
  defaultEnabled: true,
2090
- createAdapter: () => createStubAdapter("windsurf", [join6(home, ".cascade_backups")], ".md")
2546
+ createAdapter: () => createStubAdapter("windsurf", [join7(home, ".cascade_backups")], ".md")
2091
2547
  },
2092
2548
  {
2093
2549
  id: "zencoder",
@@ -2095,7 +2551,7 @@ var CATALOG = [
2095
2551
  group: "other",
2096
2552
  implemented: false,
2097
2553
  defaultEnabled: false,
2098
- createAdapter: () => createStubAdapter("zencoder", [join6(home, ".zencoder")], ".json")
2554
+ createAdapter: () => createStubAdapter("zencoder", [join7(home, ".zencoder")], ".json")
2099
2555
  },
2100
2556
  {
2101
2557
  id: "neovate",
@@ -2103,7 +2559,7 @@ var CATALOG = [
2103
2559
  group: "other",
2104
2560
  implemented: false,
2105
2561
  defaultEnabled: false,
2106
- createAdapter: () => createStubAdapter("neovate", [join6(home, ".neovate")], ".json")
2562
+ createAdapter: () => createStubAdapter("neovate", [join7(home, ".neovate")], ".json")
2107
2563
  },
2108
2564
  {
2109
2565
  id: "pochi",
@@ -2111,7 +2567,7 @@ var CATALOG = [
2111
2567
  group: "other",
2112
2568
  implemented: false,
2113
2569
  defaultEnabled: false,
2114
- createAdapter: () => createStubAdapter("pochi", [join6(home, ".pochi")], ".json")
2570
+ createAdapter: () => createStubAdapter("pochi", [join7(home, ".pochi")], ".json")
2115
2571
  },
2116
2572
  {
2117
2573
  id: "adal",
@@ -2119,7 +2575,7 @@ var CATALOG = [
2119
2575
  group: "other",
2120
2576
  implemented: false,
2121
2577
  defaultEnabled: false,
2122
- createAdapter: () => createStubAdapter("adal", [join6(home, ".adal")], ".json")
2578
+ createAdapter: () => createStubAdapter("adal", [join7(home, ".adal")], ".json")
2123
2579
  },
2124
2580
  {
2125
2581
  id: "aider",
@@ -2127,7 +2583,7 @@ var CATALOG = [
2127
2583
  group: "other",
2128
2584
  implemented: false,
2129
2585
  defaultEnabled: true,
2130
- createAdapter: () => createStubAdapter("aider", [join6(home, ".aider")], ".aider.chat.history.md")
2586
+ createAdapter: () => createStubAdapter("aider", [join7(home, ".aider")], ".aider.chat.history.md")
2131
2587
  }
2132
2588
  ];
2133
2589
  function getAdapterCatalog() {
@@ -2198,9 +2654,9 @@ function getActiveAdapters() {
2198
2654
  var activeAdapters = resolveAdapters(getDefaultAdapterIds());
2199
2655
  // ../shared/src/config.ts
2200
2656
  import { randomBytes } from "node:crypto";
2201
- import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "node:fs";
2202
- import { homedir as homedir7 } from "node:os";
2203
- import { join as join7, resolve as resolve2 } from "node:path";
2657
+ import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "node:fs";
2658
+ import { homedir as homedir8 } from "node:os";
2659
+ import { join as join8, resolve as resolve3 } from "node:path";
2204
2660
 
2205
2661
  // ../shared/src/setup/adapter-selection.ts
2206
2662
  init_dist2();
@@ -2256,6 +2712,16 @@ async function promptForAdapterSelection(options = {}) {
2256
2712
 
2257
2713
  // ../shared/src/config.ts
2258
2714
  var devModeOverride;
2715
+ function getHomeDir() {
2716
+ if (process.env.HOME)
2717
+ return process.env.HOME;
2718
+ if (process.env.USERPROFILE)
2719
+ return process.env.USERPROFILE;
2720
+ if (process.env.HOMEDRIVE && process.env.HOMEPATH) {
2721
+ return `${process.env.HOMEDRIVE}${process.env.HOMEPATH}`;
2722
+ }
2723
+ return homedir8();
2724
+ }
2259
2725
  function setDevMode(dev) {
2260
2726
  devModeOverride = dev;
2261
2727
  }
@@ -2265,16 +2731,19 @@ function isDevMode() {
2265
2731
  return process.env.AGENDEX_DEV === "1";
2266
2732
  }
2267
2733
  function getConfigDir() {
2268
- return join7(homedir7(), isDevMode() ? ".agendex-dev" : ".agendex");
2734
+ const override = process.env.AGENDEX_CONFIG_DIR?.trim();
2735
+ if (override)
2736
+ return resolve3(expandHomePath(override));
2737
+ return join8(getHomeDir(), isDevMode() ? ".agendex-dev" : ".agendex");
2269
2738
  }
2270
2739
  function ensureConfigDir() {
2271
2740
  const dir = getConfigDir();
2272
- if (!existsSync3(dir))
2741
+ if (!existsSync4(dir))
2273
2742
  mkdirSync(dir, { recursive: true });
2274
2743
  }
2275
2744
  function readStoredConfig() {
2276
2745
  const cfgPath = getConfigPath();
2277
- if (!existsSync3(cfgPath))
2746
+ if (!existsSync4(cfgPath))
2278
2747
  return null;
2279
2748
  try {
2280
2749
  const raw = JSON.parse(readFileSync3(cfgPath, "utf-8"));
@@ -2292,7 +2761,7 @@ function normalizeAdapterIds(input) {
2292
2761
  }
2293
2762
  function expandHomePath(p) {
2294
2763
  if (p.startsWith("~/") || p === "~")
2295
- return join7(homedir7(), p.slice(1));
2764
+ return join8(getHomeDir(), p.slice(1));
2296
2765
  return p;
2297
2766
  }
2298
2767
  function resolveCustomPlanDirPath(userPath) {
@@ -2300,7 +2769,7 @@ function resolveCustomPlanDirPath(userPath) {
2300
2769
  if (!trimmed) {
2301
2770
  throw new Error("Custom plan directory path must not be empty");
2302
2771
  }
2303
- return resolve2(expandHomePath(trimmed));
2772
+ return resolve3(expandHomePath(trimmed));
2304
2773
  }
2305
2774
  function normalizeCustomPlanDirs(input) {
2306
2775
  if (!Array.isArray(input))
@@ -2365,6 +2834,7 @@ function loadOrCreateToken() {
2365
2834
  return existing.token;
2366
2835
  const token = generateToken();
2367
2836
  saveConfig({
2837
+ ...existing ?? {},
2368
2838
  configVersion: 3,
2369
2839
  token,
2370
2840
  enabledAdapters: existing?.enabledAdapters ?? [],
@@ -2391,7 +2861,7 @@ function loadOrCreateDeviceId() {
2391
2861
  return deviceId;
2392
2862
  }
2393
2863
  function getConfigPath() {
2394
- return join7(getConfigDir(), "config.json");
2864
+ return join8(getConfigDir(), "config.json");
2395
2865
  }
2396
2866
  async function loadOrInitConfig(options = {}) {
2397
2867
  const configureAdapters = Boolean(options.configureAdapters);
@@ -2438,17 +2908,348 @@ async function loadOrInitConfig(options = {}) {
2438
2908
  var CLI_DAEMON_HEARTBEAT_INTERVAL_MS = 30000;
2439
2909
  var CLI_DAEMON_STALE_AFTER_MS = CLI_DAEMON_HEARTBEAT_INTERVAL_MS * 5;
2440
2910
  // ../shared/src/services/plan-service.ts
2441
- import { existsSync as existsSync4, readdirSync as readdirSync3, statSync } from "node:fs";
2442
- import { lstat, mkdir, readdir as readdir2, readFile as readFile6, stat as stat6, writeFile as writeFile3 } from "node:fs/promises";
2443
- import { homedir as homedir8 } from "node:os";
2444
- import { join as join8, resolve as resolve3, sep as sep2 } from "node:path";
2911
+ import { existsSync as existsSync5, readdirSync as readdirSync3, realpathSync, statSync } from "node:fs";
2912
+ import { lstat, mkdir, readdir as readdir2, readFile as readFile7, stat as stat7, writeFile as writeFile3 } from "node:fs/promises";
2913
+ import { homedir as homedir9 } from "node:os";
2914
+ import { dirname as dirname2, join as join9, resolve as resolve4, sep as sep3 } from "node:path";
2915
+
2916
+ // ../shared/src/services/plan-value.ts
2917
+ function isLowValuePlan(plan) {
2918
+ const metadata = plan.metadata;
2919
+ return typeof metadata === "object" && metadata !== null && !Array.isArray(metadata) && metadata.lowValue === true;
2920
+ }
2921
+ function isIndexablePlan(plan) {
2922
+ return !isLowValuePlan(plan);
2923
+ }
2924
+ var PROPOSED_PLAN_TAG_REGEX2 = /<\s*\/?\s*proposed_plan\s*>/gi;
2925
+ var ESCAPED_PROPOSED_PLAN_TAG_REGEX2 = /&lt;\s*\/?\s*proposed_plan\s*&gt;/gi;
2926
+ var VISIBLE_TEXT_REGEX = /[\p{L}\p{N}]/u;
2927
+ var LOW_VALUE_METADATA_KEYS = ["lowValue", "lowValueReasons", "lowValueSignals"];
2928
+ function normalizeLineEndings2(text) {
2929
+ return text.replace(/\r\n?/g, `
2930
+ `);
2931
+ }
2932
+ function stripBoundaryHtmlComments(text) {
2933
+ let next = text;
2934
+ let previous = "";
2935
+ while (next !== previous) {
2936
+ previous = next;
2937
+ next = next.replace(/^\s*<!--[\s\S]*?-->\s*/, "").replace(/\s*<!--[\s\S]*?-->\s*$/, "");
2938
+ }
2939
+ return next;
2940
+ }
2941
+ function normalizePlanContent(content) {
2942
+ return stripBoundaryHtmlComments(normalizeLineEndings2(content).replace(/^\uFEFF/, "").replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, "").replace(ESCAPED_PROPOSED_PLAN_TAG_REGEX2, "").replace(PROPOSED_PLAN_TAG_REGEX2, "")).trim();
2943
+ }
2944
+ function withoutLowValueMetadata(metadata) {
2945
+ const next = { ...metadata ?? {} };
2946
+ for (const key of LOW_VALUE_METADATA_KEYS)
2947
+ delete next[key];
2948
+ return next;
2949
+ }
2950
+ function unique(items) {
2951
+ return Array.from(new Set(items));
2952
+ }
2953
+ function visibleText(text) {
2954
+ return text.replace(/<!--[\s\S]*?-->/g, "").replace(/^---\s*\n[\s\S]*?\n---\s*\n?/, "").replace(ESCAPED_PROPOSED_PLAN_TAG_REGEX2, "").replace(PROPOSED_PLAN_TAG_REGEX2, "").replace(/[`*_#[\](){}<>:|~\-+=.]/g, " ").trim();
2955
+ }
2956
+ function isSeparatorLine(line) {
2957
+ return /^(?:-{3,}|_{3,}|\*{3,})$/.test(line.trim());
2958
+ }
2959
+ function isFenceLine(line) {
2960
+ return /^(?:`{3,}|~{3,})/.test(line.trim());
2961
+ }
2962
+ function meaningfulLines(text) {
2963
+ return text.split(`
2964
+ `).map((line) => line.trim()).filter((line) => line && !isSeparatorLine(line) && !isFenceLine(line));
2965
+ }
2966
+ function cleanMarkdownLine(line) {
2967
+ return line.trim().replace(/^>+\s*/, "").replace(/^#{1,6}\s+/, "").replace(/^[-*+]\s+/, "").replace(/^\d+[.)]\s+/, "").replace(/^\[[ xX]\]\s+/, "").replace(/^\*\*|\*\*$/g, "").replace(/^`|`$/g, "").trim();
2968
+ }
2969
+ function isHeadingLine(line) {
2970
+ return /^#{1,6}\s+\S/.test(line.trim());
2971
+ }
2972
+ function isChecklistLine(line) {
2973
+ return /^[-*+]\s+\[[ xX]\]\s+\S/.test(line.trim());
2974
+ }
2975
+ function isOrderedListLine(line) {
2976
+ return /^\d+[.)]\s+\S/.test(line.trim());
2977
+ }
2978
+ function isHeadingOnly(lines) {
2979
+ return lines.length > 0 && lines.some(isHeadingLine) && lines.every(isHeadingLine);
2980
+ }
2981
+ function metadataHasPlanBlocks(metadata) {
2982
+ const planBlocks = metadata?.planBlocks;
2983
+ return typeof planBlocks === "number" && planBlocks > 0;
2984
+ }
2985
+ function sectionName(line) {
2986
+ return cleanMarkdownLine(line).replace(/:$/, "").toLowerCase().replace(/\s+/g, " ");
2987
+ }
2988
+ var SECTION_LABELS = new Set([
2989
+ "context",
2990
+ "background",
2991
+ "problem",
2992
+ "goal",
2993
+ "goals",
2994
+ "scope",
2995
+ "approach",
2996
+ "strategy",
2997
+ "design",
2998
+ "implementation plan",
2999
+ "implementation",
3000
+ "plan",
3001
+ "files to modify",
3002
+ "files changed",
3003
+ "affected files",
3004
+ "steps",
3005
+ "step",
3006
+ "tasks",
3007
+ "task",
3008
+ "checklist",
3009
+ "todo",
3010
+ "todos",
3011
+ "verification",
3012
+ "testing",
3013
+ "tests",
3014
+ "test",
3015
+ "validation",
3016
+ "reuse",
3017
+ "existing utilities",
3018
+ "existing code",
3019
+ "acceptance criteria",
3020
+ "success criteria"
3021
+ ]);
3022
+ function hasSectionSyntax(line, section) {
3023
+ const cleaned = cleanMarkdownLine(line);
3024
+ return isHeadingLine(line) || /^[a-z][\w\s/&+-]{1,48}:/i.test(cleaned) || SECTION_LABELS.has(section);
3025
+ }
3026
+ function collectPositiveSignals(normalized, lines, metadata) {
3027
+ const signals = [];
3028
+ if (metadataHasPlanBlocks(metadata))
3029
+ signals.push("metadata:proposed-plan-block");
3030
+ for (const line of lines) {
3031
+ const section = sectionName(line);
3032
+ if (!hasSectionSyntax(line, section))
3033
+ continue;
3034
+ if (/^(context|background|problem|goal|goals|scope)\b/.test(section)) {
3035
+ signals.push("section:context");
3036
+ continue;
3037
+ }
3038
+ if (/^(approach|strategy|design)\b/.test(section)) {
3039
+ signals.push("section:approach");
3040
+ continue;
3041
+ }
3042
+ if (/^(implementation plan|implementation|plan)\b/.test(section)) {
3043
+ signals.push("section:implementation-plan");
3044
+ continue;
3045
+ }
3046
+ if (/^(files? to modify|files? changed|affected files?)\b/.test(section)) {
3047
+ signals.push("section:files-to-modify");
3048
+ continue;
3049
+ }
3050
+ if (/^(steps?|tasks?|checklist|todo|todos)\b/.test(section)) {
3051
+ signals.push("section:steps");
3052
+ continue;
3053
+ }
3054
+ if (/^(verification|testing|tests?|validation)\b/.test(section)) {
3055
+ signals.push("section:verification");
3056
+ continue;
3057
+ }
3058
+ if (/^(reuse|existing utilities|existing code)\b/.test(section)) {
3059
+ signals.push("section:reuse");
3060
+ continue;
3061
+ }
3062
+ if (/^(acceptance criteria|success criteria)\b/.test(section)) {
3063
+ signals.push("section:acceptance-criteria");
3064
+ }
3065
+ }
3066
+ if (lines.some(isChecklistLine))
3067
+ signals.push("checklist");
3068
+ const orderedStepCount = lines.filter(isOrderedListLine).length;
3069
+ if (orderedStepCount >= 2)
3070
+ signals.push("ordered-steps");
3071
+ const actionBulletCount = lines.filter((line) => {
3072
+ const trimmed = line.trim();
3073
+ if (!/^(?:[-*+]|\d+[.)])\s+/.test(trimmed))
3074
+ return false;
3075
+ const cleaned = cleanMarkdownLine(trimmed);
3076
+ return /^(?:add|implement|update|modify|create|remove|delete|refactor|test|verify|run|wire|persist|handle|ensure|document|rename|move|extract|reuse|validate)\b/i.test(cleaned);
3077
+ }).length;
3078
+ if (actionBulletCount >= 2)
3079
+ signals.push("action-bullets");
3080
+ if (lines.length >= 2 && /\b(?:will|should|need to|needs to|plan to|planned|approach is to|implementation will)\b/i.test(normalized)) {
3081
+ signals.push("future-plan-language");
3082
+ }
3083
+ return unique(signals);
3084
+ }
3085
+ function isStrongPositiveSignal(signal) {
3086
+ return signal === "metadata:proposed-plan-block" || signal === "checklist" || signal === "ordered-steps" || signal === "action-bullets" || signal === "section:approach" || signal === "section:implementation-plan" || signal === "section:files-to-modify" || signal === "section:steps" || signal === "section:acceptance-criteria";
3087
+ }
3088
+ function hasStrongPlanSignal(positiveSignals) {
3089
+ const sectionCount = positiveSignals.filter((signal) => signal.startsWith("section:")).length;
3090
+ return positiveSignals.some(isStrongPositiveSignal) || sectionCount >= 2;
3091
+ }
3092
+ function isPromptLikeOneLiner(line) {
3093
+ if (isHeadingLine(line) || isChecklistLine(line) || isOrderedListLine(line))
3094
+ return false;
3095
+ const cleaned = cleanMarkdownLine(line).toLowerCase();
3096
+ if (!cleaned)
3097
+ return false;
3098
+ return [
3099
+ /^(?:important:\s*)?work in\b/,
3100
+ /^(?:please|pls)\b/,
3101
+ /^(?:can|could|would|will)\s+you\b/,
3102
+ /^(?:i|we)\s+(?:need|want|would like|have to)\b/,
3103
+ /^(?:help|fix|implement|create|add|update|remove|delete|refactor|write|review|investigate|debug|plan)\b/,
3104
+ /\?$/,
3105
+ /\b(?:repository|repo|existing branch|worktree|pull request|pr)\b/
3106
+ ].some((pattern) => pattern.test(cleaned));
3107
+ }
3108
+ function normalizedTitle(title) {
3109
+ return cleanMarkdownLine(title ?? "").toLowerCase();
3110
+ }
3111
+ function looksLikeWrapperTitle(title) {
3112
+ return /^<user_(?:action|instructions|prompt)>$/i.test((title ?? "").trim());
3113
+ }
3114
+ function looksLikePromptTitle(title) {
3115
+ const cleaned = normalizedTitle(title);
3116
+ if (!cleaned)
3117
+ return false;
3118
+ return [
3119
+ /^(?:important:\s*)?work in\b/,
3120
+ /^review the code changes against\b/,
3121
+ /^perform a .*review\b/,
3122
+ /^(?:please|pls)\b/,
3123
+ /^(?:can|could|would|will)\s+you\b/,
3124
+ /^(?:i|we)\s+(?:need|want|would like|have to)\b/,
3125
+ /^(?:help|fix|implement|create|add|update|remove|delete|refactor|write|review|investigate|debug|plan)\b/,
3126
+ /\?$/,
3127
+ /\b(?:repository|repo|existing branch|worktree|pull request|pr)\b/
3128
+ ].some((pattern) => pattern.test(cleaned));
3129
+ }
3130
+ function looksLikeReviewOutput(normalized, title) {
3131
+ const cleanedTitle = normalizedTitle(title);
3132
+ const lower = normalized.toLowerCase();
3133
+ return /^review the code changes against\b/.test(cleanedTitle) || /^perform a .*review\b/.test(cleanedTitle) || /"findings"\s*:\s*\[/.test(normalized) || /"overall_correctness"\s*:/.test(normalized) || /\bfull review comments\s*:/i.test(normalized) || /\bthe patch (?:currently )?(?:breaks|introduces|regresses)\b/i.test(normalized) || /\bshould not be considered correct\b/i.test(lower);
3134
+ }
3135
+ function looksLikeSystemContext(normalized, lines) {
3136
+ const lower = normalized.toLowerCase();
3137
+ if (lower.startsWith("# agents.md instructions") || lower.startsWith("<environment_context>") || lower.startsWith("<system-reminder>") || lower.startsWith("<thinking>")) {
3138
+ return true;
3139
+ }
3140
+ const wrapperMatches = normalized.match(/<\/?(?:environment_context|system-reminder|thinking|analysis|reasoning|tool_call|tool_result)\b[^>]*>/gi);
3141
+ if (wrapperMatches && wrapperMatches.length >= 2)
3142
+ return true;
3143
+ const wrapperLineCount = lines.filter((line) => /^(?:analysis|reasoning|thought|assistant thought|system|developer):\b/i.test(line)).length;
3144
+ return wrapperLineCount > 0 && wrapperLineCount / Math.max(lines.length, 1) >= 0.4;
3145
+ }
3146
+ function looksLikeToolLog(normalized) {
3147
+ return /::[a-z0-9_-]+(?:\{|\[|\s*$)/i.test(normalized) || /<\/?(?:tool_call|tool_result)\b[^>]*>/i.test(normalized) || /\b(?:function_call|tool_calls|tool_result)\b/i.test(normalized);
3148
+ }
3149
+ function looksLikeExecutionReport(normalized) {
3150
+ const hasPastCompletion = /\b(?:fixed|pushed|committed|completed|done|implemented|updated|changed|patched|merged|deployed|passed|failed|resolved|reverted)\b/i.test(normalized);
3151
+ const hasReportSection = /^\s*(?:summary|result|results|changes|verification|status)\s*:/im.test(normalized);
3152
+ const hasReviewReportMarker = /\b(?:review findings?|review issues?|review comments?)\b/i.test(normalized);
3153
+ const hasCommandMarker = /::[a-z0-9_-]+(?:\{|\[|\s*$)/im.test(normalized) || /`[^`]*(?:bun|npm|pnpm|yarn|git|tsc|biome)[^`]*`/i.test(normalized) || /\b(?:git\s+(?:stage|commit|push|status)|bunx?\s+|npm\s+|pnpm\s+|yarn\s+)\b/i.test(normalized);
3154
+ return hasPastCompletion && (hasReportSection || hasCommandMarker || hasReviewReportMarker);
3155
+ }
3156
+ function lowValueAssessment(reasons, signals) {
3157
+ return {
3158
+ lowValue: reasons.length > 0,
3159
+ reasons: unique(reasons),
3160
+ signals: unique(signals).slice(0, 20)
3161
+ };
3162
+ }
3163
+ function assessPlanValue(input) {
3164
+ const metadata = withoutLowValueMetadata(input.metadata);
3165
+ const normalized = normalizePlanContent(input.content);
3166
+ const lines = meaningfulLines(normalized);
3167
+ const positiveSignals = collectPositiveSignals(normalized, lines, metadata);
3168
+ const signals = [...positiveSignals];
3169
+ const reasons = [];
3170
+ if (!VISIBLE_TEXT_REGEX.test(visibleText(normalized))) {
3171
+ return lowValueAssessment(["empty-content"], ["negative:empty-content"]);
3172
+ }
3173
+ if (isHeadingOnly(lines)) {
3174
+ return lowValueAssessment(["heading-only"], [...signals, "negative:heading-only"]);
3175
+ }
3176
+ const explicitPlanBlock = metadataHasPlanBlocks(metadata);
3177
+ const strongPositive = hasStrongPlanSignal(positiveSignals);
3178
+ const systemContext = looksLikeSystemContext(normalized, lines);
3179
+ const toolLog = looksLikeToolLog(normalized);
3180
+ const executionReport = looksLikeExecutionReport(normalized);
3181
+ const wrapperTitle = looksLikeWrapperTitle(input.title);
3182
+ const promptTitle = looksLikePromptTitle(input.title);
3183
+ const reviewOutput = looksLikeReviewOutput(normalized, input.title);
3184
+ if (systemContext)
3185
+ signals.push("negative:system-context");
3186
+ if (toolLog)
3187
+ signals.push("negative:tool-log");
3188
+ if (executionReport)
3189
+ signals.push("negative:execution-report");
3190
+ if (wrapperTitle)
3191
+ signals.push("negative:wrapper-title");
3192
+ if (promptTitle)
3193
+ signals.push("negative:prompt-title");
3194
+ if (reviewOutput)
3195
+ signals.push("negative:review-output");
3196
+ if (lines.length === 1)
3197
+ signals.push("shape:single-line");
3198
+ if (lines.length === 1 && positiveSignals.length === 0) {
3199
+ reasons.push(isPromptLikeOneLiner(lines[0] ?? "") ? "prompt-like" : "no-plan-signals");
3200
+ }
3201
+ if (systemContext && !strongPositive)
3202
+ reasons.push("system-context");
3203
+ if (executionReport && !strongPositive)
3204
+ reasons.push("execution-report");
3205
+ if (wrapperTitle && !explicitPlanBlock)
3206
+ reasons.push("wrapper-title");
3207
+ if (reviewOutput && !explicitPlanBlock)
3208
+ reasons.push("review-output");
3209
+ if (promptTitle && !strongPositive && positiveSignals.length === 0)
3210
+ reasons.push("prompt-like");
3211
+ if (toolLog && !strongPositive && positiveSignals.length === 0)
3212
+ reasons.push("no-plan-signals");
3213
+ if (reasons.length === 0 && positiveSignals.length === 0 && lines.length <= 3) {
3214
+ reasons.push("no-plan-signals");
3215
+ }
3216
+ return lowValueAssessment(reasons, signals);
3217
+ }
3218
+ function annotatePlanValueMetadata(plan) {
3219
+ const baseMetadata = withoutLowValueMetadata(plan.metadata);
3220
+ const assessment = assessPlanValue({
3221
+ content: plan.content,
3222
+ title: plan.title,
3223
+ metadata: baseMetadata
3224
+ });
3225
+ if (!assessment.lowValue) {
3226
+ return {
3227
+ ...plan,
3228
+ metadata: baseMetadata
3229
+ };
3230
+ }
3231
+ return {
3232
+ ...plan,
3233
+ metadata: {
3234
+ ...baseMetadata,
3235
+ lowValue: true,
3236
+ lowValueReasons: assessment.reasons,
3237
+ lowValueSignals: assessment.signals
3238
+ }
3239
+ };
3240
+ }
3241
+
3242
+ // ../shared/src/services/plan-service.ts
2445
3243
  function getUserPlansDir() {
2446
- return join8(getConfigDir(), "plans");
3244
+ return join9(getConfigDir(), "plans");
2447
3245
  }
2448
3246
  var store = new Map;
2449
3247
  var MAX_DEPTH = 6;
2450
3248
  var DISCOVERY_MAX_DEPTH = 4;
2451
- var PROJECT_PLAN_MARKERS = [{ marker: ".sisyphus/plans", agent: "oh-my-opencode" }];
3249
+ var PROJECT_PLAN_MARKERS = [
3250
+ { marker: ".sisyphus/plans", agent: "oh-my-opencode" },
3251
+ { marker: "@plans", agent: "plannotator" }
3252
+ ];
2452
3253
  var SKIP_DIRS = new Set([
2453
3254
  "node_modules",
2454
3255
  ".git",
@@ -2481,19 +3282,58 @@ var SKIP_DIRS = new Set([
2481
3282
  "Google Drive",
2482
3283
  "iCloud Drive"
2483
3284
  ]);
3285
+ function getRuntimeHomeDir() {
3286
+ const homeFromEnv = process.env.HOME || process.env.USERPROFILE || (process.env.HOMEDRIVE && process.env.HOMEPATH ? `${process.env.HOMEDRIVE}${process.env.HOMEPATH}` : undefined);
3287
+ return resolve4(homeFromEnv || homedir9());
3288
+ }
2484
3289
  function discoverProjectPlanDirs() {
2485
- const home2 = homedir8();
3290
+ const home2 = canonicalPath(getRuntimeHomeDir());
2486
3291
  const results = [];
2487
- function walk(dir, depth) {
2488
- if (depth > DISCOVERY_MAX_DEPTH)
3292
+ const seen = new Set;
3293
+ let nearestAncestorMarkerRoot;
3294
+ function addResult(dir, agent) {
3295
+ const resolved = canonicalPath(dir);
3296
+ const key = `${agent}:${resolved}`;
3297
+ if (seen.has(key))
2489
3298
  return;
3299
+ seen.add(key);
3300
+ results.push({ dir: resolved, agent });
3301
+ }
3302
+ function inspectMarkers(dir) {
3303
+ let found = false;
2490
3304
  for (const { marker, agent } of PROJECT_PLAN_MARKERS) {
2491
- const candidate = join8(dir, marker);
2492
- if (existsSync4(candidate)) {
2493
- results.push({ dir: candidate, agent });
3305
+ const candidate = join9(dir, marker);
3306
+ if (existsSync5(candidate)) {
3307
+ addResult(candidate, agent);
3308
+ found = true;
3309
+ }
3310
+ }
3311
+ return found;
3312
+ }
3313
+ function walkAncestorsForCurrentProject() {
3314
+ let dir = canonicalPath(process.cwd());
3315
+ while (true) {
3316
+ if (inspectMarkers(dir)) {
3317
+ nearestAncestorMarkerRoot = dir;
2494
3318
  return;
2495
3319
  }
3320
+ if (dir === home2)
3321
+ return;
3322
+ const parent = dirname2(dir);
3323
+ if (parent === dir)
3324
+ return;
3325
+ dir = parent;
2496
3326
  }
3327
+ }
3328
+ function isAncestorOfNearestMarker(dir) {
3329
+ return Boolean(nearestAncestorMarkerRoot?.startsWith(dir + sep3));
3330
+ }
3331
+ function walk(dir, depth) {
3332
+ if (depth > DISCOVERY_MAX_DEPTH)
3333
+ return;
3334
+ const resolved = canonicalPath(dir);
3335
+ if (!isAncestorOfNearestMarker(resolved) && inspectMarkers(dir))
3336
+ return;
2497
3337
  let names;
2498
3338
  try {
2499
3339
  names = readdirSync3(dir);
@@ -2505,13 +3345,14 @@ function discoverProjectPlanDirs() {
2505
3345
  continue;
2506
3346
  if (SKIP_DIRS.has(name))
2507
3347
  continue;
2508
- const full = join8(dir, name);
3348
+ const full = join9(dir, name);
2509
3349
  try {
2510
3350
  if (statSync(full).isDirectory())
2511
3351
  walk(full, depth + 1);
2512
3352
  } catch {}
2513
3353
  }
2514
3354
  }
3355
+ walkAncestorsForCurrentProject();
2515
3356
  walk(home2, 0);
2516
3357
  return results;
2517
3358
  }
@@ -2525,9 +3366,9 @@ function notifyPlansChanged() {
2525
3366
  async function walkDir(dir, depth = 0, seen = new Set) {
2526
3367
  if (depth > MAX_DEPTH)
2527
3368
  return [];
2528
- if (!existsSync4(dir))
3369
+ if (!existsSync5(dir))
2529
3370
  return [];
2530
- const real = resolve3(dir);
3371
+ const real = resolve4(dir);
2531
3372
  if (seen.has(real))
2532
3373
  return [];
2533
3374
  seen.add(real);
@@ -2535,7 +3376,7 @@ async function walkDir(dir, depth = 0, seen = new Set) {
2535
3376
  try {
2536
3377
  const entries = await readdir2(dir, { withFileTypes: true });
2537
3378
  for (const entry of entries) {
2538
- const full = join8(dir, entry.name);
3379
+ const full = join9(dir, entry.name);
2539
3380
  try {
2540
3381
  const stats = await lstat(full);
2541
3382
  if (stats.isSymbolicLink())
@@ -2552,8 +3393,8 @@ async function walkDir(dir, depth = 0, seen = new Set) {
2552
3393
  }
2553
3394
  async function parseGenericMarkdownPlan(filePath, extraMetadata) {
2554
3395
  try {
2555
- const content = await readFile6(filePath, "utf-8");
2556
- const stats = await stat6(filePath);
3396
+ const content = await readFile7(filePath, "utf-8");
3397
+ const stats = await stat7(filePath);
2557
3398
  let agent = (typeof extraMetadata.agentHint === "string" ? extraMetadata.agentHint : "") || "unknown";
2558
3399
  const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
2559
3400
  if (fmMatch) {
@@ -2581,7 +3422,7 @@ async function parseGenericMarkdownPlan(filePath, extraMetadata) {
2581
3422
  }
2582
3423
  async function scanUserPlans(into) {
2583
3424
  const userPlansDir = getUserPlansDir();
2584
- if (!existsSync4(userPlansDir))
3425
+ if (!existsSync5(userPlansDir))
2585
3426
  return;
2586
3427
  const files = await walkDir(userPlansDir);
2587
3428
  for (const file of files) {
@@ -2595,19 +3436,27 @@ async function scanUserPlans(into) {
2595
3436
  function getCustomPlanDirs() {
2596
3437
  return loadConfig()?.customPlanDirs ?? [];
2597
3438
  }
3439
+ function canonicalPath(path) {
3440
+ const resolved = resolve4(path);
3441
+ try {
3442
+ return realpathSync(resolved);
3443
+ } catch {
3444
+ return resolved;
3445
+ }
3446
+ }
2598
3447
  function pathsOverlapFilesystemTree(a, b) {
2599
- const ra = resolve3(a);
2600
- const rb = resolve3(b);
3448
+ const ra = canonicalPath(a);
3449
+ const rb = canonicalPath(b);
2601
3450
  if (ra === rb)
2602
3451
  return true;
2603
- if (ra.startsWith(rb + sep2))
3452
+ if (ra.startsWith(rb + sep3))
2604
3453
  return true;
2605
- if (rb.startsWith(ra + sep2))
3454
+ if (rb.startsWith(ra + sep3))
2606
3455
  return true;
2607
3456
  return false;
2608
3457
  }
2609
3458
  function overlapsAnyRoot(candidate, roots) {
2610
- const resolvedCandidate = resolve3(candidate);
3459
+ const resolvedCandidate = canonicalPath(candidate);
2611
3460
  for (const root of roots) {
2612
3461
  if (pathsOverlapFilesystemTree(resolvedCandidate, root))
2613
3462
  return true;
@@ -2616,9 +3465,9 @@ function overlapsAnyRoot(candidate, roots) {
2616
3465
  }
2617
3466
  async function scanCustomPlanDirs(coveredPaths, into) {
2618
3467
  const dirs = getCustomPlanDirs();
2619
- const userPlansDir = resolve3(getUserPlansDir());
3468
+ const userPlansDir = canonicalPath(getUserPlansDir());
2620
3469
  for (const dir of dirs) {
2621
- const resolved = resolve3(dir);
3470
+ const resolved = canonicalPath(dir);
2622
3471
  if (overlapsAnyRoot(resolved, coveredPaths)) {
2623
3472
  console.log(`[agendex] skipping custom dir (overlaps adapter / discovered coverage): ${dir}`);
2624
3473
  continue;
@@ -2627,7 +3476,7 @@ async function scanCustomPlanDirs(coveredPaths, into) {
2627
3476
  console.log(`[agendex] skipping custom dir (overlaps user plans): ${dir}`);
2628
3477
  continue;
2629
3478
  }
2630
- if (!existsSync4(dir)) {
3479
+ if (!existsSync5(dir)) {
2631
3480
  console.log(`[agendex] skipping custom dir (not found): ${dir}`);
2632
3481
  continue;
2633
3482
  }
@@ -2656,21 +3505,22 @@ async function scan() {
2656
3505
  const coveredPaths = new Set;
2657
3506
  for (const adapter of adapters) {
2658
3507
  for (const searchPath of adapter.getSearchPaths()) {
2659
- coveredPaths.add(resolve3(searchPath));
3508
+ coveredPaths.add(canonicalPath(searchPath));
2660
3509
  const files = await walkDir(searchPath);
2661
3510
  for (const file of files) {
2662
3511
  if (!adapter.matches(file))
2663
3512
  continue;
2664
3513
  const plans = await adapter.parse(file);
2665
3514
  for (const plan of plans) {
2666
- next.set(plan.id, plan);
3515
+ const annotated = annotatePlanValueMetadata(plan);
3516
+ next.set(annotated.id, annotated);
2667
3517
  }
2668
3518
  }
2669
3519
  }
2670
3520
  }
2671
3521
  const discovered = discoverProjectPlanDirs();
2672
3522
  for (const { dir, agent } of discovered) {
2673
- const resolvedDir = resolve3(dir);
3523
+ const resolvedDir = canonicalPath(dir);
2674
3524
  if (coveredPaths.has(resolvedDir))
2675
3525
  continue;
2676
3526
  const adapter = adapters.find((a) => a.agent === agent);
@@ -2682,7 +3532,8 @@ async function scan() {
2682
3532
  continue;
2683
3533
  const plans = await adapter.parse(file);
2684
3534
  for (const plan of plans) {
2685
- next.set(plan.id, plan);
3535
+ const annotated = annotatePlanValueMetadata(plan);
3536
+ next.set(annotated.id, annotated);
2686
3537
  }
2687
3538
  }
2688
3539
  coveredPaths.add(resolvedDir);
@@ -2692,34 +3543,97 @@ async function scan() {
2692
3543
  await scanCustomPlanDirs(coveredPaths, next);
2693
3544
  store = next;
2694
3545
  notifyPlansChanged();
2695
- console.log(`[agendex] indexed ${store.size} plans from ${adapters.length} adapters`);
3546
+ const indexableCount = getIndexablePlans().length;
3547
+ const hiddenCount = store.size - indexableCount;
3548
+ const hiddenSuffix = hiddenCount > 0 ? ` (${hiddenCount} hidden as low-value)` : "";
3549
+ console.log(`[agendex] indexed ${indexableCount} plans${hiddenSuffix} from ${adapters.length} adapters`);
2696
3550
  }
2697
3551
  function getAll() {
2698
3552
  return Array.from(store.values());
2699
3553
  }
3554
+ function getIndexablePlans() {
3555
+ return getAll().filter(isIndexablePlan);
3556
+ }
3557
+ function getById(id) {
3558
+ return store.get(id);
3559
+ }
3560
+ function getPlanSourceAdapter(plan) {
3561
+ const sourceAdapter = plan.metadata.sourceAdapter;
3562
+ return typeof sourceAdapter === "string" && sourceAdapter.trim() ? sourceAdapter : undefined;
3563
+ }
3564
+ function findAdapterForPlan(plan) {
3565
+ const adapters = getActiveAdapters();
3566
+ const sourceAdapter = getPlanSourceAdapter(plan);
3567
+ return adapters.find((adapter) => adapter.agent === sourceAdapter) ?? adapters.find((adapter) => adapter.agent === plan.agent);
3568
+ }
3569
+ async function requestChanges(id, payload) {
3570
+ const plan = store.get(id);
3571
+ if (!plan)
3572
+ return false;
3573
+ const adapter = findAdapterForPlan(plan);
3574
+ if (!adapter?.requestChanges)
3575
+ return false;
3576
+ const ok = await adapter.requestChanges(plan, payload);
3577
+ if (ok) {
3578
+ const plannotator = typeof plan.metadata.plannotator === "object" && plan.metadata.plannotator !== null ? { ...plan.metadata.plannotator, lastWritebackStatus: "sent", lastWritebackAt: Date.now() } : undefined;
3579
+ if (plannotator)
3580
+ plan.metadata = { ...plan.metadata, plannotator };
3581
+ plan.updatedAt = new Date;
3582
+ notifyPlansChanged();
3583
+ }
3584
+ return ok;
3585
+ }
3586
+ function planBelongsToAdapter(plan, adapter) {
3587
+ if (plan.agent === adapter.agent)
3588
+ return true;
3589
+ return adapter.agent === "plannotator" && typeof plan.metadata.plannotator === "object" && plan.metadata.plannotator !== null;
3590
+ }
3591
+ function removePlansForPath(filePath, adapter) {
3592
+ const normalized = resolve4(filePath);
3593
+ const removed = [];
3594
+ for (const [id, plan] of store.entries()) {
3595
+ const planPath = resolve4(plan.filePath);
3596
+ const sessionPath = typeof plan.metadata.plannotator === "object" && plan.metadata.plannotator !== null ? plan.metadata.plannotator.sessionPath : undefined;
3597
+ if ((planPath === normalized || sessionPath === normalized) && (!adapter || planBelongsToAdapter(plan, adapter))) {
3598
+ removed.push(plan);
3599
+ store.delete(id);
3600
+ }
3601
+ }
3602
+ if (removed.length > 0)
3603
+ notifyPlansChanged();
3604
+ return removed;
3605
+ }
2700
3606
  async function rescanFile(filePath) {
2701
3607
  const adapters = getActiveAdapters();
2702
- const normalized = resolve3(filePath);
3608
+ const normalized = resolve4(filePath);
3609
+ const removedPlans = [];
2703
3610
  for (const adapter of adapters) {
2704
3611
  if (!adapter.matches(filePath))
2705
3612
  continue;
2706
- const discoveredDirs = discoverProjectPlanDirs().filter((d2) => d2.agent === adapter.agent).map((d2) => resolve3(d2.dir));
3613
+ const discoveredDirs = discoverProjectPlanDirs().filter((d2) => d2.agent === adapter.agent).map((d2) => resolve4(d2.dir));
2707
3614
  const allSearchPaths = [
2708
- ...adapter.getSearchPaths().map((sp) => resolve3(sp)),
3615
+ ...adapter.getSearchPaths().map((sp) => resolve4(sp)),
2709
3616
  ...discoveredDirs
2710
3617
  ];
2711
- const isInSearchPath = allSearchPaths.some((sp) => normalized.startsWith(sp + sep2) || normalized === sp);
3618
+ const isInSearchPath = allSearchPaths.some((sp) => normalized.startsWith(sp + sep3) || normalized === sp);
2712
3619
  if (!isInSearchPath)
2713
3620
  continue;
2714
- const plans = await adapter.parse(filePath);
3621
+ const rawPlans = await adapter.parse(filePath);
3622
+ if (rawPlans.length === 0) {
3623
+ removedPlans.push(...removePlansForPath(filePath, adapter));
3624
+ continue;
3625
+ }
3626
+ const plans = rawPlans.map(annotatePlanValueMetadata);
2715
3627
  for (const plan of plans) {
2716
3628
  store.set(plan.id, plan);
2717
3629
  }
2718
3630
  notifyPlansChanged();
2719
3631
  return plans;
2720
3632
  }
2721
- const userPlansDir = resolve3(getUserPlansDir());
2722
- if (normalized.endsWith(".md") && (normalized.startsWith(userPlansDir + sep2) || normalized === userPlansDir)) {
3633
+ if (removedPlans.length > 0)
3634
+ return removedPlans;
3635
+ const userPlansDir = resolve4(getUserPlansDir());
3636
+ if (normalized.endsWith(".md") && (normalized.startsWith(userPlansDir + sep3) || normalized === userPlansDir)) {
2723
3637
  const plan = await parseGenericMarkdownPlan(filePath, { userCreated: true });
2724
3638
  if (plan) {
2725
3639
  store.set(plan.id, plan);
@@ -2730,8 +3644,8 @@ async function rescanFile(filePath) {
2730
3644
  if (normalized.endsWith(".md")) {
2731
3645
  const customDirs = getCustomPlanDirs();
2732
3646
  for (const dir of customDirs) {
2733
- const resolvedDir = resolve3(dir);
2734
- if (normalized.startsWith(resolvedDir + sep2) || normalized === resolvedDir) {
3647
+ const resolvedDir = resolve4(dir);
3648
+ if (normalized.startsWith(resolvedDir + sep3) || normalized === resolvedDir) {
2735
3649
  const plan = await parseGenericMarkdownPlan(filePath, {
2736
3650
  source: "custom-dir",
2737
3651
  customDir: dir
@@ -2747,8 +3661,8 @@ async function rescanFile(filePath) {
2747
3661
  return [];
2748
3662
  }
2749
3663
  // ../shared/src/services/watcher.ts
2750
- import { existsSync as existsSync5, watch } from "node:fs";
2751
- import { join as join9, resolve as resolve4 } from "node:path";
3664
+ import { existsSync as existsSync6, watch } from "node:fs";
3665
+ import { join as join10, resolve as resolve5 } from "node:path";
2752
3666
  var debounceTimer = null;
2753
3667
  var pendingFiles = new Set;
2754
3668
  var activeWatchers = [];
@@ -2766,13 +3680,13 @@ function closeAllWatchers() {
2766
3680
  activeWatchers.length = 0;
2767
3681
  }
2768
3682
  function watchDir(dir, matchFn, onChange) {
2769
- if (!existsSync5(dir))
3683
+ if (!existsSync6(dir))
2770
3684
  return;
2771
3685
  try {
2772
3686
  const watcher = watch(dir, { recursive: true }, async (_event, filename) => {
2773
3687
  if (!filename)
2774
3688
  return;
2775
- const fullPath = join9(dir, filename);
3689
+ const fullPath = join10(dir, filename);
2776
3690
  if (!matchFn(fullPath))
2777
3691
  return;
2778
3692
  pendingFiles.add(fullPath);
@@ -2805,23 +3719,23 @@ function setupWatchers(onChange) {
2805
3719
  const watchedPaths = new Set;
2806
3720
  for (const adapter of adapters) {
2807
3721
  for (const watchPath of adapter.getWatchPaths()) {
2808
- watchedPaths.add(resolve4(watchPath));
3722
+ watchedPaths.add(resolve5(watchPath));
2809
3723
  watchDir(watchPath, (f) => adapter.matches(f), onChange);
2810
3724
  }
2811
3725
  }
2812
3726
  const discovered = discoverProjectPlanDirs();
2813
3727
  for (const { dir, agent } of discovered) {
2814
- if (watchedPaths.has(resolve4(dir)))
3728
+ if (watchedPaths.has(resolve5(dir)))
2815
3729
  continue;
2816
3730
  const adapter = adapters.find((a) => a.agent === agent);
2817
3731
  if (!adapter)
2818
3732
  continue;
2819
- watchedPaths.add(resolve4(dir));
3733
+ watchedPaths.add(resolve5(dir));
2820
3734
  watchDir(dir, (f) => adapter.matches(f), onChange);
2821
3735
  }
2822
3736
  const customDirs = getCustomPlanDirs();
2823
3737
  for (const dir of customDirs) {
2824
- const resolvedCustom = resolve4(dir);
3738
+ const resolvedCustom = resolve5(dir);
2825
3739
  let overlaps = false;
2826
3740
  for (const watched of watchedPaths) {
2827
3741
  if (pathsOverlapFilesystemTree(resolvedCustom, watched)) {
@@ -2841,15 +3755,15 @@ import { request as httpsRequest } from "node:https";
2841
3755
  import { hostname as osHostname } from "node:os";
2842
3756
 
2843
3757
  // src/pid.ts
2844
- import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
3758
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
2845
3759
  import { hostname } from "node:os";
2846
- import { dirname, join as join10 } from "node:path";
3760
+ import { dirname as dirname3, join as join11 } from "node:path";
2847
3761
  function getPidPath() {
2848
- return join10(getConfigDir(), "daemon.pid");
3762
+ return join11(getConfigDir(), "daemon.pid");
2849
3763
  }
2850
3764
  function writePid() {
2851
3765
  const path = getPidPath();
2852
- mkdirSync2(dirname(path), { recursive: true });
3766
+ mkdirSync2(dirname3(path), { recursive: true });
2853
3767
  const info = {
2854
3768
  pid: process.pid,
2855
3769
  startedAtMs: Date.now(),
@@ -2859,7 +3773,7 @@ function writePid() {
2859
3773
  }
2860
3774
  function readPidInfo() {
2861
3775
  const path = getPidPath();
2862
- if (!existsSync6(path))
3776
+ if (!existsSync7(path))
2863
3777
  return null;
2864
3778
  const raw = readFileSync4(path, "utf-8").trim();
2865
3779
  const asNumber = Number(raw);
@@ -2909,6 +3823,22 @@ function getCloudConfig() {
2909
3823
  convexUrl: config.convexUrl
2910
3824
  };
2911
3825
  }
3826
+ function parseSyncSuccess(body) {
3827
+ try {
3828
+ const parsed = JSON.parse(body);
3829
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
3830
+ return { ok: true };
3831
+ }
3832
+ const result = parsed;
3833
+ return {
3834
+ ok: true,
3835
+ skippedLowValue: result.skippedLowValue === true,
3836
+ deleted: result.deleted === true
3837
+ };
3838
+ } catch {
3839
+ return { ok: true };
3840
+ }
3841
+ }
2912
3842
  async function syncPlan(plan) {
2913
3843
  const { token, convexUrl } = getCloudConfig();
2914
3844
  const url = `${convexUrl}/api/cli/sync`;
@@ -2940,7 +3870,7 @@ async function syncPlan(plan) {
2940
3870
  if (res.status < 200 || res.status >= 300) {
2941
3871
  return { ok: false, error: `${res.status}: ${res.body}` };
2942
3872
  }
2943
- return { ok: true };
3873
+ return parseSyncSuccess(res.body);
2944
3874
  }
2945
3875
  async function refreshStoredToken(currentToken, convexUrl) {
2946
3876
  const refreshed = await refreshToken(currentToken, convexUrl);
@@ -2995,7 +3925,7 @@ async function sendHeartbeat() {
2995
3925
  }
2996
3926
  async function sendShutdown() {
2997
3927
  try {
2998
- const { token, convexUrl } = getCloudConfig();
3928
+ getCloudConfig();
2999
3929
  cachedDeviceId ??= loadOrCreateDeviceId();
3000
3930
  await deleteDaemons([cachedDeviceId]);
3001
3931
  } catch {}
@@ -3023,7 +3953,7 @@ function requestText(urlString, options) {
3023
3953
  if (options.body) {
3024
3954
  headers["Content-Length"] = String(Buffer.byteLength(options.body));
3025
3955
  }
3026
- return new Promise((resolve5, reject) => {
3956
+ return new Promise((resolve6, reject) => {
3027
3957
  const req = request(url, {
3028
3958
  agent: false,
3029
3959
  headers,
@@ -3035,7 +3965,7 @@ function requestText(urlString, options) {
3035
3965
  body += chunk;
3036
3966
  });
3037
3967
  res.on("end", () => {
3038
- resolve5({
3968
+ resolve6({
3039
3969
  status: res.statusCode ?? 0,
3040
3970
  body
3041
3971
  });
@@ -3049,6 +3979,62 @@ function requestText(urlString, options) {
3049
3979
  req.end();
3050
3980
  });
3051
3981
  }
3982
+ function authHeaders(token, contentType = false) {
3983
+ return {
3984
+ Authorization: `Bearer ${token}`,
3985
+ Connection: "close",
3986
+ ...contentType && { "Content-Type": "application/json" }
3987
+ };
3988
+ }
3989
+ async function fetchPlannotatorWritebacks(limit = 10) {
3990
+ const { token, convexUrl } = getCloudConfig();
3991
+ cachedDeviceId ??= loadOrCreateDeviceId();
3992
+ const url = `${convexUrl}/api/cli/plannotator/writebacks?deviceId=${encodeURIComponent(cachedDeviceId)}&limit=${limit}`;
3993
+ let activeToken = token;
3994
+ let res = await requestText(url, {
3995
+ method: "GET",
3996
+ headers: authHeaders(activeToken)
3997
+ });
3998
+ if (res.status === 401) {
3999
+ const refreshed = await refreshStoredToken(activeToken, convexUrl);
4000
+ if (refreshed) {
4001
+ activeToken = refreshed;
4002
+ res = await requestText(url, {
4003
+ method: "GET",
4004
+ headers: authHeaders(activeToken)
4005
+ });
4006
+ }
4007
+ }
4008
+ if (res.status === 401)
4009
+ throw new AuthExpiredError;
4010
+ if (res.status < 200 || res.status >= 300)
4011
+ return [];
4012
+ const body = JSON.parse(res.body);
4013
+ return body.writebacks ?? [];
4014
+ }
4015
+ async function reportPlannotatorWriteback(writebackId, status, error) {
4016
+ const { token, convexUrl } = getCloudConfig();
4017
+ const url = `${convexUrl}/api/cli/plannotator/writebacks/report`;
4018
+ let activeToken = token;
4019
+ const body = JSON.stringify({ writebackId, status, error });
4020
+ let res = await requestText(url, {
4021
+ method: "POST",
4022
+ headers: authHeaders(activeToken, true),
4023
+ body
4024
+ });
4025
+ if (res.status === 401) {
4026
+ const refreshed = await refreshStoredToken(activeToken, convexUrl);
4027
+ if (refreshed) {
4028
+ activeToken = refreshed;
4029
+ res = await requestText(url, {
4030
+ method: "POST",
4031
+ headers: authHeaders(activeToken, true),
4032
+ body
4033
+ });
4034
+ }
4035
+ }
4036
+ return res.status >= 200 && res.status < 300;
4037
+ }
3052
4038
  async function fetchDevices() {
3053
4039
  const { token, convexUrl } = getCloudConfig();
3054
4040
  const url = `${convexUrl}/api/cli/devices`;
@@ -3149,6 +4135,7 @@ async function login(siteUrlOverride) {
3149
4135
  token: existing?.token,
3150
4136
  cloudToken: callback.token,
3151
4137
  convexUrl: callback.convexUrl,
4138
+ deviceId: existing?.deviceId,
3152
4139
  enabledAdapters: existing?.enabledAdapters ?? [],
3153
4140
  customPlanDirs: existing?.customPlanDirs ?? []
3154
4141
  };
@@ -3167,6 +4154,7 @@ function logout() {
3167
4154
  token: existing.token,
3168
4155
  cloudToken: undefined,
3169
4156
  convexUrl: undefined,
4157
+ deviceId: existing.deviceId,
3170
4158
  enabledAdapters: existing.enabledAdapters,
3171
4159
  customPlanDirs: existing.customPlanDirs
3172
4160
  };
@@ -3179,14 +4167,14 @@ async function startCallbackServer() {
3179
4167
  let timeout;
3180
4168
  let settle;
3181
4169
  let fail;
3182
- const result = new Promise((resolve5, reject) => {
3183
- settle = resolve5;
4170
+ const result = new Promise((resolve6, reject) => {
4171
+ settle = resolve6;
3184
4172
  fail = reject;
3185
4173
  });
3186
4174
  const finish = (value) => {
3187
4175
  if (!settle || !fail)
3188
4176
  return;
3189
- const resolve5 = settle;
4177
+ const resolve6 = settle;
3190
4178
  const reject = fail;
3191
4179
  settle = undefined;
3192
4180
  fail = undefined;
@@ -3203,7 +4191,7 @@ async function startCallbackServer() {
3203
4191
  reject(value);
3204
4192
  return;
3205
4193
  }
3206
- resolve5(value);
4194
+ resolve6(value);
3207
4195
  };
3208
4196
  server.on("request", (req, res) => {
3209
4197
  const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
@@ -3237,8 +4225,8 @@ async function startCallbackServer() {
3237
4225
  sockets.delete(socket);
3238
4226
  });
3239
4227
  });
3240
- await new Promise((resolve5, reject) => {
3241
- server.listen(0, "127.0.0.1", () => resolve5());
4228
+ await new Promise((resolve6, reject) => {
4229
+ server.listen(0, "127.0.0.1", () => resolve6());
3242
4230
  server.once("error", reject);
3243
4231
  });
3244
4232
  server.once("error", (error) => {
@@ -3318,19 +4306,77 @@ function spawnBrowser(command, args, options = {}) {
3318
4306
 
3319
4307
  // src/daemon.ts
3320
4308
  import { spawn as spawn2 } from "node:child_process";
3321
- import { resolve as resolve5 } from "node:path";
4309
+ import { resolve as resolve6 } from "node:path";
3322
4310
  import { fileURLToPath } from "node:url";
3323
4311
 
4312
+ // src/adapters.ts
4313
+ var PLANNOTATOR_ADAPTER_ID = "plannotator";
4314
+ function envFlag(name) {
4315
+ const value = process.env[name]?.trim().toLowerCase();
4316
+ if (!value)
4317
+ return;
4318
+ if (value === "1" || value === "true" || value === "yes" || value === "on")
4319
+ return true;
4320
+ if (value === "0" || value === "false" || value === "no" || value === "off")
4321
+ return false;
4322
+ return;
4323
+ }
4324
+ function shouldEnablePlannotatorSync(config) {
4325
+ const explicit = envFlag("AGENDEX_PLANNOTATOR_SYNC");
4326
+ if (explicit !== undefined)
4327
+ return explicit;
4328
+ return Boolean(config.cloudToken && config.convexUrl);
4329
+ }
4330
+ function resolveCliAdapterIds(config) {
4331
+ const ids = config.enabledAdapters.length > 0 ? [...config.enabledAdapters] : getDefaultAdapterIds();
4332
+ if (shouldEnablePlannotatorSync(config) && !ids.includes(PLANNOTATOR_ADAPTER_ID)) {
4333
+ ids.push(PLANNOTATOR_ADAPTER_ID);
4334
+ }
4335
+ return ids;
4336
+ }
4337
+
4338
+ // src/payload.ts
4339
+ var SYNC_METADATA_KEY = "agendexSync";
4340
+ function isRecord4(value) {
4341
+ return typeof value === "object" && value !== null;
4342
+ }
4343
+ function withSyncDeviceMetadata(metadata, deviceId) {
4344
+ if (!deviceId)
4345
+ return metadata;
4346
+ const existing = isRecord4(metadata[SYNC_METADATA_KEY]) ? metadata[SYNC_METADATA_KEY] : {};
4347
+ return {
4348
+ ...metadata,
4349
+ [SYNC_METADATA_KEY]: {
4350
+ ...existing,
4351
+ deviceId
4352
+ }
4353
+ };
4354
+ }
4355
+ function planToSyncPayload(plan, deviceId) {
4356
+ return {
4357
+ localPlanId: plan.id,
4358
+ agent: plan.agent,
4359
+ title: plan.title,
4360
+ content: plan.content,
4361
+ format: plan.format,
4362
+ filePath: plan.filePath,
4363
+ workspace: plan.workspace,
4364
+ metadata: withSyncDeviceMetadata(plan.metadata, deviceId),
4365
+ createdAt: plan.createdAt.getTime(),
4366
+ updatedAt: plan.updatedAt.getTime()
4367
+ };
4368
+ }
4369
+
3324
4370
  // src/sync-cache.ts
3325
4371
  import { createHash as createHash2 } from "node:crypto";
3326
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
3327
- import { join as join11 } from "node:path";
4372
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "node:fs";
4373
+ import { join as join12 } from "node:path";
3328
4374
  function getCachePath() {
3329
- return join11(getConfigDir(), "sync-cache.json");
4375
+ return join12(getConfigDir(), "sync-cache.json");
3330
4376
  }
3331
4377
  function loadSyncCache() {
3332
4378
  const cachePath = getCachePath();
3333
- if (!existsSync7(cachePath))
4379
+ if (!existsSync8(cachePath))
3334
4380
  return {};
3335
4381
  try {
3336
4382
  const raw = JSON.parse(readFileSync5(cachePath, "utf-8"));
@@ -3343,7 +4389,7 @@ function loadSyncCache() {
3343
4389
  }
3344
4390
  function saveSyncCache(cache, options) {
3345
4391
  const dir = getConfigDir();
3346
- if (!existsSync7(dir))
4392
+ if (!existsSync8(dir))
3347
4393
  mkdirSync3(dir, { recursive: true });
3348
4394
  const cachePath = getCachePath();
3349
4395
  if (options?.replace) {
@@ -3369,32 +4415,71 @@ function computePayloadHash(payload) {
3369
4415
  return createHash2("sha256").update(canonical).digest("hex").slice(0, 20);
3370
4416
  }
3371
4417
 
4418
+ // src/writeback-delivery-cache.ts
4419
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
4420
+ import { join as join13 } from "node:path";
4421
+ var MAX_PENDING_WRITEBACK_REPORTS = 1000;
4422
+ function getCachePath2() {
4423
+ return join13(getConfigDir(), "plannotator-writebacks-delivered.json");
4424
+ }
4425
+ function isPendingWritebackReportStatus(value) {
4426
+ return value === "sent" || value === "failed" || value === "expired";
4427
+ }
4428
+ function normalizeReports(input) {
4429
+ if (!Array.isArray(input))
4430
+ return [];
4431
+ const reports = new Map;
4432
+ for (const item of input) {
4433
+ if (typeof item === "string" && item.length > 0) {
4434
+ reports.set(item, "sent");
4435
+ continue;
4436
+ }
4437
+ if (item && typeof item === "object" && "id" in item && "status" in item && typeof item.id === "string" && item.id.length > 0 && isPendingWritebackReportStatus(item.status)) {
4438
+ reports.set(item.id, item.status);
4439
+ }
4440
+ }
4441
+ return [...reports.entries()].slice(-MAX_PENDING_WRITEBACK_REPORTS);
4442
+ }
4443
+ function loadPendingWritebackReports() {
4444
+ const cachePath = getCachePath2();
4445
+ if (!existsSync9(cachePath))
4446
+ return new Map;
4447
+ try {
4448
+ return new Map(normalizeReports(JSON.parse(readFileSync6(cachePath, "utf-8"))));
4449
+ } catch {
4450
+ return new Map;
4451
+ }
4452
+ }
4453
+ function savePendingWritebackReports(reports) {
4454
+ try {
4455
+ const dir = getConfigDir();
4456
+ if (!existsSync9(dir))
4457
+ mkdirSync4(dir, { recursive: true });
4458
+ const normalizedReports = normalizeReports([...reports].map(([id, status]) => ({ id, status })));
4459
+ writeFileSync4(getCachePath2(), JSON.stringify(normalizedReports.map(([id, status]) => ({ id, status }))));
4460
+ return true;
4461
+ } catch {
4462
+ return false;
4463
+ }
4464
+ }
4465
+
3372
4466
  // src/daemon.ts
3373
4467
  var MAX_RESTARTS = 5;
3374
4468
  var RESTART_WINDOW_MS = 60000;
3375
4469
  var RESTART_DELAY_MS = 5000;
3376
- function planToPayload(plan) {
3377
- return {
3378
- localPlanId: plan.id,
3379
- agent: plan.agent,
3380
- title: plan.title,
3381
- content: plan.content,
3382
- format: plan.format,
3383
- filePath: plan.filePath,
3384
- workspace: plan.workspace,
3385
- metadata: plan.metadata,
3386
- createdAt: plan.createdAt.getTime(),
3387
- updatedAt: plan.updatedAt.getTime()
3388
- };
3389
- }
4470
+ var PLANNOTATOR_WRITEBACK_POLL_INTERVAL_MS = 15000;
4471
+ var PLANNOTATOR_WRITEBACK_EXPIRED_ERROR = "Write-back expired before delivery.";
4472
+ var PLANNOTATOR_WRITEBACK_FAILED_ERROR = "No live Plannotator session accepted the request-changes payload.";
3390
4473
  async function runWorker() {
3391
4474
  const config = await loadOrInitConfig();
3392
- const adapters = resolveAdapters(config.enabledAdapters);
4475
+ const adapterIds = resolveCliAdapterIds(config);
4476
+ const adapters = resolveAdapters(adapterIds);
3393
4477
  setActiveAdapters(adapters);
3394
- console.log(`[agendex] daemon starting with ${config.enabledAdapters.length} adapters`);
4478
+ console.log(`[agendex] daemon starting with ${adapterIds.length} adapters`);
3395
4479
  await sendHeartbeat();
3396
4480
  const syncCache = loadSyncCache();
3397
4481
  const syncQueue = [];
4482
+ const pendingWritebackReports = loadPendingWritebackReports();
3398
4483
  let syncing = false;
3399
4484
  async function tryRefreshToken() {
3400
4485
  const cfg = loadConfig();
@@ -3413,6 +4498,8 @@ async function runWorker() {
3413
4498
  syncing = true;
3414
4499
  const batch = syncQueue.splice(0);
3415
4500
  let syncedCount = 0;
4501
+ let lowValueSkippedCount = 0;
4502
+ let lowValueDeletedCount = 0;
3416
4503
  let failedCount = 0;
3417
4504
  try {
3418
4505
  for (const payload of batch) {
@@ -3433,35 +4520,134 @@ async function runWorker() {
3433
4520
  failedCount++;
3434
4521
  console.error(`[agendex] sync failed for "${payload.title}": ${result.error}`);
3435
4522
  } else {
3436
- syncedCount++;
4523
+ if (result.skippedLowValue) {
4524
+ lowValueSkippedCount++;
4525
+ if (result.deleted)
4526
+ lowValueDeletedCount++;
4527
+ } else {
4528
+ syncedCount++;
4529
+ }
3437
4530
  syncCache[payload.localPlanId] = computePayloadHash(payload);
3438
4531
  }
3439
4532
  }
3440
4533
  } catch (err) {
3441
4534
  console.error("[agendex] sync error:", err);
3442
- syncQueue.unshift(...batch.slice(syncedCount + failedCount));
4535
+ syncQueue.unshift(...batch.slice(syncedCount + lowValueSkippedCount + failedCount));
3443
4536
  } finally {
3444
4537
  syncing = false;
3445
4538
  }
3446
- if (syncedCount > 0 || failedCount > 0) {
4539
+ if (syncedCount > 0 || lowValueSkippedCount > 0 || failedCount > 0) {
3447
4540
  saveSyncCache(syncCache);
3448
- console.log(`[agendex] sync complete: ${syncedCount} synced, ${failedCount} failed`);
4541
+ const lowValueSuffix2 = lowValueSkippedCount > 0 ? `, ${lowValueSkippedCount} low-value skipped/pruned${lowValueDeletedCount > 0 ? ` (${lowValueDeletedCount} deleted)` : ""}` : "";
4542
+ console.log(`[agendex] sync complete: ${syncedCount} synced${lowValueSuffix2}, ${failedCount} failed`);
3449
4543
  }
3450
4544
  if (syncQueue.length > 0)
3451
4545
  processSyncQueue();
3452
4546
  }
4547
+ function persistPendingWritebackReports() {
4548
+ if (!savePendingWritebackReports(pendingWritebackReports)) {
4549
+ console.error("[agendex] failed to persist Plannotator write-back delivery cache");
4550
+ }
4551
+ }
4552
+ async function reportPendingWriteback(writebackId) {
4553
+ const status = pendingWritebackReports.get(writebackId);
4554
+ if (!status)
4555
+ return;
4556
+ const reported = await reportPlannotatorWriteback(writebackId, status, status === "expired" ? PLANNOTATOR_WRITEBACK_EXPIRED_ERROR : status === "failed" ? PLANNOTATOR_WRITEBACK_FAILED_ERROR : undefined);
4557
+ if (reported) {
4558
+ pendingWritebackReports.delete(writebackId);
4559
+ persistPendingWritebackReports();
4560
+ } else {
4561
+ console.error("[agendex] failed to report write-back status for", writebackId, "- will retry");
4562
+ }
4563
+ }
4564
+ async function handlePlannotatorWriteback(job) {
4565
+ if (pendingWritebackReports.has(job._id)) {
4566
+ await reportPendingWriteback(job._id);
4567
+ return;
4568
+ }
4569
+ if (job.expiresAt <= Date.now()) {
4570
+ pendingWritebackReports.set(job._id, "expired");
4571
+ persistPendingWritebackReports();
4572
+ await reportPendingWriteback(job._id);
4573
+ return;
4574
+ }
4575
+ let localPlan = getById(job.localPlanId);
4576
+ if (!localPlan) {
4577
+ await scan();
4578
+ localPlan = getById(job.localPlanId);
4579
+ }
4580
+ if (!localPlan) {
4581
+ if (job.deviceId) {
4582
+ pendingWritebackReports.set(job._id, "failed");
4583
+ persistPendingWritebackReports();
4584
+ await reportPendingWriteback(job._id);
4585
+ }
4586
+ return;
4587
+ }
4588
+ const ok = await requestChanges(job.localPlanId, {
4589
+ feedback: job.feedback,
4590
+ revisedContent: job.revisedContent,
4591
+ annotations: job.annotations,
4592
+ source: job.source,
4593
+ writebackId: job._id,
4594
+ requestedAt: Date.now()
4595
+ });
4596
+ if (ok) {
4597
+ const updatedPlan = getById(job.localPlanId);
4598
+ if (updatedPlan)
4599
+ syncQueue.push(planToSyncPayload(updatedPlan, config.deviceId));
4600
+ pendingWritebackReports.set(job._id, "sent");
4601
+ persistPendingWritebackReports();
4602
+ await reportPendingWriteback(job._id);
4603
+ processSyncQueue();
4604
+ return;
4605
+ }
4606
+ pendingWritebackReports.set(job._id, "failed");
4607
+ persistPendingWritebackReports();
4608
+ await reportPendingWriteback(job._id);
4609
+ }
4610
+ let pollingWritebacks = false;
4611
+ async function pollPlannotatorWritebacks() {
4612
+ if (pollingWritebacks)
4613
+ return;
4614
+ pollingWritebacks = true;
4615
+ try {
4616
+ const jobs = await fetchPlannotatorWritebacks();
4617
+ for (const job of jobs) {
4618
+ await handlePlannotatorWriteback(job);
4619
+ }
4620
+ } catch (err) {
4621
+ if (err instanceof Error && err.name === "AuthExpiredError") {
4622
+ console.error("[agendex] session expired. Run `agendex login` to re-authenticate.");
4623
+ } else {
4624
+ console.error("[agendex] Plannotator write-back polling failed:", err);
4625
+ }
4626
+ } finally {
4627
+ pollingWritebacks = false;
4628
+ }
4629
+ }
3453
4630
  setOnPlansChanged(() => {});
3454
4631
  console.log(`[agendex] initial scan...`);
3455
4632
  await scan();
3456
4633
  const plans = getAll();
4634
+ const syncablePlanCount = plans.filter(isIndexablePlan).length;
4635
+ const lowValuePlanCount = plans.length - syncablePlanCount;
3457
4636
  let initialSkipped = 0;
4637
+ let initialQueuedSyncable = 0;
4638
+ let initialQueuedLowValue = 0;
3458
4639
  for (const plan of plans) {
3459
- const payload = planToPayload(plan);
4640
+ const payload = planToSyncPayload(plan, config.deviceId);
3460
4641
  const hash = computePayloadHash(payload);
3461
4642
  if (syncCache[plan.id] === hash) {
3462
4643
  initialSkipped++;
3463
4644
  continue;
3464
4645
  }
4646
+ if (isLowValuePlan(plan)) {
4647
+ initialQueuedLowValue++;
4648
+ } else {
4649
+ initialQueuedSyncable++;
4650
+ }
3465
4651
  syncQueue.push(payload);
3466
4652
  }
3467
4653
  const activePlanIds = new Set(plans.map((plan) => plan.id));
@@ -3470,12 +4656,17 @@ async function runWorker() {
3470
4656
  delete syncCache[id];
3471
4657
  }
3472
4658
  saveSyncCache(syncCache, { replace: true });
3473
- console.log(`[agendex] syncing ${syncQueue.length} plans (${initialSkipped} unchanged)...`);
4659
+ const lowValueSuffix = lowValuePlanCount > 0 ? `, ${lowValuePlanCount} low-value hidden/pruned` : "";
4660
+ console.log(`[agendex] syncing ${initialQueuedSyncable} plans${lowValueSuffix} (${initialQueuedLowValue} low-value queued, ${initialSkipped} unchanged)...`);
3474
4661
  await processSyncQueue();
3475
4662
  setInterval(() => void sendHeartbeat(), CLI_DAEMON_HEARTBEAT_INTERVAL_MS);
4663
+ if (shouldEnablePlannotatorSync(config)) {
4664
+ setInterval(() => void pollPlannotatorWritebacks(), PLANNOTATOR_WRITEBACK_POLL_INTERVAL_MS);
4665
+ pollPlannotatorWritebacks();
4666
+ }
3476
4667
  startWatching((changedPlans) => {
3477
4668
  for (const plan of changedPlans) {
3478
- syncQueue.push(planToPayload(plan));
4669
+ syncQueue.push(planToSyncPayload(plan, config.deviceId));
3479
4670
  }
3480
4671
  processSyncQueue();
3481
4672
  });
@@ -3505,17 +4696,17 @@ async function startSupervisor() {
3505
4696
  };
3506
4697
  process.on("SIGTERM", shutdown);
3507
4698
  process.on("SIGINT", shutdown);
3508
- const scriptPath = resolve5(process.argv[1] ?? fileURLToPath(new URL("./cli.ts", import.meta.url)));
4699
+ const scriptPath = resolve6(process.argv[1] ?? fileURLToPath(new URL("./cli.ts", import.meta.url)));
3509
4700
  const restartTimes = [];
3510
4701
  while (!stopping) {
3511
4702
  workerProc = spawn2(process.execPath, [scriptPath, "start", "--worker"], {
3512
4703
  stdio: ["ignore", "inherit", "inherit"]
3513
4704
  });
3514
- const exitCode = await new Promise((resolve6) => {
3515
- workerProc?.once("exit", (code) => resolve6(code));
4705
+ const exitCode = await new Promise((resolve7) => {
4706
+ workerProc?.once("exit", (code) => resolve7(code));
3516
4707
  workerProc?.once("error", (error) => {
3517
4708
  console.error("[agendex] failed to spawn worker:", error);
3518
- resolve6(1);
4709
+ resolve7(1);
3519
4710
  });
3520
4711
  });
3521
4712
  workerProc = null;
@@ -3540,31 +4731,26 @@ async function startSupervisor() {
3540
4731
  // src/sync.ts
3541
4732
  async function syncAll(force = false) {
3542
4733
  const config = await loadOrInitConfig();
3543
- const adapters = resolveAdapters(config.enabledAdapters);
4734
+ const adapterIds = resolveCliAdapterIds(config);
4735
+ const adapters = resolveAdapters(adapterIds);
3544
4736
  setActiveAdapters(adapters);
3545
4737
  console.log(`[agendex] Scanning local plans...`);
3546
4738
  await scan();
3547
- const plans = getAll();
3548
- console.log(`[agendex] Found ${plans.length} plans. Syncing to cloud...`);
4739
+ const allPlans = getAll();
4740
+ const syncablePlans = allPlans.filter(isIndexablePlan);
4741
+ const lowValuePlans = allPlans.filter(isLowValuePlan);
4742
+ const hiddenSuffix = lowValuePlans.length > 0 ? ` (${lowValuePlans.length} low-value hidden/pruned)` : "";
4743
+ console.log(`[agendex] Found ${syncablePlans.length} syncable plans${hiddenSuffix}. Syncing to cloud...`);
3549
4744
  const cache = force ? {} : loadSyncCache();
3550
4745
  const activePlanIds = new Set;
3551
4746
  let synced = 0;
4747
+ let lowValueSkipped = 0;
4748
+ let lowValueDeleted = 0;
3552
4749
  let skipped = 0;
3553
4750
  let failed = 0;
3554
- for (const plan of plans) {
4751
+ for (const plan of [...syncablePlans, ...lowValuePlans]) {
3555
4752
  activePlanIds.add(plan.id);
3556
- const payload = {
3557
- localPlanId: plan.id,
3558
- agent: plan.agent,
3559
- title: plan.title,
3560
- content: plan.content,
3561
- format: plan.format,
3562
- filePath: plan.filePath,
3563
- workspace: plan.workspace,
3564
- metadata: plan.metadata,
3565
- createdAt: plan.createdAt.getTime(),
3566
- updatedAt: plan.updatedAt.getTime()
3567
- };
4753
+ const payload = planToSyncPayload(plan, config.deviceId);
3568
4754
  const hash = computePayloadHash(payload);
3569
4755
  if (!force && cache[plan.id] === hash) {
3570
4756
  skipped++;
@@ -3572,7 +4758,13 @@ async function syncAll(force = false) {
3572
4758
  }
3573
4759
  const result = await syncPlan(payload);
3574
4760
  if (result.ok) {
3575
- synced++;
4761
+ if (result.skippedLowValue) {
4762
+ lowValueSkipped++;
4763
+ if (result.deleted)
4764
+ lowValueDeleted++;
4765
+ } else {
4766
+ synced++;
4767
+ }
3576
4768
  cache[plan.id] = hash;
3577
4769
  } else {
3578
4770
  failed++;
@@ -3584,17 +4776,18 @@ async function syncAll(force = false) {
3584
4776
  delete cache[id];
3585
4777
  }
3586
4778
  saveSyncCache(cache, { replace: true });
3587
- console.log(`[agendex] Sync complete: ${synced} synced, ${skipped} unchanged, ${failed} failed`);
4779
+ const lowValueSuffix = lowValueSkipped > 0 ? `, ${lowValueSkipped} low-value skipped/pruned${lowValueDeleted > 0 ? ` (${lowValueDeleted} deleted)` : ""}` : "";
4780
+ console.log(`[agendex] Sync complete: ${synced} synced${lowValueSuffix}, ${skipped} unchanged, ${failed} failed`);
3588
4781
  }
3589
4782
 
3590
4783
  // src/version.ts
3591
- import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
4784
+ import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "node:fs";
3592
4785
  import { tmpdir } from "node:os";
3593
- import { join as join12 } from "node:path";
4786
+ import { join as join14 } from "node:path";
3594
4787
  // package.json
3595
4788
  var package_default = {
3596
4789
  name: "agendex-cli",
3597
- version: "0.13.0",
4790
+ version: "0.15.0",
3598
4791
  description: "Agendex CLI for login, sync, and daemon workflows",
3599
4792
  homepage: "https://github.com/Tyru5/Agendex#readme",
3600
4793
  repository: {
@@ -3638,14 +4831,14 @@ var package_default = {
3638
4831
 
3639
4832
  // src/version.ts
3640
4833
  var CLI_VERSION = package_default.version;
3641
- var CACHE_FILE = process.env.AGENDEX_UPDATE_CACHE_FILE ?? join12(tmpdir(), ".agendex-update-cache.json");
4834
+ var CACHE_FILE = process.env.AGENDEX_UPDATE_CACHE_FILE ?? join14(tmpdir(), ".agendex-update-cache.json");
3642
4835
  var CACHE_TTL_MS = 24 * 60 * 60 * 1000;
3643
4836
  var UPDATE_URL = process.env.AGENDEX_UPDATE_URL ?? "https://registry.npmjs.org/agendex-cli/latest";
3644
4837
  function readCache(current) {
3645
4838
  try {
3646
- if (!existsSync8(CACHE_FILE))
4839
+ if (!existsSync10(CACHE_FILE))
3647
4840
  return null;
3648
- const { result, ts } = JSON.parse(readFileSync6(CACHE_FILE, "utf8"));
4841
+ const { result, ts } = JSON.parse(readFileSync7(CACHE_FILE, "utf8"));
3649
4842
  if (Date.now() - ts > CACHE_TTL_MS)
3650
4843
  return null;
3651
4844
  return normalizeResult(result, current);
@@ -3655,7 +4848,7 @@ function readCache(current) {
3655
4848
  }
3656
4849
  function writeCache(result) {
3657
4850
  try {
3658
- writeFileSync4(CACHE_FILE, JSON.stringify({ result, ts: Date.now() }));
4851
+ writeFileSync5(CACHE_FILE, JSON.stringify({ result, ts: Date.now() }));
3659
4852
  } catch {}
3660
4853
  }
3661
4854
  async function checkForUpdate() {
@@ -3741,7 +4934,7 @@ function firstCommandToken(argv) {
3741
4934
  return;
3742
4935
  }
3743
4936
  var command = firstCommandToken(args) ?? "start";
3744
- var cliEntry = resolve6(process.argv[1] ?? fileURLToPath2(import.meta.url));
4937
+ var cliEntry = resolve7(process.argv[1] ?? fileURLToPath2(import.meta.url));
3745
4938
  async function main() {
3746
4939
  const isInternal = args.includes("--daemon") || args.includes("--worker");
3747
4940
  if (command === "--version" || command === "-v") {
@@ -3941,7 +5134,7 @@ async function main() {
3941
5134
  return 1;
3942
5135
  }
3943
5136
  const resolved = resolveCustomPlanDirPath(dirPath);
3944
- if (!existsSync9(resolved)) {
5137
+ if (!existsSync11(resolved)) {
3945
5138
  writeStderr(`[agendex] path does not exist: ${resolved}`);
3946
5139
  return 1;
3947
5140
  }
@@ -3960,7 +5153,7 @@ async function main() {
3960
5153
  const { request } = await import("node:http");
3961
5154
  const body = JSON.stringify({ path: resolved });
3962
5155
  try {
3963
- const res = await new Promise((resolve7, reject) => {
5156
+ const res = await new Promise((resolve8, reject) => {
3964
5157
  const req = request(`http://localhost:${port}/api/v1/plan-sources`, {
3965
5158
  method: "POST",
3966
5159
  headers: {
@@ -3974,7 +5167,7 @@ async function main() {
3974
5167
  res2.on("data", (chunk) => {
3975
5168
  data += chunk;
3976
5169
  });
3977
- res2.on("end", () => resolve7({ status: res2.statusCode ?? 0, body: data }));
5170
+ res2.on("end", () => resolve8({ status: res2.statusCode ?? 0, body: data }));
3978
5171
  res2.on("error", reject);
3979
5172
  });
3980
5173
  req.on("error", reject);
@@ -4156,13 +5349,13 @@ function flushStream(stream) {
4156
5349
  if (stream.destroyed || !stream.writable) {
4157
5350
  return Promise.resolve();
4158
5351
  }
4159
- return new Promise((resolve7, reject) => {
5352
+ return new Promise((resolve8, reject) => {
4160
5353
  stream.write("", (error) => {
4161
5354
  if (error) {
4162
5355
  reject(error);
4163
5356
  return;
4164
5357
  }
4165
- resolve7();
5358
+ resolve8();
4166
5359
  });
4167
5360
  });
4168
5361
  }