deuk-agent-rule 1.0.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.ko.md ADDED
@@ -0,0 +1,79 @@
1
+ # DeukAgentRules (득에이전트룰스)
2
+
3
+ **npm 패키지:** `deuk-agent-rule` · **CLI:** `deuk-agent-rule`
4
+
5
+ **English:** [README.md](README.md)
6
+
7
+ Cursor, Copilot, Gemini 등 여러 에이전트·도구를 함께 쓸 때를 위한 `AGENTS.md`·`.cursor/rules` 버전 관리형 템플릿. 핸드오프·간결 응답으로 비용·성능을 개선합니다.
8
+
9
+ ## 워크스페이스 초기화
10
+
11
+ 설치만으로는 파일이 바뀌지 않습니다. **대상 프로젝트 루트**에서 CLI를 실행하거나 `--cwd`로 경로를 줍니다.
12
+
13
+ ```bash
14
+ npm install deuk-agent-rule
15
+ npx deuk-agent-rule init
16
+ ```
17
+
18
+ 첫 적용 흐름:
19
+
20
+ 1. `AGENTS.md`를 둘 프로젝트 루트로 이동.
21
+ 2. `npm install deuk-agent-rule` (devDependency 권장 가능).
22
+ 3. `npx deuk-agent-rule init` — 마커가 없으면 끝에 `<!-- deuk-agent-rule:begin -->` … `<!-- deuk-agent-rule:end -->` 블록을 붙이고 안쪽을 번들 템플릿으로 채움. `.mdc`는 `.cursor/rules/`에 복사(이름 충돌 시 기본 **prefix**).
23
+
24
+ 패키지 업데이트 후:
25
+
26
+ ```bash
27
+ npm update deuk-agent-rule
28
+ npx deuk-agent-rule init
29
+ ```
30
+
31
+ `AGENTS.md`에서는 **마커 안**만 다시 갱신되고, 바깥 내용은 유지됩니다.
32
+
33
+ ### `init` 파라미터
34
+
35
+ 플래그는 모두 `init` **뒤**에 둡니다. 예: `npx deuk-agent-rule init --cwd ../다른레포 --dry-run`
36
+
37
+ | 플래그 | init 기본값 | 설명 |
38
+ |--------|-------------|------|
39
+ | `--cwd <path>` | 현재 디렉터리 | `AGENTS.md`·`.cursor/rules/`를 쓸 레포 루트. |
40
+ | `--dry-run` | 끔 | 쓰기 없이 할 일만 출력. |
41
+ | `--backup` | 끔 | 덮어쓰기 전 같은 이름에 `.bak` 저장. |
42
+ | `--tag <id>` | `deuk-agent-rule` | `<!-- <id>:begin -->` ~ `<!-- <id>:end -->` 마커 쌍. |
43
+ | `--marker-begin` / `--marker-end` | (`--tag` 권장) | 임의 마커 문자열, **둘 다** 필요. |
44
+ | `--agents <mode>` | `inject` | `inject` — 마커 안만 갱신(마커 없으면 블록 추가). `skip` — `AGENTS.md` 건드리지 않음. `overwrite` — 전체 교체(주의). |
45
+ | `--rules <mode>` | `prefix` | `prefix` — 있으면 `deuk-agent-rule-foo.mdc`로 추가. `skip` — 스킵. `overwrite` — 덮어쓰기. |
46
+ | `--append-if-no-markers` | 끔 | `merge`에서 주로 사용. |
47
+
48
+ 예시:
49
+
50
+ ```bash
51
+ npx deuk-agent-rule init --cwd /path/to/repo
52
+ npx deuk-agent-rule init --dry-run
53
+ npx deuk-agent-rule init --tag mycompany --rules overwrite
54
+ npx deuk-agent-rule init --agents skip --rules prefix
55
+ npx deuk-agent-rule init --backup
56
+ ```
57
+
58
+ ### 동작 모델
59
+
60
+ - **관리 구역**: 마커 쌍 **사이**만 패키지가 갱신.
61
+ - **`.mdc`**: 별도 파일로 복사; 기본 `prefix`로 기존 파일 보호.
62
+
63
+ ### 번들에 포함되는 규칙
64
+
65
+ - **`multi-ai-workflow.mdc`** — `alwaysApply: true`
66
+ - **`git-commit.mdc`** — `alwaysApply: false`
67
+
68
+ ### `merge` (엄격)
69
+
70
+ 옵션은 동일. 마커가 없으면 `inject`는 실패(`--append-if-no-markers` 없을 때). 기본 `--rules skip`.
71
+
72
+ ### 주의
73
+
74
+ - `alwaysApply: true` 규칙이 겹치면 컨텍스트가 길어질 수 있음.
75
+ - `postinstall`에서 `init` 자동 실행은 권장하지 않음.
76
+
77
+ ## 버전
78
+
79
+ 배포 전 `package.json`의 `version` 상향.
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # DeukAgentRules
2
+
3
+ **npm package:** `deuk-agent-rule` · **CLI:** `deuk-agent-rule`
4
+
5
+ **한국어:** [README.ko.md](README.ko.md)
6
+
7
+ Versioned templates for `AGENTS.md` and `.cursor/rules` for Cursor, Copilot, Gemini and similar agents: shared handoff format, concise execution, stronger cost-efficiency and responsiveness.
8
+
9
+ ## Initialize a workspace
10
+
11
+ Install does nothing until you run the CLI from the **target repository root** (or pass `--cwd`).
12
+
13
+ ```bash
14
+ npm install deuk-agent-rule
15
+ npx deuk-agent-rule init
16
+ ```
17
+
18
+ Typical first run:
19
+
20
+ 1. `cd` into your project root (where `AGENTS.md` should live).
21
+ 2. `npm install deuk-agent-rule` (devDependency is fine).
22
+ 3. `npx deuk-agent-rule init` — appends `<!-- deuk-agent-rule:begin -->` … `<!-- deuk-agent-rule:end -->` if missing, fills the inner region with the bundled template, and copies `.mdc` files under `.cursor/rules/` (default **prefix** on name clash).
23
+
24
+ After a package upgrade:
25
+
26
+ ```bash
27
+ npm update deuk-agent-rule
28
+ npx deuk-agent-rule init
29
+ ```
30
+
31
+ Only the **marker region** in `AGENTS.md` is replaced again; your text outside the markers stays.
32
+
33
+ ### `init` parameters
34
+
35
+ All flags go **after** `init` (e.g. `npx deuk-agent-rule init --cwd ../other-repo --dry-run`).
36
+
37
+ | Flag | Default (init) | Description |
38
+ |------|----------------|-------------|
39
+ | `--cwd <path>` | current directory | Root of the repo to modify (`AGENTS.md`, `.cursor/rules/`). |
40
+ | `--dry-run` | off | Print planned actions; do not write files. |
41
+ | `--backup` | off | Write `*.bak` next to any file that would be overwritten. |
42
+ | `--tag <id>` | `deuk-agent-rule` | HTML comment markers: `<!-- <id>:begin -->` … `<!-- <id>:end -->`. |
43
+ | `--marker-begin <s>` / `--marker-end <s>` | (use `--tag` instead) | Custom marker strings; **both** required if either is set. |
44
+ | `--agents <mode>` | `inject` | `inject` — update only inside markers (or append block if no markers). `skip` — do not change `AGENTS.md`. `overwrite` — replace entire `AGENTS.md` with the bundle (use with care). |
45
+ | `--rules <mode>` | `prefix` | `prefix` — if `foo.mdc` exists, write `deuk-agent-rule-foo.mdc`. `skip` — skip existing names. `overwrite` — replace on clash. |
46
+ | `--append-if-no-markers` | off | Rare for `init` (init already appends when markers are missing). Same flag is mainly for `merge`. |
47
+
48
+ Examples:
49
+
50
+ ```bash
51
+ npx deuk-agent-rule init --cwd /path/to/repo
52
+ npx deuk-agent-rule init --dry-run
53
+ npx deuk-agent-rule init --tag mycompany --rules overwrite
54
+ npx deuk-agent-rule init --agents skip --rules prefix
55
+ npx deuk-agent-rule init --backup
56
+ ```
57
+
58
+ ### Product model
59
+
60
+ - **Managed block in `AGENTS.md`**: Between `<!-- deuk-agent-rule:begin -->` and `<!-- deuk-agent-rule:end -->` (or your `--tag`) — `init` / `merge` replace **only** that inner content with the bundled template.
61
+ - **Updates**: Re-run `init` after `npm update` to refresh the managed block idempotently.
62
+ - **`.mdc`**: Copied as **separate files**; default `prefix` avoids overwriting your existing rules.
63
+
64
+ ### Bundled rules
65
+
66
+ - **`multi-ai-workflow.mdc`** — `alwaysApply: true`
67
+ - **`git-commit.mdc`** — `alwaysApply: false`
68
+
69
+ ### `merge` (stricter)
70
+
71
+ Same flags; **`AGENTS.md` `inject` fails if markers are missing** unless `--append-if-no-markers`. Default **`--rules skip`**.
72
+
73
+ ### Caveats
74
+
75
+ - Multiple `alwaysApply: true` rule files (yours + prefixed copies) all apply — trim duplicates if context grows too large.
76
+ - Do **not** run `init` from `postinstall` without an explicit team decision.
77
+
78
+ ## Versioning
79
+
80
+ Bump `version` in `package.json` before publishing.
@@ -0,0 +1,51 @@
1
+ # Project Agent Rules
2
+
3
+ ## Identity
4
+
5
+ Senior software engineer. Correctness, minimal diffs, safety.
6
+
7
+ ## Code Quality
8
+
9
+ - Minimal diffs: keep existing conventions, public API, and serialized/config shapes stable unless the task requires a deliberate change.
10
+ - Hot paths (per-frame loops, tight inner loops): avoid unnecessary allocation; cache lookups where appropriate.
11
+ - Prefer one clear solution; do not list alternatives without applying one.
12
+ - Follow your stack’s official guidance for editor-only code, time steps, and serialization migrations.
13
+
14
+ ## Documentation
15
+
16
+ - User-facing docs: product behavior, compatibility, packaging, and security — not internal runbooks pasted verbatim.
17
+ - Changelog entries: factual, consumer-relevant changes only.
18
+
19
+ ## Cost-effective sessions
20
+
21
+ - Prefer **short, high-signal** answers and patches; avoid filler, long tutorials, and repeating context the user already has unless they ask for depth.
22
+ - **One clear objective** per session or turn when practical; do not expand scope into unrelated refactors.
23
+ - For read-only or exploratory tasks, **summarize** and point to paths instead of pasting large blobs.
24
+
25
+ ## IDE Branding
26
+
27
+ No editor or vendor tool branding in code, docs, README, commits, or published artifacts.
28
+
29
+ ## 요약 (한국어)
30
+
31
+ - **정체성**: 시니어 소프트웨어 엔지니어. 정확성, 최소 diff, 안전.
32
+ - **코드 품질**: 관례·공개 API·직렬화 형태를 불필요하게 흔들지 않음. 핫패스에서 불필요한 할당 지양, 스택 공식 가이드를 따름.
33
+ - **문서**: 사용자에게 보이는 동작·호환·패키징·보안 위주. 내부 절차 전문을 그대로 붙여 넣지 않음.
34
+ - **브랜딩**: 코드·문서·README·커밋·배포물에 에디터·벤더 도구 이름을 넣지 않음.
35
+ - **비용·효율**: 짧고 신호가 큰 답·패치 위주; 불필요한 장문·동일 맥락 반복 지양. 한 번에 목표 하나. 읽기 전용 작업은 요약과 경로 위주.
36
+
37
+ English sections above are canonical for tooling; this block is a short Korean mirror for the same rules.
38
+
39
+ ## Handoff format
40
+
41
+ When handing work between tools or people, use:
42
+
43
+ ```markdown
44
+ ## Task: [title]
45
+ ### Files to modify
46
+ - `path/to/file`: [what to change]
47
+ ### Design decisions
48
+ - [decision]
49
+ ### Constraints
50
+ - [constraint]
51
+ ```
@@ -0,0 +1,24 @@
1
+ ---
2
+ description: Git 커밋 메시지 — 짧고 의미 있게(저비용 출력)
3
+ alwaysApply: false
4
+ ---
5
+
6
+ # Git commit messages (agent)
7
+
8
+ 커밋 메시지·스테이징 요약을 쓸 때만 적용. **출력은 최소 토큰**, 내용은 **검색·리뷰에 쓸 만한 정보**만.
9
+
10
+ ## 형식 (기본)
11
+
12
+ - **첫 줄만**: 명령형, **72자 이내**, 마침표 없음.
13
+ - **선택** `type(scope): summary` — `feat` `fix` `refactor` `docs` `chore` 등; `scope`는 디렉터리·모듈 한 단어.
14
+ - **본문**: 사용자가 요청하거나 변경이 여러 주제일 때만. 그때도 **불릿 최대 3줄**, 각 80자 이내.
15
+
16
+ ## 쓸 것 / 쓰지 말 것
17
+
18
+ - **쓸 것**: 무엇이 바뀌었는지 한 문장(동작·계약·호환). 위험한 변경이면 한 단어로 표시(`BREAKING` 또는 마이그레이션 한 줄).
19
+ - **쓰지 말 것**: diff 재서술, 파일 목록 나열, “업데이트함/개선함” 같은 빈 말, 이모지, 장문 튜토리얼, 영어·한국어 이중 번역(팀 언어 하나만).
20
+
21
+ ## 비용
22
+
23
+ - 한 커밋 주제면 **제목 한 줄로 끝낸다.**
24
+ - 스테이징이 크면 제목은 요약하고, 본문은 **사용자가 본문 달라고 할 때만** 추가한다.
@@ -0,0 +1,34 @@
1
+ ---
2
+ description: Handoff format rules for multi-AI workflow
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # Multi-AI Workflow
7
+
8
+ ## Receiving Handoff from Other Tools
9
+
10
+ When the user pastes a design or plan from another assistant or planning tool:
11
+
12
+ 1. Parse the **handoff format** (see `AGENTS.md`, heading **Handoff format**).
13
+ 2. Validate file paths exist before modifying.
14
+ 3. Execute changes as specified — do not redesign unless the user asks.
15
+ 4. Report back in the same handoff format for further iteration.
16
+
17
+ ## Producing Handoff for Other Tools
18
+
19
+ When the user asks to prepare work for another tool:
20
+
21
+ 1. Output the handoff format with concrete file paths and change descriptions.
22
+ 2. Include any constraints or dependencies.
23
+ 3. Do not include editor-specific instructions (token budgets, local rule file paths).
24
+
25
+ ## Role: Execution
26
+
27
+ In this workflow the agent's primary role is **execution** — applying concrete changes across multiple files. For design, analysis, or planning, defer to the user's chosen tool when they indicate.
28
+
29
+ ## Cost awareness (effective use of context)
30
+
31
+ - Keep sessions **focused** — one clear objective per session when possible.
32
+ - Prefer **concise** handoffs and replies: concrete paths, decisions, and diffs over narrative padding.
33
+ - For read-only analysis, prefer **summaries** and scoped excerpts over dumping whole files; use lightweight review modes when the product offers them.
34
+ - When producing handoffs for *other* tools, omit local-only tuning hints (see **Producing Handoff** above); still stay efficient in your own responses here.
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "deuk-agent-rule",
3
+ "version": "1.0.1",
4
+ "description": "DeukAgentRules: generic AGENTS.md + .cursor rule templates with init/merge CLI (npm name: deuk-agent-rule).",
5
+ "license": "UNLICENSED",
6
+ "files": [
7
+ "bundle/**/*",
8
+ "scripts/**/*.mjs",
9
+ "README.md",
10
+ "README.ko.md"
11
+ ],
12
+ "scripts": {
13
+ "sync": "node scripts/sync-bundle.mjs",
14
+ "prepack": "node scripts/sync-bundle.mjs",
15
+ "merge:dry": "node scripts/cli.mjs merge --dry-run --cwd .. --agents skip",
16
+ "sync:oss": "node scripts/sync-oss.mjs"
17
+ },
18
+ "engines": {
19
+ "node": ">=18"
20
+ },
21
+ "bin": {
22
+ "deuk-agent-rule": "./scripts/cli.mjs"
23
+ }
24
+ }
@@ -0,0 +1,199 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync } from "fs";
3
+ import { dirname, join } from "path";
4
+ import { fileURLToPath } from "url";
5
+ import {
6
+ applyAgents,
7
+ applyRules,
8
+ readBundleAgents,
9
+ resolveMarkers,
10
+ } from "./merge-logic.mjs";
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const pkgRoot = join(__dirname, "..");
14
+ const bundleRoot = join(pkgRoot, "bundle");
15
+
16
+ function printHelp() {
17
+ console.log(
18
+ "DeukAgentRules (npm: deuk-agent-rule) — AGENTS.md + .cursor/rules templates\n\nUsage:\n npx deuk-agent-rule init [options]\n npx deuk-agent-rule merge [options]\n\nPrimary flow:\n npm install deuk-agent-rule\n npx deuk-agent-rule init\n\nOptions:\n --cwd <path>\n --dry-run\n --backup\n --tag <id>\n --marker-begin <string>\n --marker-end <string>\n\ninit defaults: agents inject (append markers if missing), rules prefix\nmerge defaults: agents inject (fail if no markers unless --append-if-no-markers), rules skip\n\nAlso:\n --agents skip|overwrite|inject\n --rules skip|overwrite|prefix\n --append-if-no-markers\n\nKorean: package README.ko.md\n",
19
+ );
20
+ }
21
+
22
+ function parseArgs(argv) {
23
+ const out = {
24
+ cwd: process.cwd(),
25
+ dryRun: false,
26
+ backup: false,
27
+ tag: undefined,
28
+ markerBegin: undefined,
29
+ markerEnd: undefined,
30
+ agents: undefined,
31
+ rules: undefined,
32
+ appendIfNoMarkers: false,
33
+ };
34
+ for (let i = 0; i < argv.length; i++) {
35
+ const a = argv[i];
36
+ if (a === "--cwd") {
37
+ out.cwd = argv[++i];
38
+ if (!out.cwd) throw new Error("--cwd requires a path");
39
+ } else if (a === "--dry-run") out.dryRun = true;
40
+ else if (a === "--backup") out.backup = true;
41
+ else if (a === "--tag") {
42
+ out.tag = argv[++i];
43
+ if (out.tag == null) throw new Error("--tag requires a value");
44
+ } else if (a === "--marker-begin") {
45
+ out.markerBegin = argv[++i];
46
+ if (out.markerBegin == null) throw new Error("--marker-begin requires a value");
47
+ } else if (a === "--marker-end") {
48
+ out.markerEnd = argv[++i];
49
+ if (out.markerEnd == null) throw new Error("--marker-end requires a value");
50
+ } else if (a === "--agents") {
51
+ out.agents = argv[++i];
52
+ if (!out.agents) throw new Error("--agents requires skip|overwrite|inject");
53
+ } else if (a === "--rules") {
54
+ out.rules = argv[++i];
55
+ if (!out.rules) throw new Error("--rules requires skip|overwrite|prefix");
56
+ } else if (a === "--append-if-no-markers") out.appendIfNoMarkers = true;
57
+ else if (a === "-h" || a === "--help") {
58
+ printHelp();
59
+ process.exit(0);
60
+ } else {
61
+ throw new Error("Unknown argument: " + a);
62
+ }
63
+ }
64
+ return out;
65
+ }
66
+
67
+ function validateMode(name, v, allowed) {
68
+ if (!allowed.includes(v)) {
69
+ throw new Error("Invalid " + name + ": " + v + " (allowed: " + allowed.join(", ") + ")");
70
+ }
71
+ }
72
+
73
+ function runInit(opts) {
74
+ const markers = resolveMarkers({
75
+ tag: opts.tag,
76
+ markerBegin: opts.markerBegin,
77
+ markerEnd: opts.markerEnd,
78
+ });
79
+ const agentsMode = opts.agents ?? "inject";
80
+ validateMode("agents", agentsMode, ["skip", "overwrite", "inject"]);
81
+
82
+ const rulesMode = opts.rules ?? "prefix";
83
+ validateMode("rules", rulesMode, ["skip", "overwrite", "prefix"]);
84
+
85
+ const bundleContent = readBundleAgents(bundleRoot);
86
+ const targetAgents = join(opts.cwd, "AGENTS.md");
87
+ const targetRules = join(opts.cwd, ".cursor", "rules");
88
+
89
+ const agentsResult = applyAgents({
90
+ targetPath: targetAgents,
91
+ bundleContent,
92
+ markers,
93
+ flavor: "init",
94
+ appendIfNoMarkers: opts.appendIfNoMarkers,
95
+ dryRun: opts.dryRun,
96
+ backup: opts.backup,
97
+ agentsMode,
98
+ });
99
+ console.log("AGENTS.md: " + agentsResult.action + (agentsResult.mode ? " (" + agentsResult.mode + ")" : ""));
100
+
101
+ const ruleActions = applyRules({
102
+ bundleRulesDir: join(bundleRoot, "rules"),
103
+ targetRulesDir: targetRules,
104
+ rulesMode,
105
+ dryRun: opts.dryRun,
106
+ backup: opts.backup,
107
+ });
108
+ for (const r of ruleActions) {
109
+ console.log("rule " + r.action + ": " + (r.dest || r.src) + (r.reason ? " (" + r.reason + ")" : ""));
110
+ }
111
+ }
112
+
113
+ function runMerge(opts) {
114
+ const markers = resolveMarkers({
115
+ tag: opts.tag,
116
+ markerBegin: opts.markerBegin,
117
+ markerEnd: opts.markerEnd,
118
+ });
119
+ const agentsMode = opts.agents ?? "inject";
120
+ validateMode("agents", agentsMode, ["skip", "overwrite", "inject"]);
121
+
122
+ const rulesMode = opts.rules ?? "skip";
123
+ validateMode("rules", rulesMode, ["skip", "overwrite", "prefix"]);
124
+
125
+ const bundleContent = readBundleAgents(bundleRoot);
126
+ const targetAgents = join(opts.cwd, "AGENTS.md");
127
+ const targetRules = join(opts.cwd, ".cursor", "rules");
128
+
129
+ const agentsResult = applyAgents({
130
+ targetPath: targetAgents,
131
+ bundleContent,
132
+ markers,
133
+ flavor: "merge",
134
+ appendIfNoMarkers: opts.appendIfNoMarkers,
135
+ dryRun: opts.dryRun,
136
+ backup: opts.backup,
137
+ agentsMode,
138
+ });
139
+ console.log("AGENTS.md: " + agentsResult.action + (agentsResult.mode ? " (" + agentsResult.mode + ")" : ""));
140
+
141
+ const ruleActions = applyRules({
142
+ bundleRulesDir: join(bundleRoot, "rules"),
143
+ targetRulesDir: targetRules,
144
+ rulesMode,
145
+ dryRun: opts.dryRun,
146
+ backup: opts.backup,
147
+ });
148
+ for (const r of ruleActions) {
149
+ console.log("rule " + r.action + ": " + (r.dest || r.src) + (r.reason ? " (" + r.reason + ")" : ""));
150
+ }
151
+ }
152
+
153
+ function main() {
154
+ const argv = process.argv.slice(2);
155
+ const sub = argv[0];
156
+ if (!sub || sub === "-h" || sub === "--help") {
157
+ printHelp();
158
+ process.exit(0);
159
+ }
160
+
161
+ const rest = argv.slice(1);
162
+ if (sub === "help") {
163
+ printHelp();
164
+ process.exit(0);
165
+ }
166
+
167
+ let opts;
168
+ try {
169
+ opts = parseArgs(rest);
170
+ } catch (e) {
171
+ console.error(e.message || e);
172
+ printHelp();
173
+ process.exit(1);
174
+ }
175
+
176
+ if (!existsSync(bundleRoot)) {
177
+ console.error(
178
+ "Missing bundle/ (run from published package or run npm run sync in deuk-agent-rule when developing in the monorepo).",
179
+ );
180
+ process.exit(1);
181
+ }
182
+
183
+ try {
184
+ if (sub === "init") {
185
+ runInit(opts);
186
+ } else if (sub === "merge") {
187
+ runMerge(opts);
188
+ } else {
189
+ console.error("Unknown command: " + sub);
190
+ printHelp();
191
+ process.exit(1);
192
+ }
193
+ } catch (e) {
194
+ console.error(e.message || e);
195
+ process.exit(1);
196
+ }
197
+ }
198
+
199
+ main();
@@ -0,0 +1,203 @@
1
+ import {
2
+ copyFileSync,
3
+ existsSync,
4
+ mkdirSync,
5
+ readFileSync,
6
+ readdirSync,
7
+ writeFileSync,
8
+ } from "fs";
9
+ import { join } from "path";
10
+
11
+ export const DEFAULT_TAG = "deuk-agent-rule";
12
+
13
+ export function resolveMarkers(o) {
14
+ const hasBegin = o.markerBegin != null && o.markerBegin !== "";
15
+ const hasEnd = o.markerEnd != null && o.markerEnd !== "";
16
+ if (hasBegin !== hasEnd) {
17
+ throw new Error("Use both --marker-begin and --marker-end, or neither.");
18
+ }
19
+ if (hasBegin && hasEnd) {
20
+ if (o.markerBegin === o.markerEnd) {
21
+ throw new Error("--marker-begin and --marker-end must differ.");
22
+ }
23
+ return { begin: o.markerBegin, end: o.markerEnd };
24
+ }
25
+ const id = o.tag && o.tag.trim() ? o.tag.trim() : DEFAULT_TAG;
26
+ return {
27
+ begin: "<!-- " + id + ":begin -->",
28
+ end: "<!-- " + id + ":end -->",
29
+ };
30
+ }
31
+
32
+ export function findMarkerRegion(content, begin, end) {
33
+ const i = content.indexOf(begin);
34
+ if (i === -1) return null;
35
+ const j = content.indexOf(end, i + begin.length);
36
+ if (j === -1) {
37
+ throw new Error(
38
+ "Found begin marker but no matching end marker after it.\n begin: " + begin + "\n end: " + end,
39
+ );
40
+ }
41
+ const innerStart = i + begin.length;
42
+ const innerEnd = j;
43
+ return { innerStart, innerEnd };
44
+ }
45
+
46
+ export function applyAgents(opts) {
47
+ const {
48
+ targetPath,
49
+ bundleContent,
50
+ markers,
51
+ flavor,
52
+ appendIfNoMarkers,
53
+ dryRun,
54
+ backup,
55
+ agentsMode,
56
+ } = opts;
57
+
58
+ if (agentsMode === "skip") {
59
+ return {
60
+ action: "skip",
61
+ reason: existsSync(targetPath)
62
+ ? "agents mode skip (file exists)"
63
+ : "agents mode skip",
64
+ };
65
+ }
66
+
67
+ if (agentsMode === "overwrite") {
68
+ const prev = existsSync(targetPath) ? readFileSync(targetPath, "utf8") : "";
69
+ if (dryRun) {
70
+ return { action: "would-write", path: targetPath, mode: "overwrite" };
71
+ }
72
+ if (backup && existsSync(targetPath)) {
73
+ copyFileSync(targetPath, targetPath + ".bak");
74
+ }
75
+ writeFileSync(targetPath, bundleContent, "utf8");
76
+ return { action: "write", path: targetPath, mode: "overwrite", hadPrevious: !!prev };
77
+ }
78
+
79
+ const existing = existsSync(targetPath) ? readFileSync(targetPath, "utf8") : "";
80
+ const region = findMarkerRegion(existing, markers.begin, markers.end);
81
+
82
+ if (region) {
83
+ const inner = bundleContent.trimEnd() + (bundleContent.endsWith("\n") ? "" : "\n");
84
+ const next =
85
+ existing.slice(0, region.innerStart) +
86
+ "\n" +
87
+ inner +
88
+ existing.slice(region.innerEnd);
89
+ if (dryRun) {
90
+ return { action: "would-write", path: targetPath, mode: "inject-region" };
91
+ }
92
+ if (backup && existsSync(targetPath)) {
93
+ copyFileSync(targetPath, targetPath + ".bak");
94
+ }
95
+ writeFileSync(targetPath, next, "utf8");
96
+ return { action: "write", path: targetPath, mode: "inject-region" };
97
+ }
98
+
99
+ const allowAppend =
100
+ appendIfNoMarkers || flavor === "init";
101
+
102
+ if (!allowAppend) {
103
+ const hint = [
104
+ "",
105
+ "No marker region found. Add a pair like:",
106
+ "",
107
+ markers.begin,
108
+ "",
109
+ markers.end,
110
+ "",
111
+ "Or run: npx deuk-agent-rule init (appends markers once)",
112
+ "Or pass: --append-if-no-markers",
113
+ "",
114
+ ].join("\n");
115
+ throw new Error(
116
+ "Inject mode requires markers in " + targetPath + " or use --append-if-no-markers." + hint,
117
+ );
118
+ }
119
+
120
+ const inner = bundleContent.trimEnd() + (bundleContent.endsWith("\n") ? "" : "\n");
121
+ const block = "\n" + markers.begin + "\n\n" + inner + "\n" + markers.end + "\n";
122
+ const next = existing ? existing.replace(/\s*$/, "") + block : markers.begin + "\n\n" + inner + "\n" + markers.end + "\n";
123
+
124
+ if (dryRun) {
125
+ return {
126
+ action: "would-write",
127
+ path: targetPath,
128
+ mode: flavor === "init" ? "append-markers-init" : "append-markers",
129
+ };
130
+ }
131
+ if (backup && existsSync(targetPath)) {
132
+ copyFileSync(targetPath, targetPath + ".bak");
133
+ }
134
+ writeFileSync(targetPath, next, "utf8");
135
+ return {
136
+ action: "write",
137
+ path: targetPath,
138
+ mode: flavor === "init" ? "append-markers-init" : "append-markers",
139
+ };
140
+ }
141
+
142
+ export function applyRules(opts) {
143
+ const {
144
+ bundleRulesDir,
145
+ targetRulesDir,
146
+ rulesMode,
147
+ filePrefix = "deuk-agent-rule-",
148
+ dryRun,
149
+ backup,
150
+ } = opts;
151
+
152
+ if (!existsSync(bundleRulesDir)) {
153
+ throw new Error("Bundle rules directory missing: " + bundleRulesDir);
154
+ }
155
+
156
+ const actions = [];
157
+ mkdirSync(targetRulesDir, { recursive: true });
158
+
159
+ for (const name of readdirSync(bundleRulesDir)) {
160
+ if (!name.endsWith(".mdc")) continue;
161
+ const src = join(bundleRulesDir, name);
162
+ let destPath = join(targetRulesDir, name);
163
+
164
+ if (rulesMode === "skip" && existsSync(destPath)) {
165
+ actions.push({ action: "skip", src, dest: destPath, reason: "exists" });
166
+ continue;
167
+ }
168
+
169
+ if (rulesMode === "prefix" && existsSync(destPath)) {
170
+ destPath = join(targetRulesDir, filePrefix + name);
171
+ if (existsSync(destPath)) {
172
+ actions.push({
173
+ action: "skip",
174
+ src,
175
+ dest: destPath,
176
+ reason: "prefixed target also exists",
177
+ });
178
+ continue;
179
+ }
180
+ }
181
+
182
+ if (dryRun) {
183
+ actions.push({ action: "would-copy", src, dest: destPath });
184
+ continue;
185
+ }
186
+
187
+ if (backup && existsSync(destPath)) {
188
+ copyFileSync(destPath, destPath + ".bak");
189
+ }
190
+ copyFileSync(src, destPath);
191
+ actions.push({ action: "copy", src, dest: destPath });
192
+ }
193
+
194
+ return actions;
195
+ }
196
+
197
+ export function readBundleAgents(bundleRoot) {
198
+ const p = join(bundleRoot, "AGENTS.md");
199
+ if (!existsSync(p)) {
200
+ throw new Error("Bundle AGENTS.md missing: " + p);
201
+ }
202
+ return readFileSync(p, "utf8");
203
+ }
@@ -0,0 +1,44 @@
1
+ import {
2
+ copyFileSync,
3
+ existsSync,
4
+ mkdirSync,
5
+ readFileSync,
6
+ readdirSync,
7
+ rmSync,
8
+ writeFileSync,
9
+ } from "fs";
10
+ import { join, dirname } from "path";
11
+ import { fileURLToPath } from "url";
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ const pkgRoot = join(__dirname, "..");
15
+
16
+ /** Public npm bundle: generic template only (no monorepo paths or internal handoffs). */
17
+ const publishDir = join(pkgRoot, "publish");
18
+ const publishRulesDir = join(publishDir, "rules");
19
+ const rulesDest = join(pkgRoot, "bundle", "rules");
20
+ const agentsSrc = join(publishDir, "AGENTS.md");
21
+ const agentsDest = join(pkgRoot, "bundle", "AGENTS.md");
22
+
23
+ if (!existsSync(publishDir)) {
24
+ throw new Error("Missing publish template dir: " + publishDir);
25
+ }
26
+ if (!existsSync(publishRulesDir)) {
27
+ throw new Error("Missing publish/rules: " + publishRulesDir);
28
+ }
29
+ if (!existsSync(agentsSrc)) {
30
+ throw new Error("Missing publish/AGENTS.md: " + agentsSrc);
31
+ }
32
+
33
+ if (existsSync(rulesDest)) {
34
+ rmSync(rulesDest, { recursive: true });
35
+ }
36
+ mkdirSync(rulesDest, { recursive: true });
37
+ for (const name of readdirSync(publishRulesDir)) {
38
+ if (!name.endsWith(".mdc")) continue;
39
+ copyFileSync(join(publishRulesDir, name), join(rulesDest, name));
40
+ }
41
+
42
+ const agentsBody = readFileSync(agentsSrc, "utf8");
43
+ writeFileSync(agentsDest, agentsBody, "utf8");
44
+ console.log("deuk-agent-rule: synced bundle from publish/ template.");
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Populates ../DeukAgentRulesOSS for the public GitHub repo.
3
+ * Run: cd deuk-agent-rule && npm run sync:oss
4
+ */
5
+ import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
6
+ import { dirname, join } from "path";
7
+ import { fileURLToPath } from "url";
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const pkgRoot = join(__dirname, "..");
11
+ const repoRoot = join(pkgRoot, "..");
12
+ const ossRoot = join(repoRoot, "DeukAgentRulesOSS");
13
+ const ossPublic = join(pkgRoot, "oss-public");
14
+
15
+ /** Set DEUK_AGENT_RULES_OSS_REPO to override, e.g. https://github.com/you/DeukAgentRulesOSS */
16
+ const OSS_REPO =
17
+ process.env.DEUK_AGENT_RULES_OSS_REPO || "https://github.com/joygram/DeukAgentRules";
18
+
19
+ function gitBase() {
20
+ let u = OSS_REPO.trim().replace(/\.git$/i, "").replace(/\/$/, "");
21
+ if (!u.startsWith("http")) return u;
22
+ return u;
23
+ }
24
+
25
+ const base = gitBase();
26
+ const gitUrl = base.startsWith("http") ? "git+" + base + ".git" : base;
27
+
28
+ mkdirSync(join(ossRoot, "scripts"), { recursive: true });
29
+ mkdirSync(join(ossRoot, "publish"), { recursive: true });
30
+
31
+ cpSync(join(pkgRoot, "publish"), join(ossRoot, "publish"), { recursive: true, force: true });
32
+ cpSync(join(pkgRoot, "scripts", "cli.mjs"), join(ossRoot, "scripts", "cli.mjs"), { force: true });
33
+ cpSync(join(pkgRoot, "scripts", "merge-logic.mjs"), join(ossRoot, "scripts", "merge-logic.mjs"), {
34
+ force: true,
35
+ });
36
+ cpSync(join(pkgRoot, "scripts", "sync-bundle.mjs"), join(ossRoot, "scripts", "sync-bundle.mjs"), {
37
+ force: true,
38
+ });
39
+
40
+ if (!existsSync(ossPublic)) {
41
+ throw new Error("Missing oss-public/: " + ossPublic);
42
+ }
43
+ cpSync(join(pkgRoot, "README.md"), join(ossRoot, "README.md"), { force: true });
44
+ cpSync(join(pkgRoot, "README.ko.md"), join(ossRoot, "README.ko.md"), { force: true });
45
+ cpSync(join(ossPublic, "RELEASING.md"), join(ossRoot, "RELEASING.md"), { force: true });
46
+ cpSync(join(ossPublic, "RELEASING.ko.md"), join(ossRoot, "RELEASING.ko.md"), { force: true });
47
+ cpSync(join(ossPublic, "GITHUB_DESCRIPTION.md"), join(ossRoot, "GITHUB_DESCRIPTION.md"), {
48
+ force: true,
49
+ });
50
+
51
+ const srcPkg = JSON.parse(readFileSync(join(pkgRoot, "package.json"), "utf8"));
52
+ const outPkg = {
53
+ ...srcPkg,
54
+ license: "MIT",
55
+ repository: {
56
+ type: "git",
57
+ url: gitUrl,
58
+ },
59
+ bugs: {
60
+ url: base.startsWith("http") ? base + "/issues" : base,
61
+ },
62
+ homepage: base.startsWith("http") ? base + "#readme" : base,
63
+ files: ["LICENSE", "bundle/**/*", "scripts/**/*.mjs", "README.md", "README.ko.md"],
64
+ };
65
+ delete outPkg.private;
66
+ if (outPkg.scripts && outPkg.scripts["merge:dry"]) {
67
+ const { "merge:dry": _md, ...r2 } = outPkg.scripts;
68
+ outPkg.scripts = r2;
69
+ }
70
+ if (outPkg.scripts && outPkg.scripts["sync:oss"]) {
71
+ const { "sync:oss": _drop, ...rest } = outPkg.scripts;
72
+ outPkg.scripts = rest;
73
+ }
74
+
75
+ writeFileSync(join(ossRoot, "package.json"), JSON.stringify(outPkg, null, 2) + "\n", "utf8");
76
+
77
+ console.log("deuk-agent-rule: synced OSS tree at " + ossRoot);
78
+ console.log(" Override repo URL: DEUK_AGENT_RULES_OSS_REPO=https://github.com/org/DeukAgentRulesOSS");