agentera 0.0.0 → 3.0.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -45
- package/bundle/.agentera-npx-bundle.json +4 -0
- package/bundle/references/adapters/cursor.md +213 -0
- package/bundle/references/adapters/opencode.md +530 -0
- package/bundle/references/adapters/package-manifest-interface-model.yaml +337 -0
- package/bundle/references/adapters/package-registry.yaml +247 -0
- package/bundle/references/adapters/package-surface-characterization.md +48 -0
- package/bundle/references/adapters/runtime-adapter-characterization.md +79 -0
- package/bundle/references/adapters/runtime-adapter-interface-model.yaml +200 -0
- package/bundle/references/adapters/runtime-adapter-registry.yaml +548 -0
- package/bundle/references/adapters/runtime-feature-parity.md +189 -0
- package/bundle/references/analysis/benchmark.md +267 -0
- package/bundle/references/analysis/startup-measurement-contract.yaml +424 -0
- package/bundle/references/artifacts/artifact-registry-interface-model.yaml +288 -0
- package/bundle/references/cli/agent-ready-state-contract.yaml +950 -0
- package/bundle/references/cli/app-lifecycle-vocabulary.yaml +241 -0
- package/bundle/references/cli/audience-namespace-cli-migration.yaml +355 -0
- package/bundle/references/cli/bundle-skill-vocabulary.yaml +278 -0
- package/bundle/references/cli/capability-instruction-contract.yaml +123 -0
- package/bundle/references/cli/capability-tool-classification.yaml +53 -0
- package/bundle/references/cli/routing-execution-vocabulary.yaml +281 -0
- package/bundle/references/cli/update-channels.yaml +147 -0
- package/bundle/references/cli/vocabulary-index.yaml +160 -0
- package/bundle/references/cli/vocabulary.md +566 -0
- package/bundle/references/meta/documentation-inventory.md +43 -0
- package/bundle/references/v1-section-mapping.md +47 -0
- package/bundle/registry.json +39 -0
- package/bundle/skills/agentera/.claude-plugin/plugin.json +27 -0
- package/bundle/skills/agentera/SKILL.md +470 -0
- package/bundle/skills/agentera/agents/dokumentera.toml +6 -0
- package/bundle/skills/agentera/agents/hej.toml +6 -0
- package/bundle/skills/agentera/agents/inspektera.toml +6 -0
- package/bundle/skills/agentera/agents/inspirera.toml +6 -0
- package/bundle/skills/agentera/agents/optimera.toml +6 -0
- package/bundle/skills/agentera/agents/orkestrera.toml +6 -0
- package/bundle/skills/agentera/agents/planera.toml +6 -0
- package/bundle/skills/agentera/agents/profilera.toml +6 -0
- package/bundle/skills/agentera/agents/realisera.toml +6 -0
- package/bundle/skills/agentera/agents/resonera.toml +6 -0
- package/bundle/skills/agentera/agents/visionera.toml +6 -0
- package/bundle/skills/agentera/agents/visualisera.toml +6 -0
- package/bundle/skills/agentera/capabilities/dokumentera/instructions.md +428 -0
- package/bundle/skills/agentera/capabilities/dokumentera/schemas/artifacts.yaml +73 -0
- package/bundle/skills/agentera/capabilities/dokumentera/schemas/exit.yaml +35 -0
- package/bundle/skills/agentera/capabilities/dokumentera/schemas/triggers.yaml +35 -0
- package/bundle/skills/agentera/capabilities/dokumentera/schemas/validation.yaml +139 -0
- package/bundle/skills/agentera/capabilities/hej/instructions.md +331 -0
- package/bundle/skills/agentera/capabilities/hej/schemas/artifacts.yaml +69 -0
- package/bundle/skills/agentera/capabilities/hej/schemas/exit.yaml +32 -0
- package/bundle/skills/agentera/capabilities/hej/schemas/triggers.yaml +58 -0
- package/bundle/skills/agentera/capabilities/hej/schemas/validation.yaml +55 -0
- package/bundle/skills/agentera/capabilities/inspektera/instructions.md +514 -0
- package/bundle/skills/agentera/capabilities/inspektera/schemas/artifacts.yaml +76 -0
- package/bundle/skills/agentera/capabilities/inspektera/schemas/exit.yaml +36 -0
- package/bundle/skills/agentera/capabilities/inspektera/schemas/triggers.yaml +38 -0
- package/bundle/skills/agentera/capabilities/inspektera/schemas/validation.yaml +113 -0
- package/bundle/skills/agentera/capabilities/inspirera/instructions.md +280 -0
- package/bundle/skills/agentera/capabilities/inspirera/schemas/artifacts.yaml +24 -0
- package/bundle/skills/agentera/capabilities/inspirera/schemas/exit.yaml +33 -0
- package/bundle/skills/agentera/capabilities/inspirera/schemas/triggers.yaml +34 -0
- package/bundle/skills/agentera/capabilities/inspirera/schemas/validation.yaml +58 -0
- package/bundle/skills/agentera/capabilities/optimera/instructions.md +437 -0
- package/bundle/skills/agentera/capabilities/optimera/schemas/artifacts.yaml +69 -0
- package/bundle/skills/agentera/capabilities/optimera/schemas/exit.yaml +35 -0
- package/bundle/skills/agentera/capabilities/optimera/schemas/triggers.yaml +39 -0
- package/bundle/skills/agentera/capabilities/optimera/schemas/validation.yaml +91 -0
- package/bundle/skills/agentera/capabilities/orkestrera/instructions.md +433 -0
- package/bundle/skills/agentera/capabilities/orkestrera/schemas/artifacts.yaml +64 -0
- package/bundle/skills/agentera/capabilities/orkestrera/schemas/exit.yaml +34 -0
- package/bundle/skills/agentera/capabilities/orkestrera/schemas/triggers.yaml +42 -0
- package/bundle/skills/agentera/capabilities/orkestrera/schemas/validation.yaml +107 -0
- package/bundle/skills/agentera/capabilities/planera/instructions.md +368 -0
- package/bundle/skills/agentera/capabilities/planera/schemas/artifacts.yaml +62 -0
- package/bundle/skills/agentera/capabilities/planera/schemas/exit.yaml +33 -0
- package/bundle/skills/agentera/capabilities/planera/schemas/triggers.yaml +34 -0
- package/bundle/skills/agentera/capabilities/planera/schemas/validation.yaml +61 -0
- package/bundle/skills/agentera/capabilities/profilera/instructions.md +419 -0
- package/bundle/skills/agentera/capabilities/profilera/schemas/artifacts.yaml +18 -0
- package/bundle/skills/agentera/capabilities/profilera/schemas/exit.yaml +34 -0
- package/bundle/skills/agentera/capabilities/profilera/schemas/triggers.yaml +45 -0
- package/bundle/skills/agentera/capabilities/profilera/schemas/validation.yaml +57 -0
- package/bundle/skills/agentera/capabilities/realisera/instructions.md +403 -0
- package/bundle/skills/agentera/capabilities/realisera/schemas/artifacts.yaml +80 -0
- package/bundle/skills/agentera/capabilities/realisera/schemas/exit.yaml +35 -0
- package/bundle/skills/agentera/capabilities/realisera/schemas/triggers.yaml +39 -0
- package/bundle/skills/agentera/capabilities/realisera/schemas/validation.yaml +110 -0
- package/bundle/skills/agentera/capabilities/resonera/instructions.md +329 -0
- package/bundle/skills/agentera/capabilities/resonera/schemas/artifacts.yaml +47 -0
- package/bundle/skills/agentera/capabilities/resonera/schemas/exit.yaml +35 -0
- package/bundle/skills/agentera/capabilities/resonera/schemas/triggers.yaml +46 -0
- package/bundle/skills/agentera/capabilities/resonera/schemas/validation.yaml +77 -0
- package/bundle/skills/agentera/capabilities/visionera/instructions.md +309 -0
- package/bundle/skills/agentera/capabilities/visionera/schemas/artifacts.yaml +57 -0
- package/bundle/skills/agentera/capabilities/visionera/schemas/exit.yaml +35 -0
- package/bundle/skills/agentera/capabilities/visionera/schemas/triggers.yaml +41 -0
- package/bundle/skills/agentera/capabilities/visionera/schemas/validation.yaml +74 -0
- package/bundle/skills/agentera/capabilities/visualisera/instructions.md +400 -0
- package/bundle/skills/agentera/capabilities/visualisera/schemas/artifacts.yaml +44 -0
- package/bundle/skills/agentera/capabilities/visualisera/schemas/exit.yaml +34 -0
- package/bundle/skills/agentera/capabilities/visualisera/schemas/triggers.yaml +33 -0
- package/bundle/skills/agentera/capabilities/visualisera/schemas/validation.yaml +80 -0
- package/bundle/skills/agentera/capability_schema_contract.yaml +385 -0
- package/bundle/skills/agentera/protocol.yaml +463 -0
- package/bundle/skills/agentera/references/contract.md +1039 -0
- package/bundle/skills/agentera/schemas/artifacts/changelog.yaml +60 -0
- package/bundle/skills/agentera/schemas/artifacts/decisions.yaml +461 -0
- package/bundle/skills/agentera/schemas/artifacts/design.yaml +55 -0
- package/bundle/skills/agentera/schemas/artifacts/docs.yaml +402 -0
- package/bundle/skills/agentera/schemas/artifacts/experiments.yaml +373 -0
- package/bundle/skills/agentera/schemas/artifacts/health.yaml +484 -0
- package/bundle/skills/agentera/schemas/artifacts/objective.yaml +399 -0
- package/bundle/skills/agentera/schemas/artifacts/plan.yaml +342 -0
- package/bundle/skills/agentera/schemas/artifacts/progress.yaml +325 -0
- package/bundle/skills/agentera/schemas/artifacts/todo.yaml +110 -0
- package/bundle/skills/agentera/schemas/artifacts/vision.yaml +262 -0
- package/bundle/skills/hej/.claude-plugin/plugin.json +6 -0
- package/bundle/skills/hej/SKILL.md +69 -0
- package/bundle/skills/hej/agents/hej.toml +11 -0
- package/bundle/skills/hej/agents/openai.yaml +8 -0
- package/dist/analytics/extractCorpus.js +1791 -0
- package/dist/analytics/extractCorpus.js.map +1 -0
- package/dist/analytics/usageStats.js +487 -0
- package/dist/analytics/usageStats.js.map +1 -0
- package/dist/bin/agentera.js +4 -0
- package/dist/bin/agentera.js.map +1 -0
- package/dist/cli/appContext.js +226 -0
- package/dist/cli/appContext.js.map +1 -0
- package/dist/cli/argvalidate.js +41 -0
- package/dist/cli/argvalidate.js.map +1 -0
- package/dist/cli/capabilityContext.js +2421 -0
- package/dist/cli/capabilityContext.js.map +1 -0
- package/dist/cli/commands/backfill.js +84 -0
- package/dist/cli/commands/backfill.js.map +1 -0
- package/dist/cli/commands/capability.js +44 -0
- package/dist/cli/commands/capability.js.map +1 -0
- package/dist/cli/commands/compact.js +148 -0
- package/dist/cli/commands/compact.js.map +1 -0
- package/dist/cli/commands/doctor.js +180 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/lint.js +179 -0
- package/dist/cli/commands/lint.js.map +1 -0
- package/dist/cli/commands/prime.js +544 -0
- package/dist/cli/commands/prime.js.map +1 -0
- package/dist/cli/commands/query.js +346 -0
- package/dist/cli/commands/query.js.map +1 -0
- package/dist/cli/commands/report.js +210 -0
- package/dist/cli/commands/report.js.map +1 -0
- package/dist/cli/commands/schema.js +306 -0
- package/dist/cli/commands/schema.js.map +1 -0
- package/dist/cli/commands/state.js +1012 -0
- package/dist/cli/commands/state.js.map +1 -0
- package/dist/cli/commands/upgrade.js +48 -0
- package/dist/cli/commands/upgrade.js.map +1 -0
- package/dist/cli/commands/validate.js +519 -0
- package/dist/cli/commands/validate.js.map +1 -0
- package/dist/cli/commands/verify.js +204 -0
- package/dist/cli/commands/verify.js.map +1 -0
- package/dist/cli/dispatch.js +958 -0
- package/dist/cli/dispatch.js.map +1 -0
- package/dist/cli/orientation.js +595 -0
- package/dist/cli/orientation.js.map +1 -0
- package/dist/cli/prime-blob.js +3 -0
- package/dist/cli/prime-blob.js.map +1 -0
- package/dist/cli/stateQuery.js +292 -0
- package/dist/cli/stateQuery.js.map +1 -0
- package/dist/cli/structured.js +18 -0
- package/dist/cli/structured.js.map +1 -0
- package/dist/core/difflib.js +274 -0
- package/dist/core/difflib.js.map +1 -0
- package/dist/core/git.js +43 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/paths.js +50 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/pyjson.js +101 -0
- package/dist/core/pyjson.js.map +1 -0
- package/dist/core/sourceRoot.js +72 -0
- package/dist/core/sourceRoot.js.map +1 -0
- package/dist/core/toml.js +11 -0
- package/dist/core/toml.js.map +1 -0
- package/dist/core/yaml.js +25 -0
- package/dist/core/yaml.js.map +1 -0
- package/dist/eval/evalSkills.js +258 -0
- package/dist/eval/evalSkills.js.map +1 -0
- package/dist/eval/semanticEval.js +148 -0
- package/dist/eval/semanticEval.js.map +1 -0
- package/dist/eval/semanticFixtures.js +227 -0
- package/dist/eval/semanticFixtures.js.map +1 -0
- package/dist/hooks/common.js +160 -0
- package/dist/hooks/common.js.map +1 -0
- package/dist/hooks/compaction.js +935 -0
- package/dist/hooks/compaction.js.map +1 -0
- package/dist/hooks/cursorPreToolUse.js +19 -0
- package/dist/hooks/cursorPreToolUse.js.map +1 -0
- package/dist/hooks/cursorSessionStart.js +71 -0
- package/dist/hooks/cursorSessionStart.js.map +1 -0
- package/dist/hooks/sessionStart.js +209 -0
- package/dist/hooks/sessionStart.js.map +1 -0
- package/dist/hooks/sessionStop.js +212 -0
- package/dist/hooks/sessionStop.js.map +1 -0
- package/dist/hooks/validateArtifact.js +933 -0
- package/dist/hooks/validateArtifact.js.map +1 -0
- package/dist/registries/artifactRegistry.js +206 -0
- package/dist/registries/artifactRegistry.js.map +1 -0
- package/dist/registries/capabilityContract.js +310 -0
- package/dist/registries/capabilityContract.js.map +1 -0
- package/dist/registries/packageRegistry.js +641 -0
- package/dist/registries/packageRegistry.js.map +1 -0
- package/dist/registries/runtimeAdapterRegistry.js +315 -0
- package/dist/registries/runtimeAdapterRegistry.js.map +1 -0
- package/dist/setup/codex.js +1056 -0
- package/dist/setup/codex.js.map +1 -0
- package/dist/setup/copilot.js +227 -0
- package/dist/setup/copilot.js.map +1 -0
- package/dist/setup/cursor.js +127 -0
- package/dist/setup/cursor.js.map +1 -0
- package/dist/setup/doctor.js +1276 -0
- package/dist/setup/doctor.js.map +1 -0
- package/dist/state/installRoot.js +279 -0
- package/dist/state/installRoot.js.map +1 -0
- package/dist/state/progressCommit.js +289 -0
- package/dist/state/progressCommit.js.map +1 -0
- package/dist/state/startupAnalysis.js +1953 -0
- package/dist/state/startupAnalysis.js.map +1 -0
- package/dist/upgrade/appModel.js +189 -0
- package/dist/upgrade/appModel.js.map +1 -0
- package/dist/upgrade/channels.js +208 -0
- package/dist/upgrade/channels.js.map +1 -0
- package/dist/upgrade/compatibility.js +201 -0
- package/dist/upgrade/compatibility.js.map +1 -0
- package/dist/upgrade/doctor.js +373 -0
- package/dist/upgrade/doctor.js.map +1 -0
- package/dist/upgrade/migrateArtifactsV2ToV3.js +332 -0
- package/dist/upgrade/migrateArtifactsV2ToV3.js.map +1 -0
- package/dist/upgrade/runtimeMigration.js +484 -0
- package/dist/upgrade/runtimeMigration.js.map +1 -0
- package/dist/upgrade/upgradeCommands.js +36 -0
- package/dist/upgrade/upgradeCommands.js.map +1 -0
- package/dist/upgrade/upgradeOrchestrator.js +299 -0
- package/dist/upgrade/upgradeOrchestrator.js.map +1 -0
- package/dist/upgrade/versionResolution.js +179 -0
- package/dist/upgrade/versionResolution.js.map +1 -0
- package/dist/validate/appHomeContract.js +150 -0
- package/dist/validate/appHomeContract.js.map +1 -0
- package/dist/validate/capability.js +412 -0
- package/dist/validate/capability.js.map +1 -0
- package/dist/validate/crossCapability.js +145 -0
- package/dist/validate/crossCapability.js.map +1 -0
- package/dist/validate/lifecycleAdapters.js +772 -0
- package/dist/validate/lifecycleAdapters.js.map +1 -0
- package/dist/validate/selfAudit.js +107 -0
- package/dist/validate/selfAudit.js.map +1 -0
- package/package.json +28 -8
- package/LICENSE +0 -201
- package/bin/agentera.mjs +0 -50
- package/lib/exec.mjs +0 -116
- package/lib/resolve.mjs +0 -129
|
@@ -0,0 +1,1791 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Section 22 corpus extractor — faithful TypeScript port of
|
|
3
|
+
* scripts/extract_corpus.py (git 1389867~1).
|
|
4
|
+
*
|
|
5
|
+
* Reads local AI runtime history (JSONL files + SQLite stores via node:sqlite)
|
|
6
|
+
* across Codex / Claude Code / Cursor / Cursor-agent / OpenCode / GitHub Copilot,
|
|
7
|
+
* plus instruction documents and project config signals, and emits the
|
|
8
|
+
* corpus.json envelope that profilera synthesizes PROFILE.md from and that
|
|
9
|
+
* `report`/`stats` analytics read.
|
|
10
|
+
*
|
|
11
|
+
* SQLite uses Node's built-in node:sqlite (DatabaseSync), imported lazily so the
|
|
12
|
+
* one experimental warning only appears when a SQLite store is actually read.
|
|
13
|
+
*/
|
|
14
|
+
import fs from "node:fs";
|
|
15
|
+
import os from "node:os";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import crypto from "node:crypto";
|
|
18
|
+
export const ADAPTER_VERSION = "agentera-v2-corpus-1";
|
|
19
|
+
export const FAMILIES = [
|
|
20
|
+
"instruction_document",
|
|
21
|
+
"history_prompt",
|
|
22
|
+
"conversation_turn",
|
|
23
|
+
"tool_call",
|
|
24
|
+
"project_config_signal",
|
|
25
|
+
];
|
|
26
|
+
export const RUNTIME_STORE_GLOBS = {
|
|
27
|
+
codex: "*.jsonl",
|
|
28
|
+
"claude-code": "*.jsonl",
|
|
29
|
+
cursor: "*.jsonl",
|
|
30
|
+
"cursor-agent": "store.db",
|
|
31
|
+
opencode: "opencode.db",
|
|
32
|
+
"github-copilot": "session-store.db",
|
|
33
|
+
};
|
|
34
|
+
const MAX_TOOL_ARG_TEXT = 500;
|
|
35
|
+
const MAX_SQLITE_ROWS = 100_000;
|
|
36
|
+
const MAX_SQLITE_SESSIONS = 60;
|
|
37
|
+
const COPILOT_SPARSE_REMEDIATION = "/chronicle reindex";
|
|
38
|
+
const DECISION_RE = /\b(decide|decision|prefer|preference|instead|avoid|don't|do not|should|trade[- ]?off|scope|plan|commit|review|fix|why|question|blocked|stuck|approve|reject|change|keep|remove)\b/i;
|
|
39
|
+
const CORRECTION_RE = /\b(no|not quite|actually|rather|instead|wrong|correction|that's not|that is not|don't|do not)\b/i;
|
|
40
|
+
const QUESTION_RE = /\?|^\s*(why|what|how|should|can|could|would)\b/i;
|
|
41
|
+
// ── core helpers ───────────────────────────────────────────────────
|
|
42
|
+
export function isoNow() {
|
|
43
|
+
// Python datetime.now(utc).isoformat() -> microseconds + "+00:00" -> "Z".
|
|
44
|
+
// JS gives milliseconds; corpus extracted_at is a wall-clock stamp, not a
|
|
45
|
+
// parity-critical record value.
|
|
46
|
+
return new Date().toISOString().replace(/Z$/, "Z");
|
|
47
|
+
}
|
|
48
|
+
export function isoFromMtime(p) {
|
|
49
|
+
const ms = fs.statSync(p).mtimeMs;
|
|
50
|
+
return new Date(ms).toISOString().replace(/Z$/, "Z");
|
|
51
|
+
}
|
|
52
|
+
export function stableId(...parts) {
|
|
53
|
+
const raw = parts.map((p) => pyStr(p)).join("\0");
|
|
54
|
+
return crypto.createHash("sha256").update(raw, "utf-8").digest("hex").slice(0, 24);
|
|
55
|
+
}
|
|
56
|
+
/** Python str() for the scalar/None values stable_id receives. */
|
|
57
|
+
function pyStr(value) {
|
|
58
|
+
if (value === null || value === undefined)
|
|
59
|
+
return "None";
|
|
60
|
+
if (value === true)
|
|
61
|
+
return "True";
|
|
62
|
+
if (value === false)
|
|
63
|
+
return "False";
|
|
64
|
+
return String(value);
|
|
65
|
+
}
|
|
66
|
+
export function projectIdFromPath(p) {
|
|
67
|
+
if (p === null)
|
|
68
|
+
return "global";
|
|
69
|
+
const name = path.basename(p) || p;
|
|
70
|
+
const slug = name
|
|
71
|
+
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
72
|
+
.replace(/^-+/, "")
|
|
73
|
+
.replace(/-+$/, "")
|
|
74
|
+
.toLowerCase();
|
|
75
|
+
return slug || "global";
|
|
76
|
+
}
|
|
77
|
+
export function defaultAgenteraHome(env = process.env, platform = process.platform) {
|
|
78
|
+
const override = env.AGENTERA_HOME;
|
|
79
|
+
if (override)
|
|
80
|
+
return override;
|
|
81
|
+
if (platform === "darwin")
|
|
82
|
+
return path.join(os.homedir(), "Library", "Application Support", "agentera");
|
|
83
|
+
if (platform === "win32") {
|
|
84
|
+
const appdata = env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
|
|
85
|
+
return path.join(appdata, "agentera");
|
|
86
|
+
}
|
|
87
|
+
const xdg = env.XDG_DATA_HOME || path.join(os.homedir(), ".local", "share");
|
|
88
|
+
return path.join(xdg, "agentera");
|
|
89
|
+
}
|
|
90
|
+
export function defaultProfileDir(env = process.env, platform = process.platform) {
|
|
91
|
+
const override = env.PROFILERA_PROFILE_DIR;
|
|
92
|
+
if (override)
|
|
93
|
+
return override;
|
|
94
|
+
return defaultAgenteraHome(env, platform);
|
|
95
|
+
}
|
|
96
|
+
export function defaultOutputPath(env = process.env, platform = process.platform) {
|
|
97
|
+
return path.join(defaultProfileDir(env, platform), "intermediate", "corpus.json");
|
|
98
|
+
}
|
|
99
|
+
export function runtimeStatus(runtime, opts) {
|
|
100
|
+
const item = { runtime, status: opts.status, reason: opts.reason };
|
|
101
|
+
if (opts.storePath !== null && opts.storePath !== undefined)
|
|
102
|
+
item.store_path = opts.storePath;
|
|
103
|
+
if (opts.candidateCount !== null && opts.candidateCount !== undefined)
|
|
104
|
+
item.candidate_count = opts.candidateCount;
|
|
105
|
+
if (opts.recordCount !== null && opts.recordCount !== undefined)
|
|
106
|
+
item.record_count = opts.recordCount;
|
|
107
|
+
if (opts.errorCount !== null && opts.errorCount !== undefined)
|
|
108
|
+
item.error_count = opts.errorCount;
|
|
109
|
+
if (opts.remediationLabels && opts.remediationLabels.length > 0)
|
|
110
|
+
item.remediation_labels = opts.remediationLabels;
|
|
111
|
+
return item;
|
|
112
|
+
}
|
|
113
|
+
function isDir(p) {
|
|
114
|
+
try {
|
|
115
|
+
return fs.statSync(p).isDirectory();
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
function isFilePath(p) {
|
|
122
|
+
try {
|
|
123
|
+
return fs.statSync(p).isFile();
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/** Recursive glob for a simple pattern ("*.jsonl" or an exact filename). */
|
|
130
|
+
function rglob(root, pattern) {
|
|
131
|
+
const out = [];
|
|
132
|
+
const matchesExt = pattern.startsWith("*.") ? pattern.slice(1) : null; // ".jsonl"
|
|
133
|
+
const exact = matchesExt ? null : pattern;
|
|
134
|
+
const walk = (dir) => {
|
|
135
|
+
let entries;
|
|
136
|
+
try {
|
|
137
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
for (const e of entries) {
|
|
143
|
+
const full = path.join(dir, e.name);
|
|
144
|
+
if (e.isDirectory())
|
|
145
|
+
walk(full);
|
|
146
|
+
else if (e.isFile()) {
|
|
147
|
+
if (matchesExt ? e.name.endsWith(matchesExt) : e.name === exact)
|
|
148
|
+
out.push(full);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
walk(root);
|
|
153
|
+
return out.sort();
|
|
154
|
+
}
|
|
155
|
+
export function discoverRuntimeStore(runtime, storePath) {
|
|
156
|
+
if (storePath === null) {
|
|
157
|
+
return runtimeStatus(runtime, { status: "skipped", reason: "disabled", storePath: null });
|
|
158
|
+
}
|
|
159
|
+
if (!fs.existsSync(storePath)) {
|
|
160
|
+
return runtimeStatus(runtime, {
|
|
161
|
+
status: "missing",
|
|
162
|
+
reason: "store_absent",
|
|
163
|
+
storePath,
|
|
164
|
+
remediationLabels: runtime === "github-copilot" ? [COPILOT_SPARSE_REMEDIATION] : null,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if ((runtime === "opencode" || runtime === "github-copilot") && isFilePath(storePath)) {
|
|
168
|
+
return runtimeStatus(runtime, {
|
|
169
|
+
status: "available",
|
|
170
|
+
reason: "candidate_files_found",
|
|
171
|
+
storePath,
|
|
172
|
+
candidateCount: 1,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (!isDir(storePath)) {
|
|
176
|
+
return runtimeStatus(runtime, { status: "degraded", reason: "store_not_directory", storePath });
|
|
177
|
+
}
|
|
178
|
+
let candidates;
|
|
179
|
+
try {
|
|
180
|
+
candidates = rglob(storePath, RUNTIME_STORE_GLOBS[runtime]);
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return runtimeStatus(runtime, { status: "degraded", reason: "store_unreadable", storePath });
|
|
184
|
+
}
|
|
185
|
+
if (candidates.length === 0) {
|
|
186
|
+
return runtimeStatus(runtime, {
|
|
187
|
+
status: "sparse",
|
|
188
|
+
reason: "no_candidate_files",
|
|
189
|
+
storePath,
|
|
190
|
+
candidateCount: 0,
|
|
191
|
+
remediationLabels: runtime === "github-copilot" ? [COPILOT_SPARSE_REMEDIATION] : null,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return runtimeStatus(runtime, {
|
|
195
|
+
status: "available",
|
|
196
|
+
reason: "candidate_files_found",
|
|
197
|
+
storePath,
|
|
198
|
+
candidateCount: candidates.length,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
// ── record builders + content helpers ──────────────────────────────
|
|
202
|
+
import { resolvePath } from "../core/paths.js";
|
|
203
|
+
function splitLines(text) {
|
|
204
|
+
const parts = text.split(/\r\n|\r|\n/);
|
|
205
|
+
if (parts.length > 0 && parts[parts.length - 1] === "")
|
|
206
|
+
parts.pop();
|
|
207
|
+
return parts;
|
|
208
|
+
}
|
|
209
|
+
export function record(opts) {
|
|
210
|
+
const item = {
|
|
211
|
+
source_id: stableId(opts.sourceKind, ...opts.sourceParts),
|
|
212
|
+
timestamp: opts.timestamp,
|
|
213
|
+
project_id: projectIdFromPath(opts.projectPath),
|
|
214
|
+
source_kind: opts.sourceKind,
|
|
215
|
+
runtime: opts.runtime,
|
|
216
|
+
adapter_version: ADAPTER_VERSION,
|
|
217
|
+
data: opts.data,
|
|
218
|
+
};
|
|
219
|
+
if (opts.projectPath !== null && opts.projectPath !== undefined)
|
|
220
|
+
item.project_path = opts.projectPath;
|
|
221
|
+
if (opts.sessionId) {
|
|
222
|
+
item.session_id = opts.sessionId;
|
|
223
|
+
item.conversation_key = opts.sessionId;
|
|
224
|
+
}
|
|
225
|
+
return item;
|
|
226
|
+
}
|
|
227
|
+
function isPlainObject(v) {
|
|
228
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
229
|
+
}
|
|
230
|
+
export function payloadItem(event) {
|
|
231
|
+
const payload = event.payload;
|
|
232
|
+
if (isPlainObject(payload)) {
|
|
233
|
+
const nested = payload.item;
|
|
234
|
+
if (isPlainObject(nested))
|
|
235
|
+
return nested;
|
|
236
|
+
return payload;
|
|
237
|
+
}
|
|
238
|
+
return event;
|
|
239
|
+
}
|
|
240
|
+
export function eventKind(event) {
|
|
241
|
+
for (const key of ["type", "event", "name"]) {
|
|
242
|
+
const value = event[key];
|
|
243
|
+
if (typeof value === "string")
|
|
244
|
+
return value;
|
|
245
|
+
}
|
|
246
|
+
return "";
|
|
247
|
+
}
|
|
248
|
+
export function eventTimestamp(item, fallback) {
|
|
249
|
+
const payload = isPlainObject(item.payload) ? item.payload : {};
|
|
250
|
+
const nested = isPlainObject(payload.item) ? payload.item : {};
|
|
251
|
+
for (const source of [item, payload, nested]) {
|
|
252
|
+
for (const key of ["timestamp", "created_at", "createdAt", "time"]) {
|
|
253
|
+
const value = source[key];
|
|
254
|
+
if (typeof value === "string" && value)
|
|
255
|
+
return value;
|
|
256
|
+
if (typeof value === "number") {
|
|
257
|
+
return new Date(value * 1000).toISOString();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return fallback;
|
|
262
|
+
}
|
|
263
|
+
export function textFromContent(value) {
|
|
264
|
+
if (value === null || value === undefined)
|
|
265
|
+
return "";
|
|
266
|
+
if (typeof value === "string")
|
|
267
|
+
return value;
|
|
268
|
+
if (Array.isArray(value)) {
|
|
269
|
+
const parts = value.map((it) => textFromContent(it));
|
|
270
|
+
return parts.filter((p) => p).join("\n");
|
|
271
|
+
}
|
|
272
|
+
if (isPlainObject(value)) {
|
|
273
|
+
for (const key of ["text", "input_text", "output_text", "message", "content"]) {
|
|
274
|
+
const text = textFromContent(value[key]);
|
|
275
|
+
if (text)
|
|
276
|
+
return text;
|
|
277
|
+
}
|
|
278
|
+
return "";
|
|
279
|
+
}
|
|
280
|
+
return pyStr(value);
|
|
281
|
+
}
|
|
282
|
+
export function claudeContentItems(event) {
|
|
283
|
+
const items = [];
|
|
284
|
+
const msg = isPlainObject(event.message) ? event.message : null;
|
|
285
|
+
for (const source of [event, msg]) {
|
|
286
|
+
if (!isPlainObject(source))
|
|
287
|
+
continue;
|
|
288
|
+
const content = source.content;
|
|
289
|
+
if (Array.isArray(content)) {
|
|
290
|
+
for (const it of content)
|
|
291
|
+
if (isPlainObject(it))
|
|
292
|
+
items.push(it);
|
|
293
|
+
}
|
|
294
|
+
else if (isPlainObject(content)) {
|
|
295
|
+
items.push(content);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return items;
|
|
299
|
+
}
|
|
300
|
+
export function* iterJsonl(p, errors) {
|
|
301
|
+
let text;
|
|
302
|
+
try {
|
|
303
|
+
text = fs.readFileSync(p, "utf-8");
|
|
304
|
+
}
|
|
305
|
+
catch (exc) {
|
|
306
|
+
errors.push(`${p}: cannot read: ${exc.message}`);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
const lines = text.split("\n");
|
|
310
|
+
for (let i = 0; i < lines.length; i++) {
|
|
311
|
+
const stripped = lines[i].trim();
|
|
312
|
+
if (!stripped)
|
|
313
|
+
continue;
|
|
314
|
+
let item;
|
|
315
|
+
try {
|
|
316
|
+
item = JSON.parse(stripped);
|
|
317
|
+
}
|
|
318
|
+
catch (exc) {
|
|
319
|
+
errors.push(`${p}:${i + 1}: invalid jsonl: ${exc.message}`);
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (isPlainObject(item))
|
|
323
|
+
yield item;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
export function signalType(text) {
|
|
327
|
+
if (!text || !DECISION_RE.test(text))
|
|
328
|
+
return null;
|
|
329
|
+
if (CORRECTION_RE.test(text))
|
|
330
|
+
return "correction";
|
|
331
|
+
if (QUESTION_RE.test(text))
|
|
332
|
+
return "question";
|
|
333
|
+
return "decision";
|
|
334
|
+
}
|
|
335
|
+
function toolCallRecordFromItem(args) {
|
|
336
|
+
const { item, event } = args;
|
|
337
|
+
const kind = eventKind(event);
|
|
338
|
+
const itemType = item.type;
|
|
339
|
+
if (!["tool_call", "function_call"].includes(kind) &&
|
|
340
|
+
!["tool_call", "function_call", "tool_use"].includes(itemType)) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
const toolName = item.tool_name || item.name || item.tool;
|
|
344
|
+
if (typeof toolName !== "string" || !toolName)
|
|
345
|
+
return null;
|
|
346
|
+
let argumentsVal = item.arguments || item.input || item.args || {};
|
|
347
|
+
if (typeof argumentsVal === "string") {
|
|
348
|
+
try {
|
|
349
|
+
argumentsVal = JSON.parse(argumentsVal);
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
argumentsVal = { raw: argumentsVal };
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (!isPlainObject(argumentsVal))
|
|
356
|
+
argumentsVal = { value: argumentsVal };
|
|
357
|
+
return record({
|
|
358
|
+
sourceKind: "tool_call",
|
|
359
|
+
timestamp: eventTimestamp(event, args.fallbackTimestamp),
|
|
360
|
+
projectPath: args.projectPath,
|
|
361
|
+
runtime: args.runtime,
|
|
362
|
+
sourceParts: [resolvePath(args.sourcePath), args.index, "tool", toolName],
|
|
363
|
+
sessionId: args.sessionId,
|
|
364
|
+
data: { tool_name: toolName, arguments: argumentsVal },
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
function toolCallRecord(args) {
|
|
368
|
+
return toolCallRecordFromItem({ ...args, item: payloadItem(args.event) });
|
|
369
|
+
}
|
|
370
|
+
// ── instruction documents + project config signals ─────────────────
|
|
371
|
+
export function extractInstructionDocuments(projectRoots, errors) {
|
|
372
|
+
const records = [];
|
|
373
|
+
const docNames = [
|
|
374
|
+
["AGENTS.md", "agents_md"],
|
|
375
|
+
["CLAUDE.md", "claude_md"],
|
|
376
|
+
];
|
|
377
|
+
for (const root of projectRoots) {
|
|
378
|
+
for (const [filename, docType] of docNames) {
|
|
379
|
+
const p = path.join(root, filename);
|
|
380
|
+
if (!fs.existsSync(p))
|
|
381
|
+
continue;
|
|
382
|
+
let content;
|
|
383
|
+
try {
|
|
384
|
+
content = fs.readFileSync(p, "utf-8");
|
|
385
|
+
}
|
|
386
|
+
catch (exc) {
|
|
387
|
+
errors.push(`${p}: cannot read instruction document: ${exc.message}`);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
records.push(record({
|
|
391
|
+
sourceKind: "instruction_document",
|
|
392
|
+
timestamp: isoFromMtime(p),
|
|
393
|
+
projectPath: root,
|
|
394
|
+
runtime: "filesystem",
|
|
395
|
+
sourceParts: [resolvePath(p)],
|
|
396
|
+
data: { doc_type: docType, name: filename, content, scope: "project" },
|
|
397
|
+
}));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return records;
|
|
401
|
+
}
|
|
402
|
+
function packageJsonSignals(p) {
|
|
403
|
+
const data = JSON.parse(fs.readFileSync(p, "utf-8"));
|
|
404
|
+
if (!isPlainObject(data))
|
|
405
|
+
return [];
|
|
406
|
+
const signals = [];
|
|
407
|
+
const name = data.name;
|
|
408
|
+
if (typeof name === "string" && name)
|
|
409
|
+
signals.push(`name=${name}`);
|
|
410
|
+
for (const section of ["scripts", "dependencies", "devDependencies"]) {
|
|
411
|
+
const sectionData = data[section];
|
|
412
|
+
if (!isPlainObject(sectionData))
|
|
413
|
+
continue;
|
|
414
|
+
for (const key of Object.keys(sectionData).sort().slice(0, 30)) {
|
|
415
|
+
signals.push(`${section}:${key}`);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return signals;
|
|
419
|
+
}
|
|
420
|
+
function textConfigSignals(p, configType) {
|
|
421
|
+
let lines;
|
|
422
|
+
try {
|
|
423
|
+
lines = splitLines(fs.readFileSync(p, "utf-8"));
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
return [];
|
|
427
|
+
}
|
|
428
|
+
const signals = [];
|
|
429
|
+
for (const rawLine of lines) {
|
|
430
|
+
const line = rawLine.trim();
|
|
431
|
+
if (!line || line.startsWith("#") || line.startsWith("//"))
|
|
432
|
+
continue;
|
|
433
|
+
if (configType === "gomod" && (line.startsWith("module ") || line.startsWith("go ") || line.startsWith("require "))) {
|
|
434
|
+
signals.push(line);
|
|
435
|
+
}
|
|
436
|
+
else if (configType === "pyproject" &&
|
|
437
|
+
(line.startsWith("[") || line.startsWith("requires-python") || line.startsWith("dependencies") || line.startsWith("name"))) {
|
|
438
|
+
signals.push(line);
|
|
439
|
+
}
|
|
440
|
+
else if (configType === "cargo_toml" && (line.startsWith("[") || line.startsWith("name") || line.startsWith("edition"))) {
|
|
441
|
+
signals.push(line);
|
|
442
|
+
}
|
|
443
|
+
else if (configType === "lefthook" && line.includes(":")) {
|
|
444
|
+
signals.push(line);
|
|
445
|
+
}
|
|
446
|
+
if (signals.length >= 40)
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
return signals;
|
|
450
|
+
}
|
|
451
|
+
export function extractProjectConfigSignals(projectRoots, errors) {
|
|
452
|
+
const records = [];
|
|
453
|
+
const configFiles = [
|
|
454
|
+
["package.json", "package_json", packageJsonSignals],
|
|
455
|
+
["pyproject.toml", "pyproject", (p) => textConfigSignals(p, "pyproject")],
|
|
456
|
+
["go.mod", "gomod", (p) => textConfigSignals(p, "gomod")],
|
|
457
|
+
["Cargo.toml", "cargo_toml", (p) => textConfigSignals(p, "cargo_toml")],
|
|
458
|
+
[".lefthook.yml", "lefthook", (p) => textConfigSignals(p, "lefthook")],
|
|
459
|
+
["lefthook.yml", "lefthook", (p) => textConfigSignals(p, "lefthook")],
|
|
460
|
+
];
|
|
461
|
+
for (const root of projectRoots) {
|
|
462
|
+
for (const [filename, configType, extractor] of configFiles) {
|
|
463
|
+
const p = path.join(root, filename);
|
|
464
|
+
if (!fs.existsSync(p))
|
|
465
|
+
continue;
|
|
466
|
+
let signals;
|
|
467
|
+
try {
|
|
468
|
+
signals = extractor(p);
|
|
469
|
+
}
|
|
470
|
+
catch (exc) {
|
|
471
|
+
errors.push(`${p}: cannot extract config signals: ${exc.message}`);
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
if (signals.length === 0)
|
|
475
|
+
continue;
|
|
476
|
+
records.push(record({
|
|
477
|
+
sourceKind: "project_config_signal",
|
|
478
|
+
timestamp: isoFromMtime(p),
|
|
479
|
+
projectPath: root,
|
|
480
|
+
runtime: "filesystem",
|
|
481
|
+
sourceParts: [resolvePath(p), configType],
|
|
482
|
+
data: { config_type: configType, file_path: path.relative(root, p), signals },
|
|
483
|
+
}));
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return records;
|
|
487
|
+
}
|
|
488
|
+
// ── JSONL session extractors (codex, claude-code) ──────────────────
|
|
489
|
+
function pathStem(p) {
|
|
490
|
+
const base = path.basename(p);
|
|
491
|
+
const ext = path.extname(base);
|
|
492
|
+
return ext ? base.slice(0, -ext.length) : base;
|
|
493
|
+
}
|
|
494
|
+
export function extractCodexSessions(sessionsDir, errors) {
|
|
495
|
+
if (sessionsDir === null || !fs.existsSync(sessionsDir))
|
|
496
|
+
return [];
|
|
497
|
+
const records = [];
|
|
498
|
+
for (const p of rglob(sessionsDir, "*.jsonl")) {
|
|
499
|
+
const fallbackTimestamp = isoFromMtime(p);
|
|
500
|
+
let sessionId = pathStem(p);
|
|
501
|
+
let projectPath = null;
|
|
502
|
+
let previousAssistant = "";
|
|
503
|
+
let index = 0;
|
|
504
|
+
for (const event of iterJsonl(p, errors)) {
|
|
505
|
+
index += 1;
|
|
506
|
+
const kind = eventKind(event);
|
|
507
|
+
const payload = isPlainObject(event.payload) ? event.payload : {};
|
|
508
|
+
if (kind === "session_meta") {
|
|
509
|
+
const sid = payload.id || event.id;
|
|
510
|
+
if (typeof sid === "string" && sid)
|
|
511
|
+
sessionId = sid;
|
|
512
|
+
const cwd = payload.cwd || payload.working_directory;
|
|
513
|
+
if (typeof cwd === "string" && cwd)
|
|
514
|
+
projectPath = cwd;
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
if (kind === "turn_context") {
|
|
518
|
+
const cwd = payload.cwd || payload.working_directory;
|
|
519
|
+
if (typeof cwd === "string" && cwd)
|
|
520
|
+
projectPath = cwd;
|
|
521
|
+
continue;
|
|
522
|
+
}
|
|
523
|
+
const toolRecord = toolCallRecord({ event, fallbackTimestamp, projectPath, runtime: "codex", sourcePath: p, index, sessionId });
|
|
524
|
+
if (toolRecord !== null) {
|
|
525
|
+
records.push(toolRecord);
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
const item = payloadItem(event);
|
|
529
|
+
const itemType = item.type;
|
|
530
|
+
let role = item.role || item.actor;
|
|
531
|
+
if (kind === "user_msg")
|
|
532
|
+
role = "user";
|
|
533
|
+
if (role !== "user" && role !== "assistant")
|
|
534
|
+
continue;
|
|
535
|
+
const skipType = itemType !== undefined && itemType !== null && itemType !== "message";
|
|
536
|
+
if (skipType && !["response_item", "user_msg"].includes(kind))
|
|
537
|
+
continue;
|
|
538
|
+
const content = textFromContent(item.content || item.text || item.message);
|
|
539
|
+
if (!content)
|
|
540
|
+
continue;
|
|
541
|
+
const timestamp = eventTimestamp(event, fallbackTimestamp);
|
|
542
|
+
const data = { actor: role, content };
|
|
543
|
+
if (role === "user") {
|
|
544
|
+
if (previousAssistant)
|
|
545
|
+
data.preceding_context = previousAssistant.slice(-2000);
|
|
546
|
+
const sig = signalType(content);
|
|
547
|
+
if (sig)
|
|
548
|
+
data.signal_type = sig;
|
|
549
|
+
}
|
|
550
|
+
else {
|
|
551
|
+
previousAssistant = content;
|
|
552
|
+
}
|
|
553
|
+
records.push(record({
|
|
554
|
+
sourceKind: "conversation_turn",
|
|
555
|
+
timestamp,
|
|
556
|
+
projectPath,
|
|
557
|
+
runtime: "codex",
|
|
558
|
+
sourceParts: [resolvePath(p), index, role, content.slice(0, 80)],
|
|
559
|
+
sessionId,
|
|
560
|
+
data,
|
|
561
|
+
}));
|
|
562
|
+
if (role === "user") {
|
|
563
|
+
const sig = signalType(content);
|
|
564
|
+
if (sig) {
|
|
565
|
+
records.push(record({
|
|
566
|
+
sourceKind: "history_prompt",
|
|
567
|
+
timestamp,
|
|
568
|
+
projectPath,
|
|
569
|
+
runtime: "codex",
|
|
570
|
+
sourceParts: [resolvePath(p), index, "history", content.slice(0, 120)],
|
|
571
|
+
sessionId,
|
|
572
|
+
data: { prompt: content, signal_type: sig },
|
|
573
|
+
}));
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return records;
|
|
579
|
+
}
|
|
580
|
+
export function extractClaudeProjectSessions(projectsDir, errors) {
|
|
581
|
+
if (projectsDir === null || !fs.existsSync(projectsDir))
|
|
582
|
+
return [];
|
|
583
|
+
const records = [];
|
|
584
|
+
for (const p of rglob(projectsDir, "*.jsonl")) {
|
|
585
|
+
const fallbackTimestamp = isoFromMtime(p);
|
|
586
|
+
let sessionId = pathStem(p);
|
|
587
|
+
let projectPath = null;
|
|
588
|
+
let previousAssistant = "";
|
|
589
|
+
let index = 0;
|
|
590
|
+
for (const event of iterJsonl(p, errors)) {
|
|
591
|
+
index += 1;
|
|
592
|
+
const sid = event.sessionId || event.session_id || event.sessionID;
|
|
593
|
+
if (typeof sid === "string" && sid)
|
|
594
|
+
sessionId = sid;
|
|
595
|
+
const cwd = event.cwd || event.workingDirectory || event.working_directory;
|
|
596
|
+
if (typeof cwd === "string" && cwd)
|
|
597
|
+
projectPath = cwd;
|
|
598
|
+
const toolRecord = toolCallRecord({ event, fallbackTimestamp, projectPath, runtime: "claude-code", sourcePath: p, index, sessionId });
|
|
599
|
+
if (toolRecord !== null) {
|
|
600
|
+
records.push(toolRecord);
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
let contentIndex = 0;
|
|
604
|
+
for (const item of claudeContentItems(event)) {
|
|
605
|
+
contentIndex += 1;
|
|
606
|
+
const tr = toolCallRecordFromItem({
|
|
607
|
+
item,
|
|
608
|
+
event,
|
|
609
|
+
fallbackTimestamp,
|
|
610
|
+
projectPath,
|
|
611
|
+
runtime: "claude-code",
|
|
612
|
+
sourcePath: p,
|
|
613
|
+
index: index * 1000 + contentIndex,
|
|
614
|
+
sessionId,
|
|
615
|
+
});
|
|
616
|
+
if (tr !== null)
|
|
617
|
+
records.push(tr);
|
|
618
|
+
}
|
|
619
|
+
let role = event.role || event.type;
|
|
620
|
+
if (role !== "user" && role !== "assistant") {
|
|
621
|
+
const message = event.message;
|
|
622
|
+
if (isPlainObject(message))
|
|
623
|
+
role = message.role;
|
|
624
|
+
}
|
|
625
|
+
if (role !== "user" && role !== "assistant")
|
|
626
|
+
continue;
|
|
627
|
+
const content = textFromContent(event.content || event.text || (isPlainObject(event.message) ? event.message : null));
|
|
628
|
+
if (!content)
|
|
629
|
+
continue;
|
|
630
|
+
const timestamp = eventTimestamp(event, fallbackTimestamp);
|
|
631
|
+
const data = { actor: role, content };
|
|
632
|
+
if (role === "user") {
|
|
633
|
+
if (previousAssistant)
|
|
634
|
+
data.preceding_context = previousAssistant.slice(-2000);
|
|
635
|
+
const sig = signalType(content);
|
|
636
|
+
if (sig)
|
|
637
|
+
data.signal_type = sig;
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
previousAssistant = content;
|
|
641
|
+
}
|
|
642
|
+
records.push(record({
|
|
643
|
+
sourceKind: "conversation_turn",
|
|
644
|
+
timestamp,
|
|
645
|
+
projectPath,
|
|
646
|
+
runtime: "claude-code",
|
|
647
|
+
sourceParts: [resolvePath(p), index, role, content.slice(0, 80)],
|
|
648
|
+
sessionId,
|
|
649
|
+
data,
|
|
650
|
+
}));
|
|
651
|
+
if (role === "user") {
|
|
652
|
+
const sig = signalType(content);
|
|
653
|
+
if (sig) {
|
|
654
|
+
records.push(record({
|
|
655
|
+
sourceKind: "history_prompt",
|
|
656
|
+
timestamp,
|
|
657
|
+
projectPath,
|
|
658
|
+
runtime: "claude-code",
|
|
659
|
+
sourceParts: [resolvePath(p), index, "history", content.slice(0, 120)],
|
|
660
|
+
sessionId,
|
|
661
|
+
data: { prompt: content, signal_type: sig },
|
|
662
|
+
}));
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return records;
|
|
668
|
+
}
|
|
669
|
+
// ── SQLite (node:sqlite, lazy) ──────────────────────────────────────
|
|
670
|
+
import { createRequire } from "node:module";
|
|
671
|
+
const requireCjs = createRequire(import.meta.url);
|
|
672
|
+
/** Lazy open a read-only SQLite db via node:sqlite (warning only fires here). */
|
|
673
|
+
function openSqlite(p) {
|
|
674
|
+
const { DatabaseSync } = requireCjs("node:sqlite");
|
|
675
|
+
return new DatabaseSync(resolvePath(p), { readOnly: true });
|
|
676
|
+
}
|
|
677
|
+
function sqliteTimestamp(value, fallback) {
|
|
678
|
+
if (typeof value === "string" && value)
|
|
679
|
+
return value;
|
|
680
|
+
if (typeof value === "number") {
|
|
681
|
+
let numeric = value;
|
|
682
|
+
if (numeric > 10_000_000_000)
|
|
683
|
+
numeric /= 1000;
|
|
684
|
+
return new Date(numeric * 1000).toISOString();
|
|
685
|
+
}
|
|
686
|
+
return fallback;
|
|
687
|
+
}
|
|
688
|
+
function jsonDict(value) {
|
|
689
|
+
if (isPlainObject(value))
|
|
690
|
+
return value;
|
|
691
|
+
if (typeof value === "string" && value) {
|
|
692
|
+
try {
|
|
693
|
+
const parsed = JSON.parse(value);
|
|
694
|
+
return isPlainObject(parsed) ? parsed : {};
|
|
695
|
+
}
|
|
696
|
+
catch {
|
|
697
|
+
return {};
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return {};
|
|
701
|
+
}
|
|
702
|
+
function nestedTimeCreated(data) {
|
|
703
|
+
const timeData = data.time;
|
|
704
|
+
if (isPlainObject(timeData))
|
|
705
|
+
return timeData.created || timeData.start;
|
|
706
|
+
return data.time_created || data.timestamp;
|
|
707
|
+
}
|
|
708
|
+
function tableColumns(conn, table) {
|
|
709
|
+
const rows = conn.prepare(`PRAGMA table_info(${table})`).all();
|
|
710
|
+
return new Set(rows.map((r) => String(r.name)));
|
|
711
|
+
}
|
|
712
|
+
function firstColumn(columns, candidates) {
|
|
713
|
+
for (const c of candidates)
|
|
714
|
+
if (columns.has(c))
|
|
715
|
+
return c;
|
|
716
|
+
return null;
|
|
717
|
+
}
|
|
718
|
+
function qualified(alias, column, label) {
|
|
719
|
+
if (column === null)
|
|
720
|
+
return `NULL AS ${label}`;
|
|
721
|
+
const escaped = column.replace(/"/g, '""');
|
|
722
|
+
return `${alias}."${escaped}" AS ${label}`;
|
|
723
|
+
}
|
|
724
|
+
function opencodeRows(conn) {
|
|
725
|
+
const tables = new Set(conn.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all().map((r) => String(r.name)));
|
|
726
|
+
for (const t of ["session", "message", "part"]) {
|
|
727
|
+
if (!tables.has(t)) {
|
|
728
|
+
const missing = ["session", "message", "part"].filter((x) => !tables.has(x)).sort().join(",");
|
|
729
|
+
throw new Error(`missing opencode tables: ${missing}`);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
const sessionCols = tableColumns(conn, "session");
|
|
733
|
+
const messageCols = tableColumns(conn, "message");
|
|
734
|
+
const partCols = tableColumns(conn, "part");
|
|
735
|
+
const sessionId = firstColumn(sessionCols, ["id", "session_id", "sessionID"]);
|
|
736
|
+
const messageId = firstColumn(messageCols, ["id", "message_id", "messageID"]);
|
|
737
|
+
const messageSession = firstColumn(messageCols, ["sessionID", "session_id", "session", "sessionId"]);
|
|
738
|
+
const partMessage = firstColumn(partCols, ["messageID", "message_id", "message", "messageId"]);
|
|
739
|
+
if (!(sessionId && messageId && messageSession && partMessage))
|
|
740
|
+
throw new Error("missing opencode join columns");
|
|
741
|
+
const messageData = firstColumn(messageCols, ["data"]);
|
|
742
|
+
const partData = firstColumn(partCols, ["data"]);
|
|
743
|
+
const roleCol = firstColumn(messageCols, ["role", "actor", "author"]);
|
|
744
|
+
if (roleCol === null && messageData === null)
|
|
745
|
+
throw new Error("missing opencode message role column");
|
|
746
|
+
const messageTime = firstColumn(messageCols, ["time_created", "time", "timestamp", "created_at", "createdAt"]);
|
|
747
|
+
const partTime = firstColumn(partCols, ["time_created", "time", "timestamp", "created_at", "createdAt"]);
|
|
748
|
+
const sessionTime = firstColumn(sessionCols, ["time_created", "time", "timestamp", "created_at", "createdAt"]);
|
|
749
|
+
const sessionUpdated = firstColumn(sessionCols, ["time_updated", "updated_at", "updatedAt", "time_created", "time"]);
|
|
750
|
+
const projectCol = firstColumn(sessionCols, ["cwd", "project_path", "projectPath", "directory", "path"]);
|
|
751
|
+
const messageText = firstColumn(messageCols, ["content", "text", "message"]);
|
|
752
|
+
const partText = firstColumn(partCols, ["text", "content", "input", "output"]);
|
|
753
|
+
const partType = firstColumn(partCols, ["type", "kind"]);
|
|
754
|
+
const partId = firstColumn(partCols, ["id", "part_id", "partID"]);
|
|
755
|
+
const sortExpr = `COALESCE(m."${messageTime || messageId}", p."${partTime || partMessage}", s."${sessionTime || sessionId}")`;
|
|
756
|
+
const recentSessionExpr = `s."${sessionUpdated || sessionTime || sessionId}"`;
|
|
757
|
+
const query = `
|
|
758
|
+
WITH recent_sessions AS (
|
|
759
|
+
SELECT s."${sessionId}" AS recent_session_id
|
|
760
|
+
FROM session s
|
|
761
|
+
ORDER BY ${recentSessionExpr} DESC,
|
|
762
|
+
s."${sessionId}" DESC
|
|
763
|
+
LIMIT ?
|
|
764
|
+
),
|
|
765
|
+
recent AS (
|
|
766
|
+
SELECT
|
|
767
|
+
${qualified("s", sessionId, "session_id")},
|
|
768
|
+
${qualified("s", projectCol, "project_path")},
|
|
769
|
+
${qualified("m", messageId, "message_id")},
|
|
770
|
+
${qualified("m", roleCol, "role")},
|
|
771
|
+
${qualified("m", messageTime, "message_time")},
|
|
772
|
+
${qualified("m", messageText, "message_text")},
|
|
773
|
+
${qualified("m", messageData, "message_data")},
|
|
774
|
+
${qualified("p", partId, "part_id")},
|
|
775
|
+
${qualified("p", partTime, "part_time")},
|
|
776
|
+
${qualified("p", partType, "part_type")},
|
|
777
|
+
${qualified("p", partText, "part_text")},
|
|
778
|
+
${qualified("p", partData, "part_data")},
|
|
779
|
+
${sortExpr} AS sort_time
|
|
780
|
+
FROM message m
|
|
781
|
+
JOIN session s ON m."${messageSession}" = s."${sessionId}"
|
|
782
|
+
JOIN recent_sessions rs ON rs.recent_session_id = s."${sessionId}"
|
|
783
|
+
LEFT JOIN part p ON p."${partMessage}" = m."${messageId}"
|
|
784
|
+
ORDER BY sort_time DESC,
|
|
785
|
+
m."${messageId}" DESC,
|
|
786
|
+
p."${partId || partMessage}" DESC
|
|
787
|
+
LIMIT ?
|
|
788
|
+
)
|
|
789
|
+
SELECT
|
|
790
|
+
session_id, project_path, message_id, role, message_time,
|
|
791
|
+
message_text, message_data, part_id, part_time, part_type,
|
|
792
|
+
part_text, part_data
|
|
793
|
+
FROM recent
|
|
794
|
+
ORDER BY sort_time, message_id, part_id
|
|
795
|
+
`;
|
|
796
|
+
return conn.prepare(query).all(MAX_SQLITE_SESSIONS, MAX_SQLITE_ROWS);
|
|
797
|
+
}
|
|
798
|
+
function opencodeDbCandidates(storePath) {
|
|
799
|
+
if (isFilePath(storePath))
|
|
800
|
+
return [storePath];
|
|
801
|
+
return rglob(storePath, "opencode.db");
|
|
802
|
+
}
|
|
803
|
+
export function extractOpencodeSessions(storePath, errors) {
|
|
804
|
+
if (storePath === null || !fs.existsSync(storePath))
|
|
805
|
+
return [];
|
|
806
|
+
const records = [];
|
|
807
|
+
for (const dbPath of opencodeDbCandidates(storePath).slice(0, 1)) {
|
|
808
|
+
const fallbackTimestamp = isoFromMtime(dbPath);
|
|
809
|
+
let rows;
|
|
810
|
+
let conn = null;
|
|
811
|
+
try {
|
|
812
|
+
conn = openSqlite(dbPath);
|
|
813
|
+
rows = opencodeRows(conn);
|
|
814
|
+
}
|
|
815
|
+
catch (exc) {
|
|
816
|
+
const msg = exc.message || "";
|
|
817
|
+
if (/lock|busy/i.test(msg))
|
|
818
|
+
throw new PermissionDeniedError(msg);
|
|
819
|
+
errors.push(`${dbPath}: opencode sqlite read failed: ${exc.name || "Error"}`);
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
finally {
|
|
823
|
+
try {
|
|
824
|
+
conn?.close();
|
|
825
|
+
}
|
|
826
|
+
catch {
|
|
827
|
+
/* ignore */
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
const messages = new Map();
|
|
831
|
+
for (const row of rows) {
|
|
832
|
+
const messageData = jsonDict(row.message_data);
|
|
833
|
+
const partData = jsonDict(row.part_data);
|
|
834
|
+
const messageId = String(row.message_id);
|
|
835
|
+
const role = row.role || messageData.role;
|
|
836
|
+
const messageTime = row.message_time || nestedTimeCreated(messageData);
|
|
837
|
+
let item = messages.get(messageId);
|
|
838
|
+
if (item === undefined) {
|
|
839
|
+
item = {
|
|
840
|
+
role,
|
|
841
|
+
session_id: String(row.session_id),
|
|
842
|
+
project_path: row.project_path,
|
|
843
|
+
timestamp: sqliteTimestamp(messageTime, fallbackTimestamp),
|
|
844
|
+
parts: [],
|
|
845
|
+
tools: [],
|
|
846
|
+
};
|
|
847
|
+
messages.set(messageId, item);
|
|
848
|
+
}
|
|
849
|
+
const partType = row.part_type || partData.type;
|
|
850
|
+
const partText = textFromContent(row.part_text || partData.text);
|
|
851
|
+
if (partText)
|
|
852
|
+
item.parts.push(partText);
|
|
853
|
+
else if (row.message_text)
|
|
854
|
+
item.parts.push(textFromContent(row.message_text));
|
|
855
|
+
if (partType === "tool" || partData.tool) {
|
|
856
|
+
const state = isPlainObject(partData.state) ? partData.state : {};
|
|
857
|
+
const argumentsVal = isPlainObject(state.input) ? state.input : {};
|
|
858
|
+
const toolName = partData.tool || partData.name;
|
|
859
|
+
if (typeof toolName === "string" && toolName) {
|
|
860
|
+
item.tools.push({
|
|
861
|
+
part_id: row.part_id,
|
|
862
|
+
tool_name: toolName,
|
|
863
|
+
arguments: argumentsVal,
|
|
864
|
+
timestamp: sqliteTimestamp(row.part_time || nestedTimeCreated(partData), item.timestamp),
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
let previousAssistant = "";
|
|
870
|
+
let index = 0;
|
|
871
|
+
for (const item of messages.values()) {
|
|
872
|
+
index += 1;
|
|
873
|
+
const role = String(item.role).toLowerCase();
|
|
874
|
+
if (role !== "user" && role !== "assistant")
|
|
875
|
+
continue;
|
|
876
|
+
const content = item.parts.filter((p) => p).join("\n");
|
|
877
|
+
if (!content && item.tools.length === 0)
|
|
878
|
+
continue;
|
|
879
|
+
const projectPath = item.project_path ? String(item.project_path) : null;
|
|
880
|
+
if (content) {
|
|
881
|
+
const data = { actor: role, content };
|
|
882
|
+
if (role === "user") {
|
|
883
|
+
if (previousAssistant)
|
|
884
|
+
data.preceding_context = previousAssistant.slice(-2000);
|
|
885
|
+
const sig = signalType(content);
|
|
886
|
+
if (sig)
|
|
887
|
+
data.signal_type = sig;
|
|
888
|
+
}
|
|
889
|
+
else {
|
|
890
|
+
previousAssistant = content;
|
|
891
|
+
}
|
|
892
|
+
records.push(record({
|
|
893
|
+
sourceKind: "conversation_turn",
|
|
894
|
+
timestamp: item.timestamp,
|
|
895
|
+
projectPath,
|
|
896
|
+
runtime: "opencode",
|
|
897
|
+
sourceParts: [resolvePath(dbPath), index, role, content.slice(0, 80)],
|
|
898
|
+
sessionId: item.session_id,
|
|
899
|
+
data,
|
|
900
|
+
}));
|
|
901
|
+
}
|
|
902
|
+
let toolIndex = 0;
|
|
903
|
+
for (const toolItem of item.tools) {
|
|
904
|
+
toolIndex += 1;
|
|
905
|
+
records.push(record({
|
|
906
|
+
sourceKind: "tool_call",
|
|
907
|
+
timestamp: toolItem.timestamp,
|
|
908
|
+
projectPath,
|
|
909
|
+
runtime: "opencode",
|
|
910
|
+
sourceParts: [resolvePath(dbPath), index, toolIndex, "tool", toolItem.tool_name, toolItem.part_id],
|
|
911
|
+
sessionId: item.session_id,
|
|
912
|
+
data: { tool_name: toolItem.tool_name, arguments: toolItem.arguments },
|
|
913
|
+
}));
|
|
914
|
+
}
|
|
915
|
+
if (role === "user" && content) {
|
|
916
|
+
const sig = signalType(content);
|
|
917
|
+
if (sig) {
|
|
918
|
+
records.push(record({
|
|
919
|
+
sourceKind: "history_prompt",
|
|
920
|
+
timestamp: item.timestamp,
|
|
921
|
+
projectPath,
|
|
922
|
+
runtime: "opencode",
|
|
923
|
+
sourceParts: [resolvePath(dbPath), index, "history", content.slice(0, 120)],
|
|
924
|
+
sessionId: item.session_id,
|
|
925
|
+
data: { prompt: content, signal_type: sig },
|
|
926
|
+
}));
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return records;
|
|
932
|
+
}
|
|
933
|
+
/** Mirrors Python raising PermissionError on a locked/busy store. */
|
|
934
|
+
export class PermissionDeniedError extends Error {
|
|
935
|
+
constructor(message) {
|
|
936
|
+
super(message);
|
|
937
|
+
this.name = "PermissionDeniedError";
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
// ── Copilot SQLite extractor ────────────────────────────────────────
|
|
941
|
+
function copilotDbCandidates(storePath) {
|
|
942
|
+
if (isFilePath(storePath))
|
|
943
|
+
return [storePath];
|
|
944
|
+
return rglob(storePath, "session-store.db");
|
|
945
|
+
}
|
|
946
|
+
function copilotRows(conn) {
|
|
947
|
+
const tables = new Set(conn.prepare("SELECT name FROM sqlite_master WHERE type = 'table'").all().map((r) => String(r.name)));
|
|
948
|
+
for (const t of ["sessions", "turns"]) {
|
|
949
|
+
if (!tables.has(t)) {
|
|
950
|
+
const missing = ["sessions", "turns"].filter((x) => !tables.has(x)).sort().join(",");
|
|
951
|
+
throw new Error(`missing copilot tables: ${missing}`);
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
const sessionCols = tableColumns(conn, "sessions");
|
|
955
|
+
const turnCols = tableColumns(conn, "turns");
|
|
956
|
+
const sessionId = firstColumn(sessionCols, ["id", "session_id", "sessionID"]);
|
|
957
|
+
const turnId = firstColumn(turnCols, ["id", "turn_id", "turnID"]);
|
|
958
|
+
const turnSession = firstColumn(turnCols, ["session_id", "sessionID", "session", "sessionId"]);
|
|
959
|
+
if (!(sessionId && turnSession))
|
|
960
|
+
throw new Error("missing copilot join columns");
|
|
961
|
+
const roleCol = firstColumn(turnCols, ["role", "actor", "author"]);
|
|
962
|
+
const textCol = firstColumn(turnCols, ["content", "text", "message", "prompt", "response"]);
|
|
963
|
+
if (roleCol === null)
|
|
964
|
+
throw new Error("missing copilot turn role column");
|
|
965
|
+
if (textCol === null)
|
|
966
|
+
throw new Error("missing copilot turn text column");
|
|
967
|
+
const sessionTime = firstColumn(sessionCols, ["time", "timestamp", "created_at", "createdAt"]);
|
|
968
|
+
const turnTime = firstColumn(turnCols, ["time", "timestamp", "created_at", "createdAt"]);
|
|
969
|
+
const turnOrder = firstColumn(turnCols, ["turn_index", "turnIndex", "idx", "position", "sequence"]);
|
|
970
|
+
const projectCol = firstColumn(sessionCols, ["cwd", "project_path", "projectPath", "directory", "path"]);
|
|
971
|
+
const turnData = firstColumn(turnCols, ["data", "payload", "json"]);
|
|
972
|
+
const turnType = firstColumn(turnCols, ["type", "kind"]);
|
|
973
|
+
const toolNameCol = firstColumn(turnCols, ["tool_name", "toolName", "tool", "name", "command_name", "commandName"]);
|
|
974
|
+
const toolArgs = firstColumn(turnCols, [
|
|
975
|
+
"arguments", "args", "input", "tool_input", "toolInput", "command", "command_line", "commandLine",
|
|
976
|
+
]);
|
|
977
|
+
const orderExpr = turnOrder ? `t."${turnOrder}"` : `t."${turnTime || turnSession}"`;
|
|
978
|
+
const idExpr = turnId ? `t."${turnId}"` : "t.rowid";
|
|
979
|
+
const query = `
|
|
980
|
+
SELECT
|
|
981
|
+
${qualified("s", sessionId, "session_id")},
|
|
982
|
+
${qualified("s", projectCol, "project_path")},
|
|
983
|
+
${qualified("t", turnId, "turn_id")},
|
|
984
|
+
${qualified("t", roleCol, "role")},
|
|
985
|
+
${qualified("t", turnTime, "turn_time")},
|
|
986
|
+
${qualified("s", sessionTime, "session_time")},
|
|
987
|
+
${qualified("t", textCol, "turn_text")},
|
|
988
|
+
${qualified("t", turnData, "turn_data")},
|
|
989
|
+
${qualified("t", turnType, "turn_type")},
|
|
990
|
+
${qualified("t", toolNameCol, "tool_name")},
|
|
991
|
+
${qualified("t", toolArgs, "tool_args")}
|
|
992
|
+
FROM turns t
|
|
993
|
+
JOIN sessions s ON t."${turnSession}" = s."${sessionId}"
|
|
994
|
+
ORDER BY COALESCE(${orderExpr}, t."${turnSession}"),
|
|
995
|
+
CASE LOWER(t."${roleCol}") WHEN 'user' THEN 0 WHEN 'assistant' THEN 1 ELSE 2 END,
|
|
996
|
+
${idExpr}
|
|
997
|
+
LIMIT ?
|
|
998
|
+
`;
|
|
999
|
+
return conn.prepare(query).all(MAX_SQLITE_ROWS);
|
|
1000
|
+
}
|
|
1001
|
+
function copilotArgumentDict(value) {
|
|
1002
|
+
if (isPlainObject(value)) {
|
|
1003
|
+
const stateInput = value.input;
|
|
1004
|
+
if (isPlainObject(stateInput) && !("command" in value))
|
|
1005
|
+
return stateInput;
|
|
1006
|
+
return value;
|
|
1007
|
+
}
|
|
1008
|
+
if (typeof value === "string" && value) {
|
|
1009
|
+
try {
|
|
1010
|
+
const parsed = JSON.parse(value);
|
|
1011
|
+
return isPlainObject(parsed) ? parsed : { value: parsed };
|
|
1012
|
+
}
|
|
1013
|
+
catch {
|
|
1014
|
+
return { command: value };
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
return {};
|
|
1018
|
+
}
|
|
1019
|
+
function copilotJsonTools(value, errors, malformedLabel) {
|
|
1020
|
+
if (typeof value === "string" && value) {
|
|
1021
|
+
const raw = value;
|
|
1022
|
+
try {
|
|
1023
|
+
value = JSON.parse(value);
|
|
1024
|
+
}
|
|
1025
|
+
catch {
|
|
1026
|
+
if (errors && malformedLabel && /^\s*[{[]/.test(raw))
|
|
1027
|
+
errors.push(malformedLabel);
|
|
1028
|
+
return [];
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
if (Array.isArray(value)) {
|
|
1032
|
+
const tools = [];
|
|
1033
|
+
for (const item of value)
|
|
1034
|
+
tools.push(...copilotJsonTools(item, errors, malformedLabel));
|
|
1035
|
+
return tools;
|
|
1036
|
+
}
|
|
1037
|
+
if (!isPlainObject(value))
|
|
1038
|
+
return [];
|
|
1039
|
+
const toolName = value.tool_name || value.toolName || value.tool || value.name;
|
|
1040
|
+
const valueType = String(value.type || value.kind || "").toLowerCase();
|
|
1041
|
+
if (typeof toolName === "string" &&
|
|
1042
|
+
toolName &&
|
|
1043
|
+
["", "tool", "tool_call", "function_call", "tool_use", "command"].includes(valueType)) {
|
|
1044
|
+
return [
|
|
1045
|
+
{
|
|
1046
|
+
tool_name: toolName,
|
|
1047
|
+
arguments: copilotArgumentDict(value.arguments || value.args || value.input || value.state || {}),
|
|
1048
|
+
},
|
|
1049
|
+
];
|
|
1050
|
+
}
|
|
1051
|
+
const tools = [];
|
|
1052
|
+
for (const key of ["tool_calls", "toolCalls", "tools", "content", "parts", "items"]) {
|
|
1053
|
+
const nested = value[key];
|
|
1054
|
+
if (isPlainObject(nested) || Array.isArray(nested))
|
|
1055
|
+
tools.push(...copilotJsonTools(nested, errors, malformedLabel));
|
|
1056
|
+
}
|
|
1057
|
+
return tools;
|
|
1058
|
+
}
|
|
1059
|
+
function copilotRowTools(row, errors) {
|
|
1060
|
+
const tools = copilotJsonTools(row.turn_data, errors, "copilot malformed json tool payload");
|
|
1061
|
+
tools.push(...copilotJsonTools(row.turn_text));
|
|
1062
|
+
const toolName = row.tool_name;
|
|
1063
|
+
if (typeof toolName === "string" && toolName) {
|
|
1064
|
+
tools.push({ tool_name: toolName, arguments: copilotArgumentDict(row.tool_args) });
|
|
1065
|
+
}
|
|
1066
|
+
else if (String(row.turn_type || "").toLowerCase() === "command") {
|
|
1067
|
+
const command = row.tool_args || row.turn_text;
|
|
1068
|
+
if (typeof command === "string" && command)
|
|
1069
|
+
tools.push({ tool_name: "bash", arguments: { command } });
|
|
1070
|
+
}
|
|
1071
|
+
return tools.filter((t) => typeof t.tool_name === "string" && t.tool_name);
|
|
1072
|
+
}
|
|
1073
|
+
export function extractCopilotSessions(storePath, errors) {
|
|
1074
|
+
if (storePath === null || !fs.existsSync(storePath))
|
|
1075
|
+
return [];
|
|
1076
|
+
const records = [];
|
|
1077
|
+
for (const dbPath of copilotDbCandidates(storePath).slice(0, 1)) {
|
|
1078
|
+
const fallbackTimestamp = isoFromMtime(dbPath);
|
|
1079
|
+
let rows;
|
|
1080
|
+
let conn = null;
|
|
1081
|
+
try {
|
|
1082
|
+
conn = openSqlite(dbPath);
|
|
1083
|
+
rows = copilotRows(conn);
|
|
1084
|
+
}
|
|
1085
|
+
catch (exc) {
|
|
1086
|
+
const msg = exc.message || "";
|
|
1087
|
+
if (/lock|busy/i.test(msg))
|
|
1088
|
+
throw new PermissionDeniedError(msg);
|
|
1089
|
+
errors.push(`${dbPath}: copilot sqlite read failed: ${exc.name || "Error"}`);
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1092
|
+
finally {
|
|
1093
|
+
try {
|
|
1094
|
+
conn?.close();
|
|
1095
|
+
}
|
|
1096
|
+
catch {
|
|
1097
|
+
/* ignore */
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
let previousAssistant = "";
|
|
1101
|
+
let index = 0;
|
|
1102
|
+
for (const row of rows) {
|
|
1103
|
+
index += 1;
|
|
1104
|
+
const role = String(row.role).toLowerCase();
|
|
1105
|
+
if (role !== "user" && role !== "assistant")
|
|
1106
|
+
continue;
|
|
1107
|
+
const content = textFromContent(row.turn_text);
|
|
1108
|
+
const toolItems = copilotRowTools(row, errors);
|
|
1109
|
+
if (!content && toolItems.length === 0)
|
|
1110
|
+
continue;
|
|
1111
|
+
const projectPath = row.project_path ? String(row.project_path) : null;
|
|
1112
|
+
const timestamp = sqliteTimestamp(row.turn_time || row.session_time, fallbackTimestamp);
|
|
1113
|
+
if (content) {
|
|
1114
|
+
const data = { actor: role, content };
|
|
1115
|
+
if (role === "user") {
|
|
1116
|
+
if (previousAssistant)
|
|
1117
|
+
data.preceding_context = previousAssistant.slice(-2000);
|
|
1118
|
+
const sig = signalType(content);
|
|
1119
|
+
if (sig)
|
|
1120
|
+
data.signal_type = sig;
|
|
1121
|
+
}
|
|
1122
|
+
else {
|
|
1123
|
+
previousAssistant = content;
|
|
1124
|
+
}
|
|
1125
|
+
records.push(record({
|
|
1126
|
+
sourceKind: "conversation_turn",
|
|
1127
|
+
timestamp,
|
|
1128
|
+
projectPath,
|
|
1129
|
+
runtime: "github-copilot",
|
|
1130
|
+
sourceParts: [resolvePath(dbPath), index, role, content.slice(0, 80)],
|
|
1131
|
+
sessionId: String(row.session_id),
|
|
1132
|
+
data,
|
|
1133
|
+
}));
|
|
1134
|
+
}
|
|
1135
|
+
let toolIndex = 0;
|
|
1136
|
+
for (const toolItem of toolItems) {
|
|
1137
|
+
toolIndex += 1;
|
|
1138
|
+
records.push(record({
|
|
1139
|
+
sourceKind: "tool_call",
|
|
1140
|
+
timestamp,
|
|
1141
|
+
projectPath,
|
|
1142
|
+
runtime: "github-copilot",
|
|
1143
|
+
sourceParts: [resolvePath(dbPath), index, toolIndex, "tool", toolItem.tool_name, row.turn_id],
|
|
1144
|
+
sessionId: String(row.session_id),
|
|
1145
|
+
data: { tool_name: String(toolItem.tool_name), arguments: toolItem.arguments || {} },
|
|
1146
|
+
}));
|
|
1147
|
+
}
|
|
1148
|
+
if (role === "user") {
|
|
1149
|
+
const sig = signalType(content);
|
|
1150
|
+
if (sig) {
|
|
1151
|
+
records.push(record({
|
|
1152
|
+
sourceKind: "history_prompt",
|
|
1153
|
+
timestamp,
|
|
1154
|
+
projectPath,
|
|
1155
|
+
runtime: "github-copilot",
|
|
1156
|
+
sourceParts: [resolvePath(dbPath), index, "history", content.slice(0, 120)],
|
|
1157
|
+
sessionId: String(row.session_id),
|
|
1158
|
+
data: { prompt: content, signal_type: sig },
|
|
1159
|
+
}));
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
return records;
|
|
1165
|
+
}
|
|
1166
|
+
// ── runtime path resolvers ──────────────────────────────────────────
|
|
1167
|
+
import { execFileSync } from "node:child_process";
|
|
1168
|
+
import { expanduser } from "../core/paths.js";
|
|
1169
|
+
export function resolveOpencodeDbPath(env = process.env) {
|
|
1170
|
+
let out;
|
|
1171
|
+
try {
|
|
1172
|
+
out = execFileSync("opencode", ["db", "path"], { encoding: "utf-8", timeout: 2000 });
|
|
1173
|
+
}
|
|
1174
|
+
catch {
|
|
1175
|
+
return null;
|
|
1176
|
+
}
|
|
1177
|
+
const trimmed = out.trim();
|
|
1178
|
+
if (!trimmed)
|
|
1179
|
+
return null;
|
|
1180
|
+
const candidate = trimmed.split(/\r\n|\r|\n/)[0];
|
|
1181
|
+
return candidate ? expanduser(candidate) : null;
|
|
1182
|
+
}
|
|
1183
|
+
export function resolveCopilotStorePath(env = process.env) {
|
|
1184
|
+
return expanduser(env.COPILOT_HOME || path.join(os.homedir(), ".copilot"));
|
|
1185
|
+
}
|
|
1186
|
+
export function resolveCursorProjectsPath(env = process.env) {
|
|
1187
|
+
return path.join(expanduser(env.CURSOR_HOME || path.join(os.homedir(), ".cursor")), "projects");
|
|
1188
|
+
}
|
|
1189
|
+
export function resolveCursorChatsPath(env = process.env) {
|
|
1190
|
+
const configHome = env.CURSOR_CONFIG_HOME;
|
|
1191
|
+
if (configHome)
|
|
1192
|
+
return path.join(expanduser(configHome), "chats");
|
|
1193
|
+
return path.join(os.homedir(), ".config", "cursor", "chats");
|
|
1194
|
+
}
|
|
1195
|
+
// ── cursor helpers ──────────────────────────────────────────────────
|
|
1196
|
+
function pathParents(p) {
|
|
1197
|
+
const parents = [];
|
|
1198
|
+
let cur = p;
|
|
1199
|
+
for (;;) {
|
|
1200
|
+
const parent = path.dirname(cur);
|
|
1201
|
+
if (parent === cur)
|
|
1202
|
+
break;
|
|
1203
|
+
parents.push(parent);
|
|
1204
|
+
cur = parent;
|
|
1205
|
+
}
|
|
1206
|
+
return parents;
|
|
1207
|
+
}
|
|
1208
|
+
export function cursorWorkspaceHash(projectRoot) {
|
|
1209
|
+
return crypto.createHash("md5").update(resolvePath(projectRoot), "utf-8").digest("hex");
|
|
1210
|
+
}
|
|
1211
|
+
export function cursorProjectDirSlug(projectRoot) {
|
|
1212
|
+
const resolved = resolvePath(projectRoot);
|
|
1213
|
+
// Python Path.parts: ("/", "a", "b") for absolute; drop the leading "/" anchor.
|
|
1214
|
+
let segs = resolved.split(path.sep).filter((s) => s !== "");
|
|
1215
|
+
// (leading-"/" anchor already dropped by filtering empties)
|
|
1216
|
+
const slug = segs.join("-").toLowerCase();
|
|
1217
|
+
const cleaned = slug.replace(/[^a-z0-9._-]+/g, "-").replace(/^-+/, "").replace(/-+$/, "");
|
|
1218
|
+
return cleaned || "global";
|
|
1219
|
+
}
|
|
1220
|
+
function cursorProjectPathFromDir(projectDir) {
|
|
1221
|
+
const name = path.basename(projectDir);
|
|
1222
|
+
if (!name || /^\d+$/.test(name) || name === "empty-window")
|
|
1223
|
+
return null;
|
|
1224
|
+
const parts = name.split("-");
|
|
1225
|
+
if (parts.length > 1 && parts[0] === "home")
|
|
1226
|
+
return "/" + parts.join("/");
|
|
1227
|
+
return null;
|
|
1228
|
+
}
|
|
1229
|
+
function boundToolArguments(args) {
|
|
1230
|
+
const bounded = {};
|
|
1231
|
+
for (const [key, value] of Object.entries(args)) {
|
|
1232
|
+
if (typeof value === "string" && value.length > MAX_TOOL_ARG_TEXT) {
|
|
1233
|
+
bounded[key] = value.slice(0, MAX_TOOL_ARG_TEXT) + "…";
|
|
1234
|
+
}
|
|
1235
|
+
else if (isPlainObject(value)) {
|
|
1236
|
+
bounded[key] = boundToolArguments(value);
|
|
1237
|
+
}
|
|
1238
|
+
else if (Array.isArray(value)) {
|
|
1239
|
+
bounded[key] = value.map((it) => typeof it === "string" && it.length > MAX_TOOL_ARG_TEXT ? it.slice(0, MAX_TOOL_ARG_TEXT) + "…" : it);
|
|
1240
|
+
}
|
|
1241
|
+
else {
|
|
1242
|
+
bounded[key] = value;
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
return bounded;
|
|
1246
|
+
}
|
|
1247
|
+
function cursorContentItems(event) {
|
|
1248
|
+
const message = event.message;
|
|
1249
|
+
if (!isPlainObject(message))
|
|
1250
|
+
return [];
|
|
1251
|
+
const content = message.content;
|
|
1252
|
+
if (Array.isArray(content))
|
|
1253
|
+
return content.filter((it) => isPlainObject(it));
|
|
1254
|
+
if (isPlainObject(content))
|
|
1255
|
+
return [content];
|
|
1256
|
+
return [];
|
|
1257
|
+
}
|
|
1258
|
+
function iterCursorTranscriptPaths(projectsDir, projectRoots) {
|
|
1259
|
+
if (!isDir(projectsDir))
|
|
1260
|
+
return [];
|
|
1261
|
+
let searchDirs;
|
|
1262
|
+
if (projectRoots.length > 0) {
|
|
1263
|
+
searchDirs = [];
|
|
1264
|
+
for (const root of projectRoots) {
|
|
1265
|
+
const candidate = path.join(projectsDir, cursorProjectDirSlug(root));
|
|
1266
|
+
if (isDir(candidate))
|
|
1267
|
+
searchDirs.push(candidate);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
else {
|
|
1271
|
+
searchDirs = fs
|
|
1272
|
+
.readdirSync(projectsDir, { withFileTypes: true })
|
|
1273
|
+
.filter((e) => e.isDirectory())
|
|
1274
|
+
.map((e) => path.join(projectsDir, e.name));
|
|
1275
|
+
}
|
|
1276
|
+
const paths = [];
|
|
1277
|
+
for (const projectDir of searchDirs) {
|
|
1278
|
+
const transcriptsDir = path.join(projectDir, "agent-transcripts");
|
|
1279
|
+
if (!isDir(transcriptsDir))
|
|
1280
|
+
continue;
|
|
1281
|
+
paths.push(...rglob(transcriptsDir, "*.jsonl"));
|
|
1282
|
+
}
|
|
1283
|
+
return paths;
|
|
1284
|
+
}
|
|
1285
|
+
export function extractCursorSessions(projectsDir, errors, projectRoots = null) {
|
|
1286
|
+
if (projectsDir === null || !fs.existsSync(projectsDir))
|
|
1287
|
+
return [];
|
|
1288
|
+
const roots = projectRoots || [];
|
|
1289
|
+
const slugToRoot = new Map();
|
|
1290
|
+
for (const root of roots)
|
|
1291
|
+
slugToRoot.set(cursorProjectDirSlug(root), root);
|
|
1292
|
+
const records = [];
|
|
1293
|
+
for (const p of iterCursorTranscriptPaths(projectsDir, roots)) {
|
|
1294
|
+
const fallbackTimestamp = isoFromMtime(p);
|
|
1295
|
+
const sessionId = path.basename(path.dirname(p)) || pathStem(p);
|
|
1296
|
+
const parents = pathParents(p);
|
|
1297
|
+
const projectDir = parents.length > 2 ? parents[2] : null;
|
|
1298
|
+
let projectPath = null;
|
|
1299
|
+
if (projectDir) {
|
|
1300
|
+
projectPath = slugToRoot.get(path.basename(projectDir)) || cursorProjectPathFromDir(projectDir);
|
|
1301
|
+
}
|
|
1302
|
+
let previousAssistant = "";
|
|
1303
|
+
let index = 0;
|
|
1304
|
+
for (const event of iterJsonl(p, errors)) {
|
|
1305
|
+
index += 1;
|
|
1306
|
+
const role = event.role;
|
|
1307
|
+
if (role !== "user" && role !== "assistant")
|
|
1308
|
+
continue;
|
|
1309
|
+
let contentIndex = 0;
|
|
1310
|
+
for (const item of cursorContentItems(event)) {
|
|
1311
|
+
contentIndex += 1;
|
|
1312
|
+
const tr = toolCallRecordFromItem({
|
|
1313
|
+
item,
|
|
1314
|
+
event,
|
|
1315
|
+
fallbackTimestamp,
|
|
1316
|
+
projectPath,
|
|
1317
|
+
runtime: "cursor",
|
|
1318
|
+
sourcePath: p,
|
|
1319
|
+
index: index * 1000 + contentIndex,
|
|
1320
|
+
sessionId,
|
|
1321
|
+
});
|
|
1322
|
+
if (tr !== null) {
|
|
1323
|
+
if (isPlainObject(tr.data) && isPlainObject(tr.data.arguments)) {
|
|
1324
|
+
tr.data.arguments = boundToolArguments(tr.data.arguments);
|
|
1325
|
+
}
|
|
1326
|
+
records.push(tr);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
const items = cursorContentItems(event);
|
|
1330
|
+
const content = textFromContent(items.length > 0 ? items : event.message);
|
|
1331
|
+
if (!content)
|
|
1332
|
+
continue;
|
|
1333
|
+
const timestamp = eventTimestamp(event, fallbackTimestamp);
|
|
1334
|
+
const data = { actor: role, content };
|
|
1335
|
+
if (role === "user") {
|
|
1336
|
+
if (previousAssistant)
|
|
1337
|
+
data.preceding_context = previousAssistant.slice(-2000);
|
|
1338
|
+
const sig = signalType(content);
|
|
1339
|
+
if (sig)
|
|
1340
|
+
data.signal_type = sig;
|
|
1341
|
+
}
|
|
1342
|
+
else {
|
|
1343
|
+
previousAssistant = content;
|
|
1344
|
+
}
|
|
1345
|
+
records.push(record({
|
|
1346
|
+
sourceKind: "conversation_turn",
|
|
1347
|
+
timestamp,
|
|
1348
|
+
projectPath,
|
|
1349
|
+
runtime: "cursor",
|
|
1350
|
+
sourceParts: [resolvePath(p), index, role, content.slice(0, 80)],
|
|
1351
|
+
sessionId,
|
|
1352
|
+
data,
|
|
1353
|
+
}));
|
|
1354
|
+
if (role === "user") {
|
|
1355
|
+
const sig = signalType(content);
|
|
1356
|
+
if (sig) {
|
|
1357
|
+
records.push(record({
|
|
1358
|
+
sourceKind: "history_prompt",
|
|
1359
|
+
timestamp,
|
|
1360
|
+
projectPath,
|
|
1361
|
+
runtime: "cursor",
|
|
1362
|
+
sourceParts: [resolvePath(p), index, "history", content.slice(0, 120)],
|
|
1363
|
+
sessionId,
|
|
1364
|
+
data: { prompt: content, signal_type: sig },
|
|
1365
|
+
}));
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
return records;
|
|
1371
|
+
}
|
|
1372
|
+
function cursorJsonlSessionIds(projectsDir, projectRoots) {
|
|
1373
|
+
if (projectsDir === null)
|
|
1374
|
+
return new Set();
|
|
1375
|
+
const ids = new Set();
|
|
1376
|
+
for (const p of iterCursorTranscriptPaths(projectsDir, projectRoots)) {
|
|
1377
|
+
ids.add(path.basename(path.dirname(p)) || pathStem(p));
|
|
1378
|
+
}
|
|
1379
|
+
return ids;
|
|
1380
|
+
}
|
|
1381
|
+
function cursorAgentMessageText(message) {
|
|
1382
|
+
const content = message.content;
|
|
1383
|
+
if (typeof content === "string")
|
|
1384
|
+
return content.trim();
|
|
1385
|
+
if (Array.isArray(content)) {
|
|
1386
|
+
const parts = [];
|
|
1387
|
+
for (const item of content) {
|
|
1388
|
+
if (!isPlainObject(item))
|
|
1389
|
+
continue;
|
|
1390
|
+
if (item.type === "text") {
|
|
1391
|
+
const text = item.text;
|
|
1392
|
+
if (typeof text === "string" && text.trim())
|
|
1393
|
+
parts.push(text.trim());
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
return parts.join("\n");
|
|
1397
|
+
}
|
|
1398
|
+
return textFromContent(content);
|
|
1399
|
+
}
|
|
1400
|
+
function cursorAgentToolItems(message) {
|
|
1401
|
+
const items = [];
|
|
1402
|
+
const content = message.content;
|
|
1403
|
+
if (!Array.isArray(content))
|
|
1404
|
+
return items;
|
|
1405
|
+
for (const item of content) {
|
|
1406
|
+
if (!isPlainObject(item))
|
|
1407
|
+
continue;
|
|
1408
|
+
if (item.type !== "tool-call")
|
|
1409
|
+
continue;
|
|
1410
|
+
const toolName = item.toolName || item.tool_name || item.name;
|
|
1411
|
+
if (typeof toolName !== "string" || !toolName)
|
|
1412
|
+
continue;
|
|
1413
|
+
const args = item.args || item.arguments || item.input || {};
|
|
1414
|
+
items.push({ type: "tool_use", name: toolName, input: isPlainObject(args) ? args : { value: args } });
|
|
1415
|
+
}
|
|
1416
|
+
return items;
|
|
1417
|
+
}
|
|
1418
|
+
function iterCursorAgentStorePaths(chatsDir, projectRoots, jsonlSessionIds) {
|
|
1419
|
+
if (!isDir(chatsDir))
|
|
1420
|
+
return [];
|
|
1421
|
+
const workspaceDirs = [];
|
|
1422
|
+
if (projectRoots.length > 0) {
|
|
1423
|
+
for (const root of projectRoots) {
|
|
1424
|
+
const workspace = path.join(chatsDir, cursorWorkspaceHash(root));
|
|
1425
|
+
if (isDir(workspace))
|
|
1426
|
+
workspaceDirs.push([workspace, resolvePath(root)]);
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
else {
|
|
1430
|
+
for (const name of fs.readdirSync(chatsDir).sort()) {
|
|
1431
|
+
const workspace = path.join(chatsDir, name);
|
|
1432
|
+
if (isDir(workspace))
|
|
1433
|
+
workspaceDirs.push([workspace, null]);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
const items = [];
|
|
1437
|
+
for (const [workspace, projectPath] of workspaceDirs) {
|
|
1438
|
+
for (const name of fs.readdirSync(workspace).sort()) {
|
|
1439
|
+
const sessionDir = path.join(workspace, name);
|
|
1440
|
+
if (!isDir(sessionDir))
|
|
1441
|
+
continue;
|
|
1442
|
+
const sessionId = name;
|
|
1443
|
+
if (jsonlSessionIds.has(sessionId))
|
|
1444
|
+
continue;
|
|
1445
|
+
const storeDb = path.join(sessionDir, "store.db");
|
|
1446
|
+
if (isFilePath(storeDb))
|
|
1447
|
+
items.push([storeDb, projectPath, sessionId]);
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
return items;
|
|
1451
|
+
}
|
|
1452
|
+
function cursorAgentBlobMessages(storeDb, errors) {
|
|
1453
|
+
const messages = [];
|
|
1454
|
+
let conn = null;
|
|
1455
|
+
let rows;
|
|
1456
|
+
try {
|
|
1457
|
+
conn = openSqlite(storeDb);
|
|
1458
|
+
}
|
|
1459
|
+
catch (exc) {
|
|
1460
|
+
errors.push(`${storeDb}: cursor-agent sqlite open failed: ${exc.name || "Error"}`);
|
|
1461
|
+
return messages;
|
|
1462
|
+
}
|
|
1463
|
+
try {
|
|
1464
|
+
rows = conn.prepare("SELECT id, data FROM blobs ORDER BY id").all();
|
|
1465
|
+
}
|
|
1466
|
+
catch (exc) {
|
|
1467
|
+
errors.push(`${storeDb}: cursor-agent sqlite read failed: ${exc.name || "Error"}`);
|
|
1468
|
+
conn.close();
|
|
1469
|
+
return messages;
|
|
1470
|
+
}
|
|
1471
|
+
conn.close();
|
|
1472
|
+
for (const row of rows) {
|
|
1473
|
+
const blobId = row.id;
|
|
1474
|
+
const payload = row.data;
|
|
1475
|
+
let raw;
|
|
1476
|
+
if (payload instanceof Uint8Array)
|
|
1477
|
+
raw = Buffer.from(payload);
|
|
1478
|
+
else if (typeof payload === "string")
|
|
1479
|
+
raw = Buffer.from(payload, "utf-8");
|
|
1480
|
+
else {
|
|
1481
|
+
errors.push(`${storeDb}: cursor-agent blob payload not bytes: ${blobId}`);
|
|
1482
|
+
continue;
|
|
1483
|
+
}
|
|
1484
|
+
let parsed;
|
|
1485
|
+
try {
|
|
1486
|
+
parsed = JSON.parse(raw.toString("utf-8"));
|
|
1487
|
+
}
|
|
1488
|
+
catch {
|
|
1489
|
+
errors.push(`${storeDb}: cursor-agent blob not json: ${blobId}`);
|
|
1490
|
+
continue;
|
|
1491
|
+
}
|
|
1492
|
+
if (isPlainObject(parsed))
|
|
1493
|
+
messages.push([String(blobId), parsed]);
|
|
1494
|
+
}
|
|
1495
|
+
return messages;
|
|
1496
|
+
}
|
|
1497
|
+
export function extractCursorAgentSessions(chatsDir, errors, projectRoots = null, cursorProjectsDir = null) {
|
|
1498
|
+
if (chatsDir === null || !fs.existsSync(chatsDir))
|
|
1499
|
+
return [];
|
|
1500
|
+
const roots = projectRoots || [];
|
|
1501
|
+
const jsonlSessionIds = cursorJsonlSessionIds(cursorProjectsDir, roots);
|
|
1502
|
+
const records = [];
|
|
1503
|
+
for (const [storeDb, projectPath, sessionId] of iterCursorAgentStorePaths(chatsDir, roots, jsonlSessionIds)) {
|
|
1504
|
+
const fallbackTimestamp = isoFromMtime(storeDb);
|
|
1505
|
+
let previousAssistant = "";
|
|
1506
|
+
let index = 0;
|
|
1507
|
+
for (const [blobId, message] of cursorAgentBlobMessages(storeDb, errors)) {
|
|
1508
|
+
index += 1;
|
|
1509
|
+
const role = message.role;
|
|
1510
|
+
let toolIndex = 0;
|
|
1511
|
+
for (const toolItem of cursorAgentToolItems(message)) {
|
|
1512
|
+
toolIndex += 1;
|
|
1513
|
+
const tr = toolCallRecordFromItem({
|
|
1514
|
+
item: toolItem,
|
|
1515
|
+
event: message,
|
|
1516
|
+
fallbackTimestamp,
|
|
1517
|
+
projectPath,
|
|
1518
|
+
runtime: "cursor-agent",
|
|
1519
|
+
sourcePath: storeDb,
|
|
1520
|
+
index: index * 1000 + toolIndex,
|
|
1521
|
+
sessionId,
|
|
1522
|
+
});
|
|
1523
|
+
if (tr !== null) {
|
|
1524
|
+
if (isPlainObject(tr.data) && isPlainObject(tr.data.arguments)) {
|
|
1525
|
+
tr.data.arguments = boundToolArguments(tr.data.arguments);
|
|
1526
|
+
}
|
|
1527
|
+
records.push(tr);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
if (role !== "user" && role !== "assistant")
|
|
1531
|
+
continue;
|
|
1532
|
+
const content = cursorAgentMessageText(message);
|
|
1533
|
+
if (!content)
|
|
1534
|
+
continue;
|
|
1535
|
+
const timestamp = eventTimestamp(message, fallbackTimestamp);
|
|
1536
|
+
const data = { actor: role, content };
|
|
1537
|
+
if (role === "user") {
|
|
1538
|
+
if (previousAssistant)
|
|
1539
|
+
data.preceding_context = previousAssistant.slice(-2000);
|
|
1540
|
+
const sig = signalType(content);
|
|
1541
|
+
if (sig)
|
|
1542
|
+
data.signal_type = sig;
|
|
1543
|
+
}
|
|
1544
|
+
else {
|
|
1545
|
+
previousAssistant = content;
|
|
1546
|
+
}
|
|
1547
|
+
records.push(record({
|
|
1548
|
+
sourceKind: "conversation_turn",
|
|
1549
|
+
timestamp,
|
|
1550
|
+
projectPath,
|
|
1551
|
+
runtime: "cursor-agent",
|
|
1552
|
+
sourceParts: [resolvePath(storeDb), blobId, role, content.slice(0, 80)],
|
|
1553
|
+
sessionId,
|
|
1554
|
+
data,
|
|
1555
|
+
}));
|
|
1556
|
+
if (role === "user") {
|
|
1557
|
+
const sig = signalType(content);
|
|
1558
|
+
if (sig) {
|
|
1559
|
+
records.push(record({
|
|
1560
|
+
sourceKind: "history_prompt",
|
|
1561
|
+
timestamp,
|
|
1562
|
+
projectPath,
|
|
1563
|
+
runtime: "cursor-agent",
|
|
1564
|
+
sourceParts: [resolvePath(storeDb), blobId, "history", content.slice(0, 120)],
|
|
1565
|
+
sessionId,
|
|
1566
|
+
data: { prompt: content, signal_type: sig },
|
|
1567
|
+
}));
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
return records;
|
|
1573
|
+
}
|
|
1574
|
+
// ── dispatch + envelope + main ──────────────────────────────────────
|
|
1575
|
+
import { pyJsonIndent } from "../core/pyjson.js";
|
|
1576
|
+
export class ExtractionNotImplementedError extends Error {
|
|
1577
|
+
}
|
|
1578
|
+
function extractRuntimeStore(runtime, storePath, errors, extractor) {
|
|
1579
|
+
const discovery = discoverRuntimeStore(runtime, storePath);
|
|
1580
|
+
if (discovery.status !== "available")
|
|
1581
|
+
return [[], discovery];
|
|
1582
|
+
const errorStart = errors.length;
|
|
1583
|
+
let records;
|
|
1584
|
+
try {
|
|
1585
|
+
records = extractor(storePath, errors);
|
|
1586
|
+
}
|
|
1587
|
+
catch (exc) {
|
|
1588
|
+
const cc = discovery.candidate_count ?? null;
|
|
1589
|
+
if (exc instanceof ExtractionNotImplementedError) {
|
|
1590
|
+
return [[], runtimeStatus(runtime, { status: "degraded", reason: "extractor_unimplemented", storePath, candidateCount: cc, recordCount: 0, errorCount: 0 })];
|
|
1591
|
+
}
|
|
1592
|
+
if (exc instanceof PermissionDeniedError) {
|
|
1593
|
+
return [[], runtimeStatus(runtime, { status: "degraded", reason: "store_locked", storePath, candidateCount: cc })];
|
|
1594
|
+
}
|
|
1595
|
+
return [[], runtimeStatus(runtime, { status: "degraded", reason: "store_unreadable", storePath, candidateCount: cc })];
|
|
1596
|
+
}
|
|
1597
|
+
const cc = discovery.candidate_count ?? null;
|
|
1598
|
+
const errorCount = errors.length - errorStart;
|
|
1599
|
+
if (errorCount) {
|
|
1600
|
+
return [records, runtimeStatus(runtime, { status: "degraded", reason: "schema_divergent", storePath, candidateCount: cc, recordCount: records.length, errorCount })];
|
|
1601
|
+
}
|
|
1602
|
+
if (records.length === 0) {
|
|
1603
|
+
return [records, runtimeStatus(runtime, {
|
|
1604
|
+
status: "sparse",
|
|
1605
|
+
reason: "no_matching_records",
|
|
1606
|
+
storePath,
|
|
1607
|
+
candidateCount: cc,
|
|
1608
|
+
recordCount: 0,
|
|
1609
|
+
remediationLabels: runtime === "github-copilot" ? [COPILOT_SPARSE_REMEDIATION] : null,
|
|
1610
|
+
})];
|
|
1611
|
+
}
|
|
1612
|
+
return [records, runtimeStatus(runtime, { status: "ok", reason: "records_extracted", storePath, candidateCount: cc, recordCount: records.length, errorCount: 0 })];
|
|
1613
|
+
}
|
|
1614
|
+
export function dedupeRecords(records) {
|
|
1615
|
+
const byId = new Map();
|
|
1616
|
+
for (const item of records)
|
|
1617
|
+
byId.set(item.source_id, item);
|
|
1618
|
+
const actorOrder = (item) => {
|
|
1619
|
+
const actor = isPlainObject(item.data) ? item.data.actor : null;
|
|
1620
|
+
return actor === "user" ? 0 : actor === "assistant" ? 1 : 2;
|
|
1621
|
+
};
|
|
1622
|
+
return Array.from(byId.values()).sort((a, b) => {
|
|
1623
|
+
const at = (a.timestamp ?? "");
|
|
1624
|
+
const bt = (b.timestamp ?? "");
|
|
1625
|
+
if (at !== bt)
|
|
1626
|
+
return at < bt ? -1 : 1;
|
|
1627
|
+
const ak = (a.source_kind ?? "");
|
|
1628
|
+
const bk = (b.source_kind ?? "");
|
|
1629
|
+
if (ak !== bk)
|
|
1630
|
+
return ak < bk ? -1 : 1;
|
|
1631
|
+
const ao = actorOrder(a);
|
|
1632
|
+
const bo = actorOrder(b);
|
|
1633
|
+
if (ao !== bo)
|
|
1634
|
+
return ao - bo;
|
|
1635
|
+
const ai = (a.source_id ?? "");
|
|
1636
|
+
const bi = (b.source_id ?? "");
|
|
1637
|
+
return ai < bi ? -1 : ai > bi ? 1 : 0;
|
|
1638
|
+
});
|
|
1639
|
+
}
|
|
1640
|
+
export function buildMetadata(records, errors, runtimeStatuses) {
|
|
1641
|
+
const counts = new Map();
|
|
1642
|
+
for (const item of records) {
|
|
1643
|
+
const sk = item.source_kind;
|
|
1644
|
+
if (FAMILIES.includes(sk))
|
|
1645
|
+
counts.set(sk, (counts.get(sk) ?? 0) + 1);
|
|
1646
|
+
}
|
|
1647
|
+
const families = {};
|
|
1648
|
+
for (const family of FAMILIES) {
|
|
1649
|
+
const count = counts.get(family) ?? 0;
|
|
1650
|
+
families[family] = { count, status: count ? "ok" : "missing" };
|
|
1651
|
+
if (count === 0)
|
|
1652
|
+
families[family].error = "no records extracted for this family";
|
|
1653
|
+
}
|
|
1654
|
+
const runtimes = Array.from(new Set(records.filter((i) => i.runtime).map((i) => String(i.runtime)))).sort();
|
|
1655
|
+
return {
|
|
1656
|
+
extracted_at: isoNow(),
|
|
1657
|
+
runtimes,
|
|
1658
|
+
adapter_version: ADAPTER_VERSION,
|
|
1659
|
+
families,
|
|
1660
|
+
runtime_statuses: runtimeStatuses,
|
|
1661
|
+
total_records: records.length,
|
|
1662
|
+
errors,
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
export function buildCorpus(opts) {
|
|
1666
|
+
const errors = [];
|
|
1667
|
+
const normalizedRoots = [];
|
|
1668
|
+
for (const root of opts.projectRoots) {
|
|
1669
|
+
if (fs.existsSync(root))
|
|
1670
|
+
normalizedRoots.push(resolvePath(root));
|
|
1671
|
+
else
|
|
1672
|
+
errors.push(`${root}: project root does not exist`);
|
|
1673
|
+
}
|
|
1674
|
+
const records = [];
|
|
1675
|
+
records.push(...extractInstructionDocuments(normalizedRoots, errors));
|
|
1676
|
+
records.push(...extractProjectConfigSignals(normalizedRoots, errors));
|
|
1677
|
+
const runtimeStatuses = [];
|
|
1678
|
+
const runtimes = [
|
|
1679
|
+
["codex", opts.codexSessionsDir, extractCodexSessions],
|
|
1680
|
+
["claude-code", opts.claudeProjectsDir, extractClaudeProjectSessions],
|
|
1681
|
+
["cursor", opts.cursorProjectsDir ?? null, (sp, err) => extractCursorSessions(sp, err, normalizedRoots)],
|
|
1682
|
+
[
|
|
1683
|
+
"cursor-agent",
|
|
1684
|
+
opts.cursorChatsDir ?? null,
|
|
1685
|
+
(sp, err) => extractCursorAgentSessions(sp, err, normalizedRoots, opts.cursorProjectsDir ?? null),
|
|
1686
|
+
],
|
|
1687
|
+
["opencode", opts.opencodeConversationsDir ?? null, extractOpencodeSessions],
|
|
1688
|
+
["github-copilot", opts.copilotConversationsDir ?? null, extractCopilotSessions],
|
|
1689
|
+
];
|
|
1690
|
+
for (const [runtime, storePath, extractor] of runtimes) {
|
|
1691
|
+
const [runtimeRecords, status] = extractRuntimeStore(runtime, storePath, errors, extractor);
|
|
1692
|
+
records.push(...runtimeRecords);
|
|
1693
|
+
runtimeStatuses.push(status);
|
|
1694
|
+
}
|
|
1695
|
+
const deduped = dedupeRecords(records);
|
|
1696
|
+
return { metadata: buildMetadata(deduped, errors, runtimeStatuses), records: deduped };
|
|
1697
|
+
}
|
|
1698
|
+
export function parseExtractArgs(argv, env = process.env, platform = process.platform) {
|
|
1699
|
+
const home = os.homedir();
|
|
1700
|
+
const args = {
|
|
1701
|
+
output: defaultOutputPath(env, platform),
|
|
1702
|
+
projectRoot: [],
|
|
1703
|
+
codexSessionsDir: path.join(home, ".codex", "sessions"),
|
|
1704
|
+
claudeProjectsDir: path.join(home, ".claude", "projects"),
|
|
1705
|
+
opencodeConversationsDir: null,
|
|
1706
|
+
copilotConversationsDir: null,
|
|
1707
|
+
cursorProjectsDir: null,
|
|
1708
|
+
cursorChatsDir: null,
|
|
1709
|
+
noCodex: false,
|
|
1710
|
+
noClaude: false,
|
|
1711
|
+
noOpencode: false,
|
|
1712
|
+
noCopilot: false,
|
|
1713
|
+
noCursor: false,
|
|
1714
|
+
};
|
|
1715
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1716
|
+
const a = argv[i];
|
|
1717
|
+
const val = (name) => {
|
|
1718
|
+
if (a === name)
|
|
1719
|
+
return argv[++i] ?? null;
|
|
1720
|
+
if (a.startsWith(name + "="))
|
|
1721
|
+
return a.slice(name.length + 1);
|
|
1722
|
+
return null;
|
|
1723
|
+
};
|
|
1724
|
+
let v;
|
|
1725
|
+
if ((v = val("--output")) !== null)
|
|
1726
|
+
args.output = v;
|
|
1727
|
+
else if ((v = val("--project-root")) !== null)
|
|
1728
|
+
args.projectRoot.push(v);
|
|
1729
|
+
else if ((v = val("--codex-sessions-dir")) !== null)
|
|
1730
|
+
args.codexSessionsDir = v;
|
|
1731
|
+
else if ((v = val("--claude-projects-dir")) !== null)
|
|
1732
|
+
args.claudeProjectsDir = v;
|
|
1733
|
+
else if ((v = val("--opencode-conversations-dir")) !== null)
|
|
1734
|
+
args.opencodeConversationsDir = v;
|
|
1735
|
+
else if ((v = val("--copilot-conversations-dir")) !== null)
|
|
1736
|
+
args.copilotConversationsDir = v;
|
|
1737
|
+
else if ((v = val("--cursor-projects-dir")) !== null)
|
|
1738
|
+
args.cursorProjectsDir = v;
|
|
1739
|
+
else if ((v = val("--cursor-chats-dir")) !== null)
|
|
1740
|
+
args.cursorChatsDir = v;
|
|
1741
|
+
else if (a === "--no-codex")
|
|
1742
|
+
args.noCodex = true;
|
|
1743
|
+
else if (a === "--no-claude")
|
|
1744
|
+
args.noClaude = true;
|
|
1745
|
+
else if (a === "--no-opencode")
|
|
1746
|
+
args.noOpencode = true;
|
|
1747
|
+
else if (a === "--no-copilot")
|
|
1748
|
+
args.noCopilot = true;
|
|
1749
|
+
else if (a === "--no-cursor")
|
|
1750
|
+
args.noCursor = true;
|
|
1751
|
+
else
|
|
1752
|
+
throw new Error(`extract-corpus: unrecognized argument: ${a}`);
|
|
1753
|
+
}
|
|
1754
|
+
return args;
|
|
1755
|
+
}
|
|
1756
|
+
/** Engine entry point mirroring scripts/extract_corpus.py main(). */
|
|
1757
|
+
export function extractCorpusMain(argv, io = {}) {
|
|
1758
|
+
const out = io.out ?? ((t) => process.stdout.write(t + "\n"));
|
|
1759
|
+
const err = io.err ?? ((t) => process.stderr.write(t + "\n"));
|
|
1760
|
+
const env = io.env ?? process.env;
|
|
1761
|
+
const platform = io.platform ?? process.platform;
|
|
1762
|
+
const cwd = io.cwd ?? process.cwd();
|
|
1763
|
+
let args;
|
|
1764
|
+
try {
|
|
1765
|
+
args = parseExtractArgs(argv, env, platform);
|
|
1766
|
+
}
|
|
1767
|
+
catch (exc) {
|
|
1768
|
+
err(exc.message);
|
|
1769
|
+
return 2;
|
|
1770
|
+
}
|
|
1771
|
+
const projectRoots = args.projectRoot.length > 0 ? args.projectRoot : [cwd];
|
|
1772
|
+
const skipCursor = args.noCursor;
|
|
1773
|
+
const corpus = buildCorpus({
|
|
1774
|
+
projectRoots,
|
|
1775
|
+
codexSessionsDir: args.noCodex ? null : args.codexSessionsDir,
|
|
1776
|
+
claudeProjectsDir: args.noClaude ? null : args.claudeProjectsDir,
|
|
1777
|
+
opencodeConversationsDir: args.noOpencode ? null : args.opencodeConversationsDir || resolveOpencodeDbPath(env),
|
|
1778
|
+
copilotConversationsDir: args.noCopilot ? null : args.copilotConversationsDir || resolveCopilotStorePath(env),
|
|
1779
|
+
cursorProjectsDir: skipCursor ? null : args.cursorProjectsDir || resolveCursorProjectsPath(env),
|
|
1780
|
+
cursorChatsDir: skipCursor ? null : args.cursorChatsDir || resolveCursorChatsPath(env),
|
|
1781
|
+
});
|
|
1782
|
+
fs.mkdirSync(path.dirname(args.output), { recursive: true });
|
|
1783
|
+
fs.writeFileSync(args.output, pyJsonIndent(corpus) + "\n", "utf-8");
|
|
1784
|
+
const total = corpus.metadata.total_records;
|
|
1785
|
+
const familyBits = Object.entries(corpus.metadata.families)
|
|
1786
|
+
.map(([name, summary]) => `${name}=${summary.count}`)
|
|
1787
|
+
.join(", ");
|
|
1788
|
+
out(`wrote corpus: ${args.output} (${total} records; ${familyBits})`);
|
|
1789
|
+
return 0;
|
|
1790
|
+
}
|
|
1791
|
+
//# sourceMappingURL=extractCorpus.js.map
|