@on-par/repo-context 0.0.0-stage → 0.9.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 On PAR Dev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,15 @@
1
- # Temporary Holding Version
1
+ # @on-par/repo-context
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Read-only repository reader behind a stable interface, with GitHub contents-API and in-memory implementations. Zero runtime dependencies.
4
+
5
+ Part of [Software Factory](https://github.com/on-par/software-factory), a multi-agent factory that ships verified work autonomously. Most users want the CLI, [`@on-par/factory-cli`](https://www.npmjs.com/package/@on-par/factory-cli); the other packages are its building blocks.
6
+
7
+ ## Documentation
8
+
9
+ - [README and Quick Start](https://github.com/on-par/software-factory#readme)
10
+ - [Architecture and package graph](https://github.com/on-par/software-factory/blob/main/docs/architecture.md)
11
+ - [Changelog](https://github.com/on-par/software-factory/blob/main/CHANGELOG.md)
12
+
13
+ ## License
14
+
15
+ MIT. See [LICENSE](./LICENSE).
package/dist/fs.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ import type { OnDegrade, RepoContextReader } from './reader.js';
2
+ /** Minimal `node:fs/promises` surface the reader needs; injected in tests. */
3
+ export interface FsDirent {
4
+ name: string;
5
+ isDirectory(): boolean;
6
+ isFile(): boolean;
7
+ }
8
+ export interface FsStatLike {
9
+ isDirectory(): boolean;
10
+ isFile(): boolean;
11
+ size: number;
12
+ }
13
+ export interface FsLike {
14
+ stat(path: string): Promise<FsStatLike>;
15
+ readdir(path: string, options: {
16
+ withFileTypes: true;
17
+ }): Promise<readonly FsDirent[]>;
18
+ readFile(path: string, encoding: 'utf8'): Promise<string>;
19
+ realpath(path: string): Promise<string>;
20
+ }
21
+ export interface FsReaderOptions {
22
+ /** Absolute path to the checkout root; every read is confined to it. */
23
+ root: string;
24
+ /** Files larger than this degrade to `too-large`. Defaults to DEFAULT_MAX_FILE_BYTES. */
25
+ maxFileBytes?: number;
26
+ /** Injected for tests; defaults to node:fs/promises. */
27
+ fs?: FsLike;
28
+ onDegrade?: OnDegrade;
29
+ }
30
+ export declare function createFsReader(options: FsReaderOptions): RepoContextReader;
31
+ //# sourceMappingURL=fs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAA+B,SAAS,EAAE,iBAAiB,EAAgB,MAAM,aAAa,CAAC;AAG3G,8EAA8E;AAC9E,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,IAAI,OAAO,CAAC;IACvB,MAAM,IAAI,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,IAAI,OAAO,CAAC;IACvB,MAAM,IAAI,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACxC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,aAAa,EAAE,IAAI,CAAA;KAAE,GAAG,OAAO,CAAC,SAAS,QAAQ,EAAE,CAAC,CAAC;IACtF,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,IAAI,EAAE,MAAM,CAAC;IACb,yFAAyF;IACzF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wDAAwD;IACxD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AA0BD,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,iBAAiB,CAyK1E"}
package/dist/fs.js ADDED
@@ -0,0 +1,184 @@
1
+ // src/fs.ts — local-filesystem RepoContextReader impl over a checkout root (#481).
2
+ import { readdir, readFile, realpath, stat } from 'node:fs/promises';
3
+ import { join, sep } from 'node:path';
4
+ import { DEFAULT_MAX_FILE_BYTES } from './github.js';
5
+ import { joinRepoPath, normalizeRepoPath } from './path.js';
6
+ import { EMPTY_DIR } from './reader.js';
7
+ const nodeFs = {
8
+ stat: (p) => stat(p),
9
+ readdir: (p, o) => readdir(p, o),
10
+ readFile: (p, e) => readFile(p, e),
11
+ realpath: (p) => realpath(p),
12
+ };
13
+ function classifyFsError(error) {
14
+ const code = error?.code;
15
+ switch (code) {
16
+ case 'ENOENT':
17
+ case 'ENOTDIR':
18
+ return 'not-found';
19
+ case 'EACCES':
20
+ case 'EPERM':
21
+ return 'unauthorized';
22
+ case 'ELOOP':
23
+ case 'ENAMETOOLONG':
24
+ return 'invalid-path';
25
+ default:
26
+ return 'invalid-response';
27
+ }
28
+ }
29
+ export function createFsReader(options) {
30
+ const { root, onDegrade } = options;
31
+ const fs = options.fs ?? nodeFs;
32
+ const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
33
+ let rootRealPromise;
34
+ function rootReal() {
35
+ rootRealPromise ??= fs.realpath(root).catch(() => undefined);
36
+ return rootRealPromise;
37
+ }
38
+ function emit(event) {
39
+ if (!onDegrade) {
40
+ return;
41
+ }
42
+ try {
43
+ onDegrade(event);
44
+ }
45
+ catch {
46
+ // Never let a throwing observer break the "never throws" contract.
47
+ }
48
+ }
49
+ async function resolveWithin(normalized) {
50
+ const abs = normalized === '' ? root : join(root, normalized);
51
+ let real;
52
+ try {
53
+ real = await fs.realpath(abs);
54
+ }
55
+ catch (error) {
56
+ return { ok: false, reason: classifyFsError(error) };
57
+ }
58
+ const base = await rootReal();
59
+ if (base === undefined) {
60
+ return { ok: false, reason: 'not-found' };
61
+ }
62
+ if (real !== base && !real.startsWith(base + sep)) {
63
+ return { ok: false, reason: 'invalid-path' };
64
+ }
65
+ return { ok: true, abs: real };
66
+ }
67
+ return {
68
+ async readFile(rawPath) {
69
+ const normalized = normalizeRepoPath(rawPath);
70
+ if (normalized === undefined) {
71
+ emit({ operation: 'readFile', path: rawPath, reason: 'invalid-path' });
72
+ return undefined;
73
+ }
74
+ const resolved = await resolveWithin(normalized);
75
+ if (!resolved.ok) {
76
+ emit({ operation: 'readFile', path: normalized, reason: resolved.reason });
77
+ return undefined;
78
+ }
79
+ let fileStat;
80
+ try {
81
+ fileStat = await fs.stat(resolved.abs);
82
+ }
83
+ catch (error) {
84
+ emit({ operation: 'readFile', path: normalized, reason: classifyFsError(error) });
85
+ return undefined;
86
+ }
87
+ if (fileStat.isDirectory()) {
88
+ emit({ operation: 'readFile', path: normalized, reason: 'wrong-type' });
89
+ return undefined;
90
+ }
91
+ if (fileStat.size > maxFileBytes) {
92
+ emit({ operation: 'readFile', path: normalized, reason: 'too-large' });
93
+ return undefined;
94
+ }
95
+ if (!fileStat.isFile()) {
96
+ emit({ operation: 'readFile', path: normalized, reason: 'wrong-type' });
97
+ return undefined;
98
+ }
99
+ let text;
100
+ try {
101
+ text = await fs.readFile(resolved.abs, 'utf8');
102
+ }
103
+ catch (error) {
104
+ emit({ operation: 'readFile', path: normalized, reason: classifyFsError(error) });
105
+ return undefined;
106
+ }
107
+ if (text.includes('\u0000')) {
108
+ emit({ operation: 'readFile', path: normalized, reason: 'unsupported-content' });
109
+ return undefined;
110
+ }
111
+ return { path: normalized, text, size: fileStat.size };
112
+ },
113
+ async readDir(rawPath) {
114
+ const normalized = normalizeRepoPath(rawPath);
115
+ if (normalized === undefined) {
116
+ emit({ operation: 'readDir', path: rawPath, reason: 'invalid-path' });
117
+ return EMPTY_DIR;
118
+ }
119
+ const resolved = await resolveWithin(normalized);
120
+ if (!resolved.ok) {
121
+ emit({ operation: 'readDir', path: normalized, reason: resolved.reason });
122
+ return EMPTY_DIR;
123
+ }
124
+ let dirStat;
125
+ try {
126
+ dirStat = await fs.stat(resolved.abs);
127
+ }
128
+ catch (error) {
129
+ emit({ operation: 'readDir', path: normalized, reason: classifyFsError(error) });
130
+ return EMPTY_DIR;
131
+ }
132
+ if (!dirStat.isDirectory()) {
133
+ emit({ operation: 'readDir', path: normalized, reason: 'wrong-type' });
134
+ return EMPTY_DIR;
135
+ }
136
+ let dirents;
137
+ try {
138
+ dirents = await fs.readdir(resolved.abs, { withFileTypes: true });
139
+ }
140
+ catch (error) {
141
+ emit({ operation: 'readDir', path: normalized, reason: classifyFsError(error) });
142
+ return EMPTY_DIR;
143
+ }
144
+ const entries = [];
145
+ for (const dirent of dirents) {
146
+ const childPath = joinRepoPath(normalized, dirent.name);
147
+ if (dirent.isDirectory()) {
148
+ entries.push({ name: dirent.name, path: childPath, type: 'dir' });
149
+ continue;
150
+ }
151
+ if (dirent.isFile()) {
152
+ entries.push({ name: dirent.name, path: childPath, type: 'file' });
153
+ continue;
154
+ }
155
+ // Symlink or other special file: resolve via stat, skip on failure.
156
+ try {
157
+ const childStat = await fs.stat(join(resolved.abs, dirent.name));
158
+ entries.push({ name: dirent.name, path: childPath, type: childStat.isDirectory() ? 'dir' : 'file' });
159
+ }
160
+ catch {
161
+ continue;
162
+ }
163
+ }
164
+ return entries.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
165
+ },
166
+ async exists(rawPath) {
167
+ const normalized = normalizeRepoPath(rawPath);
168
+ if (normalized === undefined) {
169
+ emit({ operation: 'exists', path: rawPath, reason: 'invalid-path' });
170
+ return false;
171
+ }
172
+ const resolved = await resolveWithin(normalized);
173
+ if (!resolved.ok) {
174
+ if (resolved.reason === 'not-found') {
175
+ return false;
176
+ }
177
+ emit({ operation: 'exists', path: normalized, reason: resolved.reason });
178
+ return false;
179
+ }
180
+ return true;
181
+ },
182
+ };
183
+ }
184
+ //# sourceMappingURL=fs.js.map
package/dist/fs.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../src/fs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ import type { OnDegrade, RepoContextReader } from './reader.js';
2
+ export interface FetchLikeResponse {
3
+ ok: boolean;
4
+ status: number;
5
+ headers: {
6
+ get(name: string): string | null;
7
+ };
8
+ text(): Promise<string>;
9
+ }
10
+ export type FetchLike = (url: string, init?: {
11
+ headers?: Record<string, string>;
12
+ }) => Promise<FetchLikeResponse>;
13
+ export declare const DEFAULT_GITHUB_API_BASE_URL = "https://api.github.com";
14
+ /** GitHub's contents API refuses to inline files above ~1 MB. */
15
+ export declare const DEFAULT_MAX_FILE_BYTES = 1000000;
16
+ export interface GitHubContentsReaderOptions {
17
+ owner: string;
18
+ repo: string;
19
+ /** Branch, tag, or commit SHA. Omit to use the repo's default branch. */
20
+ ref?: string;
21
+ /** Read-only token. Omit for unauthenticated (public, heavily rate-limited) access. */
22
+ token?: string;
23
+ baseUrl?: string;
24
+ maxFileBytes?: number;
25
+ /** Injected for tests; defaults to the global `fetch`. */
26
+ fetch?: FetchLike;
27
+ onDegrade?: OnDegrade;
28
+ }
29
+ export declare function createGitHubContentsReader(options: GitHubContentsReaderOptions): RepoContextReader;
30
+ //# sourceMappingURL=github.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAGV,SAAS,EAET,iBAAiB,EAElB,MAAM,aAAa,CAAC;AAGrB,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC9C,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACzB;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAEjH,eAAO,MAAM,2BAA2B,2BAA2B,CAAC;AACpE,iEAAiE;AACjE,eAAO,MAAM,sBAAsB,UAAY,CAAC;AAEhD,MAAM,WAAW,2BAA2B;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uFAAuF;IACvF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAqBD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,2BAA2B,GAAG,iBAAiB,CAoLlG"}
package/dist/github.js ADDED
@@ -0,0 +1,178 @@
1
+ // src/github.ts — GitHub contents-API RepoContextReader impl (#468).
2
+ import { Buffer } from 'node:buffer';
3
+ import { normalizeRepoPath } from './path.js';
4
+ import { EMPTY_DIR } from './reader.js';
5
+ export const DEFAULT_GITHUB_API_BASE_URL = 'https://api.github.com';
6
+ /** GitHub's contents API refuses to inline files above ~1 MB. */
7
+ export const DEFAULT_MAX_FILE_BYTES = 1_000_000;
8
+ function encodeContentsPath(path) {
9
+ return path
10
+ .split('/')
11
+ .map((segment) => encodeURIComponent(segment))
12
+ .join('/');
13
+ }
14
+ function stripTrailingSlashes(url) {
15
+ let end = url.length;
16
+ while (end > 0 && url[end - 1] === '/') {
17
+ end -= 1;
18
+ }
19
+ return url.slice(0, end);
20
+ }
21
+ export function createGitHubContentsReader(options) {
22
+ const { owner, repo, ref, token, onDegrade } = options;
23
+ const baseUrl = stripTrailingSlashes(options.baseUrl ?? DEFAULT_GITHUB_API_BASE_URL);
24
+ const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
25
+ const globalFetch = globalThis.fetch;
26
+ const doFetch = options.fetch ?? (globalFetch ? (url, init) => globalFetch(url, init) : undefined);
27
+ function emit(event) {
28
+ if (!onDegrade) {
29
+ return;
30
+ }
31
+ try {
32
+ onDegrade(event);
33
+ }
34
+ catch {
35
+ // Never let a throwing observer break the "never throws" contract.
36
+ }
37
+ }
38
+ function emitFailure(operation, result) {
39
+ emit({ operation, path: result.path, reason: result.reason, status: result.status, detail: result.detail });
40
+ }
41
+ function buildUrl(path) {
42
+ const encodedPath = encodeContentsPath(path);
43
+ const suffix = encodedPath === '' ? '' : `/${encodedPath}`;
44
+ const url = `${baseUrl}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/contents${suffix}`;
45
+ return ref ? `${url}?ref=${encodeURIComponent(ref)}` : url;
46
+ }
47
+ function buildHeaders() {
48
+ const headers = {
49
+ Accept: 'application/vnd.github+json',
50
+ 'X-GitHub-Api-Version': '2022-11-28',
51
+ };
52
+ if (token) {
53
+ headers.Authorization = `Bearer ${token}`;
54
+ }
55
+ return headers;
56
+ }
57
+ function classifyStatus(res) {
58
+ switch (res.status) {
59
+ case 404:
60
+ return 'not-found';
61
+ case 401:
62
+ return 'unauthorized';
63
+ case 403:
64
+ return res.headers.get('x-ratelimit-remaining') === '0' ? 'rate-limited' : 'unauthorized';
65
+ case 429:
66
+ return 'rate-limited';
67
+ default:
68
+ return 'invalid-response';
69
+ }
70
+ }
71
+ async function request(rawPath) {
72
+ const normalized = normalizeRepoPath(rawPath);
73
+ if (normalized === undefined) {
74
+ return { ok: false, path: rawPath, reason: 'invalid-path' };
75
+ }
76
+ if (!doFetch) {
77
+ return { ok: false, path: normalized, reason: 'network', detail: 'global fetch is unavailable' };
78
+ }
79
+ let res;
80
+ try {
81
+ res = await doFetch(buildUrl(normalized), { headers: buildHeaders() });
82
+ }
83
+ catch (error) {
84
+ return { ok: false, path: normalized, reason: 'network', detail: error.message };
85
+ }
86
+ if (!res.ok) {
87
+ return { ok: false, path: normalized, reason: classifyStatus(res), status: res.status };
88
+ }
89
+ let raw;
90
+ try {
91
+ raw = await res.text();
92
+ }
93
+ catch (error) {
94
+ return { ok: false, path: normalized, reason: 'invalid-response', detail: error.message };
95
+ }
96
+ try {
97
+ return { ok: true, path: normalized, body: JSON.parse(raw) };
98
+ }
99
+ catch (error) {
100
+ return { ok: false, path: normalized, reason: 'invalid-response', detail: error.message };
101
+ }
102
+ }
103
+ return {
104
+ async readFile(rawPath) {
105
+ const result = await request(rawPath);
106
+ if (!result.ok) {
107
+ emitFailure('readFile', result);
108
+ return undefined;
109
+ }
110
+ const body = result.body;
111
+ if (body === null ||
112
+ typeof body !== 'object' ||
113
+ Array.isArray(body) ||
114
+ body.type !== 'file') {
115
+ emit({ operation: 'readFile', path: result.path, reason: 'wrong-type' });
116
+ return undefined;
117
+ }
118
+ const record = body;
119
+ const size = typeof record.size === 'number' ? record.size : 0;
120
+ if (size > maxFileBytes) {
121
+ emit({ operation: 'readFile', path: result.path, reason: 'too-large' });
122
+ return undefined;
123
+ }
124
+ if (record.encoding === 'none') {
125
+ emit({ operation: 'readFile', path: result.path, reason: 'too-large' });
126
+ return undefined;
127
+ }
128
+ if (record.encoding !== 'base64' || typeof record.content !== 'string') {
129
+ emit({ operation: 'readFile', path: result.path, reason: 'unsupported-content' });
130
+ return undefined;
131
+ }
132
+ const text = Buffer.from(record.content, 'base64').toString('utf8');
133
+ if (text.includes('\u0000')) {
134
+ emit({ operation: 'readFile', path: result.path, reason: 'unsupported-content' });
135
+ return undefined;
136
+ }
137
+ return { path: result.path, text, size };
138
+ },
139
+ async readDir(rawPath) {
140
+ const result = await request(rawPath);
141
+ if (!result.ok) {
142
+ emitFailure('readDir', result);
143
+ return EMPTY_DIR;
144
+ }
145
+ if (!Array.isArray(result.body)) {
146
+ emit({ operation: 'readDir', path: result.path, reason: 'wrong-type' });
147
+ return EMPTY_DIR;
148
+ }
149
+ const entries = [];
150
+ for (const item of result.body) {
151
+ if (item === null || typeof item !== 'object') {
152
+ continue;
153
+ }
154
+ const record = item;
155
+ if (typeof record.name !== 'string' || typeof record.path !== 'string') {
156
+ continue;
157
+ }
158
+ if (record.type !== 'file' && record.type !== 'dir') {
159
+ continue;
160
+ }
161
+ entries.push({ name: record.name, path: record.path, type: record.type });
162
+ }
163
+ return entries.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
164
+ },
165
+ async exists(rawPath) {
166
+ const result = await request(rawPath);
167
+ if (!result.ok) {
168
+ if (result.reason === 'not-found') {
169
+ return false;
170
+ }
171
+ emitFailure('exists', result);
172
+ return false;
173
+ }
174
+ return Array.isArray(result.body) || (result.body !== null && typeof result.body === 'object');
175
+ },
176
+ };
177
+ }
178
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.js","sourceRoot":"","sources":["../src/github.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export * from './fs.js';
2
+ export * from './github.js';
3
+ export * from './memory.js';
4
+ export * from './path.js';
5
+ export * from './reader.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // src/index.ts — @on-par/repo-context public surface (#468).
2
+ export * from './fs.js';
3
+ export * from './github.js';
4
+ export * from './memory.js';
5
+ export * from './path.js';
6
+ export * from './reader.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { OnDegrade, RepoContextReader } from './reader.js';
2
+ export interface InMemoryReaderOptions {
3
+ onDegrade?: OnDegrade;
4
+ }
5
+ /** In-memory `RepoContextReader` over a flat `path -> contents` map. */
6
+ export declare function createInMemoryReader(files: Readonly<Record<string, string>>, options?: InMemoryReaderOptions): RepoContextReader;
7
+ //# sourceMappingURL=memory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAgB,SAAS,EAAE,iBAAiB,EAAgB,MAAM,aAAa,CAAC;AAG5F,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,EAAE,SAAS,CAAC;CACvB;AAED,wEAAwE;AACxE,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACvC,OAAO,CAAC,EAAE,qBAAqB,GAC9B,iBAAiB,CAmGnB"}
package/dist/memory.js ADDED
@@ -0,0 +1,99 @@
1
+ // src/memory.ts — in-memory RepoContextReader over a flat path -> contents map (#468).
2
+ import { Buffer } from 'node:buffer';
3
+ import { normalizeRepoPath } from './path.js';
4
+ import { EMPTY_DIR } from './reader.js';
5
+ /** In-memory `RepoContextReader` over a flat `path -> contents` map. */
6
+ export function createInMemoryReader(files, options) {
7
+ const onDegrade = options?.onDegrade;
8
+ const store = new Map();
9
+ for (const [rawPath, contents] of Object.entries(files)) {
10
+ const normalized = normalizeRepoPath(rawPath);
11
+ if (normalized === undefined || normalized === '') {
12
+ continue;
13
+ }
14
+ store.set(normalized, contents);
15
+ }
16
+ function emit(event) {
17
+ if (!onDegrade) {
18
+ return;
19
+ }
20
+ try {
21
+ onDegrade(event);
22
+ }
23
+ catch {
24
+ // Never let a throwing observer break the "never throws" contract.
25
+ }
26
+ }
27
+ function isDirPrefix(path) {
28
+ const prefix = `${path}/`;
29
+ for (const key of store.keys()) {
30
+ if (key.startsWith(prefix)) {
31
+ return true;
32
+ }
33
+ }
34
+ return false;
35
+ }
36
+ return {
37
+ async readFile(rawPath) {
38
+ const normalized = normalizeRepoPath(rawPath);
39
+ if (normalized === undefined) {
40
+ emit({ operation: 'readFile', path: rawPath, reason: 'invalid-path' });
41
+ return undefined;
42
+ }
43
+ const text = store.get(normalized);
44
+ if (text !== undefined) {
45
+ return { path: normalized, text, size: Buffer.byteLength(text, 'utf8') };
46
+ }
47
+ if (isDirPrefix(normalized)) {
48
+ emit({ operation: 'readFile', path: normalized, reason: 'wrong-type' });
49
+ return undefined;
50
+ }
51
+ emit({ operation: 'readFile', path: normalized, reason: 'not-found' });
52
+ return undefined;
53
+ },
54
+ async readDir(rawPath) {
55
+ const normalized = normalizeRepoPath(rawPath);
56
+ if (normalized === undefined) {
57
+ emit({ operation: 'readDir', path: rawPath, reason: 'invalid-path' });
58
+ return EMPTY_DIR;
59
+ }
60
+ if (store.has(normalized)) {
61
+ emit({ operation: 'readDir', path: normalized, reason: 'wrong-type' });
62
+ return EMPTY_DIR;
63
+ }
64
+ const prefix = normalized === '' ? '' : `${normalized}/`;
65
+ const children = new Map();
66
+ for (const key of store.keys()) {
67
+ if (!key.startsWith(prefix)) {
68
+ continue;
69
+ }
70
+ const rest = key.slice(prefix.length);
71
+ const segments = rest.split('/');
72
+ const name = segments[0];
73
+ const childPath = prefix === '' ? name : `${prefix}${name}`;
74
+ const type = segments.length === 1 ? 'file' : 'dir';
75
+ const existing = children.get(childPath);
76
+ if (!existing || (existing.type === 'file' && type === 'dir')) {
77
+ children.set(childPath, { name, path: childPath, type });
78
+ }
79
+ }
80
+ if (children.size === 0 && normalized !== '') {
81
+ emit({ operation: 'readDir', path: normalized, reason: 'not-found' });
82
+ return EMPTY_DIR;
83
+ }
84
+ return [...children.values()].sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
85
+ },
86
+ async exists(rawPath) {
87
+ const normalized = normalizeRepoPath(rawPath);
88
+ if (normalized === undefined) {
89
+ emit({ operation: 'exists', path: rawPath, reason: 'invalid-path' });
90
+ return false;
91
+ }
92
+ if (normalized === '' || store.has(normalized) || isDirPrefix(normalized)) {
93
+ return true;
94
+ }
95
+ return false;
96
+ },
97
+ };
98
+ }
99
+ //# sourceMappingURL=memory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"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"}
package/dist/path.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Normalizes a repo-root-relative path: trims whitespace, collapses `//`, drops a leading
3
+ * `/` or `./`, drops a trailing `/`, and maps '', '.', '/', './' to the repo root ''.
4
+ * Returns `undefined` when any segment is `..` — the path would escape the repo root.
5
+ */
6
+ export declare function normalizeRepoPath(path: string): string | undefined;
7
+ export declare function joinRepoPath(base: string, name: string): string;
8
+ //# sourceMappingURL=path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAWlE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/D"}
package/dist/path.js ADDED
@@ -0,0 +1,20 @@
1
+ // src/path.ts — pure repo-path normalization (#468).
2
+ /**
3
+ * Normalizes a repo-root-relative path: trims whitespace, collapses `//`, drops a leading
4
+ * `/` or `./`, drops a trailing `/`, and maps '', '.', '/', './' to the repo root ''.
5
+ * Returns `undefined` when any segment is `..` — the path would escape the repo root.
6
+ */
7
+ export function normalizeRepoPath(path) {
8
+ const segments = path
9
+ .trim()
10
+ .split('/')
11
+ .filter((segment) => segment !== '' && segment !== '.');
12
+ if (segments.some((segment) => segment === '..')) {
13
+ return undefined;
14
+ }
15
+ return segments.join('/');
16
+ }
17
+ export function joinRepoPath(base, name) {
18
+ return base === '' ? name : `${base}/${name}`;
19
+ }
20
+ //# sourceMappingURL=path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.js","sourceRoot":"","sources":["../src/path.ts"],"names":[],"mappings":"AAAA,qDAAqD;AAErD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,QAAQ,GAAG,IAAI;SAClB,IAAI,EAAE;SACN,KAAK,CAAC,GAAG,CAAC;SACV,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,EAAE,IAAI,OAAO,KAAK,GAAG,CAAC,CAAC;IAE1D,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,KAAK,IAAI,CAAC,EAAE,CAAC;QACjD,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,IAAY;IACrD,OAAO,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;AAChD,CAAC"}
@@ -0,0 +1,47 @@
1
+ /** A file read from the target repo. `path` is the normalized, repo-root-relative path. */
2
+ export interface RepoFile {
3
+ path: string;
4
+ /** UTF-8 decoded contents. */
5
+ text: string;
6
+ /** Byte length reported by the backend. */
7
+ size: number;
8
+ }
9
+ export type RepoEntryType = 'file' | 'dir';
10
+ export interface RepoDirEntry {
11
+ /** Basename, e.g. 'README.md'. */
12
+ name: string;
13
+ /** Repo-root-relative path, e.g. 'docs/adr/README.md'. */
14
+ path: string;
15
+ type: RepoEntryType;
16
+ }
17
+ /**
18
+ * Read-only access to one repository at one revision.
19
+ *
20
+ * Backend coordinates (owner/repo/ref/token/worktree root) are bound when the reader is
21
+ * constructed, never passed here — that is what lets a gh-contents reader, an fs-backed
22
+ * reader, and a future blobless sparse-checkout reader be swapped without changing callers.
23
+ *
24
+ * No method throws. A path that is missing, unreadable, unauthorized, rate-limited, too
25
+ * large, or not text degrades to the empty result for that method; pass `onDegrade` to the
26
+ * factory to observe why.
27
+ */
28
+ export interface RepoContextReader {
29
+ readFile(path: string): Promise<RepoFile | undefined>;
30
+ readDir(path: string): Promise<readonly RepoDirEntry[]>;
31
+ exists(path: string): Promise<boolean>;
32
+ }
33
+ export type RepoContextOperation = 'readFile' | 'readDir' | 'exists';
34
+ export type DegradeReason = 'invalid-path' | 'not-found' | 'wrong-type' | 'unauthorized' | 'rate-limited' | 'too-large' | 'unsupported-content' | 'network' | 'invalid-response';
35
+ export interface DegradeEvent {
36
+ operation: RepoContextOperation;
37
+ /** The requested path, normalized when normalization succeeded. */
38
+ path: string;
39
+ reason: DegradeReason;
40
+ /** HTTP status, when the backend is remote. */
41
+ status?: number;
42
+ detail?: string;
43
+ }
44
+ export type OnDegrade = (event: DegradeEvent) => void;
45
+ /** The empty listing every backend degrades to, so callers can compare identity if useful. */
46
+ export declare const EMPTY_DIR: readonly RepoDirEntry[];
47
+ //# sourceMappingURL=reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader.d.ts","sourceRoot":"","sources":["../src/reader.ts"],"names":[],"mappings":"AAEA,2FAA2F;AAC3F,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,KAAK,CAAC;AAE3C,MAAM,WAAW,YAAY;IAC3B,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,aAAa,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;IACtD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC,CAAC;IACxD,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACxC;AAED,MAAM,MAAM,oBAAoB,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAErE,MAAM,MAAM,aAAa,GACrB,cAAc,GACd,WAAW,GACX,YAAY,GACZ,cAAc,GACd,cAAc,GACd,WAAW,GACX,qBAAqB,GACrB,SAAS,GACT,kBAAkB,CAAC;AAEvB,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,oBAAoB,CAAC;IAChC,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;IACtB,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,SAAS,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;AAEtD,8FAA8F;AAC9F,eAAO,MAAM,SAAS,EAAE,SAAS,YAAY,EAAsB,CAAC"}
package/dist/reader.js ADDED
@@ -0,0 +1,4 @@
1
+ // src/reader.ts — the read-only repo access port every backend implements (#468).
2
+ /** The empty listing every backend degrades to, so callers can compare identity if useful. */
3
+ export const EMPTY_DIR = Object.freeze([]);
4
+ //# sourceMappingURL=reader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader.js","sourceRoot":"","sources":["../src/reader.ts"],"names":[],"mappings":"AAAA,kFAAkF;AA+DlF,8FAA8F;AAC9F,MAAM,CAAC,MAAM,SAAS,GAA4B,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,45 @@
1
1
  {
2
2
  "name": "@on-par/repo-context",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.9.1",
4
+ "description": "Read-only repo reader behind a stable interface: GitHub contents-API and in-memory implementations.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "development": "./src/index.ts",
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "!dist/.tsbuildinfo"
18
+ ],
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/on-par/software-factory.git",
25
+ "directory": "packages/repo-context"
26
+ },
27
+ "homepage": "https://github.com/on-par/software-factory#readme",
28
+ "bugs": {
29
+ "url": "https://github.com/on-par/software-factory/issues"
30
+ },
31
+ "engines": {
32
+ "node": ">=24.0.0"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc -p tsconfig.build.json",
36
+ "typecheck": "tsc --noEmit -p tsconfig.json",
37
+ "test": "vitest run",
38
+ "prepublishOnly": "npm run build",
39
+ "prepack": "node ../../scripts/pack-hygiene-cli.ts prepack"
40
+ },
41
+ "license": "MIT",
42
+ "devDependencies": {
43
+ "@types/node": "^26.6.4"
44
+ }
45
+ }