codiedev 0.9.60 → 0.9.61
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/captureSignals.d.ts +4 -0
- package/dist/captureSignals.js +18 -0
- package/dist/cli.js +3 -1
- package/dist/commands/doctor.js +11 -0
- package/dist/connect.js +9 -1
- package/dist/copilotHooks.d.ts +85 -0
- package/dist/copilotHooks.js +458 -0
- package/dist/copilotInstallation.d.ts +10 -0
- package/dist/copilotInstallation.js +65 -0
- package/dist/cursorHooks.d.ts +5 -1
- package/dist/cursorHooks.js +50 -7
- package/dist/cursorLifecycle.d.ts +2 -0
- package/dist/cursorLifecycle.js +115 -0
- package/dist/hook.js +133 -36
- package/dist/launchContext.d.ts +20 -6
- package/dist/launchContext.js +65 -10
- package/dist/limitEvent.d.ts +3 -0
- package/dist/limitEvent.js +42 -0
- package/dist/limitEventReporter.d.ts +1 -1
- package/dist/limitEventReporter.js +1 -1
- package/dist/nativeSteering.d.ts +5 -0
- package/dist/nativeSteering.js +27 -0
- package/dist/teamSkillContext.d.ts +9 -0
- package/dist/teamSkillContext.js +11 -0
- package/dist/transcriptModels.d.ts +2 -0
- package/dist/transcriptModels.js +8 -2
- package/dist/utils.d.ts +19 -3
- package/dist/utils.js +157 -42
- package/package.json +8 -5
package/dist/limitEvent.js
CHANGED
|
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.classifyLimitKind = classifyLimitKind;
|
|
4
4
|
exports.parseLimitEventFromTranscript = parseLimitEventFromTranscript;
|
|
5
5
|
exports.parseCodexLimitEventFromTranscript = parseCodexLimitEventFromTranscript;
|
|
6
|
+
exports.parseNativeLimitError = parseNativeLimitError;
|
|
7
|
+
exports.parseNativeLimitEventFromTranscript = parseNativeLimitEventFromTranscript;
|
|
6
8
|
exports.tailForLimitEvent = tailForLimitEvent;
|
|
7
9
|
function classifyLimitKind(text) {
|
|
8
10
|
const t = text.toLowerCase();
|
|
@@ -129,6 +131,46 @@ function parseCodexLimitEventFromTranscript(transcript) {
|
|
|
129
131
|
}
|
|
130
132
|
return found;
|
|
131
133
|
}
|
|
134
|
+
/** Only explicit provider errors qualify; never scan user/assistant prose. */
|
|
135
|
+
function parseNativeLimitError(input, now = Date.now()) {
|
|
136
|
+
const context = input.errorContext ?? input.error_context;
|
|
137
|
+
if (context !== undefined && context !== "model_call")
|
|
138
|
+
return null;
|
|
139
|
+
const error = input.error;
|
|
140
|
+
const fields = error && typeof error === "object" ? error : {};
|
|
141
|
+
const code = String(fields.code ?? fields.name ?? input.error_code ?? "");
|
|
142
|
+
const text = typeof error === "string" ? error
|
|
143
|
+
: typeof fields.message === "string" ? fields.message
|
|
144
|
+
: typeof input.error_message === "string" ? input.error_message : "";
|
|
145
|
+
if (!/^(?:429|rate[_-]?limit(?:ed|_exceeded|_error)?|RateLimitError|quota_exceeded)$/i.test(code)
|
|
146
|
+
&& !/\b(?:rate limit (?:exceeded|reached)|too many requests|usage limit (?:exceeded|reached)|premium requests? (?:exhausted|limit reached))\b/i.test(text))
|
|
147
|
+
return null;
|
|
148
|
+
const rawAt = input.timestamp;
|
|
149
|
+
const parsedAt = typeof rawAt === "number" ? rawAt : typeof rawAt === "string" ? Date.parse(rawAt) : NaN;
|
|
150
|
+
return { kind: classifyLimitKind(text), at: Number.isFinite(parsedAt) && parsedAt > 0 ? parsedAt : now,
|
|
151
|
+
resetText: parseResetText(text), reason: code || "rate_limit" };
|
|
152
|
+
}
|
|
153
|
+
function parseNativeLimitEventFromTranscript(transcript) {
|
|
154
|
+
let found = null;
|
|
155
|
+
for (const line of transcript.split("\n")) {
|
|
156
|
+
try {
|
|
157
|
+
const entry = JSON.parse(line);
|
|
158
|
+
if (entry.type === "agent.limit" && Number.isFinite(entry.at) && entry.at > 0
|
|
159
|
+
&& ["session", "user_spend", "org_spend", "other"].includes(entry.kind)) {
|
|
160
|
+
found = { kind: entry.kind, at: entry.at, resetText: typeof entry.resetText === "string" ? entry.resetText : null,
|
|
161
|
+
...(typeof entry.reason === "string" ? { reason: entry.reason } : {}) };
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (!entry.timestamp)
|
|
165
|
+
continue; // no stable timestamp means snapshots cannot deduplicate
|
|
166
|
+
if (entry.type === "agent.error" || entry.type === "session.error") {
|
|
167
|
+
found = parseNativeLimitError({ ...(entry.data ?? entry), timestamp: entry.timestamp }) ?? found;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch { /* tolerant of incomplete transcript tails */ }
|
|
171
|
+
}
|
|
172
|
+
return found;
|
|
173
|
+
}
|
|
132
174
|
// Tail of the raw transcript to ship with the limit event so the server can
|
|
133
175
|
// summarize "what they were working on" immediately — without waiting for the
|
|
134
176
|
// SessionEnd capture (which can lag hours, or drop entirely on Windows).
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type LimitEvent } from "./limitEvent";
|
|
2
|
-
export type LimitEventSource = "claude-code" | "codex";
|
|
2
|
+
export type LimitEventSource = "claude-code" | "codex" | "cursor" | "copilot";
|
|
3
3
|
export type LimitEventPayload = LimitEvent & {
|
|
4
4
|
source: LimitEventSource;
|
|
5
5
|
sessionId?: string;
|
|
@@ -76,7 +76,7 @@ function postLimitEvent(config, payload, timeoutMs = 5_000) {
|
|
|
76
76
|
async function reportTranscriptLimitEvent(args) {
|
|
77
77
|
const parse = args.source === "codex"
|
|
78
78
|
? limitEvent_1.parseCodexLimitEventFromTranscript
|
|
79
|
-
: limitEvent_1.parseLimitEventFromTranscript;
|
|
79
|
+
: args.source === "cursor" || args.source === "copilot" ? limitEvent_1.parseNativeLimitEventFromTranscript : limitEvent_1.parseLimitEventFromTranscript;
|
|
80
80
|
const parsed = parse(args.transcript);
|
|
81
81
|
if (!parsed && !args.emitMinimalOnMiss)
|
|
82
82
|
return false;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseNativeSteering = parseNativeSteering;
|
|
4
|
+
/** Native records only: quoted denial/interrupt text in conversation is not evidence. */
|
|
5
|
+
function parseNativeSteering(content) {
|
|
6
|
+
const interrupts = new Set();
|
|
7
|
+
const abortedTurns = new Set();
|
|
8
|
+
const rejections = new Set();
|
|
9
|
+
for (const [index, line] of content.split("\n").entries()) {
|
|
10
|
+
try {
|
|
11
|
+
const event = JSON.parse(line);
|
|
12
|
+
const id = String(event.id ?? event.uuid ?? event.toolCallId ?? index);
|
|
13
|
+
if (event.type === "agent.interruption")
|
|
14
|
+
interrupts.add(id);
|
|
15
|
+
if (event.type === "turn_ended" && event.status === "aborted")
|
|
16
|
+
abortedTurns.add(id);
|
|
17
|
+
if (event.type === "agent.rejection")
|
|
18
|
+
rejections.add(id);
|
|
19
|
+
// Cursor tool failures distinguish a user-denied permission from an
|
|
20
|
+
// ordinary tool failure. Don't guess from free-form error messages.
|
|
21
|
+
if (event.type === "tool_result" && event.error?.code === "user_denied")
|
|
22
|
+
rejections.add(id);
|
|
23
|
+
}
|
|
24
|
+
catch { /* incomplete transcript records are ignored */ }
|
|
25
|
+
}
|
|
26
|
+
return { interrupts: Math.max(interrupts.size, abortedTurns.size), rejections: rejections.size };
|
|
27
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Session-start context uses each client's supported output contract. */
|
|
2
|
+
export declare const TEAM_SKILL_CONTEXT = "This machine is connected to Codiedev, the team's artifact and skill library.\nBefore a substantial coding task, run codiedev search \"<brief task description>\" and reuse a relevant team skill by running codiedev pull <exact-key> and following its instructions. If no skill fits, continue the task.\nUse codiedev push <file.md> to share an artifact when asked, codiedev ask \"<question>\" for team knowledge, and codiedev inbox to check messages. Use codiedev --help for other commands. If codiedev is unavailable, use npx codiedev@latest.";
|
|
3
|
+
export declare function teamSkillSessionOutput(source: "cursor" | "copilot", context?: string): {
|
|
4
|
+
additional_context: string;
|
|
5
|
+
additionalContext?: undefined;
|
|
6
|
+
} | {
|
|
7
|
+
additionalContext: string;
|
|
8
|
+
additional_context?: undefined;
|
|
9
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TEAM_SKILL_CONTEXT = void 0;
|
|
4
|
+
exports.teamSkillSessionOutput = teamSkillSessionOutput;
|
|
5
|
+
/** Session-start context uses each client's supported output contract. */
|
|
6
|
+
exports.TEAM_SKILL_CONTEXT = `This machine is connected to Codiedev, the team's artifact and skill library.
|
|
7
|
+
Before a substantial coding task, run codiedev search "<brief task description>" and reuse a relevant team skill by running codiedev pull <exact-key> and following its instructions. If no skill fits, continue the task.
|
|
8
|
+
Use codiedev push <file.md> to share an artifact when asked, codiedev ask "<question>" for team knowledge, and codiedev inbox to check messages. Use codiedev --help for other commands. If codiedev is unavailable, use npx codiedev@latest.`;
|
|
9
|
+
function teamSkillSessionOutput(source, context = exports.TEAM_SKILL_CONTEXT) {
|
|
10
|
+
return source === "cursor" ? { additional_context: context } : { additionalContext: context };
|
|
11
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/** Compact per-model usage, derived from captured transcript records. */
|
|
2
2
|
export type TranscriptModelUsage = {
|
|
3
3
|
model: string;
|
|
4
|
+
/** False for Copilot model/request hints before token totals arrive. */
|
|
5
|
+
tokensAvailable?: boolean;
|
|
4
6
|
/** Assistant message IDs; nonzero cumulative usage events for Codex. */
|
|
5
7
|
requests: number;
|
|
6
8
|
inputTokens: number;
|
package/dist/transcriptModels.js
CHANGED
|
@@ -44,6 +44,7 @@ function* transcriptRecords(content, source) {
|
|
|
44
44
|
function parseTranscriptModels(content, source) {
|
|
45
45
|
const buckets = new Map();
|
|
46
46
|
const messages = new Map();
|
|
47
|
+
const tokenEvidence = new Set();
|
|
47
48
|
let model = "Unattributed";
|
|
48
49
|
let previous = {
|
|
49
50
|
inputTokens: 0,
|
|
@@ -143,9 +144,12 @@ function parseTranscriptModels(content, source) {
|
|
|
143
144
|
model =
|
|
144
145
|
typeof message.model === "string" ? message.model : "Unattributed";
|
|
145
146
|
const raw = message.usage;
|
|
147
|
+
// Copilot's shutdown token totals carry `requests: 0`: its calls are
|
|
148
|
+
// already counted from the assistant records.
|
|
149
|
+
const requests = event.requests;
|
|
146
150
|
usage = {
|
|
147
151
|
...empty(model),
|
|
148
|
-
requests: 1,
|
|
152
|
+
requests: typeof requests === "number" && Number.isFinite(requests) ? Math.max(0, requests) : 1,
|
|
149
153
|
inputTokens: positive(raw.input_tokens),
|
|
150
154
|
outputTokens: positive(raw.output_tokens),
|
|
151
155
|
cacheReadTokens: positive(raw.cache_read_input_tokens),
|
|
@@ -153,6 +157,8 @@ function parseTranscriptModels(content, source) {
|
|
|
153
157
|
};
|
|
154
158
|
messageId = message.id ?? event.uuid ?? messageId;
|
|
155
159
|
}
|
|
160
|
+
if (source === "copilot" && event.type === "copilot.usage")
|
|
161
|
+
tokenEvidence.add(usage.model);
|
|
156
162
|
// Client placeholders are not model executions.
|
|
157
163
|
if (!usage.model.trim() || usage.model.startsWith("<"))
|
|
158
164
|
continue;
|
|
@@ -197,7 +203,7 @@ function parseTranscriptModels(content, source) {
|
|
|
197
203
|
delete buckets.get(key).costUsd;
|
|
198
204
|
for (const key of incompleteReasoning)
|
|
199
205
|
delete buckets.get(key).reasoningTokens;
|
|
200
|
-
return [...buckets.values()];
|
|
206
|
+
return [...buckets.values()].map(row => source === "copilot" && !tokenEvidence.has(row.model) ? { ...row, tokensAvailable: false } : row);
|
|
201
207
|
}
|
|
202
208
|
function totalModelTokens(row) {
|
|
203
209
|
return (row.inputTokens +
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import type { CaptureSignals } from "./captureSignals";
|
|
3
|
-
import type
|
|
3
|
+
import { type TranscriptModelUsage } from "./transcriptModels";
|
|
4
4
|
import { spawn as nodeSpawn } from "child_process";
|
|
5
5
|
import type { CaptureLogEntry } from "./captureLog";
|
|
6
6
|
import { codiedevStateDir } from "./configDir";
|
|
@@ -89,6 +89,7 @@ export interface CapturePayloadInput {
|
|
|
89
89
|
final?: boolean;
|
|
90
90
|
costUsd?: number;
|
|
91
91
|
}
|
|
92
|
+
export declare function stripRepoUrlCredentials(url: string): string;
|
|
92
93
|
export declare function buildCapturePayload(input: CapturePayloadInput): Record<string, unknown>;
|
|
93
94
|
export declare function hashToken(token: string): string;
|
|
94
95
|
export declare function extractHookNodePath(cmd: string | undefined): string | undefined;
|
|
@@ -96,6 +97,12 @@ export declare function resetStableNodeCacheForTests(): void;
|
|
|
96
97
|
export declare function claudeCodeInstalled(): boolean;
|
|
97
98
|
export declare function codexInstalled(): boolean;
|
|
98
99
|
export declare function cursorInstalled(): boolean;
|
|
100
|
+
/**
|
|
101
|
+
* GitHub Copilot: `$COPILOT_HOME` / ~/.copilot (either surface has run once),
|
|
102
|
+
* the `copilot` CLI on PATH, or VS Code's Copilot Chat extension. VS Code's
|
|
103
|
+
* agent and the CLI read the same user hooks, so one install covers both.
|
|
104
|
+
*/
|
|
105
|
+
export declare function copilotInstalled(): boolean;
|
|
99
106
|
export declare function openCodeInstalled(): boolean;
|
|
100
107
|
/** Install a dormant global OpenCode adapter. Installing it unconditionally
|
|
101
108
|
* keeps `codiedev connect` as the only setup step even when Codie/OpenCode is
|
|
@@ -116,6 +123,8 @@ export declare function ensureAllCodiedevHooksInstalled(): void;
|
|
|
116
123
|
export declare function ensureSessionStartHookInstalled(): void;
|
|
117
124
|
export declare function installClaudeCodeInstructions(): void;
|
|
118
125
|
export declare function installCodexInstructions(): void;
|
|
126
|
+
export declare function copilotInstructionsPath(): string;
|
|
127
|
+
export declare function installCopilotInstructions(): void;
|
|
119
128
|
export declare function installCursorInstructions(): void;
|
|
120
129
|
/**
|
|
121
130
|
* Install the Codiedev MCP server into Claude Code's user-scope config.
|
|
@@ -173,6 +182,13 @@ export declare function installCodexMcp(): boolean;
|
|
|
173
182
|
export declare function installCodexHook(): void;
|
|
174
183
|
export declare function ensureCodexLimitHookInstalled(): void;
|
|
175
184
|
export declare function installCursorHook(): void;
|
|
185
|
+
export declare function copilotHooksPath(): string;
|
|
186
|
+
export declare function installCopilotHook(agentSecurityEnabled?: boolean): void;
|
|
187
|
+
/** Self-heal the Copilot hook file after upgrades or drift (like Cursor's). */
|
|
188
|
+
export declare function ensureCopilotCodiedevHooksInstalled(): void;
|
|
189
|
+
/** Repair Cursor's complete flat hook set after upgrades or configuration
|
|
190
|
+
* drift. This is deliberately separate from Claude Code's nested-hook healer:
|
|
191
|
+
* the schemas and event names differ, and mixing them risks corrupting both. */
|
|
176
192
|
export declare function ensureCursorCodiedevHooksInstalled(): void;
|
|
177
193
|
export interface ParsedStats {
|
|
178
194
|
messageCount: number;
|
|
@@ -184,7 +200,7 @@ export interface SteeringStats {
|
|
|
184
200
|
}
|
|
185
201
|
export declare function parseClaudeSteering(content: string): SteeringStats;
|
|
186
202
|
export declare function parseCodexSteering(content: string): SteeringStats;
|
|
187
|
-
export declare function parseSteering(content: string, mode: "claude-code" | "codex" | "cursor" | "opencode"): SteeringStats;
|
|
203
|
+
export declare function parseSteering(content: string, mode: "claude-code" | "codex" | "cursor" | "opencode" | "copilot"): SteeringStats;
|
|
188
204
|
export interface UsageStats {
|
|
189
205
|
inputTokens: number;
|
|
190
206
|
outputTokens: number;
|
|
@@ -196,7 +212,7 @@ export interface UsageStats {
|
|
|
196
212
|
/** Engine-reported spend (opencode SDK `info.cost`). Desktop mode only. */
|
|
197
213
|
costUsd?: number;
|
|
198
214
|
}
|
|
199
|
-
export declare function parseUsage(content: string, mode: "claude-code" | "codex" | "cursor" | "opencode" | "desktop"): UsageStats;
|
|
215
|
+
export declare function parseUsage(content: string, mode: "claude-code" | "codex" | "cursor" | "opencode" | "desktop" | "copilot"): UsageStats;
|
|
200
216
|
export declare function parseSessionStart(content: string): number | null;
|
|
201
217
|
export declare function parseClaudeCodeStats(content: string): ParsedStats;
|
|
202
218
|
/** Convert OpenCode's SDK message array (`{info, parts}[]`) into the same
|
package/dist/utils.js
CHANGED
|
@@ -53,6 +53,7 @@ exports.parseAuthoredBranches = parseAuthoredBranches;
|
|
|
53
53
|
exports.parseAuthoredShas = parseAuthoredShas;
|
|
54
54
|
exports.getGitBranch = getGitBranch;
|
|
55
55
|
exports.getGitHeadSha = getGitHeadSha;
|
|
56
|
+
exports.stripRepoUrlCredentials = stripRepoUrlCredentials;
|
|
56
57
|
exports.buildCapturePayload = buildCapturePayload;
|
|
57
58
|
exports.hashToken = hashToken;
|
|
58
59
|
exports.extractHookNodePath = extractHookNodePath;
|
|
@@ -60,6 +61,7 @@ exports.resetStableNodeCacheForTests = resetStableNodeCacheForTests;
|
|
|
60
61
|
exports.claudeCodeInstalled = claudeCodeInstalled;
|
|
61
62
|
exports.codexInstalled = codexInstalled;
|
|
62
63
|
exports.cursorInstalled = cursorInstalled;
|
|
64
|
+
exports.copilotInstalled = copilotInstalled;
|
|
63
65
|
exports.openCodeInstalled = openCodeInstalled;
|
|
64
66
|
exports.installOpenCodeCapturePlugin = installOpenCodeCapturePlugin;
|
|
65
67
|
exports.vscodeCopilotInstalled = vscodeCopilotInstalled;
|
|
@@ -72,6 +74,8 @@ exports.ensureAllCodiedevHooksInstalled = ensureAllCodiedevHooksInstalled;
|
|
|
72
74
|
exports.ensureSessionStartHookInstalled = ensureSessionStartHookInstalled;
|
|
73
75
|
exports.installClaudeCodeInstructions = installClaudeCodeInstructions;
|
|
74
76
|
exports.installCodexInstructions = installCodexInstructions;
|
|
77
|
+
exports.copilotInstructionsPath = copilotInstructionsPath;
|
|
78
|
+
exports.installCopilotInstructions = installCopilotInstructions;
|
|
75
79
|
exports.installCursorInstructions = installCursorInstructions;
|
|
76
80
|
exports.installClaudeCodeMcp = installClaudeCodeMcp;
|
|
77
81
|
exports.installCursorMcp = installCursorMcp;
|
|
@@ -84,6 +88,9 @@ exports.installCodexMcp = installCodexMcp;
|
|
|
84
88
|
exports.installCodexHook = installCodexHook;
|
|
85
89
|
exports.ensureCodexLimitHookInstalled = ensureCodexLimitHookInstalled;
|
|
86
90
|
exports.installCursorHook = installCursorHook;
|
|
91
|
+
exports.copilotHooksPath = copilotHooksPath;
|
|
92
|
+
exports.installCopilotHook = installCopilotHook;
|
|
93
|
+
exports.ensureCopilotCodiedevHooksInstalled = ensureCopilotCodiedevHooksInstalled;
|
|
87
94
|
exports.ensureCursorCodiedevHooksInstalled = ensureCursorCodiedevHooksInstalled;
|
|
88
95
|
exports.parseClaudeSteering = parseClaudeSteering;
|
|
89
96
|
exports.parseCodexSteering = parseCodexSteering;
|
|
@@ -114,6 +121,7 @@ exports.runDeferredUpload = runDeferredUpload;
|
|
|
114
121
|
exports.transcriptReadSkipReason = transcriptReadSkipReason;
|
|
115
122
|
exports.shouldReportCaptureSkip = shouldReportCaptureSkip;
|
|
116
123
|
const fs = __importStar(require("fs"));
|
|
124
|
+
const transcriptModels_1 = require("./transcriptModels");
|
|
117
125
|
const path = __importStar(require("path"));
|
|
118
126
|
const os = __importStar(require("os"));
|
|
119
127
|
const child_process_1 = require("child_process");
|
|
@@ -122,6 +130,9 @@ const captureLog_1 = require("./captureLog");
|
|
|
122
130
|
const configDir_1 = require("./configDir");
|
|
123
131
|
Object.defineProperty(exports, "codiedevStateDir", { enumerable: true, get: function () { return configDir_1.codiedevStateDir; } });
|
|
124
132
|
const codexHookTrust_1 = require("./codexHookTrust");
|
|
133
|
+
const teamSkillContext_1 = require("./teamSkillContext");
|
|
134
|
+
const nativeSteering_1 = require("./nativeSteering");
|
|
135
|
+
const copilotHooks_1 = require("./copilotHooks");
|
|
125
136
|
const CONFIG_DIR = (0, configDir_1.codiedevStateDir)();
|
|
126
137
|
exports.CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
127
138
|
function readConfig() {
|
|
@@ -824,10 +835,13 @@ function getGitHeadSha(cwd) {
|
|
|
824
835
|
return null;
|
|
825
836
|
}
|
|
826
837
|
}
|
|
838
|
+
function stripRepoUrlCredentials(url) {
|
|
839
|
+
return url.replace(/^(\s*[a-zA-Z][a-zA-Z0-9+.-]*:\/\/)[^/?#]*@/, "$1");
|
|
840
|
+
}
|
|
827
841
|
function buildCapturePayload(input) {
|
|
828
842
|
return {
|
|
829
843
|
sessionId: input.sessionId,
|
|
830
|
-
repoUrl: input.remoteUrl ?? "",
|
|
844
|
+
repoUrl: stripRepoUrlCredentials(input.remoteUrl ?? ""),
|
|
831
845
|
repoId: input.matchedRepoId,
|
|
832
846
|
companyId: input.companyId,
|
|
833
847
|
branch: input.branch ?? "",
|
|
@@ -836,7 +850,7 @@ function buildCapturePayload(input) {
|
|
|
836
850
|
// see no change; newer servers index the session under every key.
|
|
837
851
|
branches: input.branches && input.branches.length ? input.branches : undefined,
|
|
838
852
|
headShas: input.headShas && input.headShas.length ? input.headShas : undefined,
|
|
839
|
-
gitRefs: input.gitRefs && input.gitRefs.length ? input.gitRefs : undefined,
|
|
853
|
+
gitRefs: input.gitRefs && input.gitRefs.length ? input.gitRefs.map(ref => ({ ...ref, repoUrl: stripRepoUrlCredentials(ref.repoUrl) })) : undefined,
|
|
840
854
|
pullRequestUrls: input.pullRequestUrls && input.pullRequestUrls.length
|
|
841
855
|
? input.pullRequestUrls
|
|
842
856
|
: undefined,
|
|
@@ -1044,6 +1058,24 @@ function codexInstalled() {
|
|
|
1044
1058
|
function cursorInstalled() {
|
|
1045
1059
|
return fs.existsSync(CURSOR_DIR);
|
|
1046
1060
|
}
|
|
1061
|
+
/**
|
|
1062
|
+
* GitHub Copilot: `$COPILOT_HOME` / ~/.copilot (either surface has run once),
|
|
1063
|
+
* the `copilot` CLI on PATH, or VS Code's Copilot Chat extension. VS Code's
|
|
1064
|
+
* agent and the CLI read the same user hooks, so one install covers both.
|
|
1065
|
+
*/
|
|
1066
|
+
function copilotInstalled() {
|
|
1067
|
+
if (fs.existsSync((0, copilotHooks_1.copilotHome)()))
|
|
1068
|
+
return true;
|
|
1069
|
+
if (vscodeCopilotInstalled())
|
|
1070
|
+
return true;
|
|
1071
|
+
try {
|
|
1072
|
+
(0, child_process_1.execSync)(process.platform === "win32" ? "where copilot" : "command -v copilot", { stdio: ["pipe", "pipe", "pipe"] });
|
|
1073
|
+
return true;
|
|
1074
|
+
}
|
|
1075
|
+
catch {
|
|
1076
|
+
return false;
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1047
1079
|
function openCodeInstalled() {
|
|
1048
1080
|
if (fs.existsSync(OPENCODE_DIR))
|
|
1049
1081
|
return true;
|
|
@@ -1475,13 +1507,13 @@ Tell the user the portal URL when the submit returns.
|
|
|
1475
1507
|
* (no per-repo CLAUDE.md needed). Idempotent — replaces the existing block
|
|
1476
1508
|
* between its begin/end markers on re-run.
|
|
1477
1509
|
*/
|
|
1478
|
-
function writeInstructionsBlock(targetPath) {
|
|
1510
|
+
function writeInstructionsBlock(targetPath, body = CODIEDEV_INSTRUCTIONS_BODY) {
|
|
1479
1511
|
const dir = path.dirname(targetPath);
|
|
1480
1512
|
if (!fs.existsSync(dir)) {
|
|
1481
1513
|
fs.mkdirSync(dir, { recursive: true });
|
|
1482
1514
|
}
|
|
1483
1515
|
const block = `${CODIEDEV_INSTRUCTIONS_BEGIN}\n` +
|
|
1484
|
-
|
|
1516
|
+
body +
|
|
1485
1517
|
`\n${CODIEDEV_INSTRUCTIONS_END}\n`;
|
|
1486
1518
|
let existing = "";
|
|
1487
1519
|
if (fs.existsSync(targetPath)) {
|
|
@@ -1512,6 +1544,12 @@ function installClaudeCodeInstructions() {
|
|
|
1512
1544
|
function installCodexInstructions() {
|
|
1513
1545
|
writeInstructionsBlock(CODEX_USER_INSTRUCTIONS_PATH);
|
|
1514
1546
|
}
|
|
1547
|
+
function copilotInstructionsPath() {
|
|
1548
|
+
return path.join((0, copilotHooks_1.copilotHome)(), "copilot-instructions.md");
|
|
1549
|
+
}
|
|
1550
|
+
function installCopilotInstructions() {
|
|
1551
|
+
writeInstructionsBlock(copilotInstructionsPath(), `${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${CODIEDEV_INSTRUCTIONS_BODY}`);
|
|
1552
|
+
}
|
|
1515
1553
|
// Cursor uses .mdc rule files with YAML frontmatter (description, alwaysApply,
|
|
1516
1554
|
// globs). `.cursorrules` is deprecated and ignored by Agent mode as of 2026.
|
|
1517
1555
|
const CURSOR_INSTRUCTIONS_FRONTMATTER = `---
|
|
@@ -1527,7 +1565,7 @@ function installCursorInstructions() {
|
|
|
1527
1565
|
const block = CURSOR_INSTRUCTIONS_FRONTMATTER +
|
|
1528
1566
|
CODIEDEV_INSTRUCTIONS_BEGIN +
|
|
1529
1567
|
"\n" +
|
|
1530
|
-
CODIEDEV_INSTRUCTIONS_BODY +
|
|
1568
|
+
`${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${CODIEDEV_INSTRUCTIONS_BODY}` +
|
|
1531
1569
|
"\n" +
|
|
1532
1570
|
CODIEDEV_INSTRUCTIONS_END +
|
|
1533
1571
|
"\n";
|
|
@@ -1545,7 +1583,7 @@ function installCursorInstructions() {
|
|
|
1545
1583
|
existing.slice(0, beginIdx) +
|
|
1546
1584
|
CODIEDEV_INSTRUCTIONS_BEGIN +
|
|
1547
1585
|
"\n" +
|
|
1548
|
-
CODIEDEV_INSTRUCTIONS_BODY +
|
|
1586
|
+
`${teamSkillContext_1.TEAM_SKILL_CONTEXT}\n\n${CODIEDEV_INSTRUCTIONS_BODY}` +
|
|
1549
1587
|
"\n" +
|
|
1550
1588
|
CODIEDEV_INSTRUCTIONS_END +
|
|
1551
1589
|
existing.slice(endIdx + CODIEDEV_INSTRUCTIONS_END.length);
|
|
@@ -1904,12 +1942,93 @@ function installCursorHook() {
|
|
|
1904
1942
|
replaceManaged("sessionStart", ["session-start-cursor"]);
|
|
1905
1943
|
replaceManaged("sessionEnd", ["capture-cursor"]);
|
|
1906
1944
|
replaceManaged("stop", ["backup-cursor"]);
|
|
1945
|
+
for (const event of ["postToolUse", "postToolUseFailure", "subagentStart", "subagentStop"])
|
|
1946
|
+
replaceManaged(event, ["observe-cursor"]);
|
|
1907
1947
|
// Cursor cannot inject dynamic context from beforeSubmitPrompt today.
|
|
1908
1948
|
// Remove older Codiedev discovery entries so prompts are not uploaded for
|
|
1909
1949
|
// a response the host will discard; preserve any third-party hooks.
|
|
1910
1950
|
replaceManaged("beforeSubmitPrompt", []);
|
|
1911
1951
|
fs.writeFileSync(CURSOR_HOOKS_PATH, JSON.stringify(hooksFile, null, 2), "utf8");
|
|
1912
1952
|
}
|
|
1953
|
+
// GitHub Copilot reads every `$COPILOT_HOME/hooks/*.json`, so Codiedev owns
|
|
1954
|
+
// one file and never edits anyone else's. camelCase events only: VS Code also
|
|
1955
|
+
// accepts Claude-style PascalCase names from the same file, and configuring
|
|
1956
|
+
// both would fire every hook twice.
|
|
1957
|
+
function copilotHooksPath() {
|
|
1958
|
+
return path.join((0, copilotHooks_1.copilotHome)(), "hooks", "codiedev.json");
|
|
1959
|
+
}
|
|
1960
|
+
function copilotManagedHooks(agentSecurityEnabled) {
|
|
1961
|
+
return [
|
|
1962
|
+
["sessionStart", "session-start-copilot", 10],
|
|
1963
|
+
["userPromptSubmitted", "discover-copilot", 10],
|
|
1964
|
+
// agentStop = end of a turn: a non-final snapshot. VS Code sessions live
|
|
1965
|
+
// until the chat is closed, so this is what keeps them captured.
|
|
1966
|
+
["agentStop", "backup-copilot", 30],
|
|
1967
|
+
["sessionEnd", "capture-copilot", 30],
|
|
1968
|
+
["errorOccurred", "limit-copilot", 10],
|
|
1969
|
+
];
|
|
1970
|
+
}
|
|
1971
|
+
const COPILOT_EVENTS = ["sessionStart", "userPromptSubmitted", "agentStop", "sessionEnd", "preToolUse", "postToolUse", "errorOccurred"];
|
|
1972
|
+
function installCopilotHook(agentSecurityEnabled = false) {
|
|
1973
|
+
const hooksPath = copilotHooksPath();
|
|
1974
|
+
let hooksFile = {};
|
|
1975
|
+
try {
|
|
1976
|
+
if (fs.existsSync(hooksPath)) {
|
|
1977
|
+
const raw = fs.readFileSync(hooksPath, "utf8");
|
|
1978
|
+
hooksFile = raw.trim() ? JSON.parse(raw) : {};
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
catch {
|
|
1982
|
+
hooksFile = {};
|
|
1983
|
+
}
|
|
1984
|
+
hooksFile.version = 1;
|
|
1985
|
+
const hooks = (hooksFile.hooks && typeof hooksFile.hooks === "object" ? hooksFile.hooks : {});
|
|
1986
|
+
const managed = copilotManagedHooks(agentSecurityEnabled);
|
|
1987
|
+
for (const eventName of COPILOT_EVENTS) {
|
|
1988
|
+
const existing = Array.isArray(hooks[eventName]) ? hooks[eventName] : [];
|
|
1989
|
+
// Anything a user added to this file by hand stays.
|
|
1990
|
+
const preserved = existing.filter((hook) => !(0, detection_1.isCodiedevHookCommand)((hook.bash ?? hook.powershell ?? hook.command)));
|
|
1991
|
+
const ours = managed
|
|
1992
|
+
.filter(([event]) => event === eventName)
|
|
1993
|
+
.map(([, subcommand, timeoutSec]) => {
|
|
1994
|
+
const command = resolveHookCommand(subcommand);
|
|
1995
|
+
return { type: "command", bash: command, powershell: command, timeoutSec };
|
|
1996
|
+
});
|
|
1997
|
+
if (preserved.length + ours.length === 0)
|
|
1998
|
+
delete hooks[eventName];
|
|
1999
|
+
else
|
|
2000
|
+
hooks[eventName] = [...preserved, ...ours];
|
|
2001
|
+
}
|
|
2002
|
+
hooksFile.hooks = hooks;
|
|
2003
|
+
fs.mkdirSync(path.dirname(hooksPath), { recursive: true });
|
|
2004
|
+
fs.writeFileSync(hooksPath, JSON.stringify(hooksFile, null, 2), "utf8");
|
|
2005
|
+
}
|
|
2006
|
+
/** Self-heal the Copilot hook file after upgrades or drift (like Cursor's). */
|
|
2007
|
+
function ensureCopilotCodiedevHooksInstalled() {
|
|
2008
|
+
const agentSecurityEnabled = false; // production line: no security hooks
|
|
2009
|
+
try {
|
|
2010
|
+
const hooksPath = copilotHooksPath();
|
|
2011
|
+
if (!fs.existsSync(hooksPath))
|
|
2012
|
+
return;
|
|
2013
|
+
const parsed = JSON.parse(fs.readFileSync(hooksPath, "utf8"));
|
|
2014
|
+
const hooks = parsed.hooks ?? {};
|
|
2015
|
+
const commandsFor = (eventName) => (Array.isArray(hooks[eventName]) ? hooks[eventName] : [])
|
|
2016
|
+
.map((entry) => (entry.bash ?? entry.powershell ?? entry.command))
|
|
2017
|
+
.filter((command) => typeof command === "string" && (0, detection_1.isCodiedevHookCommand)(command));
|
|
2018
|
+
const required = copilotManagedHooks(agentSecurityEnabled);
|
|
2019
|
+
const complete = required.every(([eventName, subcommand]) => commandsFor(eventName).filter((command) => command.trim().endsWith(subcommand)).length === 1);
|
|
2020
|
+
const stale = COPILOT_EVENTS.some((eventName) => commandsFor(eventName).length !== required.filter(([event]) => event === eventName).length);
|
|
2021
|
+
if (complete && !stale)
|
|
2022
|
+
return;
|
|
2023
|
+
installCopilotHook(agentSecurityEnabled);
|
|
2024
|
+
}
|
|
2025
|
+
catch {
|
|
2026
|
+
// Best-effort — a malformed hook file must never block capture.
|
|
2027
|
+
}
|
|
2028
|
+
}
|
|
2029
|
+
/** Repair Cursor's complete flat hook set after upgrades or configuration
|
|
2030
|
+
* drift. This is deliberately separate from Claude Code's nested-hook healer:
|
|
2031
|
+
* the schemas and event names differ, and mixing them risks corrupting both. */
|
|
1913
2032
|
function ensureCursorCodiedevHooksInstalled() {
|
|
1914
2033
|
try {
|
|
1915
2034
|
if (!fs.existsSync(CURSOR_HOOKS_PATH))
|
|
@@ -1920,6 +2039,10 @@ function ensureCursorCodiedevHooksInstalled() {
|
|
|
1920
2039
|
["sessionStart", "session-start-cursor"],
|
|
1921
2040
|
["sessionEnd", "capture-cursor"],
|
|
1922
2041
|
["stop", "backup-cursor"],
|
|
2042
|
+
["postToolUse", "observe-cursor"],
|
|
2043
|
+
["postToolUseFailure", "observe-cursor"],
|
|
2044
|
+
["subagentStart", "observe-cursor"],
|
|
2045
|
+
["subagentStop", "observe-cursor"],
|
|
1923
2046
|
];
|
|
1924
2047
|
const has = (eventName, subcommand) => {
|
|
1925
2048
|
const entries = hooks[eventName];
|
|
@@ -1981,46 +2104,27 @@ function parseCodexSteering(content) {
|
|
|
1981
2104
|
return { interrupts, rejections };
|
|
1982
2105
|
}
|
|
1983
2106
|
function parseSteering(content, mode) {
|
|
2107
|
+
if (mode === "cursor" || mode === "copilot")
|
|
2108
|
+
return (0, nativeSteering_1.parseNativeSteering)(content);
|
|
1984
2109
|
return mode === "codex"
|
|
1985
2110
|
? parseCodexSteering(content)
|
|
1986
2111
|
: parseClaudeSteering(content);
|
|
1987
2112
|
}
|
|
1988
2113
|
function parseClaudeUsage(content) {
|
|
1989
|
-
|
|
1990
|
-
// already fresh (cache read/creation are reported separately).
|
|
1991
|
-
let inputTokens = 0;
|
|
1992
|
-
let outputTokens = 0;
|
|
1993
|
-
let cacheReadTokens = 0;
|
|
1994
|
-
let cacheCreationTokens = 0;
|
|
2114
|
+
const models = (0, transcriptModels_1.parseTranscriptModels)(content, "claude-code");
|
|
1995
2115
|
let model = null;
|
|
1996
2116
|
for (const line of content.split("\n")) {
|
|
1997
|
-
if (!line.trim())
|
|
1998
|
-
continue;
|
|
1999
2117
|
try {
|
|
2000
|
-
const
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
if (u) {
|
|
2004
|
-
inputTokens += u.input_tokens ?? 0;
|
|
2005
|
-
outputTokens += u.output_tokens ?? 0;
|
|
2006
|
-
cacheReadTokens += u.cache_read_input_tokens ?? 0;
|
|
2007
|
-
cacheCreationTokens += u.cache_creation_input_tokens ?? 0;
|
|
2008
|
-
}
|
|
2009
|
-
if (typeof m?.model === "string" && m.model)
|
|
2010
|
-
model = m.model;
|
|
2011
|
-
}
|
|
2012
|
-
catch {
|
|
2013
|
-
// skip malformed
|
|
2118
|
+
const value = JSON.parse(line)?.message?.model;
|
|
2119
|
+
if (typeof value === "string" && value.trim() && !value.startsWith("<"))
|
|
2120
|
+
model = value;
|
|
2014
2121
|
}
|
|
2122
|
+
catch { /* partial records */ }
|
|
2015
2123
|
}
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
cacheTokens: cacheReadTokens + cacheCreationTokens,
|
|
2020
|
-
cacheReadTokens,
|
|
2021
|
-
cacheCreationTokens,
|
|
2022
|
-
model,
|
|
2023
|
-
};
|
|
2124
|
+
const sum = (field) => models.reduce((total, usage) => total + usage[field], 0);
|
|
2125
|
+
const cacheReadTokens = sum("cacheReadTokens"), cacheCreationTokens = sum("cacheCreationTokens");
|
|
2126
|
+
return { inputTokens: sum("inputTokens"), outputTokens: sum("outputTokens"),
|
|
2127
|
+
cacheTokens: cacheReadTokens + cacheCreationTokens, cacheReadTokens, cacheCreationTokens, model };
|
|
2024
2128
|
}
|
|
2025
2129
|
function parseCodexUsage(content) {
|
|
2026
2130
|
// Codex reports a CUMULATIVE token_count per turn — the last one is the
|
|
@@ -2154,20 +2258,30 @@ function parseSessionStart(content) {
|
|
|
2154
2258
|
function parseClaudeCodeStats(content) {
|
|
2155
2259
|
let messageCount = 0;
|
|
2156
2260
|
let toolCallCount = 0;
|
|
2261
|
+
const messages = new Set();
|
|
2262
|
+
const tools = new Set();
|
|
2157
2263
|
const lines = content.split("\n").filter((l) => l.trim());
|
|
2158
2264
|
for (const line of lines) {
|
|
2159
2265
|
try {
|
|
2160
2266
|
const entry = JSON.parse(line);
|
|
2161
2267
|
if (!entry || typeof entry !== "object")
|
|
2162
2268
|
continue;
|
|
2163
|
-
|
|
2269
|
+
const id = entry.message?.id ?? entry.uuid;
|
|
2270
|
+
const messageKey = `${entry.type}:${id ?? messages.size}`;
|
|
2271
|
+
if ((entry.type === "user" || entry.type === "assistant") && !messages.has(messageKey)) {
|
|
2272
|
+
messages.add(messageKey);
|
|
2164
2273
|
messageCount++;
|
|
2165
2274
|
}
|
|
2166
2275
|
const c = entry.message?.content;
|
|
2167
2276
|
if (Array.isArray(c)) {
|
|
2168
|
-
for (const block of c) {
|
|
2169
|
-
if (block
|
|
2277
|
+
for (const [index, block] of c.entries()) {
|
|
2278
|
+
if (!block || block.type !== "tool_use")
|
|
2279
|
+
continue;
|
|
2280
|
+
const toolKey = block.id ?? `${messageKey}:${index}`;
|
|
2281
|
+
if (!tools.has(toolKey)) {
|
|
2282
|
+
tools.add(toolKey);
|
|
2170
2283
|
toolCallCount++;
|
|
2284
|
+
}
|
|
2171
2285
|
}
|
|
2172
2286
|
}
|
|
2173
2287
|
}
|
|
@@ -2215,14 +2329,14 @@ function normalizeOpenCodeTranscript(content) {
|
|
|
2215
2329
|
else if (part.type === "tool") {
|
|
2216
2330
|
normalizedParts.push({
|
|
2217
2331
|
type: "tool_use",
|
|
2218
|
-
id: part.callID ?? part.id
|
|
2332
|
+
id: part.callID ?? part.id,
|
|
2219
2333
|
name: part.tool ?? "tool",
|
|
2220
2334
|
input: part.state?.input ?? {},
|
|
2221
2335
|
});
|
|
2222
2336
|
if (part.state?.output != null || part.state?.error != null) {
|
|
2223
2337
|
normalizedParts.push({
|
|
2224
2338
|
type: "tool_result",
|
|
2225
|
-
tool_use_id: part.callID ?? part.id
|
|
2339
|
+
tool_use_id: part.callID ?? part.id,
|
|
2226
2340
|
content: part.state?.output ?? part.state?.error,
|
|
2227
2341
|
is_error: part.state?.status === "error" || part.state?.error != null,
|
|
2228
2342
|
});
|
|
@@ -2238,6 +2352,7 @@ function normalizeOpenCodeTranscript(content) {
|
|
|
2238
2352
|
type: role,
|
|
2239
2353
|
timestamp,
|
|
2240
2354
|
message: {
|
|
2355
|
+
id: typeof info.id === "string" ? info.id : undefined,
|
|
2241
2356
|
role,
|
|
2242
2357
|
model: typeof info.modelID === "string" ? info.modelID : undefined,
|
|
2243
2358
|
usage: role === "assistant" ? {
|
|
@@ -2568,7 +2683,7 @@ function resolveCaptureFinal(argv2) {
|
|
|
2568
2683
|
return false;
|
|
2569
2684
|
if (argv2 === "capture-opencode-final" || argv2 === "capture-desktop-final")
|
|
2570
2685
|
return true;
|
|
2571
|
-
return argv2 === "capture" || argv2 === "capture-cursor" || argv2 === undefined
|
|
2686
|
+
return argv2 === "capture" || argv2 === "capture-cursor" || argv2 === "capture-copilot" || argv2 === undefined
|
|
2572
2687
|
? true
|
|
2573
2688
|
: undefined;
|
|
2574
2689
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codiedev",
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"description": "Connect Claude Code, Codex, Cursor, OpenCode, or VS Code Copilot to Codiedev for team session capture and artifact collaboration
|
|
3
|
+
"version": "0.9.61",
|
|
4
|
+
"description": "Connect Claude Code, Codex, Cursor, OpenCode, or VS Code Copilot to Codiedev for team session capture and artifact collaboration \u2014 and run the governed Codie harness",
|
|
5
5
|
"bin": {
|
|
6
6
|
"codiedev": "dist/cli.js",
|
|
7
7
|
"codiedev-hook": "dist/hook.js",
|
|
@@ -12,10 +12,13 @@
|
|
|
12
12
|
"dev": "tsc --watch",
|
|
13
13
|
"prepublishOnly": "tsc",
|
|
14
14
|
"test": "vitest run",
|
|
15
|
-
"test:functional": "vitest run src/__tests__/limitEvent.functional.test.ts src/__tests__/harnessParity.functional.test.ts src/__tests__/cursorParity.functional.test.ts",
|
|
16
|
-
"test:harness-parity": "vitest run src/__tests__/harnessParity.functional.test.ts src/__tests__/cursorParity.functional.test.ts",
|
|
15
|
+
"test:functional": "vitest run src/__tests__/limitEvent.functional.test.ts src/__tests__/harnessParity.functional.test.ts src/__tests__/cursorParity.functional.test.ts src/__tests__/copilotCapture.functional.test.ts",
|
|
16
|
+
"test:harness-parity": "vitest run src/__tests__/harnessParity.functional.test.ts src/__tests__/cursorParity.functional.test.ts src/__tests__/copilotCapture.functional.test.ts",
|
|
17
17
|
"test:harness-binaries": "node scripts/checkHarnessBinaries.mjs",
|
|
18
|
-
"test:watch": "vitest"
|
|
18
|
+
"test:watch": "vitest",
|
|
19
|
+
"test:live-sources": "npm run build && node scripts/liveSourceParity.mjs",
|
|
20
|
+
"test:ide-sources": "npm run build && node scripts/liveSourceParity.mjs --ide",
|
|
21
|
+
"test:metadata-parity": "vitest run src/__tests__/sourceMetadataMatrix.test.ts src/__tests__/copilotCapture.functional.test.ts src/__tests__/copilotCapture.test.ts src/__tests__/cursorParity.functional.test.ts src/__tests__/nativeSourceParity.test.ts src/__tests__/harnessParity.functional.test.ts src/__tests__/subagents.test.ts src/__tests__/transcriptModels.test.ts src/__tests__/captureRetry.test.ts src/__tests__/limitEvent.functional.test.ts src/__tests__/captureExclusions.test.ts"
|
|
19
22
|
},
|
|
20
23
|
"files": [
|
|
21
24
|
"dist",
|