Repository navigation
Expand file tree
/
Copy pathentity.ts
More file actions
122 lines (103 loc) · 3.63 KB
/
Copy pathentity.ts
File metadata and controls
122 lines (103 loc) · 3.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
export type EntityKind = 'skill' | 'agent' | 'mcp' | 'instruction' | 'hook';
export type Scope = 'personal' | 'project' | 'workspace';
export type EntityProvenance = 'workspace-managed' | 'claude-code';
export type EntitySource =
| { kind: 'workspace' }
| { kind: 'plugin'; pluginId: string; provenance: EntityProvenance };
export const WORKSPACE_SOURCE: EntitySource = { kind: 'workspace' };
export function isPluginSource(
source: EntitySource,
): source is { kind: 'plugin'; pluginId: string; provenance: EntityProvenance } {
return source.kind === 'plugin';
}
export function isWorkspaceSource(source: EntitySource): source is { kind: 'workspace' } {
return source.kind === 'workspace';
}
export interface EntityMetadata {
version: string;
tags?: string[];
createdAt: string;
updatedAt: string;
}
export interface Entity {
urn: string;
kind: EntityKind;
name: string;
description: string;
scopes: Scope[];
scopeId?: string;
metadata: EntityMetadata;
source: EntitySource;
ext?: Record<string, unknown>;
}
export interface Skill extends Entity {
kind: 'skill';
content: string;
explicitOnly?: boolean;
}
export interface Agent extends Entity {
kind: 'agent';
systemPrompt: string;
model?: string;
tools?: string[];
deniedTools?: string[];
}
/**
* `name === 'default'` and `scopes === ['personal']` identify the personal
* singleton; any other instruction carries `scopes[0] === 'project' | 'workspace'`
* and a `scopeId` resolved against `Project.id` / `Workspace.id` (see
* `resolveScopePath`). `legacyRepoPath` is set only on read, only for
* pre-migration on-disk data that predates `scopeId` — see
* `InstructionService.get`/`.list`.
*/
export interface Instruction extends Entity {
kind: 'instruction';
content: string;
legacyRepoPath?: string;
}
export function isPersonalInstruction(entity: Instruction): boolean {
return entity.scopes[0] === 'personal';
}
export function isProjectInstruction(entity: Instruction): boolean {
return entity.scopes[0] === 'project';
}
export function isWorkspaceInstruction(entity: Instruction): boolean {
return entity.scopes[0] === 'workspace';
}
/**
* Sidecar metadata for instructions. Instructions are stored frontmatter-free
* on disk so the sync target (AGENTS.md, CLAUDE.md) is a clean body — this
* struct captures everything else (description, version, timestamps, and the
* per-project repoPath) and lives in a separate `meta.json` for project
* instructions. Personal instructions default this struct in memory.
*/
export interface InstructionSidecar {
description: string;
version: string;
tags?: string[];
createdAt: string;
updatedAt: string;
/** Current shape (post scopeId migration). */
scope?: 'project' | 'workspace';
scopeId?: string;
/** Legacy shape — read-only. Never written again after the scopeId migration lands. */
repoPath?: string;
}
export interface EntityChangedEvent {
kind: EntityKind;
urn: string;
}
/** Push channel main→renderer, fired when a workspace's file watcher re-syncs an entity edited outside the app's own save() flow (see `docs/reference/ipc-contract.md#push-channels-exception-to-requestresponse`). */
export const ENTITY_CHANGED_CHANNEL = 'entity:changed' as const;
export function entityUrn(kind: EntityKind, name: string): string {
return `urn:${kind}:${name}`;
}
export function parseUrn(urn: string): { kind: EntityKind; name: string } {
const match = /^urn:([a-z]+):(.+)$/.exec(urn);
const kind = match?.[1];
const name = match?.[2];
if (kind === undefined || name === undefined) {
throw new Error(`Invalid URN: ${urn}`);
}
return { kind: kind as EntityKind, name };
}