neuvikon 0.2.23 → 0.2.24

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.
Files changed (3) hide show
  1. package/README.md +25 -0
  2. package/dist/index.mjs +123 -9
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -42,6 +42,9 @@ as being worked on, by whom, with which tool.
42
42
  | `neuvikon logout` | Revoke this machine's token and forget it |
43
43
  | `neuvikon pages [--ticket EWWQ-1]` | List the ticket's project pages, flattened to plain text |
44
44
  | `neuvikon pages update "Title" [--ticket EWWQ-1]` | Create or update a page by title, body on stdin (or `--text`) |
45
+ | `neuvikon create "Title" [--ticket EWWQ-1]` | File something you noticed as a new ticket, same project |
46
+ | `neuvikon create "Title" --sub [--ticket EWWQ-1]` | ...or as a sub-ticket of the ticket you're on |
47
+ | `neuvikon create "Title" --parent NS-12` | ...or of a different ticket entirely |
45
48
 
46
49
  `start` also accepts the ticket on stdin, and pulls the reference out of it:
47
50
 
@@ -74,6 +77,28 @@ always has. Without a detected agent, or on a platform/terminal this can't
74
77
  drive, it just prints the ticket ref and that command for you to paste by
75
78
  hand.
76
79
 
80
+ ## Filing what you notice along the way
81
+
82
+ Working a ticket turns up other things — a bug, a chunk of related work that
83
+ doesn't belong in the same diff. Rather than silently expanding scope or
84
+ silently dropping it, an agent can file it as its own ticket:
85
+
86
+ ```sh
87
+ npx neuvikon create "Invite emails aren't localized" --ticket EWWQ-1
88
+ ```
89
+
90
+ Or as a sub-ticket, if it's really part of finishing the one you're on:
91
+
92
+ ```sh
93
+ npx neuvikon create "Add French copy" --sub --ticket EWWQ-1
94
+ ```
95
+
96
+ `--parent NS-12` files it under a different ticket instead. Sub-tickets are
97
+ one level deep — a ticket that's already a sub-ticket can't take `--sub`
98
+ itself, same restriction the app's own "+ sub-ticket" control has. Either way
99
+ the ticket you were on gets an automated comment pointing at the new one, so
100
+ nothing gets filed invisibly.
101
+
77
102
  ## Project pages
78
103
 
79
104
  An agent's most useful output often isn't the ticket, it's what it learned
package/dist/index.mjs CHANGED
@@ -248,12 +248,12 @@ function fail(message, hint) {
248
248
  }
249
249
  //#endregion
250
250
  //#region src/commands/board.ts
251
- function requireCredentials$3() {
251
+ function requireCredentials$4() {
252
252
  const credentials = readCredentials();
253
253
  if (!credentials) fail("Not logged in.", "Run `npx neuvikon login` first.");
254
254
  return credentials;
255
255
  }
256
- function resolveTicketRef$3(flags) {
256
+ function resolveTicketRef$4(flags) {
257
257
  const ref = flags.get("ticket") ?? readSession()?.ticket;
258
258
  if (!ref) fail("No ticket to look up a board for.", "Pass --ticket EWWQ-1, or run this where `npx neuvikon start` is already tracking one.");
259
259
  return ref;
@@ -263,8 +263,8 @@ function resolveTicketRef$3(flags) {
263
263
  * a column name — boards vary per project, and some have no "done" at all.
264
264
  */
265
265
  async function board(flags) {
266
- const credentials = requireCredentials$3();
267
- const ticketRef = resolveTicketRef$3(flags);
266
+ const credentials = requireCredentials$4();
267
+ const ticketRef = resolveTicketRef$4(flags);
268
268
  let result;
269
269
  try {
270
270
  result = await post(credentials.url, "/api/agent/board", {
@@ -311,20 +311,20 @@ async function readStdin() {
311
311
  }
312
312
  //#endregion
313
313
  //#region src/commands/comment.ts
314
- function requireCredentials$2() {
314
+ function requireCredentials$3() {
315
315
  const credentials = readCredentials();
316
316
  if (!credentials) fail("Not logged in.", "Run `npx neuvikon login` first.");
317
317
  return credentials;
318
318
  }
319
- function resolveTicketRef$2(flags) {
319
+ function resolveTicketRef$3(flags) {
320
320
  const ref = flags.get("ticket") ?? readSession()?.ticket;
321
321
  if (!ref) fail("No ticket to comment on.", "Pass --ticket EWWQ-1, or run this where `npx neuvikon start` is already tracking one.");
322
322
  return ref;
323
323
  }
324
324
  /** A note on a ticket that doesn't move it — e.g. explaining a handoff mid-ticket. */
325
325
  async function comment(flags) {
326
- const credentials = requireCredentials$2();
327
- const ticketRef = resolveTicketRef$2(flags);
326
+ const credentials = requireCredentials$3();
327
+ const ticketRef = resolveTicketRef$3(flags);
328
328
  const text = (flags.get("text") || await readStdin()).trim();
329
329
  if (!text) fail("No comment text given.", "Pipe the comment in on stdin, or pass --text.");
330
330
  try {
@@ -344,6 +344,79 @@ async function comment(flags) {
344
344
  success(`Commented on ${ticketRef}`);
345
345
  }
346
346
  //#endregion
347
+ //#region src/commands/create.ts
348
+ const TYPES = /* @__PURE__ */ new Set(["feature", "bug"]);
349
+ const PRIORITIES = /* @__PURE__ */ new Set([
350
+ "low",
351
+ "medium",
352
+ "high",
353
+ "urgent"
354
+ ]);
355
+ function requireCredentials$2() {
356
+ const credentials = readCredentials();
357
+ if (!credentials) fail("Not logged in.", "Run `npx neuvikon login` first.");
358
+ return credentials;
359
+ }
360
+ function resolveTicketRef$2(flags) {
361
+ const ref = flags.get("ticket") ?? readSession()?.ticket;
362
+ if (!ref) fail("No ticket to file this against.", "Pass --ticket EWWQ-1, or run this where `npx neuvikon start` is already tracking one.");
363
+ return ref;
364
+ }
365
+ /**
366
+ * `--sub` means "a sub-ticket of the ticket I'm already on"; `--parent`
367
+ * names a different one. Not both — a new ticket has exactly one parent (or
368
+ * none), and naming it two ways at once is more likely a mistake than intent.
369
+ */
370
+ function resolveParentRef(flags, ticketRef) {
371
+ const parentFlag = flags.get("parent");
372
+ if (parentFlag && flags.has("sub")) fail("Pass --sub or --parent, not both.", `--sub already means "a sub-ticket of ${ticketRef}".`);
373
+ if (parentFlag) {
374
+ const [ref] = extractTicketRefs(parentFlag);
375
+ if (!ref) fail(`"${parentFlag}" is not a ticket reference.`, "Expected something like EWWQ-1.");
376
+ return ref;
377
+ }
378
+ return flags.has("sub") ? ticketRef : void 0;
379
+ }
380
+ /**
381
+ * An agent filing something it noticed that isn't the ticket it's on — a bug
382
+ * spotted mid-fix, a chunk of follow-up work worth tracking on its own.
383
+ * Without `--sub`/`--parent` it's a new standalone ticket in the same
384
+ * project; with one of them, a sub-ticket one level deep, same as the app's
385
+ * own "+ sub-ticket" control.
386
+ */
387
+ async function create(flags, positional) {
388
+ const credentials = requireCredentials$2();
389
+ const ticketRef = resolveTicketRef$2(flags);
390
+ const parentRef = resolveParentRef(flags, ticketRef);
391
+ const title = flags.get("title") ?? positional[0];
392
+ if (!title) fail("A ticket title is required.", `Pass --title "Invite emails aren't localized", or give it as the first argument.`);
393
+ const type = flags.get("type");
394
+ if (type && !TYPES.has(type)) fail(`Unknown type "${type}".`, "Expected feature or bug.");
395
+ const priority = flags.get("priority");
396
+ if (priority && !PRIORITIES.has(priority)) fail(`Unknown priority "${priority}".`, "Expected low, medium, high, or urgent.");
397
+ const description = (flags.get("description") || await readStdin()).trim() || void 0;
398
+ let result;
399
+ try {
400
+ result = await post(credentials.url, "/api/agent/tickets/create", {
401
+ token: credentials.token,
402
+ ticketRef,
403
+ title,
404
+ description,
405
+ type,
406
+ priority,
407
+ parentRef
408
+ });
409
+ } catch (error) {
410
+ if (error instanceof NetworkError) fail("Could not reach Neuvikon.");
411
+ if (error instanceof ApiError) {
412
+ if (error.status === 401) fail(error.message, "Run `npx neuvikon login` again.");
413
+ fail(error.message);
414
+ }
415
+ throw error;
416
+ }
417
+ success(result.parentRef ? `Filed ${bold(result.ref)} as a sub-ticket of ${bold(result.parentRef)}` : `Filed ${bold(result.ref)}`);
418
+ }
419
+ //#endregion
347
420
  //#region src/usage.ts
348
421
  /**
349
422
  * Sums token usage out of a JSONL agent transcript, starting from `offset`.
@@ -774,6 +847,42 @@ creating a duplicate, so reuse a title for anything you're extending.
774
847
  Reference another page by writing its title in double brackets, e.g.
775
848
  \`[[Auth Flow]]\` — it becomes a real link if that page already exists.
776
849
 
850
+ ## Filing work you notice along the way
851
+
852
+ Working a ticket often turns up something else — a bug you tripped over, a
853
+ piece of real work that doesn't belong in this diff. Don't silently fix it if
854
+ it's unrelated, and don't silently drop it either — file it:
855
+
856
+ npx neuvikon create "Session cookie missing Secure flag" --ticket EWWQ-1
857
+
858
+ Decide sub-ticket vs. standalone ticket by whether it's part of finishing
859
+ *this* ticket:
860
+
861
+ - Still part of this ticket — something that has to happen before it's really
862
+ done, e.g. "write tests for the new endpoint" — file it as a sub-ticket of
863
+ the one you're on:
864
+
865
+ npx neuvikon create "Add tests for the invite endpoint" --sub --ticket EWWQ-1
866
+
867
+ - Not part of it — a bug, tech debt, an idea that has nothing to do with
868
+ finishing this ticket — file it as its own ticket and get back to work.
869
+ Fixing it now just because you're already in the file is how one ticket
870
+ quietly becomes three:
871
+
872
+ npx neuvikon create "Invite emails aren't localized" --ticket EWWQ-1
873
+
874
+ \`--parent OTHER-4\` instead of \`--sub\` files it under a different ticket,
875
+ if that's genuinely where it belongs instead of standing alone.
876
+
877
+ A sub-ticket can't itself have sub-tickets — if the ticket you're on is
878
+ already one, \`--sub\` will fail; file a standalone ticket instead and say in
879
+ its description what it's related to.
880
+
881
+ This is judgment, not a rule to apply every turn: a one-line fix in a file
882
+ you're already touching doesn't need a ticket, and not everything you notice
883
+ belongs on the board. File it when it's something the team would actually
884
+ want to see and decide on — not for every passing thought.
885
+
777
886
  Before you change the ticket's status, look at what columns this project's
778
887
  board actually has — they vary per project, and there may be no column
779
888
  literally called "Done":
@@ -1707,7 +1816,11 @@ ${bold("Board")}
1707
1816
  Move the ticket to a specific column, with why
1708
1817
  npx neuvikon comment --text "Handing off, blocked on design"
1709
1818
  Leave a note without changing the column
1710
- All three accept --ticket EWWQ-1 instead of relying on a tracked session.
1819
+ npx neuvikon create "Invite emails aren't localized"
1820
+ File something you noticed as its own ticket, same project
1821
+ npx neuvikon create "Add French copy" --sub
1822
+ ...or as a sub-ticket (--sub for this one, --parent NS-12 for another)
1823
+ All four accept --ticket EWWQ-1 instead of relying on a tracked session.
1711
1824
 
1712
1825
  ${bold("Project pages")}
1713
1826
  npx neuvikon pages List the current ticket's project pages
@@ -1768,6 +1881,7 @@ async function main() {
1768
1881
  case "board": return board(flags);
1769
1882
  case "move": return move(flags);
1770
1883
  case "comment": return comment(flags);
1884
+ case "create": return create(flags, positional);
1771
1885
  case void 0:
1772
1886
  case "help":
1773
1887
  case "--help":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neuvikon",
3
- "version": "0.2.23",
3
+ "version": "0.2.24",
4
4
  "description": "Tell Neuvikon which ticket your coding agent is working on.",
5
5
  "keywords": [
6
6
  "neuvikon",