@kitsy/coop 2.2.3 → 2.2.4

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 (2) hide show
  1. package/dist/index.js +86 -21
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -44,8 +44,6 @@ var BUILT_IN_NAMING_TOKENS = /* @__PURE__ */ new Set([
44
44
  "TYPE",
45
45
  "ENTITY",
46
46
  "TITLE",
47
- "TITLE16",
48
- "TITLE24",
49
47
  "TRACK",
50
48
  "STATUS",
51
49
  "TASK_TYPE",
@@ -57,6 +55,10 @@ var BUILT_IN_NAMING_TOKENS = /* @__PURE__ */ new Set([
57
55
  "NAME",
58
56
  "NAME_SLUG"
59
57
  ]);
58
+ function isBuiltInNamingToken(token) {
59
+ const upper = token.toUpperCase();
60
+ return BUILT_IN_NAMING_TOKENS.has(upper) || /^TITLE\d{1,2}$/.test(upper);
61
+ }
60
62
  var SEMANTIC_STOP_WORDS = /* @__PURE__ */ new Set([
61
63
  "A",
62
64
  "AN",
@@ -400,7 +402,7 @@ function semanticTitleToken(input2, maxLength = DEFAULT_TITLE_TOKEN_LENGTH) {
400
402
  const segments = [];
401
403
  let used = 0;
402
404
  for (const word of words) {
403
- const remaining = safeMaxLength - used - (segments.length > 0 ? 1 : 0);
405
+ const remaining = safeMaxLength - used;
404
406
  if (remaining < 2) {
405
407
  break;
406
408
  }
@@ -409,7 +411,7 @@ function semanticTitleToken(input2, maxLength = DEFAULT_TITLE_TOKEN_LENGTH) {
409
411
  continue;
410
412
  }
411
413
  segments.push(segment);
412
- used += segment.length + (segments.length > 1 ? 1 : 0);
414
+ used += segment.length;
413
415
  }
414
416
  if (segments.length > 0) {
415
417
  return segments.join("-");
@@ -466,6 +468,7 @@ function buildIdContext(root, config, context) {
466
468
  ENTITY: sanitizeTemplateValue(entityType, entityType),
467
469
  USER: normalizeIdPart(actor, "USER", 16),
468
470
  YYMMDD: shortDateToken(now),
471
+ TITLE_SOURCE: title,
469
472
  TITLE: semanticTitleToken(title, DEFAULT_TITLE_TOKEN_LENGTH),
470
473
  TITLE16: semanticTitleToken(title, 16),
471
474
  TITLE24: semanticTitleToken(title, 24),
@@ -505,7 +508,11 @@ function replaceTemplateToken(token, contextMap) {
505
508
  if (upper === "TYPE" || upper === "ENTITY") return contextMap.TYPE;
506
509
  if (upper === "TITLE") return contextMap.TITLE;
507
510
  if (/^TITLE\d+$/.test(upper)) {
508
- return contextMap[upper] || contextMap.TITLE;
511
+ const parsed = Number(upper.slice("TITLE".length));
512
+ if (Number.isInteger(parsed) && parsed > 0) {
513
+ return semanticTitleToken(contextMap.TITLE_SOURCE ?? "", parsed);
514
+ }
515
+ return contextMap.TITLE;
509
516
  }
510
517
  if (upper === "TRACK") return contextMap.TRACK;
511
518
  if (upper === "TASK_TYPE") return contextMap.TASK_TYPE;
@@ -539,6 +546,11 @@ function namingTemplatesForRoot(root) {
539
546
  function namingTokensForRoot(root) {
540
547
  return readCoopConfig(root).idTokens;
541
548
  }
549
+ function requiredCustomNamingTokens(root, entityType) {
550
+ const config = readCoopConfig(root);
551
+ const template = config.idNamingTemplates[entityType];
552
+ return extractTemplateTokens(template).filter((token) => !isBuiltInNamingToken(token)).map((token) => token.toLowerCase());
553
+ }
542
554
  function generateStableShortId(root, entityType, primaryId, existingShortIds = []) {
543
555
  const config = readCoopConfig(root);
544
556
  const digest = crypto.createHash("sha256").update(`${config.projectId}:${entityType}:${primaryId}`).digest("hex").toLowerCase();
@@ -594,7 +606,7 @@ function previewNamingTemplate(template, context, root = process.cwd()) {
594
606
  const usedTemplate = template.trim().length > 0 ? template : config.idNamingTemplates[context.entityType];
595
607
  const referencedTokens = extractTemplateTokens(usedTemplate);
596
608
  for (const token of referencedTokens) {
597
- if (!BUILT_IN_NAMING_TOKENS.has(token) && !config.idTokens[token.toLowerCase()]) {
609
+ if (!isBuiltInNamingToken(token) && !config.idTokens[token.toLowerCase()]) {
598
610
  throw new Error(`Naming template references unknown token <${token}>.`);
599
611
  }
600
612
  }
@@ -612,7 +624,7 @@ function generateConfiguredId(root, existingIds, context) {
612
624
  const template = config.idNamingTemplates[context.entityType];
613
625
  const referencedTokens = extractTemplateTokens(template);
614
626
  for (const token of referencedTokens) {
615
- if (BUILT_IN_NAMING_TOKENS.has(token)) {
627
+ if (isBuiltInNamingToken(token)) {
616
628
  continue;
617
629
  }
618
630
  if (!config.idTokens[token.toLowerCase()]) {
@@ -1037,7 +1049,7 @@ function registerAliasCommand(program) {
1037
1049
  const suffix = result.added.length > 0 ? result.added.join(", ") : "no new aliases";
1038
1050
  console.log(`Updated ${result.target.type} ${result.target.id}: ${suffix}`);
1039
1051
  });
1040
- alias.command("rm").description("Remove aliases from an item").argument("<idOrAlias>", "Target item id or alias").argument("<aliases...>", "Aliases to remove").action((idOrAlias, aliases) => {
1052
+ alias.command("rm").alias("remove").description("Remove aliases from an item").argument("<idOrAlias>", "Target item id or alias").argument("<aliases...>", "Aliases to remove").action((idOrAlias, aliases) => {
1041
1053
  const root = resolveRepoRoot();
1042
1054
  const result = removeAliases(root, idOrAlias, aliases);
1043
1055
  const suffix = result.removed.length > 0 ? result.removed.join(", ") : "no aliases removed";
@@ -3089,6 +3101,30 @@ function assertKnownDynamicFields(root, fields) {
3089
3101
  }
3090
3102
  }
3091
3103
  }
3104
+ async function collectRequiredNamingFields(root, entityType, fields) {
3105
+ const next = { ...fields };
3106
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
3107
+ return next;
3108
+ }
3109
+ const tokens = namingTokensForRoot(root);
3110
+ for (const token of requiredCustomNamingTokens(root, entityType)) {
3111
+ const existing = next[token]?.trim();
3112
+ if (existing) {
3113
+ continue;
3114
+ }
3115
+ const definition = tokens[token];
3116
+ if (definition && definition.values.length > 0) {
3117
+ const choice = await select(
3118
+ `Naming token ${token}`,
3119
+ definition.values.map((value) => ({ label: value, value }))
3120
+ );
3121
+ next[token] = choice;
3122
+ continue;
3123
+ }
3124
+ next[token] = await ask(`Naming token ${token}`);
3125
+ }
3126
+ return next;
3127
+ }
3092
3128
  function resolveIdeaFile2(root, idOrAlias) {
3093
3129
  const target = resolveReference(root, idOrAlias, "idea");
3094
3130
  return path8.join(root, ...target.file.split("/"));
@@ -3121,7 +3157,7 @@ function registerCreateCommand(program) {
3121
3157
  const root = resolveRepoRoot();
3122
3158
  const coop = ensureCoopInitialized(root);
3123
3159
  const interactive = Boolean(options.interactive);
3124
- const dynamicFields = extractDynamicTokenFlags(
3160
+ let dynamicFields = extractDynamicTokenFlags(
3125
3161
  ["create", "task"],
3126
3162
  [
3127
3163
  "id",
@@ -3144,6 +3180,7 @@ function registerCreateCommand(program) {
3144
3180
  ]
3145
3181
  );
3146
3182
  assertKnownDynamicFields(root, dynamicFields);
3183
+ dynamicFields = await collectRequiredNamingFields(root, "task", dynamicFields);
3147
3184
  if (options.fromFile?.trim() || options.stdin) {
3148
3185
  if (options.id || options.from || options.ai || options.title || titleArg || options.type || options.status || options.track || options.delivery || options.priority || options.body || (options.acceptance?.length ?? 0) > 0 || (options.testsRequired?.length ?? 0) > 0 || (options.authorityRef?.length ?? 0) > 0 || (options.derivedRef?.length ?? 0) > 0) {
3149
3186
  throw new Error("Cannot combine --from-file/--stdin with direct task field flags. Use one input mode.");
@@ -3319,11 +3356,12 @@ function registerCreateCommand(program) {
3319
3356
  const root = resolveRepoRoot();
3320
3357
  const coop = ensureCoopInitialized(root);
3321
3358
  const interactive = Boolean(options.interactive);
3322
- const dynamicFields = extractDynamicTokenFlags(
3359
+ let dynamicFields = extractDynamicTokenFlags(
3323
3360
  ["create", "idea"],
3324
3361
  ["id", "title", "author", "source", "status", "tags", "body", "from-file", "stdin", "interactive"]
3325
3362
  );
3326
3363
  assertKnownDynamicFields(root, dynamicFields);
3364
+ dynamicFields = await collectRequiredNamingFields(root, "idea", dynamicFields);
3327
3365
  if (options.fromFile?.trim() || options.stdin) {
3328
3366
  if (options.id || options.title || titleArg || options.author || options.source || options.status || options.tags || options.body) {
3329
3367
  throw new Error("Cannot combine --from-file/--stdin with direct idea field flags. Use one input mode.");
@@ -3380,11 +3418,12 @@ function registerCreateCommand(program) {
3380
3418
  const root = resolveRepoRoot();
3381
3419
  const coop = ensureCoopInitialized(root);
3382
3420
  const interactive = Boolean(options.interactive);
3383
- const dynamicFields = extractDynamicTokenFlags(
3421
+ let dynamicFields = extractDynamicTokenFlags(
3384
3422
  ["create", "track"],
3385
3423
  ["id", "name", "profiles", "max-wip", "allowed-types", "interactive"]
3386
3424
  );
3387
3425
  assertKnownDynamicFields(root, dynamicFields);
3426
+ dynamicFields = await collectRequiredNamingFields(root, "track", dynamicFields);
3388
3427
  const name = options.name?.trim() || nameArg?.trim() || await ask("Track name");
3389
3428
  if (!name) throw new Error("Track name is required.");
3390
3429
  const capacityProfiles = unique2(
@@ -3436,7 +3475,7 @@ function registerCreateCommand(program) {
3436
3475
  const root = resolveRepoRoot();
3437
3476
  const coop = ensureCoopInitialized(root);
3438
3477
  const interactive = Boolean(options.interactive);
3439
- const dynamicFields = extractDynamicTokenFlags(
3478
+ let dynamicFields = extractDynamicTokenFlags(
3440
3479
  ["create", "delivery"],
3441
3480
  [
3442
3481
  "id",
@@ -3455,6 +3494,7 @@ function registerCreateCommand(program) {
3455
3494
  ]
3456
3495
  );
3457
3496
  assertKnownDynamicFields(root, dynamicFields);
3497
+ dynamicFields = await collectRequiredNamingFields(root, "delivery", dynamicFields);
3458
3498
  const user = options.user?.trim() || defaultCoopAuthor(root);
3459
3499
  const config = readCoopConfig(root);
3460
3500
  const auth = load_auth_config2(config.raw);
@@ -3912,14 +3952,18 @@ var catalog = {
3912
3952
  { usage: "coop use track <id>", purpose: "Set the default working track for commands that can infer scope." },
3913
3953
  { usage: "coop use delivery <id>", purpose: "Set the default working delivery for commands that need delivery scope." },
3914
3954
  { usage: "coop use version <id>", purpose: "Set the default working version for promotion and prompt generation." },
3955
+ { usage: "coop use reset", purpose: "Clear the user-local working track, delivery, and version defaults." },
3915
3956
  { usage: "coop list tracks", purpose: "List valid named tracks before assigning or updating task track values." },
3916
3957
  { usage: "coop list deliveries", purpose: "List valid named deliveries before assigning or updating task delivery values." },
3917
3958
  { usage: "coop current", purpose: "Show the active project, working context, my active tasks, and the next ready task." },
3918
3959
  { usage: "coop naming", purpose: "Explain the effective per-entity naming rules, custom tokens, and examples." },
3919
3960
  { usage: 'coop naming preview "Natural-language COOP command recommender" --entity task', purpose: "Preview the generated ID before creating an item." },
3920
- { usage: "coop naming set task <TYPE>-<TITLE16>-<SEQ>", purpose: "Set one entity's naming template without editing config by hand." },
3961
+ { usage: "coop naming set task <TYPE>-<TITLE8>-<SEQ>", purpose: "Set one entity's naming template without editing config by hand; `TITLE##` supports arbitrary 1-2 digit caps." },
3962
+ { usage: "coop naming reset task", purpose: "Reset one entity's naming template back to the default." },
3921
3963
  { usage: "coop naming token create proj", purpose: "Create a custom naming token." },
3922
- { usage: "coop naming token value add proj UX", purpose: "Register an allowed value for a naming token." }
3964
+ { usage: "coop naming token remove proj", purpose: "Delete a custom naming token." },
3965
+ { usage: "coop naming token value add proj UX", purpose: "Register an allowed value for a naming token." },
3966
+ { usage: "coop naming token value remove proj UX", purpose: "Remove an allowed value from a naming token." }
3923
3967
  ]
3924
3968
  },
3925
3969
  {
@@ -3994,6 +4038,7 @@ var catalog = {
3994
4038
  { usage: "coop update PM-101 --priority p1 --add-fix-version v2", purpose: "Update task metadata without editing `.coop` files directly." },
3995
4039
  { usage: 'coop comment PM-101 --message "Needs API review"', purpose: "Append a comment to a task." },
3996
4040
  { usage: 'coop log-time PM-101 --hours 2 --kind worked --note "pairing"', purpose: "Append a planned or worked time log to a task." },
4041
+ { usage: "coop alias remove PM-101 PAY.UPI", purpose: "Remove an alias from a task or idea." },
3997
4042
  { usage: "coop plan delivery MVP", purpose: "Run delivery feasibility analysis." },
3998
4043
  { usage: "coop plan delivery MVP --monte-carlo --iterations 5000", purpose: "Run probabilistic delivery forecasting." },
3999
4044
  { usage: "coop view velocity", purpose: "Show historical throughput." },
@@ -4080,7 +4125,9 @@ function renderTopicPayload(topic) {
4080
4125
  "Use `coop naming` to inspect per-entity templates, custom tokens, and examples.",
4081
4126
  'Use `coop naming preview "<title>" --entity <entity>` before creating a new item if predictable IDs matter.',
4082
4127
  "Use `coop naming set <entity> <template>` to update one entity's naming rule.",
4083
- "Use `coop naming token create <token>` and `coop naming token value add <token> <value>` before passing custom token flags like `--proj UX`."
4128
+ "Built-in title tokens support `TITLE##` with a 1-2 digit cap, such as `TITLE18`, `TITLE12`, `TITLE8`, `TITLE08`, `TITLE4`, or `TITLE02`.",
4129
+ "Use `coop naming reset <entity>` to revert one entity back to the default naming template.",
4130
+ "Use `coop naming token create <token>`, `coop naming token remove <token>`, and `coop naming token value add|remove <token> <value>` before passing custom token flags like `--proj UX`."
4084
4131
  ]
4085
4132
  };
4086
4133
  }
@@ -6071,8 +6118,7 @@ function printNamingOverview() {
6071
6118
  console.log("Built-in tokens:");
6072
6119
  console.log(" <TYPE> entity type such as IDEA, TASK, DELIVERY");
6073
6120
  console.log(" <TITLE> semantic title token (defaults to TITLE16)");
6074
- console.log(" <TITLE16> semantic title token capped to 16 chars");
6075
- console.log(" <TITLE24> semantic title token capped to 24 chars");
6121
+ console.log(" <TITLE##> semantic title token capped to the numeric suffix, e.g. TITLE18, TITLE8, or TITLE08");
6076
6122
  console.log(" <TRACK> task track");
6077
6123
  console.log(" <NAME> entity name/title");
6078
6124
  console.log(" <NAME_SLUG> lower-case slug of the entity name");
@@ -6107,7 +6153,7 @@ function printNamingOverview() {
6107
6153
  }, root)}`);
6108
6154
  console.log("Try:");
6109
6155
  console.log(` coop naming preview "${sampleTitle}"`);
6110
- console.log(" coop naming set task <TYPE>-<TITLE24>-<SEQ>");
6156
+ console.log(" coop naming set task <TYPE>-<TITLE8>-<SEQ>");
6111
6157
  console.log(" coop naming token create proj");
6112
6158
  console.log(" coop naming token value add proj UX");
6113
6159
  }
@@ -6126,6 +6172,17 @@ function setEntityNamingTemplate(root, entity, template) {
6126
6172
  return next;
6127
6173
  });
6128
6174
  }
6175
+ function resetEntityNamingTemplate(root, entity) {
6176
+ writeNamingConfig(root, (config) => {
6177
+ const next = { ...config };
6178
+ const idRaw = typeof next.id === "object" && next.id !== null ? { ...next.id } : {};
6179
+ const namingRaw = typeof idRaw.naming === "object" && idRaw.naming !== null ? { ...idRaw.naming } : typeof idRaw.naming === "string" ? { task: idRaw.naming, idea: idRaw.naming } : {};
6180
+ namingRaw[entity] = DEFAULT_NAMING_TEMPLATES[entity];
6181
+ idRaw.naming = namingRaw;
6182
+ next.id = idRaw;
6183
+ return next;
6184
+ });
6185
+ }
6129
6186
  function ensureTokenRecord(config) {
6130
6187
  const next = { ...config };
6131
6188
  const idRaw = typeof next.id === "object" && next.id !== null ? { ...next.id } : {};
@@ -6187,6 +6244,12 @@ function registerNamingCommand(program) {
6187
6244
  setEntityNamingTemplate(root, assertNamingEntity(entity), template);
6188
6245
  console.log(`${assertNamingEntity(entity)}=${namingTemplatesForRoot(root)[assertNamingEntity(entity)]}`);
6189
6246
  });
6247
+ naming.command("reset").description("Reset the naming template for one entity type to the default").argument("<entity>", "Entity: task|idea|track|delivery|run").action((entity) => {
6248
+ const root = resolveRepoRoot();
6249
+ const resolved = assertNamingEntity(entity);
6250
+ resetEntityNamingTemplate(root, resolved);
6251
+ console.log(`${resolved}=${namingTemplatesForRoot(root)[resolved]}`);
6252
+ });
6190
6253
  const token = naming.command("token").description("Manage custom naming tokens");
6191
6254
  token.command("list").description("List naming tokens").action(() => {
6192
6255
  listTokens();
@@ -6223,7 +6286,7 @@ function registerNamingCommand(program) {
6223
6286
  });
6224
6287
  console.log(`Renamed naming token: ${normalizedToken} -> ${normalizedNext}`);
6225
6288
  });
6226
- token.command("delete").description("Delete a custom naming token").argument("<token>", "Token name").action((tokenName) => {
6289
+ token.command("delete").alias("remove").alias("rm").description("Delete a custom naming token").argument("<token>", "Token name").action((tokenName) => {
6227
6290
  const root = resolveRepoRoot();
6228
6291
  const normalizedToken = normalizeNamingTokenName(tokenName);
6229
6292
  writeNamingConfig(root, (config) => {
@@ -8731,16 +8794,18 @@ function renderBasicHelp() {
8731
8794
  "",
8732
8795
  "Day-to-day commands:",
8733
8796
  "- `coop current`: show working context, active work, and the next ready task",
8734
- "- `coop use track <id>` / `coop use delivery <id>`: set working scope defaults",
8797
+ "- `coop use track <id>` / `coop use delivery <id>` / `coop use reset`: manage working scope defaults",
8735
8798
  "- `coop list tracks` / `coop list deliveries`: inspect valid named values before assigning them",
8736
8799
  "- `coop naming`: inspect per-entity ID rules and naming tokens",
8737
- '- `coop naming preview "Title" --entity task`: preview the generated ID before creating an item',
8800
+ '- `coop naming preview "Title" --entity task`: preview the generated ID before creating an item; templates support `TITLE##` like `TITLE18`, `TITLE8`, or `TITLE08`',
8801
+ "- `coop naming reset task`: reset one entity's naming template to the default",
8738
8802
  "- `coop next task` or `coop pick task`: choose work from COOP",
8739
8803
  "- `coop show <id>`: inspect a task, idea, or delivery",
8740
8804
  "- `coop list tasks --track <id>`: browse scoped work",
8741
8805
  "- `coop update <id> --track <id> --delivery <id>`: update task metadata",
8742
8806
  '- `coop comment <id> --message "..."`: append a task comment',
8743
8807
  "- `coop log-time <id> --hours 2 --kind worked`: append time spent",
8808
+ "- `coop alias remove <id> <alias>`: remove a shorthand alias from a task or idea",
8744
8809
  "- `coop review task <id>` / `coop complete task <id>`: move work through lifecycle",
8745
8810
  "- `coop help-ai --initial-prompt --strict --repo C:/path/to/repo --delivery MVP --command coop.cmd`: hand off COOP context to an agent",
8746
8811
  "",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kitsy/coop",
3
3
  "description": "COOP command-line interface.",
4
- "version": "2.2.3",
4
+ "version": "2.2.4",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "publishConfig": {
@@ -39,9 +39,9 @@
39
39
  "chalk": "^5.6.2",
40
40
  "commander": "^14.0.0",
41
41
  "octokit": "^5.0.5",
42
- "@kitsy/coop-ai": "2.2.3",
43
- "@kitsy/coop-ui": "^2.2.3",
44
- "@kitsy/coop-core": "2.2.3"
42
+ "@kitsy/coop-ai": "2.2.4",
43
+ "@kitsy/coop-core": "2.2.4",
44
+ "@kitsy/coop-ui": "^2.2.4"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@types/node": "^24.12.0",