@dogfood-lab/findings 1.2.2 → 1.2.3

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.
@@ -1,153 +1,153 @@
1
- /**
2
- * Record loader for the derivation engine.
3
- * Discovers and loads verified dogfood records from the filesystem.
4
- */
5
-
6
- import { readdirSync, readFileSync, existsSync, statSync } from 'node:fs';
7
- import { resolve, join, extname } from 'node:path';
8
-
9
- import { isUnsafeSegment } from '@dogfood-lab/ingest/lib/unsafe-segment.js';
10
-
11
- /**
12
- * Load all records for a specific repo.
13
- *
14
- * @param {string} rootDir - dogfood-labs repo root.
15
- * @param {string} repoKey - Full org/repo key (e.g. "mcp-tool-shop-org/repo-crawler-mcp").
16
- * @returns {Array<{ record: object, rejected: boolean, path: string }>}
17
- */
18
- export function loadRecordsForRepo(rootDir, repoKey) {
19
- const [org, repo] = repoKey.split('/');
20
- // Path-traversal guard: F-916867-005. Mirrors persist.js + load-context.js
21
- // via the central helper at @dogfood-lab/ingest/lib/unsafe-segment.js.
22
- // A malformed repoKey (`..` or path-separator) would otherwise resolve
23
- // outside the records tree and silently load (or skip) unrelated files.
24
- if (!org || !repo || isUnsafeSegment(org) || isUnsafeSegment(repo)) {
25
- return [];
26
- }
27
- const results = [];
28
-
29
- // Accepted records
30
- const acceptedDir = resolve(rootDir, 'records', org, repo);
31
- results.push(...walkRecords(acceptedDir, false));
32
-
33
- // Rejected records
34
- const rejectedDir = resolve(rootDir, 'records', '_rejected', org, repo);
35
- results.push(...walkRecords(rejectedDir, true));
36
-
37
- return results;
38
- }
39
-
40
- /**
41
- * Load a single record by run_id.
42
- *
43
- * @param {string} rootDir - dogfood-labs repo root.
44
- * @param {string} runId - The run_id to find.
45
- * @returns {{ record: object, rejected: boolean, path: string } | null}
46
- */
47
- export function loadRecordById(rootDir, runId) {
48
- // Search accepted records
49
- const acceptedRoot = resolve(rootDir, 'records');
50
- const found = findRecordFile(acceptedRoot, runId, false);
51
- if (found) return found;
52
-
53
- // Search rejected records
54
- const rejectedRoot = resolve(rootDir, 'records', '_rejected');
55
- return findRecordFile(rejectedRoot, runId, true);
56
- }
57
-
58
- /**
59
- * Load all records across all repos.
60
- *
61
- * @param {string} rootDir - dogfood-labs repo root.
62
- * @returns {Array<{ record: object, rejected: boolean, path: string }>}
63
- */
64
- export function loadAllRecords(rootDir) {
65
- const results = [];
66
-
67
- // Accepted records
68
- const recordsDir = resolve(rootDir, 'records');
69
- if (existsSync(recordsDir)) {
70
- for (const org of listDirs(recordsDir)) {
71
- if (org === '_rejected') continue;
72
- const orgDir = join(recordsDir, org);
73
- for (const repo of listDirs(orgDir)) {
74
- const repoDir = join(orgDir, repo);
75
- results.push(...walkRecords(repoDir, false));
76
- }
77
- }
78
- }
79
-
80
- // Rejected records
81
- const rejectedDir = resolve(rootDir, 'records', '_rejected');
82
- if (existsSync(rejectedDir)) {
83
- for (const org of listDirs(rejectedDir)) {
84
- const orgDir = join(rejectedDir, org);
85
- for (const repo of listDirs(orgDir)) {
86
- const repoDir = join(orgDir, repo);
87
- results.push(...walkRecords(repoDir, true));
88
- }
89
- }
90
- }
91
-
92
- return results;
93
- }
94
-
95
- /** Walk a record directory tree and load all .json files. */
96
- function walkRecords(dir, rejected) {
97
- if (!existsSync(dir)) return [];
98
- const results = [];
99
-
100
- function walk(d) {
101
- for (const entry of readdirSync(d)) {
102
- const full = join(d, entry);
103
- try {
104
- if (statSync(full).isDirectory()) {
105
- walk(full);
106
- } else if (extname(entry) === '.json') {
107
- const data = JSON.parse(readFileSync(full, 'utf-8'));
108
- results.push({ record: data, rejected, path: full });
109
- }
110
- } catch {
111
- // Skip unreadable files
112
- }
113
- }
114
- }
115
-
116
- walk(dir);
117
- return results;
118
- }
119
-
120
- /** Find a specific record file by run_id pattern. */
121
- function findRecordFile(rootDir, runId, rejected) {
122
- if (!existsSync(rootDir)) return null;
123
-
124
- function search(dir) {
125
- for (const entry of readdirSync(dir)) {
126
- const full = join(dir, entry);
127
- try {
128
- if (statSync(full).isDirectory()) {
129
- const found = search(full);
130
- if (found) return found;
131
- } else if (extname(entry) === '.json' && entry.includes(runId)) {
132
- const data = JSON.parse(readFileSync(full, 'utf-8'));
133
- if (data.run_id === runId) {
134
- return { record: data, rejected, path: full };
135
- }
136
- }
137
- } catch {
138
- // Skip
139
- }
140
- }
141
- return null;
142
- }
143
-
144
- return search(rootDir);
145
- }
146
-
147
- function listDirs(dir) {
148
- if (!existsSync(dir)) return [];
149
- return readdirSync(dir).filter(name => {
150
- try { return statSync(join(dir, name)).isDirectory(); }
151
- catch { return false; }
152
- });
153
- }
1
+ /**
2
+ * Record loader for the derivation engine.
3
+ * Discovers and loads verified dogfood records from the filesystem.
4
+ */
5
+
6
+ import { readdirSync, readFileSync, existsSync, statSync } from 'node:fs';
7
+ import { resolve, join, extname } from 'node:path';
8
+
9
+ import { isUnsafeSegment } from '@dogfood-lab/ingest/lib/unsafe-segment.js';
10
+
11
+ /**
12
+ * Load all records for a specific repo.
13
+ *
14
+ * @param {string} rootDir - dogfood-labs repo root.
15
+ * @param {string} repoKey - Full org/repo key (e.g. "mcp-tool-shop-org/repo-crawler-mcp").
16
+ * @returns {Array<{ record: object, rejected: boolean, path: string }>}
17
+ */
18
+ export function loadRecordsForRepo(rootDir, repoKey) {
19
+ const [org, repo] = repoKey.split('/');
20
+ // Path-traversal guard: F-916867-005. Mirrors persist.js + load-context.js
21
+ // via the central helper at @dogfood-lab/ingest/lib/unsafe-segment.js.
22
+ // A malformed repoKey (`..` or path-separator) would otherwise resolve
23
+ // outside the records tree and silently load (or skip) unrelated files.
24
+ if (!org || !repo || isUnsafeSegment(org) || isUnsafeSegment(repo)) {
25
+ return [];
26
+ }
27
+ const results = [];
28
+
29
+ // Accepted records
30
+ const acceptedDir = resolve(rootDir, 'records', org, repo);
31
+ results.push(...walkRecords(acceptedDir, false));
32
+
33
+ // Rejected records
34
+ const rejectedDir = resolve(rootDir, 'records', '_rejected', org, repo);
35
+ results.push(...walkRecords(rejectedDir, true));
36
+
37
+ return results;
38
+ }
39
+
40
+ /**
41
+ * Load a single record by run_id.
42
+ *
43
+ * @param {string} rootDir - dogfood-labs repo root.
44
+ * @param {string} runId - The run_id to find.
45
+ * @returns {{ record: object, rejected: boolean, path: string } | null}
46
+ */
47
+ export function loadRecordById(rootDir, runId) {
48
+ // Search accepted records
49
+ const acceptedRoot = resolve(rootDir, 'records');
50
+ const found = findRecordFile(acceptedRoot, runId, false);
51
+ if (found) return found;
52
+
53
+ // Search rejected records
54
+ const rejectedRoot = resolve(rootDir, 'records', '_rejected');
55
+ return findRecordFile(rejectedRoot, runId, true);
56
+ }
57
+
58
+ /**
59
+ * Load all records across all repos.
60
+ *
61
+ * @param {string} rootDir - dogfood-labs repo root.
62
+ * @returns {Array<{ record: object, rejected: boolean, path: string }>}
63
+ */
64
+ export function loadAllRecords(rootDir) {
65
+ const results = [];
66
+
67
+ // Accepted records
68
+ const recordsDir = resolve(rootDir, 'records');
69
+ if (existsSync(recordsDir)) {
70
+ for (const org of listDirs(recordsDir)) {
71
+ if (org === '_rejected') continue;
72
+ const orgDir = join(recordsDir, org);
73
+ for (const repo of listDirs(orgDir)) {
74
+ const repoDir = join(orgDir, repo);
75
+ results.push(...walkRecords(repoDir, false));
76
+ }
77
+ }
78
+ }
79
+
80
+ // Rejected records
81
+ const rejectedDir = resolve(rootDir, 'records', '_rejected');
82
+ if (existsSync(rejectedDir)) {
83
+ for (const org of listDirs(rejectedDir)) {
84
+ const orgDir = join(rejectedDir, org);
85
+ for (const repo of listDirs(orgDir)) {
86
+ const repoDir = join(orgDir, repo);
87
+ results.push(...walkRecords(repoDir, true));
88
+ }
89
+ }
90
+ }
91
+
92
+ return results;
93
+ }
94
+
95
+ /** Walk a record directory tree and load all .json files. */
96
+ function walkRecords(dir, rejected) {
97
+ if (!existsSync(dir)) return [];
98
+ const results = [];
99
+
100
+ function walk(d) {
101
+ for (const entry of readdirSync(d)) {
102
+ const full = join(d, entry);
103
+ try {
104
+ if (statSync(full).isDirectory()) {
105
+ walk(full);
106
+ } else if (extname(entry) === '.json') {
107
+ const data = JSON.parse(readFileSync(full, 'utf-8'));
108
+ results.push({ record: data, rejected, path: full });
109
+ }
110
+ } catch {
111
+ // Skip unreadable files
112
+ }
113
+ }
114
+ }
115
+
116
+ walk(dir);
117
+ return results;
118
+ }
119
+
120
+ /** Find a specific record file by run_id pattern. */
121
+ function findRecordFile(rootDir, runId, rejected) {
122
+ if (!existsSync(rootDir)) return null;
123
+
124
+ function search(dir) {
125
+ for (const entry of readdirSync(dir)) {
126
+ const full = join(dir, entry);
127
+ try {
128
+ if (statSync(full).isDirectory()) {
129
+ const found = search(full);
130
+ if (found) return found;
131
+ } else if (extname(entry) === '.json' && entry.includes(runId)) {
132
+ const data = JSON.parse(readFileSync(full, 'utf-8'));
133
+ if (data.run_id === runId) {
134
+ return { record: data, rejected, path: full };
135
+ }
136
+ }
137
+ } catch {
138
+ // Skip
139
+ }
140
+ }
141
+ return null;
142
+ }
143
+
144
+ return search(rootDir);
145
+ }
146
+
147
+ function listDirs(dir) {
148
+ if (!existsSync(dir)) return [];
149
+ return readdirSync(dir).filter(name => {
150
+ try { return statSync(join(dir, name)).isDirectory(); }
151
+ catch { return false; }
152
+ });
153
+ }