claude-nexus 0.2.0
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/.claude-plugin/marketplace.json +23 -0
- package/.claude-plugin/plugin.json +17 -0
- package/.mcp.json +8 -0
- package/LICENSE +21 -0
- package/README.md +106 -0
- package/agents/analyst.md +43 -0
- package/agents/architect.md +43 -0
- package/agents/builder.md +36 -0
- package/agents/debugger.md +38 -0
- package/agents/finder.md +35 -0
- package/agents/guard.md +42 -0
- package/agents/reviewer.md +42 -0
- package/agents/strategist.md +37 -0
- package/agents/tester.md +43 -0
- package/agents/writer.md +42 -0
- package/bridge/mcp-server.cjs +22147 -0
- package/bridge/mcp-server.cjs.map +7 -0
- package/hooks/hooks.json +89 -0
- package/package.json +45 -0
- package/scripts/gate.cjs +294 -0
- package/scripts/gate.cjs.map +7 -0
- package/scripts/pulse.cjs +295 -0
- package/scripts/pulse.cjs.map +7 -0
- package/scripts/statusline.cjs +329 -0
- package/scripts/statusline.cjs.map +7 -0
- package/scripts/tracker.cjs +325 -0
- package/scripts/tracker.cjs.map +7 -0
- package/skills/consult/SKILL.md +165 -0
- package/skills/init/SKILL.md +196 -0
- package/skills/plan/SKILL.md +176 -0
- package/skills/setup/SKILL.md +275 -0
- package/skills/sync/SKILL.md +118 -0
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Nexus orchestration hooks",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"Stop": [
|
|
5
|
+
{
|
|
6
|
+
"matcher": "*",
|
|
7
|
+
"hooks": [
|
|
8
|
+
{
|
|
9
|
+
"type": "command",
|
|
10
|
+
"command": "node \"$CLAUDE_PLUGIN_ROOT\"/scripts/gate.cjs",
|
|
11
|
+
"timeout": 5
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"UserPromptSubmit": [
|
|
17
|
+
{
|
|
18
|
+
"matcher": "*",
|
|
19
|
+
"hooks": [
|
|
20
|
+
{
|
|
21
|
+
"type": "command",
|
|
22
|
+
"command": "node \"$CLAUDE_PLUGIN_ROOT\"/scripts/gate.cjs",
|
|
23
|
+
"timeout": 5
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"PreToolUse": [
|
|
29
|
+
{
|
|
30
|
+
"matcher": "*",
|
|
31
|
+
"hooks": [
|
|
32
|
+
{
|
|
33
|
+
"type": "command",
|
|
34
|
+
"command": "node \"$CLAUDE_PLUGIN_ROOT\"/scripts/pulse.cjs",
|
|
35
|
+
"timeout": 3
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"SubagentStart": [
|
|
41
|
+
{
|
|
42
|
+
"matcher": "*",
|
|
43
|
+
"hooks": [
|
|
44
|
+
{
|
|
45
|
+
"type": "command",
|
|
46
|
+
"command": "node \"$CLAUDE_PLUGIN_ROOT\"/scripts/tracker.cjs",
|
|
47
|
+
"timeout": 3
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"SubagentStop": [
|
|
53
|
+
{
|
|
54
|
+
"matcher": "*",
|
|
55
|
+
"hooks": [
|
|
56
|
+
{
|
|
57
|
+
"type": "command",
|
|
58
|
+
"command": "node \"$CLAUDE_PLUGIN_ROOT\"/scripts/tracker.cjs",
|
|
59
|
+
"timeout": 3
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
"SessionStart": [
|
|
65
|
+
{
|
|
66
|
+
"matcher": "*",
|
|
67
|
+
"hooks": [
|
|
68
|
+
{
|
|
69
|
+
"type": "command",
|
|
70
|
+
"command": "node \"$CLAUDE_PLUGIN_ROOT\"/scripts/tracker.cjs",
|
|
71
|
+
"timeout": 5
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
"SessionEnd": [
|
|
77
|
+
{
|
|
78
|
+
"matcher": "*",
|
|
79
|
+
"hooks": [
|
|
80
|
+
{
|
|
81
|
+
"type": "command",
|
|
82
|
+
"command": "node \"$CLAUDE_PLUGIN_ROOT\"/scripts/tracker.cjs",
|
|
83
|
+
"timeout": 5
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-nexus",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Agent orchestration plugin for Claude Code — optimized context injection per role",
|
|
6
|
+
"author": "kih",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/moreih29/claude-nexus.git"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/moreih29/claude-nexus#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/moreih29/claude-nexus/issues"
|
|
15
|
+
},
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=20"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "node esbuild.config.mjs",
|
|
21
|
+
"build:types": "tsc --noEmit",
|
|
22
|
+
"check": "tsc --noEmit && npm run build",
|
|
23
|
+
"clean": "rm -rf dist bridge/mcp-server.cjs scripts/*.cjs",
|
|
24
|
+
"dev": "node esbuild.config.mjs && node dev-sync.mjs"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"agents",
|
|
28
|
+
"skills",
|
|
29
|
+
"hooks",
|
|
30
|
+
"scripts",
|
|
31
|
+
"bridge",
|
|
32
|
+
".claude-plugin",
|
|
33
|
+
".mcp.json"
|
|
34
|
+
],
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^25.5.0",
|
|
37
|
+
"esbuild": "^0.25.0",
|
|
38
|
+
"typescript": "^5.8.0"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@ast-grep/napi": "^0.42.0",
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
43
|
+
"zod": "^3.24.0"
|
|
44
|
+
}
|
|
45
|
+
}
|
package/scripts/gate.cjs
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// src/shared/hook-io.ts
|
|
4
|
+
function readStdin() {
|
|
5
|
+
return new Promise((resolve2) => {
|
|
6
|
+
let data = "";
|
|
7
|
+
process.stdin.on("data", (chunk) => data += chunk);
|
|
8
|
+
process.stdin.on("end", () => resolve2(data));
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function respond(obj) {
|
|
12
|
+
process.stdout.write(JSON.stringify(obj));
|
|
13
|
+
}
|
|
14
|
+
function pass() {
|
|
15
|
+
respond({ continue: true });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/hooks/gate.ts
|
|
19
|
+
var import_fs3 = require("fs");
|
|
20
|
+
|
|
21
|
+
// src/shared/paths.ts
|
|
22
|
+
var import_path = require("path");
|
|
23
|
+
var import_fs = require("fs");
|
|
24
|
+
function findProjectRoot() {
|
|
25
|
+
let dir = process.cwd();
|
|
26
|
+
while (dir !== "/") {
|
|
27
|
+
if ((0, import_fs.existsSync)((0, import_path.join)(dir, ".git"))) return dir;
|
|
28
|
+
dir = (0, import_path.resolve)(dir, "..");
|
|
29
|
+
}
|
|
30
|
+
return process.cwd();
|
|
31
|
+
}
|
|
32
|
+
var PROJECT_ROOT = findProjectRoot();
|
|
33
|
+
var RUNTIME_ROOT = (0, import_path.join)(PROJECT_ROOT, ".nexus");
|
|
34
|
+
var KNOWLEDGE_ROOT = (0, import_path.join)(PROJECT_ROOT, ".claude", "nexus");
|
|
35
|
+
function sessionDir(sessionId) {
|
|
36
|
+
return (0, import_path.join)(RUNTIME_ROOT, "state", "sessions", sessionId);
|
|
37
|
+
}
|
|
38
|
+
function ensureDir(dir) {
|
|
39
|
+
if (!(0, import_fs.existsSync)(dir)) {
|
|
40
|
+
(0, import_fs.mkdirSync)(dir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function updateWorkflowPhase(sid, phase) {
|
|
44
|
+
const workflowPath = (0, import_path.join)(sessionDir(sid), "workflow.json");
|
|
45
|
+
if (!(0, import_fs.existsSync)(workflowPath)) return;
|
|
46
|
+
try {
|
|
47
|
+
const state = JSON.parse((0, import_fs.readFileSync)(workflowPath, "utf-8"));
|
|
48
|
+
if ((state.mode === "consult" || state.mode === "plan") && state.phase !== phase) {
|
|
49
|
+
state.phase = phase;
|
|
50
|
+
(0, import_fs.writeFileSync)(workflowPath, JSON.stringify(state, null, 2));
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/shared/session.ts
|
|
57
|
+
var import_crypto = require("crypto");
|
|
58
|
+
var import_fs2 = require("fs");
|
|
59
|
+
var import_path2 = require("path");
|
|
60
|
+
var SESSION_FILE = (0, import_path2.join)(RUNTIME_ROOT, "state", "current-session.json");
|
|
61
|
+
function getSessionId() {
|
|
62
|
+
if ((0, import_fs2.existsSync)(SESSION_FILE)) {
|
|
63
|
+
try {
|
|
64
|
+
const data = JSON.parse((0, import_fs2.readFileSync)(SESSION_FILE, "utf-8"));
|
|
65
|
+
if (data.sessionId && typeof data.sessionId === "string") {
|
|
66
|
+
return data.sessionId;
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return createSession();
|
|
72
|
+
}
|
|
73
|
+
function createSession() {
|
|
74
|
+
const sessionId = (0, import_crypto.randomUUID)().slice(0, 8);
|
|
75
|
+
ensureDir((0, import_path2.join)(RUNTIME_ROOT, "state"));
|
|
76
|
+
(0, import_fs2.writeFileSync)(SESSION_FILE, JSON.stringify({ sessionId, createdAt: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
77
|
+
return sessionId;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/hooks/gate.ts
|
|
81
|
+
var import_path3 = require("path");
|
|
82
|
+
function activateMode(mode, sid, extra) {
|
|
83
|
+
const dir = sessionDir(sid);
|
|
84
|
+
ensureDir(dir);
|
|
85
|
+
const state = {
|
|
86
|
+
mode,
|
|
87
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
88
|
+
...extra
|
|
89
|
+
};
|
|
90
|
+
(0, import_fs3.writeFileSync)((0, import_path3.join)(dir, "workflow.json"), JSON.stringify(state, null, 2));
|
|
91
|
+
}
|
|
92
|
+
function handleStop() {
|
|
93
|
+
const sid = getSessionId();
|
|
94
|
+
const sessDir = sessionDir(sid);
|
|
95
|
+
const workflowPath = (0, import_path3.join)(sessDir, "workflow.json");
|
|
96
|
+
if ((0, import_fs3.existsSync)(workflowPath)) {
|
|
97
|
+
try {
|
|
98
|
+
const state = JSON.parse((0, import_fs3.readFileSync)(workflowPath, "utf-8"));
|
|
99
|
+
if ((state.mode === "consult" || state.mode === "plan") && state.phase) {
|
|
100
|
+
respond({
|
|
101
|
+
decision: "block",
|
|
102
|
+
reason: `[${state.mode.toUpperCase()}: ${state.phase}] Workflow is active. Complete the current phase or clear with nx_state_clear({ key: "${state.mode}" }).`
|
|
103
|
+
});
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const agentsPath = (0, import_path3.join)(sessDir, "agents.json");
|
|
110
|
+
if ((0, import_fs3.existsSync)(agentsPath)) {
|
|
111
|
+
try {
|
|
112
|
+
const record = JSON.parse((0, import_fs3.readFileSync)(agentsPath, "utf-8"));
|
|
113
|
+
if (record.active && record.active.length > 0) {
|
|
114
|
+
respond({
|
|
115
|
+
decision: "block",
|
|
116
|
+
reason: `[AGENTS: ${record.active.join(", ")}] Agents are still working. Wait for completion.`
|
|
117
|
+
});
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
pass();
|
|
124
|
+
}
|
|
125
|
+
var EXPLICIT_TAGS = {
|
|
126
|
+
consult: { primitive: "consult", skill: "nexus:consult" },
|
|
127
|
+
init: { primitive: "init", skill: "nexus:init" },
|
|
128
|
+
plan: { primitive: "plan", skill: "nexus:plan" },
|
|
129
|
+
setup: { primitive: "setup", skill: "nexus:setup" }
|
|
130
|
+
};
|
|
131
|
+
var NATURAL_PATTERNS = [
|
|
132
|
+
{
|
|
133
|
+
patterns: [/\bconsult\b/i, /상담/, /어떻게\s*하면\s*좋을까/, /뭐가\s*좋을까/, /방법을?\s*찾아/],
|
|
134
|
+
match: { primitive: "consult", skill: "nexus:consult" }
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
patterns: [/계획\s*(세워|짜|수립)/, /\bplan\b/i, /구현\s*계획/, /설계해/, /어떻게\s*구현/, /plan\s*this/i],
|
|
138
|
+
match: { primitive: "plan", skill: "nexus:plan" }
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
patterns: [/\bsetup\b/i, /nexus\s*설정/, /nexus\s*세팅/, /setup\s*nexus/i],
|
|
142
|
+
match: { primitive: "setup", skill: "nexus:setup" }
|
|
143
|
+
}
|
|
144
|
+
];
|
|
145
|
+
var ERROR_CONTEXT = /에러|버그|오류|\bfix\b|\bbug\b|\berror\b|이슈|\bissue\b/i;
|
|
146
|
+
var PRIMITIVE_NAMES = /\b(plan|setup|init|consult)\b/i;
|
|
147
|
+
function isPrimitiveMention(prompt) {
|
|
148
|
+
if (PRIMITIVE_NAMES.test(prompt) && ERROR_CONTEXT.test(prompt)) return true;
|
|
149
|
+
if (PRIMITIVE_NAMES.test(prompt) && /뭐야|뭔가요|what\s+is|what\s+does|설명해|explain/i.test(prompt)) return true;
|
|
150
|
+
if (/[`"'](?:plan|setup|init|consult)[`"']/i.test(prompt)) return true;
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
function detectKeywords(prompt) {
|
|
154
|
+
const tagMatch = prompt.match(/\[(\w+)\]/);
|
|
155
|
+
if (tagMatch) {
|
|
156
|
+
const tag = tagMatch[1].toLowerCase();
|
|
157
|
+
if (tag in EXPLICIT_TAGS) return EXPLICIT_TAGS[tag];
|
|
158
|
+
}
|
|
159
|
+
for (const { patterns, match } of NATURAL_PATTERNS) {
|
|
160
|
+
if (patterns.some((p) => p.test(prompt))) {
|
|
161
|
+
if (isPrimitiveMention(prompt)) continue;
|
|
162
|
+
return match;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
function handleUserPromptSubmit(event) {
|
|
168
|
+
const prompt = event.prompt ?? event.user_prompt ?? "";
|
|
169
|
+
if (!prompt) {
|
|
170
|
+
pass();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const sid = getSessionId();
|
|
174
|
+
const workflowPath = (0, import_path3.join)(sessionDir(sid), "workflow.json");
|
|
175
|
+
if ((0, import_fs3.existsSync)(workflowPath)) {
|
|
176
|
+
try {
|
|
177
|
+
const state = JSON.parse((0, import_fs3.readFileSync)(workflowPath, "utf-8"));
|
|
178
|
+
if (state.phase === "waiting") {
|
|
179
|
+
updateWorkflowPhase(sid, "delegating");
|
|
180
|
+
}
|
|
181
|
+
} catch {
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const dTag = prompt.match(/\[d\]/i);
|
|
185
|
+
if (dTag) {
|
|
186
|
+
let branch = "unknown";
|
|
187
|
+
try {
|
|
188
|
+
branch = require("child_process").execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8" }).trim();
|
|
189
|
+
} catch {
|
|
190
|
+
}
|
|
191
|
+
const branchDir = branch.replace(/\//g, "--");
|
|
192
|
+
respond({
|
|
193
|
+
continue: true,
|
|
194
|
+
additionalContext: `[NEXUS] Decision tag detected. Record this decision in .claude/nexus/plans/${branchDir}/plan.md under the decisions section.`
|
|
195
|
+
});
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const match = detectKeywords(prompt);
|
|
199
|
+
if (match) {
|
|
200
|
+
if (match.primitive === "init") {
|
|
201
|
+
respond({
|
|
202
|
+
continue: true,
|
|
203
|
+
additionalContext: `[NEXUS] Init mode activated. Follow the init workflow:
|
|
204
|
+
1. SCAN: Read project structure (top-level dirs, config files), CLAUDE.md, README.md, docs/, .claude/, and other .md files. Use git log for recent activity.
|
|
205
|
+
2. TRIAGE: Classify each doc as Essential (\u2192 knowledge/), Useful (\u2192 knowledge/ condensed), Redundant (Nexus handles better), or Outdated (skip).
|
|
206
|
+
3. PROPOSE: Present triage results to user via AskUserQuestion. Ask about CLAUDE.md slimming strategy and which knowledge files to generate.
|
|
207
|
+
4. GENERATE: Create .claude/nexus/knowledge/ files (architecture.md, conventions.md, project-context.md). Backup original CLAUDE.md. Slim CLAUDE.md per user choice.
|
|
208
|
+
5. VERIFY: Confirm generated files are readable via nx_knowledge_read. Report summary.
|
|
209
|
+
IMPORTANT: Always backup before modifying. Never delete without user approval.`
|
|
210
|
+
});
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (match.primitive === "consult") {
|
|
214
|
+
const sid2 = getSessionId();
|
|
215
|
+
activateMode("consult", sid2, { phase: "exploring" });
|
|
216
|
+
respond({
|
|
217
|
+
continue: true,
|
|
218
|
+
additionalContext: `[NEXUS] Consult mode activated. Follow the consult workflow:
|
|
219
|
+
1. EXPLORE: Read code (nx_lsp_document_symbols, nx_ast_search for brownfield), knowledge (nx_knowledge_read), context (nx_context). Auto-detect brownfield vs greenfield.
|
|
220
|
+
2. ASSESS: Evaluate 4 dimensions \u2014 [Goal: ?] [Constraints: ?] [Criteria: ?] [Context: ?]. Mark each \u2705/\u26A0\uFE0F/\u274C. If \u22641 unclear \u2192 lightweight mode. If \u22652 unclear \u2192 deep mode.
|
|
221
|
+
3. CLARIFY (if unclear dimensions exist; 1-2 questions in lightweight, extended in deep): MUST use AskUserQuestion with concrete options \u2014 never ask as plain text. One question at a time targeting the weakest dimension.
|
|
222
|
+
4. DIVERGE: Generate 2-4 genuinely different approaches with pros/cons/effort.
|
|
223
|
+
5. PROPOSE: Present options via AskUserQuestion with preview for concrete comparisons.
|
|
224
|
+
6. CONVERGE: Elaborate chosen approach, follow-up if needed, produce concrete plan.
|
|
225
|
+
7. CRYSTALLIZE: Finalize plan. If unclear dimensions remain, disclose risks transparently \u2014 but never block the user.
|
|
226
|
+
8. EXECUTE BRIDGE: Offer 2-3 options via AskUserQuestion: Execute with delegation (Recommended) / Plan only / Skip.
|
|
227
|
+
Key: One question at a time. Specific choices, not vague "what do you think?". Respect user autonomy.
|
|
228
|
+
If a plan directory exists for the current branch, record decisions from user selections in the plan.md file.`
|
|
229
|
+
});
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (match.primitive === "plan") {
|
|
233
|
+
let currentBranch = "unknown";
|
|
234
|
+
try {
|
|
235
|
+
currentBranch = require("child_process").execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8" }).trim();
|
|
236
|
+
} catch {
|
|
237
|
+
}
|
|
238
|
+
const onMain = currentBranch === "main" || currentBranch === "master";
|
|
239
|
+
const sid2 = getSessionId();
|
|
240
|
+
if (!onMain) {
|
|
241
|
+
activateMode("plan", sid2, { phase: "analyzing" });
|
|
242
|
+
}
|
|
243
|
+
const branchInstruction = onMain ? `
|
|
244
|
+
IMPORTANT: You are on the ${currentBranch} branch. Before planning, create a feature branch:
|
|
245
|
+
1. Analyze the user's request to generate a descriptive branch name (e.g., feat/phase-auto-tracking, fix/statusline-bug).
|
|
246
|
+
2. Run: git checkout -b <branch-name>
|
|
247
|
+
3. Then proceed with the plan workflow.
|
|
248
|
+
Plans are stored under .claude/nexus/plans/{branch}/ \u2014 planning on main is not allowed.` : "";
|
|
249
|
+
respond({
|
|
250
|
+
continue: true,
|
|
251
|
+
additionalContext: `[NEXUS] Plan mode activated. Follow the plan workflow:${branchInstruction}
|
|
252
|
+
1. ANALYZE: Analyze the request. Determine scale \u2014 small (1-3 files), medium (module-level), large (architecture/security/migration). Auto-escalate to large if request mentions auth, migration, delete, or security.
|
|
253
|
+
2. DRAFT: Spawn Agent({ subagent_type: "nexus:strategist", prompt: "<full request context>" }) to create initial plan.
|
|
254
|
+
3. REVIEW (medium+): Spawn Agent({ subagent_type: "nexus:architect", prompt: "Review this plan: <strategist output>" }) for structural review.
|
|
255
|
+
4. CRITIQUE (large only): Spawn Agent({ subagent_type: "nexus:reviewer", prompt: "Critique this plan: <architect output>" }). If critical issues, loop back to DRAFT (max 3 iterations).
|
|
256
|
+
5. PERSIST: Save plan to .claude/nexus/plans/{branch}/plan.md. Generate .claude/nexus/plans/{branch}/tasks.json with task list including dependencies.
|
|
257
|
+
6. EXECUTE BRIDGE: Offer 2-3 options via AskUserQuestion: Execute with delegation (Recommended) / Plan only / Skip.
|
|
258
|
+
Key: This is the standalone Plan skill \u2014 not the plan stage within auto. Scale determines formality. Small tasks need only a checklist, not a full ADR.
|
|
259
|
+
`
|
|
260
|
+
});
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (match.primitive === "setup") {
|
|
264
|
+
respond({
|
|
265
|
+
continue: true,
|
|
266
|
+
additionalContext: `[NEXUS] Setup wizard activated. Guide the user through these steps IN ORDER using AskUserQuestion for each:
|
|
267
|
+
1. SCOPE: Ask configuration scope \u2014 User (all projects, ~/.claude/CLAUDE.md) or Project (this project only, CLAUDE.md). This determines write paths for all subsequent steps.
|
|
268
|
+
2. STATUSLINE: Ask preset choice (Full recommended / Standard / Minimal / Skip).
|
|
269
|
+
3. DELEGATION: Ask enforcement level (Warn recommended / Strict / Off / Skip).
|
|
270
|
+
4. CLAUDE.MD: Generate Nexus delegation section in CLAUDE.md using <!-- NEXUS:START --> / <!-- NEXUS:END --> markers. Content in English: delegation rules, agent routing table, 6-Section format guide, skill list. Preserve existing content outside markers.
|
|
271
|
+
5. OMC CHECK: Check if oh-my-claudecode (omc) plugin is active. If found, warn about conflicts and offer: Disable omc (recommended) / Keep both / Skip. If Disable chosen, set {"enabledPlugins":{"omc":false}} in .claude/settings.json.
|
|
272
|
+
6. INIT: Ask whether to run knowledge init (Yes recommended / Skip).
|
|
273
|
+
7. COMPLETE: Show summary of applied settings.
|
|
274
|
+
Key: Use AskUserQuestion for every step. Always offer Skip option.`
|
|
275
|
+
});
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
pass();
|
|
280
|
+
}
|
|
281
|
+
async function main() {
|
|
282
|
+
const input = await readStdin();
|
|
283
|
+
const event = JSON.parse(input);
|
|
284
|
+
const hasPrompt = "prompt" in event || "user_prompt" in event;
|
|
285
|
+
if (hasPrompt) {
|
|
286
|
+
handleUserPromptSubmit(event);
|
|
287
|
+
} else {
|
|
288
|
+
handleStop();
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
main().catch(() => {
|
|
292
|
+
respond({ continue: true });
|
|
293
|
+
});
|
|
294
|
+
//# sourceMappingURL=gate.cjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/shared/hook-io.ts", "../src/hooks/gate.ts", "../src/shared/paths.ts", "../src/shared/session.ts"],
|
|
4
|
+
"sourcesContent": ["/** \uD6C5 \uC2A4\uD06C\uB9BD\uD2B8 \uACF5\uD1B5 I/O: stdin JSON \uC77D\uAE30 + stdout JSON \uC751\uB2F5 */\n\nexport function readStdin(): Promise<string> {\n return new Promise((resolve) => {\n let data = '';\n process.stdin.on('data', (chunk: Buffer) => (data += chunk));\n process.stdin.on('end', () => resolve(data));\n });\n}\n\nexport function respond(obj: Record<string, unknown>): void {\n process.stdout.write(JSON.stringify(obj));\n}\n\nexport function pass(): void {\n respond({ continue: true });\n}\n", "// Gate \uD6C5: Stop (Workflow \uCC28\uB2E8) + UserPromptSubmit (\uD0A4\uC6CC\uB4DC \uAC10\uC9C0)\nimport { readStdin, respond, pass } from '../shared/hook-io.js';\nimport { existsSync, readFileSync, writeFileSync } from 'fs';\nimport { sessionDir, ensureDir, updateWorkflowPhase } from '../shared/paths.js';\nimport { getSessionId } from '../shared/session.js';\nimport { join } from 'path';\n\n// --- Workflow State ---\n\ninterface WorkflowState {\n mode: 'consult' | 'plan' | 'idle';\n phase?: string;\n startedAt: string;\n}\n\nfunction activateMode(mode: string, sid: string, extra?: Partial<WorkflowState>): void {\n const dir = sessionDir(sid);\n ensureDir(dir);\n const state: WorkflowState = {\n mode: mode as WorkflowState['mode'],\n startedAt: new Date().toISOString(),\n ...extra,\n };\n writeFileSync(join(dir, 'workflow.json'), JSON.stringify(state, null, 2));\n}\n\n// --- Stop \uC774\uBCA4\uD2B8 \uCC98\uB9AC ---\n\nfunction handleStop(): void {\n const sid = getSessionId();\n const sessDir = sessionDir(sid);\n\n // Check active workflow mode\n const workflowPath = join(sessDir, 'workflow.json');\n if (existsSync(workflowPath)) {\n try {\n const state = JSON.parse(readFileSync(workflowPath, 'utf-8'));\n if ((state.mode === 'consult' || state.mode === 'plan') && state.phase) {\n respond({\n decision: 'block',\n reason: `[${state.mode.toUpperCase()}: ${state.phase}] Workflow is active. Complete the current phase or clear with nx_state_clear({ key: \"${state.mode}\" }).`,\n });\n return;\n }\n } catch { /* skip */ }\n }\n\n // Check active agents\n const agentsPath = join(sessDir, 'agents.json');\n if (existsSync(agentsPath)) {\n try {\n const record = JSON.parse(readFileSync(agentsPath, 'utf-8'));\n if (record.active && record.active.length > 0) {\n respond({\n decision: 'block',\n reason: `[AGENTS: ${record.active.join(', ')}] Agents are still working. Wait for completion.`,\n });\n return;\n }\n } catch { /* skip */ }\n }\n\n pass();\n}\n\n// --- UserPromptSubmit \uC774\uBCA4\uD2B8 \uCC98\uB9AC: \uD0A4\uC6CC\uB4DC \uAC10\uC9C0 ---\n\ninterface KeywordMatch {\n primitive: 'consult' | 'init' | 'plan' | 'setup';\n skill: string;\n}\n\nconst EXPLICIT_TAGS: Record<string, KeywordMatch> = {\n consult: { primitive: 'consult', skill: 'nexus:consult' },\n init: { primitive: 'init', skill: 'nexus:init' },\n plan: { primitive: 'plan', skill: 'nexus:plan' },\n setup: { primitive: 'setup', skill: 'nexus:setup' },\n};\n\nconst NATURAL_PATTERNS: Array<{ patterns: RegExp[]; match: KeywordMatch }> = [\n {\n patterns: [/\\bconsult\\b/i, /\uC0C1\uB2F4/, /\uC5B4\uB5BB\uAC8C\\s*\uD558\uBA74\\s*\uC88B\uC744\uAE4C/, /\uBB50\uAC00\\s*\uC88B\uC744\uAE4C/, /\uBC29\uBC95\uC744?\\s*\uCC3E\uC544/],\n match: { primitive: 'consult', skill: 'nexus:consult' },\n },\n {\n patterns: [/\uACC4\uD68D\\s*(\uC138\uC6CC|\uC9DC|\uC218\uB9BD)/, /\\bplan\\b/i, /\uAD6C\uD604\\s*\uACC4\uD68D/, /\uC124\uACC4\uD574/, /\uC5B4\uB5BB\uAC8C\\s*\uAD6C\uD604/, /plan\\s*this/i],\n match: { primitive: 'plan', skill: 'nexus:plan' },\n },\n {\n patterns: [/\\bsetup\\b/i, /nexus\\s*\uC124\uC815/, /nexus\\s*\uC138\uD305/, /setup\\s*nexus/i],\n match: { primitive: 'setup', skill: 'nexus:setup' },\n },\n];\n\n// \uD504\uB9AC\uBBF8\uD2F0\uBE0C \uC774\uB984\uC774 \uC5D0\uB7EC/\uBC84\uADF8 \uB9E5\uB77D\uC5D0\uC11C \uC5B8\uAE09\uB418\uBA74 \uD65C\uC131\uD654\uAC00 \uC544\uB2CC \"\uB300\uD654\" \u2014 \uC624\uD0D0 \uBC29\uC9C0\nconst ERROR_CONTEXT = /\uC5D0\uB7EC|\uBC84\uADF8|\uC624\uB958|\\bfix\\b|\\bbug\\b|\\berror\\b|\uC774\uC288|\\bissue\\b/i;\nconst PRIMITIVE_NAMES = /\\b(plan|setup|init|consult)\\b/i;\n\n/** \uD504\uB9AC\uBBF8\uD2F0\uBE0C \uC774\uB984\uC774 \uC5D0\uB7EC/\uBC84\uADF8 \uB9E5\uB77D\uACFC \uD568\uAED8 \uB4F1\uC7A5\uD558\uAC70\uB098, \uB2E8\uC21C \uC9C8\uBB38/\uC778\uC6A9 \uB9E5\uB77D\uC778\uC9C0 \uD310\uBCC4 */\nfunction isPrimitiveMention(prompt: string): boolean {\n // \uC5D0\uB7EC/\uBC84\uADF8 \uB9E5\uB77D\n if (PRIMITIVE_NAMES.test(prompt) && ERROR_CONTEXT.test(prompt)) return true;\n // \uC9C8\uBB38 \uB9E5\uB77D: \"plan\uC774 \uBB50\uC57C\", \"what is consult\" \uB4F1\n if (PRIMITIVE_NAMES.test(prompt) && /\uBB50\uC57C|\uBB54\uAC00\uC694|what\\s+is|what\\s+does|\uC124\uBA85\uD574|explain/i.test(prompt)) return true;\n // \uC778\uC6A9 \uB9E5\uB77D: `plan`, \"consult\", 'setup'\n if (/[`\"'](?:plan|setup|init|consult)[`\"']/i.test(prompt)) return true;\n return false;\n}\n\nfunction detectKeywords(prompt: string): KeywordMatch | null {\n // 1\uCC28: \uBA85\uC2DC\uC801 \uD0DC\uADF8 [consult], [plan] \uB4F1 \u2014 \uD56D\uC0C1 \uD655\uC815\n const tagMatch = prompt.match(/\\[(\\w+)\\]/);\n if (tagMatch) {\n const tag = tagMatch[1].toLowerCase();\n if (tag in EXPLICIT_TAGS) return EXPLICIT_TAGS[tag];\n }\n\n // 2\uCC28: \uC790\uC5F0\uC5B4 \uD328\uD134 (\uD504\uB9AC\uBBF8\uD2F0\uBE0C \uC774\uB984 + \uC5D0\uB7EC \uB9E5\uB77D\uC77C \uB54C\uB9CC \uD544\uD130)\n for (const { patterns, match } of NATURAL_PATTERNS) {\n if (patterns.some((p) => p.test(prompt))) {\n if (isPrimitiveMention(prompt)) continue;\n return match;\n }\n }\n\n return null;\n}\n\nfunction handleUserPromptSubmit(event: Record<string, unknown>): void {\n const prompt = (event.prompt ?? event.user_prompt ?? '') as string;\n if (!prompt) { pass(); return; }\n\n // Phase \uC790\uB3D9 \uC804\uD658: waiting \u2192 delegating (\uC0AC\uC6A9\uC790\uAC00 \uC751\uB2F5\uD568)\n const sid = getSessionId();\n const workflowPath = join(sessionDir(sid), 'workflow.json');\n if (existsSync(workflowPath)) {\n try {\n const state = JSON.parse(readFileSync(workflowPath, 'utf-8'));\n if (state.phase === 'waiting') {\n updateWorkflowPhase(sid, 'delegating');\n }\n } catch { /* skip */ }\n }\n\n // [d] \uACB0\uC815 \uD0DC\uADF8 \uAC10\uC9C0 \u2014 plan \uB514\uB809\uD1A0\uB9AC \uC874\uC7AC \uC2DC \uACB0\uC815 \uAE30\uB85D \uC9C0\uC2DC\n const dTag = prompt.match(/\\[d\\]/i);\n if (dTag) {\n let branch = 'unknown';\n try { branch = require('child_process').execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim(); } catch {}\n const branchDir = branch.replace(/\\//g, '--');\n respond({\n continue: true,\n additionalContext: `[NEXUS] Decision tag detected. Record this decision in .claude/nexus/plans/${branchDir}/plan.md under the decisions section.`,\n });\n return;\n }\n\n const match = detectKeywords(prompt);\n if (match) {\n // init\uB294 \uB300\uD654\uD615 \u2014 \uC0C1\uD0DC \uD30C\uC77C \uBD88\uD544\uC694, \uCEE8\uD14D\uC2A4\uD2B8 \uC8FC\uC785\uB9CC\n if (match.primitive === 'init') {\n respond({\n continue: true,\n additionalContext: `[NEXUS] Init mode activated. Follow the init workflow:\n1. SCAN: Read project structure (top-level dirs, config files), CLAUDE.md, README.md, docs/, .claude/, and other .md files. Use git log for recent activity.\n2. TRIAGE: Classify each doc as Essential (\u2192 knowledge/), Useful (\u2192 knowledge/ condensed), Redundant (Nexus handles better), or Outdated (skip).\n3. PROPOSE: Present triage results to user via AskUserQuestion. Ask about CLAUDE.md slimming strategy and which knowledge files to generate.\n4. GENERATE: Create .claude/nexus/knowledge/ files (architecture.md, conventions.md, project-context.md). Backup original CLAUDE.md. Slim CLAUDE.md per user choice.\n5. VERIFY: Confirm generated files are readable via nx_knowledge_read. Report summary.\nIMPORTANT: Always backup before modifying. Never delete without user approval.`,\n });\n return;\n }\n\n if (match.primitive === 'consult') {\n const sid = getSessionId();\n activateMode('consult', sid, { phase: 'exploring' });\n respond({\n continue: true,\n additionalContext: `[NEXUS] Consult mode activated. Follow the consult workflow:\n1. EXPLORE: Read code (nx_lsp_document_symbols, nx_ast_search for brownfield), knowledge (nx_knowledge_read), context (nx_context). Auto-detect brownfield vs greenfield.\n2. ASSESS: Evaluate 4 dimensions \u2014 [Goal: ?] [Constraints: ?] [Criteria: ?] [Context: ?]. Mark each \u2705/\u26A0\uFE0F/\u274C. If \u22641 unclear \u2192 lightweight mode. If \u22652 unclear \u2192 deep mode.\n3. CLARIFY (if unclear dimensions exist; 1-2 questions in lightweight, extended in deep): MUST use AskUserQuestion with concrete options \u2014 never ask as plain text. One question at a time targeting the weakest dimension.\n4. DIVERGE: Generate 2-4 genuinely different approaches with pros/cons/effort.\n5. PROPOSE: Present options via AskUserQuestion with preview for concrete comparisons.\n6. CONVERGE: Elaborate chosen approach, follow-up if needed, produce concrete plan.\n7. CRYSTALLIZE: Finalize plan. If unclear dimensions remain, disclose risks transparently \u2014 but never block the user.\n8. EXECUTE BRIDGE: Offer 2-3 options via AskUserQuestion: Execute with delegation (Recommended) / Plan only / Skip.\nKey: One question at a time. Specific choices, not vague \"what do you think?\". Respect user autonomy.\nIf a plan directory exists for the current branch, record decisions from user selections in the plan.md file.`,\n });\n return;\n }\n\n if (match.primitive === 'plan') {\n // main/master \uBE0C\uB79C\uCE58\uC5D0\uC11C\uB294 feature \uBE0C\uB79C\uCE58 \uC0DD\uC131 \uBA3C\uC800 \uC720\uB3C4\n let currentBranch = 'unknown';\n try { currentBranch = require('child_process').execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim(); } catch {}\n const onMain = currentBranch === 'main' || currentBranch === 'master';\n\n const sid = getSessionId();\n if (!onMain) {\n activateMode('plan', sid, { phase: 'analyzing' });\n }\n\n const branchInstruction = onMain\n ? `\\nIMPORTANT: You are on the ${currentBranch} branch. Before planning, create a feature branch:\n1. Analyze the user's request to generate a descriptive branch name (e.g., feat/phase-auto-tracking, fix/statusline-bug).\n2. Run: git checkout -b <branch-name>\n3. Then proceed with the plan workflow.\nPlans are stored under .claude/nexus/plans/{branch}/ \u2014 planning on main is not allowed.`\n : '';\n\n respond({\n continue: true,\n additionalContext: `[NEXUS] Plan mode activated. Follow the plan workflow:${branchInstruction}\n1. ANALYZE: Analyze the request. Determine scale \u2014 small (1-3 files), medium (module-level), large (architecture/security/migration). Auto-escalate to large if request mentions auth, migration, delete, or security.\n2. DRAFT: Spawn Agent({ subagent_type: \"nexus:strategist\", prompt: \"<full request context>\" }) to create initial plan.\n3. REVIEW (medium+): Spawn Agent({ subagent_type: \"nexus:architect\", prompt: \"Review this plan: <strategist output>\" }) for structural review.\n4. CRITIQUE (large only): Spawn Agent({ subagent_type: \"nexus:reviewer\", prompt: \"Critique this plan: <architect output>\" }). If critical issues, loop back to DRAFT (max 3 iterations).\n5. PERSIST: Save plan to .claude/nexus/plans/{branch}/plan.md. Generate .claude/nexus/plans/{branch}/tasks.json with task list including dependencies.\n6. EXECUTE BRIDGE: Offer 2-3 options via AskUserQuestion: Execute with delegation (Recommended) / Plan only / Skip.\nKey: This is the standalone Plan skill \u2014 not the plan stage within auto. Scale determines formality. Small tasks need only a checklist, not a full ADR.\n`,\n });\n return;\n }\n\n if (match.primitive === 'setup') {\n respond({\n continue: true,\n additionalContext: `[NEXUS] Setup wizard activated. Guide the user through these steps IN ORDER using AskUserQuestion for each:\n1. SCOPE: Ask configuration scope \u2014 User (all projects, ~/.claude/CLAUDE.md) or Project (this project only, CLAUDE.md). This determines write paths for all subsequent steps.\n2. STATUSLINE: Ask preset choice (Full recommended / Standard / Minimal / Skip).\n3. DELEGATION: Ask enforcement level (Warn recommended / Strict / Off / Skip).\n4. CLAUDE.MD: Generate Nexus delegation section in CLAUDE.md using <!-- NEXUS:START --> / <!-- NEXUS:END --> markers. Content in English: delegation rules, agent routing table, 6-Section format guide, skill list. Preserve existing content outside markers.\n5. OMC CHECK: Check if oh-my-claudecode (omc) plugin is active. If found, warn about conflicts and offer: Disable omc (recommended) / Keep both / Skip. If Disable chosen, set {\"enabledPlugins\":{\"omc\":false}} in .claude/settings.json.\n6. INIT: Ask whether to run knowledge init (Yes recommended / Skip).\n7. COMPLETE: Show summary of applied settings.\nKey: Use AskUserQuestion for every step. Always offer Skip option.`,\n });\n return;\n }\n }\n\n pass();\n}\n\n// --- \uBA54\uC778 ---\n\nasync function main() {\n const input = await readStdin();\n const event = JSON.parse(input);\n // Claude Code\uB294 hook_event_name\uC744 \uBCF4\uB0B4\uC9C0 \uC54A\uC744 \uC218 \uC788\uC74C.\n // UserPromptSubmit\uC740 prompt \uD544\uB4DC\uAC00 \uC788\uACE0, Stop\uC740 \uC5C6\uC74C.\n const hasPrompt = 'prompt' in event || 'user_prompt' in event;\n\n if (hasPrompt) {\n handleUserPromptSubmit(event);\n } else {\n handleStop();\n }\n}\n\nmain().catch(() => {\n respond({ continue: true });\n});\n", "import { resolve, join } from 'path';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\n\n/** \uD504\uB85C\uC81D\uD2B8 \uB8E8\uD2B8 (.git\uC774 \uC788\uB294 \uB514\uB809\uD1A0\uB9AC) */\nfunction findProjectRoot(): string {\n let dir = process.cwd();\n while (dir !== '/') {\n if (existsSync(join(dir, '.git'))) return dir;\n dir = resolve(dir, '..');\n }\n return process.cwd();\n}\n\nconst PROJECT_ROOT = findProjectRoot();\n\n/** .nexus/ \u2014 gitignore, \uB7F0\uD0C0\uC784 \uC0C1\uD0DC */\nexport const RUNTIME_ROOT = join(PROJECT_ROOT, '.nexus');\n\n/** .claude/nexus/ \u2014 git \uCD94\uC801, \uACF5\uC720 \uC9C0\uC2DD */\nexport const KNOWLEDGE_ROOT = join(PROJECT_ROOT, '.claude', 'nexus');\n\n/** \uC138\uC158\uBCC4 \uC0C1\uD0DC \uB514\uB809\uD1A0\uB9AC */\nexport function sessionDir(sessionId: string): string {\n return join(RUNTIME_ROOT, 'state', 'sessions', sessionId);\n}\n\n/** \uC0C1\uD0DC \uD30C\uC77C \uACBD\uB85C */\nexport function statePath(sessionId: string, key: string): string {\n return join(sessionDir(sessionId), `${key}.json`);\n}\n\n/** \uC9C0\uC2DD \uD30C\uC77C \uACBD\uB85C */\nexport function knowledgePath(topic: string): string {\n return join(KNOWLEDGE_ROOT, 'knowledge', `${topic}.md`);\n}\n\n/** \uD50C\uB79C \uB514\uB809\uD1A0\uB9AC */\nexport function plansDir(): string {\n return join(KNOWLEDGE_ROOT, 'plans');\n}\n\n/** \uB514\uB809\uD1A0\uB9AC \uC0DD\uC131 (\uC7AC\uADC0) */\nexport function ensureDir(dir: string): void {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n}\n\n/** workflow.json\uC758 phase\uB97C \uAC31\uC2E0 (consult/plan \uBAA8\uB4DC\uC77C \uB54C\uB9CC) */\nexport function updateWorkflowPhase(sid: string, phase: string): void {\n const workflowPath = join(sessionDir(sid), 'workflow.json');\n if (!existsSync(workflowPath)) return;\n try {\n const state = JSON.parse(readFileSync(workflowPath, 'utf-8'));\n if ((state.mode === 'consult' || state.mode === 'plan') && state.phase !== phase) {\n state.phase = phase;\n writeFileSync(workflowPath, JSON.stringify(state, null, 2));\n }\n } catch { /* skip */ }\n}\n\n/** \uD604\uC7AC \uC6CC\uD06C\uD50C\uB85C\uC6B0\uC758 base phase \uBC18\uD658 (consult\u2192exploring, plan\u2192analyzing) */\nexport function getBasePhase(sid: string): string | null {\n const workflowPath = join(sessionDir(sid), 'workflow.json');\n if (!existsSync(workflowPath)) return null;\n try {\n const state = JSON.parse(readFileSync(workflowPath, 'utf-8'));\n if (state.mode === 'consult') return 'exploring';\n if (state.mode === 'plan') return 'analyzing';\n } catch { /* skip */ }\n return null;\n}\n", "import { randomUUID } from 'crypto';\nimport { existsSync, readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { RUNTIME_ROOT, ensureDir } from './paths.js';\n\nconst SESSION_FILE = join(RUNTIME_ROOT, 'state', 'current-session.json');\n\n/** \uD604\uC7AC \uC138\uC158 ID\uB97C \uAC00\uC838\uC624\uAC70\uB098 \uC0C8\uB85C \uC0DD\uC131 */\nexport function getSessionId(): string {\n if (existsSync(SESSION_FILE)) {\n try {\n const data = JSON.parse(readFileSync(SESSION_FILE, 'utf-8'));\n if (data.sessionId && typeof data.sessionId === 'string') {\n return data.sessionId;\n }\n } catch {\n // \uD30C\uC2F1 \uC2E4\uD328 \uC2DC \uC0C8\uB85C \uC0DD\uC131\n }\n }\n return createSession();\n}\n\n/** \uD604\uC7AC \uC800\uC7A5\uB41C \uC138\uC158 ID\uB97C \uC77D\uAE30 (\uB36E\uC5B4\uC4F0\uAE30 \uC804 \uD638\uCD9C\uC6A9) */\nexport function getPreviousSessionId(): string | null {\n if (existsSync(SESSION_FILE)) {\n try {\n const data = JSON.parse(readFileSync(SESSION_FILE, 'utf-8'));\n if (data.sessionId && typeof data.sessionId === 'string') {\n return data.sessionId;\n }\n } catch { /* skip */ }\n }\n return null;\n}\n\n/** \uC0C8 \uC138\uC158 \uC0DD\uC131 */\nexport function createSession(): string {\n const sessionId = randomUUID().slice(0, 8);\n ensureDir(join(RUNTIME_ROOT, 'state'));\n writeFileSync(SESSION_FILE, JSON.stringify({ sessionId, createdAt: new Date().toISOString() }));\n return sessionId;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["resolve", "import_fs", "import_fs", "import_path", "import_path", "sid"]
|
|
7
|
+
}
|