agent-handoff 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/README.md +32 -0
- package/dist/index.js +896 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { createRequire } from "module";
|
|
5
|
-
import { Command as
|
|
5
|
+
import { Command as Command13 } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/cli/commands/init.ts
|
|
8
8
|
import { Command } from "commander";
|
|
@@ -178,6 +178,14 @@ async function detectStepOutputs(workspacePath, workflow) {
|
|
|
178
178
|
}
|
|
179
179
|
return outputs;
|
|
180
180
|
}
|
|
181
|
+
async function fileExists(filePath) {
|
|
182
|
+
try {
|
|
183
|
+
await fs3.access(filePath);
|
|
184
|
+
return true;
|
|
185
|
+
} catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
181
189
|
|
|
182
190
|
// src/core/state-machine.ts
|
|
183
191
|
function computeState(workflow, stepOutputs) {
|
|
@@ -1777,10 +1785,892 @@ var exportCommand = new Command8("export").description("\u5BFC\u51FA\u9759\u6001
|
|
|
1777
1785
|
}
|
|
1778
1786
|
);
|
|
1779
1787
|
|
|
1788
|
+
// src/cli/commands/index.ts
|
|
1789
|
+
import { Command as Command9 } from "commander";
|
|
1790
|
+
import fs14 from "fs/promises";
|
|
1791
|
+
import path16 from "path";
|
|
1792
|
+
|
|
1793
|
+
// src/core/index/indexer.ts
|
|
1794
|
+
import fs12 from "fs/promises";
|
|
1795
|
+
import path14 from "path";
|
|
1796
|
+
async function readPreview(filePath, maxChars) {
|
|
1797
|
+
try {
|
|
1798
|
+
const content = await fs12.readFile(filePath, "utf-8");
|
|
1799
|
+
const sliced = content.slice(0, maxChars);
|
|
1800
|
+
return sliced;
|
|
1801
|
+
} catch {
|
|
1802
|
+
return void 0;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
async function statInfo(filePath) {
|
|
1806
|
+
try {
|
|
1807
|
+
const st = await fs12.stat(filePath);
|
|
1808
|
+
return { bytes: st.size, updatedAt: st.mtime.toISOString() };
|
|
1809
|
+
} catch {
|
|
1810
|
+
return {};
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
async function addArtifact(workspacePath, relPath, kind) {
|
|
1814
|
+
const full = path14.join(workspacePath, relPath);
|
|
1815
|
+
const exists = await fileExists(full);
|
|
1816
|
+
if (!exists) {
|
|
1817
|
+
return { path: relPath, kind };
|
|
1818
|
+
}
|
|
1819
|
+
const { bytes, updatedAt } = await statInfo(full);
|
|
1820
|
+
const preview = await readPreview(full, 2e3);
|
|
1821
|
+
return { path: relPath, kind, bytes, updatedAt, ...preview !== void 0 && { preview } };
|
|
1822
|
+
}
|
|
1823
|
+
async function buildWorkspaceIndex(workspacePath) {
|
|
1824
|
+
const info = await loadWorkspace(workspacePath);
|
|
1825
|
+
if (!info.exists) {
|
|
1826
|
+
throw new Error(`workspace not found: ${path14.resolve(workspacePath)}`);
|
|
1827
|
+
}
|
|
1828
|
+
if (!info.hasWorkflow || !info.workflow) {
|
|
1829
|
+
throw new Error(`workflow.yaml not found in ${path14.resolve(workspacePath)}`);
|
|
1830
|
+
}
|
|
1831
|
+
const abs = info.path;
|
|
1832
|
+
const indexedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1833
|
+
const workspaceName = path14.basename(abs);
|
|
1834
|
+
const steps = await Promise.all(
|
|
1835
|
+
info.workflow.steps.map(async (step, i) => {
|
|
1836
|
+
const outputRel = step.output;
|
|
1837
|
+
const outputFull = path14.join(abs, outputRel);
|
|
1838
|
+
const outputExists = await fileExists(outputFull);
|
|
1839
|
+
return { id: step.id, index: i + 1, outputPath: outputRel, outputExists };
|
|
1840
|
+
})
|
|
1841
|
+
);
|
|
1842
|
+
const artifacts = [];
|
|
1843
|
+
const briefRel = "brief.md";
|
|
1844
|
+
if (await fileExists(path14.join(abs, briefRel))) {
|
|
1845
|
+
artifacts.push(await addArtifact(abs, briefRel, "brief"));
|
|
1846
|
+
}
|
|
1847
|
+
for (const step of info.workflow.steps) {
|
|
1848
|
+
const rel = step.output;
|
|
1849
|
+
artifacts.push(await addArtifact(abs, rel, "step.output"));
|
|
1850
|
+
}
|
|
1851
|
+
const { events } = await readEventsJsonl({ workspacePath: abs });
|
|
1852
|
+
const items = toTimelineItems(events);
|
|
1853
|
+
const indexedEvents = items.map((item) => ({
|
|
1854
|
+
ts: item.ts,
|
|
1855
|
+
stepId: item.stepId,
|
|
1856
|
+
stepIndex: item.stepIndex,
|
|
1857
|
+
type: item.type,
|
|
1858
|
+
summary: item.summary,
|
|
1859
|
+
...item.workItemId && { workItemId: item.workItemId },
|
|
1860
|
+
...item.links && item.links.length > 0 && { links: item.links }
|
|
1861
|
+
}));
|
|
1862
|
+
return {
|
|
1863
|
+
version: 1,
|
|
1864
|
+
workspacePath: abs,
|
|
1865
|
+
workspaceName,
|
|
1866
|
+
indexedAt,
|
|
1867
|
+
workflowName: info.workflow.name,
|
|
1868
|
+
steps,
|
|
1869
|
+
artifacts,
|
|
1870
|
+
events: indexedEvents
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
// src/core/index/registry.ts
|
|
1875
|
+
import fs13 from "fs/promises";
|
|
1876
|
+
import os3 from "os";
|
|
1877
|
+
import path15 from "path";
|
|
1878
|
+
function getHomeDir() {
|
|
1879
|
+
try {
|
|
1880
|
+
const envHome = process.env.HOME;
|
|
1881
|
+
if (envHome && envHome.trim()) return envHome;
|
|
1882
|
+
const home = os3.homedir();
|
|
1883
|
+
if (home && home.trim()) return home;
|
|
1884
|
+
return null;
|
|
1885
|
+
} catch {
|
|
1886
|
+
return null;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
function defaultRegistryFilePath() {
|
|
1890
|
+
const home = getHomeDir();
|
|
1891
|
+
const base = home ? home : process.cwd();
|
|
1892
|
+
return path15.join(base, ".agenthandoff", "registry.json");
|
|
1893
|
+
}
|
|
1894
|
+
async function ensureParentDir(filePath) {
|
|
1895
|
+
await fs13.mkdir(path15.dirname(filePath), { recursive: true });
|
|
1896
|
+
}
|
|
1897
|
+
function normalizeRegistry(registry) {
|
|
1898
|
+
if (typeof registry === "object" && registry !== null && "version" in registry && registry.version === 1 && "items" in registry && Array.isArray(registry.items)) {
|
|
1899
|
+
const items = registry.items.map((item) => {
|
|
1900
|
+
if (typeof item !== "object" || item === null) return null;
|
|
1901
|
+
const obj = item;
|
|
1902
|
+
const name = typeof obj.name === "string" ? obj.name : "";
|
|
1903
|
+
const itemPath = typeof obj.path === "string" ? obj.path : "";
|
|
1904
|
+
const addedAt = typeof obj.addedAt === "string" ? obj.addedAt : "";
|
|
1905
|
+
const updatedAt = typeof obj.updatedAt === "string" ? obj.updatedAt : "";
|
|
1906
|
+
if (!name || !itemPath) return null;
|
|
1907
|
+
return { name, path: itemPath, addedAt, updatedAt };
|
|
1908
|
+
}).filter((x) => Boolean(x));
|
|
1909
|
+
return { version: 1, items };
|
|
1910
|
+
}
|
|
1911
|
+
return { version: 1, items: [] };
|
|
1912
|
+
}
|
|
1913
|
+
function createDefaultRegistryStore() {
|
|
1914
|
+
const filePath = defaultRegistryFilePath();
|
|
1915
|
+
return {
|
|
1916
|
+
async load() {
|
|
1917
|
+
try {
|
|
1918
|
+
const raw = await fs13.readFile(filePath, "utf-8");
|
|
1919
|
+
const parsed = JSON.parse(raw);
|
|
1920
|
+
return normalizeRegistry(parsed);
|
|
1921
|
+
} catch {
|
|
1922
|
+
return { version: 1, items: [] };
|
|
1923
|
+
}
|
|
1924
|
+
},
|
|
1925
|
+
async save(registry) {
|
|
1926
|
+
await ensureParentDir(filePath);
|
|
1927
|
+
const normalized = normalizeRegistry(registry);
|
|
1928
|
+
await fs13.writeFile(filePath, JSON.stringify(normalized, null, 2) + "\n", "utf-8");
|
|
1929
|
+
}
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
function defaultNameForPath(workspacePath) {
|
|
1933
|
+
const abs = path15.resolve(workspacePath);
|
|
1934
|
+
return path15.basename(abs) || abs;
|
|
1935
|
+
}
|
|
1936
|
+
async function registerWorkspace(workspacePath, name) {
|
|
1937
|
+
const store = createDefaultRegistryStore();
|
|
1938
|
+
const registry = await store.load();
|
|
1939
|
+
const absPath = path15.resolve(workspacePath);
|
|
1940
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1941
|
+
const itemName = name && name.trim() ? name.trim() : defaultNameForPath(absPath);
|
|
1942
|
+
const existingIndex = registry.items.findIndex((i) => path15.resolve(i.path) === absPath);
|
|
1943
|
+
if (existingIndex >= 0) {
|
|
1944
|
+
const existing = registry.items[existingIndex];
|
|
1945
|
+
registry.items[existingIndex] = {
|
|
1946
|
+
...existing,
|
|
1947
|
+
name: itemName,
|
|
1948
|
+
updatedAt: now,
|
|
1949
|
+
addedAt: existing.addedAt || now,
|
|
1950
|
+
path: absPath
|
|
1951
|
+
};
|
|
1952
|
+
} else {
|
|
1953
|
+
registry.items.push({
|
|
1954
|
+
name: itemName,
|
|
1955
|
+
path: absPath,
|
|
1956
|
+
addedAt: now,
|
|
1957
|
+
updatedAt: now
|
|
1958
|
+
});
|
|
1959
|
+
}
|
|
1960
|
+
await store.save(registry);
|
|
1961
|
+
}
|
|
1962
|
+
async function unregisterWorkspace(pathOrName) {
|
|
1963
|
+
const store = createDefaultRegistryStore();
|
|
1964
|
+
const registry = await store.load();
|
|
1965
|
+
const token = String(pathOrName).trim();
|
|
1966
|
+
if (!token) return;
|
|
1967
|
+
const abs = path15.resolve(token);
|
|
1968
|
+
registry.items = registry.items.filter((item) => {
|
|
1969
|
+
const itemAbs = path15.resolve(item.path);
|
|
1970
|
+
return itemAbs !== abs && item.name !== token;
|
|
1971
|
+
});
|
|
1972
|
+
await store.save(registry);
|
|
1973
|
+
}
|
|
1974
|
+
async function listWorkspaces() {
|
|
1975
|
+
const store = createDefaultRegistryStore();
|
|
1976
|
+
const registry = await store.load();
|
|
1977
|
+
return [...registry.items].sort((a, b) => a.name.localeCompare(b.name));
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
// src/cli/commands/index.ts
|
|
1981
|
+
async function ensureDir2(dir) {
|
|
1982
|
+
await fs14.mkdir(dir, { recursive: true });
|
|
1983
|
+
}
|
|
1984
|
+
var indexCommand = new Command9("index").description("\u751F\u6210 workspace \u7D22\u5F15\uFF0C\u5E76\u652F\u6301 registry \u7BA1\u7406").argument("[workspace]", "workspace \u8DEF\u5F84", ".").option("--add", "\u5C06 workspace \u52A0\u5165 registry").option("--list", "\u5217\u51FA registry \u4E2D\u7684 workspaces").option("--remove <pathOrName>", "\u4ECE registry \u5220\u9664 workspace\uFF08path \u6216 name\uFF09").option("--output <file>", "\u8F93\u51FA\u7D22\u5F15\u6587\u4EF6\u8DEF\u5F84\uFF08\u9ED8\u8BA4 <workspace>/.agenthandoff/index.json\uFF09").option("--json", "\u4EE5 JSON \u8F93\u51FA\u7ED3\u679C\u5230 stdout").action(
|
|
1985
|
+
async (workspace, options) => {
|
|
1986
|
+
try {
|
|
1987
|
+
if (options.list) {
|
|
1988
|
+
const items = await listWorkspaces();
|
|
1989
|
+
if (options.json) {
|
|
1990
|
+
console.log(JSON.stringify(items, null, 2));
|
|
1991
|
+
return;
|
|
1992
|
+
}
|
|
1993
|
+
if (items.length === 0) {
|
|
1994
|
+
console.log("Registry is empty");
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
for (const item of items) {
|
|
1998
|
+
console.log(`${item.name} ${item.path}`);
|
|
1999
|
+
}
|
|
2000
|
+
return;
|
|
2001
|
+
}
|
|
2002
|
+
if (options.remove) {
|
|
2003
|
+
await unregisterWorkspace(options.remove);
|
|
2004
|
+
console.log(`\u2705 Removed from registry: ${options.remove}`);
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
const workspacePath = path16.resolve(workspace);
|
|
2008
|
+
const info = await loadWorkspace(workspacePath);
|
|
2009
|
+
if (!info.exists) {
|
|
2010
|
+
console.error(`Error: workspace not found: ${workspacePath}`);
|
|
2011
|
+
process.exit(1);
|
|
2012
|
+
}
|
|
2013
|
+
if (!info.hasWorkflow || !info.workflow) {
|
|
2014
|
+
console.error(`Error: workflow.yaml not found in ${workspacePath}`);
|
|
2015
|
+
process.exit(1);
|
|
2016
|
+
}
|
|
2017
|
+
if (options.add) {
|
|
2018
|
+
await registerWorkspace(workspacePath, info.workflow.name);
|
|
2019
|
+
}
|
|
2020
|
+
const index = await buildWorkspaceIndex(workspacePath);
|
|
2021
|
+
const outputFile = options.output ? path16.resolve(options.output) : path16.join(workspacePath, ".agenthandoff", "index.json");
|
|
2022
|
+
await ensureDir2(path16.dirname(outputFile));
|
|
2023
|
+
await fs14.writeFile(outputFile, JSON.stringify(index, null, 2) + "\n", "utf-8");
|
|
2024
|
+
if (options.json) {
|
|
2025
|
+
console.log(JSON.stringify(index, null, 2));
|
|
2026
|
+
return;
|
|
2027
|
+
}
|
|
2028
|
+
console.log(`\u2705 Indexed: ${workspacePath}`);
|
|
2029
|
+
console.log(` output: ${outputFile}`);
|
|
2030
|
+
console.log(` steps: ${index.steps.length}`);
|
|
2031
|
+
console.log(` artifacts: ${index.artifacts.length}`);
|
|
2032
|
+
console.log(` events: ${index.events.length}`);
|
|
2033
|
+
} catch (error) {
|
|
2034
|
+
console.error(`Error: ${error}`);
|
|
2035
|
+
process.exit(1);
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
);
|
|
2039
|
+
|
|
2040
|
+
// src/cli/commands/search.ts
|
|
2041
|
+
import { Command as Command10 } from "commander";
|
|
2042
|
+
|
|
2043
|
+
// src/core/search/search.ts
|
|
2044
|
+
import fs15 from "fs/promises";
|
|
2045
|
+
import path17 from "path";
|
|
2046
|
+
async function readIndexFile(workspacePath, indexDir) {
|
|
2047
|
+
const indexFile = indexDir ? path17.join(indexDir, "index.json") : path17.join(workspacePath, ".agenthandoff", "index.json");
|
|
2048
|
+
try {
|
|
2049
|
+
const raw = await fs15.readFile(indexFile, "utf-8");
|
|
2050
|
+
return JSON.parse(raw);
|
|
2051
|
+
} catch {
|
|
2052
|
+
return null;
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
function toLower(input) {
|
|
2056
|
+
return input.toLowerCase();
|
|
2057
|
+
}
|
|
2058
|
+
function normalizeList(values) {
|
|
2059
|
+
if (!values || values.length === 0) return void 0;
|
|
2060
|
+
return values.map((v) => v.trim()).filter((v) => v.length > 0);
|
|
2061
|
+
}
|
|
2062
|
+
function textIncludes(haystack, needle) {
|
|
2063
|
+
return toLower(haystack).includes(needle);
|
|
2064
|
+
}
|
|
2065
|
+
function matchOptional(value, filter) {
|
|
2066
|
+
if (!filter || filter.length === 0) return true;
|
|
2067
|
+
if (!value) return false;
|
|
2068
|
+
return filter.includes(value);
|
|
2069
|
+
}
|
|
2070
|
+
function makeSnippet(text, q) {
|
|
2071
|
+
const lower = toLower(text);
|
|
2072
|
+
const idx = lower.indexOf(q);
|
|
2073
|
+
if (idx < 0) {
|
|
2074
|
+
return text.length > 160 ? text.slice(0, 160) : text;
|
|
2075
|
+
}
|
|
2076
|
+
const start = Math.max(0, idx - 40);
|
|
2077
|
+
const end = Math.min(text.length, idx + q.length + 80);
|
|
2078
|
+
return text.slice(start, end);
|
|
2079
|
+
}
|
|
2080
|
+
function scoreHit(hit) {
|
|
2081
|
+
return hit.score;
|
|
2082
|
+
}
|
|
2083
|
+
async function resolveTargets(query, options) {
|
|
2084
|
+
const workspaceFilters = normalizeList(query.workspace);
|
|
2085
|
+
const registryItems = options.registryOnly ? await listWorkspaces() : [];
|
|
2086
|
+
const targets = [];
|
|
2087
|
+
if (registryItems.length > 0) {
|
|
2088
|
+
for (const item of registryItems) {
|
|
2089
|
+
if (!workspaceFilters || workspaceFilters.includes(item.name) || workspaceFilters.includes(item.path)) {
|
|
2090
|
+
targets.push({ name: item.name, path: item.path });
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
if (!options.registryOnly && workspaceFilters && workspaceFilters.length > 0) {
|
|
2095
|
+
for (const token of workspaceFilters) {
|
|
2096
|
+
const exists = targets.some((t) => t.path === token || t.name === token);
|
|
2097
|
+
if (!exists) {
|
|
2098
|
+
targets.push({ name: path17.basename(token), path: token });
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
return targets;
|
|
2103
|
+
}
|
|
2104
|
+
async function search(query, options) {
|
|
2105
|
+
const q = query.q.trim();
|
|
2106
|
+
const qLower = toLower(q);
|
|
2107
|
+
const stepFilter = normalizeList(query.stepId);
|
|
2108
|
+
const typeFilter = normalizeList(query.type);
|
|
2109
|
+
const workItemFilter = normalizeList(query.workItemId);
|
|
2110
|
+
const limit = query.limit && query.limit > 0 ? query.limit : void 0;
|
|
2111
|
+
const targets = await resolveTargets(query, options);
|
|
2112
|
+
const hits = [];
|
|
2113
|
+
for (const target of targets) {
|
|
2114
|
+
const index = await readIndexFile(target.path, options.indexDir);
|
|
2115
|
+
if (!index) {
|
|
2116
|
+
continue;
|
|
2117
|
+
}
|
|
2118
|
+
const workspaceName = index.workspaceName || target.name;
|
|
2119
|
+
const workspacePath = index.workspacePath || target.path;
|
|
2120
|
+
if (options.targets === "events" || options.targets === "all") {
|
|
2121
|
+
const events = index.events || [];
|
|
2122
|
+
for (const event of events) {
|
|
2123
|
+
if (!matchOptional(event.stepId, stepFilter)) continue;
|
|
2124
|
+
if (!matchOptional(event.type, typeFilter)) continue;
|
|
2125
|
+
if (!matchOptional(event.workItemId, workItemFilter)) continue;
|
|
2126
|
+
if (!textIncludes(event.summary || "", qLower)) continue;
|
|
2127
|
+
hits.push({
|
|
2128
|
+
workspaceName,
|
|
2129
|
+
workspacePath,
|
|
2130
|
+
kind: "event",
|
|
2131
|
+
score: 1,
|
|
2132
|
+
title: `${event.type} \xB7 ${event.stepId}`,
|
|
2133
|
+
snippet: makeSnippet(event.summary || "", qLower),
|
|
2134
|
+
meta: {
|
|
2135
|
+
ts: event.ts,
|
|
2136
|
+
stepId: event.stepId,
|
|
2137
|
+
stepIndex: event.stepIndex,
|
|
2138
|
+
type: event.type,
|
|
2139
|
+
workItemId: event.workItemId
|
|
2140
|
+
}
|
|
2141
|
+
});
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
if (options.targets === "artifacts" || options.targets === "all") {
|
|
2145
|
+
const artifacts = index.artifacts || [];
|
|
2146
|
+
for (const artifact of artifacts) {
|
|
2147
|
+
const preview = artifact.preview || "";
|
|
2148
|
+
if (!textIncludes(preview, qLower)) continue;
|
|
2149
|
+
hits.push({
|
|
2150
|
+
workspaceName,
|
|
2151
|
+
workspacePath,
|
|
2152
|
+
kind: "artifact",
|
|
2153
|
+
score: 1,
|
|
2154
|
+
title: artifact.path,
|
|
2155
|
+
snippet: makeSnippet(preview, qLower),
|
|
2156
|
+
link: artifact.path,
|
|
2157
|
+
meta: {
|
|
2158
|
+
path: artifact.path,
|
|
2159
|
+
kind: artifact.kind
|
|
2160
|
+
}
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
hits.sort((a, b) => {
|
|
2166
|
+
const scoreDiff = scoreHit(b) - scoreHit(a);
|
|
2167
|
+
if (scoreDiff !== 0) return scoreDiff;
|
|
2168
|
+
const aTs = typeof a.meta.ts === "string" ? a.meta.ts : "";
|
|
2169
|
+
const bTs = typeof b.meta.ts === "string" ? b.meta.ts : "";
|
|
2170
|
+
return bTs.localeCompare(aTs);
|
|
2171
|
+
});
|
|
2172
|
+
return limit ? hits.slice(0, limit) : hits;
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
// src/cli/commands/search.ts
|
|
2176
|
+
function collect(value, previous = []) {
|
|
2177
|
+
return previous.concat([value]);
|
|
2178
|
+
}
|
|
2179
|
+
var searchCommand = new Command10("search").description("\u5728 workspace \u7D22\u5F15\u4E0A\u6267\u884C\u641C\u7D22").argument("<query>", "\u641C\u7D22\u5173\u952E\u8BCD").option("--workspace <pathOrName>", "\u6307\u5B9A workspace\uFF08\u53EF\u91CD\u590D\uFF09", collect, []).option("--step <id>", "\u7B5B\u9009 stepId\uFF08\u53EF\u91CD\u590D\uFF09", collect, []).option("--type <type>", "\u7B5B\u9009\u4E8B\u4EF6\u7C7B\u578B\uFF08\u53EF\u91CD\u590D\uFF09", collect, []).option("--work-item <id>", "\u7B5B\u9009 workItemId\uFF08\u53EF\u91CD\u590D\uFF09", collect, []).option("--limit <n>", "\u9650\u5236\u7ED3\u679C\u6570\u91CF", (v) => parseInt(v, 10)).option("--json", "\u4EE5 JSON \u8F93\u51FA\u7ED3\u679C").action(
|
|
2180
|
+
async (query, options) => {
|
|
2181
|
+
try {
|
|
2182
|
+
const registryItems = await listWorkspaces();
|
|
2183
|
+
const workspaces = options.workspace || [];
|
|
2184
|
+
const registryOnly = workspaces.length === 0;
|
|
2185
|
+
const results = await search(
|
|
2186
|
+
{
|
|
2187
|
+
q: query,
|
|
2188
|
+
workspace: workspaces,
|
|
2189
|
+
stepId: options.step,
|
|
2190
|
+
type: options.type,
|
|
2191
|
+
workItemId: options.workItem,
|
|
2192
|
+
limit: options.limit
|
|
2193
|
+
},
|
|
2194
|
+
{ targets: "all", registryOnly }
|
|
2195
|
+
);
|
|
2196
|
+
if (options.json) {
|
|
2197
|
+
console.log(JSON.stringify(results, null, 2));
|
|
2198
|
+
return;
|
|
2199
|
+
}
|
|
2200
|
+
if (!registryOnly && registryItems.length === 0) {
|
|
2201
|
+
console.log("\u26A0\uFE0F registry \u4E3A\u7A7A\uFF0C\u4F7F\u7528\u547D\u4EE4\u884C\u6307\u5B9A\u7684 workspace");
|
|
2202
|
+
}
|
|
2203
|
+
if (results.length === 0) {
|
|
2204
|
+
console.log("\u672A\u627E\u5230\u5339\u914D\u7ED3\u679C");
|
|
2205
|
+
if (registryItems.length === 0 && registryOnly) {
|
|
2206
|
+
console.log("\u63D0\u793A\uFF1A\u8BF7\u5148\u8FD0\u884C agent-handoff index --add \u6CE8\u518C workspace");
|
|
2207
|
+
}
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
2210
|
+
for (const hit of results) {
|
|
2211
|
+
console.log(`${hit.workspaceName} ${hit.kind} ${hit.title}`);
|
|
2212
|
+
if (hit.snippet) {
|
|
2213
|
+
console.log(` ${hit.snippet}`);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
} catch (error) {
|
|
2217
|
+
console.error(`Error: ${error}`);
|
|
2218
|
+
process.exit(1);
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
);
|
|
2222
|
+
|
|
2223
|
+
// src/cli/commands/diff.ts
|
|
2224
|
+
import { Command as Command11 } from "commander";
|
|
2225
|
+
import path19 from "path";
|
|
2226
|
+
|
|
2227
|
+
// src/core/diff/diff.ts
|
|
2228
|
+
import fs16 from "fs/promises";
|
|
2229
|
+
import path18 from "path";
|
|
2230
|
+
async function readIndex(workspacePath) {
|
|
2231
|
+
const abs = path18.resolve(workspacePath);
|
|
2232
|
+
const indexPath = path18.join(abs, ".agenthandoff", "index.json");
|
|
2233
|
+
try {
|
|
2234
|
+
const raw = await fs16.readFile(indexPath, "utf-8");
|
|
2235
|
+
return JSON.parse(raw);
|
|
2236
|
+
} catch {
|
|
2237
|
+
throw new Error(`index not found: ${indexPath}. \u8BF7\u5148\u8FD0\u884C agent-handoff index`);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
async function readTextFile(filePath) {
|
|
2241
|
+
try {
|
|
2242
|
+
return await fs16.readFile(filePath, "utf-8");
|
|
2243
|
+
} catch {
|
|
2244
|
+
return null;
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
function uniqueSorted(values) {
|
|
2248
|
+
return Array.from(new Set(values)).sort((a, b) => a.localeCompare(b));
|
|
2249
|
+
}
|
|
2250
|
+
function defaultArtifactPaths(index) {
|
|
2251
|
+
const artifacts = index.artifacts || [];
|
|
2252
|
+
const paths = artifacts.map((a) => a.path);
|
|
2253
|
+
return paths.filter((p) => p === "brief.md" || /^steps\/[^/]+\/output\.md$/.test(p));
|
|
2254
|
+
}
|
|
2255
|
+
function buildLcsOps(a, b) {
|
|
2256
|
+
const n = a.length;
|
|
2257
|
+
const m = b.length;
|
|
2258
|
+
const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
|
|
2259
|
+
for (let i2 = n - 1; i2 >= 0; i2--) {
|
|
2260
|
+
for (let j2 = m - 1; j2 >= 0; j2--) {
|
|
2261
|
+
dp[i2][j2] = a[i2] === b[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
const ops = [];
|
|
2265
|
+
let i = 0;
|
|
2266
|
+
let j = 0;
|
|
2267
|
+
while (i < n && j < m) {
|
|
2268
|
+
if (a[i] === b[j]) {
|
|
2269
|
+
ops.push({ t: "equal", line: a[i] });
|
|
2270
|
+
i++;
|
|
2271
|
+
j++;
|
|
2272
|
+
} else if (dp[i + 1][j] >= dp[i][j + 1]) {
|
|
2273
|
+
ops.push({ t: "del", line: a[i] });
|
|
2274
|
+
i++;
|
|
2275
|
+
} else {
|
|
2276
|
+
ops.push({ t: "add", line: b[j] });
|
|
2277
|
+
j++;
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
while (i < n) {
|
|
2281
|
+
ops.push({ t: "del", line: a[i] });
|
|
2282
|
+
i++;
|
|
2283
|
+
}
|
|
2284
|
+
while (j < m) {
|
|
2285
|
+
ops.push({ t: "add", line: b[j] });
|
|
2286
|
+
j++;
|
|
2287
|
+
}
|
|
2288
|
+
return ops;
|
|
2289
|
+
}
|
|
2290
|
+
function formatUnifiedDiff(filePath, left, right, contextLines) {
|
|
2291
|
+
const leftLines = (left ?? "").split("\n");
|
|
2292
|
+
const rightLines = (right ?? "").split("\n");
|
|
2293
|
+
const ops = buildLcsOps(leftLines, rightLines);
|
|
2294
|
+
const lines = [];
|
|
2295
|
+
lines.push(`--- a/${filePath}`);
|
|
2296
|
+
lines.push(`+++ b/${filePath}`);
|
|
2297
|
+
let aLine = 1;
|
|
2298
|
+
let bLine = 1;
|
|
2299
|
+
let i = 0;
|
|
2300
|
+
const consumeEqual = (count) => {
|
|
2301
|
+
for (let k = 0; k < count && i < ops.length; k++) {
|
|
2302
|
+
const op = ops[i];
|
|
2303
|
+
if (op.t !== "equal") break;
|
|
2304
|
+
aLine++;
|
|
2305
|
+
bLine++;
|
|
2306
|
+
i++;
|
|
2307
|
+
}
|
|
2308
|
+
};
|
|
2309
|
+
while (i < ops.length) {
|
|
2310
|
+
while (i < ops.length && ops[i].t === "equal") {
|
|
2311
|
+
i++;
|
|
2312
|
+
aLine++;
|
|
2313
|
+
bLine++;
|
|
2314
|
+
}
|
|
2315
|
+
if (i >= ops.length) break;
|
|
2316
|
+
const hunkStart = Math.max(0, i - contextLines);
|
|
2317
|
+
let hunkEnd = i;
|
|
2318
|
+
let trailingContext = 0;
|
|
2319
|
+
while (hunkEnd < ops.length) {
|
|
2320
|
+
if (ops[hunkEnd].t === "equal") {
|
|
2321
|
+
trailingContext++;
|
|
2322
|
+
if (trailingContext > contextLines) break;
|
|
2323
|
+
} else {
|
|
2324
|
+
trailingContext = 0;
|
|
2325
|
+
}
|
|
2326
|
+
hunkEnd++;
|
|
2327
|
+
}
|
|
2328
|
+
let aStart = aLine;
|
|
2329
|
+
let bStart = bLine;
|
|
2330
|
+
for (let k = i - 1; k >= hunkStart; k--) {
|
|
2331
|
+
const op = ops[k];
|
|
2332
|
+
if (op.t === "equal" || op.t === "del") aStart--;
|
|
2333
|
+
if (op.t === "equal" || op.t === "add") bStart--;
|
|
2334
|
+
}
|
|
2335
|
+
let aLen = 0;
|
|
2336
|
+
let bLen = 0;
|
|
2337
|
+
for (let k = hunkStart; k < hunkEnd; k++) {
|
|
2338
|
+
const op = ops[k];
|
|
2339
|
+
if (op.t === "equal" || op.t === "del") aLen++;
|
|
2340
|
+
if (op.t === "equal" || op.t === "add") bLen++;
|
|
2341
|
+
}
|
|
2342
|
+
lines.push(`@@ -${aStart},${aLen} +${bStart},${bLen} @@`);
|
|
2343
|
+
for (let k = hunkStart; k < hunkEnd; k++) {
|
|
2344
|
+
const op = ops[k];
|
|
2345
|
+
if (op.t === "equal") lines.push(` ${op.line}`);
|
|
2346
|
+
if (op.t === "del") lines.push(`-${op.line}`);
|
|
2347
|
+
if (op.t === "add") lines.push(`+${op.line}`);
|
|
2348
|
+
}
|
|
2349
|
+
i = hunkEnd;
|
|
2350
|
+
consumeEqual(contextLines);
|
|
2351
|
+
}
|
|
2352
|
+
return lines.join("\n");
|
|
2353
|
+
}
|
|
2354
|
+
function diffWorkflow(left, right) {
|
|
2355
|
+
const leftSteps = left.steps || [];
|
|
2356
|
+
const rightSteps = right.steps || [];
|
|
2357
|
+
const leftIds = leftSteps.map((s) => s.id);
|
|
2358
|
+
const rightIds = rightSteps.map((s) => s.id);
|
|
2359
|
+
const leftSet = new Set(leftIds);
|
|
2360
|
+
const rightSet = new Set(rightIds);
|
|
2361
|
+
const added = rightIds.filter((id) => !leftSet.has(id));
|
|
2362
|
+
const removed = leftIds.filter((id) => !rightSet.has(id));
|
|
2363
|
+
let changedSteps = 0;
|
|
2364
|
+
const maxLen = Math.max(leftIds.length, rightIds.length);
|
|
2365
|
+
for (let i = 0; i < maxLen; i++) {
|
|
2366
|
+
if (leftIds[i] !== rightIds[i]) changedSteps++;
|
|
2367
|
+
}
|
|
2368
|
+
const orderChanged = added.length === 0 && removed.length === 0 && leftIds.join("|") !== rightIds.join("|");
|
|
2369
|
+
const changed = added.length > 0 || removed.length > 0 || orderChanged;
|
|
2370
|
+
if (!changed) {
|
|
2371
|
+
return { changed: false, changedSteps: 0 };
|
|
2372
|
+
}
|
|
2373
|
+
const parts = [];
|
|
2374
|
+
if (added.length > 0) parts.push(`added: ${added.join(", ")}`);
|
|
2375
|
+
if (removed.length > 0) parts.push(`removed: ${removed.join(", ")}`);
|
|
2376
|
+
if (orderChanged) parts.push("order: changed");
|
|
2377
|
+
return { changed: true, details: parts.join("\n"), changedSteps };
|
|
2378
|
+
}
|
|
2379
|
+
function diffEvents(left, right) {
|
|
2380
|
+
const leftEvents = left.events || [];
|
|
2381
|
+
const rightEvents = right.events || [];
|
|
2382
|
+
const leftCount = leftEvents.length;
|
|
2383
|
+
const rightCount = rightEvents.length;
|
|
2384
|
+
const added = rightCount > leftCount ? rightCount - leftCount : 0;
|
|
2385
|
+
const latest = rightEvents[rightEvents.length - 1];
|
|
2386
|
+
return {
|
|
2387
|
+
leftCount,
|
|
2388
|
+
rightCount,
|
|
2389
|
+
added,
|
|
2390
|
+
...latest ? { latestRight: { ts: latest.ts, summary: latest.summary } } : {}
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2393
|
+
async function diffWorkspaces(options) {
|
|
2394
|
+
const leftWs = path18.resolve(options.leftWorkspace);
|
|
2395
|
+
const rightWs = path18.resolve(options.rightWorkspace);
|
|
2396
|
+
const leftIndex = await readIndex(leftWs);
|
|
2397
|
+
const rightIndex = await readIndex(rightWs);
|
|
2398
|
+
const contextLines = typeof options.contextLines === "number" && options.contextLines >= 0 ? options.contextLines : 3;
|
|
2399
|
+
const requestedPaths = options.paths && options.paths.length > 0 ? options.paths : void 0;
|
|
2400
|
+
const leftPaths = requestedPaths ? requestedPaths : defaultArtifactPaths(leftIndex);
|
|
2401
|
+
const rightPaths = requestedPaths ? requestedPaths : defaultArtifactPaths(rightIndex);
|
|
2402
|
+
const paths = uniqueSorted([...leftPaths, ...rightPaths]);
|
|
2403
|
+
const results = [];
|
|
2404
|
+
let addedArtifacts = 0;
|
|
2405
|
+
let removedArtifacts = 0;
|
|
2406
|
+
let changedArtifacts = 0;
|
|
2407
|
+
for (const rel of paths) {
|
|
2408
|
+
const leftText = await readTextFile(path18.join(leftWs, rel));
|
|
2409
|
+
const rightText = await readTextFile(path18.join(rightWs, rel));
|
|
2410
|
+
if (leftText === null && rightText === null) {
|
|
2411
|
+
continue;
|
|
2412
|
+
}
|
|
2413
|
+
if (leftText === null && rightText !== null) {
|
|
2414
|
+
addedArtifacts++;
|
|
2415
|
+
const diff = formatUnifiedDiff(rel, "", rightText, contextLines);
|
|
2416
|
+
results.push({ path: rel, status: "added", diff });
|
|
2417
|
+
continue;
|
|
2418
|
+
}
|
|
2419
|
+
if (leftText !== null && rightText === null) {
|
|
2420
|
+
removedArtifacts++;
|
|
2421
|
+
const diff = formatUnifiedDiff(rel, leftText, "", contextLines);
|
|
2422
|
+
results.push({ path: rel, status: "removed", diff });
|
|
2423
|
+
continue;
|
|
2424
|
+
}
|
|
2425
|
+
if (leftText !== rightText) {
|
|
2426
|
+
changedArtifacts++;
|
|
2427
|
+
const diff = formatUnifiedDiff(rel, leftText, rightText, contextLines);
|
|
2428
|
+
results.push({ path: rel, status: "changed", diff });
|
|
2429
|
+
continue;
|
|
2430
|
+
}
|
|
2431
|
+
results.push({ path: rel, status: "unchanged" });
|
|
2432
|
+
}
|
|
2433
|
+
const wf = diffWorkflow(leftIndex, rightIndex);
|
|
2434
|
+
const summary = {
|
|
2435
|
+
left: { name: leftIndex.workspaceName, path: leftWs },
|
|
2436
|
+
right: { name: rightIndex.workspaceName, path: rightWs },
|
|
2437
|
+
changedArtifacts,
|
|
2438
|
+
addedArtifacts,
|
|
2439
|
+
removedArtifacts,
|
|
2440
|
+
changedSteps: wf.changedSteps
|
|
2441
|
+
};
|
|
2442
|
+
return {
|
|
2443
|
+
summary,
|
|
2444
|
+
artifacts: results.sort((a, b) => a.path.localeCompare(b.path)),
|
|
2445
|
+
workflow: wf.changed ? { changed: true, details: wf.details } : { changed: false },
|
|
2446
|
+
events: diffEvents(leftIndex, rightIndex)
|
|
2447
|
+
};
|
|
2448
|
+
}
|
|
2449
|
+
function formatDiffResult(result, format) {
|
|
2450
|
+
const lines = [];
|
|
2451
|
+
const s = result.summary;
|
|
2452
|
+
lines.push(`left: ${s.left.name} ${s.left.path}`);
|
|
2453
|
+
lines.push(`right: ${s.right.name} ${s.right.path}`);
|
|
2454
|
+
lines.push(
|
|
2455
|
+
`artifacts: changed=${s.changedArtifacts} added=${s.addedArtifacts} removed=${s.removedArtifacts} stepsChanged=${s.changedSteps}`
|
|
2456
|
+
);
|
|
2457
|
+
if (result.workflow?.changed) {
|
|
2458
|
+
lines.push("");
|
|
2459
|
+
lines.push("workflow: changed");
|
|
2460
|
+
if (result.workflow.details) lines.push(result.workflow.details);
|
|
2461
|
+
}
|
|
2462
|
+
if (result.events) {
|
|
2463
|
+
lines.push("");
|
|
2464
|
+
lines.push(`events: left=${result.events.leftCount} right=${result.events.rightCount} added=${result.events.added}`);
|
|
2465
|
+
if (result.events.latestRight) {
|
|
2466
|
+
lines.push(`latest: ${result.events.latestRight.ts} ${result.events.latestRight.summary}`);
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
for (const a of result.artifacts) {
|
|
2470
|
+
if (a.status === "unchanged") continue;
|
|
2471
|
+
lines.push("");
|
|
2472
|
+
lines.push(`${a.status}: ${a.path}`);
|
|
2473
|
+
if (a.diff) {
|
|
2474
|
+
if (format === "markdown") {
|
|
2475
|
+
lines.push("```diff");
|
|
2476
|
+
lines.push(a.diff);
|
|
2477
|
+
lines.push("```");
|
|
2478
|
+
} else {
|
|
2479
|
+
lines.push(a.diff);
|
|
2480
|
+
}
|
|
2481
|
+
}
|
|
2482
|
+
}
|
|
2483
|
+
return lines.join("\n");
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
// src/cli/commands/diff.ts
|
|
2487
|
+
function collect2(value, previous = []) {
|
|
2488
|
+
return previous.concat([value]);
|
|
2489
|
+
}
|
|
2490
|
+
async function resolveWorkspaceToken(token) {
|
|
2491
|
+
const resolved = path19.resolve(token);
|
|
2492
|
+
const items = await listWorkspaces().catch(() => []);
|
|
2493
|
+
const byName = items.find((i) => i.name === token);
|
|
2494
|
+
if (byName) return byName.path;
|
|
2495
|
+
return resolved;
|
|
2496
|
+
}
|
|
2497
|
+
var diffCommand = new Command11("diff").description("\u5BF9\u4E24\u4E2A workspace \u8FDB\u884C diff\uFF08\u57FA\u4E8E index.json\uFF09").argument("<left>", "\u5DE6\u4FA7 workspace\uFF08path \u6216 registry name\uFF09").argument("<right>", "\u53F3\u4FA7 workspace\uFF08path \u6216 registry name\uFF09").option("--format <format>", "\u8F93\u51FA\u683C\u5F0F\uFF1Atext|markdown|json", "text").option("--path <path>", "\u4EC5\u5BF9\u6307\u5B9A\u6587\u4EF6\u505A diff\uFF08\u53EF\u91CD\u590D\uFF09", collect2, []).option("--context <n>", "diff context \u884C\u6570", (v) => parseInt(v, 10)).action(
|
|
2498
|
+
async (left, right, options) => {
|
|
2499
|
+
try {
|
|
2500
|
+
const leftPath = await resolveWorkspaceToken(left);
|
|
2501
|
+
const rightPath = await resolveWorkspaceToken(right);
|
|
2502
|
+
const format = options.format === "markdown" || options.format === "json" ? options.format : "text";
|
|
2503
|
+
const paths = options.path && options.path.length > 0 ? options.path : void 0;
|
|
2504
|
+
const result = await diffWorkspaces({
|
|
2505
|
+
leftWorkspace: leftPath,
|
|
2506
|
+
rightWorkspace: rightPath,
|
|
2507
|
+
format,
|
|
2508
|
+
paths,
|
|
2509
|
+
contextLines: options.context
|
|
2510
|
+
});
|
|
2511
|
+
if (format === "json") {
|
|
2512
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2513
|
+
return;
|
|
2514
|
+
}
|
|
2515
|
+
console.log(formatDiffResult(result, format));
|
|
2516
|
+
} catch (error) {
|
|
2517
|
+
console.error(`Error: ${error}`);
|
|
2518
|
+
process.exit(1);
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
);
|
|
2522
|
+
|
|
2523
|
+
// src/cli/commands/stats.ts
|
|
2524
|
+
import { Command as Command12 } from "commander";
|
|
2525
|
+
import path21 from "path";
|
|
2526
|
+
|
|
2527
|
+
// src/core/stats/stats.ts
|
|
2528
|
+
import fs17 from "fs/promises";
|
|
2529
|
+
import path20 from "path";
|
|
2530
|
+
async function readIndex2(workspacePath) {
|
|
2531
|
+
const abs = path20.resolve(workspacePath);
|
|
2532
|
+
const indexPath = path20.join(abs, ".agenthandoff", "index.json");
|
|
2533
|
+
try {
|
|
2534
|
+
const raw = await fs17.readFile(indexPath, "utf-8");
|
|
2535
|
+
return JSON.parse(raw);
|
|
2536
|
+
} catch {
|
|
2537
|
+
return null;
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
function countEventsByType(events) {
|
|
2541
|
+
const counts = {};
|
|
2542
|
+
for (const ev of events) {
|
|
2543
|
+
const type = ev.type || "unknown";
|
|
2544
|
+
counts[type] = (counts[type] || 0) + 1;
|
|
2545
|
+
}
|
|
2546
|
+
return counts;
|
|
2547
|
+
}
|
|
2548
|
+
function buildDurations(events) {
|
|
2549
|
+
const map = /* @__PURE__ */ new Map();
|
|
2550
|
+
for (const ev of events) {
|
|
2551
|
+
if (!ev.stepId || !ev.type || !ev.ts) continue;
|
|
2552
|
+
const key = ev.stepId;
|
|
2553
|
+
const current = map.get(key) || {};
|
|
2554
|
+
if (ev.type === "step.started") {
|
|
2555
|
+
if (!current.startedAt || ev.ts < current.startedAt) current.startedAt = ev.ts;
|
|
2556
|
+
}
|
|
2557
|
+
if (ev.type === "step.done") {
|
|
2558
|
+
if (!current.doneAt || ev.ts > current.doneAt) current.doneAt = ev.ts;
|
|
2559
|
+
}
|
|
2560
|
+
map.set(key, current);
|
|
2561
|
+
}
|
|
2562
|
+
const items = [];
|
|
2563
|
+
for (const [stepId, v] of map.entries()) {
|
|
2564
|
+
let durationMs;
|
|
2565
|
+
if (v.startedAt && v.doneAt) {
|
|
2566
|
+
const start = new Date(v.startedAt).getTime();
|
|
2567
|
+
const end = new Date(v.doneAt).getTime();
|
|
2568
|
+
if (!Number.isNaN(start) && !Number.isNaN(end) && end >= start) {
|
|
2569
|
+
durationMs = end - start;
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
items.push({ stepId, startedAt: v.startedAt, doneAt: v.doneAt, ...durationMs !== void 0 && { durationMs } });
|
|
2573
|
+
}
|
|
2574
|
+
return items.sort((a, b) => a.stepId.localeCompare(b.stepId));
|
|
2575
|
+
}
|
|
2576
|
+
function buildWorkspaceStats(index, mode) {
|
|
2577
|
+
const steps = index.steps || [];
|
|
2578
|
+
const stepsTotal = steps.length;
|
|
2579
|
+
const stepsDone = steps.filter((s) => Boolean(s.outputExists)).length;
|
|
2580
|
+
const events = index.events || [];
|
|
2581
|
+
const eventsTotal = events.length;
|
|
2582
|
+
const eventsByType = countEventsByType(events);
|
|
2583
|
+
const automationSessions = events.filter((e) => e.type === "automation.session").length;
|
|
2584
|
+
const stats = {
|
|
2585
|
+
workspaceName: index.workspaceName,
|
|
2586
|
+
workspacePath: index.workspacePath,
|
|
2587
|
+
indexedAt: index.indexedAt,
|
|
2588
|
+
stepsTotal,
|
|
2589
|
+
stepsDone,
|
|
2590
|
+
eventsTotal,
|
|
2591
|
+
eventsByType,
|
|
2592
|
+
automation: {
|
|
2593
|
+
sessions: automationSessions,
|
|
2594
|
+
summary: {}
|
|
2595
|
+
}
|
|
2596
|
+
};
|
|
2597
|
+
if (mode === "full") {
|
|
2598
|
+
stats.durations = buildDurations(events);
|
|
2599
|
+
}
|
|
2600
|
+
return stats;
|
|
2601
|
+
}
|
|
2602
|
+
async function buildStats(query) {
|
|
2603
|
+
const workspaces = query.workspaces || [];
|
|
2604
|
+
const results = [];
|
|
2605
|
+
for (const ws of workspaces) {
|
|
2606
|
+
const index = await readIndex2(ws);
|
|
2607
|
+
if (!index) continue;
|
|
2608
|
+
results.push(buildWorkspaceStats(index, query.mode));
|
|
2609
|
+
}
|
|
2610
|
+
return {
|
|
2611
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2612
|
+
workspaces: results
|
|
2613
|
+
};
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
// src/cli/commands/stats.ts
|
|
2617
|
+
function isValidFormat(value) {
|
|
2618
|
+
return value === "json" || value === "markdown";
|
|
2619
|
+
}
|
|
2620
|
+
async function resolveWorkspaceToken2(token) {
|
|
2621
|
+
const resolved = path21.resolve(token);
|
|
2622
|
+
const items = await listWorkspaces().catch(() => []);
|
|
2623
|
+
const byName = items.find((i) => i.name === token);
|
|
2624
|
+
return byName ? byName.path : resolved;
|
|
2625
|
+
}
|
|
2626
|
+
function formatMarkdown(result) {
|
|
2627
|
+
const lines = [];
|
|
2628
|
+
lines.push("| Workspace | Steps | Events | Automation |");
|
|
2629
|
+
lines.push("|---|---|---|---|");
|
|
2630
|
+
for (const ws of result.workspaces) {
|
|
2631
|
+
const steps = `${ws.stepsDone}/${ws.stepsTotal}`;
|
|
2632
|
+
const events = `${ws.eventsTotal}`;
|
|
2633
|
+
const automation = `${ws.automation?.sessions ?? 0}`;
|
|
2634
|
+
lines.push(`| ${ws.workspaceName} | ${steps} | ${events} | ${automation} |`);
|
|
2635
|
+
}
|
|
2636
|
+
if (result.workspaces.length === 0) {
|
|
2637
|
+
lines.push("| - | - | - | - |");
|
|
2638
|
+
}
|
|
2639
|
+
return lines.join("\n");
|
|
2640
|
+
}
|
|
2641
|
+
var statsCommand = new Command12("stats").description("\u8F93\u51FA workspace \u7EDF\u8BA1\u4FE1\u606F").argument("[workspaces...]", "workspace \u8DEF\u5F84\u6216 registry name").option("--registry", "\u7EDF\u8BA1 registry \u4E2D\u6240\u6709 workspace").option("--mode <mode>", "summary|full", "summary").option("--format <format>", "json|markdown", "markdown").action(
|
|
2642
|
+
async (workspaces, options) => {
|
|
2643
|
+
try {
|
|
2644
|
+
const mode = options.mode === "full" ? "full" : "summary";
|
|
2645
|
+
const format = isValidFormat(options.format) ? options.format : "markdown";
|
|
2646
|
+
let targets = [];
|
|
2647
|
+
if (options.registry) {
|
|
2648
|
+
const items = await listWorkspaces();
|
|
2649
|
+
targets = items.map((i) => i.path);
|
|
2650
|
+
} else if (workspaces.length > 0) {
|
|
2651
|
+
targets = await Promise.all(workspaces.map(resolveWorkspaceToken2));
|
|
2652
|
+
}
|
|
2653
|
+
if (targets.length === 0) {
|
|
2654
|
+
console.log("\u672A\u627E\u5230\u53EF\u7EDF\u8BA1\u7684 workspace");
|
|
2655
|
+
return;
|
|
2656
|
+
}
|
|
2657
|
+
const result = await buildStats({ workspaces: targets, mode });
|
|
2658
|
+
if (format === "json") {
|
|
2659
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2660
|
+
return;
|
|
2661
|
+
}
|
|
2662
|
+
console.log(formatMarkdown(result));
|
|
2663
|
+
} catch (error) {
|
|
2664
|
+
console.error(`Error: ${error}`);
|
|
2665
|
+
process.exit(1);
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
);
|
|
2669
|
+
|
|
1780
2670
|
// src/index.ts
|
|
1781
2671
|
var require2 = createRequire(import.meta.url);
|
|
1782
2672
|
var { version } = require2("../package.json");
|
|
1783
|
-
var program = new
|
|
2673
|
+
var program = new Command13();
|
|
1784
2674
|
program.name("agent-handoff").description("\u8F7B\u91CF\u7EA7\u591A Agent \u534F\u4F5C\u63A5\u529B\u5DE5\u5177").version(version);
|
|
1785
2675
|
program.addCommand(initCommand);
|
|
1786
2676
|
program.addCommand(statusCommand);
|
|
@@ -1790,5 +2680,9 @@ program.addCommand(advanceCommand);
|
|
|
1790
2680
|
program.addCommand(configCommand);
|
|
1791
2681
|
program.addCommand(reportCommand);
|
|
1792
2682
|
program.addCommand(exportCommand);
|
|
2683
|
+
program.addCommand(indexCommand);
|
|
2684
|
+
program.addCommand(searchCommand);
|
|
2685
|
+
program.addCommand(diffCommand);
|
|
2686
|
+
program.addCommand(statsCommand);
|
|
1793
2687
|
program.parse();
|
|
1794
2688
|
//# sourceMappingURL=index.js.map
|