@kitsy/coop 2.1.0 → 2.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.
Files changed (3) hide show
  1. package/README.md +23 -2
  2. package/dist/index.js +722 -142
  3. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import fs18 from "fs";
5
- import path22 from "path";
5
+ import path24 from "path";
6
6
  import { fileURLToPath as fileURLToPath2 } from "url";
7
7
  import { Command } from "commander";
8
8
 
@@ -29,6 +29,28 @@ import {
29
29
  writeYamlFile
30
30
  } from "@kitsy/coop-core";
31
31
  var SEQ_MARKER = "COOPSEQTOKEN";
32
+ var DEFAULT_ID_NAMING_TEMPLATE = "<TYPE>-<TITLE16>-<SEQ>";
33
+ var DEFAULT_ARTIFACTS_DIR = "docs";
34
+ var DEFAULT_TITLE_TOKEN_LENGTH = 16;
35
+ var SEMANTIC_WORD_MAX = 4;
36
+ var SEMANTIC_STOP_WORDS = /* @__PURE__ */ new Set([
37
+ "A",
38
+ "AN",
39
+ "AND",
40
+ "AS",
41
+ "AT",
42
+ "BY",
43
+ "FOR",
44
+ "FROM",
45
+ "IN",
46
+ "INTO",
47
+ "OF",
48
+ "ON",
49
+ "OR",
50
+ "THE",
51
+ "TO",
52
+ "WITH"
53
+ ]);
32
54
  function resolveCoopHome() {
33
55
  const configured = process.env.COOP_HOME?.trim();
34
56
  if (configured) {
@@ -103,8 +125,9 @@ function readCoopConfig(root, projectId = resolveRequestedProject()) {
103
125
  ideaPrefix: "IDEA",
104
126
  taskPrefix: "PM",
105
127
  indexDataFormat: "yaml",
106
- idNamingTemplate: "<TYPE>-<USER>-<YYMMDD>-<RAND>",
128
+ idNamingTemplate: DEFAULT_ID_NAMING_TEMPLATE,
107
129
  idSeqPadding: 0,
130
+ artifactsDir: DEFAULT_ARTIFACTS_DIR,
108
131
  projectName: repoName || "COOP Workspace",
109
132
  projectId: repoName || "workspace",
110
133
  projectAliases: [],
@@ -126,15 +149,18 @@ function readCoopConfig(root, projectId = resolveRequestedProject()) {
126
149
  const indexDataFormat = indexDataRaw === "json" ? "json" : "yaml";
127
150
  const idRaw = typeof config.id === "object" && config.id !== null ? config.id : {};
128
151
  const idNamingTemplateRaw = idRaw.naming;
129
- const idNamingTemplate = typeof idNamingTemplateRaw === "string" && idNamingTemplateRaw.trim().length > 0 ? idNamingTemplateRaw.trim() : "<TYPE>-<USER>-<YYMMDD>-<RAND>";
152
+ const idNamingTemplate = typeof idNamingTemplateRaw === "string" && idNamingTemplateRaw.trim().length > 0 ? idNamingTemplateRaw.trim() : DEFAULT_ID_NAMING_TEMPLATE;
130
153
  const idSeqPaddingRaw = idRaw.seq_padding;
131
154
  const idSeqPadding = Number.isInteger(idSeqPaddingRaw) && Number(idSeqPaddingRaw) >= 0 ? Number(idSeqPaddingRaw) : 0;
155
+ const artifactsRaw = typeof config.artifacts === "object" && config.artifacts !== null ? config.artifacts : {};
156
+ const artifactsDir = typeof artifactsRaw.dir === "string" && artifactsRaw.dir.trim().length > 0 ? artifactsRaw.dir.trim() : DEFAULT_ARTIFACTS_DIR;
132
157
  return {
133
158
  ideaPrefix,
134
159
  taskPrefix,
135
160
  indexDataFormat,
136
161
  idNamingTemplate,
137
162
  idSeqPadding,
163
+ artifactsDir,
138
164
  projectName: projectName || "COOP Workspace",
139
165
  projectId: resolvedProjectId || "workspace",
140
166
  projectAliases,
@@ -249,6 +275,59 @@ function sanitizeTemplateValue(input3, fallback = "X") {
249
275
  const normalized = input3.toUpperCase().replace(/[^A-Z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
250
276
  return normalized || fallback;
251
277
  }
278
+ function sanitizeSemanticWord(input3) {
279
+ return input3.toUpperCase().replace(/[^A-Z0-9]+/g, "").trim();
280
+ }
281
+ function semanticWords(input3) {
282
+ const rawWords = input3.split(/[^a-zA-Z0-9]+/g).map(sanitizeSemanticWord).filter(Boolean);
283
+ if (rawWords.length <= 1) {
284
+ return rawWords;
285
+ }
286
+ const filtered = rawWords.filter((word) => !SEMANTIC_STOP_WORDS.has(word));
287
+ return filtered.length > 0 ? filtered : rawWords;
288
+ }
289
+ function semanticTitleToken(input3, maxLength = DEFAULT_TITLE_TOKEN_LENGTH) {
290
+ const safeMaxLength = Number.isFinite(maxLength) ? Math.max(4, Math.floor(maxLength)) : DEFAULT_TITLE_TOKEN_LENGTH;
291
+ const words = semanticWords(input3);
292
+ if (words.length === 0) {
293
+ return "UNTITLED";
294
+ }
295
+ if (words.length === 1) {
296
+ return words[0].slice(0, safeMaxLength) || "UNTITLED";
297
+ }
298
+ const segments = [];
299
+ let used = 0;
300
+ for (const word of words) {
301
+ const remaining = safeMaxLength - used - (segments.length > 0 ? 1 : 0);
302
+ if (remaining < 2) {
303
+ break;
304
+ }
305
+ const segment = word.slice(0, Math.min(SEMANTIC_WORD_MAX, remaining));
306
+ if (!segment) {
307
+ continue;
308
+ }
309
+ segments.push(segment);
310
+ used += segment.length + (segments.length > 1 ? 1 : 0);
311
+ }
312
+ if (segments.length > 0) {
313
+ return segments.join("-");
314
+ }
315
+ return words[0].slice(0, safeMaxLength) || "UNTITLED";
316
+ }
317
+ function namingTokenExamples(title = "Natural-language COOP command recommender") {
318
+ return {
319
+ "<TYPE>": "IDEA",
320
+ "<TITLE>": semanticTitleToken(title, DEFAULT_TITLE_TOKEN_LENGTH),
321
+ "<TITLE16>": semanticTitleToken(title, 16),
322
+ "<TITLE24>": semanticTitleToken(title, 24),
323
+ "<TRACK>": "MVP",
324
+ "<SEQ>": "1",
325
+ "<USER>": "PKVSI",
326
+ "<YYMMDD>": "260320",
327
+ "<RAND>": "AB12CD34",
328
+ "<PREFIX>": "PM"
329
+ };
330
+ }
252
331
  function sequenceForPattern(existingIds, prefix, suffix) {
253
332
  const escapedPrefix = prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
254
333
  const escapedSuffix = suffix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -284,7 +363,9 @@ function buildIdContext(root, config, context) {
284
363
  ENTITY: sanitizeTemplateValue(entityType, entityType),
285
364
  USER: normalizeIdPart(actor, "USER", 16),
286
365
  YYMMDD: shortDateToken(now),
287
- TITLE: sanitizeTemplateValue(title, "UNTITLED"),
366
+ TITLE: semanticTitleToken(title, DEFAULT_TITLE_TOKEN_LENGTH),
367
+ TITLE16: semanticTitleToken(title, 16),
368
+ TITLE24: semanticTitleToken(title, 24),
288
369
  TRACK: sanitizeTemplateValue(track || "UNASSIGNED", "UNASSIGNED"),
289
370
  STATUS: sanitizeTemplateValue(status || "TODO", "TODO"),
290
371
  TASK_TYPE: sanitizeTemplateValue(taskType || "FEATURE", "FEATURE"),
@@ -307,6 +388,9 @@ function replaceTemplateToken(token, contextMap) {
307
388
  if (upper === "USER") return contextMap.USER;
308
389
  if (upper === "TYPE" || upper === "ENTITY") return contextMap.TYPE;
309
390
  if (upper === "TITLE") return contextMap.TITLE;
391
+ if (/^TITLE\d+$/.test(upper)) {
392
+ return contextMap[upper] || contextMap.TITLE;
393
+ }
310
394
  if (upper === "TRACK") return contextMap.TRACK;
311
395
  if (upper === "TASK_TYPE") return contextMap.TASK_TYPE;
312
396
  if (upper === "STATUS") return contextMap.STATUS;
@@ -316,7 +400,7 @@ function replaceTemplateToken(token, contextMap) {
316
400
  return sanitizeTemplateValue(upper);
317
401
  }
318
402
  function renderNamingTemplate(template, contextMap) {
319
- const normalizedTemplate = template.trim().length > 0 ? template : "<TYPE>-<USER>-<YYMMDD>-<RAND>";
403
+ const normalizedTemplate = template.trim().length > 0 ? template : DEFAULT_ID_NAMING_TEMPLATE;
320
404
  const replaced = normalizedTemplate.replace(/<([^>]+)>/g, (_, token) => replaceTemplateToken(token, contextMap));
321
405
  return sanitizeTemplateValue(replaced, "COOP-ID");
322
406
  }
@@ -324,6 +408,17 @@ function defaultCoopAuthor(root) {
324
408
  const actor = inferActor(root);
325
409
  return actor.trim() || "unknown";
326
410
  }
411
+ function previewNamingTemplate(template, context, root = process.cwd()) {
412
+ const config = readCoopConfig(root);
413
+ const contextMap = buildIdContext(root, config, context);
414
+ const rendered = renderNamingTemplate(template, {
415
+ ...contextMap,
416
+ RAND: "AB12CD34",
417
+ YYMMDD: "260320",
418
+ USER: "PKVSI"
419
+ });
420
+ return rendered.replace(SEQ_MARKER, "1");
421
+ }
327
422
  function generateConfiguredId(root, existingIds, context) {
328
423
  const config = readCoopConfig(root);
329
424
  const contextMap = buildIdContext(root, config, context);
@@ -1550,6 +1645,7 @@ var INDEX_DATA_KEY = "index.data";
1550
1645
  var ID_NAMING_KEY = "id.naming";
1551
1646
  var AI_PROVIDER_KEY = "ai.provider";
1552
1647
  var AI_MODEL_KEY = "ai.model";
1648
+ var ARTIFACTS_DIR_KEY = "artifacts.dir";
1553
1649
  var PROJECT_NAME_KEY = "project.name";
1554
1650
  var PROJECT_ID_KEY = "project.id";
1555
1651
  var PROJECT_ALIASES_KEY = "project.aliases";
@@ -1558,6 +1654,7 @@ var SUPPORTED_KEYS = [
1558
1654
  ID_NAMING_KEY,
1559
1655
  AI_PROVIDER_KEY,
1560
1656
  AI_MODEL_KEY,
1657
+ ARTIFACTS_DIR_KEY,
1561
1658
  PROJECT_NAME_KEY,
1562
1659
  PROJECT_ID_KEY,
1563
1660
  PROJECT_ALIASES_KEY
@@ -1585,6 +1682,9 @@ function readAiModelValue(root) {
1585
1682
  function readProjectNameValue(root) {
1586
1683
  return readCoopConfig(root).projectName;
1587
1684
  }
1685
+ function readArtifactsDirValue(root) {
1686
+ return readCoopConfig(root).artifactsDir;
1687
+ }
1588
1688
  function readProjectIdValue(root) {
1589
1689
  return readCoopConfig(root).projectId;
1590
1690
  }
@@ -1653,6 +1753,18 @@ function writeProjectNameValue(root, value) {
1653
1753
  next.project = projectRaw;
1654
1754
  writeCoopConfig(root, next);
1655
1755
  }
1756
+ function writeArtifactsDirValue(root, value) {
1757
+ const nextValue = value.trim().replace(/\\/g, "/").replace(/\/+/g, "/").replace(/^\/+|\/+$/g, "");
1758
+ if (!nextValue) {
1759
+ throw new Error("Invalid value for artifacts.dir. Provide a non-empty relative path.");
1760
+ }
1761
+ const config = readCoopConfig(root).raw;
1762
+ const next = { ...config };
1763
+ const artifactsRaw = typeof next.artifacts === "object" && next.artifacts !== null ? { ...next.artifacts } : {};
1764
+ artifactsRaw.dir = nextValue;
1765
+ next.artifacts = artifactsRaw;
1766
+ writeCoopConfig(root, next);
1767
+ }
1656
1768
  function writeProjectIdValue(root, value) {
1657
1769
  const nextValue = value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
1658
1770
  if (!nextValue) {
@@ -1702,6 +1814,10 @@ function registerConfigCommand(program) {
1702
1814
  console.log(`${AI_PROVIDER_KEY}=${readAiProviderValue(root)}`);
1703
1815
  return;
1704
1816
  }
1817
+ if (keyPath === ARTIFACTS_DIR_KEY) {
1818
+ console.log(`${ARTIFACTS_DIR_KEY}=${readArtifactsDirValue(root)}`);
1819
+ return;
1820
+ }
1705
1821
  if (keyPath === PROJECT_NAME_KEY) {
1706
1822
  console.log(`${PROJECT_NAME_KEY}=${readProjectNameValue(root)}`);
1707
1823
  return;
@@ -1732,6 +1848,11 @@ function registerConfigCommand(program) {
1732
1848
  console.log(`${AI_PROVIDER_KEY}=${readAiProviderValue(root)}`);
1733
1849
  return;
1734
1850
  }
1851
+ if (keyPath === ARTIFACTS_DIR_KEY) {
1852
+ writeArtifactsDirValue(root, value);
1853
+ console.log(`${ARTIFACTS_DIR_KEY}=${readArtifactsDirValue(root)}`);
1854
+ return;
1855
+ }
1735
1856
  if (keyPath === PROJECT_NAME_KEY) {
1736
1857
  writeProjectNameValue(root, value);
1737
1858
  console.log(`${PROJECT_NAME_KEY}=${readProjectNameValue(root)}`);
@@ -2778,6 +2899,7 @@ function registerGraphCommand(program) {
2778
2899
  }
2779
2900
 
2780
2901
  // src/utils/ai-help.ts
2902
+ import path7 from "path";
2781
2903
  var catalog = {
2782
2904
  purpose: "COOP is a Git-native planning, backlog, execution, and orchestration CLI. It stores canonical data in .coop/projects/<project.id>/ and should be treated as the source of truth for work selection and lifecycle state.",
2783
2905
  selection_rules: [
@@ -2792,15 +2914,49 @@ var catalog = {
2792
2914
  "Multi-project workspaces require `--project <id>` or `coop project use <id>`.",
2793
2915
  "Canonical storage is under `.coop/projects/<project.id>/...`."
2794
2916
  ],
2917
+ allowed_lifecycle_commands: [
2918
+ "coop start task <id>",
2919
+ "coop review task <id>",
2920
+ "coop complete task <id>",
2921
+ "coop block task <id>",
2922
+ "coop unblock task <id>",
2923
+ "coop cancel task <id>",
2924
+ "coop reopen task <id>",
2925
+ "coop transition task <id> <status>"
2926
+ ],
2927
+ lifecycle_requirements: [
2928
+ "`coop start task <id>` moves a ready task into `in_progress`.",
2929
+ "`coop review task <id>` requires the task to already be `in_progress` and moves it to `in_review`.",
2930
+ "`coop complete task <id>` requires the task to already be `in_review` and moves it to `done`.",
2931
+ "Do not call `coop complete task <id>` directly from `in_progress` unless COOP explicitly adds that transition later."
2932
+ ],
2933
+ unsupported_command_warnings: [
2934
+ "Do not invent COOP commands such as `coop complete` when they are not present in `help-ai` output.",
2935
+ "If a desired lifecycle action is not represented by an allowed command, state that explicitly instead of paraphrasing.",
2936
+ "Quote exact COOP command names from `coop help-ai --format json` before proposing them."
2937
+ ],
2938
+ artifact_policy: {
2939
+ config_key: "artifacts.dir",
2940
+ default_dir: "docs",
2941
+ required_for: ["contract reviews", "spike findings", "audit notes", "planning artifacts"],
2942
+ guidance: [
2943
+ "Write agent-produced artifacts under the configured artifact directory.",
2944
+ "If `artifacts.dir` is unset, use `docs`.",
2945
+ "Do not invent ad hoc artifact paths outside the configured directory unless the user explicitly asks for a different location."
2946
+ ]
2947
+ },
2795
2948
  command_groups: [
2796
2949
  {
2797
2950
  name: "Workspace",
2798
2951
  description: "Initialize and select the active COOP project.",
2799
2952
  commands: [
2800
2953
  { usage: "coop init --yes", purpose: "Initialize a COOP workspace with repo-name defaults." },
2954
+ { usage: "coop init --naming <TYPE>-<TITLE16>-<SEQ>", purpose: "Initialize a workspace with an explicit ID naming template." },
2801
2955
  { usage: "coop project list", purpose: "List projects in the current workspace." },
2802
2956
  { usage: "coop project show", purpose: "Show the active project id, name, path, and layout." },
2803
- { usage: "coop project use <id>", purpose: "Switch the active project in a multi-project workspace." }
2957
+ { usage: "coop project use <id>", purpose: "Switch the active project in a multi-project workspace." },
2958
+ { usage: "coop naming", purpose: "Explain the current naming template, tokens, and examples." },
2959
+ { usage: 'coop naming preview "Natural-language COOP command recommender"', purpose: "Preview a semantic ID before creating an item." }
2804
2960
  ]
2805
2961
  },
2806
2962
  {
@@ -2838,7 +2994,13 @@ var catalog = {
2838
2994
  { usage: "coop next task", purpose: "Show the top ready task using the default track or full workspace context." },
2839
2995
  { usage: "coop graph next --delivery MVP", purpose: "Show the ready queue for a delivery with scores and blockers." },
2840
2996
  { usage: "coop pick task --delivery MVP --claim --actor dev1 --user lead-user", purpose: "Select the top ready task, optionally assign it, and move it to in_progress." },
2841
- { usage: "coop start task PM-101 --claim --actor dev1 --user lead-user", purpose: "Start a specific task or the top ready task if no id is provided." }
2997
+ { usage: "coop start task PM-101 --claim --actor dev1 --user lead-user", purpose: "Start a specific task or the top ready task if no id is provided." },
2998
+ { usage: "coop review task PM-101", purpose: "Move an in-progress task into in_review using a DX-friendly verb." },
2999
+ { usage: "coop complete task PM-101", purpose: "Move a task in review into done using a DX-friendly verb." },
3000
+ { usage: "coop block task PM-101", purpose: "Mark a task as blocked." },
3001
+ { usage: "coop unblock task PM-101", purpose: "Move a blocked task back to todo." },
3002
+ { usage: "coop cancel task PM-101", purpose: "Cancel a task." },
3003
+ { usage: "coop reopen task PM-101", purpose: "Move a canceled task back to todo." }
2842
3004
  ]
2843
3005
  },
2844
3006
  {
@@ -2858,6 +3020,13 @@ var catalog = {
2858
3020
  name: "Execute And Integrate",
2859
3021
  description: "Hand execution to an AI provider or code-agent CLI and expose COOP through MCP/API.",
2860
3022
  commands: [
3023
+ { usage: "coop help-ai --selection --format markdown", purpose: "Show the focused rules for deterministic task selection and workspace resolution." },
3024
+ { usage: "coop help-ai --state-transitions --format json", purpose: "Show exact lifecycle commands, prerequisites, and warnings against invented transitions." },
3025
+ { usage: "coop help-ai --artifacts --format markdown", purpose: "Show where contract-review, audit, and planning artifacts must be written." },
3026
+ { usage: "coop help-ai --post-execution --format markdown", purpose: "Show what an agent must do after finishing the selected task." },
3027
+ { usage: "coop help-ai --naming --format markdown", purpose: "Show naming guidance when IDs and naming templates matter." },
3028
+ { usage: "coop help-ai --initial-prompt --strict --repo C:/path/to/repo --delivery MVP --command coop.cmd", purpose: "Generate a strict agent bootstrap prompt for a repository and delivery." },
3029
+ { usage: "coop help-ai --initial-prompt --rigour balanced --repo C:/path/to/repo --delivery MVP --command coop.cmd", purpose: "Generate the recommended balanced agent bootstrap prompt for day-to-day implementation work." },
2861
3030
  { usage: "coop config ai.provider codex_cli", purpose: "Use the installed Codex CLI as the execution/refinement backend." },
2862
3031
  { usage: "coop config ai.provider claude_cli", purpose: "Use the installed Claude CLI as the execution/refinement backend." },
2863
3032
  { usage: "coop config ai.provider gemini_cli", purpose: "Use the installed Gemini CLI as the execution/refinement backend." },
@@ -2883,9 +3052,201 @@ var catalog = {
2883
3052
  execution_model: [
2884
3053
  "Agents or services may send drafts through files or stdin, but COOP owns canonical writes.",
2885
3054
  "Use `coop create ... --from-file|--stdin` and `coop apply draft` instead of editing `.coop` task or idea files directly.",
2886
- "Use `coop log --last --verbose` when command execution fails and the concise error points to a stack trace."
3055
+ "Use `coop log --last --verbose` when command execution fails and the concise error points to a stack trace.",
3056
+ "If a workflow depends on stable human-readable IDs, inspect `coop naming` or `coop config id.naming` before creating items."
3057
+ ],
3058
+ post_execution_rules: [
3059
+ "After completing the currently selected task, stop and wait instead of automatically selecting the next task.",
3060
+ "Do not auto-start a second task in the same run unless the user explicitly asks you to continue.",
3061
+ "If you changed task state in COOP, report the exact command used and the resulting new state before doing anything else."
2887
3062
  ]
2888
3063
  };
3064
+ function renderTopicPayload(topic) {
3065
+ if (topic === "state-transitions") {
3066
+ return {
3067
+ topic,
3068
+ allowed_lifecycle_commands: catalog.allowed_lifecycle_commands,
3069
+ lifecycle_requirements: catalog.lifecycle_requirements,
3070
+ warnings: catalog.unsupported_command_warnings
3071
+ };
3072
+ }
3073
+ if (topic === "artifacts") {
3074
+ return {
3075
+ topic,
3076
+ artifact_policy: catalog.artifact_policy
3077
+ };
3078
+ }
3079
+ if (topic === "post-execution") {
3080
+ return {
3081
+ topic,
3082
+ post_execution_rules: catalog.post_execution_rules
3083
+ };
3084
+ }
3085
+ if (topic === "selection") {
3086
+ return {
3087
+ topic,
3088
+ selection_rules: catalog.selection_rules,
3089
+ workspace_rules: catalog.workspace_rules
3090
+ };
3091
+ }
3092
+ return {
3093
+ topic,
3094
+ naming_guidance: [
3095
+ "Use `coop naming` to inspect the current naming template and token behavior.",
3096
+ 'Use `coop naming preview "<title>"` before creating a new idea, task, or delivery if predictable IDs matter.',
3097
+ "Use `coop config id.naming ...` to override the default semantic naming template."
3098
+ ]
3099
+ };
3100
+ }
3101
+ function renderAiHelpTopic(format, topic) {
3102
+ const payload = renderTopicPayload(topic);
3103
+ if (format === "json") {
3104
+ return `${JSON.stringify(payload, null, 2)}
3105
+ `;
3106
+ }
3107
+ const lines = [];
3108
+ const bullet = (value) => `- ${value}`;
3109
+ const title = topic.split("-").map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" ");
3110
+ lines.push(format === "markdown" ? `# COOP AI Help: ${title}` : `COOP AI Help: ${title}`, "");
3111
+ if (topic === "state-transitions") {
3112
+ lines.push(format === "markdown" ? "## Allowed Lifecycle Commands" : "Allowed Lifecycle Commands");
3113
+ for (const item of catalog.allowed_lifecycle_commands) {
3114
+ lines.push(bullet(`\`${item}\``));
3115
+ }
3116
+ lines.push("");
3117
+ lines.push(format === "markdown" ? "## Lifecycle Requirements" : "Lifecycle Requirements");
3118
+ for (const item of catalog.lifecycle_requirements) {
3119
+ lines.push(bullet(item));
3120
+ }
3121
+ lines.push("");
3122
+ lines.push(format === "markdown" ? "## Warnings" : "Warnings");
3123
+ for (const item of catalog.unsupported_command_warnings) {
3124
+ lines.push(bullet(item));
3125
+ }
3126
+ lines.push("");
3127
+ return `${lines.join("\n")}
3128
+ `;
3129
+ }
3130
+ if (topic === "artifacts") {
3131
+ lines.push(bullet(`Config key: \`${catalog.artifact_policy.config_key}\``));
3132
+ lines.push(bullet(`Default dir: \`${catalog.artifact_policy.default_dir}\``));
3133
+ lines.push(bullet(`Required for: ${catalog.artifact_policy.required_for.join(", ")}`));
3134
+ for (const item of catalog.artifact_policy.guidance) {
3135
+ lines.push(bullet(item));
3136
+ }
3137
+ lines.push("");
3138
+ return `${lines.join("\n")}
3139
+ `;
3140
+ }
3141
+ if (topic === "post-execution") {
3142
+ for (const item of catalog.post_execution_rules) {
3143
+ lines.push(bullet(item));
3144
+ }
3145
+ lines.push("");
3146
+ return `${lines.join("\n")}
3147
+ `;
3148
+ }
3149
+ if (topic === "selection") {
3150
+ lines.push(format === "markdown" ? "## Selection Rules" : "Selection Rules");
3151
+ for (const item of catalog.selection_rules) {
3152
+ lines.push(bullet(item));
3153
+ }
3154
+ lines.push("");
3155
+ lines.push(format === "markdown" ? "## Workspace Rules" : "Workspace Rules");
3156
+ for (const item of catalog.workspace_rules) {
3157
+ lines.push(bullet(item));
3158
+ }
3159
+ lines.push("");
3160
+ return `${lines.join("\n")}
3161
+ `;
3162
+ }
3163
+ for (const item of renderTopicPayload("naming").naming_guidance) {
3164
+ lines.push(bullet(item));
3165
+ }
3166
+ lines.push("");
3167
+ return `${lines.join("\n")}
3168
+ `;
3169
+ }
3170
+ function normalizeRepoPath(repoPath) {
3171
+ return repoPath ? path7.resolve(repoPath) : process.cwd();
3172
+ }
3173
+ function formatSelectionCommand(commandName, delivery, track) {
3174
+ if (delivery) {
3175
+ return `${commandName} graph next --delivery ${delivery}`;
3176
+ }
3177
+ if (track) {
3178
+ return `${commandName} next task --track ${track}`;
3179
+ }
3180
+ return `${commandName} next task`;
3181
+ }
3182
+ function renderInitialPrompt(options = {}) {
3183
+ const rigour = options.rigour ?? "balanced";
3184
+ const repoPath = normalizeRepoPath(options.repoPath);
3185
+ const commandName = options.commandName?.trim() || "coop";
3186
+ const artifactsDir = options.artifactsDir?.trim() || "docs";
3187
+ const selectionCommand = formatSelectionCommand(commandName, options.delivery, options.track);
3188
+ const planningCommand = options.delivery ? `${commandName} plan delivery ${options.delivery}` : `${commandName} status`;
3189
+ const scopeLine = options.delivery ? `- Active delivery: ${options.delivery}` : options.track ? `- Active track: ${options.track}` : "- Work scope: use the workspace default track or delivery context from COOP";
3190
+ const lines = [
3191
+ `You are working in:`,
3192
+ repoPath,
3193
+ "",
3194
+ "COOP is the source of truth for backlog, planning, and execution in this repository.",
3195
+ "",
3196
+ "Before doing anything else:",
3197
+ `1. Run \`${commandName} help-ai --format markdown\``,
3198
+ `2. Run \`${commandName} project show\``,
3199
+ `3. Run \`${selectionCommand}\``,
3200
+ `4. Run \`${planningCommand}\``,
3201
+ "",
3202
+ "Context:",
3203
+ scopeLine,
3204
+ `- Artifact directory: ${artifactsDir}`,
3205
+ "",
3206
+ "Rules:",
3207
+ "- Learn COOP capabilities from `coop help-ai` before proposing commands.",
3208
+ `- If you are unsure how to pick work, run \`${commandName} help-ai --selection --format markdown\`.`,
3209
+ `- If you are unsure about lifecycle changes, run \`${commandName} help-ai --state-transitions --format markdown\`.`,
3210
+ `- If you are unsure where artifacts should go, run \`${commandName} help-ai --artifacts --format markdown\`.`,
3211
+ `- If you are unsure whether to continue after a task, run \`${commandName} help-ai --post-execution --format markdown\`.`,
3212
+ "- Use only commands that actually exist in COOP. Do not invent command names.",
3213
+ "- Do not reprioritize work outside COOP unless the user explicitly overrides it.",
3214
+ "- Select only the first ready task from the COOP readiness output.",
3215
+ "- Inspect the selected task with `coop show task <id>` before implementation.",
3216
+ "- Do not create, edit, move, or normalize files directly inside `.coop/`; use COOP commands, MCP, or API surfaces so COOP remains the canonical writer.",
3217
+ "- Respect lifecycle prerequisites: use `coop review task <id>` before `coop complete task <id>`; do not complete directly from `in_progress`.",
3218
+ `- Write any contract-review, audit, or planning artifact under \`${artifactsDir}\` unless the user explicitly chooses another location.`
3219
+ ];
3220
+ if (rigour === "strict") {
3221
+ lines.push(
3222
+ "- Before mentioning a COOP command, verify the exact command name from `coop help-ai --format json`.",
3223
+ "- If a needed workflow is not supported by an exact COOP command, say that explicitly instead of inventing one.",
3224
+ "- Do not switch to another task unless COOP state changes or the user explicitly redirects you.",
3225
+ "- After executing or completing the selected task, stop and wait for user confirmation before picking another task."
3226
+ );
3227
+ } else if (rigour === "balanced") {
3228
+ lines.push(
3229
+ "- Prefer exact COOP commands from `coop help-ai --format json` when describing next actions.",
3230
+ "- If COOP lacks a named command for a desired action, state the limitation plainly.",
3231
+ "- After finishing the selected task, report the resulting COOP state before proposing follow-up work."
3232
+ );
3233
+ } else {
3234
+ lines.push("- Keep COOP as the authority for task selection and lifecycle state.");
3235
+ }
3236
+ lines.push(
3237
+ "",
3238
+ "Output format:",
3239
+ "- Active project",
3240
+ "- Selected task",
3241
+ "- Why selected",
3242
+ "- Task acceptance/tests/refs summary",
3243
+ "- Immediate implementation steps",
3244
+ "",
3245
+ "Do not start code changes until you have reported the selected task and plan."
3246
+ );
3247
+ return `${lines.join("\n")}
3248
+ `;
3249
+ }
2889
3250
  function renderAiHelp(format) {
2890
3251
  if (format === "json") {
2891
3252
  return `${JSON.stringify(catalog, null, 2)}
@@ -2906,6 +3267,29 @@ function renderAiHelp(format) {
2906
3267
  lines.push(bullet(rule));
2907
3268
  }
2908
3269
  lines.push("");
3270
+ lines.push(format === "markdown" ? "## Allowed Lifecycle Commands" : "Allowed Lifecycle Commands");
3271
+ for (const item of catalog.allowed_lifecycle_commands) {
3272
+ lines.push(bullet(`\`${item}\``));
3273
+ }
3274
+ lines.push("");
3275
+ lines.push(format === "markdown" ? "## Lifecycle Requirements" : "Lifecycle Requirements");
3276
+ for (const item of catalog.lifecycle_requirements) {
3277
+ lines.push(bullet(item));
3278
+ }
3279
+ lines.push("");
3280
+ lines.push(format === "markdown" ? "## Unsupported / Invented Command Warnings" : "Unsupported / Invented Command Warnings");
3281
+ for (const item of catalog.unsupported_command_warnings) {
3282
+ lines.push(bullet(item));
3283
+ }
3284
+ lines.push("");
3285
+ lines.push(format === "markdown" ? "## Artifact Policy" : "Artifact Policy");
3286
+ lines.push(`${format === "markdown" ? "- " : "- "}Config key: \`${catalog.artifact_policy.config_key}\``);
3287
+ lines.push(`${format === "markdown" ? "- " : "- "}Default dir: \`${catalog.artifact_policy.default_dir}\``);
3288
+ lines.push(`${format === "markdown" ? "- " : "- "}Required for: ${catalog.artifact_policy.required_for.join(", ")}`);
3289
+ for (const item of catalog.artifact_policy.guidance) {
3290
+ lines.push(bullet(item));
3291
+ }
3292
+ lines.push("");
2909
3293
  for (const group of catalog.command_groups) {
2910
3294
  lines.push(format === "markdown" ? `## ${group.name}` : group.name);
2911
3295
  lines.push(group.description);
@@ -2925,23 +3309,82 @@ function renderAiHelp(format) {
2925
3309
  lines.push(bullet(item));
2926
3310
  }
2927
3311
  lines.push("");
3312
+ lines.push(format === "markdown" ? "## Post-Execution Rules" : "Post-Execution Rules");
3313
+ for (const item of catalog.post_execution_rules) {
3314
+ lines.push(bullet(item));
3315
+ }
3316
+ lines.push("");
2928
3317
  return `${lines.join("\n")}
2929
3318
  `;
2930
3319
  }
3320
+ function renderAiInitialPrompt(options = {}) {
3321
+ return renderInitialPrompt(options);
3322
+ }
2931
3323
 
2932
3324
  // src/commands/help-ai.ts
2933
3325
  function registerHelpAiCommand(program) {
2934
- program.command("help-ai").description("Print AI-oriented COOP capability and usage help").option("--format <format>", "Output format: text | json | markdown", "text").action((options) => {
3326
+ program.command("help-ai").description("Print AI-oriented COOP capability and usage help").option("--format <format>", "Output format: text | json | markdown", "text").option("--initial-prompt", "Print an agent bootstrap prompt instead of the capability catalog").option("--topic <topic>", "Focused help topic: state-transitions | artifacts | post-execution | selection | naming").option("--state-transitions", "Focused help for lifecycle and state transition usage").option("--artifacts", "Focused help for artifact placement and policy").option("--post-execution", "Focused help for what to do after finishing the selected task").option("--selection", "Focused help for task selection and workspace resolution").option("--naming", "Focused help for naming IDs and naming templates").option("--repo <path>", "Repository path to embed in the initial prompt").option("--delivery <delivery>", "Delivery to embed in the initial prompt").option("--track <track>", "Track to embed in the initial prompt").option("--command <name>", "Command name to embed, e.g. coop or coop.cmd", "coop").option("--rigour <level>", "Prompt rigour: strict | balanced | light", "balanced").option("--strict", "Shortcut for --rigour strict").option("--balanced", "Shortcut for --rigour balanced").option("--light", "Shortcut for --rigour light").action((options) => {
2935
3327
  const format = options.format ?? "text";
2936
3328
  if (format !== "text" && format !== "json" && format !== "markdown") {
2937
3329
  throw new Error(`Unsupported help-ai format '${format}'. Expected text|json|markdown.`);
2938
3330
  }
2939
- console.log(renderAiHelp(format).trimEnd());
3331
+ const rigour = options.strict ? "strict" : options.light ? "light" : options.balanced ? "balanced" : options.rigour ?? "balanced";
3332
+ if (rigour !== "strict" && rigour !== "balanced" && rigour !== "light") {
3333
+ throw new Error(`Unsupported help-ai rigour '${rigour}'. Expected strict|balanced|light.`);
3334
+ }
3335
+ const topic = resolveHelpTopic(options);
3336
+ let artifactsDir = "docs";
3337
+ const repoRoot = options.repo ? resolveRepoRoot(options.repo) : resolveRepoRoot();
3338
+ try {
3339
+ ensureCoopInitialized(repoRoot);
3340
+ artifactsDir = readCoopConfig(repoRoot).artifactsDir;
3341
+ } catch {
3342
+ artifactsDir = "docs";
3343
+ }
3344
+ const output3 = options.initialPrompt ? renderAiInitialPrompt({
3345
+ repoPath: options.repo,
3346
+ delivery: options.delivery,
3347
+ track: options.track,
3348
+ commandName: options.command,
3349
+ rigour,
3350
+ artifactsDir
3351
+ }) : topic ? renderAiHelpTopic(format, topic) : renderAiHelp(format);
3352
+ console.log(output3.trimEnd());
2940
3353
  });
2941
3354
  }
3355
+ function resolveHelpTopic(options) {
3356
+ const requestedTopics = [];
3357
+ if (options.topic) {
3358
+ requestedTopics.push(options.topic);
3359
+ }
3360
+ if (options.stateTransitions) {
3361
+ requestedTopics.push("state-transitions");
3362
+ }
3363
+ if (options.artifacts) {
3364
+ requestedTopics.push("artifacts");
3365
+ }
3366
+ if (options.postExecution) {
3367
+ requestedTopics.push("post-execution");
3368
+ }
3369
+ if (options.selection) {
3370
+ requestedTopics.push("selection");
3371
+ }
3372
+ if (options.naming) {
3373
+ requestedTopics.push("naming");
3374
+ }
3375
+ const unique2 = [...new Set(requestedTopics)];
3376
+ if (unique2.length > 1) {
3377
+ throw new Error(`Specify only one focused help-ai topic at a time. Received: ${unique2.join(", ")}.`);
3378
+ }
3379
+ const topic = unique2[0];
3380
+ if (topic !== void 0 && topic !== "state-transitions" && topic !== "artifacts" && topic !== "post-execution" && topic !== "selection" && topic !== "naming") {
3381
+ throw new Error(`Unsupported help-ai topic '${topic}'. Expected state-transitions|artifacts|post-execution|selection|naming.`);
3382
+ }
3383
+ return topic;
3384
+ }
2942
3385
 
2943
3386
  // src/commands/index.ts
2944
- import path7 from "path";
3387
+ import path8 from "path";
2945
3388
  import { IndexManager as IndexManager2 } from "@kitsy/coop-core";
2946
3389
  function runStatus(options) {
2947
3390
  const root = resolveRepoRoot();
@@ -2951,7 +3394,7 @@ function runStatus(options) {
2951
3394
  const freshness = status.stale ? "stale" : "fresh";
2952
3395
  const existsText = status.exists ? "present" : "missing";
2953
3396
  console.log(`[COOP] index ${existsText}, ${freshness}`);
2954
- console.log(`[COOP] graph: ${path7.relative(root, status.graph_path)}`);
3397
+ console.log(`[COOP] graph: ${path8.relative(root, status.graph_path)}`);
2955
3398
  if (status.generated_at) {
2956
3399
  console.log(`[COOP] generated_at: ${status.generated_at}`);
2957
3400
  }
@@ -2974,7 +3417,7 @@ function runRebuild() {
2974
3417
  const graph = manager.build_full_index();
2975
3418
  const elapsed = Date.now() - start;
2976
3419
  console.log(`[COOP] index rebuilt: ${graph.nodes.size} tasks (${elapsed} ms)`);
2977
- console.log(`[COOP] graph: ${path7.relative(root, manager.graphPath)}`);
3420
+ console.log(`[COOP] graph: ${path8.relative(root, manager.graphPath)}`);
2978
3421
  }
2979
3422
  function registerIndexCommand(program) {
2980
3423
  const index = program.command("index").description("Index management commands");
@@ -2991,7 +3434,7 @@ function registerIndexCommand(program) {
2991
3434
 
2992
3435
  // src/commands/init.ts
2993
3436
  import fs8 from "fs";
2994
- import path10 from "path";
3437
+ import path11 from "path";
2995
3438
  import { spawnSync as spawnSync4 } from "child_process";
2996
3439
  import { createInterface } from "readline/promises";
2997
3440
  import { stdin as input, stdout as output } from "process";
@@ -2999,7 +3442,7 @@ import { CURRENT_SCHEMA_VERSION, write_schema_version } from "@kitsy/coop-core";
2999
3442
 
3000
3443
  // src/hooks/pre-commit.ts
3001
3444
  import fs6 from "fs";
3002
- import path8 from "path";
3445
+ import path9 from "path";
3003
3446
  import { spawnSync as spawnSync3 } from "child_process";
3004
3447
  import { detect_cycle, parseTaskContent, parseTaskFile as parseTaskFile7, validateStructural as validateStructural5 } from "@kitsy/coop-core";
3005
3448
  var HOOK_BLOCK_START = "# COOP_PRE_COMMIT_START";
@@ -3030,15 +3473,15 @@ function projectRootFromRelativePath(repoRoot, relativePath) {
3030
3473
  const normalized = toPosixPath2(relativePath);
3031
3474
  const projectMatch = /^\.coop\/projects\/([^/]+)\/tasks\/.+\.md$/i.exec(normalized);
3032
3475
  if (projectMatch?.[1]) {
3033
- return path8.join(repoRoot, ".coop", "projects", projectMatch[1]);
3476
+ return path9.join(repoRoot, ".coop", "projects", projectMatch[1]);
3034
3477
  }
3035
3478
  if (normalized.startsWith(".coop/tasks/")) {
3036
- return path8.join(repoRoot, ".coop");
3479
+ return path9.join(repoRoot, ".coop");
3037
3480
  }
3038
3481
  throw new Error(`Unsupported staged COOP task path '${relativePath}'.`);
3039
3482
  }
3040
3483
  function listTaskFilesForProject(projectRoot) {
3041
- const tasksDir = path8.join(projectRoot, "tasks");
3484
+ const tasksDir = path9.join(projectRoot, "tasks");
3042
3485
  if (!fs6.existsSync(tasksDir)) return [];
3043
3486
  const out = [];
3044
3487
  const stack = [tasksDir];
@@ -3046,12 +3489,12 @@ function listTaskFilesForProject(projectRoot) {
3046
3489
  const current = stack.pop();
3047
3490
  const entries = fs6.readdirSync(current, { withFileTypes: true });
3048
3491
  for (const entry of entries) {
3049
- const fullPath = path8.join(current, entry.name);
3492
+ const fullPath = path9.join(current, entry.name);
3050
3493
  if (entry.isDirectory()) {
3051
3494
  stack.push(fullPath);
3052
3495
  continue;
3053
3496
  }
3054
- if (entry.isFile() && path8.extname(entry.name).toLowerCase() === ".md") {
3497
+ if (entry.isFile() && path9.extname(entry.name).toLowerCase() === ".md") {
3055
3498
  out.push(fullPath);
3056
3499
  }
3057
3500
  }
@@ -3070,7 +3513,7 @@ function parseStagedTasks(repoRoot, relativePaths) {
3070
3513
  const errors = [];
3071
3514
  const staged = [];
3072
3515
  for (const relativePath of relativePaths) {
3073
- const absolutePath = path8.join(repoRoot, ...relativePath.split("/"));
3516
+ const absolutePath = path9.join(repoRoot, ...relativePath.split("/"));
3074
3517
  const projectRoot = projectRootFromRelativePath(repoRoot, relativePath);
3075
3518
  const stagedBlob = readGitBlob(repoRoot, `:${relativePath}`);
3076
3519
  if (!stagedBlob) {
@@ -3134,7 +3577,7 @@ function collectTasksForCycleCheck(projectRoot, stagedTasks) {
3134
3577
  }
3135
3578
  const tasks = [];
3136
3579
  for (const filePath of listTaskFilesForProject(projectRoot)) {
3137
- const normalized = toPosixPath2(path8.resolve(filePath));
3580
+ const normalized = toPosixPath2(path9.resolve(filePath));
3138
3581
  const stagedTask = stagedByPath.get(normalized);
3139
3582
  if (stagedTask) {
3140
3583
  tasks.push(stagedTask);
@@ -3164,7 +3607,7 @@ function runPreCommitChecks(repoRoot) {
3164
3607
  const graph = buildGraphForCycleCheck(tasks);
3165
3608
  const cycle = detect_cycle(graph);
3166
3609
  if (cycle) {
3167
- const projectLabel = toPosixPath2(path8.relative(repoRoot, projectRoot));
3610
+ const projectLabel = toPosixPath2(path9.relative(repoRoot, projectRoot));
3168
3611
  errors.push(`[COOP] Dependency cycle detected in ${projectLabel}: ${cycle.join(" -> ")}.`);
3169
3612
  }
3170
3613
  } catch (error) {
@@ -3196,8 +3639,8 @@ function hookScriptBlock() {
3196
3639
  ].join("\n");
3197
3640
  }
3198
3641
  function installPreCommitHook(repoRoot) {
3199
- const hookPath = path8.join(repoRoot, ".git", "hooks", "pre-commit");
3200
- const hookDir = path8.dirname(hookPath);
3642
+ const hookPath = path9.join(repoRoot, ".git", "hooks", "pre-commit");
3643
+ const hookDir = path9.dirname(hookPath);
3201
3644
  if (!fs6.existsSync(hookDir)) {
3202
3645
  return {
3203
3646
  installed: false,
@@ -3229,13 +3672,13 @@ function installPreCommitHook(repoRoot) {
3229
3672
 
3230
3673
  // src/hooks/post-merge-validate.ts
3231
3674
  import fs7 from "fs";
3232
- import path9 from "path";
3675
+ import path10 from "path";
3233
3676
  import { list_projects } from "@kitsy/coop-core";
3234
3677
  import { load_graph as load_graph4, validate_graph as validate_graph2 } from "@kitsy/coop-core";
3235
3678
  var HOOK_BLOCK_START2 = "# COOP_POST_MERGE_START";
3236
3679
  var HOOK_BLOCK_END2 = "# COOP_POST_MERGE_END";
3237
3680
  function runPostMergeValidate(repoRoot) {
3238
- const workspaceDir = path9.join(repoRoot, ".coop");
3681
+ const workspaceDir = path10.join(repoRoot, ".coop");
3239
3682
  if (!fs7.existsSync(workspaceDir)) {
3240
3683
  return {
3241
3684
  ok: true,
@@ -3288,8 +3731,8 @@ function postMergeHookBlock() {
3288
3731
  ].join("\n");
3289
3732
  }
3290
3733
  function installPostMergeHook(repoRoot) {
3291
- const hookPath = path9.join(repoRoot, ".git", "hooks", "post-merge");
3292
- const hookDir = path9.dirname(hookPath);
3734
+ const hookPath = path10.join(repoRoot, ".git", "hooks", "post-merge");
3735
+ const hookDir = path10.dirname(hookPath);
3293
3736
  if (!fs7.existsSync(hookDir)) {
3294
3737
  return {
3295
3738
  installed: false,
@@ -3339,7 +3782,7 @@ function aliasesYaml(aliases) {
3339
3782
  return `
3340
3783
  ${aliases.map((alias) => ` - "${alias}"`).join("\n")}`;
3341
3784
  }
3342
- function buildProjectConfig(projectId, projectName, projectAliases) {
3785
+ function buildProjectConfig(projectId, projectName, projectAliases, namingTemplate) {
3343
3786
  return `version: 2
3344
3787
  project:
3345
3788
  name: ${JSON.stringify(projectName)}
@@ -3353,7 +3796,7 @@ id_prefixes:
3353
3796
  run: "RUN"
3354
3797
 
3355
3798
  id:
3356
- naming: "<TYPE>-<USER>-<YYMMDD>-<RAND>"
3799
+ naming: ${JSON.stringify(namingTemplate)}
3357
3800
  seq_padding: 0
3358
3801
 
3359
3802
  defaults:
@@ -3482,7 +3925,7 @@ function writeIfMissing(filePath, content) {
3482
3925
  }
3483
3926
  }
3484
3927
  function ensureGitignoreEntry(root, entry) {
3485
- const gitignorePath = path10.join(root, ".gitignore");
3928
+ const gitignorePath = path11.join(root, ".gitignore");
3486
3929
  if (!fs8.existsSync(gitignorePath)) {
3487
3930
  fs8.writeFileSync(gitignorePath, `${entry}
3488
3931
  `, "utf8");
@@ -3497,7 +3940,7 @@ function ensureGitignoreEntry(root, entry) {
3497
3940
  }
3498
3941
  }
3499
3942
  function ensureGitattributesEntry(root, entry) {
3500
- const attrsPath = path10.join(root, ".gitattributes");
3943
+ const attrsPath = path11.join(root, ".gitattributes");
3501
3944
  if (!fs8.existsSync(attrsPath)) {
3502
3945
  fs8.writeFileSync(attrsPath, `${entry}
3503
3946
  `, "utf8");
@@ -3559,10 +4002,12 @@ async function promptInitIdentity(root, options) {
3559
4002
  throw new Error("Invalid project id. Use letters, numbers, and hyphens.");
3560
4003
  }
3561
4004
  const aliasesInput = options.aliases !== void 0 ? options.aliases : await ask2("Aliases (csv, optional)", "");
4005
+ const namingTemplate = options.naming?.trim() || await ask2("ID naming template", DEFAULT_ID_NAMING_TEMPLATE);
3562
4006
  return {
3563
4007
  projectName,
3564
4008
  projectId,
3565
- projectAliases: parseAliases2(aliasesInput)
4009
+ projectAliases: parseAliases2(aliasesInput),
4010
+ namingTemplate
3566
4011
  };
3567
4012
  } finally {
3568
4013
  rl.close();
@@ -3573,6 +4018,7 @@ async function resolveInitIdentity(root, options) {
3573
4018
  const fallbackName = options.name?.trim() || defaultName;
3574
4019
  const fallbackId = normalizeProjectId(options.id?.trim() || fallbackName) || repoIdentityId(root);
3575
4020
  const fallbackAliases = parseAliases2(options.aliases);
4021
+ const fallbackNaming = options.naming?.trim() || DEFAULT_ID_NAMING_TEMPLATE;
3576
4022
  const interactive = Boolean(options.interactive || !options.yes && input.isTTY && output.isTTY);
3577
4023
  if (interactive) {
3578
4024
  return promptInitIdentity(root, options);
@@ -3580,16 +4026,17 @@ async function resolveInitIdentity(root, options) {
3580
4026
  return {
3581
4027
  projectName: fallbackName,
3582
4028
  projectId: fallbackId,
3583
- projectAliases: fallbackAliases
4029
+ projectAliases: fallbackAliases,
4030
+ namingTemplate: fallbackNaming
3584
4031
  };
3585
4032
  }
3586
4033
  function registerInitCommand(program) {
3587
- program.command("init").description("Initialize .coop/ structure in the current repository").option("--name <name>", "Project display name").option("--id <id>", "Project id").option("--aliases <csv>", "Comma-separated project aliases").option("-y, --yes", "Use defaults without prompting").option("--interactive", "Prompt for missing project identity values").action(async (options) => {
4034
+ program.command("init").description("Initialize .coop/ structure in the current repository").option("--name <name>", "Project display name").option("--id <id>", "Project id").option("--aliases <csv>", "Comma-separated project aliases").option("--naming <template>", "ID naming template (default: <TYPE>-<TITLE16>-<SEQ>)").option("-y, --yes", "Use defaults without prompting").option("--interactive", "Prompt for missing project identity values").action(async (options) => {
3588
4035
  const root = resolveRepoRoot();
3589
4036
  const workspaceDir = coopWorkspaceDir(root);
3590
4037
  const identity = await resolveInitIdentity(root, options);
3591
4038
  const projectId = identity.projectId;
3592
- const projectRoot = path10.join(workspaceDir, "projects", projectId);
4039
+ const projectRoot = path11.join(workspaceDir, "projects", projectId);
3593
4040
  const dirs = [
3594
4041
  "ideas",
3595
4042
  "tasks",
@@ -3605,23 +4052,23 @@ function registerInitCommand(program) {
3605
4052
  "history/deliveries",
3606
4053
  ".index"
3607
4054
  ];
3608
- ensureDir(path10.join(workspaceDir, "projects"));
4055
+ ensureDir(path11.join(workspaceDir, "projects"));
3609
4056
  for (const dir of dirs) {
3610
- ensureDir(path10.join(projectRoot, dir));
4057
+ ensureDir(path11.join(projectRoot, dir));
3611
4058
  }
3612
4059
  writeIfMissing(
3613
- path10.join(projectRoot, "config.yml"),
3614
- buildProjectConfig(projectId, identity.projectName, identity.projectAliases)
4060
+ path11.join(projectRoot, "config.yml"),
4061
+ buildProjectConfig(projectId, identity.projectName, identity.projectAliases, identity.namingTemplate)
3615
4062
  );
3616
- if (!fs8.existsSync(path10.join(projectRoot, "schema-version"))) {
4063
+ if (!fs8.existsSync(path11.join(projectRoot, "schema-version"))) {
3617
4064
  write_schema_version(projectRoot, CURRENT_SCHEMA_VERSION);
3618
4065
  }
3619
- writeIfMissing(path10.join(projectRoot, "templates/task.md"), TASK_TEMPLATE);
3620
- writeIfMissing(path10.join(projectRoot, "templates/idea.md"), IDEA_TEMPLATE);
3621
- writeIfMissing(path10.join(projectRoot, "plugins/console-log.yml"), PLUGIN_CONSOLE_TEMPLATE);
3622
- writeIfMissing(path10.join(projectRoot, "plugins/github-pr.yml"), PLUGIN_GITHUB_TEMPLATE);
3623
- writeIfMissing(path10.join(workspaceDir, ".ignore"), COOP_IGNORE_TEMPLATE);
3624
- writeIfMissing(path10.join(workspaceDir, ".gitignore"), COOP_IGNORE_TEMPLATE);
4066
+ writeIfMissing(path11.join(projectRoot, "templates/task.md"), TASK_TEMPLATE);
4067
+ writeIfMissing(path11.join(projectRoot, "templates/idea.md"), IDEA_TEMPLATE);
4068
+ writeIfMissing(path11.join(projectRoot, "plugins/console-log.yml"), PLUGIN_CONSOLE_TEMPLATE);
4069
+ writeIfMissing(path11.join(projectRoot, "plugins/github-pr.yml"), PLUGIN_GITHUB_TEMPLATE);
4070
+ writeIfMissing(path11.join(workspaceDir, ".ignore"), COOP_IGNORE_TEMPLATE);
4071
+ writeIfMissing(path11.join(workspaceDir, ".gitignore"), COOP_IGNORE_TEMPLATE);
3625
4072
  writeWorkspaceConfig(root, { version: 2, current_project: projectId });
3626
4073
  ensureGitignoreEntry(root, ".coop/logs/");
3627
4074
  ensureGitignoreEntry(root, ".coop/tmp/");
@@ -3631,17 +4078,18 @@ function registerInitCommand(program) {
3631
4078
  const project = resolveProject(root, projectId);
3632
4079
  console.log("Initialized COOP workspace.");
3633
4080
  console.log(`- Root: ${root}`);
3634
- console.log(`- Workspace: ${path10.relative(root, workspaceDir)}`);
3635
- console.log(`- Project: ${project.id} (${path10.relative(root, project.root)})`);
4081
+ console.log(`- Workspace: ${path11.relative(root, workspaceDir)}`);
4082
+ console.log(`- Project: ${project.id} (${path11.relative(root, project.root)})`);
3636
4083
  console.log(`- Name: ${identity.projectName}`);
3637
4084
  console.log(`- Aliases: ${identity.projectAliases.length > 0 ? identity.projectAliases.join(", ") : "(none)"}`);
4085
+ console.log(`- ID naming: ${identity.namingTemplate}`);
3638
4086
  console.log(`- ${preCommitHook.message}`);
3639
4087
  if (preCommitHook.installed) {
3640
- console.log(`- Hook: ${path10.relative(root, preCommitHook.hookPath)}`);
4088
+ console.log(`- Hook: ${path11.relative(root, preCommitHook.hookPath)}`);
3641
4089
  }
3642
4090
  console.log(`- ${postMergeHook.message}`);
3643
4091
  if (postMergeHook.installed) {
3644
- console.log(`- Hook: ${path10.relative(root, postMergeHook.hookPath)}`);
4092
+ console.log(`- Hook: ${path11.relative(root, postMergeHook.hookPath)}`);
3645
4093
  }
3646
4094
  console.log(`- ${mergeDrivers}`);
3647
4095
  console.log("- Next steps:");
@@ -3651,9 +4099,70 @@ function registerInitCommand(program) {
3651
4099
  });
3652
4100
  }
3653
4101
 
4102
+ // src/commands/lifecycle.ts
4103
+ import path12 from "path";
4104
+ import { parseTaskFile as parseTaskFile8 } from "@kitsy/coop-core";
4105
+ var lifecycleVerbs = [
4106
+ {
4107
+ name: "review",
4108
+ description: "Move a task into review",
4109
+ targetStatus: "in_review"
4110
+ },
4111
+ {
4112
+ name: "complete",
4113
+ description: "Complete a task",
4114
+ targetStatus: "done"
4115
+ },
4116
+ {
4117
+ name: "block",
4118
+ description: "Mark a task as blocked",
4119
+ targetStatus: "blocked"
4120
+ },
4121
+ {
4122
+ name: "unblock",
4123
+ description: "Move a blocked task back to todo",
4124
+ targetStatus: "todo"
4125
+ },
4126
+ {
4127
+ name: "reopen",
4128
+ description: "Move a task back to todo",
4129
+ targetStatus: "todo"
4130
+ },
4131
+ {
4132
+ name: "cancel",
4133
+ description: "Cancel a task",
4134
+ targetStatus: "canceled"
4135
+ }
4136
+ ];
4137
+ function currentTaskSelection(root, id) {
4138
+ const reference = resolveReference(root, id, "task");
4139
+ const filePath = path12.join(root, ...reference.file.split("/"));
4140
+ const parsed = parseTaskFile8(filePath);
4141
+ return formatSelectedTask(
4142
+ {
4143
+ task: parsed.task,
4144
+ score: 0,
4145
+ readiness: parsed.task.status === "blocked" ? "blocked" : parsed.task.status === "in_review" ? "waiting_review" : parsed.task.status === "done" || parsed.task.status === "canceled" ? "done" : parsed.task.status === "in_progress" ? "in_progress" : "ready",
4146
+ fits_capacity: true,
4147
+ fits_wip: true
4148
+ },
4149
+ {}
4150
+ );
4151
+ }
4152
+ function registerLifecycleCommands(program) {
4153
+ for (const verb of lifecycleVerbs) {
4154
+ program.command(verb.name).description(verb.description).command("task").description(`${verb.description} by task id or alias`).argument("<id>", "Task ID or alias").option("--actor <actor>", "Actor performing the transition").option("--user <user>", "Current user for advisory authorization checks").option("--force", "Override advisory authorization checks").action(async (id, options) => {
4155
+ const root = resolveRepoRoot();
4156
+ console.log(currentTaskSelection(root, id));
4157
+ const result = await transitionTaskByReference(root, id, verb.targetStatus, options);
4158
+ console.log(`Updated ${result.task.id}: ${result.from} -> ${result.to}`);
4159
+ });
4160
+ }
4161
+ }
4162
+
3654
4163
  // src/commands/list.ts
3655
- import path11 from "path";
3656
- import { parseIdeaFile as parseIdeaFile3, parseTaskFile as parseTaskFile8 } from "@kitsy/coop-core";
4164
+ import path13 from "path";
4165
+ import { parseIdeaFile as parseIdeaFile3, parseTaskFile as parseTaskFile9 } from "@kitsy/coop-core";
3657
4166
  import chalk2 from "chalk";
3658
4167
 
3659
4168
  // src/utils/not-implemented.ts
@@ -3683,7 +4192,7 @@ function sortByIdAsc(items) {
3683
4192
  }
3684
4193
  function loadTasks(root) {
3685
4194
  return listTaskFiles(root).map((filePath) => ({
3686
- task: parseTaskFile8(filePath).task,
4195
+ task: parseTaskFile9(filePath).task,
3687
4196
  filePath
3688
4197
  }));
3689
4198
  }
@@ -3723,7 +4232,7 @@ function listTasks(options) {
3723
4232
  statusColor(entry.status),
3724
4233
  entry.priority,
3725
4234
  entry.track,
3726
- path11.relative(root, entry.filePath)
4235
+ path13.relative(root, entry.filePath)
3727
4236
  ])
3728
4237
  )
3729
4238
  );
@@ -3758,7 +4267,7 @@ function listIdeas(options) {
3758
4267
  statusColor(entry.status),
3759
4268
  entry.priority,
3760
4269
  entry.track,
3761
- path11.relative(root, entry.filePath)
4270
+ path13.relative(root, entry.filePath)
3762
4271
  ])
3763
4272
  )
3764
4273
  );
@@ -3783,24 +4292,24 @@ function registerListCommand(program) {
3783
4292
 
3784
4293
  // src/utils/logger.ts
3785
4294
  import fs9 from "fs";
3786
- import path12 from "path";
4295
+ import path14 from "path";
3787
4296
  function resolveRepoSafe(start = process.cwd()) {
3788
4297
  try {
3789
4298
  return resolveRepoRoot(start);
3790
4299
  } catch {
3791
- return path12.resolve(start);
4300
+ return path14.resolve(start);
3792
4301
  }
3793
4302
  }
3794
4303
  function resolveCliLogFile(start = process.cwd()) {
3795
4304
  const root = resolveRepoSafe(start);
3796
4305
  const workspace = coopWorkspaceDir(root);
3797
4306
  if (fs9.existsSync(workspace)) {
3798
- return path12.join(workspace, "logs", "cli.log");
4307
+ return path14.join(workspace, "logs", "cli.log");
3799
4308
  }
3800
- return path12.join(resolveCoopHome(), "logs", "cli.log");
4309
+ return path14.join(resolveCoopHome(), "logs", "cli.log");
3801
4310
  }
3802
4311
  function appendLogEntry(entry, logFile) {
3803
- fs9.mkdirSync(path12.dirname(logFile), { recursive: true });
4312
+ fs9.mkdirSync(path14.dirname(logFile), { recursive: true });
3804
4313
  fs9.appendFileSync(logFile, `${JSON.stringify(entry)}
3805
4314
  `, "utf8");
3806
4315
  }
@@ -3877,7 +4386,7 @@ function registerLogCommand(program) {
3877
4386
 
3878
4387
  // src/commands/migrate.ts
3879
4388
  import fs10 from "fs";
3880
- import path13 from "path";
4389
+ import path15 from "path";
3881
4390
  import { createInterface as createInterface2 } from "readline/promises";
3882
4391
  import { stdin as input2, stdout as output2 } from "process";
3883
4392
  import { CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION2, IndexManager as IndexManager3, migrate_repository, parseYamlFile as parseYamlFile2, writeYamlFile as writeYamlFile4 } from "@kitsy/coop-core";
@@ -3901,7 +4410,7 @@ function writeIfMissing2(filePath, content) {
3901
4410
  }
3902
4411
  }
3903
4412
  function ensureGitignoreEntry2(root, entry) {
3904
- const gitignorePath = path13.join(root, ".gitignore");
4413
+ const gitignorePath = path15.join(root, ".gitignore");
3905
4414
  if (!fs10.existsSync(gitignorePath)) {
3906
4415
  fs10.writeFileSync(gitignorePath, `${entry}
3907
4416
  `, "utf8");
@@ -3936,7 +4445,7 @@ function legacyWorkspaceProjectEntries(root) {
3936
4445
  "backlog",
3937
4446
  "plans",
3938
4447
  "releases"
3939
- ].filter((entry) => fs10.existsSync(path13.join(workspaceDir, entry)));
4448
+ ].filter((entry) => fs10.existsSync(path15.join(workspaceDir, entry)));
3940
4449
  }
3941
4450
  function normalizeProjectId2(value) {
3942
4451
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
@@ -3981,7 +4490,7 @@ async function promptProjectIdentity(defaults, options) {
3981
4490
  async function resolveMigrationIdentity(root, options) {
3982
4491
  const existing = readCoopConfig(root);
3983
4492
  const defaults = {
3984
- projectName: existing.projectName || path13.basename(root),
4493
+ projectName: existing.projectName || path15.basename(root),
3985
4494
  projectId: normalizeProjectId2(existing.projectId || repoIdentityId(root)) || repoIdentityId(root),
3986
4495
  projectAliases: options.aliases !== void 0 ? parseAliases3(options.aliases) : existing.projectAliases
3987
4496
  };
@@ -4004,7 +4513,7 @@ async function migrateWorkspaceLayout(root, options) {
4004
4513
  if (!fs10.existsSync(workspaceDir)) {
4005
4514
  throw new Error("Missing .coop directory. Run 'coop init' first.");
4006
4515
  }
4007
- const projectsDir = path13.join(workspaceDir, "projects");
4516
+ const projectsDir = path15.join(workspaceDir, "projects");
4008
4517
  const legacyEntries = legacyWorkspaceProjectEntries(root);
4009
4518
  if (legacyEntries.length === 0 && fs10.existsSync(projectsDir)) {
4010
4519
  console.log("[COOP] workspace layout already uses v2.");
@@ -4012,11 +4521,11 @@ async function migrateWorkspaceLayout(root, options) {
4012
4521
  }
4013
4522
  const identity = await resolveMigrationIdentity(root, options);
4014
4523
  const projectId = identity.projectId;
4015
- const projectRoot = path13.join(projectsDir, projectId);
4524
+ const projectRoot = path15.join(projectsDir, projectId);
4016
4525
  if (fs10.existsSync(projectRoot) && !options.force) {
4017
- throw new Error(`Project destination '${path13.relative(root, projectRoot)}' already exists. Re-run with --force.`);
4526
+ throw new Error(`Project destination '${path15.relative(root, projectRoot)}' already exists. Re-run with --force.`);
4018
4527
  }
4019
- const changed = legacyEntries.map((entry) => `${path13.join(".coop", entry)} -> ${path13.join(".coop", "projects", projectId, entry)}`);
4528
+ const changed = legacyEntries.map((entry) => `${path15.join(".coop", entry)} -> ${path15.join(".coop", "projects", projectId, entry)}`);
4020
4529
  changed.push(`.coop/config.yml -> workspace current_project=${projectId}`);
4021
4530
  console.log(`Workspace layout migration (${options.dryRun ? "DRY RUN" : "APPLY"})`);
4022
4531
  console.log(`- from: v1 flat layout`);
@@ -4035,17 +4544,17 @@ async function migrateWorkspaceLayout(root, options) {
4035
4544
  fs10.mkdirSync(projectsDir, { recursive: true });
4036
4545
  fs10.mkdirSync(projectRoot, { recursive: true });
4037
4546
  for (const entry of legacyEntries) {
4038
- const source = path13.join(workspaceDir, entry);
4039
- const destination = path13.join(projectRoot, entry);
4547
+ const source = path15.join(workspaceDir, entry);
4548
+ const destination = path15.join(projectRoot, entry);
4040
4549
  if (fs10.existsSync(destination)) {
4041
4550
  if (!options.force) {
4042
- throw new Error(`Destination '${path13.relative(root, destination)}' already exists.`);
4551
+ throw new Error(`Destination '${path15.relative(root, destination)}' already exists.`);
4043
4552
  }
4044
4553
  fs10.rmSync(destination, { recursive: true, force: true });
4045
4554
  }
4046
4555
  fs10.renameSync(source, destination);
4047
4556
  }
4048
- const movedConfigPath = path13.join(projectRoot, "config.yml");
4557
+ const movedConfigPath = path15.join(projectRoot, "config.yml");
4049
4558
  if (fs10.existsSync(movedConfigPath)) {
4050
4559
  const movedConfig = parseYamlFile2(movedConfigPath);
4051
4560
  const nextProject = typeof movedConfig.project === "object" && movedConfig.project !== null ? { ...movedConfig.project } : {};
@@ -4063,13 +4572,13 @@ async function migrateWorkspaceLayout(root, options) {
4063
4572
  }
4064
4573
  const workspace = readWorkspaceConfig(root);
4065
4574
  writeWorkspaceConfig(root, { ...workspace, version: 2, current_project: projectId });
4066
- writeIfMissing2(path13.join(workspaceDir, ".ignore"), COOP_IGNORE_TEMPLATE2);
4067
- writeIfMissing2(path13.join(workspaceDir, ".gitignore"), COOP_IGNORE_TEMPLATE2);
4575
+ writeIfMissing2(path15.join(workspaceDir, ".ignore"), COOP_IGNORE_TEMPLATE2);
4576
+ writeIfMissing2(path15.join(workspaceDir, ".gitignore"), COOP_IGNORE_TEMPLATE2);
4068
4577
  ensureGitignoreEntry2(root, ".coop/logs/");
4069
4578
  ensureGitignoreEntry2(root, ".coop/tmp/");
4070
4579
  const manager = new IndexManager3(projectRoot);
4071
4580
  manager.build_full_index();
4072
- console.log(`[COOP] migrated workspace to v2 at ${path13.relative(root, projectRoot)}`);
4581
+ console.log(`[COOP] migrated workspace to v2 at ${path15.relative(root, projectRoot)}`);
4073
4582
  }
4074
4583
  function registerMigrateCommand(program) {
4075
4584
  const migrate = program.command("migrate").description("Migrate COOP data and workspace layouts").option("--dry-run", "Preview migration without writing files").option("--to <version>", "Target schema version", String(CURRENT_SCHEMA_VERSION2)).action((options) => {
@@ -4088,7 +4597,7 @@ function registerMigrateCommand(program) {
4088
4597
  if (report.changed_files.length > 0) {
4089
4598
  console.log("- changed files:");
4090
4599
  for (const filePath of report.changed_files) {
4091
- console.log(` - ${path13.relative(root, filePath)}`);
4600
+ console.log(` - ${path15.relative(root, filePath)}`);
4092
4601
  }
4093
4602
  }
4094
4603
  if (report.dry_run) {
@@ -4108,6 +4617,75 @@ function registerMigrateCommand(program) {
4108
4617
  });
4109
4618
  }
4110
4619
 
4620
+ // src/commands/naming.ts
4621
+ function printNamingOverview() {
4622
+ const root = resolveRepoRoot();
4623
+ const config = readCoopConfig(root);
4624
+ const sampleTitle = "Natural-language COOP command recommender";
4625
+ const sampleTaskTitle = "Implement billing payment contract review";
4626
+ console.log("COOP Naming");
4627
+ console.log(`Current template: ${config.idNamingTemplate}`);
4628
+ console.log(`Default template: ${DEFAULT_ID_NAMING_TEMPLATE}`);
4629
+ console.log("Tokens:");
4630
+ console.log(" <TYPE> entity type such as IDEA, TASK, DELIVERY");
4631
+ console.log(" <TITLE> semantic title token (defaults to TITLE16)");
4632
+ console.log(" <TITLE16> semantic title token capped to 16 chars");
4633
+ console.log(" <TITLE24> semantic title token capped to 24 chars");
4634
+ console.log(" <TRACK> task track");
4635
+ console.log(" <SEQ> sequential number within the rendered pattern");
4636
+ console.log(" <USER> actor/user namespace");
4637
+ console.log(" <YYMMDD> short date token");
4638
+ console.log(" <RAND> random uniqueness token");
4639
+ console.log(" <PREFIX> entity prefix override");
4640
+ console.log("Examples:");
4641
+ const tokenExamples = namingTokenExamples(sampleTitle);
4642
+ for (const [token, value] of Object.entries(tokenExamples)) {
4643
+ console.log(` ${token.padEnd(10)} ${value}`);
4644
+ }
4645
+ console.log(`Preview (${DEFAULT_ID_NAMING_TEMPLATE}): ${previewNamingTemplate(DEFAULT_ID_NAMING_TEMPLATE, {
4646
+ entityType: "idea",
4647
+ title: sampleTitle
4648
+ }, root)}`);
4649
+ console.log(`Preview (${config.idNamingTemplate}): ${previewNamingTemplate(config.idNamingTemplate, {
4650
+ entityType: "task",
4651
+ title: sampleTaskTitle,
4652
+ track: "mvp",
4653
+ status: "todo",
4654
+ taskType: "spike"
4655
+ }, root)}`);
4656
+ console.log("Try:");
4657
+ console.log(` coop naming preview "${sampleTitle}"`);
4658
+ console.log(' coop config id.naming "<TYPE>-<TITLE24>"');
4659
+ console.log(' coop config id.naming "<TRACK>-<SEQ>"');
4660
+ }
4661
+ function registerNamingCommand(program) {
4662
+ const naming = program.command("naming").description("Explain COOP ID naming templates and preview examples");
4663
+ naming.action(() => {
4664
+ printNamingOverview();
4665
+ });
4666
+ naming.command("preview").description("Preview the current or supplied naming template for a sample title").argument("<title>", "Sample title to render").option("--template <template>", "Override naming template").option("--entity <entity>", "Entity type: idea|task|track|delivery|run", "task").option("--track <track>", "Track token value").option("--status <status>", "Status token value").option("--task-type <taskType>", "Task type token value").action(
4667
+ (title, options) => {
4668
+ const root = resolveRepoRoot();
4669
+ const config = readCoopConfig(root);
4670
+ const entity = options.entity?.trim().toLowerCase() || "task";
4671
+ const template = options.template?.trim() || config.idNamingTemplate;
4672
+ console.log(
4673
+ previewNamingTemplate(
4674
+ template,
4675
+ {
4676
+ entityType: entity,
4677
+ title,
4678
+ track: options.track,
4679
+ status: options.status,
4680
+ taskType: options.taskType
4681
+ },
4682
+ root
4683
+ )
4684
+ );
4685
+ }
4686
+ );
4687
+ }
4688
+
4111
4689
  // src/commands/plan.ts
4112
4690
  import chalk3 from "chalk";
4113
4691
  import {
@@ -4343,7 +4921,7 @@ function registerPlanCommand(program) {
4343
4921
 
4344
4922
  // src/commands/project.ts
4345
4923
  import fs11 from "fs";
4346
- import path14 from "path";
4924
+ import path16 from "path";
4347
4925
  import { CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION3, write_schema_version as write_schema_version2 } from "@kitsy/coop-core";
4348
4926
  var TASK_TEMPLATE2 = `---
4349
4927
  id: TASK-001
@@ -4376,7 +4954,7 @@ linked_tasks: []
4376
4954
  ## Problem
4377
4955
  What problem are you solving?
4378
4956
  `;
4379
- var PROJECT_CONFIG_TEMPLATE = (projectId, projectName) => `version: 2
4957
+ var PROJECT_CONFIG_TEMPLATE = (projectId, projectName, namingTemplate) => `version: 2
4380
4958
  project:
4381
4959
  name: "${projectName}"
4382
4960
  id: "${projectId}"
@@ -4389,7 +4967,7 @@ id_prefixes:
4389
4967
  run: "RUN"
4390
4968
 
4391
4969
  id:
4392
- naming: "<TYPE>-<USER>-<YYMMDD>-<RAND>"
4970
+ naming: ${JSON.stringify(namingTemplate)}
4393
4971
  seq_padding: 0
4394
4972
 
4395
4973
  defaults:
@@ -4462,19 +5040,19 @@ function writeIfMissing3(filePath, content) {
4462
5040
  function normalizeProjectId3(value) {
4463
5041
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
4464
5042
  }
4465
- function createProject(root, projectId, projectName) {
5043
+ function createProject(root, projectId, projectName, namingTemplate = DEFAULT_ID_NAMING_TEMPLATE) {
4466
5044
  const workspaceDir = coopWorkspaceDir(root);
4467
- const projectRoot = path14.join(workspaceDir, "projects", projectId);
4468
- ensureDir2(path14.join(workspaceDir, "projects"));
5045
+ const projectRoot = path16.join(workspaceDir, "projects", projectId);
5046
+ ensureDir2(path16.join(workspaceDir, "projects"));
4469
5047
  for (const dir of PROJECT_DIRS) {
4470
- ensureDir2(path14.join(projectRoot, dir));
5048
+ ensureDir2(path16.join(projectRoot, dir));
4471
5049
  }
4472
- writeIfMissing3(path14.join(projectRoot, "config.yml"), PROJECT_CONFIG_TEMPLATE(projectId, projectName));
4473
- if (!fs11.existsSync(path14.join(projectRoot, "schema-version"))) {
5050
+ writeIfMissing3(path16.join(projectRoot, "config.yml"), PROJECT_CONFIG_TEMPLATE(projectId, projectName, namingTemplate));
5051
+ if (!fs11.existsSync(path16.join(projectRoot, "schema-version"))) {
4474
5052
  write_schema_version2(projectRoot, CURRENT_SCHEMA_VERSION3);
4475
5053
  }
4476
- writeIfMissing3(path14.join(projectRoot, "templates/task.md"), TASK_TEMPLATE2);
4477
- writeIfMissing3(path14.join(projectRoot, "templates/idea.md"), IDEA_TEMPLATE2);
5054
+ writeIfMissing3(path16.join(projectRoot, "templates/task.md"), TASK_TEMPLATE2);
5055
+ writeIfMissing3(path16.join(projectRoot, "templates/idea.md"), IDEA_TEMPLATE2);
4478
5056
  return projectRoot;
4479
5057
  }
4480
5058
  function registerProjectCommand(program) {
@@ -4502,7 +5080,7 @@ function registerProjectCommand(program) {
4502
5080
  }
4503
5081
  console.log(`id=${active.id}`);
4504
5082
  console.log(`name=${active.name}`);
4505
- console.log(`path=${path14.relative(root, active.root)}`);
5083
+ console.log(`path=${path16.relative(root, active.root)}`);
4506
5084
  console.log(`layout=${active.layout}`);
4507
5085
  });
4508
5086
  project.command("use").description("Set the active COOP project").argument("<id>", "Project id").action((id) => {
@@ -4516,7 +5094,7 @@ function registerProjectCommand(program) {
4516
5094
  writeWorkspaceConfig(root, { ...workspace, version: 2, current_project: match.id });
4517
5095
  console.log(`current_project=${match.id}`);
4518
5096
  });
4519
- project.command("create").description("Create a new COOP project in the current workspace").argument("<id>", "Project id").option("--name <name>", "Project display name").action((id, options) => {
5097
+ project.command("create").description("Create a new COOP project in the current workspace").argument("<id>", "Project id").option("--name <name>", "Project display name").option("--naming <template>", "ID naming template (default: <TYPE>-<TITLE16>-<SEQ>)").action((id, options) => {
4520
5098
  const root = resolveRepoRoot();
4521
5099
  const projectId = normalizeProjectId3(id);
4522
5100
  if (!projectId) {
@@ -4527,33 +5105,33 @@ function registerProjectCommand(program) {
4527
5105
  throw new Error(`Project '${projectId}' already exists.`);
4528
5106
  }
4529
5107
  const projectName = options.name?.trim() || repoDisplayName(root);
4530
- const projectRoot = createProject(root, projectId, projectName);
5108
+ const projectRoot = createProject(root, projectId, projectName, options.naming?.trim() || DEFAULT_ID_NAMING_TEMPLATE);
4531
5109
  const workspace = readWorkspaceConfig(root);
4532
5110
  writeWorkspaceConfig(root, {
4533
5111
  ...workspace,
4534
5112
  version: 2,
4535
5113
  current_project: workspace.current_project || projectId
4536
5114
  });
4537
- console.log(`Created project '${projectId}' at ${path14.relative(root, projectRoot)}`);
5115
+ console.log(`Created project '${projectId}' at ${path16.relative(root, projectRoot)}`);
4538
5116
  });
4539
5117
  }
4540
5118
 
4541
5119
  // src/commands/refine.ts
4542
5120
  import fs12 from "fs";
4543
- import path15 from "path";
4544
- import { parseIdeaFile as parseIdeaFile4, parseTaskFile as parseTaskFile9 } from "@kitsy/coop-core";
5121
+ import path17 from "path";
5122
+ import { parseIdeaFile as parseIdeaFile4, parseTaskFile as parseTaskFile10 } from "@kitsy/coop-core";
4545
5123
  import { create_provider_refinement_client, refine_idea_to_draft, refine_task_to_draft } from "@kitsy/coop-ai";
4546
5124
  function resolveTaskFile(root, idOrAlias) {
4547
5125
  const target = resolveReference(root, idOrAlias, "task");
4548
- return path15.join(root, ...target.file.split("/"));
5126
+ return path17.join(root, ...target.file.split("/"));
4549
5127
  }
4550
5128
  function resolveIdeaFile2(root, idOrAlias) {
4551
5129
  const target = resolveReference(root, idOrAlias, "idea");
4552
- return path15.join(root, ...target.file.split("/"));
5130
+ return path17.join(root, ...target.file.split("/"));
4553
5131
  }
4554
5132
  async function readSupplementalInput(root, options) {
4555
5133
  if (options.inputFile?.trim()) {
4556
- return fs12.readFileSync(path15.resolve(root, options.inputFile.trim()), "utf8");
5134
+ return fs12.readFileSync(path17.resolve(root, options.inputFile.trim()), "utf8");
4557
5135
  }
4558
5136
  if (options.stdin) {
4559
5137
  return readStdinText();
@@ -4571,7 +5149,7 @@ function loadAuthorityContext(root, refs) {
4571
5149
  for (const ref of refs ?? []) {
4572
5150
  const filePart = extractRefFile(ref);
4573
5151
  if (!filePart) continue;
4574
- const fullPath = path15.resolve(root, filePart);
5152
+ const fullPath = path17.resolve(root, filePart);
4575
5153
  if (!fs12.existsSync(fullPath) || !fs12.statSync(fullPath).isFile()) continue;
4576
5154
  out.push({
4577
5155
  ref,
@@ -4610,7 +5188,7 @@ function registerRefineCommand(program) {
4610
5188
  const root = resolveRepoRoot();
4611
5189
  const projectDir = ensureCoopInitialized(root);
4612
5190
  const taskFile = resolveTaskFile(root, id);
4613
- const parsed = parseTaskFile9(taskFile);
5191
+ const parsed = parseTaskFile10(taskFile);
4614
5192
  const supplemental = await readSupplementalInput(root, options);
4615
5193
  const client = create_provider_refinement_client(readCoopConfig(root).raw);
4616
5194
  const draft = await refine_task_to_draft(
@@ -4638,15 +5216,15 @@ function registerRefineCommand(program) {
4638
5216
  const written = applyRefinementDraft(root, projectDir, draft);
4639
5217
  console.log(`[COOP] applied draft from ${draftInput.source}: ${written.length} task file(s) updated`);
4640
5218
  for (const filePath of written) {
4641
- console.log(`- ${path15.relative(root, filePath)}`);
5219
+ console.log(`- ${path17.relative(root, filePath)}`);
4642
5220
  }
4643
5221
  });
4644
5222
  }
4645
5223
 
4646
5224
  // src/commands/run.ts
4647
5225
  import fs13 from "fs";
4648
- import path16 from "path";
4649
- import { load_graph as load_graph6, parseTaskFile as parseTaskFile10 } from "@kitsy/coop-core";
5226
+ import path18 from "path";
5227
+ import { load_graph as load_graph6, parseTaskFile as parseTaskFile11 } from "@kitsy/coop-core";
4650
5228
  import {
4651
5229
  build_contract,
4652
5230
  create_provider_agent_client,
@@ -4655,11 +5233,11 @@ import {
4655
5233
  } from "@kitsy/coop-ai";
4656
5234
  function loadTask(root, idOrAlias) {
4657
5235
  const target = resolveReference(root, idOrAlias, "task");
4658
- const taskFile = path16.join(root, ...target.file.split("/"));
5236
+ const taskFile = path18.join(root, ...target.file.split("/"));
4659
5237
  if (!fs13.existsSync(taskFile)) {
4660
5238
  throw new Error(`Task file not found: ${target.file}`);
4661
5239
  }
4662
- return parseTaskFile10(taskFile).task;
5240
+ return parseTaskFile11(taskFile).task;
4663
5241
  }
4664
5242
  function printContract(contract) {
4665
5243
  console.log(JSON.stringify(contract, null, 2));
@@ -4695,26 +5273,26 @@ function registerRunCommand(program) {
4695
5273
  on_progress: (message) => console.log(`[COOP] ${message}`)
4696
5274
  });
4697
5275
  if (result.status === "failed") {
4698
- throw new Error(`Run failed: ${result.run.id}. Log: ${path16.relative(root, result.log_path)}`);
5276
+ throw new Error(`Run failed: ${result.run.id}. Log: ${path18.relative(root, result.log_path)}`);
4699
5277
  }
4700
5278
  if (result.status === "paused") {
4701
5279
  console.log(`[COOP] run paused: ${result.run.id}`);
4702
- console.log(`[COOP] log: ${path16.relative(root, result.log_path)}`);
5280
+ console.log(`[COOP] log: ${path18.relative(root, result.log_path)}`);
4703
5281
  return;
4704
5282
  }
4705
5283
  console.log(`[COOP] run completed: ${result.run.id}`);
4706
- console.log(`[COOP] log: ${path16.relative(root, result.log_path)}`);
5284
+ console.log(`[COOP] log: ${path18.relative(root, result.log_path)}`);
4707
5285
  });
4708
5286
  }
4709
5287
 
4710
5288
  // src/server/api.ts
4711
5289
  import fs14 from "fs";
4712
5290
  import http2 from "http";
4713
- import path17 from "path";
5291
+ import path19 from "path";
4714
5292
  import {
4715
5293
  analyze_feasibility as analyze_feasibility2,
4716
5294
  load_graph as load_graph7,
4717
- parseTaskFile as parseTaskFile11,
5295
+ parseTaskFile as parseTaskFile12,
4718
5296
  resolve_external_dependencies
4719
5297
  } from "@kitsy/coop-core";
4720
5298
  function json(res, statusCode, payload) {
@@ -4756,12 +5334,12 @@ function taskSummary(graph, task, external = []) {
4756
5334
  };
4757
5335
  }
4758
5336
  function taskFileById(root, id) {
4759
- const tasksDir = path17.join(resolveProject(root).root, "tasks");
5337
+ const tasksDir = path19.join(resolveProject(root).root, "tasks");
4760
5338
  if (!fs14.existsSync(tasksDir)) return null;
4761
5339
  const entries = fs14.readdirSync(tasksDir, { withFileTypes: true });
4762
5340
  const target = `${id}.md`.toLowerCase();
4763
5341
  const match = entries.find((entry) => entry.isFile() && entry.name.toLowerCase() === target);
4764
- return match ? path17.join(tasksDir, match.name) : null;
5342
+ return match ? path19.join(tasksDir, match.name) : null;
4765
5343
  }
4766
5344
  function loadRemoteConfig(root) {
4767
5345
  const raw = readCoopConfig(root).raw;
@@ -4842,7 +5420,7 @@ function createApiServer(root, options = {}) {
4842
5420
  notFound(res, `Task '${taskId}' not found.`);
4843
5421
  return;
4844
5422
  }
4845
- const parsed = parseTaskFile11(filePath);
5423
+ const parsed = parseTaskFile12(filePath);
4846
5424
  const task = graph.nodes.get(parsed.task.id) ?? parsed.task;
4847
5425
  json(res, 200, {
4848
5426
  ...workspaceMeta(repoRoot),
@@ -4851,7 +5429,7 @@ function createApiServer(root, options = {}) {
4851
5429
  created: task.created,
4852
5430
  updated: task.updated,
4853
5431
  body: parsed.body,
4854
- file_path: path17.relative(repoRoot, filePath).replace(/\\/g, "/")
5432
+ file_path: path19.relative(repoRoot, filePath).replace(/\\/g, "/")
4855
5433
  }
4856
5434
  });
4857
5435
  return;
@@ -4940,8 +5518,8 @@ function registerServeCommand(program) {
4940
5518
 
4941
5519
  // src/commands/show.ts
4942
5520
  import fs15 from "fs";
4943
- import path18 from "path";
4944
- import { parseIdeaFile as parseIdeaFile5, parseTaskFile as parseTaskFile12 } from "@kitsy/coop-core";
5521
+ import path20 from "path";
5522
+ import { parseIdeaFile as parseIdeaFile5, parseTaskFile as parseTaskFile13 } from "@kitsy/coop-core";
4945
5523
  function stringify(value) {
4946
5524
  if (value === null || value === void 0) return "-";
4947
5525
  if (Array.isArray(value)) return value.length > 0 ? value.join(", ") : "-";
@@ -4959,7 +5537,7 @@ function pushListSection(lines, title, values) {
4959
5537
  }
4960
5538
  }
4961
5539
  function loadComputedFromIndex(root, taskId) {
4962
- const indexPath = path18.join(ensureCoopInitialized(root), ".index", "tasks.json");
5540
+ const indexPath = path20.join(ensureCoopInitialized(root), ".index", "tasks.json");
4963
5541
  if (!fs15.existsSync(indexPath)) {
4964
5542
  return null;
4965
5543
  }
@@ -5004,8 +5582,8 @@ function showTask(taskId) {
5004
5582
  const root = resolveRepoRoot();
5005
5583
  const coop = ensureCoopInitialized(root);
5006
5584
  const target = resolveReference(root, taskId, "task");
5007
- const taskFile = path18.join(root, ...target.file.split("/"));
5008
- const parsed = parseTaskFile12(taskFile);
5585
+ const taskFile = path20.join(root, ...target.file.split("/"));
5586
+ const parsed = parseTaskFile13(taskFile);
5009
5587
  const task = parsed.task;
5010
5588
  const body = parsed.body.trim();
5011
5589
  const computed = loadComputedFromIndex(root, task.id);
@@ -5023,7 +5601,7 @@ function showTask(taskId) {
5023
5601
  `Tags: ${stringify(task.tags)}`,
5024
5602
  `Created: ${task.created}`,
5025
5603
  `Updated: ${task.updated}`,
5026
- `File: ${path18.relative(root, taskFile)}`,
5604
+ `File: ${path20.relative(root, taskFile)}`,
5027
5605
  ""
5028
5606
  ];
5029
5607
  pushListSection(lines, "Acceptance", task.acceptance);
@@ -5048,7 +5626,7 @@ function showTask(taskId) {
5048
5626
  "Computed:"
5049
5627
  );
5050
5628
  if (!computed) {
5051
- lines.push(`index not built (${path18.relative(root, path18.join(coop, ".index", "tasks.json"))} missing)`);
5629
+ lines.push(`index not built (${path20.relative(root, path20.join(coop, ".index", "tasks.json"))} missing)`);
5052
5630
  } else {
5053
5631
  for (const [key, value] of Object.entries(computed)) {
5054
5632
  lines.push(`- ${key}: ${stringify(value)}`);
@@ -5060,7 +5638,7 @@ function showIdea(ideaId) {
5060
5638
  const root = resolveRepoRoot();
5061
5639
  ensureCoopInitialized(root);
5062
5640
  const target = resolveReference(root, ideaId, "idea");
5063
- const ideaFile = path18.join(root, ...target.file.split("/"));
5641
+ const ideaFile = path20.join(root, ...target.file.split("/"));
5064
5642
  const parsed = parseIdeaFile5(ideaFile);
5065
5643
  const idea = parsed.idea;
5066
5644
  const body = parsed.body.trim();
@@ -5074,7 +5652,7 @@ function showIdea(ideaId) {
5074
5652
  `Tags: ${stringify(idea.tags)}`,
5075
5653
  `Linked Tasks: ${stringify(idea.linked_tasks)}`,
5076
5654
  `Created: ${idea.created}`,
5077
- `File: ${path18.relative(root, ideaFile)}`,
5655
+ `File: ${path20.relative(root, ideaFile)}`,
5078
5656
  "",
5079
5657
  "Body:",
5080
5658
  body || "-"
@@ -5191,8 +5769,8 @@ function registerTransitionCommand(program) {
5191
5769
  }
5192
5770
 
5193
5771
  // src/commands/taskflow.ts
5194
- import path19 from "path";
5195
- import { parseTaskFile as parseTaskFile13 } from "@kitsy/coop-core";
5772
+ import path21 from "path";
5773
+ import { parseTaskFile as parseTaskFile14 } from "@kitsy/coop-core";
5196
5774
  function normalizeExecutor(value) {
5197
5775
  if (!value?.trim()) return void 0;
5198
5776
  const normalized = value.trim().toLowerCase();
@@ -5229,8 +5807,8 @@ async function claimAndStart(root, taskId, options) {
5229
5807
  }
5230
5808
  }
5231
5809
  const reference = resolveReference(root, taskId, "task");
5232
- const filePath = path19.join(root, ...reference.file.split("/"));
5233
- const parsed = parseTaskFile13(filePath);
5810
+ const filePath = path21.join(root, ...reference.file.split("/"));
5811
+ const parsed = parseTaskFile14(filePath);
5234
5812
  if (parsed.task.status === "in_progress") {
5235
5813
  console.log(`Task ${parsed.task.id} is already in_progress.`);
5236
5814
  return;
@@ -5276,8 +5854,8 @@ function registerTaskFlowCommands(program) {
5276
5854
  today: options.today
5277
5855
  }).entry.task.id;
5278
5856
  const reference = resolveReference(root, taskId, "task");
5279
- const filePath = path19.join(root, ...reference.file.split("/"));
5280
- const parsed = parseTaskFile13(filePath);
5857
+ const filePath = path21.join(root, ...reference.file.split("/"));
5858
+ const parsed = parseTaskFile14(filePath);
5281
5859
  console.log(
5282
5860
  formatSelectedTask(
5283
5861
  {
@@ -5301,18 +5879,18 @@ function registerTaskFlowCommands(program) {
5301
5879
 
5302
5880
  // src/commands/ui.ts
5303
5881
  import fs16 from "fs";
5304
- import path20 from "path";
5882
+ import path22 from "path";
5305
5883
  import { createRequire } from "module";
5306
5884
  import { fileURLToPath } from "url";
5307
5885
  import { spawn } from "child_process";
5308
5886
  import { IndexManager as IndexManager4 } from "@kitsy/coop-core";
5309
5887
  function findPackageRoot(entryPath) {
5310
- let current = path20.dirname(entryPath);
5888
+ let current = path22.dirname(entryPath);
5311
5889
  while (true) {
5312
- if (fs16.existsSync(path20.join(current, "package.json"))) {
5890
+ if (fs16.existsSync(path22.join(current, "package.json"))) {
5313
5891
  return current;
5314
5892
  }
5315
- const parent = path20.dirname(current);
5893
+ const parent = path22.dirname(current);
5316
5894
  if (parent === current) {
5317
5895
  throw new Error(`Unable to locate package root for ${entryPath}.`);
5318
5896
  }
@@ -5361,9 +5939,9 @@ async function startUiServer(repoRoot, host, port, shouldOpen) {
5361
5939
  const project = resolveProject(repoRoot);
5362
5940
  ensureIndex(repoRoot);
5363
5941
  const uiRoot = resolveUiPackageRoot();
5364
- const requireFromUi = createRequire(path20.join(uiRoot, "package.json"));
5942
+ const requireFromUi = createRequire(path22.join(uiRoot, "package.json"));
5365
5943
  const vitePackageJson = requireFromUi.resolve("vite/package.json");
5366
- const viteBin = path20.join(path20.dirname(vitePackageJson), "bin", "vite.js");
5944
+ const viteBin = path22.join(path22.dirname(vitePackageJson), "bin", "vite.js");
5367
5945
  const url = `http://${host}:${port}`;
5368
5946
  console.log(`COOP UI: ${url}`);
5369
5947
  const child = spawn(process.execPath, [viteBin, "--host", host, "--port", String(port)], {
@@ -5722,7 +6300,7 @@ function registerWebhookCommand(program) {
5722
6300
  // src/merge-driver/merge-driver.ts
5723
6301
  import fs17 from "fs";
5724
6302
  import os2 from "os";
5725
- import path21 from "path";
6303
+ import path23 from "path";
5726
6304
  import { spawnSync as spawnSync5 } from "child_process";
5727
6305
  import { stringifyFrontmatter as stringifyFrontmatter4, parseFrontmatterContent as parseFrontmatterContent3, parseYamlContent as parseYamlContent3, stringifyYamlContent as stringifyYamlContent2 } from "@kitsy/coop-core";
5728
6306
  var STATUS_RANK = {
@@ -5812,11 +6390,11 @@ function mergeTaskFile(ancestorPath, oursPath, theirsPath) {
5812
6390
  const ours = parseTaskDocument(oursRaw, oursPath);
5813
6391
  const theirs = parseTaskDocument(theirsRaw, theirsPath);
5814
6392
  const mergedFrontmatter = mergeTaskFrontmatter(ancestor.frontmatter, ours.frontmatter, theirs.frontmatter);
5815
- const tempDir = fs17.mkdtempSync(path21.join(os2.tmpdir(), "coop-merge-body-"));
6393
+ const tempDir = fs17.mkdtempSync(path23.join(os2.tmpdir(), "coop-merge-body-"));
5816
6394
  try {
5817
- const ancestorBody = path21.join(tempDir, "ancestor.md");
5818
- const oursBody = path21.join(tempDir, "ours.md");
5819
- const theirsBody = path21.join(tempDir, "theirs.md");
6395
+ const ancestorBody = path23.join(tempDir, "ancestor.md");
6396
+ const oursBody = path23.join(tempDir, "ours.md");
6397
+ const theirsBody = path23.join(tempDir, "theirs.md");
5820
6398
  fs17.writeFileSync(ancestorBody, ancestor.body, "utf8");
5821
6399
  fs17.writeFileSync(oursBody, ours.body, "utf8");
5822
6400
  fs17.writeFileSync(theirsBody, theirs.body, "utf8");
@@ -5854,7 +6432,7 @@ function runMergeDriver(kind, ancestorPath, oursPath, theirsPath) {
5854
6432
  // src/index.ts
5855
6433
  function readVersion() {
5856
6434
  const currentFile = fileURLToPath2(import.meta.url);
5857
- const packageJsonPath = path22.resolve(path22.dirname(currentFile), "..", "package.json");
6435
+ const packageJsonPath = path24.resolve(path24.dirname(currentFile), "..", "package.json");
5858
6436
  try {
5859
6437
  const parsed = JSON.parse(fs18.readFileSync(packageJsonPath, "utf8"));
5860
6438
  return parsed.version ?? "0.0.0";
@@ -5887,7 +6465,9 @@ function createProgram() {
5887
6465
  registerHelpAiCommand(program);
5888
6466
  registerIndexCommand(program);
5889
6467
  registerLogCommand(program);
6468
+ registerLifecycleCommands(program);
5890
6469
  registerMigrateCommand(program);
6470
+ registerNamingCommand(program);
5891
6471
  registerPlanCommand(program);
5892
6472
  registerProjectCommand(program);
5893
6473
  registerRefineCommand(program);
@@ -5954,7 +6534,7 @@ async function runCli(argv = process.argv) {
5954
6534
  function isMainModule() {
5955
6535
  const entry = process.argv[1];
5956
6536
  if (!entry) return false;
5957
- return path22.resolve(entry) === fileURLToPath2(import.meta.url);
6537
+ return path24.resolve(entry) === fileURLToPath2(import.meta.url);
5958
6538
  }
5959
6539
  if (isMainModule()) {
5960
6540
  await runCli(process.argv);