deuk-agent-rule 2.4.6 → 2.5.13
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 +61 -0
- package/README.ko.md +50 -11
- package/README.md +50 -11
- package/bundle/.cursorrules +8 -4
- package/bundle/AGENTS.md +94 -21
- package/bundle/gemini.md +26 -0
- package/bundle/rules/multi-ai-workflow.mdc +2 -2
- package/bundle/rules.d/core-workflow.md +48 -0
- package/bundle/rules.d/deukrag-mcp.md +37 -0
- package/bundle/templates/TICKET_TEMPLATE.md +36 -15
- package/package.json +2 -4
- package/scripts/cli-args.mjs +1 -0
- package/scripts/cli-init-commands.mjs +246 -44
- package/scripts/cli-init-logic.mjs +27 -6
- package/scripts/cli-rule-compiler.mjs +102 -0
- package/scripts/cli-ticket-commands.mjs +49 -17
- package/scripts/cli-ticket-logic.mjs +136 -131
- package/scripts/cli-utils.mjs +70 -6
- package/scripts/cli.mjs +9 -1
- package/scripts/merge-logic.mjs +0 -28
- package/scripts/sync-bundle.mjs +15 -0
package/scripts/cli-utils.mjs
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, readdirSync } from "fs";
|
|
2
2
|
import { basename, dirname, join, relative } from "path";
|
|
3
3
|
import YAML from "yaml";
|
|
4
4
|
|
|
5
|
-
export const
|
|
5
|
+
export const AGENT_ROOT_DIR = ".deuk-agent";
|
|
6
|
+
export const TICKET_SUBDIR = "tickets";
|
|
7
|
+
export const TEMPLATE_SUBDIR = "templates";
|
|
8
|
+
export const RULES_SUBDIR = "rules";
|
|
9
|
+
|
|
10
|
+
export const TICKET_DIR_NAME = `${AGENT_ROOT_DIR}/${TICKET_SUBDIR}`;
|
|
11
|
+
export const TICKET_INDEX_FILENAME = "INDEX.json";
|
|
12
|
+
export const TICKET_LIST_FILENAME = "TICKET_LIST.md";
|
|
13
|
+
export const TICKET_LIST_TEMPLATE_FILENAME = "TICKET_LIST.template.md";
|
|
14
|
+
|
|
15
|
+
export const INIT_CONFIG_FILENAME = `${AGENT_ROOT_DIR}/config.json`;
|
|
16
|
+
export const LEGACY_INIT_CONFIG_FILENAME = ".deuk-agent-rule.config.json";
|
|
6
17
|
export const INIT_CONFIG_VERSION = 1;
|
|
7
18
|
|
|
8
19
|
export const STACKS = [
|
|
@@ -22,9 +33,13 @@ export const AGENT_TOOLS = [
|
|
|
22
33
|
|
|
23
34
|
export function loadInitConfig(cwd) {
|
|
24
35
|
const p = join(cwd, INIT_CONFIG_FILENAME);
|
|
25
|
-
|
|
36
|
+
const legacyP = join(cwd, LEGACY_INIT_CONFIG_FILENAME);
|
|
37
|
+
|
|
38
|
+
let target = existsSync(p) ? p : (existsSync(legacyP) ? legacyP : null);
|
|
39
|
+
if (!target) return null;
|
|
40
|
+
|
|
26
41
|
try {
|
|
27
|
-
const j = JSON.parse(readFileSync(
|
|
42
|
+
const j = JSON.parse(readFileSync(target, "utf8"));
|
|
28
43
|
if (j.version !== INIT_CONFIG_VERSION) return null;
|
|
29
44
|
return j;
|
|
30
45
|
} catch {
|
|
@@ -42,6 +57,7 @@ export function writeInitConfig(cwd, opts) {
|
|
|
42
57
|
shareTickets: !!opts.shareTickets,
|
|
43
58
|
remoteSync: !!opts.remoteSync,
|
|
44
59
|
pipelineUrl: opts.pipelineUrl,
|
|
60
|
+
ignoreDirs: opts.ignoreDirs || DEFAULT_IGNORE_DIRS,
|
|
45
61
|
updatedAt: new Date().toISOString(),
|
|
46
62
|
};
|
|
47
63
|
writeFileSync(p, JSON.stringify(data, null, 2), "utf8");
|
|
@@ -76,6 +92,21 @@ export function makeEntryId() {
|
|
|
76
92
|
return `000-fallback-${Date.now().toString(36)}`;
|
|
77
93
|
}
|
|
78
94
|
|
|
95
|
+
export function findFileRecursively(dir, fileName) {
|
|
96
|
+
if (!existsSync(dir)) return null;
|
|
97
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
const res = join(dir, entry.name);
|
|
100
|
+
if (entry.isDirectory()) {
|
|
101
|
+
const found = findFileRecursively(res, fileName);
|
|
102
|
+
if (found) return found;
|
|
103
|
+
} else if (entry.name === fileName) {
|
|
104
|
+
return res;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
79
110
|
export function detectProjectFromBody(body) {
|
|
80
111
|
const content = String(body || "");
|
|
81
112
|
const metaMatch = content.match(/^project:\s*(.+)$/mi);
|
|
@@ -144,7 +175,10 @@ export function stringifyFrontMatter(meta, content) {
|
|
|
144
175
|
return `---\n${yamlStr}\n---\n\n${content.trim()}\n`;
|
|
145
176
|
}
|
|
146
177
|
|
|
147
|
-
|
|
178
|
+
/**
|
|
179
|
+
* Returns true if semver a is less than b
|
|
180
|
+
*/
|
|
181
|
+
export function semverLt(a, b) {
|
|
148
182
|
const pa = String(a || "0").replace(/[^0-9.]/g, "").split(".").map(Number);
|
|
149
183
|
const pb = String(b || "0").replace(/[^0-9.]/g, "").split(".").map(Number);
|
|
150
184
|
for (let i = 0; i < 3; i++) {
|
|
@@ -166,7 +200,7 @@ export async function checkUpdateNotifier() {
|
|
|
166
200
|
if (res.ok) {
|
|
167
201
|
const data = await res.json();
|
|
168
202
|
// Only notify when registry version is strictly newer than local (handles local dev symlink case)
|
|
169
|
-
if (data.version &&
|
|
203
|
+
if (data.version && semverLt(currentVersion, data.version)) {
|
|
170
204
|
console.warn(`\n\x1b[33m💡 Update available! ${currentVersion} → ${data.version}\x1b[0m`);
|
|
171
205
|
console.warn(`\x1b[36mRun 'npm install -g deuk-agent-rule' to update.\x1b[0m\n`);
|
|
172
206
|
}
|
|
@@ -175,3 +209,33 @@ export async function checkUpdateNotifier() {
|
|
|
175
209
|
// Ignore timeout or network errors silently
|
|
176
210
|
}
|
|
177
211
|
}
|
|
212
|
+
|
|
213
|
+
export const DEFAULT_IGNORE_DIRS = ["node_modules", ".git", ".deuk-agent", "tmp", "temp", ".tmp", ".cache"];
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Recursively finds all directories containing deuk-agent ticket structures.
|
|
217
|
+
*/
|
|
218
|
+
export function discoverAllSubmodules(baseCwd, ignoreDirs = DEFAULT_IGNORE_DIRS, out = new Set()) {
|
|
219
|
+
if (!existsSync(baseCwd)) return Array.from(out);
|
|
220
|
+
|
|
221
|
+
const hasLegacy1 = existsSync(join(baseCwd, ".deuk-agent-ticket"));
|
|
222
|
+
const hasLegacy2 = existsSync(join(baseCwd, ".deuk-agent-tickets"));
|
|
223
|
+
const hasNew = existsSync(join(baseCwd, AGENT_ROOT_DIR, TICKET_SUBDIR));
|
|
224
|
+
|
|
225
|
+
if (hasLegacy1 || hasLegacy2 || hasNew) {
|
|
226
|
+
out.add(baseCwd);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const entries = readdirSync(baseCwd, { withFileTypes: true });
|
|
231
|
+
for (const ent of entries) {
|
|
232
|
+
if (!ent.isDirectory()) continue;
|
|
233
|
+
// Skip system or noisy directories based on ignoreDirs configuration
|
|
234
|
+
if (ignoreDirs.includes(ent.name) || ent.name.startsWith(".deuk-agent")) continue;
|
|
235
|
+
discoverAllSubmodules(join(baseCwd, ent.name), ignoreDirs, out);
|
|
236
|
+
}
|
|
237
|
+
} catch {
|
|
238
|
+
// Ignore permission errors on specific subfolders
|
|
239
|
+
}
|
|
240
|
+
return Array.from(out);
|
|
241
|
+
}
|
package/scripts/cli.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { existsSync } from "fs";
|
|
2
|
+
import { existsSync, rmSync } from "fs";
|
|
3
3
|
import { dirname, join } from "path";
|
|
4
4
|
import { fileURLToPath } from "url";
|
|
5
5
|
import { parseArgs, parseTicketArgs } from "./cli-args.mjs";
|
|
@@ -77,6 +77,14 @@ async function main() {
|
|
|
77
77
|
// Removed legacy migration runTicketMigrate
|
|
78
78
|
|
|
79
79
|
async function handleInit(opts) {
|
|
80
|
+
if (opts.clean && !opts.dryRun) {
|
|
81
|
+
console.log(`[CLEAN] Removing legacy templates and config...`);
|
|
82
|
+
const templatesDir = join(opts.cwd, ".deuk-agent-templates");
|
|
83
|
+
const configFile = join(opts.cwd, ".deuk-agent-rule.config.json");
|
|
84
|
+
if (existsSync(templatesDir)) rmSync(templatesDir, { recursive: true, force: true });
|
|
85
|
+
if (existsSync(configFile)) rmSync(configFile, { force: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
80
88
|
if (!opts.interactive && !opts.nonInteractive && !loadInitConfig(opts.cwd)) {
|
|
81
89
|
// If no config and not interactive, prompt unless non-interactive
|
|
82
90
|
await runInteractive(opts);
|
package/scripts/merge-logic.mjs
CHANGED
|
@@ -266,34 +266,6 @@ export function removeTaggedBlock(content, begin, end) {
|
|
|
266
266
|
return { ok: true, content: next };
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
-
/**
|
|
270
|
-
* Strip `deuk-agent-rule-cursorrules` (or custom markers) region from `.cursorrules`.
|
|
271
|
-
* @param {{ cwd: string, markers: { begin: string, end: string }, dryRun?: boolean, backup?: boolean }} opts
|
|
272
|
-
*/
|
|
273
|
-
export function removeCursorrules(opts) {
|
|
274
|
-
const { cwd, markers, dryRun, backup } = opts;
|
|
275
|
-
const targetPath = join(cwd, ".cursorrules");
|
|
276
|
-
if (!existsSync(targetPath)) {
|
|
277
|
-
return { action: "skip", reason: "no file" };
|
|
278
|
-
}
|
|
279
|
-
const existing = readFileSync(targetPath, "utf8");
|
|
280
|
-
const result = removeTaggedBlock(existing, markers.begin, markers.end);
|
|
281
|
-
if (!result.ok) {
|
|
282
|
-
return { action: "skip", path: targetPath, reason: result.reason };
|
|
283
|
-
}
|
|
284
|
-
if (dryRun) {
|
|
285
|
-
return { action: "would-write", path: targetPath, mode: "remove-tagged" };
|
|
286
|
-
}
|
|
287
|
-
if (backup && existsSync(targetPath)) {
|
|
288
|
-
copyFileSync(targetPath, targetPath + ".bak");
|
|
289
|
-
}
|
|
290
|
-
if (result.content === "") {
|
|
291
|
-
unlinkSync(targetPath);
|
|
292
|
-
return { action: "delete", path: targetPath, mode: "remove-tagged" };
|
|
293
|
-
}
|
|
294
|
-
writeFileSync(targetPath, result.content, "utf8");
|
|
295
|
-
return { action: "write", path: targetPath, mode: "remove-tagged" };
|
|
296
|
-
}
|
|
297
269
|
|
|
298
270
|
/**
|
|
299
271
|
* @param {{
|
package/scripts/sync-bundle.mjs
CHANGED
|
@@ -21,6 +21,8 @@ const agentsSrc = join(publishDir, "AGENTS.md");
|
|
|
21
21
|
const agentsDest = join(pkgRoot, "bundle", "AGENTS.md");
|
|
22
22
|
const cursorrulesSrc = join(publishDir, ".cursorrules");
|
|
23
23
|
const cursorrulesDest = join(pkgRoot, "bundle", ".cursorrules");
|
|
24
|
+
const geminiSrc = join(publishDir, "gemini.md");
|
|
25
|
+
const geminiDest = join(pkgRoot, "bundle", "gemini.md");
|
|
24
26
|
|
|
25
27
|
if (!existsSync(publishDir)) {
|
|
26
28
|
throw new Error("Missing publish template dir: " + publishDir);
|
|
@@ -56,7 +58,20 @@ if (existsSync(templatesSrc)) {
|
|
|
56
58
|
}
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
const dynamicRulesSrc = join(publishDir, "rules.d");
|
|
62
|
+
const dynamicRulesDest = join(pkgRoot, "bundle", "rules.d");
|
|
63
|
+
if (existsSync(dynamicRulesSrc)) {
|
|
64
|
+
if (existsSync(dynamicRulesDest)) {
|
|
65
|
+
rmSync(dynamicRulesDest, { recursive: true });
|
|
66
|
+
}
|
|
67
|
+
mkdirSync(dynamicRulesDest, { recursive: true });
|
|
68
|
+
for (const name of readdirSync(dynamicRulesSrc)) {
|
|
69
|
+
copyFileSync(join(dynamicRulesSrc, name), join(dynamicRulesDest, name));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
59
73
|
const agentsBody = readFileSync(agentsSrc, "utf8");
|
|
60
74
|
writeFileSync(agentsDest, agentsBody, "utf8");
|
|
61
75
|
copyFileSync(cursorrulesSrc, cursorrulesDest);
|
|
76
|
+
if (existsSync(geminiSrc)) copyFileSync(geminiSrc, geminiDest);
|
|
62
77
|
console.log("deuk-agent-rule: synced bundle from publish/ template.");
|