@nseng-ai/ccc 0.1.1 → 0.1.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nseng-ai/ccc",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src"
@@ -26,15 +26,15 @@
26
26
  "./pi/extension": "./src/pi/extension.ts"
27
27
  },
28
28
  "dependencies": {
29
- "@nseng-ai/branch-context": "0.1.1",
30
- "@nseng-ai/capability-kit": "0.1.1",
31
- "@nseng-ai/clinkr": "0.1.1",
32
- "@nseng-ai/foundation": "0.1.1",
33
- "@nseng-ai/flow": "0.1.1",
34
- "@nseng-ai/ns": "0.1.1",
35
- "@nseng-ai/objectives": "0.1.1",
36
- "@nseng-ai/plans": "0.1.1",
37
- "@nseng-ai/slots": "0.1.1",
29
+ "@nseng-ai/branch-context": "0.1.2",
30
+ "@nseng-ai/capability-kit": "0.1.2",
31
+ "@nseng-ai/clinkr": "0.1.2",
32
+ "@nseng-ai/foundation": "0.1.2",
33
+ "@nseng-ai/flow": "0.1.2",
34
+ "@nseng-ai/kernel": "0.1.2",
35
+ "@nseng-ai/objectives": "0.1.2",
36
+ "@nseng-ai/plans": "0.1.2",
37
+ "@nseng-ai/slots": "0.1.2",
38
38
  "zod": "^4.4.3"
39
39
  },
40
40
  "peerDependencies": {
@@ -4,15 +4,15 @@ import {
4
4
  sanitizeBranchName,
5
5
  trimBranchSlugToLength,
6
6
  } from "@nseng-ai/foundation/branch-slug";
7
+ import {
8
+ formatRawTextModelFailure,
9
+ generateRawTextWithModel,
10
+ } from "@nseng-ai/capability-kit/model-slug";
7
11
  import type { TextResult } from "@nseng-ai/foundation/primitives";
8
12
  import type { ExtensionAPI } from "@nseng-ai/capability-kit/cmux/types";
9
13
 
10
14
  export { finalizeBranchSlug, MAX_BRANCH_SLUG_LENGTH, sanitizeBranchName, trimBranchSlugToLength };
11
15
 
12
- const GPT_NANO_PROVIDER = "openai";
13
- const GPT_NANO_MODEL = "gpt-5.4-nano";
14
- const SLUG_PROMPT_TIMEOUT_MS = 60_000;
15
- const SUMMARY_PROMPT_TIMEOUT_MS = 60_000;
16
16
  const MAX_SLUG_INPUT_CHARS = 12_000;
17
17
  const MAX_SUMMARY_INPUT_CHARS = 16_000;
18
18
 
@@ -31,7 +31,7 @@ export async function generateBranchSlug(
31
31
  },
32
32
  ): Promise<TextResult> {
33
33
  const prompt = buildSlugPrompt(input);
34
- const result = await runGptNanoText(pi, cwd, prompt, SLUG_PROMPT_TIMEOUT_MS);
34
+ const result = await generateRawText(pi, cwd, prompt);
35
35
  if (!result.ok) {
36
36
  return {
37
37
  ok: false,
@@ -56,12 +56,7 @@ export async function summarizePlanWithGptNano(
56
56
  sourceLabel?: string;
57
57
  },
58
58
  ): Promise<TextResult> {
59
- const result = await runGptNanoText(
60
- pi,
61
- cwd,
62
- buildPlanSummaryPrompt(input),
63
- SUMMARY_PROMPT_TIMEOUT_MS,
64
- );
59
+ const result = await generateRawText(pi, cwd, buildPlanSummaryPrompt(input));
65
60
  if (!result.ok) {
66
61
  return { ok: false, message: `Could not summarize plan with GPT Nano: ${result.message}` };
67
62
  }
@@ -73,39 +68,20 @@ export async function summarizePlanWithGptNano(
73
68
  return { ok: true, text: summary };
74
69
  }
75
70
 
76
- async function runGptNanoText(
71
+ async function generateRawText(
77
72
  pi: BranchSlugRuntime,
78
73
  cwd: string,
79
74
  prompt: string,
80
- timeout: number,
81
75
  ): Promise<TextResult> {
82
- const result = await pi.exec("pi", buildGptNanoTextArgs(prompt), { cwd, timeout });
83
- if (result.code !== 0) {
84
- const details = result.stderr.trim() || result.stdout.trim();
85
- return { ok: false, message: details };
86
- }
87
- return { ok: true, text: result.stdout.trim() };
88
- }
89
-
90
- export function buildGptNanoTextArgs(prompt: string): string[] {
91
- return [
92
- "--provider",
93
- GPT_NANO_PROVIDER,
94
- "--model",
95
- GPT_NANO_MODEL,
96
- "--thinking",
97
- "low",
98
- "--no-session",
99
- "--no-extensions",
100
- "--no-skills",
101
- "--no-prompt-templates",
102
- "--no-context-files",
103
- "--no-tools",
104
- "--mode",
105
- "text",
106
- "--print",
76
+ const result = await generateRawTextWithModel({
77
+ cwd,
107
78
  prompt,
108
- ];
79
+ exec: (command, args, options) => pi.exec(command, args, options),
80
+ });
81
+ if (!result.ok) {
82
+ return { ok: false, message: formatRawTextModelFailure(result.failure) };
83
+ }
84
+ return { ok: true, text: result.evidence.rawOutput.trim() };
109
85
  }
110
86
 
111
87
  export function buildSlugPrompt(input: {
@@ -6,11 +6,23 @@ import {
6
6
  piExecApiToCommandExecApi,
7
7
  } from "@nseng-ai/foundation/command";
8
8
  import { firstNonEmptyLine } from "@nseng-ai/foundation/text-normalization";
9
+ import { isAbsolute, resolve } from "node:path";
10
+
9
11
  import {
10
12
  planLocalBranchRefreshFromWorktrees,
11
13
  type LocalBranchRefreshPlan,
12
14
  } from "@nseng-ai/capability-kit/git";
13
15
  import { runGraphiteCommand } from "@nseng-ai/capability-kit/graphite/branch";
16
+ import {
17
+ createGraphiteMetadataDbAccess,
18
+ GRAPHITE_BRANCH_METADATA_QUERY,
19
+ GRAPHITE_BRANCH_METADATA_SCHEMA_QUERY,
20
+ graphiteMetadataDbPath,
21
+ hasExpectedGraphiteBranchMetadataSchema,
22
+ parseGraphiteBranchMetadataRows,
23
+ resolveGraphiteTrunkBranchFromTopology,
24
+ type GraphiteMetadataDbAccess,
25
+ } from "@nseng-ai/capability-kit/graphite/metadata";
14
26
  import { optionalEntry } from "@nseng-ai/foundation/primitives";
15
27
 
16
28
  import { CCC_WORKSPACE_DISPATCH_FROM_TRUNK_COMMAND_NAME } from "./command-surfaces.ts";
@@ -30,12 +42,19 @@ const GIT_TRUNK_REFRESH_TIMEOUT_MS = 2 * 60 * 1000;
30
42
  const TRUNK_DISPATCH_CONTEXT_NOTE =
31
43
  "This branch was created from refreshed Graphite trunk and is intentionally unrelated to the caller's current stack.";
32
44
 
45
+ interface GraphiteTrunkResolutionContext {
46
+ pi: Pick<ExtensionAPI, "exec">;
47
+ cwd: string;
48
+ metadataDbAccess: GraphiteMetadataDbAccess;
49
+ }
50
+
33
51
  export async function handleCccSlotDispatchFromTrunk(options: {
34
52
  pi: Pick<ExtensionAPI, "exec" | "getThinkingLevel">;
35
53
  payloadOptions: ReturnType<typeof resolveDispatchPromptPayloadOptions>;
36
54
  args: string;
37
55
  ctx: CommandContext;
38
56
  slotClient?: SlotClient;
57
+ metadataDbAccess?: GraphiteMetadataDbAccess;
39
58
  notifyProgress: (message: string) => void;
40
59
  }): Promise<void> {
41
60
  const { pi, payloadOptions, args, ctx } = options;
@@ -45,13 +64,13 @@ export async function handleCccSlotDispatchFromTrunk(options: {
45
64
  return;
46
65
  }
47
66
 
48
- options.notifyProgress("Resolving Graphite trunk…");
49
67
  await ctx.waitForIdle();
50
68
  const branch = await createTrackedBranchFromTrunkForPrompt({
51
69
  pi,
52
70
  cwd: ctx.cwd,
53
71
  prompt,
54
72
  notify: options.notifyProgress,
73
+ ...optionalEntry("metadataDbAccess", options.metadataDbAccess),
55
74
  });
56
75
  if ("error" in branch) {
57
76
  ctx.ui.notify(branch.error, "error");
@@ -75,26 +94,17 @@ export async function createTrackedBranchFromTrunkForPrompt(options: {
75
94
  cwd: string;
76
95
  prompt: string;
77
96
  notify?: (message: string) => void;
97
+ metadataDbAccess?: GraphiteMetadataDbAccess;
78
98
  }): Promise<BranchCreateResult | { error: string }> {
79
99
  const { pi, cwd, prompt, notify } = options;
80
100
  notify?.("Resolving Graphite trunk…");
81
- const trunk = await runGraphiteCommand(execApiToCommandRunner(piExecApiToCommandExecApi(pi)), {
101
+ const trunk = await resolveGraphiteTrunkBranch({
102
+ pi,
82
103
  cwd,
83
- args: ["trunk", "--no-interactive"],
104
+ metadataDbAccess: options.metadataDbAccess ?? createGraphiteMetadataDbAccess(),
84
105
  });
85
- if (!commandSucceeded(trunk)) {
86
- return {
87
- error: formatCommandFailure(
88
- "Could not resolve Graphite trunk.",
89
- "gt trunk --no-interactive",
90
- trunk,
91
- ),
92
- };
93
- }
94
- const trunkBranch = firstNonEmptyLine(trunk.stdout);
95
- if (trunkBranch === undefined) {
96
- return { error: "gt trunk --no-interactive returned no branch." };
97
- }
106
+ if ("error" in trunk) return trunk;
107
+ const trunkBranch = trunk.branch;
98
108
 
99
109
  notify?.("Refreshing Graphite trunk…");
100
110
  const refresh = await refreshLocalTrunkBranch({ pi, cwd, trunkBranch });
@@ -124,6 +134,86 @@ export async function createTrackedBranchFromTrunkForPrompt(options: {
124
134
  });
125
135
  }
126
136
 
137
+ async function resolveGraphiteTrunkBranch(
138
+ context: GraphiteTrunkResolutionContext,
139
+ ): Promise<{ branch: string } | { error: string }> {
140
+ const { pi, cwd } = context;
141
+ const trunk = await runGraphiteCommand(execApiToCommandRunner(piExecApiToCommandExecApi(pi)), {
142
+ cwd,
143
+ args: ["trunk", "--no-interactive"],
144
+ });
145
+ if (commandSucceeded(trunk)) {
146
+ const trunkBranch = firstNonEmptyLine(trunk.stdout);
147
+ if (trunkBranch === undefined) {
148
+ return { error: "gt trunk --no-interactive returned no branch." };
149
+ }
150
+ return { branch: trunkBranch };
151
+ }
152
+ const trunkFailureMessage = formatCommandFailure(
153
+ "Could not resolve Graphite trunk.",
154
+ "gt trunk --no-interactive",
155
+ trunk,
156
+ );
157
+ if (!isDetachedHeadGraphiteTrunkFailure(trunk)) {
158
+ return { error: trunkFailureMessage };
159
+ }
160
+
161
+ const metadataTrunk = await resolveGraphiteTrunkBranchFromMetadata(context);
162
+ if ("branch" in metadataTrunk) return metadataTrunk;
163
+ return { error: [trunkFailureMessage, metadataTrunk.error].join("\n\n") };
164
+ }
165
+
166
+ function isDetachedHeadGraphiteTrunkFailure(result: { stdout: string; stderr: string }): boolean {
167
+ return `${result.stdout}\n${result.stderr}`.toLowerCase().includes("no current branch");
168
+ }
169
+
170
+ async function resolveGraphiteTrunkBranchFromMetadata(
171
+ context: GraphiteTrunkResolutionContext,
172
+ ): Promise<{ branch: string } | { error: string }> {
173
+ const commonDir = await runText(context.pi, context.cwd, "git", [
174
+ "rev-parse",
175
+ "--git-common-dir",
176
+ ]);
177
+ if (!commonDir.ok) {
178
+ return { error: `Could not inspect Graphite metadata fallback: ${commonDir.message}` };
179
+ }
180
+ const commonGitDir = commonDir.text.trim();
181
+ const dbPath = graphiteMetadataDbPath(
182
+ isAbsolute(commonGitDir) ? commonGitDir : resolve(context.cwd, commonGitDir),
183
+ );
184
+ if (!context.metadataDbAccess.exists(dbPath)) {
185
+ return {
186
+ error: `Graphite metadata fallback unavailable: metadata store not found at ${dbPath}`,
187
+ };
188
+ }
189
+ const schemaRows = context.metadataDbAccess.queryJson(
190
+ dbPath,
191
+ GRAPHITE_BRANCH_METADATA_SCHEMA_QUERY,
192
+ );
193
+ if (!schemaRows.ok) {
194
+ return { error: `Graphite metadata fallback failed: ${schemaRows.error.message}` };
195
+ }
196
+ if (!hasExpectedGraphiteBranchMetadataSchema(schemaRows.value)) {
197
+ return { error: "Graphite metadata fallback failed: branch_metadata schema mismatch." };
198
+ }
199
+ const metadataRows = context.metadataDbAccess.queryJson(dbPath, GRAPHITE_BRANCH_METADATA_QUERY);
200
+ if (!metadataRows.ok) {
201
+ return { error: `Graphite metadata fallback failed: ${metadataRows.error.message}` };
202
+ }
203
+ const parsed = parseGraphiteBranchMetadataRows(metadataRows.value);
204
+ if (parsed.type === "not_array") {
205
+ return { error: "Graphite metadata fallback failed: branch_metadata rows were not an array." };
206
+ }
207
+ const resolution = resolveGraphiteTrunkBranchFromTopology(parsed.topology);
208
+ if (resolution.type === "trunk") return { branch: resolution.branch };
209
+ if (resolution.type === "none") {
210
+ return { error: "Graphite metadata fallback found no trunk marker." };
211
+ }
212
+ return {
213
+ error: `Graphite metadata fallback found multiple trunk markers: ${resolution.branches.join(", ")}`,
214
+ };
215
+ }
216
+
127
217
  async function refreshLocalTrunkBranch(options: {
128
218
  pi: Pick<ExtensionAPI, "exec">;
129
219
  cwd: string;
@@ -2,6 +2,7 @@ import { DEFAULT_FAST_MODEL_REF, resolveModelRef } from "@nseng-ai/foundation/mo
2
2
  import {
3
3
  chooseActiveObjectiveSlug,
4
4
  objectiveSelectionContextFromCommandContext,
5
+ objectiveSelectionHostFromExec,
5
6
  type ObjectiveSelectionSpec,
6
7
  } from "@nseng-ai/objectives/api";
7
8
 
@@ -33,7 +34,7 @@ const SIDEBAR_MODEL_ENV = "NS_CCC_SIDEBAR_MODEL";
33
34
  const OBJECTIVE_SIDEBAR_SELECTION_SPEC = {
34
35
  statusKey: PI_SIDEBAR_STATUS_KEY,
35
36
  selectionTitle: "Select an active Objective for cmux sidebar",
36
- shouldCompactDiffSuggestion: true,
37
+ selectionMode: "compact-diff-suggestion",
37
38
  } satisfies ObjectiveSelectionSpec;
38
39
 
39
40
  interface RestoreState {
@@ -227,7 +228,7 @@ async function resolveObjectiveSidebarSlug(
227
228
  }
228
229
 
229
230
  return chooseActiveObjectiveSlug(
230
- pi,
231
+ objectiveSelectionHostFromExec(pi),
231
232
  objectiveSelectionContextFromCommandContext(ctx),
232
233
  OBJECTIVE_SIDEBAR_SELECTION_SPEC,
233
234
  );
@@ -465,7 +465,7 @@ function formatLaunchPreview(options: {
465
465
  options.launchCommand,
466
466
  );
467
467
  return [
468
- "cmux new-surface --type terminal --workspace <caller-workspace> --pane <caller-pane> --focus true",
468
+ "cmux new-surface --type terminal --workspace <caller-workspace> --pane <caller-pane> --focus false",
469
469
  `cmux rename-tab --title ${formatShellArg(options.branch)}`,
470
470
  `cmux send -- ${formatShellArg(`${surfaceLaunchCommand}\n`)}`,
471
471
  ].join("\n");
@@ -517,6 +517,7 @@ async function openBranchInCmuxSurface(options: {
517
517
  tabTitle,
518
518
  command: surfaceLaunchCommand,
519
519
  signal: undefined,
520
+ shouldFocus: false,
520
521
  onStage: (stage) => setStatus(ctx, config, formatSurfaceStageStatus(stage)),
521
522
  });
522
523
  if (launched.type === "failed") {
@@ -4,7 +4,7 @@ import {
4
4
  type CommandResult,
5
5
  type PreparedCheckpointMessage,
6
6
  } from "@nseng-ai/capability-kit/checkpoint-flow";
7
- import { createTextGenerator } from "@nseng-ai/ns/kernel/context";
7
+ import { createTextGenerator } from "@nseng-ai/kernel/context";
8
8
  import type { PendingWorktreeSnapshot } from "@nseng-ai/capability-kit/pending-worktree";
9
9
  import { selectCheckpointModelRef } from "@nseng-ai/capability-kit/text-generation";
10
10
 
@@ -2,8 +2,8 @@ import {
2
2
  createCliCommandIo,
3
3
  type CliCommandIoInput,
4
4
  type CliCommandIoOptions,
5
- } from "@nseng-ai/ns/kernel/command-io";
6
- import type { NsCommandIo } from "@nseng-ai/ns/kernel/sdk";
5
+ } from "@nseng-ai/kernel/command-io";
6
+ import type { NsCommandIo } from "@nseng-ai/kernel/sdk";
7
7
 
8
8
  /** Minimal CLI stream/callback surface that CCC standalone commands adapt to NsCommandIo. */
9
9
  export type CccCliCommandIoInput = CliCommandIoInput;
package/src/ns/cli.ts CHANGED
@@ -6,7 +6,6 @@ import { ClinkrGroup, failure, negative, ok, type ClinkrExit } from "@nseng-ai/c
6
6
  import { defineCli, type CliEntrypointDeps } from "@nseng-ai/foundation/cli-runtime";
7
7
  import { NodeCommandExecApi } from "@nseng-ai/foundation/exec";
8
8
  import type { CommandExecApi } from "@nseng-ai/foundation/command";
9
-
10
9
  import {
11
10
  commitAutobranchCheckpointMessage,
12
11
  prepareAutobranchCheckpointMessage,
@@ -143,11 +142,12 @@ async function handleAutobranch(
143
142
  ): Promise<ClinkrExit<AutobranchResult>> {
144
143
  const args: FlowAutobranchRequest = request.slug === undefined ? {} : { slug: request.slug };
145
144
  const autobranch = ctx.autobranch ?? {};
145
+ const exec = (command: string, commandArgs: string[], timeout: number) =>
146
+ ctx.commands.exec(command, commandArgs, { cwd: ctx.cwd, timeout, env: ctx.env });
146
147
  const result = await createFlowAutobranchCheckpointFlow({
147
148
  cwd: ctx.cwd,
148
149
  args,
149
- exec: (command, commandArgs, timeout) =>
150
- ctx.commands.exec(command, commandArgs, { cwd: ctx.cwd, timeout, env: ctx.env }),
150
+ exec,
151
151
  prepareCheckpointMessage:
152
152
  autobranch.prepareCheckpointMessage ??
153
153
  ((snapshot) => prepareAutobranchCheckpointMessage(snapshot, ctx.env)),
@@ -183,4 +183,6 @@ function renderAutobranch(result: AutobranchResult): string {
183
183
  return result.summary;
184
184
  }
185
185
 
186
+ export const VERSION = entry.version;
187
+
186
188
  await entry.runIfMain({ isImportMetaMain: import.meta.main });
package/src/ns/land.ts CHANGED
@@ -9,4 +9,9 @@ export type {
9
9
  PrintOutput,
10
10
  ValidPullRequestView,
11
11
  } from "@nseng-ai/flow/api";
12
- export { parsePullRequestView, registerLandCommand, runLandCli } from "@nseng-ai/flow/api";
12
+ export {
13
+ FLOW_LAND_PR_FIELDS,
14
+ parsePullRequestView,
15
+ registerLandCommand,
16
+ runLandCli,
17
+ } from "@nseng-ai/flow/api";
@@ -10,12 +10,17 @@ import {
10
10
  type DispatchPromptPayloadOptions,
11
11
  } from "../api/handlers.ts";
12
12
  import type { ExtensionAPI } from "@nseng-ai/capability-kit/cmux/types";
13
+ import type { GraphiteMetadataDbAccess } from "@nseng-ai/capability-kit/graphite/metadata";
13
14
 
14
15
  const COMMAND_NAME = CCC_WORKSPACE_DISPATCH_FROM_TRUNK_COMMAND_NAME;
15
16
 
17
+ export interface CccSlotDispatchFromTrunkOptions extends DispatchPromptPayloadOptions {
18
+ metadataDbAccess?: GraphiteMetadataDbAccess;
19
+ }
20
+
16
21
  export function registerCccSlotDispatchFromTrunkCommand(
17
22
  pi: ExtensionAPI,
18
- options: DispatchPromptPayloadOptions = {},
23
+ options: CccSlotDispatchFromTrunkOptions = {},
19
24
  ): void {
20
25
  const payloadOptions = resolveDispatchPromptPayloadOptions(options);
21
26
  registerCommandWithImmediateAck({
@@ -33,6 +38,7 @@ export function registerCccSlotDispatchFromTrunkCommand(
33
38
  args,
34
39
  ctx,
35
40
  ...optionalEntry("slotClient", options.slotClient),
41
+ ...optionalEntry("metadataDbAccess", options.metadataDbAccess),
36
42
  notifyProgress,
37
43
  });
38
44
  },
@@ -50,8 +50,7 @@ function registerDispatchPlanCommand(
50
50
  host: pi,
51
51
  commandName: config.commandName,
52
52
  commandDefinition: {
53
- description:
54
- "Attach the latest session-saved plan to a new Graphite-tracked branch via branch-context and launch it in a new cmux workspace.",
53
+ description: `Attach the latest session-saved plan to a new Graphite-tracked branch via branch-context and launch it in a new cmux ${config.destination}.`,
55
54
  argumentHint: "[--dry-run] [--help]",
56
55
  handler: async (rawArgs, ctx) => {
57
56
  const notifyProgress = makeCommandProgressNotifier({ host: pi, ctx });