neuvikon 0.2.31 → 0.2.33

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 +37 -0
  2. package/dist/index.mjs +84 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -45,6 +45,10 @@ as being worked on, by whom, with which tool.
45
45
  | `neuvikon create "Title" [--ticket EWWQ-1]` | File something you noticed as a new ticket, same project |
46
46
  | `neuvikon create "Title" --sub [--ticket EWWQ-1]` | ...or as a sub-ticket of the ticket you're on |
47
47
  | `neuvikon create "Title" --parent NS-12` | ...or of a different ticket entirely |
48
+ | `neuvikon update --assignee Ogulcan [--ticket EWWQ-1]` | Change a ticket's fields: assignee, team, sprint, type, priority, labels, title, description |
49
+ | `neuvikon board [--ticket EWWQ-1]` | This project's columns, and where the ticket sits |
50
+ | `neuvikon move --column "Code Review" --comment "why"` | Move the ticket to a column, with a reason |
51
+ | `neuvikon comment --text "..."` | Leave a note without moving the ticket |
48
52
 
49
53
  `start` also accepts the ticket on stdin, and pulls the reference out of it:
50
54
 
@@ -99,6 +103,39 @@ itself, same restriction the app's own "+ sub-ticket" control has. Either way
99
103
  the ticket you were on gets an automated comment pointing at the new one, so
100
104
  nothing gets filed invisibly.
101
105
 
106
+ ## Changing a ticket
107
+
108
+ Anything the board's ticket dialog can change, `update` can change from a
109
+ terminal — so an agent can carry out "assign this to Ogulcan and bump it to
110
+ high" without anyone opening the app:
111
+
112
+ ```sh
113
+ npx neuvikon update --ticket EWWQ-1 --assignee Ogulcan --priority high
114
+ ```
115
+
116
+ One flag per field, any number at once: `--assignee`, `--team`, `--sprint`,
117
+ `--type` (`feature`/`bug`), `--priority` (`low`/`medium`/`high`/`urgent`),
118
+ `--labels` (comma-separated, replaces the whole set), `--title`,
119
+ `--description`.
120
+
121
+ People, teams and sprints are named the way you'd say them out loud — a first
122
+ name, an email, `"Sprint 1"` — and resolved on the server, which refuses an
123
+ ambiguous name instead of guessing at one. `none` clears one of those three,
124
+ an empty value clears labels or the description, and `--description -` takes
125
+ it from stdin:
126
+
127
+ ```sh
128
+ npx neuvikon update --ticket EWWQ-1 --assignee none --labels ""
129
+
130
+ npx neuvikon update --ticket EWWQ-1 --description - <<'EOF'
131
+ What this ticket actually turned out to be about.
132
+ EOF
133
+ ```
134
+
135
+ Being assigned a ticket this way notifies the assignee exactly as the board
136
+ does. Two things `update` won't do: move the ticket to another column (that's
137
+ `move`, which requires a reason) or delete it.
138
+
102
139
  ## Project pages
103
140
 
104
141
  An agent's most useful output often isn't the ticket, it's what it learned
package/dist/index.mjs CHANGED
@@ -373,7 +373,7 @@ async function comment(flags) {
373
373
  }
374
374
  //#endregion
375
375
  //#region src/commands/create.ts
376
- const TYPES = /* @__PURE__ */ new Set(["feature", "bug"]);
376
+ const TYPES$1 = /* @__PURE__ */ new Set(["feature", "bug"]);
377
377
  const PRIORITIES$1 = /* @__PURE__ */ new Set([
378
378
  "low",
379
379
  "medium",
@@ -419,12 +419,14 @@ async function create(flags, positional) {
419
419
  const title = flags.get("title") ?? positional[0];
420
420
  if (!title) fail("A ticket title is required.", `Pass --title "Invite emails aren't localized", or give it as the first argument.`);
421
421
  const type = flags.get("type");
422
- if (type && !TYPES.has(type)) fail(`Unknown type "${type}".`, "Expected feature or bug.");
422
+ if (type && !TYPES$1.has(type)) fail(`Unknown type "${type}".`, "Expected feature or bug.");
423
423
  const priority = flags.get("priority");
424
424
  if (priority && !PRIORITIES$1.has(priority)) fail(`Unknown priority "${priority}".`, "Expected low, medium, high, or urgent.");
425
425
  const description = (flags.get("description") || await readStdin()).trim() || void 0;
426
426
  const assignee = flags.get("assignee");
427
427
  const sprint = flags.get("sprint");
428
+ const labelsFlag = flags.get("labels");
429
+ const labels = labelsFlag ? labelsFlag.split(",").map((label) => label.trim()).filter(Boolean) : void 0;
428
430
  let result;
429
431
  try {
430
432
  result = await post(credentials.url, "/api/agent/tickets/create", {
@@ -434,6 +436,7 @@ async function create(flags, positional) {
434
436
  description,
435
437
  type,
436
438
  priority,
439
+ labels,
437
440
  parentRef,
438
441
  assignee,
439
442
  sprint
@@ -750,7 +753,7 @@ function parsePayload(raw) {
750
753
  * in front of the model at the one moment it's actually relevant instead of
751
754
  * buried in a rules file next to a dozen other instructions.
752
755
  */
753
- const MODEL_TRIAGE_CONTEXT = "A Neuvikon ticket just landed in this prompt. Before reading any file or running `start`: judge how difficult it actually is. If it's a small, mechanical change (button placement, a copy/label edit, a trivial styling tweak, a one-line config change), send `/model haiku` and `/effort low` now, as their own messages, before touching the ticket — there's no reason to spend a frontier model thinking hard about something this small. If it needs real design judgment, touches multiple systems, or you're unsure, stay on the current model and effort level instead.";
756
+ const MODEL_TRIAGE_CONTEXT = "A Neuvikon ticket just landed in this prompt. Before reading any file or running `start`: judge how difficult it actually is. If it's a small, mechanical change (button placement, a copy/label edit, a trivial styling tweak, a one-line config change), send `/model haiku` and `/effort low` now, as their own messages, before touching the ticket — there's no reason to spend a frontier model thinking hard about something this small. If it needs real design judgment, touches multiple systems, or you're unsure, stay on the current model and effort level instead. Either way, don't stop and wait after sending those commands — keep going immediately into the ticket itself in the same turn, exactly as if the model/effort switch were one more step in starting the ticket, not the end of your response.";
754
757
  function hookResponse(additionalContext) {
755
758
  return JSON.stringify({ hookSpecificOutput: {
756
759
  hookEventName: "UserPromptSubmit",
@@ -898,6 +901,10 @@ on its own, before you touch the ticket:
898
901
  - Your CLI doesn't expose a way to change model or effort mid-session: skip
899
902
  this and go straight to the ticket.
900
903
 
904
+ Don't stop and wait after sending a \`/model\`/\`/effort\` command — continue
905
+ immediately into the ticket in the same turn, as one more step in starting
906
+ it, not as the end of your response.
907
+
901
908
  Only after that's decided, run this before you start working on the ticket:
902
909
 
903
910
  npx neuvikon start --ticket EWWQ-1
@@ -957,6 +964,35 @@ you're already touching doesn't need a ticket, and not everything you notice
957
964
  belongs on the board. File it when it's something the team would actually
958
965
  want to see and decide on — not for every passing thought.
959
966
 
967
+ ## Changing a ticket
968
+
969
+ Every field the board's own ticket dialog can change, you can change from
970
+ here — when the user asks for it, or when what you learned working the ticket
971
+ makes its own fields wrong:
972
+
973
+ npx neuvikon update --ticket EWWQ-1 --assignee Ogulcan --priority high
974
+
975
+ One flag per field, as many at once as you like: \`--assignee\`, \`--team\`,
976
+ \`--sprint\`, \`--type\` (feature or bug), \`--priority\` (low, medium, high,
977
+ urgent), \`--labels\` (comma-separated, replaces the whole set), \`--title\`,
978
+ \`--description\`. People, teams and sprints are named the way a person says
979
+ them and resolved on the server, which refuses an ambiguous name instead of
980
+ guessing — so pass through what the user actually said rather than an id.
981
+
982
+ \`none\` clears a reference, an empty value clears free text, and
983
+ \`--description -\` reads stdin, for one too long to put on the command line:
984
+
985
+ npx neuvikon update --ticket EWWQ-1 --assignee none --labels ""
986
+
987
+ npx neuvikon update --ticket EWWQ-1 --description - <<'EOF'
988
+ What this ticket actually turned out to be about.
989
+ EOF
990
+
991
+ Two changes \`update\` deliberately won't make: moving the ticket to another
992
+ column (that's \`move\`, which makes you say why) and deleting it. And don't
993
+ retitle or rewrite a ticket just to tidy it up — do that when it's actually
994
+ wrong or the user asked, and say what you changed.
995
+
960
996
  ## Auditing the project on request
961
997
 
962
998
  Sometimes the ask isn't "work this ticket" but "find what's wrong with this
@@ -1915,14 +1951,17 @@ async function stop(flags) {
1915
1951
  }
1916
1952
  //#endregion
1917
1953
  //#region src/commands/update.ts
1954
+ const TYPES = /* @__PURE__ */ new Set(["feature", "bug"]);
1918
1955
  const PRIORITIES = /* @__PURE__ */ new Set([
1919
1956
  "low",
1920
1957
  "medium",
1921
1958
  "high",
1922
1959
  "urgent"
1923
1960
  ]);
1924
- /** What `--assignee none` / `--sprint none` means: clear the field. */
1961
+ /** What `--assignee none` / `--sprint none` / `--team none` means: clear the field. */
1925
1962
  const NONE = "none";
1963
+ /** `--description -` reads stdin, so a heredoc can carry a long one. */
1964
+ const STDIN = "-";
1926
1965
  function requireCredentials() {
1927
1966
  const credentials = readCredentials();
1928
1967
  if (!credentials) fail("Not logged in.", "Run `npx neuvikon login` first.");
@@ -1937,23 +1976,46 @@ function resolveTicketRef(flags) {
1937
1976
  function resolveClearable(flags, name) {
1938
1977
  const value = flags.get(name);
1939
1978
  if (value === void 0) return void 0;
1979
+ if (!value) fail(`--${name} needs a value.`, `Pass a name, or "${NONE}" to clear it.`);
1940
1980
  return value.toLowerCase() === NONE ? null : value;
1941
1981
  }
1982
+ /** A flag with a fixed set of values — caught here so the error names the choices. */
1983
+ function resolveChoice(flags, name, allowed, expected) {
1984
+ const value = flags.get(name);
1985
+ if (value === void 0) return void 0;
1986
+ if (!allowed.has(value)) fail(`Unknown ${name} "${value}".`, `Expected ${expected}.`);
1987
+ return value;
1988
+ }
1989
+ async function resolveDescription(flags) {
1990
+ const value = flags.get("description");
1991
+ if (value !== STDIN) return value;
1992
+ const piped = await readStdin();
1993
+ if (!piped.trim()) fail("Nothing arrived on stdin for --description.", `Pipe the text in, or pass --description "the text" directly.`);
1994
+ return piped;
1995
+ }
1942
1996
  /**
1943
- * Changes fields on a ticket that already exists, e.g.
1944
- * `npx neuvikon update --ticket NS-91 --assignee Ogulcan --sprint "Sprint 1"`.
1945
- * `--assignee none` / `--sprint none` clears that field instead of setting it.
1997
+ * Everything the board's ticket dialog can change, from a terminal, e.g.
1998
+ * `npx neuvikon update --ticket NS-91 --assignee Ogulcan --priority high`.
1999
+ * People, sprints and teams are named the way a person would say them and
2000
+ * resolved server-side; `none` clears one of those three, and an empty value
2001
+ * clears labels or the description.
2002
+ *
2003
+ * Which column the ticket sits in is `move`'s job — that one needs a reason.
1946
2004
  */
1947
2005
  async function update(flags) {
1948
2006
  const credentials = requireCredentials();
1949
2007
  const ticketRef = resolveTicketRef(flags);
1950
2008
  const assignee = resolveClearable(flags, "assignee");
1951
2009
  const sprint = resolveClearable(flags, "sprint");
1952
- const priority = flags.get("priority");
1953
- if (priority && !PRIORITIES.has(priority)) fail(`Unknown priority "${priority}".`, "Expected low, medium, high, or urgent.");
2010
+ const team = resolveClearable(flags, "team");
2011
+ const type = resolveChoice(flags, "type", TYPES, "feature or bug");
2012
+ const priority = resolveChoice(flags, "priority", PRIORITIES, "low, medium, high, or urgent");
1954
2013
  const labelsFlag = flags.get("labels");
1955
2014
  const labels = labelsFlag !== void 0 ? labelsFlag.split(",").map((label) => label.trim()).filter(Boolean) : void 0;
1956
- if (assignee === void 0 && sprint === void 0 && priority === void 0 && labels === void 0) fail("Nothing to update.", "Pass at least one of --assignee, --sprint, --priority, --labels.");
2015
+ const title = flags.get("title");
2016
+ if (title !== void 0 && !title.trim()) fail("A ticket can't have an empty title.", `Pass --title "What it should say instead".`);
2017
+ const description = await resolveDescription(flags);
2018
+ if (assignee === void 0 && sprint === void 0 && team === void 0 && type === void 0 && priority === void 0 && labels === void 0 && title === void 0 && description === void 0) fail("Nothing to update.", "Pass at least one of --assignee, --sprint, --team, --type, --priority, --labels, --title, --description.");
1957
2019
  let result;
1958
2020
  try {
1959
2021
  result = await post(credentials.url, "/api/agent/tickets/update", {
@@ -1961,8 +2023,12 @@ async function update(flags) {
1961
2023
  ticketRef,
1962
2024
  assignee,
1963
2025
  sprint,
2026
+ team,
2027
+ type,
1964
2028
  priority,
1965
- labels
2029
+ labels,
2030
+ title,
2031
+ description
1966
2032
  });
1967
2033
  } catch (error) {
1968
2034
  if (error instanceof NetworkError) fail("Could not reach Neuvikon.");
@@ -1998,11 +2064,13 @@ ${bold("Board")}
1998
2064
  File something you noticed as its own ticket, same project
1999
2065
  npx neuvikon create "Add French copy" --sub
2000
2066
  ...or as a sub-ticket (--sub for this one, --parent NS-12 for another)
2001
- npx neuvikon create "..." --assignee Ogulcan --sprint "Sprint 1"
2002
- File it already assigned and/or planned in a sprint
2003
- npx neuvikon update --assignee Ogulcan --sprint "Sprint 1" --priority high
2004
- Change assignee, sprint, priority, or labels on an existing ticket
2005
- ("none" clears --assignee/--sprint)
2067
+ npx neuvikon create "..." --assignee Ogulcan --sprint "Sprint 1" --labels "api,auth"
2068
+ File it already assigned, planned and labelled
2069
+ npx neuvikon update --assignee Ogulcan --priority high
2070
+ Change any field on an existing ticket: --assignee, --team,
2071
+ --sprint, --type, --priority, --labels, --title, --description
2072
+ ("none" clears assignee/team/sprint, "" clears labels/description,
2073
+ --description - reads stdin)
2006
2074
  All five accept --ticket EWWQ-1 instead of relying on a tracked session.
2007
2075
 
2008
2076
  ${bold("Project pages")}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neuvikon",
3
- "version": "0.2.31",
3
+ "version": "0.2.33",
4
4
  "description": "Tell Neuvikon which ticket your coding agent is working on.",
5
5
  "keywords": [
6
6
  "neuvikon",