deuk-agent-rule 2.2.2 → 2.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.ko.md +48 -0
- package/CHANGELOG.md +192 -149
- package/README.ko.md +40 -16
- package/README.md +30 -7
- package/bundle/AGENTS.md +70 -36
- package/bundle/rules/multi-ai-workflow.mdc +0 -1
- package/bundle/templates/TICKET_TEMPLATE.md +1 -1
- package/package.json +57 -52
- package/scripts/cli-args.mjs +9 -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 +100 -56
- package/scripts/cli-ticket-logic.mjs +322 -46
- package/scripts/cli-utils.mjs +64 -1
- package/scripts/cli.mjs +18 -6
- package/scripts/sync-oss.mjs +5 -1
package/bundle/AGENTS.md
CHANGED
|
@@ -1,36 +1,70 @@
|
|
|
1
|
-
<!-- deuk-agent-rule:begin -->
|
|
2
|
-
|
|
3
|
-
# Project Agent Rules
|
|
4
|
-
|
|
5
|
-
## Identity
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
1
|
+
<!-- deuk-agent-rule:begin -->
|
|
2
|
+
|
|
3
|
+
# Project Agent Rules
|
|
4
|
+
|
|
5
|
+
## Identity
|
|
6
|
+
|
|
7
|
+
- **[COMMUNICATION TONE STRICT RULE]**
|
|
8
|
+
- You are a Senior Fullstack Systems Engineer specializing in Unity/C#, WebApp architectures, and High-Performance C++ Server development.
|
|
9
|
+
- Your communication must be strictly dry, concise, and technical.
|
|
10
|
+
- You MUST NOT use emojis, exclamation marks(!), or dramatic language (e.g., "대참사", "완벽하게", "시한폭탄").
|
|
11
|
+
- Do not attempt to "wow" the user with your tone.
|
|
12
|
+
- For Korean responses, use polite '해요체(-요)' instead of formal '하십시오체(-다/까)'.
|
|
13
|
+
- **핸드오프 저장 후 채팅**: 파일로 남긴 뒤 채팅에 **`Path: \`루트기준/전체/경로.md\``** 형태로 **한 줄**을 반드시 넣어 다음 세션이 동일 파일을 연다.
|
|
14
|
+
- **플랜 UI(선택)**: 플랜 전용 패널에 같은 문서를 띄우려면, 관리 중인 **multi-ai-workflow** 규칙에 적힌 **선택적 미러 경로**(예: `.cursor/plans/*.plan.md`)에 동일 본문을 둘 수 있다. 정본은 지정된 티켓 폴더를 유지하고 두 곳 내용을 맞출 것.
|
|
15
|
+
|
|
16
|
+
English sections above are canonical for tooling; this block is a short Korean mirror for the same rules.
|
|
17
|
+
|
|
18
|
+
## Ticket format & Submodule Isolation
|
|
19
|
+
|
|
20
|
+
When handing work between tools or people—especially in an environment with multiple submodules like DeukUI, DeukPack, etc.—you **MUST NOT** use free-form markdown.
|
|
21
|
+
|
|
22
|
+
You **MUST** use the official Ticket Skeleton Template located at:
|
|
23
|
+
`<Current Repo Root>/.deuk-agent-templates/TICKET_TEMPLATE.md`
|
|
24
|
+
|
|
25
|
+
**Hard Rules**:
|
|
26
|
+
- **No hotpath LINQ (금지)**: Update 루프에서 LINQ, boxing, frame allocation 없음
|
|
27
|
+
- **Root Cleanliness (하드룰)**: 작업용 스크립트(`fix_*.py`, `tmp_*.js` 등)를 워크스페이스 루트에 직접 생성하지 마십시오. 모든 일시적 스크립트는 `tmp/scripts/` 또는 `tmp/` 폴더 내에 생성해야 합니다.
|
|
28
|
+
- **C++ Server Hard Rules**:
|
|
29
|
+
- **No Raw Pointers**: Use `std::unique_ptr` or `std::shared_ptr` for resource ownership.
|
|
30
|
+
- **Header Cleanliness**: Keep `#include` minimum in headers; use forward declarations where possible.
|
|
31
|
+
- **Async Safety**: Every shared resource in the logic loop strictly requires a mutex or atomic protection.
|
|
32
|
+
- **WebApp / Frontend**:
|
|
33
|
+
- **Protocol Integrity**: Never hardcode JSON structures; always use `DeukPack` generated JS/TS codecs for communication.
|
|
34
|
+
- **Ticket format (필수)**: 멀티스텝은 `.deuk-agent-templates/TICKET_TEMPLATE.md` (또는 활성 서브모듈의 템플릿) 사용
|
|
35
|
+
|
|
36
|
+
By **creating a ticket using the CLI** (`npx deuk-agent-rule ticket create --topic <name>`), you ensure that:
|
|
37
|
+
1. The **Target Submodule** is explicitly locked.
|
|
38
|
+
2. The agent is forced to read specific **Module Rules** (e.g., `.deuk-agent-templates/MODULE_RULE_TEMPLATE.md`).
|
|
39
|
+
3. Execution happens in explicit **Phases** to prevent context bleed.
|
|
40
|
+
|
|
41
|
+
## DeukPack Codec & IDL Strict Rules (득팩 코어 체재 하드 룰)
|
|
42
|
+
|
|
43
|
+
- **IDL Field Syntax (앵글 브래킷)**: 득팩의 필드 정의는 `1> int32 id` 형식을 따릅니다. Thrift 레거시 문법인 `:`(콜론), 세미콜론(`;`), `i32`, `i64`를 더 이상 문서나 코드에 사용하지 마십시오. 오로지 `id> type name` 및 `int32`, `int64` 표준 명칭을 강제합니다.
|
|
44
|
+
- **Unified Pack API**: 과거의 `DeukPackSerializer`, `DeukPackEngine`, `WriteWithOverrides`, `toJsonWithOverrides` 등은 모두 폐기되었습니다. 모든 코드에는 `DeukPackCodec` 식별자와 유니파이드 API(`byte[] bin = Dto.Hero.Pack(format, fieldIds, overrides)`, `hero.UnpackFrom(bin)`)만 사용해야 합니다.
|
|
45
|
+
- **Namespace Requirement**: Every `.deuk` schema MUST explicitly declare a namespace (e.g., `namespace Dto`). Never define global structs without a namespace. Code examples MUST use the fully qualified namespace path (e.g., `Dto.Hero.Pack()`).
|
|
46
|
+
|
|
47
|
+
## AI Model Compliance & Selection Policy
|
|
48
|
+
|
|
49
|
+
**Model Over-alignment vs Compliance (High vs Flash)**
|
|
50
|
+
- **Flash/Fast Models**: Highly instruct-tuned for strict mechanical task execution. Due to a smaller parameter footprint and less internal "world knowledge", they explicitly follow literal agent rules and format templates exactly as instructed.
|
|
51
|
+
- **High/Pro Models**: Possess vast world knowledge and are optimized for helpfulness. This often leads to "overthinking" (prioritizing a helpful or natural answer over rigid, arbitrary constraints), resulting in frequent rule violations like ignoring length limits or template structures. They also experience attention dilution in deep analysis tasks.
|
|
52
|
+
|
|
53
|
+
**Assignment Strategy**:
|
|
54
|
+
1. **Flash Models**: Use for strict template filling, simple code generation, porting, and repetitive ticket execution (`.deuk-agent-ticket/*`) where strict compliance is mandatory.
|
|
55
|
+
2. **High/Pro Models**: Use for architectural planning, deep legacy code comprehension, complex bug squashing, and creative solutions. Supply heavy failure warnings in the prompt to force formatting compliance.
|
|
56
|
+
|
|
57
|
+
## 🔗 Ticket Framework & Execution Strategy
|
|
58
|
+
|
|
59
|
+
When given a ticket, you MUST run commands and write code **strictly within the boundaries** of the `[Target Submodule]` defined in the `TICKET-XXX.md`.
|
|
60
|
+
|
|
61
|
+
1. **Create the Ticket**: ALWAYS use `npx deuk-agent-rule ticket create --topic <name>` to generate a new ticket. **DO NOT** manually create `.md` files or copy templates directly to bypass the CLI.
|
|
62
|
+
2. **Read the Ticket**: ALWAYS use `npx deuk-agent-rule ticket use --latest` (or `npx deuk-agent-rule ticket list`) to locate and read the active ticket. DO NOT manually parse INDEX.json or scan directories.
|
|
63
|
+
3. **Fill the Ticket (CRITICAL)**: The newly created ticket already contains YAML Front Matter (`--- id: ... ---`). **DO NOT** overwrite the entire file when adding your plan. ALWAYS use partial file editing tools or append text carefully to preserve the existing YAML Front Matter. Erasing the Front Matter corrupts the ticketing index.
|
|
64
|
+
4. **Execute Phase**: Process only the checklist for the **Current Phase**. Do not hallucinate or wander into other architectural areas.
|
|
65
|
+
5. **Update Status**: Mark checkboxes (`[x]`) as tasks are completed.
|
|
66
|
+
6. **Archive on Completion**: When all phases are completed, append the execution report at the bottom under a `## 📜 Execution Report` header. **Then, YOU MUST execute `npx deuk-agent-rule ticket archive <ticket-id>` (or `--latest`)** to properly close and archive the ticket. DO NOT attempt to manually `mv` files.
|
|
67
|
+
|
|
68
|
+
All Tickets are volatile and strictly local. Do not attempt to version them or mirror them to obsolete plan directories.
|
|
69
|
+
|
|
70
|
+
<!-- deuk-agent-rule:end -->
|
|
@@ -36,7 +36,6 @@ When the user asks to prepare work for another tool:
|
|
|
36
36
|
3. Do not include editor-specific instructions (token budgets, local rule file paths).
|
|
37
37
|
4. When the ticket must persist (user asks to save or document it, or it is the canonical next-step spec), create or update a Markdown file under internal implementation documentation (see AGENTS.md, Ticket persistence). Prefer the unified .deuk-agent-ticket/ directory for both the index (TICKET_LIST.md) and the topic files; init gitignores .deuk-agent-ticket/ by default. Use the same ticket template; do not place this in public README/landing unless the project explicitly treats this as its internal log. In Markdown links and backtick citations, use the full path from the repository root — never a bare filename.
|
|
38
38
|
5. After writing a persisted ticket, in chat include one dedicated line with the full repo-root-relative path so the next session can open it unambiguously, for example: `Path: .deuk-agent-ticket/sub/container-unified-20260329-120000.md` (same path you used in the file). Repeat the full path in links elsewhere in the same message if helpful.
|
|
39
|
-
6. Optional last line inside the persisted file (improves search and skim): `<!-- Ticket (repo-relative): path/from/repo/root.md -->` on the very last line.
|
|
40
39
|
7. Write persisted ticket content in the user's conversation language (e.g., Korean) unless they specify another language. When using Korean, follow the concise polite style (-요) as described in AGENTS.md.
|
|
41
40
|
|
|
42
41
|
## Context Preservation (New Issues)
|
package/package.json
CHANGED
|
@@ -1,52 +1,57 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "deuk-agent-rule",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "DeukAgentRules: generic AGENTS.md + .cursor rule templates with init/merge CLI (npm name: deuk-agent-rule).",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"agents-md",
|
|
7
|
-
"cursor-rules",
|
|
8
|
-
"copilot",
|
|
9
|
-
"gemini",
|
|
10
|
-
"claude",
|
|
11
|
-
"windsurf",
|
|
12
|
-
"jetbrains",
|
|
13
|
-
"ticket",
|
|
14
|
-
"deuk-family",
|
|
15
|
-
"deukpack-ecosystem"
|
|
16
|
-
],
|
|
17
|
-
"license": "Apache-2.0",
|
|
18
|
-
"repository": {
|
|
19
|
-
"type": "git",
|
|
20
|
-
"url": "https://github.com/joygram/DeukAgentRules.git"
|
|
21
|
-
},
|
|
22
|
-
"bugs": {
|
|
23
|
-
"url": "https://github.com/joygram/DeukAgentRules/issues"
|
|
24
|
-
},
|
|
25
|
-
"homepage": "https://github.com/joygram/DeukAgentRules#readme",
|
|
26
|
-
"files": [
|
|
27
|
-
"bundle/**/*",
|
|
28
|
-
"scripts/**/*.mjs",
|
|
29
|
-
"README.md",
|
|
30
|
-
"README.ko.md",
|
|
31
|
-
"CHANGELOG.md"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"bump
|
|
38
|
-
"bump:
|
|
39
|
-
"bump:
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "deuk-agent-rule",
|
|
3
|
+
"version": "2.3.2",
|
|
4
|
+
"description": "DeukAgentRules: generic AGENTS.md + .cursor rule templates with init/merge CLI (npm name: deuk-agent-rule).",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"agents-md",
|
|
7
|
+
"cursor-rules",
|
|
8
|
+
"copilot",
|
|
9
|
+
"gemini",
|
|
10
|
+
"claude",
|
|
11
|
+
"windsurf",
|
|
12
|
+
"jetbrains",
|
|
13
|
+
"ticket",
|
|
14
|
+
"deuk-family",
|
|
15
|
+
"deukpack-ecosystem"
|
|
16
|
+
],
|
|
17
|
+
"license": "Apache-2.0",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/joygram/DeukAgentRules.git"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/joygram/DeukAgentRules/issues"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/joygram/DeukAgentRules#readme",
|
|
26
|
+
"files": [
|
|
27
|
+
"bundle/**/*",
|
|
28
|
+
"scripts/**/*.mjs",
|
|
29
|
+
"README.md",
|
|
30
|
+
"README.ko.md",
|
|
31
|
+
"CHANGELOG.md",
|
|
32
|
+
"CHANGELOG.ko.md"
|
|
33
|
+
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"sync": "node scripts/sync-bundle.mjs",
|
|
36
|
+
"prepack": "node scripts/sync-bundle.mjs",
|
|
37
|
+
"bump": "commit-and-tag-version",
|
|
38
|
+
"bump:patch": "commit-and-tag-version -r patch",
|
|
39
|
+
"bump:minor": "commit-and-tag-version -r minor",
|
|
40
|
+
"bump:major": "commit-and-tag-version -r major",
|
|
41
|
+
"merge:dry": "node scripts/cli.mjs merge --dry-run --cwd .. --agents skip",
|
|
42
|
+
"sync:oss": "node scripts/sync-oss.mjs"
|
|
43
|
+
},
|
|
44
|
+
"engines": {
|
|
45
|
+
"node": ">=18"
|
|
46
|
+
},
|
|
47
|
+
"bin": {
|
|
48
|
+
"deuk-agent-rule": "./scripts/cli.mjs"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"commit-and-tag-version": "^12.7.1"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"ejs": "^5.0.2",
|
|
55
|
+
"yaml": "^2.8.3"
|
|
56
|
+
}
|
|
57
|
+
}
|
package/scripts/cli-args.mjs
CHANGED
|
@@ -12,6 +12,7 @@ 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;
|
|
@@ -19,6 +20,10 @@ export function parseTicketArgs(argv) {
|
|
|
19
20
|
else if (a === "--status") out.status = argv[++i];
|
|
20
21
|
else if (a === "--archived") out.archived = true;
|
|
21
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;
|
|
22
27
|
}
|
|
23
28
|
return out;
|
|
24
29
|
}
|
|
@@ -39,6 +44,10 @@ export function parseArgs(argv) {
|
|
|
39
44
|
else if (a === "--rules") out.rules = argv[++i];
|
|
40
45
|
else if (a === "--cursorrules") out.cursorrules = argv[++i];
|
|
41
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;
|
|
42
51
|
else if (a === "-h" || a === "--help") out.help = true;
|
|
43
52
|
}
|
|
44
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
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, copyFileSync, readdirSync, rmSync, statSync } from "fs";
|
|
2
2
|
import { basename, join, dirname, relative, resolve } from "path";
|
|
3
|
-
import { toSlug, toRepoRelativePath, inferRefTitleAndTopic, resolveReferencedTicketPath, toPosixPath } from "./cli-utils.mjs";
|
|
4
|
-
import { TICKET_DIR_NAME, appendTicketEntry, rebuildTicketIndexFromTopicFilesIfNeeded, detectConsumerTicketDir, readTicketIndexJson, writeTicketIndexJson, writeTicketListFile } from "./cli-ticket-logic.mjs";
|
|
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,45 +199,7 @@ export async function runTicketUse(opts) {
|
|
|
117
199
|
}
|
|
118
200
|
}
|
|
119
201
|
|
|
120
|
-
import { getLegacyMigrationCandidate, parseLegacyTicketMeta } from "./cli-ticket-logic.mjs";
|
|
121
202
|
|
|
122
|
-
export async function runTicketMigrate(opts) {
|
|
123
|
-
const candidate = getLegacyMigrationCandidate(opts.cwd);
|
|
124
|
-
if (!candidate) {
|
|
125
|
-
console.log("ticket: no legacy LATEST.md migration candidate found");
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const { title, group, project } = parseLegacyTicketMeta(candidate.body);
|
|
130
|
-
const topic = toSlug(title);
|
|
131
|
-
const stamp = Date.now();
|
|
132
|
-
const relPath = join(TICKET_DIR_NAME, group, `${topic}-${stamp}.md`);
|
|
133
|
-
const absPath = join(opts.cwd, relPath);
|
|
134
|
-
|
|
135
|
-
if (opts.dryRun) {
|
|
136
|
-
console.log("ticket: would migrate -> " + relPath);
|
|
137
|
-
} else {
|
|
138
|
-
mkdirSync(dirname(absPath), { recursive: true });
|
|
139
|
-
const marker = `\n\n<!-- Ticket (repo-relative): ${relPath} -->\n`;
|
|
140
|
-
writeFileSync(absPath, candidate.body.trimEnd() + marker, "utf8");
|
|
141
|
-
console.log("ticket: migrated body -> " + relPath);
|
|
142
|
-
|
|
143
|
-
appendTicketEntry(opts.cwd, {
|
|
144
|
-
id: `ticket_migrated_${stamp}`,
|
|
145
|
-
title,
|
|
146
|
-
topic,
|
|
147
|
-
group,
|
|
148
|
-
project,
|
|
149
|
-
createdAt: new Date().toISOString(),
|
|
150
|
-
path: relPath,
|
|
151
|
-
source: "ticket-migrate",
|
|
152
|
-
}, opts);
|
|
153
|
-
if (existsSync(candidate.latestPath)) {
|
|
154
|
-
unlinkSync(candidate.latestPath);
|
|
155
|
-
console.log("ticket: deleted legacy LATEST.md");
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
203
|
|
|
160
204
|
export function pickTicketEntry(opts, indexJson) {
|
|
161
205
|
const rows = [...indexJson.entries].sort((a, b) => String(b.createdAt || "").localeCompare(String(a.createdAt || "")));
|