@pruddiman/hem 0.0.1-beta-1ec07ab → 0.0.1-beta-18c9997
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/index.js +0 -0
- package/dist/providers/copilot.d.ts +27 -7
- package/dist/providers/copilot.js +17 -10
- package/package.json +1 -2
- package/dist/agents/grouping-agent.d.ts +0 -167
- package/dist/agents/grouping-agent.js +0 -557
- package/dist/grouping-priors.d.ts +0 -54
- package/dist/grouping-priors.js +0 -130
- package/dist/helpers/index.d.ts +0 -11
- package/dist/helpers/index.js +0 -11
- package/dist/merge-utils.d.ts +0 -22
- package/dist/merge-utils.js +0 -34
- package/dist/session.d.ts +0 -227
- package/dist/session.js +0 -364
package/dist/index.js
CHANGED
|
File without changes
|
|
@@ -20,17 +20,37 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import type { Provider, ProviderConfig } from "./types.js";
|
|
22
22
|
/** Permission request kind from the Copilot SDK. */
|
|
23
|
-
type PermissionKind = "shell" | "write" | "mcp" | "read" | "url" | "custom-tool";
|
|
24
|
-
/**
|
|
23
|
+
type PermissionKind = "shell" | "write" | "mcp" | "read" | "url" | "custom-tool" | "memory" | "hook";
|
|
24
|
+
/**
|
|
25
|
+
* Minimal permission request shape.
|
|
26
|
+
*
|
|
27
|
+
* Mirrors the discriminated union from `@github/copilot-sdk` (defined in
|
|
28
|
+
* `generated/session-events.d.ts` as `PermissionRequest*`). The 0.3.0 SDK
|
|
29
|
+
* carries the shell command text in `fullCommandText` (not `command`); the
|
|
30
|
+
* old singular `command` field never existed in the published types, so
|
|
31
|
+
* agents were silently rejected for every `mkdir`/`touch` they tried.
|
|
32
|
+
*/
|
|
25
33
|
interface PermissionRequest {
|
|
26
34
|
kind: PermissionKind;
|
|
27
|
-
command
|
|
35
|
+
/** Present on `kind: "shell"` — the full shell command text. */
|
|
36
|
+
fullCommandText?: string;
|
|
28
37
|
[key: string]: unknown;
|
|
29
38
|
}
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Minimal permission result returned to the SDK.
|
|
41
|
+
*
|
|
42
|
+
* Mirrors the subset of `PermissionDecision` from `@github/copilot-sdk`
|
|
43
|
+
* we actually emit: a one-shot approval or a reject. The SDK changed
|
|
44
|
+
* these literals in 0.3.0 (`approved` → `approve-once`,
|
|
45
|
+
* `denied-by-rules` → `reject`); anything else round-trips as
|
|
46
|
+
* `no-result`, which silently blocks tool calls.
|
|
47
|
+
*/
|
|
48
|
+
type PermissionResult = {
|
|
49
|
+
kind: "approve-once";
|
|
50
|
+
} | {
|
|
51
|
+
kind: "reject";
|
|
52
|
+
feedback?: string;
|
|
53
|
+
};
|
|
34
54
|
/**
|
|
35
55
|
* Minimal interface for a Copilot session, matching the subset of
|
|
36
56
|
* `CopilotSession` from `@github/copilot-sdk` used by the provider.
|
|
@@ -318,30 +318,37 @@ export class CopilotProvider {
|
|
|
318
318
|
const agent = agentContext.name;
|
|
319
319
|
// No agent context yet — default to approve-all.
|
|
320
320
|
if (!agent) {
|
|
321
|
-
return { kind: "
|
|
321
|
+
return { kind: "approve-once" };
|
|
322
322
|
}
|
|
323
323
|
switch (request.kind) {
|
|
324
324
|
case "write":
|
|
325
325
|
return agentAllowsEdit(agent)
|
|
326
|
-
? { kind: "
|
|
327
|
-
: { kind: "
|
|
326
|
+
? { kind: "approve-once" }
|
|
327
|
+
: { kind: "reject" };
|
|
328
328
|
case "shell": {
|
|
329
|
-
const cmd = request.
|
|
329
|
+
const cmd = request.fullCommandText ?? "";
|
|
330
330
|
return agentAllowsShell(agent, cmd)
|
|
331
|
-
? { kind: "
|
|
332
|
-
: { kind: "
|
|
331
|
+
? { kind: "approve-once" }
|
|
332
|
+
: { kind: "reject" };
|
|
333
333
|
}
|
|
334
|
+
case "memory":
|
|
335
|
+
case "hook":
|
|
336
|
+
// Both are SDK-internal mechanisms (agent self-memory and
|
|
337
|
+
// pre/post-tool-use hook permissions). Approve so the agent can
|
|
338
|
+
// run its own lifecycle plumbing — the per-tool permissions
|
|
339
|
+
// above are what gate user-visible side effects.
|
|
340
|
+
return { kind: "approve-once" };
|
|
334
341
|
case "url":
|
|
335
342
|
return agentAllowsWebfetch(agent)
|
|
336
|
-
? { kind: "
|
|
337
|
-
: { kind: "
|
|
343
|
+
? { kind: "approve-once" }
|
|
344
|
+
: { kind: "reject" };
|
|
338
345
|
case "mcp":
|
|
339
346
|
case "read":
|
|
340
347
|
case "custom-tool":
|
|
341
348
|
// Always allow MCP (broadcast), read-only access, and custom tools.
|
|
342
|
-
return { kind: "
|
|
349
|
+
return { kind: "approve-once" };
|
|
343
350
|
default:
|
|
344
|
-
return { kind: "
|
|
351
|
+
return { kind: "reject" };
|
|
345
352
|
}
|
|
346
353
|
};
|
|
347
354
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pruddiman/hem",
|
|
3
|
-
"version": "0.0.1-beta-
|
|
3
|
+
"version": "0.0.1-beta-18c9997",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"hem": "./dist/index.js"
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
"@github/copilot-sdk": "^0.3.0",
|
|
25
25
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
26
26
|
"@opencode-ai/sdk": "^1.14.48",
|
|
27
|
-
"@pruddiman/hem": "^0.0.1-beta-1aff12a",
|
|
28
27
|
"better-sqlite3": "^12.8.0",
|
|
29
28
|
"commander": "^14.0.3",
|
|
30
29
|
"fast-glob": "^3.3.3",
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LLM-assisted file grouping agent for Hem.
|
|
3
|
-
*
|
|
4
|
-
* Uses an OpenCode session to analyze source files and produce semantically
|
|
5
|
-
* meaningful documentation groups. Falls back gracefully — returns `null`
|
|
6
|
-
* on any failure so the caller can use heuristic grouping instead.
|
|
7
|
-
*
|
|
8
|
-
* The agent:
|
|
9
|
-
* 1. Reads file contents and extracts import dependencies (programmatic).
|
|
10
|
-
* 2. Builds a structured prompt with file summaries + dependency graph.
|
|
11
|
-
* 3. Sends the prompt to an OpenCode session.
|
|
12
|
-
* 4. Parses the strongly-typed JSON response.
|
|
13
|
-
* 5. Maps results to `FileGroup[]` with hallucination guards.
|
|
14
|
-
*/
|
|
15
|
-
import type { Provider } from "../providers/types.js";
|
|
16
|
-
import type { FileInfo, FileGroup } from "../types.js";
|
|
17
|
-
import { BaseAgent } from "./base-agent.js";
|
|
18
|
-
/** Raw LLM output schema — what the JSON response must match. */
|
|
19
|
-
export interface GroupingResult {
|
|
20
|
-
id: string;
|
|
21
|
-
label: string;
|
|
22
|
-
type: "vertical" | "horizontal";
|
|
23
|
-
files: string[];
|
|
24
|
-
rationale: string;
|
|
25
|
-
}
|
|
26
|
-
/** File name for the on-disk grouping result cache. */
|
|
27
|
-
export declare const GROUPING_CACHE_FILE = "grouping-cache.json";
|
|
28
|
-
/**
|
|
29
|
-
* File count threshold: when there are more files than this, use chunked
|
|
30
|
-
* grouping (split into batches → parallel LLM sessions → merge results).
|
|
31
|
-
* Below this threshold, the original single-prompt approach is used.
|
|
32
|
-
*/
|
|
33
|
-
export declare const GROUPING_CHUNK_THRESHOLD = 200;
|
|
34
|
-
/**
|
|
35
|
-
* Target number of files per batch when chunking. Batches are sized to
|
|
36
|
-
* stay well within LLM context limits (~100 files × 40 lines each ≈ 4000
|
|
37
|
-
* lines of context, comfortably under typical 128K-200K token windows).
|
|
38
|
-
*/
|
|
39
|
-
export declare const FILES_PER_CHUNK = 100;
|
|
40
|
-
/**
|
|
41
|
-
* An agent that uses an LLM to group source files into cohesive
|
|
42
|
-
* documentation topics.
|
|
43
|
-
*/
|
|
44
|
-
export declare class GroupingAgent extends BaseAgent {
|
|
45
|
-
private projectName;
|
|
46
|
-
constructor(provider: Provider, projectName: string);
|
|
47
|
-
/**
|
|
48
|
-
* Run the full grouping pipeline: read → analyze → prompt → parse → map.
|
|
49
|
-
*
|
|
50
|
-
* @param files - Discovered source files to group.
|
|
51
|
-
* @param verbose - Optional logging callback (writes to stderr).
|
|
52
|
-
* @param cacheDir - Optional directory for the grouping result cache
|
|
53
|
-
* (typically the `.hem` directory in the project root).
|
|
54
|
-
* When provided, a cache hit skips the LLM call entirely.
|
|
55
|
-
* @returns `FileGroup[]` on success, `null` on any failure.
|
|
56
|
-
*/
|
|
57
|
-
run(files: FileInfo[], verbose?: (msg: string) => void, cacheDir?: string): Promise<FileGroup[] | null>;
|
|
58
|
-
/**
|
|
59
|
-
* Chunked grouping for large file sets.
|
|
60
|
-
*
|
|
61
|
-
* Splits files into batches that fit within context limits, runs
|
|
62
|
-
* multiple grouping sessions in parallel, and merges the results.
|
|
63
|
-
* Returns raw {@link GroupingResult}[] (before `mapToFileGroups`).
|
|
64
|
-
*/
|
|
65
|
-
private runChunkedRaw;
|
|
66
|
-
/**
|
|
67
|
-
* Read file contents for all files. Files that fail to read get an
|
|
68
|
-
* error placeholder.
|
|
69
|
-
*/
|
|
70
|
-
static readFiles(files: FileInfo[]): Promise<Array<{
|
|
71
|
-
path: string;
|
|
72
|
-
content: string;
|
|
73
|
-
size: number;
|
|
74
|
-
}>>;
|
|
75
|
-
/**
|
|
76
|
-
* Extract import dependencies from file contents via regex.
|
|
77
|
-
*
|
|
78
|
-
* Handles:
|
|
79
|
-
* - `import ... from "./foo.js"`
|
|
80
|
-
* - `import ... from "../bar/baz.js"`
|
|
81
|
-
* - `import("./dynamic.js")`
|
|
82
|
-
* - `require("./cjs.js")`
|
|
83
|
-
*
|
|
84
|
-
* Only tracks local (relative) imports — ignores node_modules.
|
|
85
|
-
*
|
|
86
|
-
* @returns Map from file path → array of imported file paths.
|
|
87
|
-
*/
|
|
88
|
-
static analyzeImports(fileContents: Array<{
|
|
89
|
-
path: string;
|
|
90
|
-
content: string;
|
|
91
|
-
}>): Map<string, string[]>;
|
|
92
|
-
/**
|
|
93
|
-
* Build the grouping prompt from file summaries and import graph.
|
|
94
|
-
*
|
|
95
|
-
* Includes the first 40 lines of each file plus the dependency graph
|
|
96
|
-
* so the LLM can make informed grouping decisions.
|
|
97
|
-
*/
|
|
98
|
-
static buildPrompt(projectName: string, fileContents: Array<{
|
|
99
|
-
path: string;
|
|
100
|
-
content: string;
|
|
101
|
-
size: number;
|
|
102
|
-
}>, imports: Map<string, string[]>): string;
|
|
103
|
-
/**
|
|
104
|
-
* Extract and validate JSON from the LLM response.
|
|
105
|
-
*
|
|
106
|
-
* Tries to find a fenced ```json block first, then falls back to
|
|
107
|
-
* parsing the entire response as JSON.
|
|
108
|
-
*
|
|
109
|
-
* @returns Validated array of `GroupingResult`, or `null` if invalid.
|
|
110
|
-
*/
|
|
111
|
-
static parseResponse(response: string): GroupingResult[] | null;
|
|
112
|
-
/**
|
|
113
|
-
* Map validated LLM results to `FileGroup[]`.
|
|
114
|
-
*
|
|
115
|
-
* Resolves file paths against the known file set, skipping any
|
|
116
|
-
* paths the LLM hallucinated. Computes `directory` via
|
|
117
|
-
* `commonDirectory()` from `grouping.ts`.
|
|
118
|
-
*/
|
|
119
|
-
static mapToFileGroups(results: GroupingResult[], filesByPath: Map<string, FileInfo>): FileGroup[];
|
|
120
|
-
/**
|
|
121
|
-
* Split file contents into batches of approximately `chunkSize` files.
|
|
122
|
-
*
|
|
123
|
-
* Files are split sequentially — no shuffling — so files that are
|
|
124
|
-
* close in the directory listing stay together, improving the LLM's
|
|
125
|
-
* ability to detect intra-chunk relationships.
|
|
126
|
-
*/
|
|
127
|
-
static splitIntoChunks(fileContents: Array<{
|
|
128
|
-
path: string;
|
|
129
|
-
content: string;
|
|
130
|
-
size: number;
|
|
131
|
-
}>, chunkSize: number): Array<Array<{
|
|
132
|
-
path: string;
|
|
133
|
-
content: string;
|
|
134
|
-
size: number;
|
|
135
|
-
}>>;
|
|
136
|
-
/**
|
|
137
|
-
* Merge grouping results from multiple chunks.
|
|
138
|
-
*
|
|
139
|
-
* Groups from different chunks may overlap — e.g., two chunks may each
|
|
140
|
-
* produce an "Authentication" group containing different files. This
|
|
141
|
-
* method merges groups with the same `id` (after kebab-case normalization)
|
|
142
|
-
* by combining their file lists and deduplicating.
|
|
143
|
-
*
|
|
144
|
-
* When groups share the same normalized ID:
|
|
145
|
-
* - File lists are unioned (deduplicated).
|
|
146
|
-
* - The label and type from the first occurrence are kept.
|
|
147
|
-
* - Rationales are concatenated.
|
|
148
|
-
*
|
|
149
|
-
* @param results - All `GroupingResult[]` from every chunk (flattened).
|
|
150
|
-
* @returns Merged array of `GroupingResult`.
|
|
151
|
-
*/
|
|
152
|
-
static mergeGroupingResults(results: GroupingResult[]): GroupingResult[];
|
|
153
|
-
/**
|
|
154
|
-
* Compute a stable hash of the file list for cache invalidation.
|
|
155
|
-
* Uses path + size so renames and size changes invalidate the cache.
|
|
156
|
-
*/
|
|
157
|
-
static computeFilesHash(files: FileInfo[]): string;
|
|
158
|
-
/**
|
|
159
|
-
* Attempt to load a valid cache entry from `cacheDir`.
|
|
160
|
-
* Returns `null` on miss, parse error, or hash mismatch.
|
|
161
|
-
*/
|
|
162
|
-
static loadGroupingCache(cacheDir: string, filesHash: string): Promise<GroupingResult[] | null>;
|
|
163
|
-
/**
|
|
164
|
-
* Write grouping results to the cache file (best-effort, never throws).
|
|
165
|
-
*/
|
|
166
|
-
static saveGroupingCache(cacheDir: string, filesHash: string, results: GroupingResult[]): Promise<void>;
|
|
167
|
-
}
|