@pruddiman/hem 0.0.1-beta-1ec07ab → 0.0.1-beta-95d42fc

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 CHANGED
File without changes
@@ -27,10 +27,21 @@ interface PermissionRequest {
27
27
  command?: string;
28
28
  [key: string]: unknown;
29
29
  }
30
- /** Minimal permission result returned to the SDK. */
31
- interface PermissionResult {
32
- kind: "approved" | "denied-by-rules";
33
- }
30
+ /**
31
+ * Minimal permission result returned to the SDK.
32
+ *
33
+ * Mirrors the subset of `PermissionDecision` from `@github/copilot-sdk`
34
+ * we actually emit: a one-shot approval or a reject. The SDK changed
35
+ * these literals in 0.3.0 (`approved` → `approve-once`,
36
+ * `denied-by-rules` → `reject`); anything else round-trips as
37
+ * `no-result`, which silently blocks tool calls.
38
+ */
39
+ type PermissionResult = {
40
+ kind: "approve-once";
41
+ } | {
42
+ kind: "reject";
43
+ feedback?: string;
44
+ };
34
45
  /**
35
46
  * Minimal interface for a Copilot session, matching the subset of
36
47
  * `CopilotSession` from `@github/copilot-sdk` used by the provider.
@@ -318,30 +318,30 @@ 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: "approved" };
321
+ return { kind: "approve-once" };
322
322
  }
323
323
  switch (request.kind) {
324
324
  case "write":
325
325
  return agentAllowsEdit(agent)
326
- ? { kind: "approved" }
327
- : { kind: "denied-by-rules" };
326
+ ? { kind: "approve-once" }
327
+ : { kind: "reject" };
328
328
  case "shell": {
329
329
  const cmd = request.command ?? "";
330
330
  return agentAllowsShell(agent, cmd)
331
- ? { kind: "approved" }
332
- : { kind: "denied-by-rules" };
331
+ ? { kind: "approve-once" }
332
+ : { kind: "reject" };
333
333
  }
334
334
  case "url":
335
335
  return agentAllowsWebfetch(agent)
336
- ? { kind: "approved" }
337
- : { kind: "denied-by-rules" };
336
+ ? { kind: "approve-once" }
337
+ : { kind: "reject" };
338
338
  case "mcp":
339
339
  case "read":
340
340
  case "custom-tool":
341
341
  // Always allow MCP (broadcast), read-only access, and custom tools.
342
- return { kind: "approved" };
342
+ return { kind: "approve-once" };
343
343
  default:
344
- return { kind: "denied-by-rules" };
344
+ return { kind: "reject" };
345
345
  }
346
346
  };
347
347
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pruddiman/hem",
3
- "version": "0.0.1-beta-1ec07ab",
3
+ "version": "0.0.1-beta-95d42fc",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "hem": "./dist/index.js"
@@ -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
- }