claude-nexus 0.27.0 → 0.28.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.mcp.json +1 -1
- package/README.en.md +2 -6
- package/README.md +2 -6
- package/VERSION +1 -1
- package/agents/architect.md +8 -9
- package/agents/designer.md +8 -9
- package/agents/engineer.md +3 -9
- package/agents/lead.md +59 -0
- package/agents/postdoc.md +8 -9
- package/agents/researcher.md +7 -9
- package/agents/reviewer.md +7 -9
- package/agents/strategist.md +8 -9
- package/agents/tester.md +7 -9
- package/agents/writer.md +3 -9
- package/dist/hooks/agent-bootstrap.js +105 -0
- package/dist/hooks/agent-finalize.js +164 -0
- package/dist/hooks/prompt-router.js +7300 -0
- package/dist/hooks/session-init.js +21 -0
- package/hooks/hooks.json +10 -121
- package/package.json +11 -19
- package/scripts/statusline.mjs +124 -0
- package/settings.json +3 -0
- package/skills/nx-init/SKILL.md +2 -31
- package/skills/nx-plan/SKILL.md +5 -18
- package/skills/nx-run/SKILL.md +8 -21
- package/skills/nx-setup/SKILL.md +1 -1
- package/skills/nx-sync/SKILL.md +6 -6
- package/bridge/mcp-server.cjs +0 -22308
- package/bridge/mcp-server.cjs.map +0 -7
- package/scripts/gate.cjs +0 -772
- package/scripts/gate.cjs.map +0 -7
- package/scripts/statusline.cjs +0 -336
- package/scripts/statusline.cjs.map +0 -7
- package/templates/nexus-section.md +0 -54
package/.mcp.json
CHANGED
package/README.en.md
CHANGED
|
@@ -88,11 +88,10 @@ Typical flow: `[plan]` to discuss and align → `[d]` to decide (within plan)
|
|
|
88
88
|
|
|
89
89
|
Claude-callable tools exposed by the Nexus MCP server.
|
|
90
90
|
|
|
91
|
-
### Core (
|
|
91
|
+
### Core (10 tools)
|
|
92
92
|
|
|
93
93
|
| Tool | Purpose |
|
|
94
94
|
|------|---------|
|
|
95
|
-
| `nx_context` | Current session state lookup (branch, tasks, plan) |
|
|
96
95
|
| `nx_task_list/add/update/close` | Task management + history.json archiving |
|
|
97
96
|
| `nx_history_search` | Search past plan/task cycles (topic/decision query, last N) |
|
|
98
97
|
| `nx_artifact_write` | Save artifacts (branch-isolated) |
|
|
@@ -101,7 +100,7 @@ Claude-callable tools exposed by the Nexus MCP server.
|
|
|
101
100
|
| `nx_plan_update` | Modify plan issues (add/remove/edit/reopen) |
|
|
102
101
|
| `nx_plan_decide` | Record issue decision (plan.json) |
|
|
103
102
|
|
|
104
|
-
### Code Intelligence (
|
|
103
|
+
### Code Intelligence (8 tools)
|
|
105
104
|
|
|
106
105
|
| Tool | Purpose |
|
|
107
106
|
|------|---------|
|
|
@@ -113,11 +112,8 @@ Claude-callable tools exposed by the Nexus MCP server.
|
|
|
113
112
|
| `nx_lsp_code_actions` | Auto-fix and refactoring suggestions |
|
|
114
113
|
| `nx_lsp_document_symbols` | Symbols in a file |
|
|
115
114
|
| `nx_lsp_workspace_symbols` | Project-wide symbol search |
|
|
116
|
-
| `nx_ast_search` | AST pattern search (tree-sitter) |
|
|
117
|
-
| `nx_ast_replace` | AST pattern replacement (dryRun supported) |
|
|
118
115
|
|
|
119
116
|
LSP auto-detects the project language (e.g., `tsconfig.json` → TypeScript).
|
|
120
|
-
AST tools require `@ast-grep/napi`: `bun install @ast-grep/napi`
|
|
121
117
|
|
|
122
118
|
</details>
|
|
123
119
|
|
package/README.md
CHANGED
|
@@ -75,11 +75,10 @@ claude plugin install claude-nexus@nexus
|
|
|
75
75
|
|
|
76
76
|
Claude가 직접 호출하는 도구입니다.
|
|
77
77
|
|
|
78
|
-
### Core (
|
|
78
|
+
### Core (10개)
|
|
79
79
|
|
|
80
80
|
| 도구 | 용도 |
|
|
81
81
|
|------|------|
|
|
82
|
-
| `nx_context` | 현재 세션 상태 조회 (브랜치, 태스크, 플랜) |
|
|
83
82
|
| `nx_task_list/add/update/close` | `.nexus/state/tasks.json` 기반 태스크 관리 + `.nexus/history.json` 아카이브 |
|
|
84
83
|
| `nx_history_search` | 과거 plan/task 사이클 검색 (topic/decision 검색, 최근 N개) |
|
|
85
84
|
| `nx_artifact_write` | 팀 산출물 저장 (`.nexus/state/artifacts/`) |
|
|
@@ -88,7 +87,7 @@ Claude가 직접 호출하는 도구입니다.
|
|
|
88
87
|
| `nx_plan_update` | 플랜 논점 수정 (add/remove/edit/reopen) |
|
|
89
88
|
| `nx_plan_decide` | 논점 결정 처리 (plan.json) |
|
|
90
89
|
|
|
91
|
-
### Code Intelligence (
|
|
90
|
+
### Code Intelligence (8개)
|
|
92
91
|
|
|
93
92
|
| 도구 | 용도 |
|
|
94
93
|
|------|------|
|
|
@@ -100,11 +99,8 @@ Claude가 직접 호출하는 도구입니다.
|
|
|
100
99
|
| `nx_lsp_code_actions` | 자동 수정/리팩토링 제안 |
|
|
101
100
|
| `nx_lsp_document_symbols` | 파일 내 심볼 목록 |
|
|
102
101
|
| `nx_lsp_workspace_symbols` | 프로젝트 전체 심볼 검색 |
|
|
103
|
-
| `nx_ast_search` | AST 패턴 검색 (tree-sitter) |
|
|
104
|
-
| `nx_ast_replace` | AST 패턴 치환 (dryRun 지원) |
|
|
105
102
|
|
|
106
103
|
LSP는 프로젝트 언어를 자동 감지합니다 (tsconfig.json → TypeScript 등).
|
|
107
|
-
AST는 `@ast-grep/napi` 필요: `bun install @ast-grep/napi`
|
|
108
104
|
|
|
109
105
|
</details>
|
|
110
106
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.28.1
|
package/agents/architect.md
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: architect
|
|
3
2
|
description: "Technical design — evaluates How, reviews architecture, advises on implementation approach"
|
|
4
|
-
model: opus
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
model: claude-opus-4
|
|
4
|
+
disallowedTools:
|
|
5
|
+
- Edit
|
|
6
|
+
- Write
|
|
7
|
+
- MultiEdit
|
|
8
|
+
- NotebookEdit
|
|
9
|
+
- mcp__plugin_claude-nexus_nx__nx_task_add
|
|
10
|
+
- mcp__plugin_claude-nexus_nx__nx_task_update
|
|
11
11
|
---
|
|
12
|
-
|
|
13
12
|
## Role
|
|
14
13
|
|
|
15
14
|
You are the Architect — the technical authority who evaluates "How" something should be built.
|
package/agents/designer.md
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: designer
|
|
3
2
|
description: "UX/UI design — evaluates user experience, interaction patterns, and how users will experience the product"
|
|
4
|
-
model: opus
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
model: claude-opus-4
|
|
4
|
+
disallowedTools:
|
|
5
|
+
- Edit
|
|
6
|
+
- Write
|
|
7
|
+
- MultiEdit
|
|
8
|
+
- NotebookEdit
|
|
9
|
+
- mcp__plugin_claude-nexus_nx__nx_task_add
|
|
10
|
+
- mcp__plugin_claude-nexus_nx__nx_task_update
|
|
11
11
|
---
|
|
12
|
-
|
|
13
12
|
## Role
|
|
14
13
|
|
|
15
14
|
You are the Designer — the user experience authority who evaluates "How" something should be experienced by users.
|
package/agents/engineer.md
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: engineer
|
|
3
2
|
description: "Implementation — writes code, debugs issues, follows specifications from Lead and architect"
|
|
4
|
-
model: sonnet
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
task: "Code implementation, edits, debugging"
|
|
8
|
-
alias_ko: 엔지니어
|
|
9
|
-
category: do
|
|
10
|
-
resume_tier: bounded
|
|
3
|
+
model: claude-sonnet-4
|
|
4
|
+
disallowedTools:
|
|
5
|
+
- mcp__plugin_claude-nexus_nx__nx_task_add
|
|
11
6
|
---
|
|
12
|
-
|
|
13
7
|
## Role
|
|
14
8
|
|
|
15
9
|
You are the Engineer — the hands-on implementer who writes code and debugs issues.
|
package/agents/lead.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Primary orchestrator — converses directly with users, composes 9 subagents across HOW/DO/CHECK categories, and owns scope decisions and task lifecycle"
|
|
3
|
+
model: claude-opus-4
|
|
4
|
+
---
|
|
5
|
+
## Identity
|
|
6
|
+
|
|
7
|
+
You are Lead — the sole agent who converses directly with users.
|
|
8
|
+
You orchestrate 9 subagents (architect, designer, postdoc, strategist, engineer, researcher, writer, reviewer, tester) to fulfill user requests.
|
|
9
|
+
Final responsibility for decision recording, scope judgment, and user-facing reporting rests with you.
|
|
10
|
+
|
|
11
|
+
## Constraints
|
|
12
|
+
|
|
13
|
+
- **Task ownership**: You are the only agent authorized to call `nx_task_add` / `nx_task_update` / `nx_task_close`. Subagents do not create or update tasks.
|
|
14
|
+
- **Scope authority**: You consult HOW agents for advice, but final scope decisions are yours alone.
|
|
15
|
+
- **Skill delegation**: Delegate execution flows to skills. Use nx-plan for `[plan]`, nx-run for `[run]`, nx-sync for `[sync]`, and nx-init for initial onboarding. Detailed execution steps live inside each skill and are not duplicated in this body.
|
|
16
|
+
- **File editing**: No `no_file_edit` restriction — handle simple tasks directly.
|
|
17
|
+
- **Absolute prohibitions**:
|
|
18
|
+
- Spawning multiple subagents in parallel for the same task (risk of target file conflicts)
|
|
19
|
+
- Destructive git operations without explicit user instruction (`reset --hard`, `push --force`, etc.)
|
|
20
|
+
- Injecting hook messages in any language other than English
|
|
21
|
+
|
|
22
|
+
## Collaboration
|
|
23
|
+
|
|
24
|
+
### HOW agents (architect / designer / postdoc / strategist)
|
|
25
|
+
They advise on technical, UX, research methodology, and business judgment. They do not hold decision authority. You review their advice and make the final call.
|
|
26
|
+
|
|
27
|
+
### DO agents (engineer / researcher / writer)
|
|
28
|
+
They handle execution, implementation, investigation, and writing. You provide task context, approach, and acceptance criteria, then review their deliverables.
|
|
29
|
+
|
|
30
|
+
### CHECK agents (reviewer / tester)
|
|
31
|
+
They verify the accuracy and quality of deliverables.
|
|
32
|
+
- writer → reviewer: mandatory pairing
|
|
33
|
+
- engineer → tester: conditional pairing (when acceptance criteria include runtime requirements)
|
|
34
|
+
|
|
35
|
+
### Direct handling vs. spawn decision
|
|
36
|
+
- Single file or small-scale edits: handle directly as Lead
|
|
37
|
+
- Three or more files, complex judgment, or specialist analysis: spawn a subagent
|
|
38
|
+
|
|
39
|
+
### Resume Dispatch
|
|
40
|
+
Decide whether to reuse a completed subagent based on the `resume_tier` field (persistent / bounded / ephemeral) in the agent's frontmatter. See the nx-run skill for detailed rules.
|
|
41
|
+
|
|
42
|
+
## Output Format
|
|
43
|
+
|
|
44
|
+
When responding to users, maintain the following structure:
|
|
45
|
+
|
|
46
|
+
- **Changes**: Paths and summaries of modified, created, or deleted files
|
|
47
|
+
- **Key Decisions**: Judgments made during this work (scope, approach, trade-offs)
|
|
48
|
+
- **Next Steps**: Follow-on actions the user can take (review, commit, further investigation, etc.)
|
|
49
|
+
|
|
50
|
+
For long responses, lead with the summary. For short questions, answer directly without structure.
|
|
51
|
+
|
|
52
|
+
## References
|
|
53
|
+
|
|
54
|
+
| Skill | Purpose |
|
|
55
|
+
|-------|---------|
|
|
56
|
+
| nx-plan | Structured multi-perspective analysis and decision recording |
|
|
57
|
+
| nx-run | Task execution orchestration |
|
|
58
|
+
| nx-sync | `.nexus/context/` knowledge synchronization |
|
|
59
|
+
| nx-init | Project onboarding |
|
package/agents/postdoc.md
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: postdoc
|
|
3
2
|
description: "Research methodology and synthesis — designs investigation approach, evaluates evidence quality, writes synthesis documents"
|
|
4
|
-
model: opus
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
model: claude-opus-4
|
|
4
|
+
disallowedTools:
|
|
5
|
+
- Edit
|
|
6
|
+
- Write
|
|
7
|
+
- MultiEdit
|
|
8
|
+
- NotebookEdit
|
|
9
|
+
- mcp__plugin_claude-nexus_nx__nx_task_add
|
|
10
|
+
- mcp__plugin_claude-nexus_nx__nx_task_update
|
|
11
11
|
---
|
|
12
|
-
|
|
13
12
|
## Role
|
|
14
13
|
|
|
15
14
|
You are the Postdoctoral Researcher — the methodological authority who evaluates "How" research should be conducted and synthesizes findings into coherent conclusions.
|
package/agents/researcher.md
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: researcher
|
|
3
2
|
description: "Independent investigation — conducts web searches, gathers evidence, and reports findings with citations"
|
|
4
|
-
model: sonnet
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
model: claude-sonnet-4
|
|
4
|
+
disallowedTools:
|
|
5
|
+
- Edit
|
|
6
|
+
- Write
|
|
7
|
+
- MultiEdit
|
|
8
|
+
- NotebookEdit
|
|
9
|
+
- mcp__plugin_claude-nexus_nx__nx_task_add
|
|
11
10
|
---
|
|
12
|
-
|
|
13
11
|
## Role
|
|
14
12
|
|
|
15
13
|
You are the Researcher — the web research specialist who gathers evidence through web searches, external document analysis, and structured inquiry.
|
package/agents/reviewer.md
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: reviewer
|
|
3
2
|
description: "Content verification — validates accuracy, checks facts, confirms grammar and format of non-code deliverables"
|
|
4
|
-
model: sonnet
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
model: claude-sonnet-4
|
|
4
|
+
disallowedTools:
|
|
5
|
+
- Edit
|
|
6
|
+
- Write
|
|
7
|
+
- MultiEdit
|
|
8
|
+
- NotebookEdit
|
|
9
|
+
- mcp__plugin_claude-nexus_nx__nx_task_add
|
|
11
10
|
---
|
|
12
|
-
|
|
13
11
|
## Role
|
|
14
12
|
|
|
15
13
|
You are the Reviewer — the content quality guardian who verifies the accuracy, clarity, and integrity of non-code deliverables.
|
package/agents/strategist.md
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: strategist
|
|
3
2
|
description: "Business strategy — evaluates market positioning, competitive landscape, and business viability of decisions"
|
|
4
|
-
model: opus
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
model: claude-opus-4
|
|
4
|
+
disallowedTools:
|
|
5
|
+
- Edit
|
|
6
|
+
- Write
|
|
7
|
+
- MultiEdit
|
|
8
|
+
- NotebookEdit
|
|
9
|
+
- mcp__plugin_claude-nexus_nx__nx_task_add
|
|
10
|
+
- mcp__plugin_claude-nexus_nx__nx_task_update
|
|
11
11
|
---
|
|
12
|
-
|
|
13
12
|
## Role
|
|
14
13
|
|
|
15
14
|
You are the Strategist — the business and market authority who evaluates "How" decisions land in the real world.
|
package/agents/tester.md
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: tester
|
|
3
2
|
description: "Testing and verification — tests, verifies, validates stability and security of implementations"
|
|
4
|
-
model: sonnet
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
model: claude-sonnet-4
|
|
4
|
+
disallowedTools:
|
|
5
|
+
- Edit
|
|
6
|
+
- Write
|
|
7
|
+
- MultiEdit
|
|
8
|
+
- NotebookEdit
|
|
9
|
+
- mcp__plugin_claude-nexus_nx__nx_task_add
|
|
11
10
|
---
|
|
12
|
-
|
|
13
11
|
## Role
|
|
14
12
|
|
|
15
13
|
You are the Tester — the code verification specialist who tests, validates, and secures implementations.
|
package/agents/writer.md
CHANGED
|
@@ -1,15 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: writer
|
|
3
2
|
description: "Technical writing — transforms research findings, code, and analysis into clear documents and presentations for the intended audience"
|
|
4
|
-
model: sonnet
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
task: "Technical writing, documentation, presentations"
|
|
8
|
-
alias_ko: 라이터
|
|
9
|
-
category: do
|
|
10
|
-
resume_tier: bounded
|
|
3
|
+
model: claude-sonnet-4
|
|
4
|
+
disallowedTools:
|
|
5
|
+
- mcp__plugin_claude-nexus_nx__nx_task_add
|
|
11
6
|
---
|
|
12
|
-
|
|
13
7
|
## Role
|
|
14
8
|
|
|
15
9
|
You are the Writer — the communication specialist who transforms technical content into clear, audience-appropriate documents.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// assets/hooks/agent-bootstrap/handler.ts
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
var CORE_INDEX_SIZE_LIMIT = 2 * 1024;
|
|
5
|
+
function loadValidRoles(cwd) {
|
|
6
|
+
const agentsDir = join(cwd, "assets/agents");
|
|
7
|
+
const roles = [];
|
|
8
|
+
if (existsSync(agentsDir)) {
|
|
9
|
+
for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
|
|
10
|
+
if (entry.isDirectory())
|
|
11
|
+
roles.push(entry.name);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return roles;
|
|
15
|
+
}
|
|
16
|
+
function readFirstLine(path) {
|
|
17
|
+
try {
|
|
18
|
+
const content = readFileSync(path, "utf-8");
|
|
19
|
+
const firstNonEmpty = content.split(`
|
|
20
|
+
`).find((l) => l.trim().length > 0) ?? "";
|
|
21
|
+
return firstNonEmpty.replace(/^#+\s*/, "").slice(0, 80);
|
|
22
|
+
} catch {
|
|
23
|
+
return "";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function buildCoreIndex(cwd) {
|
|
27
|
+
const entries = [];
|
|
28
|
+
for (const sub of [".nexus/memory", ".nexus/context"]) {
|
|
29
|
+
const absDir = join(cwd, sub);
|
|
30
|
+
if (!existsSync(absDir))
|
|
31
|
+
continue;
|
|
32
|
+
for (const f of readdirSync(absDir, { withFileTypes: true })) {
|
|
33
|
+
if (!f.isFile() || !f.name.endsWith(".md"))
|
|
34
|
+
continue;
|
|
35
|
+
const full = join(absDir, f.name);
|
|
36
|
+
entries.push({
|
|
37
|
+
path: `${sub}/${f.name}`,
|
|
38
|
+
mtime: statSync(full).mtimeMs,
|
|
39
|
+
line: readFirstLine(full)
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
entries.sort((a, b) => b.mtime - a.mtime);
|
|
44
|
+
const lines = [];
|
|
45
|
+
let bytes = 0;
|
|
46
|
+
for (const e of entries) {
|
|
47
|
+
const formatted = `- ${e.path}: ${e.line}`;
|
|
48
|
+
if (bytes + formatted.length + 1 > CORE_INDEX_SIZE_LIMIT)
|
|
49
|
+
break;
|
|
50
|
+
lines.push(formatted);
|
|
51
|
+
bytes += formatted.length + 1;
|
|
52
|
+
}
|
|
53
|
+
return lines.length > 0 ? `Available memory/context:
|
|
54
|
+
` + lines.join(`
|
|
55
|
+
`) : "";
|
|
56
|
+
}
|
|
57
|
+
function getResumeCount(cwd, sessionId, agentId) {
|
|
58
|
+
const trackerPath = join(cwd, ".nexus/state", sessionId, "agent-tracker.json");
|
|
59
|
+
if (!existsSync(trackerPath))
|
|
60
|
+
return 0;
|
|
61
|
+
try {
|
|
62
|
+
const tracker = JSON.parse(readFileSync(trackerPath, "utf-8"));
|
|
63
|
+
const entry = Array.isArray(tracker) ? tracker.find((e) => e.agent_id === agentId) : null;
|
|
64
|
+
return entry?.resume_count ?? 0;
|
|
65
|
+
} catch {
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
var handler = async (input) => {
|
|
70
|
+
if (input.hook_event_name !== "SubagentStart")
|
|
71
|
+
return;
|
|
72
|
+
const { cwd, session_id, agent_type, agent_id } = input;
|
|
73
|
+
const resumeCount = getResumeCount(cwd, session_id, agent_id);
|
|
74
|
+
if (resumeCount > 0)
|
|
75
|
+
return;
|
|
76
|
+
const validRoles = loadValidRoles(cwd);
|
|
77
|
+
if (!validRoles.includes(agent_type))
|
|
78
|
+
return;
|
|
79
|
+
const parts = [];
|
|
80
|
+
const coreIndex = buildCoreIndex(cwd);
|
|
81
|
+
if (coreIndex) {
|
|
82
|
+
parts.push(`<system-notice>
|
|
83
|
+
${coreIndex}
|
|
84
|
+
</system-notice>`);
|
|
85
|
+
}
|
|
86
|
+
const rulePath = join(cwd, ".nexus/rules", `${agent_type}.md`);
|
|
87
|
+
if (existsSync(rulePath)) {
|
|
88
|
+
const ruleContent = readFileSync(rulePath, "utf-8").trim();
|
|
89
|
+
if (ruleContent) {
|
|
90
|
+
parts.push(`<system-notice>
|
|
91
|
+
Custom rule for ${agent_type}:
|
|
92
|
+
${ruleContent}
|
|
93
|
+
</system-notice>`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (parts.length === 0)
|
|
97
|
+
return;
|
|
98
|
+
return { additional_context: parts.join(`
|
|
99
|
+
|
|
100
|
+
`) };
|
|
101
|
+
};
|
|
102
|
+
var handler_default = handler;
|
|
103
|
+
export {
|
|
104
|
+
handler_default as default
|
|
105
|
+
};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// src/shared/json-store.js
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import { constants as fsConstants, appendFileSync, mkdirSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
var inProcessQueues = new Map;
|
|
7
|
+
async function runWithInProcessLock(filePath, action) {
|
|
8
|
+
const previous = inProcessQueues.get(filePath) ?? Promise.resolve();
|
|
9
|
+
let release = () => {};
|
|
10
|
+
const gate = new Promise((resolve) => {
|
|
11
|
+
release = resolve;
|
|
12
|
+
});
|
|
13
|
+
const entry = previous.then(() => gate);
|
|
14
|
+
inProcessQueues.set(filePath, entry);
|
|
15
|
+
await previous;
|
|
16
|
+
try {
|
|
17
|
+
return await action();
|
|
18
|
+
} finally {
|
|
19
|
+
release();
|
|
20
|
+
entry.finally(() => {
|
|
21
|
+
if (inProcessQueues.get(filePath) === entry) {
|
|
22
|
+
inProcessQueues.delete(filePath);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
var LOCK_RETRY_INTERVAL_MS = 100;
|
|
28
|
+
var LOCK_MAX_RETRIES = 50;
|
|
29
|
+
var LOCK_STALE_MS = 30000;
|
|
30
|
+
function lockPath(filePath) {
|
|
31
|
+
return `${filePath}.lock`;
|
|
32
|
+
}
|
|
33
|
+
async function acquireFsLock(filePath) {
|
|
34
|
+
const lp = lockPath(filePath);
|
|
35
|
+
for (let attempt = 0;attempt <= LOCK_MAX_RETRIES; attempt++) {
|
|
36
|
+
try {
|
|
37
|
+
const fd = await fs.open(lp, fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL);
|
|
38
|
+
await fd.close();
|
|
39
|
+
return;
|
|
40
|
+
} catch (err) {
|
|
41
|
+
const e = err;
|
|
42
|
+
if (e.code !== "EEXIST")
|
|
43
|
+
throw err;
|
|
44
|
+
try {
|
|
45
|
+
const stat = await fs.stat(lp);
|
|
46
|
+
const ageMs = Date.now() - stat.mtimeMs;
|
|
47
|
+
if (ageMs > LOCK_STALE_MS) {
|
|
48
|
+
await fs.unlink(lp).catch(() => {
|
|
49
|
+
return;
|
|
50
|
+
});
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (attempt === LOCK_MAX_RETRIES) {
|
|
57
|
+
throw new Error(`Failed to acquire lock for "${filePath}" after ${LOCK_MAX_RETRIES} retries`);
|
|
58
|
+
}
|
|
59
|
+
await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_INTERVAL_MS));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function releaseFsLock(filePath) {
|
|
64
|
+
await fs.unlink(lockPath(filePath)).catch(() => {
|
|
65
|
+
return;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async function readJsonFile(filePath, defaultValue) {
|
|
69
|
+
let raw;
|
|
70
|
+
try {
|
|
71
|
+
raw = await fs.readFile(filePath, "utf8");
|
|
72
|
+
} catch (err) {
|
|
73
|
+
const e = err;
|
|
74
|
+
if (e.code === "ENOENT")
|
|
75
|
+
return defaultValue;
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
return JSON.parse(raw);
|
|
80
|
+
} catch {
|
|
81
|
+
return defaultValue;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function writeJsonFile(filePath, data) {
|
|
85
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
86
|
+
const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}.${randomUUID()}`;
|
|
87
|
+
await fs.writeFile(tmpPath, JSON.stringify(data, null, 2) + `
|
|
88
|
+
`, "utf8");
|
|
89
|
+
await fs.rename(tmpPath, filePath);
|
|
90
|
+
}
|
|
91
|
+
async function updateJsonFileLocked(filePath, defaultValue, updater) {
|
|
92
|
+
return runWithInProcessLock(filePath, async () => {
|
|
93
|
+
await acquireFsLock(filePath);
|
|
94
|
+
try {
|
|
95
|
+
const current = await readJsonFile(filePath, defaultValue);
|
|
96
|
+
const next = await updater(current);
|
|
97
|
+
await writeJsonFile(filePath, next);
|
|
98
|
+
return next;
|
|
99
|
+
} finally {
|
|
100
|
+
await releaseFsLock(filePath);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
var APPEND_SIZE_WARN_THRESHOLD = 4 * 1024;
|
|
105
|
+
|
|
106
|
+
// assets/hooks/agent-finalize/handler.ts
|
|
107
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
108
|
+
import { join } from "node:path";
|
|
109
|
+
var handler = async (input) => {
|
|
110
|
+
if (input.hook_event_name !== "SubagentStop")
|
|
111
|
+
return;
|
|
112
|
+
const { cwd, session_id, agent_type, agent_id } = input;
|
|
113
|
+
const lastMessage = (input.last_assistant_message ?? "").slice(0, 500);
|
|
114
|
+
const sessionDir = join(cwd, ".nexus/state", session_id);
|
|
115
|
+
const trackerPath = join(sessionDir, "agent-tracker.json");
|
|
116
|
+
const toolLogPath = join(sessionDir, "tool-log.jsonl");
|
|
117
|
+
const tasksPath = join(sessionDir, "tasks.json");
|
|
118
|
+
await updateJsonFileLocked(trackerPath, [], (tracker) => {
|
|
119
|
+
const entry = tracker.find((e) => e["agent_id"] === agent_id);
|
|
120
|
+
if (!entry)
|
|
121
|
+
return tracker;
|
|
122
|
+
entry["status"] = "completed";
|
|
123
|
+
entry["stopped_at"] = new Date().toISOString();
|
|
124
|
+
entry["last_message"] = lastMessage;
|
|
125
|
+
if (existsSync(toolLogPath)) {
|
|
126
|
+
const files = new Set;
|
|
127
|
+
const raw = readFileSync(toolLogPath, "utf-8");
|
|
128
|
+
for (const line of raw.split(`
|
|
129
|
+
`)) {
|
|
130
|
+
if (!line.trim())
|
|
131
|
+
continue;
|
|
132
|
+
try {
|
|
133
|
+
const log = JSON.parse(line);
|
|
134
|
+
if (log["agent_id"] === agent_id && typeof log["file"] === "string") {
|
|
135
|
+
files.add(log["file"]);
|
|
136
|
+
}
|
|
137
|
+
} catch {}
|
|
138
|
+
}
|
|
139
|
+
entry["files_touched"] = [...files];
|
|
140
|
+
}
|
|
141
|
+
return tracker;
|
|
142
|
+
});
|
|
143
|
+
if (!existsSync(tasksPath))
|
|
144
|
+
return;
|
|
145
|
+
try {
|
|
146
|
+
const tasksData = JSON.parse(readFileSync(tasksPath, "utf-8"));
|
|
147
|
+
const tasks = Array.isArray(tasksData?.["tasks"]) ? tasksData["tasks"] : [];
|
|
148
|
+
const incomplete = tasks.filter((t) => t["owner"]?.["role"] === agent_type && t["status"] !== "completed");
|
|
149
|
+
if (incomplete.length === 0)
|
|
150
|
+
return;
|
|
151
|
+
const ids = incomplete.map((t) => t["id"]).join(", ");
|
|
152
|
+
return {
|
|
153
|
+
additional_context: `<system-notice>
|
|
154
|
+
Subagent "${agent_type}" finished. Tasks still pending with this role: ${ids}. Review status and coordinate remaining subagent delegation.
|
|
155
|
+
</system-notice>`
|
|
156
|
+
};
|
|
157
|
+
} catch {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
var handler_default = handler;
|
|
162
|
+
export {
|
|
163
|
+
handler_default as default
|
|
164
|
+
};
|