deuk-agent-flow 5.0.2 → 5.0.7
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 +45 -0
- package/CHANGELOG.md +48 -0
- package/bundled/deuk-agent-flow.vsix +0 -0
- package/core-rules/AGENTS.md +5 -5
- package/package.json +20 -2
- package/scripts/bundle-vscode-vsix.ts +1 -1
- package/scripts/cli-args.ts +46 -1
- package/scripts/cli-context-commands.ts +35 -0
- package/scripts/cli-init-claude.ts +179 -0
- package/scripts/cli-init-commands.ts +21 -8
- package/scripts/cli-init-core.ts +89 -0
- package/scripts/cli-init-instructions.ts +489 -0
- package/scripts/cli-init-migrate.ts +504 -0
- package/scripts/cli-init-workspace.ts +63 -0
- package/scripts/cli-locales.ts +85 -0
- package/scripts/cli-skill-commands.ts +3 -1
- package/scripts/cli-ticket-commands.ts +68 -2
- package/scripts/cli-ticket-docmeta.ts +103 -0
- package/scripts/cli-ticket-document.ts +153 -0
- package/scripts/cli-ticket-evidence.ts +312 -0
- package/scripts/cli-ticket-home.ts +449 -0
- package/scripts/cli-ticket-index.ts +8 -0
- package/scripts/cli-ticket-scan.ts +102 -0
- package/scripts/cli-ticket-surface.ts +56 -0
- package/scripts/cli.ts +10 -1
- package/scripts/oss-prepack.js +32 -0
- package/scripts/out/scripts/bundle-vscode-vsix.js +68 -0
- package/scripts/out/scripts/bundle-vscode-vsix.js.map +1 -0
- package/scripts/out/scripts/cli-args.js +411 -0
- package/scripts/out/scripts/cli-args.js.map +1 -0
- package/scripts/out/scripts/cli-context-commands.js +31 -0
- package/scripts/out/scripts/cli-context-commands.js.map +1 -0
- package/scripts/out/scripts/cli-init-claude.js +176 -0
- package/scripts/out/scripts/cli-init-claude.js.map +1 -0
- package/scripts/out/scripts/cli-init-commands.js +98 -0
- package/scripts/out/scripts/cli-init-commands.js.map +1 -0
- package/scripts/out/scripts/cli-init-core.js +89 -0
- package/scripts/out/scripts/cli-init-core.js.map +1 -0
- package/scripts/out/scripts/cli-init-instructions.js +460 -0
- package/scripts/out/scripts/cli-init-instructions.js.map +1 -0
- package/scripts/out/scripts/cli-init-logic.js +41 -0
- package/scripts/out/scripts/cli-init-logic.js.map +1 -0
- package/scripts/out/scripts/cli-init-migrate.js +497 -0
- package/scripts/out/scripts/cli-init-migrate.js.map +1 -0
- package/scripts/out/scripts/cli-init-workspace.js +57 -0
- package/scripts/out/scripts/cli-init-workspace.js.map +1 -0
- package/scripts/out/scripts/cli-locales.js +83 -0
- package/scripts/out/scripts/cli-locales.js.map +1 -0
- package/scripts/out/scripts/cli-prompts.js +101 -0
- package/scripts/out/scripts/cli-prompts.js.map +1 -0
- package/scripts/out/scripts/cli-rule-compiler.js +121 -0
- package/scripts/out/scripts/cli-rule-compiler.js.map +1 -0
- package/scripts/out/scripts/cli-skill-commands.js +708 -0
- package/scripts/out/scripts/cli-skill-commands.js.map +1 -0
- package/scripts/out/scripts/cli-telemetry-commands.js +604 -0
- package/scripts/out/scripts/cli-telemetry-commands.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-command-shared.js +2 -0
- package/scripts/out/scripts/cli-ticket-command-shared.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-commands.js +3538 -0
- package/scripts/out/scripts/cli-ticket-commands.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-docmeta.js +90 -0
- package/scripts/out/scripts/cli-ticket-docmeta.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-document.js +121 -0
- package/scripts/out/scripts/cli-ticket-document.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-evidence.js +286 -0
- package/scripts/out/scripts/cli-ticket-evidence.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-home.js +440 -0
- package/scripts/out/scripts/cli-ticket-home.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-index.js +280 -0
- package/scripts/out/scripts/cli-ticket-index.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-migration.js +261 -0
- package/scripts/out/scripts/cli-ticket-migration.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-parser.js +90 -0
- package/scripts/out/scripts/cli-ticket-parser.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-scan.js +93 -0
- package/scripts/out/scripts/cli-ticket-scan.js.map +1 -0
- package/scripts/out/scripts/cli-ticket-surface.js +53 -0
- package/scripts/out/scripts/cli-ticket-surface.js.map +1 -0
- package/scripts/out/scripts/cli-usage-commands.js +310 -0
- package/scripts/out/scripts/cli-usage-commands.js.map +1 -0
- package/scripts/out/scripts/cli-utils.js +1544 -0
- package/scripts/out/scripts/cli-utils.js.map +1 -0
- package/scripts/out/scripts/cli.js +705 -0
- package/scripts/out/scripts/cli.js.map +1 -0
- package/scripts/out/scripts/lint-md.js +238 -0
- package/scripts/out/scripts/lint-md.js.map +1 -0
- package/scripts/out/scripts/lint-rules.js +193 -0
- package/scripts/out/scripts/lint-rules.js.map +1 -0
- package/scripts/out/scripts/merge-logic.js +38 -0
- package/scripts/out/scripts/merge-logic.js.map +1 -0
- package/scripts/out/scripts/plan-parser.js +52 -0
- package/scripts/out/scripts/plan-parser.js.map +1 -0
- package/scripts/out/scripts/ticket-machine.js +94 -0
- package/scripts/out/scripts/ticket-machine.js.map +1 -0
- package/scripts/out/scripts/ticket-workflow.js +498 -0
- package/scripts/out/scripts/ticket-workflow.js.map +1 -0
- package/scripts/ticket-machine.ts +99 -0
- package/scripts/ticket-workflow.ts +544 -0
- package/templates/skills/role-closer/SKILL.md +27 -0
- package/templates/skills/role-implementer/SKILL.md +30 -0
- package/templates/skills/role-planner/SKILL.md +29 -0
- package/templates/skills/role-qa-verifier/SKILL.md +30 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import { basename } from "path";
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, readdirSync, unlinkSync, rmSync, renameSync, statSync, copyFileSync, cpSync } from "fs";
|
|
3
|
+
import type { Dirent } from "fs";
|
|
4
|
+
import { randomUUID } from "crypto";
|
|
5
|
+
import { LEGACY_DEUK, DEUK_ROOT_DIR, CliOpts, WORKSPACE_MARKER_FILE, getWorkspaceInitConfigPath, makePath, resolveDocsLanguage, resolveUserHome, toRepoRelativePath } from "./cli-utils.js";
|
|
6
|
+
import { CANONICAL_INIT_CLEANUP_PATHS, safeReadText, splitManagedBlock } from "./cli-init-core.js";
|
|
7
|
+
import { isGeneratedDeukPointer, splitProjectDoc } from "./cli-init-instructions.js";
|
|
8
|
+
import { cliText, resolveCliLocale } from "./cli-locales.js";
|
|
9
|
+
|
|
10
|
+
function resolveInitLocale(docsLanguage = "auto") {
|
|
11
|
+
return resolveCliLocale(resolveDocsLanguage(docsLanguage));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// #713: home root directory migration ~/.deuk-agent → ~/.deuk. The literal legacy name is
|
|
15
|
+
// fixed here (DEUK_ROOT_DIR now resolves to ".deuk", so it can't name the OLD dir). Runs on
|
|
16
|
+
// every init entry and is self-healing/idempotent:
|
|
17
|
+
// (A) legacy exists && new absent → try rename (fast); on failure (another agent holds
|
|
18
|
+
// .deuk-agent, EBUSY/EPERM) fall back to recursive copy, preserving the original so
|
|
19
|
+
// the older agent keeps working.
|
|
20
|
+
// (B) BOTH exist (copy left a leftover) → keep retrying rmSync(.deuk-agent) each init;
|
|
21
|
+
// once the other agent releases it, it gets removed. Safety guard: never delete the
|
|
22
|
+
// legacy dir while the new ~/.deuk is empty (would risk data loss).
|
|
23
|
+
function dirHasEntries(dirAbs: string): boolean {
|
|
24
|
+
try {
|
|
25
|
+
return readdirSync(dirAbs).length > 0;
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function migrateDeukRootDir(opts: CliOpts = {}) {
|
|
32
|
+
const home = resolveUserHome(opts);
|
|
33
|
+
const dryRun = Boolean(opts.dryRun);
|
|
34
|
+
const legacyAbs = makePath(home, LEGACY_DEUK.rootDir);
|
|
35
|
+
const nextAbs = makePath(home, DEUK_ROOT_DIR); // ~/.deuk
|
|
36
|
+
|
|
37
|
+
const legacyExists = existsSync(legacyAbs);
|
|
38
|
+
const nextExists = existsSync(nextAbs);
|
|
39
|
+
|
|
40
|
+
// (A) move legacy → new when only legacy is present.
|
|
41
|
+
if (legacyExists && !nextExists) {
|
|
42
|
+
if (dryRun) {
|
|
43
|
+
console.log(`[MIGRATE] Would migrate ${legacyAbs} -> ${nextAbs} (rename, copy fallback)`);
|
|
44
|
+
return { migrated: false, mode: "dry-run" };
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
renameSync(legacyAbs, nextAbs);
|
|
48
|
+
console.log(`[MIGRATE] Moved ${LEGACY_DEUK.rootDir} -> ${DEUK_ROOT_DIR}`);
|
|
49
|
+
return { migrated: true, mode: "rename" };
|
|
50
|
+
} catch {
|
|
51
|
+
// Another agent likely holds the legacy dir — copy instead, keep the original intact.
|
|
52
|
+
cpSync(legacyAbs, nextAbs, { recursive: true });
|
|
53
|
+
console.log(`[MIGRATE] Copied ${LEGACY_DEUK.rootDir} -> ${DEUK_ROOT_DIR} (legacy kept for compatibility)`);
|
|
54
|
+
return { migrated: true, mode: "copy" };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// (B) both present (copy leftover) → persistently try to remove the legacy dir, but only
|
|
59
|
+
// once the new dir actually holds data, so a half-finished copy never deletes the source.
|
|
60
|
+
if (legacyExists && nextExists) {
|
|
61
|
+
if (!dirHasEntries(nextAbs)) {
|
|
62
|
+
return { migrated: false, mode: "guard-new-empty" };
|
|
63
|
+
}
|
|
64
|
+
if (dryRun) {
|
|
65
|
+
console.log(`[MIGRATE] Would remove leftover ${legacyAbs} (new ${DEUK_ROOT_DIR} populated)`);
|
|
66
|
+
return { migrated: false, mode: "dry-run" };
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
rmSync(legacyAbs, { recursive: true, force: true });
|
|
70
|
+
console.log(`[MIGRATE] Removed leftover ${LEGACY_DEUK.rootDir} (migration complete)`);
|
|
71
|
+
return { migrated: true, mode: "cleanup" };
|
|
72
|
+
} catch {
|
|
73
|
+
// Still held by another agent — leave it; the next init retries.
|
|
74
|
+
return { migrated: false, mode: "cleanup-deferred" };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return { migrated: false, mode: "noop" };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function removeLegacyPaths(cwd, dryRun, paths, label) {
|
|
82
|
+
let removed = 0;
|
|
83
|
+
for (const relPath of paths) {
|
|
84
|
+
const target = makePath(cwd, relPath);
|
|
85
|
+
if (!existsSync(target)) continue;
|
|
86
|
+
if (!dryRun) rmSync(target, { recursive: true, force: true });
|
|
87
|
+
console.log(`[CLEANUP] removed ${label}: ${toRepoRelativePath(cwd, target)}`);
|
|
88
|
+
removed += 1;
|
|
89
|
+
}
|
|
90
|
+
return removed;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function ensureWritableDirectory(dirAbs, cwd, dryRun, label) {
|
|
94
|
+
if (!existsSync(dirAbs)) return;
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
if (statSync(dirAbs).isDirectory()) return;
|
|
98
|
+
} catch (err) {
|
|
99
|
+
if (process.env.DEBUG) console.warn(`[DEBUG] Failed to inspect ${dirAbs}:`, err);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const backupBase = `${dirAbs}.bak`;
|
|
104
|
+
let backupAbs = backupBase;
|
|
105
|
+
let index = 1;
|
|
106
|
+
while (existsSync(backupAbs)) {
|
|
107
|
+
backupAbs = `${backupBase}.${index}`;
|
|
108
|
+
index += 1;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const relDir = toRepoRelativePath(cwd, dirAbs);
|
|
112
|
+
const relBackup = toRepoRelativePath(cwd, backupAbs);
|
|
113
|
+
if (!dryRun) {
|
|
114
|
+
renameSync(dirAbs, backupAbs);
|
|
115
|
+
}
|
|
116
|
+
console.log(`[MIGRATE] ${label}: ${relDir} -> ${relBackup}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function removeEmptyDirsBottomUp(dir, cwd, dryRun) {
|
|
120
|
+
if (!existsSync(dir)) return;
|
|
121
|
+
for (const entry of sortedDirEntries(dir, { withFileTypes: true })) {
|
|
122
|
+
if (entry.isDirectory()) removeEmptyDirsBottomUp(makePath(dir, entry.name), cwd, dryRun);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
if (sortedDirEntries(dir).length > 0) return;
|
|
127
|
+
if (!dryRun) rmSync(dir, { recursive: true, force: true });
|
|
128
|
+
console.log(`[CLEANUP] removed empty directory: ${toRepoRelativePath(cwd, dir)}`);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
if (process.env.DEBUG) console.warn(`[DEBUG] Failed to prune ${dir}:`, err);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function migrateLegacyStructure(cwd, dryRun) {
|
|
135
|
+
const legacyTemplates = makePath(cwd, LEGACY_DEUK.templateDir);
|
|
136
|
+
if (existsSync(legacyTemplates)) {
|
|
137
|
+
console.log(`[CLEANUP] removing legacy runtime templates: ${LEGACY_DEUK.templateDir}`);
|
|
138
|
+
if (!dryRun && existsSync(legacyTemplates)) rmSync(legacyTemplates, { recursive: true, force: true });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const legacyConfig = makePath(cwd, LEGACY_DEUK.configFile);
|
|
142
|
+
if (existsSync(legacyConfig)) {
|
|
143
|
+
console.log(`[MIGRATE] Removing legacy config: ${LEGACY_DEUK.configFile}`);
|
|
144
|
+
if (!dryRun) unlinkSync(legacyConfig);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
removeLegacyWorkspaceConfig(cwd, dryRun);
|
|
148
|
+
|
|
149
|
+
// Purge workspace .deuk-agent/ — only .deuk-workspace-id marker should remain.
|
|
150
|
+
migrateWorkspaceMarker(cwd, dryRun);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Migrates the workspace marker from the old .deuk-agent/workspace-id scheme to
|
|
154
|
+
// the new .deuk-workspace-id single-file scheme. 4 cases:
|
|
155
|
+
// A: .deuk-workspace-id already exists → skip (already migrated)
|
|
156
|
+
// B: .deuk-agent/workspace-id exists → copy UUID, delete .deuk-agent dir
|
|
157
|
+
// C: .deuk-agent/ exists but no workspace-id → mint new UUID, write file, delete .deuk-agent dir
|
|
158
|
+
// D: neither exists → skip (not a workspace)
|
|
159
|
+
export function migrateWorkspaceMarker(cwd, dryRun) {
|
|
160
|
+
const newMarker = makePath(cwd, WORKSPACE_MARKER_FILE);
|
|
161
|
+
const legacyDir = makePath(cwd, LEGACY_DEUK.rootDir);
|
|
162
|
+
const legacyIdFile = makePath(legacyDir, "workspace-id");
|
|
163
|
+
|
|
164
|
+
// Case A: marker already exists — still purge any leftover .deuk-agent/ dir
|
|
165
|
+
if (existsSync(newMarker)) {
|
|
166
|
+
if (existsSync(legacyDir)) {
|
|
167
|
+
if (!dryRun) rmSync(legacyDir, { recursive: true, force: true });
|
|
168
|
+
console.log(`[MIGRATE] ${dryRun ? "Would remove" : "Removed"} leftover ${LEGACY_DEUK.rootDir}/ directory`);
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Case D: no .deuk-agent dir at all → not a managed workspace
|
|
174
|
+
if (!existsSync(legacyDir)) return;
|
|
175
|
+
|
|
176
|
+
let uuid;
|
|
177
|
+
if (existsSync(legacyIdFile)) {
|
|
178
|
+
// Case B: read existing UUID
|
|
179
|
+
uuid = String(readFileSync(legacyIdFile, "utf8") || "").trim();
|
|
180
|
+
if (!uuid) uuid = randomUUID();
|
|
181
|
+
} else {
|
|
182
|
+
// Case C: .deuk-agent exists but no workspace-id → mint new UUID
|
|
183
|
+
uuid = randomUUID();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!dryRun) {
|
|
187
|
+
writeFileSync(newMarker, uuid + "\n", "utf8");
|
|
188
|
+
rmSync(legacyDir, { recursive: true, force: true });
|
|
189
|
+
}
|
|
190
|
+
console.log(`[MIGRATE] ${dryRun ? "Would migrate" : "Migrated"} workspace marker → ${WORKSPACE_MARKER_FILE} (uuid: ${uuid})`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function removeLegacyWorkspaceConfig(cwd, dryRun) {
|
|
194
|
+
const workspaceConfig = getWorkspaceInitConfigPath(cwd);
|
|
195
|
+
if (!existsSync(workspaceConfig)) return false;
|
|
196
|
+
|
|
197
|
+
if (!dryRun) unlinkSync(workspaceConfig);
|
|
198
|
+
console.log(`[CLEANUP] ${dryRun ? "Would remove" : "Removed"} legacy workspace config: ${toRepoRelativePath(cwd, workspaceConfig)}`);
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function removeNestedLegacyWorkspaceConfigs(cwd, dryRun, ignoreDirs = []) {
|
|
203
|
+
const ignored = new Set([...(ignoreDirs || []), ".git", "node_modules", ".cache"]);
|
|
204
|
+
const roots = [];
|
|
205
|
+
|
|
206
|
+
function visit(dir) {
|
|
207
|
+
let entries = [];
|
|
208
|
+
try {
|
|
209
|
+
entries = sortedDirEntries(dir, { withFileTypes: true });
|
|
210
|
+
} catch {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (existsSync(getWorkspaceInitConfigPath(dir))) roots.push(dir);
|
|
215
|
+
|
|
216
|
+
for (const entry of entries) {
|
|
217
|
+
if (!entry.isDirectory()) continue;
|
|
218
|
+
if (ignored.has(entry.name)) continue;
|
|
219
|
+
if (entry.name === DEUK_ROOT_DIR || LEGACY_DEUK.dirs.includes(entry.name)) continue;
|
|
220
|
+
visit(makePath(dir, entry.name));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
visit(cwd);
|
|
225
|
+
|
|
226
|
+
let migrated = 0;
|
|
227
|
+
for (const root of roots) {
|
|
228
|
+
if (removeLegacyWorkspaceConfig(root, dryRun)) migrated += 1;
|
|
229
|
+
}
|
|
230
|
+
return migrated;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
function sortedDirEntries(dir: string, options: { withFileTypes: true }): Dirent[];
|
|
236
|
+
function sortedDirEntries(dir: string, options?: Record<string, any>): string[];
|
|
237
|
+
function sortedDirEntries(dir: string, options: Record<string, any> = {}): (string | Dirent)[] {
|
|
238
|
+
const entries = readdirSync(dir, options as any) as (string | Dirent)[];
|
|
239
|
+
return entries.sort((a, b) => {
|
|
240
|
+
const aName = typeof a === "string" ? a : (a as Dirent).name;
|
|
241
|
+
const bName = typeof b === "string" ? b : (b as Dirent).name;
|
|
242
|
+
return String(aName).localeCompare(String(bName));
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function sameFileContent(leftPath, rightPath) {
|
|
247
|
+
return safeReadText(leftPath) === safeReadText(rightPath);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export function walkMdFiles(dir, callback) {
|
|
251
|
+
if (!existsSync(dir)) return;
|
|
252
|
+
for (const entry of sortedDirEntries(dir, { withFileTypes: true })) {
|
|
253
|
+
const p = makePath(dir, entry.name);
|
|
254
|
+
if (entry.isDirectory()) {
|
|
255
|
+
walkMdFiles(p, callback);
|
|
256
|
+
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
257
|
+
callback(p);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function escapeRegExp(value) {
|
|
263
|
+
return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function migrateHtmlMarkersToHeadings(cwd, dryRun) {
|
|
267
|
+
const locale = resolveInitLocale();
|
|
268
|
+
const agentsPath = makePath(cwd, "AGENTS.md");
|
|
269
|
+
if (!existsSync(agentsPath)) return;
|
|
270
|
+
|
|
271
|
+
const content = readFileSync(agentsPath, "utf8");
|
|
272
|
+
const oldBegin = "<!-- deuk-agent-rule:begin -->";
|
|
273
|
+
const oldEnd = "<!-- deuk-agent-rule:end -->";
|
|
274
|
+
|
|
275
|
+
if (!content.includes(oldBegin)) return;
|
|
276
|
+
|
|
277
|
+
const beginIdx = content.indexOf(oldBegin);
|
|
278
|
+
const endIdx = content.lastIndexOf(oldEnd);
|
|
279
|
+
if (endIdx <= beginIdx) return;
|
|
280
|
+
|
|
281
|
+
const managedContent = content.slice(beginIdx + oldBegin.length, endIdx).trim();
|
|
282
|
+
const userContent = content.slice(0, beginIdx).trim();
|
|
283
|
+
const afterContent = content.slice(endIdx + oldEnd.length).trim();
|
|
284
|
+
|
|
285
|
+
let newContent = "";
|
|
286
|
+
if (userContent) newContent += userContent + "\n\n";
|
|
287
|
+
if (afterContent) newContent += afterContent + "\n\n";
|
|
288
|
+
newContent += "---\n\n";
|
|
289
|
+
newContent += "## DeukAgentFlow\n\n";
|
|
290
|
+
newContent += "> Managed by DeukAgentFlow. Remove this section if not installed.\n\n";
|
|
291
|
+
newContent += managedContent + "\n";
|
|
292
|
+
|
|
293
|
+
if (!dryRun) {
|
|
294
|
+
copyFileSync(agentsPath, agentsPath + ".pre-v2.bak");
|
|
295
|
+
writeFileSync(agentsPath, newContent, "utf8");
|
|
296
|
+
console.log(cliText("init.migrate.convertedMarkers", { locale }));
|
|
297
|
+
console.log(cliText("init.migrate.backupSaved", { locale, vars: { path: "AGENTS.md.pre-v2.bak" } }));
|
|
298
|
+
} else {
|
|
299
|
+
console.log(cliText("init.migrate.dryRunConvertedMarkers", { locale }));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const INIT_SURFACE_IGNORE_DIRS = new Set([
|
|
304
|
+
".git",
|
|
305
|
+
".hg",
|
|
306
|
+
".svn",
|
|
307
|
+
DEUK_ROOT_DIR,
|
|
308
|
+
...LEGACY_DEUK.dirs,
|
|
309
|
+
"node_modules",
|
|
310
|
+
"dist",
|
|
311
|
+
"build",
|
|
312
|
+
".next",
|
|
313
|
+
".nuxt",
|
|
314
|
+
"coverage"
|
|
315
|
+
]);
|
|
316
|
+
|
|
317
|
+
function walkInitTextSurfaces(dir, callback) {
|
|
318
|
+
if (!existsSync(dir)) return;
|
|
319
|
+
for (const entry of sortedDirEntries(dir, { withFileTypes: true })) {
|
|
320
|
+
const absPath = makePath(dir, entry.name);
|
|
321
|
+
if (entry.isDirectory()) {
|
|
322
|
+
if (INIT_SURFACE_IGNORE_DIRS.has(entry.name)) continue;
|
|
323
|
+
walkInitTextSurfaces(absPath, callback);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (!entry.isFile()) continue;
|
|
327
|
+
if (entry.name === "AGENTS.md" || entry.name === "PROJECT_RULE.md") callback(absPath);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function removeLegacyHtmlManagedBlock(content) {
|
|
332
|
+
const oldBegin = "<!-- deuk-agent-rule:begin -->";
|
|
333
|
+
const oldEnd = "<!-- deuk-agent-rule:end -->";
|
|
334
|
+
const src = String(content || "");
|
|
335
|
+
const beginIdx = src.indexOf(oldBegin);
|
|
336
|
+
const endIdx = src.lastIndexOf(oldEnd);
|
|
337
|
+
if (beginIdx === -1 || endIdx <= beginIdx) return null;
|
|
338
|
+
return [
|
|
339
|
+
src.slice(0, beginIdx).trim(),
|
|
340
|
+
src.slice(endIdx + oldEnd.length).trim()
|
|
341
|
+
].filter(Boolean).join("\n\n");
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export function cleanupGeneratedAgentPointerSurface(absPath, cwd, dryRun) {
|
|
345
|
+
const locale = resolveInitLocale();
|
|
346
|
+
if (!existsSync(absPath)) return false;
|
|
347
|
+
if (basename(absPath) !== "AGENTS.md") return false;
|
|
348
|
+
|
|
349
|
+
const before = readFileSync(absPath, "utf8");
|
|
350
|
+
if (!/DeukAgentRules|DeukAgentFlow|deuk-agent-rule/.test(before)) return false;
|
|
351
|
+
|
|
352
|
+
const unmanagedContent = removeLegacyHtmlManagedBlock(before);
|
|
353
|
+
if (unmanagedContent !== null) {
|
|
354
|
+
const next = unmanagedContent.trim();
|
|
355
|
+
if (!dryRun) {
|
|
356
|
+
if (next) writeFileSync(absPath, `${next}\n`, "utf8");
|
|
357
|
+
else unlinkSync(absPath);
|
|
358
|
+
}
|
|
359
|
+
const action = dryRun
|
|
360
|
+
? `would ${cliText(next ? "init.cleanup.strippedBlock" : "init.cleanup.removedPointer", { locale })}`
|
|
361
|
+
: cliText(next ? "init.cleanup.strippedBlock" : "init.cleanup.removedPointer", { locale });
|
|
362
|
+
console.log(cliText("init.cleanup.message", { locale, vars: { action, path: toRepoRelativePath(cwd, absPath) } }));
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const { pointer, projectDoc } = splitProjectDoc(before);
|
|
367
|
+
if (!isGeneratedDeukPointer(pointer)) return false;
|
|
368
|
+
|
|
369
|
+
const next = projectDoc.trim();
|
|
370
|
+
if (!dryRun) {
|
|
371
|
+
if (next) writeFileSync(absPath, `${next}\n`, "utf8");
|
|
372
|
+
else unlinkSync(absPath);
|
|
373
|
+
}
|
|
374
|
+
const action = dryRun
|
|
375
|
+
? `would ${cliText(next ? "init.cleanup.strippedPointer" : "init.cleanup.removedPointer", { locale })}`
|
|
376
|
+
: cliText(next ? "init.cleanup.strippedPointer" : "init.cleanup.removedPointer", { locale });
|
|
377
|
+
console.log(cliText("init.cleanup.message", { locale, vars: { action, path: toRepoRelativePath(cwd, absPath) } }));
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function cleanupNestedGeneratedAgentPointers(cwd, dryRun) {
|
|
382
|
+
const locale = resolveInitLocale();
|
|
383
|
+
if (!existsSync(makePath(cwd, ".git"))) return 0;
|
|
384
|
+
|
|
385
|
+
let count = 0;
|
|
386
|
+
walkInitTextSurfaces(cwd, (absPath) => {
|
|
387
|
+
if (cleanupGeneratedAgentPointerSurface(absPath, cwd, dryRun)) count += 1;
|
|
388
|
+
});
|
|
389
|
+
if (count > 0) {
|
|
390
|
+
console.log(cliText("init.cleanup.summary", { locale, vars: { action: dryRun ? "would remove/strip" : "removed/stripped", count, workspace: basename(cwd) } }));
|
|
391
|
+
}
|
|
392
|
+
return count;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export function removeRuntimeTemplateCopies(cwd, dryRun) {
|
|
396
|
+
removeLegacyPaths(cwd, dryRun, CANONICAL_INIT_CLEANUP_PATHS.runtimeTemplateCopies, "runtime template copy");
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
export function removeLocalSkillCopies(cwd, dryRun) {
|
|
400
|
+
removeLegacyPaths(cwd, dryRun, CANONICAL_INIT_CLEANUP_PATHS.localSkillCopies, "local skill copy");
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Surgically remove the deuk-managed block from a shared-name surface (CLAUDE.md,
|
|
404
|
+
// AGENTS.md, copilot-instructions.md, …), preserving any user content. Deletes the
|
|
405
|
+
// file only when nothing of the user's remains. Never blunt-deletes a file that may
|
|
406
|
+
// hold the user's own Claude/agent settings.
|
|
407
|
+
function stripDeukManagedSurface(absPath, cwd, dryRun) {
|
|
408
|
+
if (!existsSync(absPath)) return;
|
|
409
|
+
const before = readFileSync(absPath, "utf8");
|
|
410
|
+
if (!/deuk-agent-managed|DeukAgentFlow|deuk-agent-rule/.test(before)) return; // not ours — leave untouched
|
|
411
|
+
|
|
412
|
+
let rest = null;
|
|
413
|
+
const block = splitManagedBlock(before); // current HTML-marked form
|
|
414
|
+
if (block) {
|
|
415
|
+
rest = [block.before, block.after].map(s => String(s || "").trim()).filter(Boolean).join("\n\n");
|
|
416
|
+
} else {
|
|
417
|
+
const legacy = removeLegacyHtmlManagedBlock(before); // older deuk-agent-rule markers
|
|
418
|
+
if (legacy !== null) {
|
|
419
|
+
rest = legacy.trim();
|
|
420
|
+
} else {
|
|
421
|
+
const { pointer, projectDoc } = splitProjectDoc(before); // bare "## DeukAgentFlow" section form
|
|
422
|
+
if (!isGeneratedDeukPointer(pointer)) return; // can't isolate deuk content safely — leave
|
|
423
|
+
rest = String(projectDoc || "").trim();
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (dryRun) {
|
|
428
|
+
console.log(`[CLEANUP] would ${rest ? "strip deuk block from" : "remove deuk surface"}: ${toRepoRelativePath(cwd, absPath)}`);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
if (rest) {
|
|
432
|
+
writeFileSync(absPath, `${rest}\n`, "utf8");
|
|
433
|
+
console.log(`[CLEANUP] stripped deuk block, kept user content: ${toRepoRelativePath(cwd, absPath)}`);
|
|
434
|
+
} else {
|
|
435
|
+
rmSync(absPath, { force: true });
|
|
436
|
+
console.log(`[CLEANUP] removed deuk-managed surface: ${toRepoRelativePath(cwd, absPath)}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export function removeLocalAgentSurfaces(cwd, dryRun) {
|
|
441
|
+
removeLegacyPaths(cwd, dryRun, CANONICAL_INIT_CLEANUP_PATHS.localAgentSurfaces, "local agent surface");
|
|
442
|
+
for (const rel of CANONICAL_INIT_CLEANUP_PATHS.sharedAgentSurfaces) {
|
|
443
|
+
stripDeukManagedSurface(makePath(cwd, rel), cwd, dryRun);
|
|
444
|
+
}
|
|
445
|
+
for (const relDir of [".cursor", ".claude", ".codex", ".windsurf", ".aiassistant"]) {
|
|
446
|
+
removeEmptyDirsBottomUp(makePath(cwd, relDir), cwd, dryRun);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const AUTO_MIGRATION_SKIP_DIRS = new Set([
|
|
451
|
+
".git",
|
|
452
|
+
".hg",
|
|
453
|
+
".svn",
|
|
454
|
+
DEUK_ROOT_DIR,
|
|
455
|
+
...LEGACY_DEUK.dirs,
|
|
456
|
+
"node_modules",
|
|
457
|
+
"dist",
|
|
458
|
+
"build",
|
|
459
|
+
"coverage",
|
|
460
|
+
".next",
|
|
461
|
+
".turbo",
|
|
462
|
+
".venv",
|
|
463
|
+
"__pycache__"
|
|
464
|
+
]);
|
|
465
|
+
|
|
466
|
+
export function removeFragmentedLocalAgentSurfaces(cwd, dryRun) {
|
|
467
|
+
let removed = removeLegacyPaths(cwd, dryRun, CANONICAL_INIT_CLEANUP_PATHS.localAgentSurfaces, "local agent surface");
|
|
468
|
+
const stack = [cwd];
|
|
469
|
+
const visited = new Set();
|
|
470
|
+
|
|
471
|
+
while (stack.length > 0) {
|
|
472
|
+
const current = stack.pop();
|
|
473
|
+
if (!current || visited.has(current) || !existsSync(current)) continue;
|
|
474
|
+
visited.add(current);
|
|
475
|
+
|
|
476
|
+
let entries = [];
|
|
477
|
+
try {
|
|
478
|
+
entries = sortedDirEntries(current, { withFileTypes: true });
|
|
479
|
+
} catch {
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
for (const relPath of CANONICAL_INIT_CLEANUP_PATHS.localAgentSurfaces) {
|
|
484
|
+
const target = makePath(current, relPath);
|
|
485
|
+
if (!existsSync(target)) continue;
|
|
486
|
+
if (!dryRun) rmSync(target, { recursive: true, force: true });
|
|
487
|
+
console.log(`[CLEANUP] removed fragmented local agent surface: ${toRepoRelativePath(cwd, target)}`);
|
|
488
|
+
removed += 1;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
for (const entry of entries) {
|
|
492
|
+
if (!entry.isDirectory()) continue;
|
|
493
|
+
if (AUTO_MIGRATION_SKIP_DIRS.has(entry.name)) continue;
|
|
494
|
+
stack.push(makePath(current, entry.name));
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
for (const relDir of [".cursor", ".claude", ".codex", ".windsurf", ".aiassistant"]) {
|
|
498
|
+
removeEmptyDirsBottomUp(makePath(current, relDir), cwd, dryRun);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return removed;
|
|
503
|
+
}
|
|
504
|
+
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { dirname, basename } from "path";
|
|
2
|
+
import { existsSync, writeFileSync, mkdirSync } from "fs";
|
|
3
|
+
import { createHash } from "crypto";
|
|
4
|
+
import { DEUK_ROOT_DIR, CliOpts, buildWorkspaceAliases, hasWorkspaceMarker, makePath, normalizeRegistryPath, resolveUserHome, writeFileLF } from "./cli-utils.js";
|
|
5
|
+
|
|
6
|
+
// ─── 내부 헬퍼: 워크스페이스 레지스트리 ─────────────────────────────────
|
|
7
|
+
function hashPath(value) {
|
|
8
|
+
return createHash("sha256").update(normalizeRegistryPath(String(value || "")), "utf8").digest("hex");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function workspaceRegistryRoot(homeDir = resolveUserHome()) {
|
|
12
|
+
return makePath(homeDir, ".agent-flow", "workspace");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function safeRegistryFileName(name) {
|
|
16
|
+
const cleaned = String(name || "workspace")
|
|
17
|
+
.replace(/[^A-Za-z0-9._-]+/g, "-")
|
|
18
|
+
.replace(/^-+|-+$/g, "");
|
|
19
|
+
return `${cleaned || "workspace"}.json`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ─── 공개 API ─────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
export function updateSiblingWorkspaceRegistry(cwd, opts: CliOpts = {}) {
|
|
25
|
+
const workspacePath = normalizeRegistryPath(cwd || "");
|
|
26
|
+
if (!hasWorkspaceMarker(workspacePath)) return false;
|
|
27
|
+
|
|
28
|
+
const homeDir = resolveUserHome(opts);
|
|
29
|
+
// #623: never register the user home or anything under ~/.deuk-agent as a
|
|
30
|
+
// workspace — that store holds relocated tickets, not a sibling workspace, and
|
|
31
|
+
// registering it makes ticket ids look ambiguous across "workspaces".
|
|
32
|
+
const homeNorm = normalizeRegistryPath(homeDir);
|
|
33
|
+
const storeNorm = normalizeRegistryPath(makePath(homeDir, DEUK_ROOT_DIR));
|
|
34
|
+
if (workspacePath === homeNorm
|
|
35
|
+
|| workspacePath === storeNorm
|
|
36
|
+
|| workspacePath.startsWith(storeNorm + "/")) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
const dryRun = Boolean(opts.dryRun);
|
|
40
|
+
const parentRoot = dirname(workspacePath);
|
|
41
|
+
const workspaceName = basename(workspacePath);
|
|
42
|
+
const parentRootHash = hashPath(parentRoot);
|
|
43
|
+
const pathHash = hashPath(workspacePath);
|
|
44
|
+
const registryDir = makePath(workspaceRegistryRoot(homeDir), parentRootHash);
|
|
45
|
+
const registryPath = makePath(registryDir, safeRegistryFileName(workspaceName));
|
|
46
|
+
const payload = {
|
|
47
|
+
version: 1,
|
|
48
|
+
name: workspaceName,
|
|
49
|
+
path: workspacePath,
|
|
50
|
+
aliases: buildWorkspaceAliases(workspaceName, workspacePath),
|
|
51
|
+
parentRoot,
|
|
52
|
+
parentRootHash,
|
|
53
|
+
pathHash,
|
|
54
|
+
updatedAt: new Date().toISOString(),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (!dryRun) {
|
|
58
|
+
mkdirSync(registryDir, { recursive: true });
|
|
59
|
+
writeFileLF(registryPath, JSON.stringify(payload, null, 2) + "\n");
|
|
60
|
+
}
|
|
61
|
+
return { registryPath, payload };
|
|
62
|
+
}
|
|
63
|
+
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { CliOpts, resolveDocsLanguage } from "./cli-utils.js";
|
|
2
|
+
|
|
3
|
+
const CLI_LOCALES = {
|
|
4
|
+
en: {
|
|
5
|
+
"prompts.init.heading": "DeukAgentFlow init — let's configure your workspace.",
|
|
6
|
+
"prompts.init.workspaceKind": "What kind of workspace is this?",
|
|
7
|
+
"prompts.init.noManagedSurface": "No managed surface file found — will create with markers.",
|
|
8
|
+
"prompts.init.missingMarkers": "Managed surface file exists without managed markers — will append a managed block.",
|
|
9
|
+
"prompts.init.summary.workspaceKind": "Workspace Kind: {{value}}",
|
|
10
|
+
"prompts.init.summary.technicalSurface": "Technical Surface: {{value}}",
|
|
11
|
+
"prompts.init.summary.aiClients": "AI Clients: {{value}}",
|
|
12
|
+
"prompts.init.summary.allSupportedClients": "all supported clients",
|
|
13
|
+
"prompts.init.summary.docsLanguage": "Docs Language: {{value}}",
|
|
14
|
+
"prompts.init.summary.workflowMode": "Workflow Mode: {{value}}",
|
|
15
|
+
"prompts.init.summary.shareTickets": "Share Tickets: {{value}}",
|
|
16
|
+
"prompts.init.summary.shareYes": "Yes (Shared)",
|
|
17
|
+
"prompts.init.summary.shareNo": "No (Private)",
|
|
18
|
+
"prompts.init.summary.contextMcp": "Deuk AgentContext MCP: hidden during init",
|
|
19
|
+
"prompts.init.summary.externalSync": "External Sync: {{value}}",
|
|
20
|
+
"prompts.init.summary.enabled": "Enabled",
|
|
21
|
+
"prompts.init.summary.disabled": "Disabled",
|
|
22
|
+
"prompts.init.summary.syncUrl": "Sync URL: {{value}}",
|
|
23
|
+
"prompts.init.summary.surfaceMode": "Managed Surface: {{value}}",
|
|
24
|
+
"ticket.validation.selfServeRecipe": "Use the self-serve recipe from the printed rules surface: do not ask the user, call help, or search for templates.",
|
|
25
|
+
"ticket.phase.fillSlots": "First fill these slots in the ticket file:",
|
|
26
|
+
"init.migrate.convertedMarkers": "[MIGRATE] Converted legacy HTML markers to heading-based format in managed surface file",
|
|
27
|
+
"init.migrate.backupSaved": "[MIGRATE] Backup saved as {{path}}",
|
|
28
|
+
"init.migrate.dryRunConvertedMarkers": "[DRY-RUN] Would convert legacy HTML markers to heading-based format in managed surface file",
|
|
29
|
+
"init.migrate.replaceLegacyHtmlBlock": "[MIGRATE] {{action}} legacy HTML managed block: {{path}}",
|
|
30
|
+
"init.migrate.replaceLegacyPointer": "[MIGRATE] {{action}} legacy managed pointer: {{path}}",
|
|
31
|
+
"init.cleanup.strippedBlock": "stripped generated managed block",
|
|
32
|
+
"init.cleanup.removedPointer": "removed generated managed pointer",
|
|
33
|
+
"init.cleanup.strippedPointer": "stripped generated managed pointer",
|
|
34
|
+
"init.cleanup.message": "[CLEANUP] {{action}}: {{path}}",
|
|
35
|
+
"init.cleanup.summary": "[SUMMARY] {{action}} {{count}} nested generated managed pointer surface(s) under {{workspace}}.",
|
|
36
|
+
"lint.rules.missingCore": "core rules file not found",
|
|
37
|
+
"lint.rules.printsCurrentBody": "Core rules must state that deuk-agent-flow rules prints the current core rules body."
|
|
38
|
+
},
|
|
39
|
+
ko: {
|
|
40
|
+
"prompts.init.heading": "DeukAgentFlow init — 워크스페이스를 설정합니다.",
|
|
41
|
+
"prompts.init.workspaceKind": "이 워크스페이스 유형은 무엇인가요?",
|
|
42
|
+
"prompts.init.noManagedSurface": "관리 표면 파일이 없어 마커와 함께 생성합니다.",
|
|
43
|
+
"prompts.init.missingMarkers": "관리 표면 파일에 관리 마커가 없어 관리 블록을 추가합니다.",
|
|
44
|
+
"prompts.init.summary.workspaceKind": "워크스페이스 유형: {{value}}",
|
|
45
|
+
"prompts.init.summary.technicalSurface": "기술 표면: {{value}}",
|
|
46
|
+
"prompts.init.summary.aiClients": "AI 클라이언트: {{value}}",
|
|
47
|
+
"prompts.init.summary.allSupportedClients": "지원되는 모든 클라이언트",
|
|
48
|
+
"prompts.init.summary.docsLanguage": "문서 언어: {{value}}",
|
|
49
|
+
"prompts.init.summary.workflowMode": "워크플로우 모드: {{value}}",
|
|
50
|
+
"prompts.init.summary.shareTickets": "티켓 공유: {{value}}",
|
|
51
|
+
"prompts.init.summary.shareYes": "예 (공유)",
|
|
52
|
+
"prompts.init.summary.shareNo": "아니오 (개인)",
|
|
53
|
+
"prompts.init.summary.contextMcp": "Deuk AgentContext MCP: init 중 숨김",
|
|
54
|
+
"prompts.init.summary.externalSync": "외부 동기화: {{value}}",
|
|
55
|
+
"prompts.init.summary.enabled": "활성화",
|
|
56
|
+
"prompts.init.summary.disabled": "비활성화",
|
|
57
|
+
"prompts.init.summary.syncUrl": "동기화 URL: {{value}}",
|
|
58
|
+
"prompts.init.summary.surfaceMode": "관리 표면: {{value}}",
|
|
59
|
+
"ticket.validation.selfServeRecipe": "출력된 rules surface의 self-serve 레시피를 사용하세요. 사용자에게 다시 묻거나 help를 호출하거나 템플릿을 검색하지 마세요.",
|
|
60
|
+
"ticket.phase.fillSlots": "먼저 티켓 파일에 다음 슬롯을 채우세요:",
|
|
61
|
+
"init.migrate.convertedMarkers": "[MIGRATE] legacy HTML 마커를 heading 기반 관리 표면 형식으로 변환했습니다",
|
|
62
|
+
"init.migrate.backupSaved": "[MIGRATE] 백업 저장: {{path}}",
|
|
63
|
+
"init.migrate.dryRunConvertedMarkers": "[DRY-RUN] legacy HTML 마커를 heading 기반 관리 표면 형식으로 변환할 예정입니다",
|
|
64
|
+
"init.migrate.replaceLegacyHtmlBlock": "[MIGRATE] legacy HTML 관리 블록 {{action}}: {{path}}",
|
|
65
|
+
"init.migrate.replaceLegacyPointer": "[MIGRATE] legacy 관리 포인터 {{action}}: {{path}}",
|
|
66
|
+
"init.cleanup.strippedBlock": "생성된 관리 블록 제거",
|
|
67
|
+
"init.cleanup.removedPointer": "생성된 관리 포인터 삭제",
|
|
68
|
+
"init.cleanup.strippedPointer": "생성된 관리 포인터 제거",
|
|
69
|
+
"init.cleanup.message": "[CLEANUP] {{action}}: {{path}}",
|
|
70
|
+
"init.cleanup.summary": "[SUMMARY] {{workspace}} 아래 중첩 생성 관리 포인터 표면 {{count}}개를 {{action}}.",
|
|
71
|
+
"lint.rules.missingCore": "core rules 파일을 찾을 수 없습니다",
|
|
72
|
+
"lint.rules.printsCurrentBody": "Core rules는 deuk-agent-flow rules가 현재 core rules 본문을 출력한다고 명시해야 합니다."
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export function resolveCliLocale(value = "auto", env = process.env) {
|
|
77
|
+
return resolveDocsLanguage(value, env);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function cliText(key, { locale = "auto", env = process.env, vars = {} } = {}) {
|
|
81
|
+
const resolvedLocale = resolveCliLocale(locale, env);
|
|
82
|
+
const dictionary = CLI_LOCALES[resolvedLocale] || CLI_LOCALES.en;
|
|
83
|
+
const template = dictionary[key] || CLI_LOCALES.en[key] || key;
|
|
84
|
+
return String(template).replace(/\{\{(\w+)\}\}/g, (_match, name) => String(vars[name] ?? ""));
|
|
85
|
+
}
|
|
@@ -297,7 +297,9 @@ export function getActiveSkillsSurface(cwd = process.cwd(), platform = null, are
|
|
|
297
297
|
const contents = ids.map(id => {
|
|
298
298
|
try {
|
|
299
299
|
const raw = readFileSync(sourceSkillPath(cwd, id), "utf8").trimEnd();
|
|
300
|
-
|
|
300
|
+
// 역할 레이어(category: role)는 행동 계약이라 잘리면 의미가 없다 — truncate 면제.
|
|
301
|
+
const isRole = readSkillMeta(cwd, id).category === "role";
|
|
302
|
+
const truncated = (!isRole && raw.length > SKILL_BODY_LIMIT)
|
|
301
303
|
? raw.slice(0, SKILL_BODY_LIMIT) + "\n… (truncated)"
|
|
302
304
|
: raw;
|
|
303
305
|
return `### Skill: ${id}\n\n${truncated}`;
|