@doingdev/opencode-claude-manager-plugin 0.1.35 → 0.1.43
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/dist/claude/claude-agent-sdk-adapter.js +1 -0
- package/dist/manager/git-operations.d.ts +10 -1
- package/dist/manager/git-operations.js +18 -3
- package/dist/manager/persistent-manager.d.ts +19 -3
- package/dist/manager/persistent-manager.js +21 -9
- package/dist/manager/session-controller.d.ts +8 -5
- package/dist/manager/session-controller.js +25 -20
- package/dist/metadata/claude-metadata.service.d.ts +12 -0
- package/dist/metadata/claude-metadata.service.js +38 -0
- package/dist/metadata/repo-claude-config-reader.d.ts +7 -0
- package/dist/metadata/repo-claude-config-reader.js +154 -0
- package/dist/plugin/agent-hierarchy.d.ts +9 -9
- package/dist/plugin/agent-hierarchy.js +25 -25
- package/dist/plugin/claude-manager.plugin.js +83 -46
- package/dist/plugin/orchestrator.plugin.d.ts +2 -0
- package/dist/plugin/orchestrator.plugin.js +116 -0
- package/dist/plugin/service-factory.js +3 -8
- package/dist/prompts/registry.js +100 -103
- package/dist/providers/claude-code-wrapper.d.ts +13 -0
- package/dist/providers/claude-code-wrapper.js +13 -0
- package/dist/safety/bash-safety.d.ts +21 -0
- package/dist/safety/bash-safety.js +62 -0
- package/dist/src/claude/claude-agent-sdk-adapter.d.ts +27 -0
- package/dist/src/claude/claude-agent-sdk-adapter.js +517 -0
- package/dist/src/claude/claude-session.service.d.ts +10 -0
- package/dist/src/claude/claude-session.service.js +18 -0
- package/dist/src/claude/session-live-tailer.d.ts +51 -0
- package/dist/src/claude/session-live-tailer.js +269 -0
- package/dist/src/claude/tool-approval-manager.d.ts +27 -0
- package/dist/src/claude/tool-approval-manager.js +232 -0
- package/dist/src/index.d.ts +6 -0
- package/dist/src/index.js +4 -0
- package/dist/src/manager/context-tracker.d.ts +33 -0
- package/dist/src/manager/context-tracker.js +106 -0
- package/dist/src/manager/git-operations.d.ts +12 -0
- package/dist/src/manager/git-operations.js +76 -0
- package/dist/src/manager/persistent-manager.d.ts +77 -0
- package/dist/src/manager/persistent-manager.js +170 -0
- package/dist/src/manager/session-controller.d.ts +44 -0
- package/dist/src/manager/session-controller.js +147 -0
- package/dist/src/plugin/agent-hierarchy.d.ts +60 -0
- package/dist/src/plugin/agent-hierarchy.js +157 -0
- package/dist/src/plugin/claude-manager.plugin.d.ts +2 -0
- package/dist/src/plugin/claude-manager.plugin.js +563 -0
- package/dist/src/plugin/service-factory.d.ts +12 -0
- package/dist/src/plugin/service-factory.js +38 -0
- package/dist/src/prompts/registry.d.ts +11 -0
- package/dist/src/prompts/registry.js +260 -0
- package/dist/src/state/file-run-state-store.d.ts +14 -0
- package/dist/src/state/file-run-state-store.js +85 -0
- package/dist/src/state/transcript-store.d.ts +15 -0
- package/dist/src/state/transcript-store.js +44 -0
- package/dist/src/types/contracts.d.ts +200 -0
- package/dist/src/types/contracts.js +1 -0
- package/dist/src/util/fs-helpers.d.ts +2 -0
- package/dist/src/util/fs-helpers.js +10 -0
- package/dist/src/util/project-context.d.ts +10 -0
- package/dist/src/util/project-context.js +105 -0
- package/dist/src/util/transcript-append.d.ts +7 -0
- package/dist/src/util/transcript-append.js +29 -0
- package/dist/test/claude-agent-sdk-adapter.test.d.ts +1 -0
- package/dist/test/claude-agent-sdk-adapter.test.js +459 -0
- package/dist/test/claude-manager.plugin.test.d.ts +1 -0
- package/dist/test/claude-manager.plugin.test.js +331 -0
- package/dist/test/context-tracker.test.d.ts +1 -0
- package/dist/test/context-tracker.test.js +138 -0
- package/dist/test/file-run-state-store.test.d.ts +1 -0
- package/dist/test/file-run-state-store.test.js +82 -0
- package/dist/test/git-operations.test.d.ts +1 -0
- package/dist/test/git-operations.test.js +90 -0
- package/dist/test/persistent-manager.test.d.ts +1 -0
- package/dist/test/persistent-manager.test.js +208 -0
- package/dist/test/project-context.test.d.ts +1 -0
- package/dist/test/project-context.test.js +92 -0
- package/dist/test/prompt-registry.test.d.ts +1 -0
- package/dist/test/prompt-registry.test.js +256 -0
- package/dist/test/session-controller.test.d.ts +1 -0
- package/dist/test/session-controller.test.js +149 -0
- package/dist/test/session-live-tailer.test.d.ts +1 -0
- package/dist/test/session-live-tailer.test.js +313 -0
- package/dist/test/tool-approval-manager.test.d.ts +1 -0
- package/dist/test/tool-approval-manager.test.js +264 -0
- package/dist/test/transcript-append.test.d.ts +1 -0
- package/dist/test/transcript-append.test.js +37 -0
- package/dist/test/transcript-store.test.d.ts +1 -0
- package/dist/test/transcript-store.test.js +50 -0
- package/dist/types/contracts.d.ts +3 -4
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +11 -0
- package/package.json +2 -2
|
@@ -162,6 +162,7 @@ export class ClaudeAgentSdkAdapter {
|
|
|
162
162
|
forkSession: input.forkSession,
|
|
163
163
|
persistSession: input.persistSession,
|
|
164
164
|
includePartialMessages: input.includePartialMessages,
|
|
165
|
+
enableFileCheckpointing: false,
|
|
165
166
|
settingSources: input.settingSources,
|
|
166
167
|
maxTurns: input.maxTurns,
|
|
167
168
|
model: input.model,
|
|
@@ -2,10 +2,19 @@ import type { GitDiffResult, GitOperationResult } from '../types/contracts.js';
|
|
|
2
2
|
export declare class GitOperations {
|
|
3
3
|
private readonly cwd;
|
|
4
4
|
constructor(cwd: string);
|
|
5
|
-
diff(
|
|
5
|
+
diff(options?: {
|
|
6
|
+
paths?: string[];
|
|
7
|
+
staged?: boolean;
|
|
8
|
+
ref?: string;
|
|
9
|
+
}): Promise<GitDiffResult>;
|
|
6
10
|
diffStat(): Promise<string>;
|
|
7
11
|
commit(message: string): Promise<GitOperationResult>;
|
|
8
12
|
resetHard(): Promise<GitOperationResult>;
|
|
13
|
+
status(): Promise<{
|
|
14
|
+
output: string;
|
|
15
|
+
isClean: boolean;
|
|
16
|
+
}>;
|
|
17
|
+
log(count?: number): Promise<string>;
|
|
9
18
|
currentBranch(): Promise<string>;
|
|
10
19
|
recentCommits(count?: number): Promise<string>;
|
|
11
20
|
private git;
|
|
@@ -6,10 +6,15 @@ export class GitOperations {
|
|
|
6
6
|
constructor(cwd) {
|
|
7
7
|
this.cwd = cwd;
|
|
8
8
|
}
|
|
9
|
-
async diff() {
|
|
9
|
+
async diff(options = {}) {
|
|
10
|
+
const ref = options.staged ? '--cached' : (options.ref ?? 'HEAD');
|
|
11
|
+
const args = ['diff', ref];
|
|
12
|
+
if (options.paths && options.paths.length > 0) {
|
|
13
|
+
args.push('--', ...options.paths);
|
|
14
|
+
}
|
|
10
15
|
const [diffText, statOutput] = await Promise.all([
|
|
11
|
-
this.git(
|
|
12
|
-
this.git([
|
|
16
|
+
this.git(args),
|
|
17
|
+
this.git([...args, '--stat']),
|
|
13
18
|
]);
|
|
14
19
|
const stats = parseStatLine(statOutput);
|
|
15
20
|
return {
|
|
@@ -50,6 +55,16 @@ export class GitOperations {
|
|
|
50
55
|
};
|
|
51
56
|
}
|
|
52
57
|
}
|
|
58
|
+
async status() {
|
|
59
|
+
const output = await this.git(['status', '-s']);
|
|
60
|
+
return {
|
|
61
|
+
output,
|
|
62
|
+
isClean: output.trim().length === 0,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async log(count = 5) {
|
|
66
|
+
return this.git(['log', '--oneline', `-${count}`]);
|
|
67
|
+
}
|
|
53
68
|
async currentBranch() {
|
|
54
69
|
const branch = await this.git(['rev-parse', '--abbrev-ref', 'HEAD']);
|
|
55
70
|
return branch.trim();
|
|
@@ -21,6 +21,7 @@ export declare class PersistentManager {
|
|
|
21
21
|
model?: string;
|
|
22
22
|
effort?: 'low' | 'medium' | 'high' | 'max';
|
|
23
23
|
mode?: 'plan' | 'free';
|
|
24
|
+
sessionSystemPrompt?: string;
|
|
24
25
|
abortSignal?: AbortSignal;
|
|
25
26
|
}, onEvent?: ClaudeSessionEventHandler): Promise<{
|
|
26
27
|
sessionId: string | undefined;
|
|
@@ -35,11 +36,26 @@ export declare class PersistentManager {
|
|
|
35
36
|
/**
|
|
36
37
|
* Get the current git diff.
|
|
37
38
|
*/
|
|
38
|
-
gitDiff(
|
|
39
|
+
gitDiff(options?: {
|
|
40
|
+
paths?: string[];
|
|
41
|
+
staged?: boolean;
|
|
42
|
+
ref?: string;
|
|
43
|
+
}): Promise<GitDiffResult>;
|
|
39
44
|
/**
|
|
40
45
|
* Commit all current changes.
|
|
41
46
|
*/
|
|
42
47
|
gitCommit(message: string): Promise<GitOperationResult>;
|
|
48
|
+
/**
|
|
49
|
+
* Get git status summary.
|
|
50
|
+
*/
|
|
51
|
+
gitStatus(): Promise<{
|
|
52
|
+
output: string;
|
|
53
|
+
isClean: boolean;
|
|
54
|
+
}>;
|
|
55
|
+
/**
|
|
56
|
+
* Get recent commit log.
|
|
57
|
+
*/
|
|
58
|
+
gitLog(count?: number): Promise<string>;
|
|
43
59
|
/**
|
|
44
60
|
* Hard reset to discard all uncommitted changes.
|
|
45
61
|
*/
|
|
@@ -51,7 +67,7 @@ export declare class PersistentManager {
|
|
|
51
67
|
/**
|
|
52
68
|
* Clear the active session. Next send creates a fresh one.
|
|
53
69
|
*/
|
|
54
|
-
clearSession(
|
|
70
|
+
clearSession(): Promise<string | null>;
|
|
55
71
|
/**
|
|
56
72
|
* Compact the current session to free context.
|
|
57
73
|
*/
|
|
@@ -70,7 +86,7 @@ export declare class PersistentManager {
|
|
|
70
86
|
/**
|
|
71
87
|
* Try to restore session state from disk on startup.
|
|
72
88
|
*/
|
|
73
|
-
tryRestore(
|
|
89
|
+
tryRestore(): Promise<boolean>;
|
|
74
90
|
listRuns(cwd: string): Promise<PersistentRunRecord[]>;
|
|
75
91
|
getRun(cwd: string, runId: string): Promise<PersistentRunRecord | null>;
|
|
76
92
|
}
|
|
@@ -17,7 +17,7 @@ export class PersistentManager {
|
|
|
17
17
|
* Creates a new session if none exists.
|
|
18
18
|
*/
|
|
19
19
|
async sendMessage(cwd, message, options, onEvent) {
|
|
20
|
-
const result = await this.sessionController.sendMessage(
|
|
20
|
+
const result = await this.sessionController.sendMessage(message, options, onEvent);
|
|
21
21
|
if (result.sessionId && result.events.length > 0) {
|
|
22
22
|
await this.transcriptStore.appendEvents(cwd, result.sessionId, result.events);
|
|
23
23
|
}
|
|
@@ -35,8 +35,8 @@ export class PersistentManager {
|
|
|
35
35
|
/**
|
|
36
36
|
* Get the current git diff.
|
|
37
37
|
*/
|
|
38
|
-
async gitDiff() {
|
|
39
|
-
return this.gitOps.diff();
|
|
38
|
+
async gitDiff(options = {}) {
|
|
39
|
+
return this.gitOps.diff(options);
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
42
|
* Commit all current changes.
|
|
@@ -44,6 +44,18 @@ export class PersistentManager {
|
|
|
44
44
|
async gitCommit(message) {
|
|
45
45
|
return this.gitOps.commit(message);
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Get git status summary.
|
|
49
|
+
*/
|
|
50
|
+
async gitStatus() {
|
|
51
|
+
return this.gitOps.status();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get recent commit log.
|
|
55
|
+
*/
|
|
56
|
+
async gitLog(count = 5) {
|
|
57
|
+
return this.gitOps.log(count);
|
|
58
|
+
}
|
|
47
59
|
/**
|
|
48
60
|
* Hard reset to discard all uncommitted changes.
|
|
49
61
|
*/
|
|
@@ -59,14 +71,14 @@ export class PersistentManager {
|
|
|
59
71
|
/**
|
|
60
72
|
* Clear the active session. Next send creates a fresh one.
|
|
61
73
|
*/
|
|
62
|
-
async clearSession(
|
|
63
|
-
return this.sessionController.clearSession(
|
|
74
|
+
async clearSession() {
|
|
75
|
+
return this.sessionController.clearSession();
|
|
64
76
|
}
|
|
65
77
|
/**
|
|
66
78
|
* Compact the current session to free context.
|
|
67
79
|
*/
|
|
68
80
|
async compactSession(cwd, onEvent) {
|
|
69
|
-
const result = await this.sessionController.compactSession(
|
|
81
|
+
const result = await this.sessionController.compactSession(onEvent);
|
|
70
82
|
if (result.sessionId && result.events.length > 0) {
|
|
71
83
|
await this.transcriptStore.appendEvents(cwd, result.sessionId, result.events);
|
|
72
84
|
}
|
|
@@ -108,7 +120,7 @@ export class PersistentManager {
|
|
|
108
120
|
await this.stateStore.saveRun(runRecord);
|
|
109
121
|
await onProgress?.(runRecord);
|
|
110
122
|
try {
|
|
111
|
-
const result = await this.sessionController.sendMessage(
|
|
123
|
+
const result = await this.sessionController.sendMessage(task, options, async (event) => {
|
|
112
124
|
// Update run record with progress events
|
|
113
125
|
const currentRun = await this.stateStore.getRun(cwd, runId);
|
|
114
126
|
if (currentRun) {
|
|
@@ -158,8 +170,8 @@ export class PersistentManager {
|
|
|
158
170
|
/**
|
|
159
171
|
* Try to restore session state from disk on startup.
|
|
160
172
|
*/
|
|
161
|
-
async tryRestore(
|
|
162
|
-
return this.sessionController.tryRestore(
|
|
173
|
+
async tryRestore() {
|
|
174
|
+
return this.sessionController.tryRestore();
|
|
163
175
|
}
|
|
164
176
|
listRuns(cwd) {
|
|
165
177
|
return this.stateStore.listRuns(cwd);
|
|
@@ -5,9 +5,11 @@ export declare class SessionController {
|
|
|
5
5
|
private readonly sdkAdapter;
|
|
6
6
|
private readonly contextTracker;
|
|
7
7
|
private readonly sessionPrompt;
|
|
8
|
+
private readonly wrapperType;
|
|
9
|
+
private readonly worktree;
|
|
8
10
|
private readonly modePrefixes;
|
|
9
11
|
private activeSessionId;
|
|
10
|
-
constructor(sdkAdapter: ClaudeAgentSdkAdapter, contextTracker: ContextTracker, sessionPrompt: string, modePrefixes?: {
|
|
12
|
+
constructor(sdkAdapter: ClaudeAgentSdkAdapter, contextTracker: ContextTracker, sessionPrompt: string | undefined, wrapperType: string, worktree: string, modePrefixes?: {
|
|
11
13
|
plan: string;
|
|
12
14
|
free: string;
|
|
13
15
|
});
|
|
@@ -17,20 +19,21 @@ export declare class SessionController {
|
|
|
17
19
|
* Send a message to the persistent session. Creates one if none exists.
|
|
18
20
|
* Returns the session result including usage data.
|
|
19
21
|
*/
|
|
20
|
-
sendMessage(
|
|
22
|
+
sendMessage(message: string, options?: {
|
|
21
23
|
model?: string;
|
|
22
24
|
effort?: 'low' | 'medium' | 'high' | 'max';
|
|
23
25
|
mode?: SessionMode;
|
|
26
|
+
sessionSystemPrompt?: string;
|
|
24
27
|
abortSignal?: AbortSignal;
|
|
25
28
|
}, onEvent?: ClaudeSessionEventHandler): Promise<ClaudeSessionRunResult>;
|
|
26
29
|
/**
|
|
27
30
|
* Send /compact to the current session to compress context.
|
|
28
31
|
*/
|
|
29
|
-
compactSession(
|
|
32
|
+
compactSession(onEvent?: ClaudeSessionEventHandler): Promise<ClaudeSessionRunResult>;
|
|
30
33
|
/**
|
|
31
34
|
* Clear the current session. The next sendMessage will create a fresh one.
|
|
32
35
|
*/
|
|
33
|
-
clearSession(
|
|
36
|
+
clearSession(): Promise<string | null>;
|
|
34
37
|
/**
|
|
35
38
|
* Get current context tracking snapshot.
|
|
36
39
|
*/
|
|
@@ -38,7 +41,7 @@ export declare class SessionController {
|
|
|
38
41
|
/**
|
|
39
42
|
* Try to restore active session from persisted state on startup.
|
|
40
43
|
*/
|
|
41
|
-
tryRestore(
|
|
44
|
+
tryRestore(): Promise<boolean>;
|
|
42
45
|
private persistActiveSession;
|
|
43
46
|
private removeActiveSession;
|
|
44
47
|
}
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
|
-
import { dirname
|
|
3
|
-
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
function activeSessionFile(_wrapperType) {
|
|
4
|
+
return `.claude-manager/active-session.json`;
|
|
5
|
+
}
|
|
4
6
|
export class SessionController {
|
|
5
7
|
sdkAdapter;
|
|
6
8
|
contextTracker;
|
|
7
9
|
sessionPrompt;
|
|
10
|
+
wrapperType;
|
|
11
|
+
worktree;
|
|
8
12
|
modePrefixes;
|
|
9
13
|
activeSessionId = null;
|
|
10
|
-
constructor(sdkAdapter, contextTracker, sessionPrompt, modePrefixes = {
|
|
14
|
+
constructor(sdkAdapter, contextTracker, sessionPrompt, wrapperType, worktree, modePrefixes = {
|
|
11
15
|
plan: '',
|
|
12
16
|
free: '',
|
|
13
17
|
}) {
|
|
14
18
|
this.sdkAdapter = sdkAdapter;
|
|
15
19
|
this.contextTracker = contextTracker;
|
|
16
20
|
this.sessionPrompt = sessionPrompt;
|
|
21
|
+
this.wrapperType = wrapperType;
|
|
22
|
+
this.worktree = worktree;
|
|
17
23
|
this.modePrefixes = modePrefixes;
|
|
18
24
|
}
|
|
19
25
|
get isActive() {
|
|
@@ -26,12 +32,12 @@ export class SessionController {
|
|
|
26
32
|
* Send a message to the persistent session. Creates one if none exists.
|
|
27
33
|
* Returns the session result including usage data.
|
|
28
34
|
*/
|
|
29
|
-
async sendMessage(
|
|
35
|
+
async sendMessage(message, options, onEvent) {
|
|
30
36
|
const mode = options?.mode ?? 'free';
|
|
31
37
|
const prefix = this.modePrefixes[mode];
|
|
32
38
|
const prompt = prefix ? `${prefix}\n\n${message}` : message;
|
|
33
39
|
const input = {
|
|
34
|
-
cwd,
|
|
40
|
+
cwd: this.worktree,
|
|
35
41
|
prompt,
|
|
36
42
|
persistSession: true,
|
|
37
43
|
permissionMode: mode === 'plan' ? 'plan' : 'acceptEdits',
|
|
@@ -46,8 +52,8 @@ export class SessionController {
|
|
|
46
52
|
input.resumeSessionId = this.activeSessionId;
|
|
47
53
|
}
|
|
48
54
|
else {
|
|
49
|
-
// New session —
|
|
50
|
-
input.systemPrompt = this.sessionPrompt;
|
|
55
|
+
// New session — prefer dynamically constructed prompt from wrapper, fall back to static default
|
|
56
|
+
input.systemPrompt = options?.sessionSystemPrompt ?? this.sessionPrompt;
|
|
51
57
|
input.model ??= 'claude-opus-4-6';
|
|
52
58
|
input.effort ??= 'high';
|
|
53
59
|
}
|
|
@@ -66,28 +72,28 @@ export class SessionController {
|
|
|
66
72
|
contextWindowSize: result.contextWindowSize,
|
|
67
73
|
});
|
|
68
74
|
// Persist active session state
|
|
69
|
-
await this.persistActiveSession(
|
|
75
|
+
await this.persistActiveSession();
|
|
70
76
|
return result;
|
|
71
77
|
}
|
|
72
78
|
/**
|
|
73
79
|
* Send /compact to the current session to compress context.
|
|
74
80
|
*/
|
|
75
|
-
async compactSession(
|
|
81
|
+
async compactSession(onEvent) {
|
|
76
82
|
if (!this.activeSessionId) {
|
|
77
83
|
throw new Error('No active session to compact');
|
|
78
84
|
}
|
|
79
|
-
const result = await this.sendMessage(
|
|
85
|
+
const result = await this.sendMessage('/compact', undefined, onEvent);
|
|
80
86
|
this.contextTracker.recordCompaction();
|
|
81
87
|
return result;
|
|
82
88
|
}
|
|
83
89
|
/**
|
|
84
90
|
* Clear the current session. The next sendMessage will create a fresh one.
|
|
85
91
|
*/
|
|
86
|
-
async clearSession(
|
|
92
|
+
async clearSession() {
|
|
87
93
|
const clearedId = this.activeSessionId;
|
|
88
94
|
this.activeSessionId = null;
|
|
89
95
|
this.contextTracker.reset();
|
|
90
|
-
await this.removeActiveSession(
|
|
96
|
+
await this.removeActiveSession();
|
|
91
97
|
return clearedId;
|
|
92
98
|
}
|
|
93
99
|
/**
|
|
@@ -99,12 +105,12 @@ export class SessionController {
|
|
|
99
105
|
/**
|
|
100
106
|
* Try to restore active session from persisted state on startup.
|
|
101
107
|
*/
|
|
102
|
-
async tryRestore(
|
|
103
|
-
const filePath =
|
|
108
|
+
async tryRestore() {
|
|
109
|
+
const filePath = activeSessionFile(this.wrapperType);
|
|
104
110
|
try {
|
|
105
111
|
const raw = await readFile(filePath, 'utf-8');
|
|
106
112
|
const state = JSON.parse(raw);
|
|
107
|
-
if (state.sessionId
|
|
113
|
+
if (state.sessionId) {
|
|
108
114
|
this.activeSessionId = state.sessionId;
|
|
109
115
|
this.contextTracker.restore(state);
|
|
110
116
|
return true;
|
|
@@ -115,14 +121,13 @@ export class SessionController {
|
|
|
115
121
|
}
|
|
116
122
|
return false;
|
|
117
123
|
}
|
|
118
|
-
async persistActiveSession(
|
|
124
|
+
async persistActiveSession() {
|
|
119
125
|
if (!this.activeSessionId) {
|
|
120
126
|
return;
|
|
121
127
|
}
|
|
122
128
|
const snap = this.contextTracker.snapshot();
|
|
123
129
|
const state = {
|
|
124
130
|
sessionId: this.activeSessionId,
|
|
125
|
-
cwd,
|
|
126
131
|
startedAt: new Date().toISOString(),
|
|
127
132
|
totalTurns: snap.totalTurns,
|
|
128
133
|
totalCostUsd: snap.totalCostUsd,
|
|
@@ -130,12 +135,12 @@ export class SessionController {
|
|
|
130
135
|
contextWindowSize: snap.contextWindowSize,
|
|
131
136
|
latestInputTokens: snap.latestInputTokens,
|
|
132
137
|
};
|
|
133
|
-
const filePath =
|
|
138
|
+
const filePath = activeSessionFile(this.wrapperType);
|
|
134
139
|
await mkdir(dirname(filePath), { recursive: true });
|
|
135
140
|
await writeFile(filePath, JSON.stringify(state, null, 2));
|
|
136
141
|
}
|
|
137
|
-
async removeActiveSession(
|
|
138
|
-
const filePath =
|
|
142
|
+
async removeActiveSession() {
|
|
143
|
+
const filePath = activeSessionFile(this.wrapperType);
|
|
139
144
|
try {
|
|
140
145
|
const { unlink } = await import('node:fs/promises');
|
|
141
146
|
await unlink(filePath);
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
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,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent hierarchy configuration for the CTO + Engineer Wrapper architecture.
|
|
3
3
|
*
|
|
4
|
-
* CTO (cto)
|
|
5
|
-
* Engineer
|
|
6
|
-
* Engineer
|
|
7
|
-
* Claude Code session
|
|
4
|
+
* CTO (cto) — pure orchestrator, spawns engineers, reviews diffs, commits
|
|
5
|
+
* Engineer Explore (engineer_explore) — manages a Claude Code session for read-only investigation
|
|
6
|
+
* Engineer Implement (engineer_implement) — manages a Claude Code session for implementation
|
|
7
|
+
* Claude Code session — the underlying AI session (prompt only, no OpenCode agent)
|
|
8
8
|
*/
|
|
9
9
|
import type { ManagerPromptRegistry } from '../types/contracts.js';
|
|
10
10
|
export declare const AGENT_CTO = "cto";
|
|
11
|
-
export declare const
|
|
12
|
-
export declare const
|
|
11
|
+
export declare const AGENT_ENGINEER_EXPLORE = "engineer_explore";
|
|
12
|
+
export declare const AGENT_ENGINEER_IMPLEMENT = "engineer_implement";
|
|
13
13
|
/** All restricted tool IDs (union of all domain groups) */
|
|
14
|
-
export declare const ALL_RESTRICTED_TOOL_IDS: readonly ["explore", "implement", "compact_context", "clear_session", "session_health", "list_transcripts", "list_history", "git_diff", "git_commit", "git_reset", "approval_policy", "approval_decisions", "approval_update"];
|
|
14
|
+
export declare const ALL_RESTRICTED_TOOL_IDS: readonly ["explore", "implement", "compact_context", "clear_session", "session_health", "list_transcripts", "list_history", "git_diff", "git_commit", "git_reset", "git_status", "git_log", "approval_policy", "approval_decisions", "approval_update"];
|
|
15
15
|
type ToolPermission = 'allow' | 'ask' | 'deny';
|
|
16
16
|
type AgentPermission = {
|
|
17
17
|
'*'?: ToolPermission;
|
|
@@ -41,14 +41,14 @@ export declare function buildCtoAgentConfig(prompts: ManagerPromptRegistry): {
|
|
|
41
41
|
permission: AgentPermission;
|
|
42
42
|
prompt: string;
|
|
43
43
|
};
|
|
44
|
-
export declare function
|
|
44
|
+
export declare function buildEngineerExploreAgentConfig(prompts: ManagerPromptRegistry): {
|
|
45
45
|
description: string;
|
|
46
46
|
mode: "subagent";
|
|
47
47
|
color: string;
|
|
48
48
|
permission: AgentPermission;
|
|
49
49
|
prompt: string;
|
|
50
50
|
};
|
|
51
|
-
export declare function
|
|
51
|
+
export declare function buildEngineerImplementAgentConfig(prompts: ManagerPromptRegistry): {
|
|
52
52
|
description: string;
|
|
53
53
|
mode: "subagent";
|
|
54
54
|
color: string;
|