codiedev 0.7.11 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -25,6 +25,7 @@ const inbox_1 = require("./commands/inbox");
25
25
  const note_1 = require("./commands/note");
26
26
  const promote_1 = require("./commands/promote");
27
27
  const reverseTicket_1 = require("./commands/reverseTicket");
28
+ const createTicket_1 = require("./commands/createTicket");
28
29
  const doctor_1 = require("./commands/doctor");
29
30
  const search_1 = require("./commands/search");
30
31
  const library_1 = require("./commands/library");
@@ -32,6 +33,7 @@ const post_1 = require("./commands/post");
32
33
  const share_1 = require("./commands/share");
33
34
  const send_1 = require("./commands/send");
34
35
  const react_1 = require("./commands/react");
36
+ const version_1 = require("./version");
35
37
  const HELP = `
36
38
  CodieDev CLI
37
39
 
@@ -62,6 +64,15 @@ Artifacts:
62
64
  Draft lands in /portal/agentic-ticketing.
63
65
  codiedev reverse-ticket --base <ref> Override the base ref in branch mode
64
66
  (defaults to origin/main).
67
+ codiedev create-ticket [<prompt>] Forward-write a scoped ticket plan.
68
+ Claude investigates the codebase, asks
69
+ "single repo or multiple? where?", then
70
+ emits a plan with N+1 split. Lands in
71
+ /portal/research/<id>; push to Jira from there.
72
+ Beta — opt-in via betaCreateTicket.
73
+ codiedev create-ticket --submit <path|->
74
+ Internal: Claude calls this itself with
75
+ a plan JSON to submit. Use - for stdin.
65
76
 
66
77
  Messaging:
67
78
  codiedev ping <user> "<msg>" [--with <key>]
@@ -240,6 +251,27 @@ codiedev promote — promote an auto-extracted artifact to authored
240
251
 
241
252
  Find artifact-ids in the portal under Knowledge → Memory, or via
242
253
  the portal search.
254
+ `.trim(),
255
+ "create-ticket": `
256
+ codiedev create-ticket — forward-write a scoped ticket plan
257
+
258
+ Default path:
259
+ codiedev create-ticket "I want to add SSO with Google"
260
+
261
+ Bare mode (no --submit) prints skill instructions for Claude to follow.
262
+ Claude asks where your repos live, investigates them locally, and emits a
263
+ plan as JSON. The CLI then re-invokes itself with --submit to upload it.
264
+
265
+ The plan lands at /portal/research/<id> as a ready-state research session.
266
+ Single ticket renders as one ticket; multi-ticket renders as a folder/group.
267
+ Push to Jira from the portal.
268
+
269
+ Beta. Opt-in by setting userProfiles.betaCreateTicket = true on your row.
270
+
271
+ Examples:
272
+ codiedev create-ticket
273
+ codiedev create-ticket "I want to add Google SSO"
274
+ codiedev create-ticket --submit /tmp/plan.json # internal, called by Claude
243
275
  `.trim(),
244
276
  };
245
277
  const DOCS = `
@@ -379,7 +411,7 @@ async function main() {
379
411
  }
380
412
  }
381
413
  if (command === "version" || command === "--version" || command === "-v") {
382
- console.log("codiedev cli");
414
+ console.log(`codiedev v${version_1.CLI_VERSION}`);
383
415
  return;
384
416
  }
385
417
  try {
@@ -430,6 +462,10 @@ async function main() {
430
462
  case "reverseTicket":
431
463
  await (0, reverseTicket_1.runReverseTicket)(rest);
432
464
  return;
465
+ case "create-ticket":
466
+ case "createTicket":
467
+ await (0, createTicket_1.runCreateTicket)(rest);
468
+ return;
433
469
  case "search":
434
470
  await (0, search_1.runSearch)(rest);
435
471
  return;
@@ -0,0 +1,27 @@
1
+ import type { CodiedevConfig } from "../utils";
2
+ export declare function substitutePrompt(protocol: string, prompt: string | undefined): string;
3
+ interface ConnectedRepo {
4
+ repoId: string;
5
+ fullName: string;
6
+ }
7
+ export declare function validatePlanHasRepo(parsedPlan: any): string | null;
8
+ export declare function resolveRepoIds(fullNames: string[], repos: ConnectedRepo[]): string[];
9
+ export interface CreateTicketArgs {
10
+ mode: "bare" | "submit";
11
+ prompt?: string;
12
+ submitSource?: "stdin" | string;
13
+ }
14
+ export declare function parseCreateTicketArgs(args: string[]): CreateTicketArgs;
15
+ export declare function runCreateTicket(args: string[]): Promise<void>;
16
+ /**
17
+ * Bare-mode entrypoint. Fetches the protocol body from the backend so we can
18
+ * iterate on it without shipping a new CLI, substitutes the user's prompt,
19
+ * and writes the result to stdout for the agent to follow. Hard-errors on
20
+ * fetch failure rather than falling back to a stale local copy — the whole
21
+ * point of moving the protocol server-side is that the freshest version is
22
+ * the only version.
23
+ */
24
+ export declare function runBare(config: CodiedevConfig, prompt: string | undefined): Promise<void>;
25
+ export declare function runSubmit(config: CodiedevConfig, rawJson: string): Promise<void>;
26
+ export declare function readSubmitInput(source: "stdin" | string): string;
27
+ export {};
@@ -0,0 +1,198 @@
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.substitutePrompt = substitutePrompt;
37
+ exports.validatePlanHasRepo = validatePlanHasRepo;
38
+ exports.resolveRepoIds = resolveRepoIds;
39
+ exports.parseCreateTicketArgs = parseCreateTicketArgs;
40
+ exports.runCreateTicket = runCreateTicket;
41
+ exports.runBare = runBare;
42
+ exports.runSubmit = runSubmit;
43
+ exports.readSubmitInput = readSubmitInput;
44
+ const fs = __importStar(require("node:fs"));
45
+ const shared_1 = require("./shared");
46
+ const PROMPT_PLACEHOLDER = "{{USER_PROMPT}}";
47
+ const MISSING_PROMPT_FALLBACK = "(not yet provided — ask the user a single combined question that covers both the intent AND repo locations: \"What are you working on, and is this in a single repo or multiple? If multiple, give me the paths.\" Skip Step 1 since you've folded it into this question. Then proceed to Step 2.)";
48
+ function substitutePrompt(protocol, prompt) {
49
+ return protocol.replace(PROMPT_PLACEHOLDER, prompt && prompt.length > 0 ? prompt : MISSING_PROMPT_FALLBACK);
50
+ }
51
+ function validatePlanHasRepo(parsedPlan) {
52
+ const fullNames = Array.isArray(parsedPlan?.repoFullNames) ? parsedPlan.repoFullNames : [];
53
+ const explicitIds = Array.isArray(parsedPlan?.repoIds) ? parsedPlan.repoIds : [];
54
+ if (fullNames.length === 0 && explicitIds.length === 0) {
55
+ return "Plan must include at least one repo (repoFullNames: ['owner/name'] or repoIds: [...])";
56
+ }
57
+ return null;
58
+ }
59
+ function resolveRepoIds(fullNames, repos) {
60
+ const lookup = new Map();
61
+ for (const r of repos) {
62
+ lookup.set(r.fullName.toLowerCase(), r.repoId);
63
+ }
64
+ const ids = [];
65
+ for (const fn of fullNames) {
66
+ const id = lookup.get(fn.toLowerCase());
67
+ if (!id) {
68
+ throw new Error(`Repo "${fn}" is not connected to your CodieDev account. Run \`codiedev connect\` to refresh, or push the missing repo through Settings.`);
69
+ }
70
+ ids.push(id);
71
+ }
72
+ return ids;
73
+ }
74
+ function parseCreateTicketArgs(args) {
75
+ let mode = "bare";
76
+ let prompt;
77
+ let submitSource;
78
+ for (let i = 0; i < args.length; i++) {
79
+ const a = args[i];
80
+ if (a === "--submit" && i + 1 < args.length) {
81
+ mode = "submit";
82
+ const next = args[++i];
83
+ submitSource = next === "-" ? "stdin" : next;
84
+ }
85
+ else if (a.startsWith("--submit=")) {
86
+ mode = "submit";
87
+ const v = a.slice("--submit=".length);
88
+ submitSource = v === "-" ? "stdin" : v;
89
+ }
90
+ else if (!a.startsWith("--") && !prompt && mode === "bare") {
91
+ prompt = a;
92
+ }
93
+ }
94
+ return { mode, prompt, submitSource };
95
+ }
96
+ async function runCreateTicket(args) {
97
+ const parsed = parseCreateTicketArgs(args);
98
+ if (parsed.mode === "bare") {
99
+ const config = (0, shared_1.requireConfig)();
100
+ await runBare(config, parsed.prompt);
101
+ return;
102
+ }
103
+ // submit mode
104
+ if (!parsed.submitSource) {
105
+ console.error("--submit requires a path or '-' for stdin");
106
+ process.exit(1);
107
+ }
108
+ const config = (0, shared_1.requireConfig)();
109
+ let raw;
110
+ try {
111
+ raw = readSubmitInput(parsed.submitSource);
112
+ }
113
+ catch (err) {
114
+ console.error(`Failed to read plan: ${err.message}`);
115
+ process.exit(1);
116
+ }
117
+ // Translate fullNames → IDs before submitting
118
+ let parsedPlan;
119
+ try {
120
+ parsedPlan = JSON.parse(raw);
121
+ }
122
+ catch (err) {
123
+ console.error(`Plan JSON did not parse: ${err.message}`);
124
+ process.exit(1);
125
+ }
126
+ const validationError = validatePlanHasRepo(parsedPlan);
127
+ if (validationError) {
128
+ console.error(validationError);
129
+ process.exit(1);
130
+ }
131
+ const fullNames = Array.isArray(parsedPlan.repoFullNames) ? parsedPlan.repoFullNames : [];
132
+ const explicitIds = Array.isArray(parsedPlan.repoIds) ? parsedPlan.repoIds : [];
133
+ if (fullNames.length > 0 && explicitIds.length === 0) {
134
+ parsedPlan.repoIds = resolveRepoIds(fullNames, config.repos);
135
+ }
136
+ for (const t of parsedPlan.plan?.tickets ?? []) {
137
+ if (t.targetRepoFullName && !t.targetRepoId) {
138
+ const ids = resolveRepoIds([t.targetRepoFullName], config.repos);
139
+ t.targetRepoId = ids[0];
140
+ }
141
+ }
142
+ await runSubmit(config, JSON.stringify(parsedPlan));
143
+ }
144
+ /**
145
+ * Bare-mode entrypoint. Fetches the protocol body from the backend so we can
146
+ * iterate on it without shipping a new CLI, substitutes the user's prompt,
147
+ * and writes the result to stdout for the agent to follow. Hard-errors on
148
+ * fetch failure rather than falling back to a stale local copy — the whole
149
+ * point of moving the protocol server-side is that the freshest version is
150
+ * the only version.
151
+ */
152
+ async function runBare(config, prompt) {
153
+ let res;
154
+ try {
155
+ res = await (0, shared_1.apiRequest)("GET", "/api/cli/createTicketProtocol", { config });
156
+ }
157
+ catch (err) {
158
+ const status = err.status;
159
+ if (status === 403) {
160
+ console.error("create-ticket is in beta and not enabled for your account. Ask your CodieDev admin to flip betaCreateTicket on your profile.");
161
+ }
162
+ else {
163
+ console.error(`Failed to fetch create-ticket protocol: ${err.message}. Check your network connection and re-run.`);
164
+ }
165
+ process.exit(1);
166
+ }
167
+ process.stdout.write(substitutePrompt(res.protocol, prompt));
168
+ }
169
+ async function runSubmit(config, rawJson) {
170
+ let payload;
171
+ try {
172
+ payload = JSON.parse(rawJson);
173
+ }
174
+ catch (err) {
175
+ throw new Error(`Plan JSON did not parse — emit valid JSON matching the schema printed by \`codiedev create-ticket\`. Original error: ${err.message}`);
176
+ }
177
+ if (!payload.plan || !payload.prompt || !payload.repoIds) {
178
+ throw new Error("Plan JSON missing one of: plan, prompt, repoIds");
179
+ }
180
+ const res = await (0, shared_1.withTelemetry)(config, "codiedev_create_ticket", () => (0, shared_1.apiRequest)("POST", "/api/cli/createPlan", {
181
+ config,
182
+ body: payload,
183
+ }));
184
+ const portalHost = (process.env.CODIEDEV_PORTAL_URL || "https://codiedev.com").replace(/\/$/, "");
185
+ const portalLink = res.portalUrl.startsWith("http")
186
+ ? res.portalUrl
187
+ : `${portalHost}${res.portalUrl}`;
188
+ console.log(`✓ Created ${res.ticketCount} ticket(s).`);
189
+ console.log(` portal: ${portalLink}`);
190
+ console.log("");
191
+ console.log("Open in your portal to review and push to Jira.");
192
+ }
193
+ function readSubmitInput(source) {
194
+ if (source === "stdin") {
195
+ return fs.readFileSync(0, "utf8");
196
+ }
197
+ return fs.readFileSync(source, "utf8");
198
+ }
@@ -1,4 +1,12 @@
1
1
  import { CodiedevConfig } from "../utils";
2
+ /**
3
+ * Layer per-command env overrides on top of the on-disk config. Useful for
4
+ * ad-hoc testing — point a prod-connected CLI at a dev Convex deployment
5
+ * without re-running `codiedev connect`. Both CODIEDEV_URL and CODIEDEV_TOKEN
6
+ * must be set together (half-overrides would mismatch URL and auth realm).
7
+ * Other fields (companyId, repos[]) come from the file unchanged.
8
+ */
9
+ export declare function applyConfigEnvOverrides(config: CodiedevConfig): CodiedevConfig;
2
10
  /**
3
11
  * Require a connected CodieDev config. Exits the process with a helpful
4
12
  * message if the user hasn't run `codiedev connect` yet.
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.applyConfigEnvOverrides = applyConfigEnvOverrides;
36
37
  exports.requireConfig = requireConfig;
37
38
  exports.apiRequest = apiRequest;
38
39
  exports.recordAgentEvent = recordAgentEvent;
@@ -43,6 +44,21 @@ const https = __importStar(require("https"));
43
44
  const http = __importStar(require("http"));
44
45
  const utils_1 = require("../utils");
45
46
  const version_1 = require("../version");
47
+ /**
48
+ * Layer per-command env overrides on top of the on-disk config. Useful for
49
+ * ad-hoc testing — point a prod-connected CLI at a dev Convex deployment
50
+ * without re-running `codiedev connect`. Both CODIEDEV_URL and CODIEDEV_TOKEN
51
+ * must be set together (half-overrides would mismatch URL and auth realm).
52
+ * Other fields (companyId, repos[]) come from the file unchanged.
53
+ */
54
+ function applyConfigEnvOverrides(config) {
55
+ const url = process.env.CODIEDEV_URL;
56
+ const token = process.env.CODIEDEV_TOKEN;
57
+ if (url && token) {
58
+ return { ...config, backendUrl: url, token };
59
+ }
60
+ return config;
61
+ }
46
62
  /**
47
63
  * Require a connected CodieDev config. Exits the process with a helpful
48
64
  * message if the user hasn't run `codiedev connect` yet.
@@ -53,7 +69,7 @@ function requireConfig() {
53
69
  console.error("Not connected. Run `npx codiedev connect` first and enter your API token.");
54
70
  process.exit(1);
55
71
  }
56
- return config;
72
+ return applyConfigEnvOverrides(config);
57
73
  }
58
74
  /**
59
75
  * Perform an authenticated HTTP request against the CodieDev backend.
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Skill instructions printed to stdout when `codiedev create-ticket` is
3
+ * invoked without `--submit`. Claude (running the CLI via Bash) reads
4
+ * this content and follows the protocol: ask the dev about repos,
5
+ * investigate locally, emit a plan, call back into the CLI to submit.
6
+ *
7
+ * The scoping rules (#### TICKET QUALITY BAR onwards) are lifted verbatim
8
+ * from `modal/runner.py:_format_ticket_from_analysis` so the CLI path
9
+ * produces structurally-identical plans to the Modal path.
10
+ */
11
+ export declare const CREATE_TICKET_SKILL_INSTRUCTIONS = "\nYou are conducting a forward ticket-writing session for the user via codiedev.\nFollow this protocol exactly. Do not improvise the ticket structure.\n\n## Step 1 \u2014 Ask about repos\nAsk the user once:\n \"Is this work in a single repo or across multiple? If multiple, please tell me where each one is located on disk.\"\nWait for their reply. They will give you absolute paths like /Users/x/code/foo.\n\n## Step 2 \u2014 Investigate (read-only)\nFor each repo path the user gave you:\n - cd into the repo\n - Read the user's intent (their prompt, given below)\n - Find the primary files related to that intent. Use grep for class names,\n function names, enum values, constants, file names extracted from the prompt.\n - For each file found, report what it contains: function signatures, enum\n values, switch/case statements, existing patterns.\n - Grep for who imports each file (consumers, dependencies).\n - Check for existing tests related to those files.\n - Note migration patterns, naming conventions, barrel exports.\nDO NOT modify any files. This is read-only.\n\n## Step 2.5 \u2014 Ask if the prompt doesn't match the codebase\n\nBefore writing the plan, check: do the user's literal words appear in the codebase as named?\n\n- If they said \"delete X\" but only \"archive X\" / \"setArchived\" exists, ask one question quoting\n both sides: \"You said `delete`, but I see only `setArchived` \u2014 confirm the modal is for the\n archive button?\"\n- If they said \"the analytics dashboard\" but the named component (e.g. AnalyticsTab) is already\n reactive and the only useEffect-fetch lives elsewhere, ask: \"AnalyticsTab is already on\n useQuery; the only useEffect-fetch I found is in admin/page.tsx LangfuseCostsSection \u2014 is\n that the target?\"\n- If they named a feature/flag/component that doesn't exist at all, ask which of the closest\n matches they meant, listing each with a one-line reason it's plausible.\n\nDo NOT silently route to the closest match in these cases. Silently routing buries the mismatch\nin scope OUT and produces a ticket the user didn't ask for. One targeted question that quotes\nthe user's word AND the codebase's actual word is the right move.\n\nIf you also have a separate genuine ambiguity (two plausible places the change could go that\nboth match the user's words), bundle it into the same question \u2014 but don't fabricate ambiguity\nto hit a quota. If the words map to the codebase clearly, do not ask further questions \u2014\nproceed to Step 3.\n\n## Step 3 \u2014 Format the plan\n\nThe user's request defines WHAT to build. The investigation defines HOW to build it.\nYour job is to translate the user's exact request into actionable tickets using\nthe investigation as supporting evidence. The user chose their words carefully \u2014\nuse them.\n\nOutput a plan as one or more tickets. Each ticket becomes a single PR handled\nby a coding agent. The agent works alone and makes one PR per ticket.\n\nEACH TICKET = ONE MERGEABLE PR\nA ticket is ready when its PR can merge to main and the app compiles, renders,\nand passes tests with zero other tickets merged. This is the only rule that matters.\n\nHow to group work:\n- The investigation identified every file that needs to change. Your job is to\n package that into tickets, not to re-scope. Every file from the investigation\n appears in at least one ticket.\n- Each ticket becomes one PR. A PR should be focused enough that a reviewer\n understands it in one sitting and CI validates it fully.\n- Include every file needed for the change to be complete \u2014 don't leave wiring,\n migrations, or consumer updates for a follow-up.\n- If the work naturally spans independent modules, output multiple tickets ordered\n by dependency. Use the `dependsOn` field (array of zero-indexed ticket numbers\n this ticket depends on).\n- \"After this PR merges, the app:\" must describe a working state. If you can't,\n the ticket is incomplete \u2014 add what's missing.\n- Default to ONE ticket. Only split when ticket 2 could merge to main without\n ticket 1 and the app still compiles.\n\nQUALITY BAR \u2014 every ticket must:\n- Quantify the problem: count the calls, files, violations. \"6 manager.getRepository()\n calls\", not \"several calls\".\n- Name the codebase's own patterns: use the exact class names, method names, and\n conventions found in the investigation. \"injectable wrapper pattern with\n @InjectRepository\", not \"create a new class\".\n- Include specific file paths with line numbers from the investigation findings.\n- Say what stays the same: \"Keep standard CRUD in AceService\", \"do not export it\",\n \"no logic changes\".\n- Scope IN/OUT must reference concrete things in the codebase, not abstract categories.\n\nFor each ticket, fill these fields:\n - title: <80 chars, imperative mood\n - description: GIVEN/WHEN/THEN format, quantify the problem\n - technicalDetails: specific files with line numbers, the approach,\n patterns from the codebase to follow, what to keep unchanged\n - scope: IN: ... / OUT: ...\n - targetRepoId: leave empty for now \u2014 the CLI will fill this from the repo paths\n - targetRepoFullName: \"owner/name\" matching the git remote of the repo this\n ticket touches (e.g. \"ericlam1114/vm-demo\")\n - dependsOn: [zero-indexed ticket numbers] or [] if independent\n\n## Step 4 \u2014 Submit\nWhen the plan is ready, write it to a temp file as JSON matching this schema:\n\n{\n \"plan\": {\n \"summary\": \"<one sentence: what this plan delivers and how many tickets>\",\n \"tickets\": [\n {\n \"title\": \"...\",\n \"description\": \"...\",\n \"technicalDetails\": \"...\",\n \"scope\": \"...\",\n \"targetRepoFullName\": \"owner/name\",\n \"dependsOn\": []\n }\n ]\n },\n \"prompt\": \"<the user's original intent verbatim>\",\n \"repoFullNames\": [\"owner/name\", ...]\n}\n\nThen run:\n codiedev create-ticket --submit <path-to-temp-file.json>\n\nThe CLI will translate `targetRepoFullName` and `repoFullNames` into repo IDs,\nPOST the plan, and print the portal URL. Tell the user the URL when it returns.\n\n## The user's intent\n{{USER_PROMPT}}\n";
12
+ export declare function renderSkillInstructions(prompt: string | undefined): string;
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CREATE_TICKET_SKILL_INSTRUCTIONS = void 0;
4
+ exports.renderSkillInstructions = renderSkillInstructions;
5
+ /**
6
+ * Skill instructions printed to stdout when `codiedev create-ticket` is
7
+ * invoked without `--submit`. Claude (running the CLI via Bash) reads
8
+ * this content and follows the protocol: ask the dev about repos,
9
+ * investigate locally, emit a plan, call back into the CLI to submit.
10
+ *
11
+ * The scoping rules (#### TICKET QUALITY BAR onwards) are lifted verbatim
12
+ * from `modal/runner.py:_format_ticket_from_analysis` so the CLI path
13
+ * produces structurally-identical plans to the Modal path.
14
+ */
15
+ exports.CREATE_TICKET_SKILL_INSTRUCTIONS = `
16
+ You are conducting a forward ticket-writing session for the user via codiedev.
17
+ Follow this protocol exactly. Do not improvise the ticket structure.
18
+
19
+ ## Step 1 — Ask about repos
20
+ Ask the user once:
21
+ "Is this work in a single repo or across multiple? If multiple, please tell me where each one is located on disk."
22
+ Wait for their reply. They will give you absolute paths like /Users/x/code/foo.
23
+
24
+ ## Step 2 — Investigate (read-only)
25
+ For each repo path the user gave you:
26
+ - cd into the repo
27
+ - Read the user's intent (their prompt, given below)
28
+ - Find the primary files related to that intent. Use grep for class names,
29
+ function names, enum values, constants, file names extracted from the prompt.
30
+ - For each file found, report what it contains: function signatures, enum
31
+ values, switch/case statements, existing patterns.
32
+ - Grep for who imports each file (consumers, dependencies).
33
+ - Check for existing tests related to those files.
34
+ - Note migration patterns, naming conventions, barrel exports.
35
+ DO NOT modify any files. This is read-only.
36
+
37
+ ## Step 2.5 — Ask if the prompt doesn't match the codebase
38
+
39
+ Before writing the plan, check: do the user's literal words appear in the codebase as named?
40
+
41
+ - If they said "delete X" but only "archive X" / "setArchived" exists, ask one question quoting
42
+ both sides: "You said \`delete\`, but I see only \`setArchived\` — confirm the modal is for the
43
+ archive button?"
44
+ - If they said "the analytics dashboard" but the named component (e.g. AnalyticsTab) is already
45
+ reactive and the only useEffect-fetch lives elsewhere, ask: "AnalyticsTab is already on
46
+ useQuery; the only useEffect-fetch I found is in admin/page.tsx LangfuseCostsSection — is
47
+ that the target?"
48
+ - If they named a feature/flag/component that doesn't exist at all, ask which of the closest
49
+ matches they meant, listing each with a one-line reason it's plausible.
50
+
51
+ Do NOT silently route to the closest match in these cases. Silently routing buries the mismatch
52
+ in scope OUT and produces a ticket the user didn't ask for. One targeted question that quotes
53
+ the user's word AND the codebase's actual word is the right move.
54
+
55
+ If you also have a separate genuine ambiguity (two plausible places the change could go that
56
+ both match the user's words), bundle it into the same question — but don't fabricate ambiguity
57
+ to hit a quota. If the words map to the codebase clearly, do not ask further questions —
58
+ proceed to Step 3.
59
+
60
+ ## Step 3 — Format the plan
61
+
62
+ The user's request defines WHAT to build. The investigation defines HOW to build it.
63
+ Your job is to translate the user's exact request into actionable tickets using
64
+ the investigation as supporting evidence. The user chose their words carefully —
65
+ use them.
66
+
67
+ Output a plan as one or more tickets. Each ticket becomes a single PR handled
68
+ by a coding agent. The agent works alone and makes one PR per ticket.
69
+
70
+ EACH TICKET = ONE MERGEABLE PR
71
+ A ticket is ready when its PR can merge to main and the app compiles, renders,
72
+ and passes tests with zero other tickets merged. This is the only rule that matters.
73
+
74
+ How to group work:
75
+ - The investigation identified every file that needs to change. Your job is to
76
+ package that into tickets, not to re-scope. Every file from the investigation
77
+ appears in at least one ticket.
78
+ - Each ticket becomes one PR. A PR should be focused enough that a reviewer
79
+ understands it in one sitting and CI validates it fully.
80
+ - Include every file needed for the change to be complete — don't leave wiring,
81
+ migrations, or consumer updates for a follow-up.
82
+ - If the work naturally spans independent modules, output multiple tickets ordered
83
+ by dependency. Use the \`dependsOn\` field (array of zero-indexed ticket numbers
84
+ this ticket depends on).
85
+ - "After this PR merges, the app:" must describe a working state. If you can't,
86
+ the ticket is incomplete — add what's missing.
87
+ - Default to ONE ticket. Only split when ticket 2 could merge to main without
88
+ ticket 1 and the app still compiles.
89
+
90
+ QUALITY BAR — every ticket must:
91
+ - Quantify the problem: count the calls, files, violations. "6 manager.getRepository()
92
+ calls", not "several calls".
93
+ - Name the codebase's own patterns: use the exact class names, method names, and
94
+ conventions found in the investigation. "injectable wrapper pattern with
95
+ @InjectRepository", not "create a new class".
96
+ - Include specific file paths with line numbers from the investigation findings.
97
+ - Say what stays the same: "Keep standard CRUD in AceService", "do not export it",
98
+ "no logic changes".
99
+ - Scope IN/OUT must reference concrete things in the codebase, not abstract categories.
100
+
101
+ For each ticket, fill these fields:
102
+ - title: <80 chars, imperative mood
103
+ - description: GIVEN/WHEN/THEN format, quantify the problem
104
+ - technicalDetails: specific files with line numbers, the approach,
105
+ patterns from the codebase to follow, what to keep unchanged
106
+ - scope: IN: ... / OUT: ...
107
+ - targetRepoId: leave empty for now — the CLI will fill this from the repo paths
108
+ - targetRepoFullName: "owner/name" matching the git remote of the repo this
109
+ ticket touches (e.g. "ericlam1114/vm-demo")
110
+ - dependsOn: [zero-indexed ticket numbers] or [] if independent
111
+
112
+ ## Step 4 — Submit
113
+ When the plan is ready, write it to a temp file as JSON matching this schema:
114
+
115
+ {
116
+ "plan": {
117
+ "summary": "<one sentence: what this plan delivers and how many tickets>",
118
+ "tickets": [
119
+ {
120
+ "title": "...",
121
+ "description": "...",
122
+ "technicalDetails": "...",
123
+ "scope": "...",
124
+ "targetRepoFullName": "owner/name",
125
+ "dependsOn": []
126
+ }
127
+ ]
128
+ },
129
+ "prompt": "<the user's original intent verbatim>",
130
+ "repoFullNames": ["owner/name", ...]
131
+ }
132
+
133
+ Then run:
134
+ codiedev create-ticket --submit <path-to-temp-file.json>
135
+
136
+ The CLI will translate \`targetRepoFullName\` and \`repoFullNames\` into repo IDs,
137
+ POST the plan, and print the portal URL. Tell the user the URL when it returns.
138
+
139
+ ## The user's intent
140
+ {{USER_PROMPT}}
141
+ `;
142
+ function renderSkillInstructions(prompt) {
143
+ return exports.CREATE_TICKET_SKILL_INSTRUCTIONS.replace("{{USER_PROMPT}}", prompt && prompt.length > 0
144
+ ? prompt
145
+ : "(not yet provided — ask the user a single combined question that covers both the intent AND repo locations: \"What are you working on, and is this in a single repo or multiple? If multiple, give me the paths.\" Skip Step 1 since you've folded it into this question. Then proceed to Step 2.)");
146
+ }
package/dist/hook.js CHANGED
File without changes
package/dist/utils.js CHANGED
@@ -302,6 +302,7 @@ thought, use the \`codiedev\` CLI via Bash:**
302
302
  | "read that reply" / "mark that ping read" | \`codiedev read <ping-id>\` |
303
303
  | "note that X is a follow-up" / "remember X" | \`codiedev note "<text>"\` |
304
304
  | "promote the extracted spec" | \`codiedev promote <artifact-id>\` |
305
+ | "create a ticket" / "scope this into tickets" / "ticket this for me" | \`codiedev create-ticket [optional-inline-prompt]\` |
305
306
  | "post this to the team" / "publish to the feed" / "share with the team" | \`codiedev post --title "<t>" --body "<b>" [--intent <intent>] [--filename <key>] [--mention <name>]\` |
306
307
  | "share with a teammate" / "give someone access" (no notification) | \`codiedev share <key> <name> [--role read\\|edit]\` |
307
308
  | "send this to a teammate" / "ask someone to look at this" | \`codiedev send <key> <name> ["<msg>"]\` |
@@ -369,6 +370,11 @@ iterate rather than duplicating.
369
370
  notification). \`codiedev send\` both grants access AND pings the
370
371
  recipient. Use send when the user is actively looping someone in.
371
372
 
373
+ **\`create-ticket\` flow:** when invoked without \`--submit\`, the CLI prints
374
+ skill instructions you should follow. Step 1 is asking the user about repos.
375
+ Step 4 calls \`codiedev create-ticket --submit <path>\` with the plan JSON.
376
+ Tell the user the portal URL when the submit returns.
377
+
372
378
  **Errors:**
373
379
  - "not connected" → user needs to run \`npx codiedev connect\` with their
374
380
  API token from https://codiedev.com/portal/integrations/claude-code
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codiedev",
3
- "version": "0.7.11",
3
+ "version": "0.8.0",
4
4
  "description": "Connect Claude Code, Codex, Cursor, or VS Code Copilot to CodieDev for org-wide session capture and artifact collaboration",
5
5
  "bin": {
6
6
  "codiedev": "dist/cli.js",