@doingdev/opencode-claude-manager-plugin 0.1.65 → 0.1.66

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.
Files changed (123) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/manager/team-orchestrator.js +1 -1
  3. package/dist/plugin/agents/common.d.ts +2 -2
  4. package/dist/plugin/agents/common.js +5 -0
  5. package/dist/plugin/claude-manager.plugin.js +104 -0
  6. package/dist/plugin/inbox-ops.d.ts +50 -0
  7. package/dist/plugin/inbox-ops.js +166 -0
  8. package/dist/types/contracts.d.ts +18 -0
  9. package/package.json +1 -1
  10. package/dist/claude/session-live-tailer.d.ts +0 -51
  11. package/dist/claude/session-live-tailer.js +0 -269
  12. package/dist/manager/session-controller.d.ts +0 -41
  13. package/dist/manager/session-controller.js +0 -97
  14. package/dist/metadata/claude-metadata.service.d.ts +0 -12
  15. package/dist/metadata/claude-metadata.service.js +0 -38
  16. package/dist/metadata/repo-claude-config-reader.d.ts +0 -7
  17. package/dist/metadata/repo-claude-config-reader.js +0 -154
  18. package/dist/plugin/orchestrator.plugin.d.ts +0 -2
  19. package/dist/plugin/orchestrator.plugin.js +0 -116
  20. package/dist/providers/claude-code-wrapper.d.ts +0 -13
  21. package/dist/providers/claude-code-wrapper.js +0 -13
  22. package/dist/safety/bash-safety.d.ts +0 -21
  23. package/dist/safety/bash-safety.js +0 -62
  24. package/dist/src/claude/claude-agent-sdk-adapter.d.ts +0 -28
  25. package/dist/src/claude/claude-agent-sdk-adapter.js +0 -559
  26. package/dist/src/claude/claude-session.service.d.ts +0 -9
  27. package/dist/src/claude/claude-session.service.js +0 -15
  28. package/dist/src/claude/session-live-tailer.d.ts +0 -51
  29. package/dist/src/claude/session-live-tailer.js +0 -269
  30. package/dist/src/claude/tool-approval-manager.d.ts +0 -30
  31. package/dist/src/claude/tool-approval-manager.js +0 -279
  32. package/dist/src/index.d.ts +0 -5
  33. package/dist/src/index.js +0 -3
  34. package/dist/src/manager/context-tracker.d.ts +0 -32
  35. package/dist/src/manager/context-tracker.js +0 -103
  36. package/dist/src/manager/git-operations.d.ts +0 -18
  37. package/dist/src/manager/git-operations.js +0 -86
  38. package/dist/src/manager/persistent-manager.d.ts +0 -39
  39. package/dist/src/manager/persistent-manager.js +0 -44
  40. package/dist/src/manager/session-controller.d.ts +0 -41
  41. package/dist/src/manager/session-controller.js +0 -97
  42. package/dist/src/manager/team-orchestrator.d.ts +0 -81
  43. package/dist/src/manager/team-orchestrator.js +0 -612
  44. package/dist/src/plugin/agent-hierarchy.d.ts +0 -1
  45. package/dist/src/plugin/agent-hierarchy.js +0 -2
  46. package/dist/src/plugin/agents/browser-qa.d.ts +0 -14
  47. package/dist/src/plugin/agents/browser-qa.js +0 -31
  48. package/dist/src/plugin/agents/common.d.ts +0 -36
  49. package/dist/src/plugin/agents/common.js +0 -59
  50. package/dist/src/plugin/agents/cto.d.ts +0 -9
  51. package/dist/src/plugin/agents/cto.js +0 -39
  52. package/dist/src/plugin/agents/engineers.d.ts +0 -9
  53. package/dist/src/plugin/agents/engineers.js +0 -11
  54. package/dist/src/plugin/agents/index.d.ts +0 -5
  55. package/dist/src/plugin/agents/index.js +0 -5
  56. package/dist/src/plugin/agents/team-planner.d.ts +0 -10
  57. package/dist/src/plugin/agents/team-planner.js +0 -23
  58. package/dist/src/plugin/claude-manager.plugin.d.ts +0 -10
  59. package/dist/src/plugin/claude-manager.plugin.js +0 -950
  60. package/dist/src/plugin/service-factory.d.ts +0 -38
  61. package/dist/src/plugin/service-factory.js +0 -101
  62. package/dist/src/prompts/registry.d.ts +0 -2
  63. package/dist/src/prompts/registry.js +0 -210
  64. package/dist/src/state/file-run-state-store.d.ts +0 -14
  65. package/dist/src/state/file-run-state-store.js +0 -85
  66. package/dist/src/state/team-state-store.d.ts +0 -14
  67. package/dist/src/state/team-state-store.js +0 -88
  68. package/dist/src/state/transcript-store.d.ts +0 -15
  69. package/dist/src/state/transcript-store.js +0 -44
  70. package/dist/src/team/roster.d.ts +0 -5
  71. package/dist/src/team/roster.js +0 -40
  72. package/dist/src/types/contracts.d.ts +0 -261
  73. package/dist/src/types/contracts.js +0 -2
  74. package/dist/src/util/fs-helpers.d.ts +0 -8
  75. package/dist/src/util/fs-helpers.js +0 -21
  76. package/dist/src/util/project-context.d.ts +0 -10
  77. package/dist/src/util/project-context.js +0 -105
  78. package/dist/src/util/transcript-append.d.ts +0 -7
  79. package/dist/src/util/transcript-append.js +0 -29
  80. package/dist/state/file-run-state-store.d.ts +0 -14
  81. package/dist/state/file-run-state-store.js +0 -85
  82. package/dist/test/claude-agent-sdk-adapter.test.d.ts +0 -1
  83. package/dist/test/claude-agent-sdk-adapter.test.js +0 -707
  84. package/dist/test/claude-manager.plugin.test.d.ts +0 -1
  85. package/dist/test/claude-manager.plugin.test.js +0 -316
  86. package/dist/test/context-tracker.test.d.ts +0 -1
  87. package/dist/test/context-tracker.test.js +0 -130
  88. package/dist/test/cto-active-team.test.d.ts +0 -1
  89. package/dist/test/cto-active-team.test.js +0 -199
  90. package/dist/test/file-run-state-store.test.d.ts +0 -1
  91. package/dist/test/file-run-state-store.test.js +0 -82
  92. package/dist/test/fs-helpers.test.d.ts +0 -1
  93. package/dist/test/fs-helpers.test.js +0 -56
  94. package/dist/test/git-operations.test.d.ts +0 -1
  95. package/dist/test/git-operations.test.js +0 -133
  96. package/dist/test/persistent-manager.test.d.ts +0 -1
  97. package/dist/test/persistent-manager.test.js +0 -48
  98. package/dist/test/project-context.test.d.ts +0 -1
  99. package/dist/test/project-context.test.js +0 -92
  100. package/dist/test/prompt-registry.test.d.ts +0 -1
  101. package/dist/test/prompt-registry.test.js +0 -117
  102. package/dist/test/report-claude-event.test.d.ts +0 -1
  103. package/dist/test/report-claude-event.test.js +0 -304
  104. package/dist/test/session-controller.test.d.ts +0 -1
  105. package/dist/test/session-controller.test.js +0 -149
  106. package/dist/test/session-live-tailer.test.d.ts +0 -1
  107. package/dist/test/session-live-tailer.test.js +0 -313
  108. package/dist/test/team-orchestrator.test.d.ts +0 -1
  109. package/dist/test/team-orchestrator.test.js +0 -583
  110. package/dist/test/team-state-store.test.d.ts +0 -1
  111. package/dist/test/team-state-store.test.js +0 -54
  112. package/dist/test/tool-approval-manager.test.d.ts +0 -1
  113. package/dist/test/tool-approval-manager.test.js +0 -260
  114. package/dist/test/transcript-append.test.d.ts +0 -1
  115. package/dist/test/transcript-append.test.js +0 -37
  116. package/dist/test/transcript-store.test.d.ts +0 -1
  117. package/dist/test/transcript-store.test.js +0 -50
  118. package/dist/test/undo-propagation.test.d.ts +0 -1
  119. package/dist/test/undo-propagation.test.js +0 -837
  120. package/dist/util/project-context.d.ts +0 -10
  121. package/dist/util/project-context.js +0 -105
  122. package/dist/vitest.config.d.ts +0 -2
  123. package/dist/vitest.config.js +0 -11
@@ -1,269 +0,0 @@
1
- import { createReadStream, existsSync, readdirSync, statSync } from 'node:fs';
2
- import { createInterface } from 'node:readline';
3
- import path from 'node:path';
4
- import os from 'node:os';
5
- /**
6
- * Tails Claude Code session JSONL files for live tool output.
7
- *
8
- * The SDK streams high-level events (assistant text, tool_call summaries, results)
9
- * but does not expose the raw tool output that Claude Code writes to the JSONL
10
- * transcript on disk. This service fills that gap by polling the file for new
11
- * lines, parsing each one, and forwarding parsed events to a caller-supplied
12
- * callback.
13
- */
14
- export class SessionLiveTailer {
15
- activeTails = new Map();
16
- // ── Path discovery ──────────────────────────────────────────────────
17
- /**
18
- * Locate the JSONL file for a session.
19
- *
20
- * Claude Code stores transcripts at:
21
- * ~/.claude/projects/<sanitized-cwd>/sessions/<session-id>.jsonl
22
- *
23
- * The `<sanitized-cwd>` folder name is an internal implementation detail that
24
- * can change across Claude Code versions, so we search all project directories
25
- * for the session file rather than attempting to replicate the sanitisation.
26
- */
27
- findSessionFile(sessionId, cwd) {
28
- const projectsDir = path.join(os.homedir(), '.claude', 'projects');
29
- if (!existsSync(projectsDir)) {
30
- return null;
31
- }
32
- // If cwd is provided, try the sanitised form first (best-effort fast path).
33
- if (cwd) {
34
- const sanitised = cwd.replace(/\//g, '-');
35
- const candidate = path.join(projectsDir, sanitised, 'sessions', `${sessionId}.jsonl`);
36
- if (existsSync(candidate)) {
37
- return candidate;
38
- }
39
- }
40
- // Fall back to scanning all project directories.
41
- try {
42
- for (const entry of readdirSync(projectsDir, { withFileTypes: true })) {
43
- if (!entry.isDirectory())
44
- continue;
45
- const candidate = path.join(projectsDir, entry.name, 'sessions', `${sessionId}.jsonl`);
46
- if (existsSync(candidate)) {
47
- return candidate;
48
- }
49
- }
50
- }
51
- catch {
52
- // Permission denied or similar — nothing we can do.
53
- }
54
- return null;
55
- }
56
- /**
57
- * Check whether we can locate a JSONL file for the given session.
58
- */
59
- sessionFileExists(sessionId, cwd) {
60
- return this.findSessionFile(sessionId, cwd) !== null;
61
- }
62
- // ── Live tailing ────────────────────────────────────────────────────
63
- /**
64
- * Poll a session's JSONL file for new lines and emit parsed events.
65
- *
66
- * Returns a stop function. The caller **must** invoke it when tailing is no
67
- * longer needed (e.g. when the session completes or the tool is interrupted).
68
- */
69
- startTailing(sessionId, cwd, onEvent, pollIntervalMs = 150) {
70
- const filePath = this.findSessionFile(sessionId, cwd);
71
- if (!filePath) {
72
- onEvent({
73
- type: 'error',
74
- sessionId,
75
- error: `Session JSONL not found for ${sessionId}`,
76
- });
77
- return () => { };
78
- }
79
- let offset = statSync(filePath).size;
80
- let buffer = '';
81
- let reading = false; // guard against overlapping reads
82
- const interval = setInterval(() => {
83
- if (reading)
84
- return;
85
- try {
86
- const currentSize = statSync(filePath).size;
87
- if (currentSize < offset) {
88
- // File was truncated (rotation / compaction) — reset.
89
- offset = 0;
90
- buffer = '';
91
- }
92
- if (currentSize <= offset)
93
- return;
94
- reading = true;
95
- const stream = createReadStream(filePath, {
96
- start: offset,
97
- end: currentSize - 1,
98
- encoding: 'utf8',
99
- });
100
- let chunk = '';
101
- stream.on('data', (data) => {
102
- chunk += typeof data === 'string' ? data : data.toString('utf8');
103
- });
104
- stream.on('end', () => {
105
- reading = false;
106
- offset = currentSize;
107
- buffer += chunk;
108
- const lines = buffer.split('\n');
109
- // Keep the last (possibly incomplete) segment in the buffer.
110
- buffer = lines.pop() ?? '';
111
- for (const line of lines) {
112
- const trimmed = line.trim();
113
- if (!trimmed)
114
- continue;
115
- try {
116
- const parsed = JSON.parse(trimmed);
117
- onEvent({
118
- type: 'line',
119
- sessionId,
120
- data: parsed,
121
- rawLine: trimmed,
122
- });
123
- }
124
- catch {
125
- onEvent({
126
- type: 'line',
127
- sessionId,
128
- data: null,
129
- rawLine: trimmed,
130
- });
131
- }
132
- }
133
- });
134
- stream.on('error', (err) => {
135
- reading = false;
136
- onEvent({
137
- type: 'error',
138
- sessionId,
139
- error: err.message,
140
- });
141
- });
142
- }
143
- catch (err) {
144
- reading = false;
145
- onEvent({
146
- type: 'error',
147
- sessionId,
148
- error: err instanceof Error ? err.message : String(err),
149
- });
150
- }
151
- }, pollIntervalMs);
152
- this.activeTails.set(sessionId, interval);
153
- return () => this.stopTailing(sessionId);
154
- }
155
- /**
156
- * Stop tailing a specific session.
157
- */
158
- stopTailing(sessionId) {
159
- const interval = this.activeTails.get(sessionId);
160
- if (interval) {
161
- clearInterval(interval);
162
- this.activeTails.delete(sessionId);
163
- }
164
- }
165
- /**
166
- * Stop all active tails (cleanup on shutdown).
167
- */
168
- stopAll() {
169
- for (const [, interval] of this.activeTails) {
170
- clearInterval(interval);
171
- }
172
- this.activeTails.clear();
173
- }
174
- // ── Snapshot helpers ────────────────────────────────────────────────
175
- /**
176
- * Read the last N lines from a session JSONL file.
177
- */
178
- async getLastLines(sessionId, cwd, lineCount = 20) {
179
- const filePath = this.findSessionFile(sessionId, cwd);
180
- if (!filePath)
181
- return [];
182
- const lines = [];
183
- const rl = createInterface({
184
- input: createReadStream(filePath, { encoding: 'utf8' }),
185
- });
186
- for await (const line of rl) {
187
- lines.push(line);
188
- if (lines.length > lineCount) {
189
- lines.shift();
190
- }
191
- }
192
- return lines;
193
- }
194
- /**
195
- * Extract a preview of recent tool output from the tail of a session file.
196
- */
197
- async getToolOutputPreview(sessionId, cwd, maxEntries = 5) {
198
- const lastLines = await this.getLastLines(sessionId, cwd, 100);
199
- const previews = [];
200
- for (const line of lastLines) {
201
- const trimmed = line.trim();
202
- if (!trimmed)
203
- continue;
204
- try {
205
- const parsed = JSON.parse(trimmed);
206
- const preview = extractToolOutput(parsed);
207
- if (preview) {
208
- previews.push(preview);
209
- }
210
- }
211
- catch {
212
- // skip unparseable lines
213
- }
214
- }
215
- return previews.slice(-maxEntries);
216
- }
217
- }
218
- // ── Internal helpers ────────────────────────────────────────────────────
219
- /**
220
- * Attempt to extract tool output information from a JSONL record.
221
- *
222
- * Claude Code JSONL records with tool results can appear as:
223
- * 1. A top-level `{ type: "user", message: { content: [{ type: "tool_result", ... }] } }`
224
- * 2. Direct `tool_result` entries (less common).
225
- */
226
- function extractToolOutput(record) {
227
- // Pattern 1: user message wrapping tool_result content blocks
228
- if (record.type === 'user') {
229
- const message = record.message;
230
- if (!message)
231
- return null;
232
- const content = message.content;
233
- if (!Array.isArray(content))
234
- return null;
235
- for (const block of content) {
236
- if (block &&
237
- typeof block === 'object' &&
238
- block.type === 'tool_result') {
239
- const b = block;
240
- return {
241
- toolUseId: typeof b.tool_use_id === 'string' ? b.tool_use_id : '',
242
- content: stringifyContent(b.content),
243
- isError: b.is_error === true,
244
- };
245
- }
246
- }
247
- }
248
- // Pattern 2: direct tool_result record (varies by Claude Code version)
249
- if (record.type === 'tool_result') {
250
- return {
251
- toolUseId: typeof record.tool_use_id === 'string' ? record.tool_use_id : '',
252
- content: stringifyContent(record.content),
253
- isError: record.is_error === true,
254
- };
255
- }
256
- return null;
257
- }
258
- function stringifyContent(value) {
259
- if (typeof value === 'string')
260
- return value;
261
- if (value === undefined || value === null)
262
- return '';
263
- try {
264
- return JSON.stringify(value);
265
- }
266
- catch {
267
- return '[non-serializable]';
268
- }
269
- }
@@ -1,41 +0,0 @@
1
- import type { ClaudeAgentSdkAdapter, ClaudeSessionEventHandler } from '../claude/claude-agent-sdk-adapter.js';
2
- import type { ClaudeSessionRunResult, SessionContextSnapshot, SessionMode } from '../types/contracts.js';
3
- import type { ContextTracker } from './context-tracker.js';
4
- export declare class SessionController {
5
- private readonly sdkAdapter;
6
- private readonly contextTracker;
7
- private readonly sessionPrompt;
8
- private readonly wrapperType;
9
- private readonly worktree;
10
- private readonly modePrefixes;
11
- private activeSessionId;
12
- constructor(sdkAdapter: ClaudeAgentSdkAdapter, contextTracker: ContextTracker, sessionPrompt: string | undefined, wrapperType: string, worktree: string, modePrefixes?: {
13
- plan: string;
14
- free: string;
15
- });
16
- get isActive(): boolean;
17
- get sessionId(): string | null;
18
- /**
19
- * Send a message to the persistent session. Creates one if none exists.
20
- * Returns the session result including usage data.
21
- */
22
- sendMessage(message: string, options?: {
23
- model?: string;
24
- effort?: 'low' | 'medium' | 'high' | 'max';
25
- mode?: SessionMode;
26
- sessionSystemPrompt?: string;
27
- abortSignal?: AbortSignal;
28
- }, onEvent?: ClaudeSessionEventHandler): Promise<ClaudeSessionRunResult>;
29
- /**
30
- * Send /compact to the current session to compress context.
31
- */
32
- compactSession(onEvent?: ClaudeSessionEventHandler): Promise<ClaudeSessionRunResult>;
33
- /**
34
- * Clear the current session. The next sendMessage will create a fresh one.
35
- */
36
- clearSession(): Promise<string | null>;
37
- /**
38
- * Get current context tracking snapshot.
39
- */
40
- getContextSnapshot(): SessionContextSnapshot;
41
- }
@@ -1,97 +0,0 @@
1
- export class SessionController {
2
- sdkAdapter;
3
- contextTracker;
4
- sessionPrompt;
5
- wrapperType;
6
- worktree;
7
- modePrefixes;
8
- activeSessionId = null;
9
- constructor(sdkAdapter, contextTracker, sessionPrompt, wrapperType, worktree, modePrefixes = {
10
- plan: '',
11
- free: '',
12
- }) {
13
- this.sdkAdapter = sdkAdapter;
14
- this.contextTracker = contextTracker;
15
- this.sessionPrompt = sessionPrompt;
16
- this.wrapperType = wrapperType;
17
- this.worktree = worktree;
18
- this.modePrefixes = modePrefixes;
19
- }
20
- get isActive() {
21
- return this.activeSessionId !== null;
22
- }
23
- get sessionId() {
24
- return this.activeSessionId;
25
- }
26
- /**
27
- * Send a message to the persistent session. Creates one if none exists.
28
- * Returns the session result including usage data.
29
- */
30
- async sendMessage(message, options, onEvent) {
31
- const mode = options?.mode ?? 'free';
32
- const prefix = this.modePrefixes[mode];
33
- const prompt = prefix ? `${prefix}\n\n${message}` : message;
34
- const input = {
35
- cwd: this.worktree,
36
- prompt,
37
- persistSession: true,
38
- permissionMode: mode === 'plan' ? 'plan' : 'acceptEdits',
39
- includePartialMessages: true,
40
- model: options?.model,
41
- effort: options?.effort,
42
- settingSources: ['user'],
43
- abortSignal: options?.abortSignal,
44
- };
45
- if (this.activeSessionId) {
46
- // Resume existing session
47
- input.resumeSessionId = this.activeSessionId;
48
- }
49
- else {
50
- // New session — prefer dynamically constructed prompt from wrapper, fall back to static default
51
- input.systemPrompt = options?.sessionSystemPrompt ?? this.sessionPrompt;
52
- input.model ??= 'claude-opus-4-6';
53
- input.effort ??= 'high';
54
- }
55
- const result = await this.sdkAdapter.runSession(input, onEvent);
56
- // Track the session ID
57
- if (result.sessionId) {
58
- this.activeSessionId = result.sessionId;
59
- }
60
- // Update context tracking
61
- this.contextTracker.recordResult({
62
- sessionId: result.sessionId,
63
- turns: result.turns,
64
- totalCostUsd: result.totalCostUsd,
65
- inputTokens: result.inputTokens,
66
- outputTokens: result.outputTokens,
67
- contextWindowSize: result.contextWindowSize,
68
- });
69
- return result;
70
- }
71
- /**
72
- * Send /compact to the current session to compress context.
73
- */
74
- async compactSession(onEvent) {
75
- if (!this.activeSessionId) {
76
- throw new Error('No active session to compact');
77
- }
78
- const result = await this.sendMessage('/compact', undefined, onEvent);
79
- this.contextTracker.recordCompaction();
80
- return result;
81
- }
82
- /**
83
- * Clear the current session. The next sendMessage will create a fresh one.
84
- */
85
- async clearSession() {
86
- const clearedId = this.activeSessionId;
87
- this.activeSessionId = null;
88
- this.contextTracker.reset();
89
- return clearedId;
90
- }
91
- /**
92
- * Get current context tracking snapshot.
93
- */
94
- getContextSnapshot() {
95
- return this.contextTracker.snapshot();
96
- }
97
- }
@@ -1,12 +0,0 @@
1
- import type { ClaudeMetadataSnapshot, ClaudeSettingSource } from '../types/contracts.js';
2
- import type { ClaudeAgentSdkAdapter } from '../claude/claude-agent-sdk-adapter.js';
3
- import type { RepoClaudeConfigReader } from './repo-claude-config-reader.js';
4
- export declare class ClaudeMetadataService {
5
- private readonly configReader;
6
- private readonly sdkAdapter;
7
- constructor(configReader: RepoClaudeConfigReader, sdkAdapter: ClaudeAgentSdkAdapter);
8
- collect(cwd: string, options?: {
9
- includeSdkProbe?: boolean;
10
- settingSources?: ClaudeSettingSource[];
11
- }): Promise<ClaudeMetadataSnapshot>;
12
- }
@@ -1,38 +0,0 @@
1
- export class ClaudeMetadataService {
2
- configReader;
3
- sdkAdapter;
4
- constructor(configReader, sdkAdapter) {
5
- this.configReader = configReader;
6
- this.sdkAdapter = sdkAdapter;
7
- }
8
- async collect(cwd, options = {}) {
9
- const baseSnapshot = await this.configReader.read(cwd);
10
- if (!options.includeSdkProbe) {
11
- return dedupeSnapshot(baseSnapshot);
12
- }
13
- const capabilities = await this.sdkAdapter.probeCapabilities(cwd, options.settingSources);
14
- return dedupeSnapshot({
15
- ...baseSnapshot,
16
- commands: [...baseSnapshot.commands, ...capabilities.commands],
17
- agents: capabilities.agents,
18
- });
19
- }
20
- }
21
- function dedupeSnapshot(snapshot) {
22
- return {
23
- ...snapshot,
24
- commands: dedupeByName(snapshot.commands),
25
- skills: dedupeByName(snapshot.skills),
26
- hooks: dedupeByName(snapshot.hooks),
27
- agents: dedupeByName(snapshot.agents),
28
- };
29
- }
30
- function dedupeByName(items) {
31
- const seen = new Map();
32
- for (const item of items) {
33
- if (!seen.has(item.name)) {
34
- seen.set(item.name, item);
35
- }
36
- }
37
- return [...seen.values()].sort((left, right) => left.name.localeCompare(right.name));
38
- }
@@ -1,7 +0,0 @@
1
- import type { ClaudeMetadataSnapshot } from '../types/contracts.js';
2
- export declare class RepoClaudeConfigReader {
3
- read(cwd: string): Promise<ClaudeMetadataSnapshot>;
4
- private readSkills;
5
- private readCommands;
6
- private readSettings;
7
- }
@@ -1,154 +0,0 @@
1
- import { promises as fs } from 'node:fs';
2
- import path from 'node:path';
3
- import JSON5 from 'json5';
4
- export class RepoClaudeConfigReader {
5
- async read(cwd) {
6
- const claudeDirectory = path.join(cwd, '.claude');
7
- const skillsDirectory = path.join(claudeDirectory, 'skills');
8
- const commandsDirectory = path.join(claudeDirectory, 'commands');
9
- const claudeMdCandidates = [
10
- path.join(cwd, 'CLAUDE.md'),
11
- path.join(claudeDirectory, 'CLAUDE.md'),
12
- ];
13
- const collectedAt = new Date().toISOString();
14
- const [skills, commands, settingsResult, claudeMdPath] = await Promise.all([
15
- this.readSkills(skillsDirectory),
16
- this.readCommands(commandsDirectory),
17
- this.readSettings(claudeDirectory),
18
- findFirstExistingPath(claudeMdCandidates),
19
- ]);
20
- return {
21
- collectedAt,
22
- cwd,
23
- commands: [...skillsToCommands(skills), ...commands],
24
- skills,
25
- hooks: settingsResult.hooks,
26
- agents: [],
27
- claudeMdPath: claudeMdPath ?? undefined,
28
- settingsPaths: settingsResult.settingsPaths,
29
- };
30
- }
31
- async readSkills(directory) {
32
- if (!(await pathExists(directory))) {
33
- return [];
34
- }
35
- const entries = await fs.readdir(directory, { withFileTypes: true });
36
- const skills = await Promise.all(entries
37
- .filter((entry) => entry.isDirectory())
38
- .map(async (entry) => {
39
- const skillPath = path.join(directory, entry.name, 'SKILL.md');
40
- if (!(await pathExists(skillPath))) {
41
- return null;
42
- }
43
- const content = await fs.readFile(skillPath, 'utf8');
44
- return {
45
- name: entry.name,
46
- description: extractMarkdownDescription(content),
47
- path: skillPath,
48
- source: 'skill',
49
- };
50
- }));
51
- return skills.filter((skill) => skill !== null);
52
- }
53
- async readCommands(directory) {
54
- if (!(await pathExists(directory))) {
55
- return [];
56
- }
57
- const commandFiles = await collectMarkdownFiles(directory);
58
- const commands = await Promise.all(commandFiles.map(async (commandPath) => {
59
- const content = await fs.readFile(commandPath, 'utf8');
60
- return {
61
- name: path.basename(commandPath, path.extname(commandPath)),
62
- description: extractMarkdownDescription(content),
63
- source: 'command',
64
- path: commandPath,
65
- };
66
- }));
67
- return commands.sort((left, right) => left.name.localeCompare(right.name));
68
- }
69
- async readSettings(claudeDirectory) {
70
- const candidatePaths = [
71
- path.join(claudeDirectory, 'settings.json'),
72
- path.join(claudeDirectory, 'settings.local.json'),
73
- ];
74
- const settingsPaths = [];
75
- const hooks = [];
76
- for (const candidatePath of candidatePaths) {
77
- if (!(await pathExists(candidatePath))) {
78
- continue;
79
- }
80
- settingsPaths.push(candidatePath);
81
- const content = await fs.readFile(candidatePath, 'utf8');
82
- const parsed = JSON5.parse(content);
83
- const hookEntries = Object.entries(parsed.hooks ?? {});
84
- for (const [hookName, hookValue] of hookEntries) {
85
- const hookMatchers = Array.isArray(hookValue) ? hookValue : [hookValue];
86
- for (const hookMatcher of hookMatchers) {
87
- if (!hookMatcher || typeof hookMatcher !== 'object') {
88
- continue;
89
- }
90
- const matcher = typeof hookMatcher.matcher === 'string'
91
- ? hookMatcher.matcher
92
- : undefined;
93
- const commandCount = Array.isArray(hookMatcher.hooks)
94
- ? (hookMatcher.hooks?.length ?? 0)
95
- : 0;
96
- hooks.push({
97
- name: hookName,
98
- matcher,
99
- sourcePath: candidatePath,
100
- commandCount,
101
- });
102
- }
103
- }
104
- }
105
- return {
106
- settingsPaths,
107
- hooks,
108
- };
109
- }
110
- }
111
- function extractMarkdownDescription(markdown) {
112
- const lines = markdown
113
- .split(/\r?\n/)
114
- .map((line) => line.trim())
115
- .filter(Boolean);
116
- const descriptionLine = lines.find((line) => !line.startsWith('#') && !line.startsWith('---'));
117
- return descriptionLine ?? 'No description provided.';
118
- }
119
- async function collectMarkdownFiles(directory) {
120
- const entries = await fs.readdir(directory, { withFileTypes: true });
121
- const files = await Promise.all(entries.map(async (entry) => {
122
- const resolvedPath = path.join(directory, entry.name);
123
- if (entry.isDirectory()) {
124
- return collectMarkdownFiles(resolvedPath);
125
- }
126
- return entry.name.endsWith('.md') ? [resolvedPath] : [];
127
- }));
128
- return files.flat();
129
- }
130
- async function pathExists(candidatePath) {
131
- try {
132
- await fs.access(candidatePath);
133
- return true;
134
- }
135
- catch {
136
- return false;
137
- }
138
- }
139
- async function findFirstExistingPath(candidatePaths) {
140
- for (const candidatePath of candidatePaths) {
141
- if (await pathExists(candidatePath)) {
142
- return candidatePath;
143
- }
144
- }
145
- return null;
146
- }
147
- function skillsToCommands(skills) {
148
- return skills.map((skill) => ({
149
- name: skill.name,
150
- description: skill.description,
151
- source: 'skill',
152
- path: skill.path,
153
- }));
154
- }
@@ -1,2 +0,0 @@
1
- import type { Plugin } from '@opencode-ai/plugin';
2
- export declare const OrchestratorPlugin: Plugin;