codiedev 0.9.34 → 0.9.35

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/codie.js CHANGED
@@ -231,7 +231,8 @@ async function main() {
231
231
  CODIEDEV_HEAD_SHA: git(["rev-parse", "HEAD"]),
232
232
  OPENCODE_CONFIG_CONTENT: JSON.stringify(config),
233
233
  };
234
- const result = (0, child_process_1.spawnSync)(runtime, parsed.args, { stdio: "inherit", env });
234
+ const runtimeArgs = (0, codieLauncher_1.withCodieWorkingDirectory)(parsed.args, process.cwd());
235
+ const result = (0, child_process_1.spawnSync)(runtime, runtimeArgs, { stdio: "inherit", env });
235
236
  if (result.error)
236
237
  throw result.error;
237
238
  process.exitCode = result.status === null ? 1 : result.status;
@@ -2,6 +2,13 @@ export interface CatalogModel {
2
2
  id: string;
3
3
  name?: string;
4
4
  }
5
+ /**
6
+ * Pin non-interactive runs to the directory where `codie` was launched.
7
+ * OpenCode can reuse a previously-known project instance; without an explicit
8
+ * --dir, a real smoke run launched in a throwaway repo resumed the caller's
9
+ * main checkout and executed there. TUI mode already owns its cwd directly.
10
+ */
11
+ export declare function withCodieWorkingDirectory(args: string[], cwd: string): string[];
5
12
  export declare function bareModel(value: unknown): string;
6
13
  export declare function buildCodieConfig({ existing, catalog, env, plugin, plugins, }: {
7
14
  existing?: Record<string, any>;
@@ -43,6 +43,7 @@ var __importStar = (this && this.__importStar) || (function () {
43
43
  })();
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.HIDDEN_WORDMARK = exports.CODIEDEV_WORDMARK = exports.LEGACY_CODIEDEV_WORDMARK = exports.STOCK_WORDMARK = exports.BRAND_PLUGIN_SPEC = void 0;
46
+ exports.withCodieWorkingDirectory = withCodieWorkingDirectory;
46
47
  exports.bareModel = bareModel;
47
48
  exports.buildCodieConfig = buildCodieConfig;
48
49
  exports.withBrandPlugin = withBrandPlugin;
@@ -61,6 +62,19 @@ const DEFAULT_NAMES = {
61
62
  "z-ai/glm-5.2": "GLM 5.2 · Fireworks ZDR",
62
63
  "moonshotai/kimi-k2.7-code": "Kimi K2.7 Code · Fireworks ZDR",
63
64
  };
65
+ /**
66
+ * Pin non-interactive runs to the directory where `codie` was launched.
67
+ * OpenCode can reuse a previously-known project instance; without an explicit
68
+ * --dir, a real smoke run launched in a throwaway repo resumed the caller's
69
+ * main checkout and executed there. TUI mode already owns its cwd directly.
70
+ */
71
+ function withCodieWorkingDirectory(args, cwd) {
72
+ if (args[0] !== "run")
73
+ return args;
74
+ if (args.some((arg) => arg === "--dir" || arg.startsWith("--dir=")))
75
+ return args;
76
+ return [...args, "--dir", cwd];
77
+ }
64
78
  function cleanBaseUrl(value) {
65
79
  return String(value || "https://gateway.codiedev.com/api/v1").replace(/\/+$/, "");
66
80
  }
package/dist/hook.js CHANGED
@@ -661,6 +661,7 @@ async function runCaptureFinishInner(input, config, session_id, transcript_path,
661
661
  // worktree's branch+sha so all the resulting PRs attribute. branch/headSha
662
662
  // remain the primary (most-recent) values for back-compat.
663
663
  const keys = (0, utils_1.collectCaptureKeys)(captureDirs);
664
+ const gitRefs = (0, utils_1.collectCaptureGitRefs)(captureDirs);
664
665
  // Union the worktree-dir branches with the branches the agent actually
665
666
  // committed/pushed to (parsed from the transcript's git output). Like the
666
667
  // SHAs below, these are independent of end-of-session filesystem state, so
@@ -678,6 +679,7 @@ async function runCaptureFinishInner(input, config, session_id, transcript_path,
678
679
  const authoredShas = (0, utils_1.parseAuthoredShas)(transcriptContent, workspaceRoot);
679
680
  const headShas = [...new Set([...keys.headShas, ...authoredShas])];
680
681
  const headSha = keys.headSha ?? headShas[0] ?? null;
682
+ const pullRequestUrls = (0, utils_1.parsePullRequestUrls)(transcriptContent);
681
683
  // Cost/velocity/steering signals parsed straight from the transcript we're
682
684
  // already holding — no extra hooks, no extra git/process calls. parseUsage
683
685
  // keeps the literal mode (it has its own desktop parser) and reads the RAW
@@ -700,6 +702,8 @@ async function runCaptureFinishInner(input, config, session_id, transcript_path,
700
702
  headSha,
701
703
  branches,
702
704
  headShas,
705
+ gitRefs,
706
+ pullRequestUrls,
703
707
  inputTokens: usage.inputTokens,
704
708
  outputTokens: usage.outputTokens,
705
709
  cacheTokens: usage.cacheTokens,
package/dist/utils.d.ts CHANGED
@@ -45,6 +45,13 @@ export declare function collectCaptureKeys(dirs: string[], getBranch?: (d: strin
45
45
  branches: string[];
46
46
  headShas: string[];
47
47
  };
48
+ export type CaptureGitRef = {
49
+ repoUrl: string;
50
+ branch?: string;
51
+ headSha?: string;
52
+ };
53
+ export declare function collectCaptureGitRefs(dirs: string[], getRemote?: (d: string) => string | null, getBranch?: (d: string) => string | null, getSha?: (d: string) => string | null): CaptureGitRef[];
54
+ export declare function parsePullRequestUrls(transcriptContent: string): string[];
48
55
  export declare function extractShaTokens(transcriptContent: string): string[];
49
56
  export declare function parseAuthoredBranches(transcriptContent: string): string[];
50
57
  export declare function parseAuthoredShas(transcriptContent: string, repoRoot: string, resolveFullBatch?: (shas: string[]) => Map<string, string>): string[];
@@ -64,6 +71,8 @@ export interface CapturePayloadInput {
64
71
  headSha?: string | null;
65
72
  branches?: string[];
66
73
  headShas?: string[];
74
+ gitRefs?: CaptureGitRef[];
75
+ pullRequestUrls?: string[];
67
76
  inputTokens?: number;
68
77
  outputTokens?: number;
69
78
  cacheTokens?: number;
package/dist/utils.js CHANGED
@@ -46,6 +46,8 @@ exports.matchRepo = matchRepo;
46
46
  exports.resolveCaptureDir = resolveCaptureDir;
47
47
  exports.resolveCaptureDirs = resolveCaptureDirs;
48
48
  exports.collectCaptureKeys = collectCaptureKeys;
49
+ exports.collectCaptureGitRefs = collectCaptureGitRefs;
50
+ exports.parsePullRequestUrls = parsePullRequestUrls;
49
51
  exports.extractShaTokens = extractShaTokens;
50
52
  exports.parseAuthoredBranches = parseAuthoredBranches;
51
53
  exports.parseAuthoredShas = parseAuthoredShas;
@@ -450,6 +452,144 @@ function collectCaptureKeys(dirs, getBranch = getGitBranch, getSha = getGitHeadS
450
452
  }
451
453
  return { branch: branches[0] ?? null, headSha: headShas[0] ?? null, branches, headShas };
452
454
  }
455
+ function collectCaptureGitRefs(dirs, getRemote = getGitRemoteUrl, getBranch = getGitBranch, getSha = getGitHeadSha) {
456
+ const out = [];
457
+ const seen = new Set();
458
+ for (const dir of dirs) {
459
+ const repoUrl = getRemote(dir)?.trim();
460
+ if (!repoUrl)
461
+ continue;
462
+ const branch = getBranch(dir)?.trim() || undefined;
463
+ const headSha = getSha(dir)?.trim().toLowerCase() || undefined;
464
+ if (!branch && !headSha)
465
+ continue;
466
+ const key = `${normalizeRepoUrl(repoUrl).toLowerCase()}::${branch ?? ""}::${headSha ?? ""}`;
467
+ if (seen.has(key))
468
+ continue;
469
+ seen.add(key);
470
+ out.push({ repoUrl, ...(branch ? { branch } : {}), ...(headSha ? { headSha } : {}) });
471
+ }
472
+ return out;
473
+ }
474
+ // Exact PR/MR URLs are the strongest provenance the transcript can provide.
475
+ // Both `gh pr create` and `glab mr create` print one, and the OpenCode capture
476
+ // is normalized through the same parser as Claude Code. A direct URL survives
477
+ // branch deletion, squash/rebase, cherry-picks, and SessionEnd happening after
478
+ // the PR was opened.
479
+ function parsePullRequestUrls(transcriptContent) {
480
+ const out = [];
481
+ const createToolIds = new Set();
482
+ const isCreateCommand = (command) => {
483
+ if (typeof command !== "string")
484
+ return false;
485
+ // A combined `gh pr view || gh pr create` result is ambiguous: when the PR
486
+ // already exists, the VIEW half prints its URL and CREATE never runs.
487
+ if (/\b(?:gh\s+pr\s+(?:view|list|status)|glab\s+mr\s+(?:view|list))\b/i.test(command)) {
488
+ return false;
489
+ }
490
+ return command.split(/&&|\|\||;|\n/).some((statement) => /^(?:(?:env\s+)?[A-Z_][A-Z0-9_]*=\S+\s+)*(?:gh\s+pr\s+create|glab\s+mr\s+create)\b/i
491
+ .test(statement.trim()));
492
+ };
493
+ const scanText = (text) => {
494
+ const normalized = text.replace(/\\\//g, "/");
495
+ const candidates = normalized.match(/https?:\/\/[^\s<>"']+/gi) ?? [];
496
+ for (const raw of candidates) {
497
+ const trimmed = raw.replace(/[),.;:\]}]+$/g, "");
498
+ try {
499
+ const url = new URL(trimmed);
500
+ const path = url.pathname.replace(/\/+$/, "");
501
+ const isGithubPr = /^\/.+\/pull\/\d+$/i.test(path);
502
+ const isGitlabMr = /^\/.+\/-\/merge_requests\/\d+$/i.test(path);
503
+ if (!isGithubPr && !isGitlabMr)
504
+ continue;
505
+ const canonical = `${url.protocol}//${url.host}${path}`;
506
+ if (!out.includes(canonical))
507
+ out.push(canonical);
508
+ }
509
+ catch {
510
+ // Malformed tool output is ignored; capture remains best-effort.
511
+ }
512
+ }
513
+ };
514
+ const scanContent = (value) => {
515
+ if (typeof value === "string") {
516
+ scanText(value);
517
+ return;
518
+ }
519
+ if (!Array.isArray(value))
520
+ return;
521
+ for (const part of value) {
522
+ if (typeof part === "string")
523
+ scanText(part);
524
+ else if (part && typeof part === "object") {
525
+ const text = part.text;
526
+ if (typeof text === "string")
527
+ scanText(text);
528
+ }
529
+ }
530
+ };
531
+ const entries = [];
532
+ for (const line of transcriptContent.split("\n")) {
533
+ if (!line.trim())
534
+ continue;
535
+ try {
536
+ const entry = JSON.parse(line);
537
+ entries.push(entry);
538
+ const content = entry?.message?.content;
539
+ if (Array.isArray(content)) {
540
+ for (const block of content) {
541
+ if (block?.type === "tool_use"
542
+ && typeof block.id === "string"
543
+ && isCreateCommand(block.input?.command)) {
544
+ createToolIds.add(block.id);
545
+ }
546
+ }
547
+ }
548
+ // Codex function calls carry arguments as serialized JSON.
549
+ if (entry?.type === "response_item"
550
+ && entry?.payload?.type === "function_call"
551
+ && typeof entry.payload.call_id === "string") {
552
+ let args = entry.payload.arguments;
553
+ if (typeof args === "string") {
554
+ try {
555
+ args = JSON.parse(args);
556
+ }
557
+ catch { /* leave as string */ }
558
+ }
559
+ if (isCreateCommand(args?.cmd ?? args?.command ?? args)) {
560
+ createToolIds.add(entry.payload.call_id);
561
+ }
562
+ }
563
+ }
564
+ catch {
565
+ // Never regex the raw line: prompts and assistant prose can contain a PR
566
+ // URL, but only actual tool output is provenance.
567
+ }
568
+ }
569
+ // Second pass: a URL is proof only when it came from the result of the
570
+ // specific gh/glab CREATE call above. `gh pr view/list` also prints URLs and
571
+ // must never claim somebody else's already-existing PR for this session.
572
+ for (const entry of entries) {
573
+ const content = entry?.message?.content;
574
+ if (Array.isArray(content)) {
575
+ for (const block of content) {
576
+ if (block?.type === "tool_result"
577
+ && typeof block.tool_use_id === "string"
578
+ && createToolIds.has(block.tool_use_id)
579
+ && block.is_error !== true) {
580
+ scanContent(block.content);
581
+ }
582
+ }
583
+ }
584
+ if (entry?.type === "response_item"
585
+ && entry?.payload?.type === "function_call_output"
586
+ && typeof entry.payload.call_id === "string"
587
+ && createToolIds.has(entry.payload.call_id)) {
588
+ scanContent(entry.payload.output);
589
+ }
590
+ }
591
+ return out;
592
+ }
453
593
  // Pull the commit SHAs the agent actually authored/pushed straight out of the
454
594
  // transcript's git command OUTPUT — independent of end-of-session filesystem
455
595
  // state. Catches: commit output "[branch 4c4e790]", push ranges
@@ -690,6 +830,10 @@ function buildCapturePayload(input) {
690
830
  // see no change; newer servers index the session under every key.
691
831
  branches: input.branches && input.branches.length ? input.branches : undefined,
692
832
  headShas: input.headShas && input.headShas.length ? input.headShas : undefined,
833
+ gitRefs: input.gitRefs && input.gitRefs.length ? input.gitRefs : undefined,
834
+ pullRequestUrls: input.pullRequestUrls && input.pullRequestUrls.length
835
+ ? input.pullRequestUrls
836
+ : undefined,
693
837
  transcript: input.transcriptBase64,
694
838
  messageCount: input.messageCount,
695
839
  toolCallCount: input.toolCallCount,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codiedev",
3
- "version": "0.9.34",
3
+ "version": "0.9.35",
4
4
  "description": "Connect Claude Code, Codex, Cursor, OpenCode, or VS Code Copilot to Codiedev for team session capture and artifact collaboration — and run the governed Codie harness",
5
5
  "bin": {
6
6
  "codiedev": "dist/cli.js",
package/dist/scope.d.ts DELETED
@@ -1,8 +0,0 @@
1
- export interface ScopeInfo {
2
- workspaceName: string;
3
- matchedRepo: {
4
- fullName: string;
5
- } | null;
6
- }
7
- export declare function formatScope(info: ScopeInfo): string;
8
- export declare function printScope(info: ScopeInfo): void;
package/dist/scope.js DELETED
@@ -1,15 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatScope = formatScope;
4
- exports.printScope = printScope;
5
- function formatScope(info) {
6
- if (!info.workspaceName)
7
- return "→ unknown workspace";
8
- if (info.matchedRepo) {
9
- return `→ ${info.workspaceName} (matched ${info.matchedRepo.fullName})`;
10
- }
11
- return `→ ${info.workspaceName} (default profile, no repo match)`;
12
- }
13
- function printScope(info) {
14
- console.log(formatScope(info));
15
- }
@@ -1,34 +0,0 @@
1
- export type SecurityHookPhase = "pre" | "post" | "failure";
2
- export type AgentSurface = "claude-code" | "codex";
3
- export interface RawSecurityHookInput {
4
- session_id?: string;
5
- sessionId?: string;
6
- cwd?: string;
7
- hook_event_name?: string;
8
- hookEventName?: string;
9
- tool_name?: string;
10
- toolName?: string;
11
- tool_input?: unknown;
12
- toolInput?: unknown;
13
- tool_response?: unknown;
14
- toolResponse?: unknown;
15
- tool_use_id?: string;
16
- toolUseId?: string;
17
- duration_ms?: number;
18
- durationMs?: number;
19
- error?: unknown;
20
- }
21
- export interface SecurityWorkflowEvent {
22
- schemaVersion: 1;
23
- source: AgentSurface;
24
- phase: SecurityHookPhase;
25
- sessionId: string;
26
- toolName: string;
27
- toolUseId?: string;
28
- cwd?: string;
29
- observedAt: number;
30
- toolDurationMs?: number;
31
- payload: unknown;
32
- redactionCount: number;
33
- }
34
- export declare function buildSecurityWorkflowEvent(input: RawSecurityHookInput, phase: SecurityHookPhase, source: AgentSurface, now?: () => number): SecurityWorkflowEvent | null;
package/dist/security.js DELETED
@@ -1,119 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.buildSecurityWorkflowEvent = buildSecurityWorkflowEvent;
37
- const crypto = __importStar(require("crypto"));
38
- const SECRET_KEY = /(?:api[_-]?key|access[_-]?token|auth[_-]?token|password|passwd|secret|private[_-]?key|client[_-]?secret)/i;
39
- const SECRET_VALUE_PATTERNS = [
40
- /\b(?:sk|rk|pk)-(?:live|test|proj)?[-_A-Za-z0-9]{16,}\b/g,
41
- /\bgh[opusr]_[A-Za-z0-9]{20,}\b/g,
42
- /\bAKIA[0-9A-Z]{16}\b/g,
43
- /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g,
44
- /\bBearer\s+[A-Za-z0-9._~+\/-]{16,}=*\b/gi,
45
- ];
46
- function fingerprint(value) {
47
- return crypto.createHash("sha256").update(value).digest("hex").slice(0, 12);
48
- }
49
- function redactString(value) {
50
- let count = 0;
51
- let redacted = value;
52
- for (const pattern of SECRET_VALUE_PATTERNS) {
53
- redacted = redacted.replace(pattern, (match) => {
54
- count += 1;
55
- return `[REDACTED_SECRET:${fingerprint(match)}]`;
56
- });
57
- }
58
- // Keep hook payloads bounded. Full transcripts already use Codie's existing
59
- // capture path; the detector only needs immediate causal context here.
60
- if (redacted.length > 16_384)
61
- redacted = `${redacted.slice(0, 16_384)}[TRUNCATED]`;
62
- return { value: redacted, count };
63
- }
64
- function redact(value, key = "", depth = 0) {
65
- if (depth > 8)
66
- return { value: "[TRUNCATED_DEPTH]", count: 0 };
67
- if (SECRET_KEY.test(key) && value !== null && value !== undefined) {
68
- const text = typeof value === "string" ? value : JSON.stringify(value);
69
- return { value: `[REDACTED_SECRET:${fingerprint(text)}]`, count: 1 };
70
- }
71
- if (typeof value === "string")
72
- return redactString(value);
73
- if (Array.isArray(value)) {
74
- let count = 0;
75
- const next = value.slice(0, 200).map((item) => {
76
- const result = redact(item, key, depth + 1);
77
- count += result.count;
78
- return result.value;
79
- });
80
- return { value: next, count };
81
- }
82
- if (value && typeof value === "object") {
83
- let count = 0;
84
- const next = {};
85
- for (const [childKey, childValue] of Object.entries(value).slice(0, 200)) {
86
- const result = redact(childValue, childKey, depth + 1);
87
- count += result.count;
88
- next[childKey] = result.value;
89
- }
90
- return { value: next, count };
91
- }
92
- return { value, count: 0 };
93
- }
94
- function buildSecurityWorkflowEvent(input, phase, source, now = Date.now) {
95
- const sessionId = input.session_id ?? input.sessionId ?? "";
96
- const toolName = input.tool_name ?? input.toolName ?? "";
97
- if (!sessionId || !toolName)
98
- return null;
99
- const rawPayload = {
100
- input: input.tool_input ?? input.toolInput,
101
- ...(phase === "post" ? { response: input.tool_response ?? input.toolResponse } : {}),
102
- ...(phase === "failure" ? { error: input.error ?? input.tool_response ?? input.toolResponse } : {}),
103
- };
104
- const sanitized = redact(rawPayload);
105
- const duration = input.duration_ms ?? input.durationMs;
106
- return {
107
- schemaVersion: 1,
108
- source,
109
- phase,
110
- sessionId,
111
- toolName,
112
- toolUseId: input.tool_use_id ?? input.toolUseId,
113
- cwd: input.cwd,
114
- observedAt: now(),
115
- toolDurationMs: typeof duration === "number" && Number.isFinite(duration) ? duration : undefined,
116
- payload: sanitized.value,
117
- redactionCount: sanitized.count,
118
- };
119
- }