@kitsy/coop 2.1.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/dist/index.js +550 -142
- 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
|
|
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:
|
|
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() :
|
|
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:
|
|
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 :
|
|
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,43 @@ 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
|
+
unsupported_command_warnings: [
|
|
2928
|
+
"Do not invent COOP commands such as `coop complete` when they are not present in `help-ai` output.",
|
|
2929
|
+
"If a desired lifecycle action is not represented by an allowed command, state that explicitly instead of paraphrasing.",
|
|
2930
|
+
"Quote exact COOP command names from `coop help-ai --format json` before proposing them."
|
|
2931
|
+
],
|
|
2932
|
+
artifact_policy: {
|
|
2933
|
+
config_key: "artifacts.dir",
|
|
2934
|
+
default_dir: "docs",
|
|
2935
|
+
required_for: ["contract reviews", "spike findings", "audit notes", "planning artifacts"],
|
|
2936
|
+
guidance: [
|
|
2937
|
+
"Write agent-produced artifacts under the configured artifact directory.",
|
|
2938
|
+
"If `artifacts.dir` is unset, use `docs`.",
|
|
2939
|
+
"Do not invent ad hoc artifact paths outside the configured directory unless the user explicitly asks for a different location."
|
|
2940
|
+
]
|
|
2941
|
+
},
|
|
2795
2942
|
command_groups: [
|
|
2796
2943
|
{
|
|
2797
2944
|
name: "Workspace",
|
|
2798
2945
|
description: "Initialize and select the active COOP project.",
|
|
2799
2946
|
commands: [
|
|
2800
2947
|
{ usage: "coop init --yes", purpose: "Initialize a COOP workspace with repo-name defaults." },
|
|
2948
|
+
{ usage: "coop init --naming <TYPE>-<TITLE16>-<SEQ>", purpose: "Initialize a workspace with an explicit ID naming template." },
|
|
2801
2949
|
{ usage: "coop project list", purpose: "List projects in the current workspace." },
|
|
2802
2950
|
{ 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." }
|
|
2951
|
+
{ usage: "coop project use <id>", purpose: "Switch the active project in a multi-project workspace." },
|
|
2952
|
+
{ usage: "coop naming", purpose: "Explain the current naming template, tokens, and examples." },
|
|
2953
|
+
{ usage: 'coop naming preview "Natural-language COOP command recommender"', purpose: "Preview a semantic ID before creating an item." }
|
|
2804
2954
|
]
|
|
2805
2955
|
},
|
|
2806
2956
|
{
|
|
@@ -2838,7 +2988,13 @@ var catalog = {
|
|
|
2838
2988
|
{ usage: "coop next task", purpose: "Show the top ready task using the default track or full workspace context." },
|
|
2839
2989
|
{ usage: "coop graph next --delivery MVP", purpose: "Show the ready queue for a delivery with scores and blockers." },
|
|
2840
2990
|
{ 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." }
|
|
2991
|
+
{ 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." },
|
|
2992
|
+
{ usage: "coop review task PM-101", purpose: "Move an in-progress task into in_review using a DX-friendly verb." },
|
|
2993
|
+
{ usage: "coop complete task PM-101", purpose: "Move a task in review into done using a DX-friendly verb." },
|
|
2994
|
+
{ usage: "coop block task PM-101", purpose: "Mark a task as blocked." },
|
|
2995
|
+
{ usage: "coop unblock task PM-101", purpose: "Move a blocked task back to todo." },
|
|
2996
|
+
{ usage: "coop cancel task PM-101", purpose: "Cancel a task." },
|
|
2997
|
+
{ usage: "coop reopen task PM-101", purpose: "Move a canceled task back to todo." }
|
|
2842
2998
|
]
|
|
2843
2999
|
},
|
|
2844
3000
|
{
|
|
@@ -2858,6 +3014,7 @@ var catalog = {
|
|
|
2858
3014
|
name: "Execute And Integrate",
|
|
2859
3015
|
description: "Hand execution to an AI provider or code-agent CLI and expose COOP through MCP/API.",
|
|
2860
3016
|
commands: [
|
|
3017
|
+
{ 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." },
|
|
2861
3018
|
{ usage: "coop config ai.provider codex_cli", purpose: "Use the installed Codex CLI as the execution/refinement backend." },
|
|
2862
3019
|
{ usage: "coop config ai.provider claude_cli", purpose: "Use the installed Claude CLI as the execution/refinement backend." },
|
|
2863
3020
|
{ usage: "coop config ai.provider gemini_cli", purpose: "Use the installed Gemini CLI as the execution/refinement backend." },
|
|
@@ -2883,9 +3040,82 @@ var catalog = {
|
|
|
2883
3040
|
execution_model: [
|
|
2884
3041
|
"Agents or services may send drafts through files or stdin, but COOP owns canonical writes.",
|
|
2885
3042
|
"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."
|
|
3043
|
+
"Use `coop log --last --verbose` when command execution fails and the concise error points to a stack trace.",
|
|
3044
|
+
"If a workflow depends on stable human-readable IDs, inspect `coop naming` or `coop config id.naming` before creating items."
|
|
2887
3045
|
]
|
|
2888
3046
|
};
|
|
3047
|
+
function normalizeRepoPath(repoPath) {
|
|
3048
|
+
return repoPath ? path7.resolve(repoPath) : process.cwd();
|
|
3049
|
+
}
|
|
3050
|
+
function formatSelectionCommand(commandName, delivery, track) {
|
|
3051
|
+
if (delivery) {
|
|
3052
|
+
return `${commandName} graph next --delivery ${delivery}`;
|
|
3053
|
+
}
|
|
3054
|
+
if (track) {
|
|
3055
|
+
return `${commandName} next task --track ${track}`;
|
|
3056
|
+
}
|
|
3057
|
+
return `${commandName} next task`;
|
|
3058
|
+
}
|
|
3059
|
+
function renderInitialPrompt(options = {}) {
|
|
3060
|
+
const rigour = options.rigour ?? "balanced";
|
|
3061
|
+
const repoPath = normalizeRepoPath(options.repoPath);
|
|
3062
|
+
const commandName = options.commandName?.trim() || "coop";
|
|
3063
|
+
const artifactsDir = options.artifactsDir?.trim() || "docs";
|
|
3064
|
+
const selectionCommand = formatSelectionCommand(commandName, options.delivery, options.track);
|
|
3065
|
+
const planningCommand = options.delivery ? `${commandName} plan delivery ${options.delivery}` : `${commandName} status`;
|
|
3066
|
+
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";
|
|
3067
|
+
const lines = [
|
|
3068
|
+
`You are working in:`,
|
|
3069
|
+
repoPath,
|
|
3070
|
+
"",
|
|
3071
|
+
"COOP is the source of truth for backlog, planning, and execution in this repository.",
|
|
3072
|
+
"",
|
|
3073
|
+
"Before doing anything else:",
|
|
3074
|
+
`1. Run \`${commandName} help-ai --format markdown\``,
|
|
3075
|
+
`2. Run \`${commandName} project show\``,
|
|
3076
|
+
`3. Run \`${selectionCommand}\``,
|
|
3077
|
+
`4. Run \`${planningCommand}\``,
|
|
3078
|
+
"",
|
|
3079
|
+
"Context:",
|
|
3080
|
+
scopeLine,
|
|
3081
|
+
`- Artifact directory: ${artifactsDir}`,
|
|
3082
|
+
"",
|
|
3083
|
+
"Rules:",
|
|
3084
|
+
"- Learn COOP capabilities from `coop help-ai` before proposing commands.",
|
|
3085
|
+
"- Use only commands that actually exist in COOP. Do not invent command names.",
|
|
3086
|
+
"- Do not reprioritize work outside COOP unless the user explicitly overrides it.",
|
|
3087
|
+
"- Select only the first ready task from the COOP readiness output.",
|
|
3088
|
+
"- Inspect the selected task with `coop show task <id>` before implementation.",
|
|
3089
|
+
`- Write any contract-review, audit, or planning artifact under \`${artifactsDir}\` unless the user explicitly chooses another location.`
|
|
3090
|
+
];
|
|
3091
|
+
if (rigour === "strict") {
|
|
3092
|
+
lines.push(
|
|
3093
|
+
"- Before mentioning a COOP command, verify the exact command name from `coop help-ai --format json`.",
|
|
3094
|
+
"- If a needed workflow is not supported by an exact COOP command, say that explicitly instead of inventing one.",
|
|
3095
|
+
"- Do not switch to another task unless COOP state changes or the user explicitly redirects you."
|
|
3096
|
+
);
|
|
3097
|
+
} else if (rigour === "balanced") {
|
|
3098
|
+
lines.push(
|
|
3099
|
+
"- Prefer exact COOP commands from `coop help-ai --format json` when describing next actions.",
|
|
3100
|
+
"- If COOP lacks a named command for a desired action, state the limitation plainly."
|
|
3101
|
+
);
|
|
3102
|
+
} else {
|
|
3103
|
+
lines.push("- Keep COOP as the authority for task selection and lifecycle state.");
|
|
3104
|
+
}
|
|
3105
|
+
lines.push(
|
|
3106
|
+
"",
|
|
3107
|
+
"Output format:",
|
|
3108
|
+
"- Active project",
|
|
3109
|
+
"- Selected task",
|
|
3110
|
+
"- Why selected",
|
|
3111
|
+
"- Task acceptance/tests/refs summary",
|
|
3112
|
+
"- Immediate implementation steps",
|
|
3113
|
+
"",
|
|
3114
|
+
"Do not start code changes until you have reported the selected task and plan."
|
|
3115
|
+
);
|
|
3116
|
+
return `${lines.join("\n")}
|
|
3117
|
+
`;
|
|
3118
|
+
}
|
|
2889
3119
|
function renderAiHelp(format) {
|
|
2890
3120
|
if (format === "json") {
|
|
2891
3121
|
return `${JSON.stringify(catalog, null, 2)}
|
|
@@ -2906,6 +3136,24 @@ function renderAiHelp(format) {
|
|
|
2906
3136
|
lines.push(bullet(rule));
|
|
2907
3137
|
}
|
|
2908
3138
|
lines.push("");
|
|
3139
|
+
lines.push(format === "markdown" ? "## Allowed Lifecycle Commands" : "Allowed Lifecycle Commands");
|
|
3140
|
+
for (const item of catalog.allowed_lifecycle_commands) {
|
|
3141
|
+
lines.push(bullet(`\`${item}\``));
|
|
3142
|
+
}
|
|
3143
|
+
lines.push("");
|
|
3144
|
+
lines.push(format === "markdown" ? "## Unsupported / Invented Command Warnings" : "Unsupported / Invented Command Warnings");
|
|
3145
|
+
for (const item of catalog.unsupported_command_warnings) {
|
|
3146
|
+
lines.push(bullet(item));
|
|
3147
|
+
}
|
|
3148
|
+
lines.push("");
|
|
3149
|
+
lines.push(format === "markdown" ? "## Artifact Policy" : "Artifact Policy");
|
|
3150
|
+
lines.push(`${format === "markdown" ? "- " : "- "}Config key: \`${catalog.artifact_policy.config_key}\``);
|
|
3151
|
+
lines.push(`${format === "markdown" ? "- " : "- "}Default dir: \`${catalog.artifact_policy.default_dir}\``);
|
|
3152
|
+
lines.push(`${format === "markdown" ? "- " : "- "}Required for: ${catalog.artifact_policy.required_for.join(", ")}`);
|
|
3153
|
+
for (const item of catalog.artifact_policy.guidance) {
|
|
3154
|
+
lines.push(bullet(item));
|
|
3155
|
+
}
|
|
3156
|
+
lines.push("");
|
|
2909
3157
|
for (const group of catalog.command_groups) {
|
|
2910
3158
|
lines.push(format === "markdown" ? `## ${group.name}` : group.name);
|
|
2911
3159
|
lines.push(group.description);
|
|
@@ -2928,20 +3176,43 @@ function renderAiHelp(format) {
|
|
|
2928
3176
|
return `${lines.join("\n")}
|
|
2929
3177
|
`;
|
|
2930
3178
|
}
|
|
3179
|
+
function renderAiInitialPrompt(options = {}) {
|
|
3180
|
+
return renderInitialPrompt(options);
|
|
3181
|
+
}
|
|
2931
3182
|
|
|
2932
3183
|
// src/commands/help-ai.ts
|
|
2933
3184
|
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) => {
|
|
3185
|
+
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("--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
3186
|
const format = options.format ?? "text";
|
|
2936
3187
|
if (format !== "text" && format !== "json" && format !== "markdown") {
|
|
2937
3188
|
throw new Error(`Unsupported help-ai format '${format}'. Expected text|json|markdown.`);
|
|
2938
3189
|
}
|
|
2939
|
-
|
|
3190
|
+
const rigour = options.strict ? "strict" : options.light ? "light" : options.balanced ? "balanced" : options.rigour ?? "balanced";
|
|
3191
|
+
if (rigour !== "strict" && rigour !== "balanced" && rigour !== "light") {
|
|
3192
|
+
throw new Error(`Unsupported help-ai rigour '${rigour}'. Expected strict|balanced|light.`);
|
|
3193
|
+
}
|
|
3194
|
+
let artifactsDir = "docs";
|
|
3195
|
+
const repoRoot = options.repo ? resolveRepoRoot(options.repo) : resolveRepoRoot();
|
|
3196
|
+
try {
|
|
3197
|
+
ensureCoopInitialized(repoRoot);
|
|
3198
|
+
artifactsDir = readCoopConfig(repoRoot).artifactsDir;
|
|
3199
|
+
} catch {
|
|
3200
|
+
artifactsDir = "docs";
|
|
3201
|
+
}
|
|
3202
|
+
const output3 = options.initialPrompt ? renderAiInitialPrompt({
|
|
3203
|
+
repoPath: options.repo,
|
|
3204
|
+
delivery: options.delivery,
|
|
3205
|
+
track: options.track,
|
|
3206
|
+
commandName: options.command,
|
|
3207
|
+
rigour,
|
|
3208
|
+
artifactsDir
|
|
3209
|
+
}) : renderAiHelp(format);
|
|
3210
|
+
console.log(output3.trimEnd());
|
|
2940
3211
|
});
|
|
2941
3212
|
}
|
|
2942
3213
|
|
|
2943
3214
|
// src/commands/index.ts
|
|
2944
|
-
import
|
|
3215
|
+
import path8 from "path";
|
|
2945
3216
|
import { IndexManager as IndexManager2 } from "@kitsy/coop-core";
|
|
2946
3217
|
function runStatus(options) {
|
|
2947
3218
|
const root = resolveRepoRoot();
|
|
@@ -2951,7 +3222,7 @@ function runStatus(options) {
|
|
|
2951
3222
|
const freshness = status.stale ? "stale" : "fresh";
|
|
2952
3223
|
const existsText = status.exists ? "present" : "missing";
|
|
2953
3224
|
console.log(`[COOP] index ${existsText}, ${freshness}`);
|
|
2954
|
-
console.log(`[COOP] graph: ${
|
|
3225
|
+
console.log(`[COOP] graph: ${path8.relative(root, status.graph_path)}`);
|
|
2955
3226
|
if (status.generated_at) {
|
|
2956
3227
|
console.log(`[COOP] generated_at: ${status.generated_at}`);
|
|
2957
3228
|
}
|
|
@@ -2974,7 +3245,7 @@ function runRebuild() {
|
|
|
2974
3245
|
const graph = manager.build_full_index();
|
|
2975
3246
|
const elapsed = Date.now() - start;
|
|
2976
3247
|
console.log(`[COOP] index rebuilt: ${graph.nodes.size} tasks (${elapsed} ms)`);
|
|
2977
|
-
console.log(`[COOP] graph: ${
|
|
3248
|
+
console.log(`[COOP] graph: ${path8.relative(root, manager.graphPath)}`);
|
|
2978
3249
|
}
|
|
2979
3250
|
function registerIndexCommand(program) {
|
|
2980
3251
|
const index = program.command("index").description("Index management commands");
|
|
@@ -2991,7 +3262,7 @@ function registerIndexCommand(program) {
|
|
|
2991
3262
|
|
|
2992
3263
|
// src/commands/init.ts
|
|
2993
3264
|
import fs8 from "fs";
|
|
2994
|
-
import
|
|
3265
|
+
import path11 from "path";
|
|
2995
3266
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
2996
3267
|
import { createInterface } from "readline/promises";
|
|
2997
3268
|
import { stdin as input, stdout as output } from "process";
|
|
@@ -2999,7 +3270,7 @@ import { CURRENT_SCHEMA_VERSION, write_schema_version } from "@kitsy/coop-core";
|
|
|
2999
3270
|
|
|
3000
3271
|
// src/hooks/pre-commit.ts
|
|
3001
3272
|
import fs6 from "fs";
|
|
3002
|
-
import
|
|
3273
|
+
import path9 from "path";
|
|
3003
3274
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
3004
3275
|
import { detect_cycle, parseTaskContent, parseTaskFile as parseTaskFile7, validateStructural as validateStructural5 } from "@kitsy/coop-core";
|
|
3005
3276
|
var HOOK_BLOCK_START = "# COOP_PRE_COMMIT_START";
|
|
@@ -3030,15 +3301,15 @@ function projectRootFromRelativePath(repoRoot, relativePath) {
|
|
|
3030
3301
|
const normalized = toPosixPath2(relativePath);
|
|
3031
3302
|
const projectMatch = /^\.coop\/projects\/([^/]+)\/tasks\/.+\.md$/i.exec(normalized);
|
|
3032
3303
|
if (projectMatch?.[1]) {
|
|
3033
|
-
return
|
|
3304
|
+
return path9.join(repoRoot, ".coop", "projects", projectMatch[1]);
|
|
3034
3305
|
}
|
|
3035
3306
|
if (normalized.startsWith(".coop/tasks/")) {
|
|
3036
|
-
return
|
|
3307
|
+
return path9.join(repoRoot, ".coop");
|
|
3037
3308
|
}
|
|
3038
3309
|
throw new Error(`Unsupported staged COOP task path '${relativePath}'.`);
|
|
3039
3310
|
}
|
|
3040
3311
|
function listTaskFilesForProject(projectRoot) {
|
|
3041
|
-
const tasksDir =
|
|
3312
|
+
const tasksDir = path9.join(projectRoot, "tasks");
|
|
3042
3313
|
if (!fs6.existsSync(tasksDir)) return [];
|
|
3043
3314
|
const out = [];
|
|
3044
3315
|
const stack = [tasksDir];
|
|
@@ -3046,12 +3317,12 @@ function listTaskFilesForProject(projectRoot) {
|
|
|
3046
3317
|
const current = stack.pop();
|
|
3047
3318
|
const entries = fs6.readdirSync(current, { withFileTypes: true });
|
|
3048
3319
|
for (const entry of entries) {
|
|
3049
|
-
const fullPath =
|
|
3320
|
+
const fullPath = path9.join(current, entry.name);
|
|
3050
3321
|
if (entry.isDirectory()) {
|
|
3051
3322
|
stack.push(fullPath);
|
|
3052
3323
|
continue;
|
|
3053
3324
|
}
|
|
3054
|
-
if (entry.isFile() &&
|
|
3325
|
+
if (entry.isFile() && path9.extname(entry.name).toLowerCase() === ".md") {
|
|
3055
3326
|
out.push(fullPath);
|
|
3056
3327
|
}
|
|
3057
3328
|
}
|
|
@@ -3070,7 +3341,7 @@ function parseStagedTasks(repoRoot, relativePaths) {
|
|
|
3070
3341
|
const errors = [];
|
|
3071
3342
|
const staged = [];
|
|
3072
3343
|
for (const relativePath of relativePaths) {
|
|
3073
|
-
const absolutePath =
|
|
3344
|
+
const absolutePath = path9.join(repoRoot, ...relativePath.split("/"));
|
|
3074
3345
|
const projectRoot = projectRootFromRelativePath(repoRoot, relativePath);
|
|
3075
3346
|
const stagedBlob = readGitBlob(repoRoot, `:${relativePath}`);
|
|
3076
3347
|
if (!stagedBlob) {
|
|
@@ -3134,7 +3405,7 @@ function collectTasksForCycleCheck(projectRoot, stagedTasks) {
|
|
|
3134
3405
|
}
|
|
3135
3406
|
const tasks = [];
|
|
3136
3407
|
for (const filePath of listTaskFilesForProject(projectRoot)) {
|
|
3137
|
-
const normalized = toPosixPath2(
|
|
3408
|
+
const normalized = toPosixPath2(path9.resolve(filePath));
|
|
3138
3409
|
const stagedTask = stagedByPath.get(normalized);
|
|
3139
3410
|
if (stagedTask) {
|
|
3140
3411
|
tasks.push(stagedTask);
|
|
@@ -3164,7 +3435,7 @@ function runPreCommitChecks(repoRoot) {
|
|
|
3164
3435
|
const graph = buildGraphForCycleCheck(tasks);
|
|
3165
3436
|
const cycle = detect_cycle(graph);
|
|
3166
3437
|
if (cycle) {
|
|
3167
|
-
const projectLabel = toPosixPath2(
|
|
3438
|
+
const projectLabel = toPosixPath2(path9.relative(repoRoot, projectRoot));
|
|
3168
3439
|
errors.push(`[COOP] Dependency cycle detected in ${projectLabel}: ${cycle.join(" -> ")}.`);
|
|
3169
3440
|
}
|
|
3170
3441
|
} catch (error) {
|
|
@@ -3196,8 +3467,8 @@ function hookScriptBlock() {
|
|
|
3196
3467
|
].join("\n");
|
|
3197
3468
|
}
|
|
3198
3469
|
function installPreCommitHook(repoRoot) {
|
|
3199
|
-
const hookPath =
|
|
3200
|
-
const hookDir =
|
|
3470
|
+
const hookPath = path9.join(repoRoot, ".git", "hooks", "pre-commit");
|
|
3471
|
+
const hookDir = path9.dirname(hookPath);
|
|
3201
3472
|
if (!fs6.existsSync(hookDir)) {
|
|
3202
3473
|
return {
|
|
3203
3474
|
installed: false,
|
|
@@ -3229,13 +3500,13 @@ function installPreCommitHook(repoRoot) {
|
|
|
3229
3500
|
|
|
3230
3501
|
// src/hooks/post-merge-validate.ts
|
|
3231
3502
|
import fs7 from "fs";
|
|
3232
|
-
import
|
|
3503
|
+
import path10 from "path";
|
|
3233
3504
|
import { list_projects } from "@kitsy/coop-core";
|
|
3234
3505
|
import { load_graph as load_graph4, validate_graph as validate_graph2 } from "@kitsy/coop-core";
|
|
3235
3506
|
var HOOK_BLOCK_START2 = "# COOP_POST_MERGE_START";
|
|
3236
3507
|
var HOOK_BLOCK_END2 = "# COOP_POST_MERGE_END";
|
|
3237
3508
|
function runPostMergeValidate(repoRoot) {
|
|
3238
|
-
const workspaceDir =
|
|
3509
|
+
const workspaceDir = path10.join(repoRoot, ".coop");
|
|
3239
3510
|
if (!fs7.existsSync(workspaceDir)) {
|
|
3240
3511
|
return {
|
|
3241
3512
|
ok: true,
|
|
@@ -3288,8 +3559,8 @@ function postMergeHookBlock() {
|
|
|
3288
3559
|
].join("\n");
|
|
3289
3560
|
}
|
|
3290
3561
|
function installPostMergeHook(repoRoot) {
|
|
3291
|
-
const hookPath =
|
|
3292
|
-
const hookDir =
|
|
3562
|
+
const hookPath = path10.join(repoRoot, ".git", "hooks", "post-merge");
|
|
3563
|
+
const hookDir = path10.dirname(hookPath);
|
|
3293
3564
|
if (!fs7.existsSync(hookDir)) {
|
|
3294
3565
|
return {
|
|
3295
3566
|
installed: false,
|
|
@@ -3339,7 +3610,7 @@ function aliasesYaml(aliases) {
|
|
|
3339
3610
|
return `
|
|
3340
3611
|
${aliases.map((alias) => ` - "${alias}"`).join("\n")}`;
|
|
3341
3612
|
}
|
|
3342
|
-
function buildProjectConfig(projectId, projectName, projectAliases) {
|
|
3613
|
+
function buildProjectConfig(projectId, projectName, projectAliases, namingTemplate) {
|
|
3343
3614
|
return `version: 2
|
|
3344
3615
|
project:
|
|
3345
3616
|
name: ${JSON.stringify(projectName)}
|
|
@@ -3353,7 +3624,7 @@ id_prefixes:
|
|
|
3353
3624
|
run: "RUN"
|
|
3354
3625
|
|
|
3355
3626
|
id:
|
|
3356
|
-
naming:
|
|
3627
|
+
naming: ${JSON.stringify(namingTemplate)}
|
|
3357
3628
|
seq_padding: 0
|
|
3358
3629
|
|
|
3359
3630
|
defaults:
|
|
@@ -3482,7 +3753,7 @@ function writeIfMissing(filePath, content) {
|
|
|
3482
3753
|
}
|
|
3483
3754
|
}
|
|
3484
3755
|
function ensureGitignoreEntry(root, entry) {
|
|
3485
|
-
const gitignorePath =
|
|
3756
|
+
const gitignorePath = path11.join(root, ".gitignore");
|
|
3486
3757
|
if (!fs8.existsSync(gitignorePath)) {
|
|
3487
3758
|
fs8.writeFileSync(gitignorePath, `${entry}
|
|
3488
3759
|
`, "utf8");
|
|
@@ -3497,7 +3768,7 @@ function ensureGitignoreEntry(root, entry) {
|
|
|
3497
3768
|
}
|
|
3498
3769
|
}
|
|
3499
3770
|
function ensureGitattributesEntry(root, entry) {
|
|
3500
|
-
const attrsPath =
|
|
3771
|
+
const attrsPath = path11.join(root, ".gitattributes");
|
|
3501
3772
|
if (!fs8.existsSync(attrsPath)) {
|
|
3502
3773
|
fs8.writeFileSync(attrsPath, `${entry}
|
|
3503
3774
|
`, "utf8");
|
|
@@ -3559,10 +3830,12 @@ async function promptInitIdentity(root, options) {
|
|
|
3559
3830
|
throw new Error("Invalid project id. Use letters, numbers, and hyphens.");
|
|
3560
3831
|
}
|
|
3561
3832
|
const aliasesInput = options.aliases !== void 0 ? options.aliases : await ask2("Aliases (csv, optional)", "");
|
|
3833
|
+
const namingTemplate = options.naming?.trim() || await ask2("ID naming template", DEFAULT_ID_NAMING_TEMPLATE);
|
|
3562
3834
|
return {
|
|
3563
3835
|
projectName,
|
|
3564
3836
|
projectId,
|
|
3565
|
-
projectAliases: parseAliases2(aliasesInput)
|
|
3837
|
+
projectAliases: parseAliases2(aliasesInput),
|
|
3838
|
+
namingTemplate
|
|
3566
3839
|
};
|
|
3567
3840
|
} finally {
|
|
3568
3841
|
rl.close();
|
|
@@ -3573,6 +3846,7 @@ async function resolveInitIdentity(root, options) {
|
|
|
3573
3846
|
const fallbackName = options.name?.trim() || defaultName;
|
|
3574
3847
|
const fallbackId = normalizeProjectId(options.id?.trim() || fallbackName) || repoIdentityId(root);
|
|
3575
3848
|
const fallbackAliases = parseAliases2(options.aliases);
|
|
3849
|
+
const fallbackNaming = options.naming?.trim() || DEFAULT_ID_NAMING_TEMPLATE;
|
|
3576
3850
|
const interactive = Boolean(options.interactive || !options.yes && input.isTTY && output.isTTY);
|
|
3577
3851
|
if (interactive) {
|
|
3578
3852
|
return promptInitIdentity(root, options);
|
|
@@ -3580,16 +3854,17 @@ async function resolveInitIdentity(root, options) {
|
|
|
3580
3854
|
return {
|
|
3581
3855
|
projectName: fallbackName,
|
|
3582
3856
|
projectId: fallbackId,
|
|
3583
|
-
projectAliases: fallbackAliases
|
|
3857
|
+
projectAliases: fallbackAliases,
|
|
3858
|
+
namingTemplate: fallbackNaming
|
|
3584
3859
|
};
|
|
3585
3860
|
}
|
|
3586
3861
|
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) => {
|
|
3862
|
+
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
3863
|
const root = resolveRepoRoot();
|
|
3589
3864
|
const workspaceDir = coopWorkspaceDir(root);
|
|
3590
3865
|
const identity = await resolveInitIdentity(root, options);
|
|
3591
3866
|
const projectId = identity.projectId;
|
|
3592
|
-
const projectRoot =
|
|
3867
|
+
const projectRoot = path11.join(workspaceDir, "projects", projectId);
|
|
3593
3868
|
const dirs = [
|
|
3594
3869
|
"ideas",
|
|
3595
3870
|
"tasks",
|
|
@@ -3605,23 +3880,23 @@ function registerInitCommand(program) {
|
|
|
3605
3880
|
"history/deliveries",
|
|
3606
3881
|
".index"
|
|
3607
3882
|
];
|
|
3608
|
-
ensureDir(
|
|
3883
|
+
ensureDir(path11.join(workspaceDir, "projects"));
|
|
3609
3884
|
for (const dir of dirs) {
|
|
3610
|
-
ensureDir(
|
|
3885
|
+
ensureDir(path11.join(projectRoot, dir));
|
|
3611
3886
|
}
|
|
3612
3887
|
writeIfMissing(
|
|
3613
|
-
|
|
3614
|
-
buildProjectConfig(projectId, identity.projectName, identity.projectAliases)
|
|
3888
|
+
path11.join(projectRoot, "config.yml"),
|
|
3889
|
+
buildProjectConfig(projectId, identity.projectName, identity.projectAliases, identity.namingTemplate)
|
|
3615
3890
|
);
|
|
3616
|
-
if (!fs8.existsSync(
|
|
3891
|
+
if (!fs8.existsSync(path11.join(projectRoot, "schema-version"))) {
|
|
3617
3892
|
write_schema_version(projectRoot, CURRENT_SCHEMA_VERSION);
|
|
3618
3893
|
}
|
|
3619
|
-
writeIfMissing(
|
|
3620
|
-
writeIfMissing(
|
|
3621
|
-
writeIfMissing(
|
|
3622
|
-
writeIfMissing(
|
|
3623
|
-
writeIfMissing(
|
|
3624
|
-
writeIfMissing(
|
|
3894
|
+
writeIfMissing(path11.join(projectRoot, "templates/task.md"), TASK_TEMPLATE);
|
|
3895
|
+
writeIfMissing(path11.join(projectRoot, "templates/idea.md"), IDEA_TEMPLATE);
|
|
3896
|
+
writeIfMissing(path11.join(projectRoot, "plugins/console-log.yml"), PLUGIN_CONSOLE_TEMPLATE);
|
|
3897
|
+
writeIfMissing(path11.join(projectRoot, "plugins/github-pr.yml"), PLUGIN_GITHUB_TEMPLATE);
|
|
3898
|
+
writeIfMissing(path11.join(workspaceDir, ".ignore"), COOP_IGNORE_TEMPLATE);
|
|
3899
|
+
writeIfMissing(path11.join(workspaceDir, ".gitignore"), COOP_IGNORE_TEMPLATE);
|
|
3625
3900
|
writeWorkspaceConfig(root, { version: 2, current_project: projectId });
|
|
3626
3901
|
ensureGitignoreEntry(root, ".coop/logs/");
|
|
3627
3902
|
ensureGitignoreEntry(root, ".coop/tmp/");
|
|
@@ -3631,17 +3906,18 @@ function registerInitCommand(program) {
|
|
|
3631
3906
|
const project = resolveProject(root, projectId);
|
|
3632
3907
|
console.log("Initialized COOP workspace.");
|
|
3633
3908
|
console.log(`- Root: ${root}`);
|
|
3634
|
-
console.log(`- Workspace: ${
|
|
3635
|
-
console.log(`- Project: ${project.id} (${
|
|
3909
|
+
console.log(`- Workspace: ${path11.relative(root, workspaceDir)}`);
|
|
3910
|
+
console.log(`- Project: ${project.id} (${path11.relative(root, project.root)})`);
|
|
3636
3911
|
console.log(`- Name: ${identity.projectName}`);
|
|
3637
3912
|
console.log(`- Aliases: ${identity.projectAliases.length > 0 ? identity.projectAliases.join(", ") : "(none)"}`);
|
|
3913
|
+
console.log(`- ID naming: ${identity.namingTemplate}`);
|
|
3638
3914
|
console.log(`- ${preCommitHook.message}`);
|
|
3639
3915
|
if (preCommitHook.installed) {
|
|
3640
|
-
console.log(`- Hook: ${
|
|
3916
|
+
console.log(`- Hook: ${path11.relative(root, preCommitHook.hookPath)}`);
|
|
3641
3917
|
}
|
|
3642
3918
|
console.log(`- ${postMergeHook.message}`);
|
|
3643
3919
|
if (postMergeHook.installed) {
|
|
3644
|
-
console.log(`- Hook: ${
|
|
3920
|
+
console.log(`- Hook: ${path11.relative(root, postMergeHook.hookPath)}`);
|
|
3645
3921
|
}
|
|
3646
3922
|
console.log(`- ${mergeDrivers}`);
|
|
3647
3923
|
console.log("- Next steps:");
|
|
@@ -3651,9 +3927,70 @@ function registerInitCommand(program) {
|
|
|
3651
3927
|
});
|
|
3652
3928
|
}
|
|
3653
3929
|
|
|
3930
|
+
// src/commands/lifecycle.ts
|
|
3931
|
+
import path12 from "path";
|
|
3932
|
+
import { parseTaskFile as parseTaskFile8 } from "@kitsy/coop-core";
|
|
3933
|
+
var lifecycleVerbs = [
|
|
3934
|
+
{
|
|
3935
|
+
name: "review",
|
|
3936
|
+
description: "Move a task into review",
|
|
3937
|
+
targetStatus: "in_review"
|
|
3938
|
+
},
|
|
3939
|
+
{
|
|
3940
|
+
name: "complete",
|
|
3941
|
+
description: "Complete a task",
|
|
3942
|
+
targetStatus: "done"
|
|
3943
|
+
},
|
|
3944
|
+
{
|
|
3945
|
+
name: "block",
|
|
3946
|
+
description: "Mark a task as blocked",
|
|
3947
|
+
targetStatus: "blocked"
|
|
3948
|
+
},
|
|
3949
|
+
{
|
|
3950
|
+
name: "unblock",
|
|
3951
|
+
description: "Move a blocked task back to todo",
|
|
3952
|
+
targetStatus: "todo"
|
|
3953
|
+
},
|
|
3954
|
+
{
|
|
3955
|
+
name: "reopen",
|
|
3956
|
+
description: "Move a task back to todo",
|
|
3957
|
+
targetStatus: "todo"
|
|
3958
|
+
},
|
|
3959
|
+
{
|
|
3960
|
+
name: "cancel",
|
|
3961
|
+
description: "Cancel a task",
|
|
3962
|
+
targetStatus: "canceled"
|
|
3963
|
+
}
|
|
3964
|
+
];
|
|
3965
|
+
function currentTaskSelection(root, id) {
|
|
3966
|
+
const reference = resolveReference(root, id, "task");
|
|
3967
|
+
const filePath = path12.join(root, ...reference.file.split("/"));
|
|
3968
|
+
const parsed = parseTaskFile8(filePath);
|
|
3969
|
+
return formatSelectedTask(
|
|
3970
|
+
{
|
|
3971
|
+
task: parsed.task,
|
|
3972
|
+
score: 0,
|
|
3973
|
+
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",
|
|
3974
|
+
fits_capacity: true,
|
|
3975
|
+
fits_wip: true
|
|
3976
|
+
},
|
|
3977
|
+
{}
|
|
3978
|
+
);
|
|
3979
|
+
}
|
|
3980
|
+
function registerLifecycleCommands(program) {
|
|
3981
|
+
for (const verb of lifecycleVerbs) {
|
|
3982
|
+
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) => {
|
|
3983
|
+
const root = resolveRepoRoot();
|
|
3984
|
+
console.log(currentTaskSelection(root, id));
|
|
3985
|
+
const result = await transitionTaskByReference(root, id, verb.targetStatus, options);
|
|
3986
|
+
console.log(`Updated ${result.task.id}: ${result.from} -> ${result.to}`);
|
|
3987
|
+
});
|
|
3988
|
+
}
|
|
3989
|
+
}
|
|
3990
|
+
|
|
3654
3991
|
// src/commands/list.ts
|
|
3655
|
-
import
|
|
3656
|
-
import { parseIdeaFile as parseIdeaFile3, parseTaskFile as
|
|
3992
|
+
import path13 from "path";
|
|
3993
|
+
import { parseIdeaFile as parseIdeaFile3, parseTaskFile as parseTaskFile9 } from "@kitsy/coop-core";
|
|
3657
3994
|
import chalk2 from "chalk";
|
|
3658
3995
|
|
|
3659
3996
|
// src/utils/not-implemented.ts
|
|
@@ -3683,7 +4020,7 @@ function sortByIdAsc(items) {
|
|
|
3683
4020
|
}
|
|
3684
4021
|
function loadTasks(root) {
|
|
3685
4022
|
return listTaskFiles(root).map((filePath) => ({
|
|
3686
|
-
task:
|
|
4023
|
+
task: parseTaskFile9(filePath).task,
|
|
3687
4024
|
filePath
|
|
3688
4025
|
}));
|
|
3689
4026
|
}
|
|
@@ -3723,7 +4060,7 @@ function listTasks(options) {
|
|
|
3723
4060
|
statusColor(entry.status),
|
|
3724
4061
|
entry.priority,
|
|
3725
4062
|
entry.track,
|
|
3726
|
-
|
|
4063
|
+
path13.relative(root, entry.filePath)
|
|
3727
4064
|
])
|
|
3728
4065
|
)
|
|
3729
4066
|
);
|
|
@@ -3758,7 +4095,7 @@ function listIdeas(options) {
|
|
|
3758
4095
|
statusColor(entry.status),
|
|
3759
4096
|
entry.priority,
|
|
3760
4097
|
entry.track,
|
|
3761
|
-
|
|
4098
|
+
path13.relative(root, entry.filePath)
|
|
3762
4099
|
])
|
|
3763
4100
|
)
|
|
3764
4101
|
);
|
|
@@ -3783,24 +4120,24 @@ function registerListCommand(program) {
|
|
|
3783
4120
|
|
|
3784
4121
|
// src/utils/logger.ts
|
|
3785
4122
|
import fs9 from "fs";
|
|
3786
|
-
import
|
|
4123
|
+
import path14 from "path";
|
|
3787
4124
|
function resolveRepoSafe(start = process.cwd()) {
|
|
3788
4125
|
try {
|
|
3789
4126
|
return resolveRepoRoot(start);
|
|
3790
4127
|
} catch {
|
|
3791
|
-
return
|
|
4128
|
+
return path14.resolve(start);
|
|
3792
4129
|
}
|
|
3793
4130
|
}
|
|
3794
4131
|
function resolveCliLogFile(start = process.cwd()) {
|
|
3795
4132
|
const root = resolveRepoSafe(start);
|
|
3796
4133
|
const workspace = coopWorkspaceDir(root);
|
|
3797
4134
|
if (fs9.existsSync(workspace)) {
|
|
3798
|
-
return
|
|
4135
|
+
return path14.join(workspace, "logs", "cli.log");
|
|
3799
4136
|
}
|
|
3800
|
-
return
|
|
4137
|
+
return path14.join(resolveCoopHome(), "logs", "cli.log");
|
|
3801
4138
|
}
|
|
3802
4139
|
function appendLogEntry(entry, logFile) {
|
|
3803
|
-
fs9.mkdirSync(
|
|
4140
|
+
fs9.mkdirSync(path14.dirname(logFile), { recursive: true });
|
|
3804
4141
|
fs9.appendFileSync(logFile, `${JSON.stringify(entry)}
|
|
3805
4142
|
`, "utf8");
|
|
3806
4143
|
}
|
|
@@ -3877,7 +4214,7 @@ function registerLogCommand(program) {
|
|
|
3877
4214
|
|
|
3878
4215
|
// src/commands/migrate.ts
|
|
3879
4216
|
import fs10 from "fs";
|
|
3880
|
-
import
|
|
4217
|
+
import path15 from "path";
|
|
3881
4218
|
import { createInterface as createInterface2 } from "readline/promises";
|
|
3882
4219
|
import { stdin as input2, stdout as output2 } from "process";
|
|
3883
4220
|
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 +4238,7 @@ function writeIfMissing2(filePath, content) {
|
|
|
3901
4238
|
}
|
|
3902
4239
|
}
|
|
3903
4240
|
function ensureGitignoreEntry2(root, entry) {
|
|
3904
|
-
const gitignorePath =
|
|
4241
|
+
const gitignorePath = path15.join(root, ".gitignore");
|
|
3905
4242
|
if (!fs10.existsSync(gitignorePath)) {
|
|
3906
4243
|
fs10.writeFileSync(gitignorePath, `${entry}
|
|
3907
4244
|
`, "utf8");
|
|
@@ -3936,7 +4273,7 @@ function legacyWorkspaceProjectEntries(root) {
|
|
|
3936
4273
|
"backlog",
|
|
3937
4274
|
"plans",
|
|
3938
4275
|
"releases"
|
|
3939
|
-
].filter((entry) => fs10.existsSync(
|
|
4276
|
+
].filter((entry) => fs10.existsSync(path15.join(workspaceDir, entry)));
|
|
3940
4277
|
}
|
|
3941
4278
|
function normalizeProjectId2(value) {
|
|
3942
4279
|
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
|
|
@@ -3981,7 +4318,7 @@ async function promptProjectIdentity(defaults, options) {
|
|
|
3981
4318
|
async function resolveMigrationIdentity(root, options) {
|
|
3982
4319
|
const existing = readCoopConfig(root);
|
|
3983
4320
|
const defaults = {
|
|
3984
|
-
projectName: existing.projectName ||
|
|
4321
|
+
projectName: existing.projectName || path15.basename(root),
|
|
3985
4322
|
projectId: normalizeProjectId2(existing.projectId || repoIdentityId(root)) || repoIdentityId(root),
|
|
3986
4323
|
projectAliases: options.aliases !== void 0 ? parseAliases3(options.aliases) : existing.projectAliases
|
|
3987
4324
|
};
|
|
@@ -4004,7 +4341,7 @@ async function migrateWorkspaceLayout(root, options) {
|
|
|
4004
4341
|
if (!fs10.existsSync(workspaceDir)) {
|
|
4005
4342
|
throw new Error("Missing .coop directory. Run 'coop init' first.");
|
|
4006
4343
|
}
|
|
4007
|
-
const projectsDir =
|
|
4344
|
+
const projectsDir = path15.join(workspaceDir, "projects");
|
|
4008
4345
|
const legacyEntries = legacyWorkspaceProjectEntries(root);
|
|
4009
4346
|
if (legacyEntries.length === 0 && fs10.existsSync(projectsDir)) {
|
|
4010
4347
|
console.log("[COOP] workspace layout already uses v2.");
|
|
@@ -4012,11 +4349,11 @@ async function migrateWorkspaceLayout(root, options) {
|
|
|
4012
4349
|
}
|
|
4013
4350
|
const identity = await resolveMigrationIdentity(root, options);
|
|
4014
4351
|
const projectId = identity.projectId;
|
|
4015
|
-
const projectRoot =
|
|
4352
|
+
const projectRoot = path15.join(projectsDir, projectId);
|
|
4016
4353
|
if (fs10.existsSync(projectRoot) && !options.force) {
|
|
4017
|
-
throw new Error(`Project destination '${
|
|
4354
|
+
throw new Error(`Project destination '${path15.relative(root, projectRoot)}' already exists. Re-run with --force.`);
|
|
4018
4355
|
}
|
|
4019
|
-
const changed = legacyEntries.map((entry) => `${
|
|
4356
|
+
const changed = legacyEntries.map((entry) => `${path15.join(".coop", entry)} -> ${path15.join(".coop", "projects", projectId, entry)}`);
|
|
4020
4357
|
changed.push(`.coop/config.yml -> workspace current_project=${projectId}`);
|
|
4021
4358
|
console.log(`Workspace layout migration (${options.dryRun ? "DRY RUN" : "APPLY"})`);
|
|
4022
4359
|
console.log(`- from: v1 flat layout`);
|
|
@@ -4035,17 +4372,17 @@ async function migrateWorkspaceLayout(root, options) {
|
|
|
4035
4372
|
fs10.mkdirSync(projectsDir, { recursive: true });
|
|
4036
4373
|
fs10.mkdirSync(projectRoot, { recursive: true });
|
|
4037
4374
|
for (const entry of legacyEntries) {
|
|
4038
|
-
const source =
|
|
4039
|
-
const destination =
|
|
4375
|
+
const source = path15.join(workspaceDir, entry);
|
|
4376
|
+
const destination = path15.join(projectRoot, entry);
|
|
4040
4377
|
if (fs10.existsSync(destination)) {
|
|
4041
4378
|
if (!options.force) {
|
|
4042
|
-
throw new Error(`Destination '${
|
|
4379
|
+
throw new Error(`Destination '${path15.relative(root, destination)}' already exists.`);
|
|
4043
4380
|
}
|
|
4044
4381
|
fs10.rmSync(destination, { recursive: true, force: true });
|
|
4045
4382
|
}
|
|
4046
4383
|
fs10.renameSync(source, destination);
|
|
4047
4384
|
}
|
|
4048
|
-
const movedConfigPath =
|
|
4385
|
+
const movedConfigPath = path15.join(projectRoot, "config.yml");
|
|
4049
4386
|
if (fs10.existsSync(movedConfigPath)) {
|
|
4050
4387
|
const movedConfig = parseYamlFile2(movedConfigPath);
|
|
4051
4388
|
const nextProject = typeof movedConfig.project === "object" && movedConfig.project !== null ? { ...movedConfig.project } : {};
|
|
@@ -4063,13 +4400,13 @@ async function migrateWorkspaceLayout(root, options) {
|
|
|
4063
4400
|
}
|
|
4064
4401
|
const workspace = readWorkspaceConfig(root);
|
|
4065
4402
|
writeWorkspaceConfig(root, { ...workspace, version: 2, current_project: projectId });
|
|
4066
|
-
writeIfMissing2(
|
|
4067
|
-
writeIfMissing2(
|
|
4403
|
+
writeIfMissing2(path15.join(workspaceDir, ".ignore"), COOP_IGNORE_TEMPLATE2);
|
|
4404
|
+
writeIfMissing2(path15.join(workspaceDir, ".gitignore"), COOP_IGNORE_TEMPLATE2);
|
|
4068
4405
|
ensureGitignoreEntry2(root, ".coop/logs/");
|
|
4069
4406
|
ensureGitignoreEntry2(root, ".coop/tmp/");
|
|
4070
4407
|
const manager = new IndexManager3(projectRoot);
|
|
4071
4408
|
manager.build_full_index();
|
|
4072
|
-
console.log(`[COOP] migrated workspace to v2 at ${
|
|
4409
|
+
console.log(`[COOP] migrated workspace to v2 at ${path15.relative(root, projectRoot)}`);
|
|
4073
4410
|
}
|
|
4074
4411
|
function registerMigrateCommand(program) {
|
|
4075
4412
|
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 +4425,7 @@ function registerMigrateCommand(program) {
|
|
|
4088
4425
|
if (report.changed_files.length > 0) {
|
|
4089
4426
|
console.log("- changed files:");
|
|
4090
4427
|
for (const filePath of report.changed_files) {
|
|
4091
|
-
console.log(` - ${
|
|
4428
|
+
console.log(` - ${path15.relative(root, filePath)}`);
|
|
4092
4429
|
}
|
|
4093
4430
|
}
|
|
4094
4431
|
if (report.dry_run) {
|
|
@@ -4108,6 +4445,75 @@ function registerMigrateCommand(program) {
|
|
|
4108
4445
|
});
|
|
4109
4446
|
}
|
|
4110
4447
|
|
|
4448
|
+
// src/commands/naming.ts
|
|
4449
|
+
function printNamingOverview() {
|
|
4450
|
+
const root = resolveRepoRoot();
|
|
4451
|
+
const config = readCoopConfig(root);
|
|
4452
|
+
const sampleTitle = "Natural-language COOP command recommender";
|
|
4453
|
+
const sampleTaskTitle = "Implement billing payment contract review";
|
|
4454
|
+
console.log("COOP Naming");
|
|
4455
|
+
console.log(`Current template: ${config.idNamingTemplate}`);
|
|
4456
|
+
console.log(`Default template: ${DEFAULT_ID_NAMING_TEMPLATE}`);
|
|
4457
|
+
console.log("Tokens:");
|
|
4458
|
+
console.log(" <TYPE> entity type such as IDEA, TASK, DELIVERY");
|
|
4459
|
+
console.log(" <TITLE> semantic title token (defaults to TITLE16)");
|
|
4460
|
+
console.log(" <TITLE16> semantic title token capped to 16 chars");
|
|
4461
|
+
console.log(" <TITLE24> semantic title token capped to 24 chars");
|
|
4462
|
+
console.log(" <TRACK> task track");
|
|
4463
|
+
console.log(" <SEQ> sequential number within the rendered pattern");
|
|
4464
|
+
console.log(" <USER> actor/user namespace");
|
|
4465
|
+
console.log(" <YYMMDD> short date token");
|
|
4466
|
+
console.log(" <RAND> random uniqueness token");
|
|
4467
|
+
console.log(" <PREFIX> entity prefix override");
|
|
4468
|
+
console.log("Examples:");
|
|
4469
|
+
const tokenExamples = namingTokenExamples(sampleTitle);
|
|
4470
|
+
for (const [token, value] of Object.entries(tokenExamples)) {
|
|
4471
|
+
console.log(` ${token.padEnd(10)} ${value}`);
|
|
4472
|
+
}
|
|
4473
|
+
console.log(`Preview (${DEFAULT_ID_NAMING_TEMPLATE}): ${previewNamingTemplate(DEFAULT_ID_NAMING_TEMPLATE, {
|
|
4474
|
+
entityType: "idea",
|
|
4475
|
+
title: sampleTitle
|
|
4476
|
+
}, root)}`);
|
|
4477
|
+
console.log(`Preview (${config.idNamingTemplate}): ${previewNamingTemplate(config.idNamingTemplate, {
|
|
4478
|
+
entityType: "task",
|
|
4479
|
+
title: sampleTaskTitle,
|
|
4480
|
+
track: "mvp",
|
|
4481
|
+
status: "todo",
|
|
4482
|
+
taskType: "spike"
|
|
4483
|
+
}, root)}`);
|
|
4484
|
+
console.log("Try:");
|
|
4485
|
+
console.log(` coop naming preview "${sampleTitle}"`);
|
|
4486
|
+
console.log(' coop config id.naming "<TYPE>-<TITLE24>"');
|
|
4487
|
+
console.log(' coop config id.naming "<TRACK>-<SEQ>"');
|
|
4488
|
+
}
|
|
4489
|
+
function registerNamingCommand(program) {
|
|
4490
|
+
const naming = program.command("naming").description("Explain COOP ID naming templates and preview examples");
|
|
4491
|
+
naming.action(() => {
|
|
4492
|
+
printNamingOverview();
|
|
4493
|
+
});
|
|
4494
|
+
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(
|
|
4495
|
+
(title, options) => {
|
|
4496
|
+
const root = resolveRepoRoot();
|
|
4497
|
+
const config = readCoopConfig(root);
|
|
4498
|
+
const entity = options.entity?.trim().toLowerCase() || "task";
|
|
4499
|
+
const template = options.template?.trim() || config.idNamingTemplate;
|
|
4500
|
+
console.log(
|
|
4501
|
+
previewNamingTemplate(
|
|
4502
|
+
template,
|
|
4503
|
+
{
|
|
4504
|
+
entityType: entity,
|
|
4505
|
+
title,
|
|
4506
|
+
track: options.track,
|
|
4507
|
+
status: options.status,
|
|
4508
|
+
taskType: options.taskType
|
|
4509
|
+
},
|
|
4510
|
+
root
|
|
4511
|
+
)
|
|
4512
|
+
);
|
|
4513
|
+
}
|
|
4514
|
+
);
|
|
4515
|
+
}
|
|
4516
|
+
|
|
4111
4517
|
// src/commands/plan.ts
|
|
4112
4518
|
import chalk3 from "chalk";
|
|
4113
4519
|
import {
|
|
@@ -4343,7 +4749,7 @@ function registerPlanCommand(program) {
|
|
|
4343
4749
|
|
|
4344
4750
|
// src/commands/project.ts
|
|
4345
4751
|
import fs11 from "fs";
|
|
4346
|
-
import
|
|
4752
|
+
import path16 from "path";
|
|
4347
4753
|
import { CURRENT_SCHEMA_VERSION as CURRENT_SCHEMA_VERSION3, write_schema_version as write_schema_version2 } from "@kitsy/coop-core";
|
|
4348
4754
|
var TASK_TEMPLATE2 = `---
|
|
4349
4755
|
id: TASK-001
|
|
@@ -4376,7 +4782,7 @@ linked_tasks: []
|
|
|
4376
4782
|
## Problem
|
|
4377
4783
|
What problem are you solving?
|
|
4378
4784
|
`;
|
|
4379
|
-
var PROJECT_CONFIG_TEMPLATE = (projectId, projectName) => `version: 2
|
|
4785
|
+
var PROJECT_CONFIG_TEMPLATE = (projectId, projectName, namingTemplate) => `version: 2
|
|
4380
4786
|
project:
|
|
4381
4787
|
name: "${projectName}"
|
|
4382
4788
|
id: "${projectId}"
|
|
@@ -4389,7 +4795,7 @@ id_prefixes:
|
|
|
4389
4795
|
run: "RUN"
|
|
4390
4796
|
|
|
4391
4797
|
id:
|
|
4392
|
-
naming:
|
|
4798
|
+
naming: ${JSON.stringify(namingTemplate)}
|
|
4393
4799
|
seq_padding: 0
|
|
4394
4800
|
|
|
4395
4801
|
defaults:
|
|
@@ -4462,19 +4868,19 @@ function writeIfMissing3(filePath, content) {
|
|
|
4462
4868
|
function normalizeProjectId3(value) {
|
|
4463
4869
|
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/-+/g, "-");
|
|
4464
4870
|
}
|
|
4465
|
-
function createProject(root, projectId, projectName) {
|
|
4871
|
+
function createProject(root, projectId, projectName, namingTemplate = DEFAULT_ID_NAMING_TEMPLATE) {
|
|
4466
4872
|
const workspaceDir = coopWorkspaceDir(root);
|
|
4467
|
-
const projectRoot =
|
|
4468
|
-
ensureDir2(
|
|
4873
|
+
const projectRoot = path16.join(workspaceDir, "projects", projectId);
|
|
4874
|
+
ensureDir2(path16.join(workspaceDir, "projects"));
|
|
4469
4875
|
for (const dir of PROJECT_DIRS) {
|
|
4470
|
-
ensureDir2(
|
|
4876
|
+
ensureDir2(path16.join(projectRoot, dir));
|
|
4471
4877
|
}
|
|
4472
|
-
writeIfMissing3(
|
|
4473
|
-
if (!fs11.existsSync(
|
|
4878
|
+
writeIfMissing3(path16.join(projectRoot, "config.yml"), PROJECT_CONFIG_TEMPLATE(projectId, projectName, namingTemplate));
|
|
4879
|
+
if (!fs11.existsSync(path16.join(projectRoot, "schema-version"))) {
|
|
4474
4880
|
write_schema_version2(projectRoot, CURRENT_SCHEMA_VERSION3);
|
|
4475
4881
|
}
|
|
4476
|
-
writeIfMissing3(
|
|
4477
|
-
writeIfMissing3(
|
|
4882
|
+
writeIfMissing3(path16.join(projectRoot, "templates/task.md"), TASK_TEMPLATE2);
|
|
4883
|
+
writeIfMissing3(path16.join(projectRoot, "templates/idea.md"), IDEA_TEMPLATE2);
|
|
4478
4884
|
return projectRoot;
|
|
4479
4885
|
}
|
|
4480
4886
|
function registerProjectCommand(program) {
|
|
@@ -4502,7 +4908,7 @@ function registerProjectCommand(program) {
|
|
|
4502
4908
|
}
|
|
4503
4909
|
console.log(`id=${active.id}`);
|
|
4504
4910
|
console.log(`name=${active.name}`);
|
|
4505
|
-
console.log(`path=${
|
|
4911
|
+
console.log(`path=${path16.relative(root, active.root)}`);
|
|
4506
4912
|
console.log(`layout=${active.layout}`);
|
|
4507
4913
|
});
|
|
4508
4914
|
project.command("use").description("Set the active COOP project").argument("<id>", "Project id").action((id) => {
|
|
@@ -4516,7 +4922,7 @@ function registerProjectCommand(program) {
|
|
|
4516
4922
|
writeWorkspaceConfig(root, { ...workspace, version: 2, current_project: match.id });
|
|
4517
4923
|
console.log(`current_project=${match.id}`);
|
|
4518
4924
|
});
|
|
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) => {
|
|
4925
|
+
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
4926
|
const root = resolveRepoRoot();
|
|
4521
4927
|
const projectId = normalizeProjectId3(id);
|
|
4522
4928
|
if (!projectId) {
|
|
@@ -4527,33 +4933,33 @@ function registerProjectCommand(program) {
|
|
|
4527
4933
|
throw new Error(`Project '${projectId}' already exists.`);
|
|
4528
4934
|
}
|
|
4529
4935
|
const projectName = options.name?.trim() || repoDisplayName(root);
|
|
4530
|
-
const projectRoot = createProject(root, projectId, projectName);
|
|
4936
|
+
const projectRoot = createProject(root, projectId, projectName, options.naming?.trim() || DEFAULT_ID_NAMING_TEMPLATE);
|
|
4531
4937
|
const workspace = readWorkspaceConfig(root);
|
|
4532
4938
|
writeWorkspaceConfig(root, {
|
|
4533
4939
|
...workspace,
|
|
4534
4940
|
version: 2,
|
|
4535
4941
|
current_project: workspace.current_project || projectId
|
|
4536
4942
|
});
|
|
4537
|
-
console.log(`Created project '${projectId}' at ${
|
|
4943
|
+
console.log(`Created project '${projectId}' at ${path16.relative(root, projectRoot)}`);
|
|
4538
4944
|
});
|
|
4539
4945
|
}
|
|
4540
4946
|
|
|
4541
4947
|
// src/commands/refine.ts
|
|
4542
4948
|
import fs12 from "fs";
|
|
4543
|
-
import
|
|
4544
|
-
import { parseIdeaFile as parseIdeaFile4, parseTaskFile as
|
|
4949
|
+
import path17 from "path";
|
|
4950
|
+
import { parseIdeaFile as parseIdeaFile4, parseTaskFile as parseTaskFile10 } from "@kitsy/coop-core";
|
|
4545
4951
|
import { create_provider_refinement_client, refine_idea_to_draft, refine_task_to_draft } from "@kitsy/coop-ai";
|
|
4546
4952
|
function resolveTaskFile(root, idOrAlias) {
|
|
4547
4953
|
const target = resolveReference(root, idOrAlias, "task");
|
|
4548
|
-
return
|
|
4954
|
+
return path17.join(root, ...target.file.split("/"));
|
|
4549
4955
|
}
|
|
4550
4956
|
function resolveIdeaFile2(root, idOrAlias) {
|
|
4551
4957
|
const target = resolveReference(root, idOrAlias, "idea");
|
|
4552
|
-
return
|
|
4958
|
+
return path17.join(root, ...target.file.split("/"));
|
|
4553
4959
|
}
|
|
4554
4960
|
async function readSupplementalInput(root, options) {
|
|
4555
4961
|
if (options.inputFile?.trim()) {
|
|
4556
|
-
return fs12.readFileSync(
|
|
4962
|
+
return fs12.readFileSync(path17.resolve(root, options.inputFile.trim()), "utf8");
|
|
4557
4963
|
}
|
|
4558
4964
|
if (options.stdin) {
|
|
4559
4965
|
return readStdinText();
|
|
@@ -4571,7 +4977,7 @@ function loadAuthorityContext(root, refs) {
|
|
|
4571
4977
|
for (const ref of refs ?? []) {
|
|
4572
4978
|
const filePart = extractRefFile(ref);
|
|
4573
4979
|
if (!filePart) continue;
|
|
4574
|
-
const fullPath =
|
|
4980
|
+
const fullPath = path17.resolve(root, filePart);
|
|
4575
4981
|
if (!fs12.existsSync(fullPath) || !fs12.statSync(fullPath).isFile()) continue;
|
|
4576
4982
|
out.push({
|
|
4577
4983
|
ref,
|
|
@@ -4610,7 +5016,7 @@ function registerRefineCommand(program) {
|
|
|
4610
5016
|
const root = resolveRepoRoot();
|
|
4611
5017
|
const projectDir = ensureCoopInitialized(root);
|
|
4612
5018
|
const taskFile = resolveTaskFile(root, id);
|
|
4613
|
-
const parsed =
|
|
5019
|
+
const parsed = parseTaskFile10(taskFile);
|
|
4614
5020
|
const supplemental = await readSupplementalInput(root, options);
|
|
4615
5021
|
const client = create_provider_refinement_client(readCoopConfig(root).raw);
|
|
4616
5022
|
const draft = await refine_task_to_draft(
|
|
@@ -4638,15 +5044,15 @@ function registerRefineCommand(program) {
|
|
|
4638
5044
|
const written = applyRefinementDraft(root, projectDir, draft);
|
|
4639
5045
|
console.log(`[COOP] applied draft from ${draftInput.source}: ${written.length} task file(s) updated`);
|
|
4640
5046
|
for (const filePath of written) {
|
|
4641
|
-
console.log(`- ${
|
|
5047
|
+
console.log(`- ${path17.relative(root, filePath)}`);
|
|
4642
5048
|
}
|
|
4643
5049
|
});
|
|
4644
5050
|
}
|
|
4645
5051
|
|
|
4646
5052
|
// src/commands/run.ts
|
|
4647
5053
|
import fs13 from "fs";
|
|
4648
|
-
import
|
|
4649
|
-
import { load_graph as load_graph6, parseTaskFile as
|
|
5054
|
+
import path18 from "path";
|
|
5055
|
+
import { load_graph as load_graph6, parseTaskFile as parseTaskFile11 } from "@kitsy/coop-core";
|
|
4650
5056
|
import {
|
|
4651
5057
|
build_contract,
|
|
4652
5058
|
create_provider_agent_client,
|
|
@@ -4655,11 +5061,11 @@ import {
|
|
|
4655
5061
|
} from "@kitsy/coop-ai";
|
|
4656
5062
|
function loadTask(root, idOrAlias) {
|
|
4657
5063
|
const target = resolveReference(root, idOrAlias, "task");
|
|
4658
|
-
const taskFile =
|
|
5064
|
+
const taskFile = path18.join(root, ...target.file.split("/"));
|
|
4659
5065
|
if (!fs13.existsSync(taskFile)) {
|
|
4660
5066
|
throw new Error(`Task file not found: ${target.file}`);
|
|
4661
5067
|
}
|
|
4662
|
-
return
|
|
5068
|
+
return parseTaskFile11(taskFile).task;
|
|
4663
5069
|
}
|
|
4664
5070
|
function printContract(contract) {
|
|
4665
5071
|
console.log(JSON.stringify(contract, null, 2));
|
|
@@ -4695,26 +5101,26 @@ function registerRunCommand(program) {
|
|
|
4695
5101
|
on_progress: (message) => console.log(`[COOP] ${message}`)
|
|
4696
5102
|
});
|
|
4697
5103
|
if (result.status === "failed") {
|
|
4698
|
-
throw new Error(`Run failed: ${result.run.id}. Log: ${
|
|
5104
|
+
throw new Error(`Run failed: ${result.run.id}. Log: ${path18.relative(root, result.log_path)}`);
|
|
4699
5105
|
}
|
|
4700
5106
|
if (result.status === "paused") {
|
|
4701
5107
|
console.log(`[COOP] run paused: ${result.run.id}`);
|
|
4702
|
-
console.log(`[COOP] log: ${
|
|
5108
|
+
console.log(`[COOP] log: ${path18.relative(root, result.log_path)}`);
|
|
4703
5109
|
return;
|
|
4704
5110
|
}
|
|
4705
5111
|
console.log(`[COOP] run completed: ${result.run.id}`);
|
|
4706
|
-
console.log(`[COOP] log: ${
|
|
5112
|
+
console.log(`[COOP] log: ${path18.relative(root, result.log_path)}`);
|
|
4707
5113
|
});
|
|
4708
5114
|
}
|
|
4709
5115
|
|
|
4710
5116
|
// src/server/api.ts
|
|
4711
5117
|
import fs14 from "fs";
|
|
4712
5118
|
import http2 from "http";
|
|
4713
|
-
import
|
|
5119
|
+
import path19 from "path";
|
|
4714
5120
|
import {
|
|
4715
5121
|
analyze_feasibility as analyze_feasibility2,
|
|
4716
5122
|
load_graph as load_graph7,
|
|
4717
|
-
parseTaskFile as
|
|
5123
|
+
parseTaskFile as parseTaskFile12,
|
|
4718
5124
|
resolve_external_dependencies
|
|
4719
5125
|
} from "@kitsy/coop-core";
|
|
4720
5126
|
function json(res, statusCode, payload) {
|
|
@@ -4756,12 +5162,12 @@ function taskSummary(graph, task, external = []) {
|
|
|
4756
5162
|
};
|
|
4757
5163
|
}
|
|
4758
5164
|
function taskFileById(root, id) {
|
|
4759
|
-
const tasksDir =
|
|
5165
|
+
const tasksDir = path19.join(resolveProject(root).root, "tasks");
|
|
4760
5166
|
if (!fs14.existsSync(tasksDir)) return null;
|
|
4761
5167
|
const entries = fs14.readdirSync(tasksDir, { withFileTypes: true });
|
|
4762
5168
|
const target = `${id}.md`.toLowerCase();
|
|
4763
5169
|
const match = entries.find((entry) => entry.isFile() && entry.name.toLowerCase() === target);
|
|
4764
|
-
return match ?
|
|
5170
|
+
return match ? path19.join(tasksDir, match.name) : null;
|
|
4765
5171
|
}
|
|
4766
5172
|
function loadRemoteConfig(root) {
|
|
4767
5173
|
const raw = readCoopConfig(root).raw;
|
|
@@ -4842,7 +5248,7 @@ function createApiServer(root, options = {}) {
|
|
|
4842
5248
|
notFound(res, `Task '${taskId}' not found.`);
|
|
4843
5249
|
return;
|
|
4844
5250
|
}
|
|
4845
|
-
const parsed =
|
|
5251
|
+
const parsed = parseTaskFile12(filePath);
|
|
4846
5252
|
const task = graph.nodes.get(parsed.task.id) ?? parsed.task;
|
|
4847
5253
|
json(res, 200, {
|
|
4848
5254
|
...workspaceMeta(repoRoot),
|
|
@@ -4851,7 +5257,7 @@ function createApiServer(root, options = {}) {
|
|
|
4851
5257
|
created: task.created,
|
|
4852
5258
|
updated: task.updated,
|
|
4853
5259
|
body: parsed.body,
|
|
4854
|
-
file_path:
|
|
5260
|
+
file_path: path19.relative(repoRoot, filePath).replace(/\\/g, "/")
|
|
4855
5261
|
}
|
|
4856
5262
|
});
|
|
4857
5263
|
return;
|
|
@@ -4940,8 +5346,8 @@ function registerServeCommand(program) {
|
|
|
4940
5346
|
|
|
4941
5347
|
// src/commands/show.ts
|
|
4942
5348
|
import fs15 from "fs";
|
|
4943
|
-
import
|
|
4944
|
-
import { parseIdeaFile as parseIdeaFile5, parseTaskFile as
|
|
5349
|
+
import path20 from "path";
|
|
5350
|
+
import { parseIdeaFile as parseIdeaFile5, parseTaskFile as parseTaskFile13 } from "@kitsy/coop-core";
|
|
4945
5351
|
function stringify(value) {
|
|
4946
5352
|
if (value === null || value === void 0) return "-";
|
|
4947
5353
|
if (Array.isArray(value)) return value.length > 0 ? value.join(", ") : "-";
|
|
@@ -4959,7 +5365,7 @@ function pushListSection(lines, title, values) {
|
|
|
4959
5365
|
}
|
|
4960
5366
|
}
|
|
4961
5367
|
function loadComputedFromIndex(root, taskId) {
|
|
4962
|
-
const indexPath =
|
|
5368
|
+
const indexPath = path20.join(ensureCoopInitialized(root), ".index", "tasks.json");
|
|
4963
5369
|
if (!fs15.existsSync(indexPath)) {
|
|
4964
5370
|
return null;
|
|
4965
5371
|
}
|
|
@@ -5004,8 +5410,8 @@ function showTask(taskId) {
|
|
|
5004
5410
|
const root = resolveRepoRoot();
|
|
5005
5411
|
const coop = ensureCoopInitialized(root);
|
|
5006
5412
|
const target = resolveReference(root, taskId, "task");
|
|
5007
|
-
const taskFile =
|
|
5008
|
-
const parsed =
|
|
5413
|
+
const taskFile = path20.join(root, ...target.file.split("/"));
|
|
5414
|
+
const parsed = parseTaskFile13(taskFile);
|
|
5009
5415
|
const task = parsed.task;
|
|
5010
5416
|
const body = parsed.body.trim();
|
|
5011
5417
|
const computed = loadComputedFromIndex(root, task.id);
|
|
@@ -5023,7 +5429,7 @@ function showTask(taskId) {
|
|
|
5023
5429
|
`Tags: ${stringify(task.tags)}`,
|
|
5024
5430
|
`Created: ${task.created}`,
|
|
5025
5431
|
`Updated: ${task.updated}`,
|
|
5026
|
-
`File: ${
|
|
5432
|
+
`File: ${path20.relative(root, taskFile)}`,
|
|
5027
5433
|
""
|
|
5028
5434
|
];
|
|
5029
5435
|
pushListSection(lines, "Acceptance", task.acceptance);
|
|
@@ -5048,7 +5454,7 @@ function showTask(taskId) {
|
|
|
5048
5454
|
"Computed:"
|
|
5049
5455
|
);
|
|
5050
5456
|
if (!computed) {
|
|
5051
|
-
lines.push(`index not built (${
|
|
5457
|
+
lines.push(`index not built (${path20.relative(root, path20.join(coop, ".index", "tasks.json"))} missing)`);
|
|
5052
5458
|
} else {
|
|
5053
5459
|
for (const [key, value] of Object.entries(computed)) {
|
|
5054
5460
|
lines.push(`- ${key}: ${stringify(value)}`);
|
|
@@ -5060,7 +5466,7 @@ function showIdea(ideaId) {
|
|
|
5060
5466
|
const root = resolveRepoRoot();
|
|
5061
5467
|
ensureCoopInitialized(root);
|
|
5062
5468
|
const target = resolveReference(root, ideaId, "idea");
|
|
5063
|
-
const ideaFile =
|
|
5469
|
+
const ideaFile = path20.join(root, ...target.file.split("/"));
|
|
5064
5470
|
const parsed = parseIdeaFile5(ideaFile);
|
|
5065
5471
|
const idea = parsed.idea;
|
|
5066
5472
|
const body = parsed.body.trim();
|
|
@@ -5074,7 +5480,7 @@ function showIdea(ideaId) {
|
|
|
5074
5480
|
`Tags: ${stringify(idea.tags)}`,
|
|
5075
5481
|
`Linked Tasks: ${stringify(idea.linked_tasks)}`,
|
|
5076
5482
|
`Created: ${idea.created}`,
|
|
5077
|
-
`File: ${
|
|
5483
|
+
`File: ${path20.relative(root, ideaFile)}`,
|
|
5078
5484
|
"",
|
|
5079
5485
|
"Body:",
|
|
5080
5486
|
body || "-"
|
|
@@ -5191,8 +5597,8 @@ function registerTransitionCommand(program) {
|
|
|
5191
5597
|
}
|
|
5192
5598
|
|
|
5193
5599
|
// src/commands/taskflow.ts
|
|
5194
|
-
import
|
|
5195
|
-
import { parseTaskFile as
|
|
5600
|
+
import path21 from "path";
|
|
5601
|
+
import { parseTaskFile as parseTaskFile14 } from "@kitsy/coop-core";
|
|
5196
5602
|
function normalizeExecutor(value) {
|
|
5197
5603
|
if (!value?.trim()) return void 0;
|
|
5198
5604
|
const normalized = value.trim().toLowerCase();
|
|
@@ -5229,8 +5635,8 @@ async function claimAndStart(root, taskId, options) {
|
|
|
5229
5635
|
}
|
|
5230
5636
|
}
|
|
5231
5637
|
const reference = resolveReference(root, taskId, "task");
|
|
5232
|
-
const filePath =
|
|
5233
|
-
const parsed =
|
|
5638
|
+
const filePath = path21.join(root, ...reference.file.split("/"));
|
|
5639
|
+
const parsed = parseTaskFile14(filePath);
|
|
5234
5640
|
if (parsed.task.status === "in_progress") {
|
|
5235
5641
|
console.log(`Task ${parsed.task.id} is already in_progress.`);
|
|
5236
5642
|
return;
|
|
@@ -5276,8 +5682,8 @@ function registerTaskFlowCommands(program) {
|
|
|
5276
5682
|
today: options.today
|
|
5277
5683
|
}).entry.task.id;
|
|
5278
5684
|
const reference = resolveReference(root, taskId, "task");
|
|
5279
|
-
const filePath =
|
|
5280
|
-
const parsed =
|
|
5685
|
+
const filePath = path21.join(root, ...reference.file.split("/"));
|
|
5686
|
+
const parsed = parseTaskFile14(filePath);
|
|
5281
5687
|
console.log(
|
|
5282
5688
|
formatSelectedTask(
|
|
5283
5689
|
{
|
|
@@ -5301,18 +5707,18 @@ function registerTaskFlowCommands(program) {
|
|
|
5301
5707
|
|
|
5302
5708
|
// src/commands/ui.ts
|
|
5303
5709
|
import fs16 from "fs";
|
|
5304
|
-
import
|
|
5710
|
+
import path22 from "path";
|
|
5305
5711
|
import { createRequire } from "module";
|
|
5306
5712
|
import { fileURLToPath } from "url";
|
|
5307
5713
|
import { spawn } from "child_process";
|
|
5308
5714
|
import { IndexManager as IndexManager4 } from "@kitsy/coop-core";
|
|
5309
5715
|
function findPackageRoot(entryPath) {
|
|
5310
|
-
let current =
|
|
5716
|
+
let current = path22.dirname(entryPath);
|
|
5311
5717
|
while (true) {
|
|
5312
|
-
if (fs16.existsSync(
|
|
5718
|
+
if (fs16.existsSync(path22.join(current, "package.json"))) {
|
|
5313
5719
|
return current;
|
|
5314
5720
|
}
|
|
5315
|
-
const parent =
|
|
5721
|
+
const parent = path22.dirname(current);
|
|
5316
5722
|
if (parent === current) {
|
|
5317
5723
|
throw new Error(`Unable to locate package root for ${entryPath}.`);
|
|
5318
5724
|
}
|
|
@@ -5361,9 +5767,9 @@ async function startUiServer(repoRoot, host, port, shouldOpen) {
|
|
|
5361
5767
|
const project = resolveProject(repoRoot);
|
|
5362
5768
|
ensureIndex(repoRoot);
|
|
5363
5769
|
const uiRoot = resolveUiPackageRoot();
|
|
5364
|
-
const requireFromUi = createRequire(
|
|
5770
|
+
const requireFromUi = createRequire(path22.join(uiRoot, "package.json"));
|
|
5365
5771
|
const vitePackageJson = requireFromUi.resolve("vite/package.json");
|
|
5366
|
-
const viteBin =
|
|
5772
|
+
const viteBin = path22.join(path22.dirname(vitePackageJson), "bin", "vite.js");
|
|
5367
5773
|
const url = `http://${host}:${port}`;
|
|
5368
5774
|
console.log(`COOP UI: ${url}`);
|
|
5369
5775
|
const child = spawn(process.execPath, [viteBin, "--host", host, "--port", String(port)], {
|
|
@@ -5722,7 +6128,7 @@ function registerWebhookCommand(program) {
|
|
|
5722
6128
|
// src/merge-driver/merge-driver.ts
|
|
5723
6129
|
import fs17 from "fs";
|
|
5724
6130
|
import os2 from "os";
|
|
5725
|
-
import
|
|
6131
|
+
import path23 from "path";
|
|
5726
6132
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
5727
6133
|
import { stringifyFrontmatter as stringifyFrontmatter4, parseFrontmatterContent as parseFrontmatterContent3, parseYamlContent as parseYamlContent3, stringifyYamlContent as stringifyYamlContent2 } from "@kitsy/coop-core";
|
|
5728
6134
|
var STATUS_RANK = {
|
|
@@ -5812,11 +6218,11 @@ function mergeTaskFile(ancestorPath, oursPath, theirsPath) {
|
|
|
5812
6218
|
const ours = parseTaskDocument(oursRaw, oursPath);
|
|
5813
6219
|
const theirs = parseTaskDocument(theirsRaw, theirsPath);
|
|
5814
6220
|
const mergedFrontmatter = mergeTaskFrontmatter(ancestor.frontmatter, ours.frontmatter, theirs.frontmatter);
|
|
5815
|
-
const tempDir = fs17.mkdtempSync(
|
|
6221
|
+
const tempDir = fs17.mkdtempSync(path23.join(os2.tmpdir(), "coop-merge-body-"));
|
|
5816
6222
|
try {
|
|
5817
|
-
const ancestorBody =
|
|
5818
|
-
const oursBody =
|
|
5819
|
-
const theirsBody =
|
|
6223
|
+
const ancestorBody = path23.join(tempDir, "ancestor.md");
|
|
6224
|
+
const oursBody = path23.join(tempDir, "ours.md");
|
|
6225
|
+
const theirsBody = path23.join(tempDir, "theirs.md");
|
|
5820
6226
|
fs17.writeFileSync(ancestorBody, ancestor.body, "utf8");
|
|
5821
6227
|
fs17.writeFileSync(oursBody, ours.body, "utf8");
|
|
5822
6228
|
fs17.writeFileSync(theirsBody, theirs.body, "utf8");
|
|
@@ -5854,7 +6260,7 @@ function runMergeDriver(kind, ancestorPath, oursPath, theirsPath) {
|
|
|
5854
6260
|
// src/index.ts
|
|
5855
6261
|
function readVersion() {
|
|
5856
6262
|
const currentFile = fileURLToPath2(import.meta.url);
|
|
5857
|
-
const packageJsonPath =
|
|
6263
|
+
const packageJsonPath = path24.resolve(path24.dirname(currentFile), "..", "package.json");
|
|
5858
6264
|
try {
|
|
5859
6265
|
const parsed = JSON.parse(fs18.readFileSync(packageJsonPath, "utf8"));
|
|
5860
6266
|
return parsed.version ?? "0.0.0";
|
|
@@ -5887,7 +6293,9 @@ function createProgram() {
|
|
|
5887
6293
|
registerHelpAiCommand(program);
|
|
5888
6294
|
registerIndexCommand(program);
|
|
5889
6295
|
registerLogCommand(program);
|
|
6296
|
+
registerLifecycleCommands(program);
|
|
5890
6297
|
registerMigrateCommand(program);
|
|
6298
|
+
registerNamingCommand(program);
|
|
5891
6299
|
registerPlanCommand(program);
|
|
5892
6300
|
registerProjectCommand(program);
|
|
5893
6301
|
registerRefineCommand(program);
|
|
@@ -5954,7 +6362,7 @@ async function runCli(argv = process.argv) {
|
|
|
5954
6362
|
function isMainModule() {
|
|
5955
6363
|
const entry = process.argv[1];
|
|
5956
6364
|
if (!entry) return false;
|
|
5957
|
-
return
|
|
6365
|
+
return path24.resolve(entry) === fileURLToPath2(import.meta.url);
|
|
5958
6366
|
}
|
|
5959
6367
|
if (isMainModule()) {
|
|
5960
6368
|
await runCli(process.argv);
|