deuk-agent-rule 2.2.1 ā 2.3.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/CHANGELOG.md +177 -132
- package/LICENSE +0 -0
- package/README.ko.md +103 -79
- package/README.md +102 -77
- package/bundle/.cursorrules +7 -7
- package/bundle/AGENTS.md +70 -87
- package/bundle/rules/delivery-and-parallel-work.mdc +26 -26
- package/bundle/rules/git-commit.mdc +24 -18
- package/bundle/rules/multi-ai-workflow.mdc +0 -1
- package/bundle/templates/MODULE_RULE_TEMPLATE.md +24 -0
- package/bundle/templates/TICKET_TEMPLATE.md +40 -0
- package/package.json +5 -3
- package/scripts/changelog-polish.mjs +0 -0
- package/scripts/cli-args.mjs +11 -0
- package/scripts/cli-init-commands.mjs +27 -2
- package/scripts/cli-init-logic.mjs +4 -0
- package/scripts/cli-prompts.mjs +17 -45
- package/scripts/cli-ticket-commands.mjs +206 -52
- package/scripts/cli-ticket-logic.mjs +322 -46
- package/scripts/cli-utils.mjs +64 -1
- package/scripts/cli.mjs +20 -6
- package/scripts/merge-logic.mjs +0 -0
- package/scripts/sync-bundle.mjs +0 -0
- package/scripts/sync-oss.mjs +3 -10
- package/README.ko.pdf +0 -0
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deuk-agent-rule",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"
|
|
5
|
-
"description": "DeukAgentRules",
|
|
3
|
+
"version": "2.3.1",
|
|
4
|
+
"description": "DeukAgentRules: generic AGENTS.md + .cursor rule templates with init/merge CLI (npm name: deuk-agent-rule).",
|
|
6
5
|
"keywords": [
|
|
7
6
|
"agents-md",
|
|
8
7
|
"cursor-rules",
|
|
@@ -49,5 +48,8 @@
|
|
|
49
48
|
},
|
|
50
49
|
"devDependencies": {
|
|
51
50
|
"commit-and-tag-version": "^12.7.1"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"ejs": "^5.0.2"
|
|
52
54
|
}
|
|
53
55
|
}
|
|
File without changes
|
package/scripts/cli-args.mjs
CHANGED
|
@@ -12,11 +12,18 @@ export function parseTicketArgs(argv) {
|
|
|
12
12
|
else if (a === "--from") out.from = argv[++i];
|
|
13
13
|
else if (a === "--ref") out.ref = argv[++i];
|
|
14
14
|
else if (a === "--limit") out.limit = Number(argv[++i]);
|
|
15
|
+
else if (a === "--submodule") out.submodule = argv[++i];
|
|
15
16
|
else if (a === "--latest") out.latest = true;
|
|
16
17
|
else if (a === "--path-only") out.pathOnly = true;
|
|
17
18
|
else if (a === "--print-content") out.printContent = true;
|
|
18
19
|
else if (a === "--all") out.all = true;
|
|
19
20
|
else if (a === "--status") out.status = argv[++i];
|
|
21
|
+
else if (a === "--archived") out.archived = true;
|
|
22
|
+
else if (a === "--report") out.report = argv[++i];
|
|
23
|
+
else if (a === "--json") out.json = true;
|
|
24
|
+
else if (a === "--remote") out.remote = argv[++i];
|
|
25
|
+
else if (a === "--sync") out.sync = true;
|
|
26
|
+
else if (a === "--no-sync") out.sync = false;
|
|
20
27
|
}
|
|
21
28
|
return out;
|
|
22
29
|
}
|
|
@@ -37,6 +44,10 @@ export function parseArgs(argv) {
|
|
|
37
44
|
else if (a === "--rules") out.rules = argv[++i];
|
|
38
45
|
else if (a === "--cursorrules") out.cursorrules = argv[++i];
|
|
39
46
|
else if (a === "--append-if-no-markers") out.appendIfNoMarkers = true;
|
|
47
|
+
else if (a === "--json") out.json = true;
|
|
48
|
+
else if (a === "--remote") out.remote = argv[++i];
|
|
49
|
+
else if (a === "--sync") out.sync = true;
|
|
50
|
+
else if (a === "--no-sync") out.sync = false;
|
|
40
51
|
else if (a === "-h" || a === "--help") out.help = true;
|
|
41
52
|
}
|
|
42
53
|
return out;
|
|
@@ -1,8 +1,25 @@
|
|
|
1
1
|
import { join } from "path";
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync } from "fs";
|
|
3
3
|
import { resolveMarkers, resolveCursorrulesMarkers, applyAgents, applyRules, applyCursorrules, readBundleAgents } from "./merge-logic.mjs";
|
|
4
4
|
import { ensureTicketDirAndGitignore } from "./cli-init-logic.mjs";
|
|
5
|
-
import { loadInitConfig, writeInitConfig } from "./cli-
|
|
5
|
+
import { loadInitConfig, writeInitConfig } from "./cli-utils.mjs";
|
|
6
|
+
import { runInteractive } from "./cli-prompts.mjs";
|
|
7
|
+
|
|
8
|
+
function syncTemplates(cwd, bundleRoot, dryRun) {
|
|
9
|
+
const tplSrcDir = join(bundleRoot, "templates");
|
|
10
|
+
const tplDestDir = join(cwd, ".deuk-agent-templates");
|
|
11
|
+
if (!existsSync(tplSrcDir)) return;
|
|
12
|
+
if (!dryRun) mkdirSync(tplDestDir, { recursive: true });
|
|
13
|
+
|
|
14
|
+
for (const name of readdirSync(tplSrcDir)) {
|
|
15
|
+
if (!name.endsWith(".md")) continue;
|
|
16
|
+
const src = join(tplSrcDir, name);
|
|
17
|
+
const dest = join(tplDestDir, name);
|
|
18
|
+
if (!dryRun) copyFileSync(src, dest);
|
|
19
|
+
console.log(`template synced: ${dest}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
6
23
|
|
|
7
24
|
export async function runInit(opts, bundleRoot) {
|
|
8
25
|
const markers = resolveMarkers(opts);
|
|
@@ -33,6 +50,12 @@ export async function runInit(opts, bundleRoot) {
|
|
|
33
50
|
console.log(`.cursorrules: ${crResult.action} (${crResult.mode || ""})`);
|
|
34
51
|
|
|
35
52
|
ensureTicketDirAndGitignore(opts);
|
|
53
|
+
syncTemplates(opts.cwd, bundleRoot, opts.dryRun);
|
|
54
|
+
|
|
55
|
+
// If no config exists, save the derived/default config to ensure persistency
|
|
56
|
+
if (!loadInitConfig(opts.cwd)) {
|
|
57
|
+
writeInitConfig(opts.cwd, opts);
|
|
58
|
+
}
|
|
36
59
|
}
|
|
37
60
|
|
|
38
61
|
export function runMerge(opts, bundleRoot) {
|
|
@@ -62,4 +85,6 @@ export function runMerge(opts, bundleRoot) {
|
|
|
62
85
|
dryRun: opts.dryRun, backup: opts.backup
|
|
63
86
|
});
|
|
64
87
|
console.log(`.cursorrules: ${crResult.action} (${crResult.mode || ""})`);
|
|
88
|
+
|
|
89
|
+
syncTemplates(opts.cwd, bundleRoot, opts.dryRun);
|
|
65
90
|
}
|
|
@@ -12,6 +12,10 @@ export function ensureTicketDirAndGitignore(opts) {
|
|
|
12
12
|
if (opts.dryRun) return;
|
|
13
13
|
|
|
14
14
|
mkdirSync(ticketPath, { recursive: true });
|
|
15
|
+
if (opts.shareTickets) {
|
|
16
|
+
console.log(`[INIT] Ticket sharing enabled. Skipping .gitignore entry for ${TICKET_DIR_NAME}/`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
15
19
|
|
|
16
20
|
let gi = existsSync(gitignorePath) ? readFileSync(gitignorePath, "utf8") : "";
|
|
17
21
|
if (!gi.includes(ignoreLine)) {
|
package/scripts/cli-prompts.mjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { createInterface } from "readline";
|
|
2
|
-
import { existsSync, readFileSync
|
|
2
|
+
import { existsSync, readFileSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
|
-
|
|
5
|
-
const INIT_CONFIG_FILENAME = ".deuk-agent-rule.config.json";
|
|
6
|
-
const INIT_CONFIG_VERSION = 1;
|
|
4
|
+
import { loadInitConfig, writeInitConfig, STACKS, AGENT_TOOLS } from "./cli-utils.mjs";
|
|
7
5
|
|
|
8
6
|
export async function ask(rl, question) {
|
|
9
7
|
return new Promise((resolve) => rl.question(question, resolve));
|
|
@@ -41,48 +39,7 @@ export async function selectMany(rl, prompt, choices) {
|
|
|
41
39
|
}
|
|
42
40
|
}
|
|
43
41
|
|
|
44
|
-
export function loadInitConfig(cwd) {
|
|
45
|
-
const p = join(cwd, INIT_CONFIG_FILENAME);
|
|
46
|
-
if (!existsSync(p)) return null;
|
|
47
|
-
try {
|
|
48
|
-
const j = JSON.parse(readFileSync(p, "utf8"));
|
|
49
|
-
if (j.version !== INIT_CONFIG_VERSION) return null;
|
|
50
|
-
return j;
|
|
51
|
-
} catch {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function writeInitConfig(cwd, opts) {
|
|
57
|
-
const p = join(cwd, INIT_CONFIG_FILENAME);
|
|
58
|
-
const body = {
|
|
59
|
-
version: INIT_CONFIG_VERSION,
|
|
60
|
-
stack: opts.stack,
|
|
61
|
-
agentTools: opts.agentTools,
|
|
62
|
-
agentsMode: opts.agents ?? "inject",
|
|
63
|
-
updatedAt: new Date().toISOString(),
|
|
64
|
-
};
|
|
65
|
-
writeFileSync(p, JSON.stringify(body, null, 2) + "\n", "utf8");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export const STACKS = [
|
|
69
|
-
{ label: "Unity / C#", value: "unity" },
|
|
70
|
-
{ label: "Next.js + C#", value: "nextjs-dotnet" },
|
|
71
|
-
{ label: "Web (React / Vue / general)", value: "web" },
|
|
72
|
-
{ label: "Java / Spring Boot", value: "java" },
|
|
73
|
-
{ label: "Other / skip", value: "other" },
|
|
74
|
-
];
|
|
75
42
|
|
|
76
|
-
export const AGENT_TOOLS = [
|
|
77
|
-
{ label: "Cursor", value: "cursor" },
|
|
78
|
-
{ label: "GitHub Copilot", value: "copilot" },
|
|
79
|
-
{ label: "Gemini / Antigravity", value: "gemini" },
|
|
80
|
-
{ label: "Claude (Cursor / Claude Code)", value: "claude" },
|
|
81
|
-
{ label: "Windsurf", value: "windsurf" },
|
|
82
|
-
{ label: "JetBrains AI Assistant", value: "jetbrains" },
|
|
83
|
-
{ label: "All of the above", value: "all" },
|
|
84
|
-
{ label: "Other / skip", value: "other" },
|
|
85
|
-
];
|
|
86
43
|
|
|
87
44
|
export async function runInteractive(opts) {
|
|
88
45
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -91,6 +48,7 @@ export async function runInteractive(opts) {
|
|
|
91
48
|
|
|
92
49
|
const stack = await selectOne(rl, "What is your primary tech stack?", STACKS);
|
|
93
50
|
const tools = await selectMany(rl, "Which agent tools do you use?", AGENT_TOOLS);
|
|
51
|
+
const shareTickets = await askYesNo("Do you want to share (git-track) tickets for this repository?", false);
|
|
94
52
|
|
|
95
53
|
const targetAgents = join(opts.cwd, "AGENTS.md");
|
|
96
54
|
let agentsDefault = "inject";
|
|
@@ -110,12 +68,26 @@ export async function runInteractive(opts) {
|
|
|
110
68
|
}
|
|
111
69
|
}
|
|
112
70
|
|
|
71
|
+
const remoteSync = opts.remoteSync !== undefined ? opts.remoteSync : (await askYesNo("Enable AI Pipeline remote synchronization? (Advanced)", false));
|
|
72
|
+
let pipelineUrl = opts.pipelineUrl || "";
|
|
73
|
+
if (remoteSync && !pipelineUrl) {
|
|
74
|
+
pipelineUrl = (await ask(rl, "Enter AI Pipeline Endpoint URL: ")).trim();
|
|
75
|
+
}
|
|
76
|
+
|
|
113
77
|
opts.agents = opts.agents ?? agentsDefault;
|
|
114
78
|
opts.stack = stack;
|
|
115
79
|
opts.agentTools = tools;
|
|
80
|
+
opts.shareTickets = shareTickets;
|
|
81
|
+
opts.remoteSync = remoteSync;
|
|
82
|
+
opts.pipelineUrl = pipelineUrl;
|
|
83
|
+
|
|
84
|
+
writeInitConfig(opts.cwd, opts);
|
|
116
85
|
|
|
117
86
|
console.log("\n Stack : " + stack);
|
|
118
87
|
console.log(" Tools : " + (tools.join(", ") || "none"));
|
|
88
|
+
console.log(" Share Tickets: " + (opts.shareTickets ? "Yes (Shared)" : "No (Private)"));
|
|
89
|
+
console.log(" Remote Sync: " + (opts.remoteSync ? "Enabled" : "Disabled"));
|
|
90
|
+
if (opts.remoteSync) console.log(" Pipeline URL: " + opts.pipelineUrl);
|
|
119
91
|
console.log(" AGENTS: " + opts.agents + "\n");
|
|
120
92
|
} finally {
|
|
121
93
|
rl.close();
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
|
|
2
|
-
import { basename, join } from "path";
|
|
3
|
-
import { toSlug, toRepoRelativePath, inferRefTitleAndTopic, resolveReferencedTicketPath } from "./cli-utils.mjs";
|
|
4
|
-
import { TICKET_DIR_NAME, appendTicketEntry, rebuildTicketIndexFromTopicFilesIfNeeded, detectConsumerTicketDir } from "./cli-ticket-logic.mjs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, copyFileSync, readdirSync, rmSync, statSync } from "fs";
|
|
2
|
+
import { basename, join, dirname, relative, resolve } from "path";
|
|
3
|
+
import { toSlug, toRepoRelativePath, inferRefTitleAndTopic, resolveReferencedTicketPath, toPosixPath, stringifyFrontMatter } from "./cli-utils.mjs";
|
|
4
|
+
import { TICKET_DIR_NAME, appendTicketEntry, rebuildTicketIndexFromTopicFilesIfNeeded, detectConsumerTicketDir, readTicketIndexJson, writeTicketIndexJson, writeTicketListFile, syncActiveTicketPointer, generateTicketId, syncToPipeline } from "./cli-ticket-logic.mjs";
|
|
5
|
+
import { loadInitConfig } from "./cli-utils.mjs";
|
|
6
|
+
import ejs from "ejs";
|
|
5
7
|
|
|
6
8
|
import { createInterface } from "readline";
|
|
7
9
|
import { selectOne } from "./cli-prompts.mjs";
|
|
@@ -19,21 +21,60 @@ export async function runTicketCreate(opts) {
|
|
|
19
21
|
path = resolveReferencedTicketPath(opts);
|
|
20
22
|
source = "ticket-reference";
|
|
21
23
|
} else {
|
|
22
|
-
let
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
24
|
+
let tplText = "";
|
|
25
|
+
const consumerTplPath = join(opts.cwd, ".deuk-agent-templates", "TICKET_TEMPLATE.md");
|
|
26
|
+
const bundleTplPath = join(new URL('.', import.meta.url).pathname, "..", "bundle", "templates", "TICKET_TEMPLATE.md");
|
|
27
|
+
|
|
28
|
+
if (existsSync(consumerTplPath)) tplText = readFileSync(consumerTplPath, "utf8");
|
|
29
|
+
else if (existsSync(bundleTplPath)) tplText = readFileSync(bundleTplPath, "utf8");
|
|
30
|
+
else throw new Error("ticket create: Template not found. Refusing to create an empty ticket.");
|
|
31
|
+
|
|
32
|
+
// Find nearest or create in CWD if missing
|
|
33
|
+
const ticketDir = detectConsumerTicketDir(opts.cwd, { createIfMissing: true });
|
|
34
|
+
const abs = join(ticketDir, group, `${topic}-${Date.now()}.md`);
|
|
35
|
+
mkdirSync(join(ticketDir, group), { recursive: true });
|
|
26
36
|
path = toRepoRelativePath(opts.cwd, abs);
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
|
|
38
|
+
const ticketId = generateTicketId(title);
|
|
39
|
+
const meta = {
|
|
40
|
+
id: ticketId,
|
|
41
|
+
title,
|
|
42
|
+
topic,
|
|
43
|
+
status: "open",
|
|
44
|
+
submodule: opts.submodule || "",
|
|
45
|
+
project: opts.project || "global",
|
|
46
|
+
createdAt: new Date().toISOString(),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const ejsFrontMatter = `---
|
|
50
|
+
id: <%= meta.id %>
|
|
51
|
+
title: "<%- meta.title.replace(/"/g, '\\"') %>"
|
|
52
|
+
topic: <%= meta.topic %>
|
|
53
|
+
status: open
|
|
54
|
+
submodule: <%= meta.submodule %>
|
|
55
|
+
project: <%= meta.project %>
|
|
56
|
+
createdAt: <%= meta.createdAt %>
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
`;
|
|
60
|
+
const finalContent = ejs.render(ejsFrontMatter + tplText, { meta });
|
|
61
|
+
writeFileSync(abs, finalContent, "utf8");
|
|
29
62
|
source = "ticket-create";
|
|
63
|
+
|
|
64
|
+
// Remote Sync Hook
|
|
65
|
+
const config = loadInitConfig(opts.cwd);
|
|
66
|
+
if (config && config.remoteSync && config.pipelineUrl) {
|
|
67
|
+
syncToPipeline(config.pipelineUrl, { action: "create", ticket: meta });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
appendTicketEntry(opts.cwd, {
|
|
71
|
+
id: ticketId,
|
|
72
|
+
title, topic, group, project: opts.project || "global",
|
|
73
|
+
createdAt: new Date().toISOString(), path, source
|
|
74
|
+
}, opts);
|
|
30
75
|
}
|
|
31
76
|
|
|
32
|
-
|
|
33
|
-
id: `ticket_${Date.now()}`,
|
|
34
|
-
title, topic, group, project: opts.project || "global",
|
|
35
|
-
createdAt: new Date().toISOString(), path, source
|
|
36
|
-
}, opts);
|
|
77
|
+
syncActiveTicketPointer(opts.cwd);
|
|
37
78
|
}
|
|
38
79
|
|
|
39
80
|
export async function runTicketList(opts) {
|
|
@@ -42,24 +83,64 @@ export async function runTicketList(opts) {
|
|
|
42
83
|
throw new Error("No ticket system found. Please run 'npx deuk-agent-rule init' first.");
|
|
43
84
|
}
|
|
44
85
|
const index = rebuildTicketIndexFromTopicFilesIfNeeded(opts.cwd, opts);
|
|
86
|
+
syncActiveTicketPointer(opts.cwd);
|
|
45
87
|
let rows = index.entries;
|
|
88
|
+
|
|
46
89
|
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
|
|
90
|
+
if (opts.active) {
|
|
91
|
+
rows = rows.filter(e => e.status === "active" || e.status === "open");
|
|
92
|
+
} else if (opts.archived) {
|
|
93
|
+
rows = rows.filter(e => e.status === "archived");
|
|
94
|
+
} else if (!opts.all) {
|
|
95
|
+
// Default: major/active list (open or active)
|
|
96
|
+
rows = rows.filter(e => e.status === "open" || e.status === "active");
|
|
50
97
|
}
|
|
51
98
|
|
|
52
99
|
if (opts.group) rows = rows.filter(e => e.group === opts.group);
|
|
53
100
|
if (opts.project) rows = rows.filter(e => e.project === opts.project);
|
|
101
|
+
if (opts.submodule) rows = rows.filter(e => e.submodule === opts.submodule);
|
|
54
102
|
|
|
55
|
-
|
|
103
|
+
if (opts.json) {
|
|
104
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log("# STATUS SUBMODULE GROUP PROJECT CREATED TITLE");
|
|
56
109
|
rows.slice(0, opts.limit).forEach((e, idx) => {
|
|
57
110
|
const stat = (e.status === "closed" ? "[x]" : "[ ]").padEnd(7);
|
|
111
|
+
const sub = (e.submodule || "-").padEnd(11);
|
|
58
112
|
const safeTitle = String(e.title || e.topic || "").replace(/(\n|\\n)+/g, " ").slice(0, 50);
|
|
59
|
-
console.log(`${String(idx+1).padEnd(2)} ${stat} ${e.group.padEnd(10)} ${e.project.padEnd(11)} ${e.createdAt.padEnd(24)} ${safeTitle}`);
|
|
113
|
+
console.log(`${String(idx+1).padEnd(2)} ${stat} ${sub} ${String(e.group||"").padEnd(10)} ${String(e.project||"").padEnd(11)} ${String(e.createdAt||"").padEnd(24)} ${safeTitle}`);
|
|
60
114
|
});
|
|
61
115
|
}
|
|
62
116
|
|
|
117
|
+
export async function runTicketMeta(opts) {
|
|
118
|
+
const index = rebuildTicketIndexFromTopicFilesIfNeeded(opts.cwd, opts);
|
|
119
|
+
const found = pickTicketEntry(opts, index);
|
|
120
|
+
if (!found) throw new Error("ticket meta: no matching ticket found");
|
|
121
|
+
|
|
122
|
+
if (opts.json) {
|
|
123
|
+
console.log(JSON.stringify(found, null, 2));
|
|
124
|
+
} else {
|
|
125
|
+
console.log(`Ticket Meta [${found.topic}]`);
|
|
126
|
+
Object.entries(found).forEach(([k, v]) => console.log(` ${k}: ${v}`));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function runTicketConnect(opts) {
|
|
131
|
+
const config = loadInitConfig(opts.cwd);
|
|
132
|
+
const url = opts.remote || config?.pipelineUrl;
|
|
133
|
+
if (!url) throw new Error("ticket connect: no pipeline URL configured or provided via --remote");
|
|
134
|
+
|
|
135
|
+
console.log(`Connecting to AI Pipeline at ${url} ...`);
|
|
136
|
+
const success = await syncToPipeline(url, { action: "ping", timestamp: new Date().toISOString() });
|
|
137
|
+
if (success) {
|
|
138
|
+
console.log("SUCCESS: Pipeline is reachable.");
|
|
139
|
+
} else {
|
|
140
|
+
console.error("FAILED: Could not connect to pipeline or returned non-OK status.");
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
63
144
|
import { updateTicketEntryStatus } from "./cli-ticket-logic.mjs";
|
|
64
145
|
|
|
65
146
|
export async function runTicketClose(opts) {
|
|
@@ -85,6 +166,7 @@ export async function runTicketClose(opts) {
|
|
|
85
166
|
}
|
|
86
167
|
opts.status = "closed";
|
|
87
168
|
const entry = updateTicketEntryStatus(opts.cwd, opts);
|
|
169
|
+
syncActiveTicketPointer(opts.cwd);
|
|
88
170
|
console.log(`ticket: closed -> ${entry.topic} (${entry.path})`);
|
|
89
171
|
}
|
|
90
172
|
|
|
@@ -117,43 +199,115 @@ export async function runTicketUse(opts) {
|
|
|
117
199
|
}
|
|
118
200
|
}
|
|
119
201
|
|
|
120
|
-
import { getLegacyMigrationCandidate, parseLegacyTicketMeta } from "./cli-ticket-logic.mjs";
|
|
121
|
-
import { dirname } from "path";
|
|
122
202
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
203
|
+
|
|
204
|
+
export function pickTicketEntry(opts, indexJson) {
|
|
205
|
+
const rows = [...indexJson.entries].sort((a, b) => String(b.createdAt || "").localeCompare(String(a.createdAt || "")));
|
|
206
|
+
if (rows.length === 0) return null;
|
|
207
|
+
if (opts.topic) {
|
|
208
|
+
const key = String(opts.topic).toLowerCase();
|
|
209
|
+
return rows.find(e => String(e.topic || "").toLowerCase().includes(key)) || null;
|
|
128
210
|
}
|
|
211
|
+
return rows[0];
|
|
212
|
+
}
|
|
129
213
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
214
|
+
export async function runTicketArchive(opts) {
|
|
215
|
+
if (!opts.latest && !opts.topic) {
|
|
216
|
+
if (process.stdout.isTTY) {
|
|
217
|
+
const index = rebuildTicketIndexFromTopicFilesIfNeeded(opts.cwd, opts);
|
|
218
|
+
const choices = index.entries
|
|
219
|
+
.filter(e => e.status !== "archived")
|
|
220
|
+
.map(e => ({ label: `[${e.group}] ${e.title}`, value: e.topic }));
|
|
221
|
+
if (choices.length > 0) {
|
|
222
|
+
const { createInterface } = await import("readline");
|
|
223
|
+
const { selectOne } = await import("./cli-prompts.mjs");
|
|
224
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
225
|
+
try {
|
|
226
|
+
opts.topic = await selectOne(rl, "Choose a ticket to archive (this will move the file to archive/):", choices);
|
|
227
|
+
} finally {
|
|
228
|
+
rl.close();
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
throw new Error("No active tickets found to archive.");
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
throw new Error("ticket archive requires --latest or --topic <prefix>");
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const indexJson = rebuildTicketIndexFromTopicFilesIfNeeded(opts.cwd, opts);
|
|
239
|
+
const found = pickTicketEntry(opts, indexJson);
|
|
240
|
+
if (!found) throw new Error("ticket archive: no matching entry");
|
|
135
241
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const marker = `\n\n<!-- Ticket (repo-relative): ${relPath} -->\n`;
|
|
141
|
-
writeFileSync(absPath, candidate.body.trimEnd() + marker, "utf8");
|
|
142
|
-
console.log("ticket: migrated body -> " + relPath);
|
|
242
|
+
const absPath = join(opts.cwd, found.path);
|
|
243
|
+
if (!existsSync(absPath)) {
|
|
244
|
+
throw new Error("ticket archive: file not found " + found.path);
|
|
245
|
+
}
|
|
143
246
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
unlinkSync(candidate.latestPath);
|
|
156
|
-
console.log("ticket: deleted legacy LATEST.md");
|
|
247
|
+
const archiveDir = join(opts.cwd, TICKET_DIR_NAME, "archive", found.group || "sub");
|
|
248
|
+
if (!opts.dryRun) mkdirSync(archiveDir, { recursive: true });
|
|
249
|
+
|
|
250
|
+
const fileName = found.path.split(/[/\\]/).pop();
|
|
251
|
+
const newAbsPath = join(archiveDir, fileName);
|
|
252
|
+
const bodyLines = readFileSync(absPath, "utf8").trimEnd().split(/\r?\n/);
|
|
253
|
+
|
|
254
|
+
if (opts.report) {
|
|
255
|
+
const reportSrc = resolve(opts.cwd, opts.report);
|
|
256
|
+
if (!existsSync(reportSrc)) {
|
|
257
|
+
throw new Error("ticket archive: report file not found " + opts.report);
|
|
157
258
|
}
|
|
259
|
+
const reportDir = join(opts.cwd, TICKET_DIR_NAME, "reports");
|
|
260
|
+
if (!opts.dryRun) mkdirSync(reportDir, { recursive: true });
|
|
261
|
+
|
|
262
|
+
const reportDest = join(reportDir, `REPORT-${fileName}`);
|
|
263
|
+
if (!opts.dryRun) copyFileSync(reportSrc, reportDest);
|
|
264
|
+
console.log("ticket archive: copied report to " + toRepoRelativePath(opts.cwd, reportDest));
|
|
265
|
+
|
|
266
|
+
bodyLines.push("");
|
|
267
|
+
bodyLines.push("## š Attached Report");
|
|
268
|
+
const relativeLink = toPosixPath(relative(dirname(newAbsPath), reportDest));
|
|
269
|
+
bodyLines.push(`- [View Report](${relativeLink})`);
|
|
158
270
|
}
|
|
271
|
+
|
|
272
|
+
if (opts.dryRun) {
|
|
273
|
+
console.log("ticket archive: would move " + toRepoRelativePath(opts.cwd, absPath) + " to " + toRepoRelativePath(opts.cwd, newAbsPath));
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
writeFileSync(newAbsPath, bodyLines.join("\n") + "\n", "utf8");
|
|
278
|
+
rmSync(absPath);
|
|
279
|
+
console.log("ticket archive: moved ticket to " + toRepoRelativePath(opts.cwd, newAbsPath));
|
|
280
|
+
|
|
281
|
+
const entryIdx = indexJson.entries.findIndex(e => e.id === found.id);
|
|
282
|
+
if (entryIdx >= 0) {
|
|
283
|
+
indexJson.entries[entryIdx].status = "archived";
|
|
284
|
+
indexJson.entries[entryIdx].path = toRepoRelativePath(opts.cwd, newAbsPath);
|
|
285
|
+
indexJson.entries[entryIdx].updatedAt = new Date().toISOString();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
writeTicketIndexJson(opts.cwd, indexJson, opts);
|
|
289
|
+
writeTicketListFile(opts.cwd, indexJson.entries, opts);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export async function runTicketReports(opts) {
|
|
293
|
+
const reportDir = join(opts.cwd, TICKET_DIR_NAME, "reports");
|
|
294
|
+
console.log("\nš Agent Reports:");
|
|
295
|
+
if (!existsSync(reportDir)) {
|
|
296
|
+
console.log(" No reports found.");
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const files = readdirSync(reportDir).filter(f => f.startsWith("REPORT-") && f.endsWith(".md"));
|
|
300
|
+
if (files.length === 0) {
|
|
301
|
+
console.log(" No reports found.");
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const sorted = files.sort((a, b) => {
|
|
306
|
+
return statSync(join(reportDir, b)).mtime.getTime() - statSync(join(reportDir, a)).mtime.getTime();
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
sorted.slice(0, opts.limit || 30).forEach(f => {
|
|
310
|
+
console.log(` - [${f}]`);
|
|
311
|
+
});
|
|
312
|
+
console.log("");
|
|
159
313
|
}
|