great-cto 3.72.0 → 3.74.0
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/board/.claude-plugin/plugin.json +1 -1
- package/board/agents/accounting-reviewer.md +2 -2
- package/board/agents/adtech-privacy-reviewer.md +3 -3
- package/board/agents/ai-eval-engineer.md +3 -3
- package/board/agents/ai-prompt-architect.md +3 -3
- package/board/agents/ai-security-reviewer.md +2 -2
- package/board/agents/api-platform-reviewer.md +2 -2
- package/board/agents/app-scaffolder.md +3 -3
- package/board/agents/architect.md +8 -8
- package/board/agents/auth-engineer.md +4 -4
- package/board/agents/ci-resolver.md +5 -5
- package/board/agents/cli-reviewer.md +2 -2
- package/board/agents/cmmc-reviewer.md +2 -2
- package/board/agents/cms-reviewer.md +2 -2
- package/board/agents/code-reviewer.md +4 -4
- package/board/agents/connector-builder.md +5 -5
- package/board/agents/continuous-learner.md +3 -3
- package/board/agents/coordinator.md +4 -4
- package/board/agents/data-platform-reviewer.md +2 -2
- package/board/agents/db-migration-reviewer.md +2 -2
- package/board/agents/decision-scorer.md +3 -3
- package/board/agents/design-advisor.md +3 -3
- package/board/agents/devops.md +12 -12
- package/board/agents/devtools-reviewer.md +2 -2
- package/board/agents/dpdpa-reviewer.md +2 -2
- package/board/agents/e2e-test-engineer.md +1 -1
- package/board/agents/edtech-reviewer.md +3 -3
- package/board/agents/enterprise-saas-reviewer.md +2 -2
- package/board/agents/firmware-reviewer.md +2 -2
- package/board/agents/game-reviewer.md +2 -2
- package/board/agents/gdpr-reviewer.md +3 -3
- package/board/agents/geo-routing-engineer.md +5 -5
- package/board/agents/gov-reviewer.md +2 -2
- package/board/agents/growth-engineer.md +3 -3
- package/board/agents/healthcare-reviewer.md +3 -3
- package/board/agents/hr-ai-reviewer.md +3 -3
- package/board/agents/infra-provisioner.md +3 -3
- package/board/agents/infra-reviewer.md +2 -2
- package/board/agents/insurance-reviewer.md +2 -2
- package/board/agents/integrations-engineer.md +5 -5
- package/board/agents/knowledge-extractor.md +2 -2
- package/board/agents/l3-support.md +8 -8
- package/board/agents/legal-reviewer.md +3 -3
- package/board/agents/library-reviewer.md +2 -2
- package/board/agents/marketplace-reviewer.md +2 -2
- package/board/agents/mcp-server-reviewer.md +2 -2
- package/board/agents/media-pipeline-engineer.md +5 -5
- package/board/agents/migration-import-engineer.md +5 -5
- package/board/agents/mlops-reviewer.md +2 -2
- package/board/agents/mobile-app-builder.md +6 -6
- package/board/agents/mobile-store-reviewer.md +2 -2
- package/board/agents/msp-reviewer.md +2 -2
- package/board/agents/oracle-reviewer.md +2 -2
- package/board/agents/pci-reviewer.md +2 -2
- package/board/agents/performance-engineer.md +3 -3
- package/board/agents/pm.md +6 -6
- package/board/agents/procurement-reviewer.md +2 -2
- package/board/agents/product-owner.md +5 -5
- package/board/agents/project-auditor.md +6 -6
- package/board/agents/qa-engineer.md +11 -11
- package/board/agents/quant-researcher.md +2 -2
- package/board/agents/rcm-reviewer.md +3 -3
- package/board/agents/regulated-reviewer.md +2 -2
- package/board/agents/security-officer.md +8 -8
- package/board/agents/senior-dev.md +7 -7
- package/board/agents/smart-contract-auditor.md +4 -4
- package/board/agents/streaming-reviewer.md +2 -2
- package/board/agents/subscription-billing-engineer.md +5 -5
- package/board/agents/tax-reviewer.md +3 -3
- package/board/agents/us-ai-reviewer.md +2 -2
- package/board/agents/us-privacy-reviewer.md +3 -3
- package/board/agents/voice-ai-reviewer.md +3 -3
- package/board/agents/web-store-reviewer.md +2 -2
- package/board/scripts/codex-pipeline.mjs +42 -7
- package/board/scripts/lib/agent-events.mjs +5 -1
- package/board/scripts/lib/cost-meter.mjs +34 -2
- package/board/scripts/lib/cross-model-review.mjs +107 -9
- package/board/scripts/lib/gate-tier.mjs +1 -1
- package/board/scripts/lib/host-graph-bindings.mjs +15 -3
- package/board/scripts/lib/host-graph-events.mjs +23 -4
- package/board/scripts/lib/host-graph-runtime.mjs +10 -3
- package/board/scripts/lib/kimi-review-profile.mjs +551 -0
- package/board/scripts/lib/kimi-review-store.mjs +469 -0
- package/board/scripts/lib/system-map.mjs +4 -2
- package/board/scripts/lib/work-publication.mjs +10 -9
- package/board/scripts/skill-lint.mjs +3 -3
- package/dist/main.js +82 -16
- package/dist/release-activation.mjs +3 -1
- package/dist/telemetry.js +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
4
|
+
|
|
5
|
+
const HASH = /^[a-f0-9]{64}$/;
|
|
6
|
+
const UUID = /^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i;
|
|
7
|
+
const HOLD = 0.25;
|
|
8
|
+
const MISSING = Symbol('missing-file');
|
|
9
|
+
const MAX_FILE_BYTES = 16 * 1024 * 1024;
|
|
10
|
+
const ACTIVE = new Set(['preparing', 'reserved', 'sent', 'Unknown']);
|
|
11
|
+
const STATES = new Set([...ACTIVE, 'complete', 'no-send', 'breach']);
|
|
12
|
+
const METADATA_KEYS = new Set([
|
|
13
|
+
'authority', 'releaseEligibility', 'status', 'reasonCode', 'bindingDigest',
|
|
14
|
+
'requestedModel', 'observedModel', 'requestedEndpoint', 'observedProvider',
|
|
15
|
+
'completionReason', 'usageState', 'costState', 'costUsd', 'reservationUsd',
|
|
16
|
+
'promptHash', 'responseHash', 'elapsedMs', 'profileHash', 'schemaHash',
|
|
17
|
+
'inputDigest', 'manifestHash', 'acceptanceHash', 'sourceSha', 'taskId',
|
|
18
|
+
'mode', 'contextClass', 'reason', 'requestId', 'advisory', 'metadataCheckedAt',
|
|
19
|
+
'httpStatus', 'diagnosticCount',
|
|
20
|
+
'evidenceVersion', 'responseState', 'deadlineMs', 'validatedElapsedMs', 'callerOutcome',
|
|
21
|
+
]);
|
|
22
|
+
const USAGE_KEYS = new Set([
|
|
23
|
+
'promptTokens', 'completionTokens', 'reasoningTokens', 'cacheReadTokens',
|
|
24
|
+
'prompt_tokens', 'completion_tokens', 'total_tokens', 'reasoning_tokens',
|
|
25
|
+
'cached_tokens', 'cache_read_tokens', 'providerReportedCost', 'calculatedCostUsd',
|
|
26
|
+
'costUsd', 'inputTokens', 'outputTokens', 'cachedTokens',
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
class StoreError extends Error {
|
|
30
|
+
constructor(reasonCode) { super(reasonCode); this.reasonCode = reasonCode; }
|
|
31
|
+
}
|
|
32
|
+
const fail = reasonCode => { throw new StoreError(reasonCode); };
|
|
33
|
+
const deny = error => ({ ok: false, reasonCode: error instanceof StoreError ? error.reasonCode : 'persistence-failed' });
|
|
34
|
+
const hashTask = taskId => createHash('sha256').update(taskId).digest('hex');
|
|
35
|
+
const hashJson = value => createHash('sha256').update(JSON.stringify(value) + '\n').digest('hex');
|
|
36
|
+
const finiteCost = value => typeof value === 'number' && Number.isFinite(value) && value >= 0;
|
|
37
|
+
const isObject = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
38
|
+
const ownUid = process.getuid?.();
|
|
39
|
+
|
|
40
|
+
function checkedDirectory(directory, { privateMode = false, ancestor = false } = {}) {
|
|
41
|
+
const stat = fs.lstatSync(directory);
|
|
42
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) fail('unsafe-path');
|
|
43
|
+
if (ownUid !== undefined && stat.uid !== ownUid && !(ancestor && stat.uid === 0)) fail('unsafe-path');
|
|
44
|
+
const permissions = stat.mode & 0o777;
|
|
45
|
+
const trustedSticky = ancestor && stat.uid === 0 && (stat.mode & 0o1000);
|
|
46
|
+
if ((permissions & 0o022) && !trustedSticky) fail('unsafe-path');
|
|
47
|
+
if (privateMode && permissions !== 0o700) fail('unsafe-path');
|
|
48
|
+
if ((permissions & 0o500) !== 0o500) fail('unsafe-path');
|
|
49
|
+
return stat;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function checkedAncestors(directory) {
|
|
53
|
+
const parsed = path.parse(directory);
|
|
54
|
+
let current = parsed.root;
|
|
55
|
+
checkedDirectory(current, { ancestor: true });
|
|
56
|
+
for (const part of directory.slice(parsed.root.length).split(path.sep).filter(Boolean)) {
|
|
57
|
+
current = path.join(current, part);
|
|
58
|
+
checkedDirectory(current, { ancestor: true });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function ensurePrivateDirectory(directory) {
|
|
63
|
+
let created = false;
|
|
64
|
+
try { fs.mkdirSync(directory, { mode: 0o700 }); created = true; }
|
|
65
|
+
catch (error) { if (error.code !== 'EEXIST') throw error; }
|
|
66
|
+
checkedDirectory(directory, { privateMode: true });
|
|
67
|
+
if (created) syncDirectory(path.dirname(directory));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function checkedFile(file, { missing = false } = {}) {
|
|
71
|
+
let stat;
|
|
72
|
+
try { stat = fs.lstatSync(file); }
|
|
73
|
+
catch (error) { if (missing && error.code === 'ENOENT') return null; throw error; }
|
|
74
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 ||
|
|
75
|
+
(ownUid !== undefined && stat.uid !== ownUid) || (stat.mode & 0o777) !== 0o600) fail('unsafe-path');
|
|
76
|
+
return stat;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function readJson(file, { missing = false } = {}) {
|
|
80
|
+
const stat = checkedFile(file, { missing });
|
|
81
|
+
if (!stat) return MISSING;
|
|
82
|
+
if (stat.size > MAX_FILE_BYTES) fail('ledger-invalid');
|
|
83
|
+
const fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
84
|
+
try {
|
|
85
|
+
const opened = fs.fstatSync(fd);
|
|
86
|
+
if (opened.dev !== stat.dev || opened.ino !== stat.ino) fail('unsafe-path');
|
|
87
|
+
try { return JSON.parse(fs.readFileSync(fd, 'utf8')); }
|
|
88
|
+
catch { fail('ledger-invalid'); }
|
|
89
|
+
} finally { fs.closeSync(fd); }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function syncDirectory(directory) {
|
|
93
|
+
const fd = fs.openSync(directory, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
94
|
+
try { fs.fsyncSync(fd); } finally { fs.closeSync(fd); }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function atomicJson(file, value) {
|
|
98
|
+
checkedDirectory(path.dirname(file), { privateMode: true });
|
|
99
|
+
checkedFile(file, { missing: true });
|
|
100
|
+
const bytes = JSON.stringify(value) + '\n';
|
|
101
|
+
if (Buffer.byteLength(bytes) > MAX_FILE_BYTES) fail('persistence-failed');
|
|
102
|
+
const temporary = path.join(path.dirname(file), `.kimi-${randomUUID()}.tmp`);
|
|
103
|
+
let fd;
|
|
104
|
+
let renamed = false;
|
|
105
|
+
try {
|
|
106
|
+
fd = fs.openSync(temporary, fs.constants.O_WRONLY | fs.constants.O_CREAT |
|
|
107
|
+
fs.constants.O_EXCL | fs.constants.O_NOFOLLOW, 0o600);
|
|
108
|
+
fs.fchmodSync(fd, 0o600);
|
|
109
|
+
fs.writeFileSync(fd, bytes);
|
|
110
|
+
fs.fsyncSync(fd);
|
|
111
|
+
fs.closeSync(fd);
|
|
112
|
+
fd = undefined;
|
|
113
|
+
checkedFile(file, { missing: true });
|
|
114
|
+
fs.renameSync(temporary, file);
|
|
115
|
+
renamed = true;
|
|
116
|
+
syncDirectory(path.dirname(file));
|
|
117
|
+
} catch (error) {
|
|
118
|
+
// A rename visible to readers is not a durable commit until its directory syncs.
|
|
119
|
+
if (renamed) error.commitUncertain = true;
|
|
120
|
+
throw error;
|
|
121
|
+
} finally {
|
|
122
|
+
if (fd !== undefined) fs.closeSync(fd);
|
|
123
|
+
try { fs.unlinkSync(temporary); } catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function acquireLock(file, timestamp) {
|
|
128
|
+
let fd;
|
|
129
|
+
try {
|
|
130
|
+
fd = fs.openSync(file, fs.constants.O_WRONLY | fs.constants.O_CREAT |
|
|
131
|
+
fs.constants.O_EXCL | fs.constants.O_NOFOLLOW, 0o600);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (error.code === 'EEXIST') {
|
|
134
|
+
checkedFile(file);
|
|
135
|
+
fail('lock-contended');
|
|
136
|
+
}
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
const identity = fs.fstatSync(fd);
|
|
140
|
+
try {
|
|
141
|
+
fs.fchmodSync(fd, 0o600);
|
|
142
|
+
fs.writeFileSync(fd, JSON.stringify({ version: 1, pid: process.pid, timestamp }));
|
|
143
|
+
fs.fsyncSync(fd);
|
|
144
|
+
syncDirectory(path.dirname(file));
|
|
145
|
+
} catch (error) {
|
|
146
|
+
fs.closeSync(fd);
|
|
147
|
+
fs.unlinkSync(file);
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
fs.closeSync(fd);
|
|
151
|
+
return () => {
|
|
152
|
+
const current = checkedFile(file);
|
|
153
|
+
if (current.dev !== identity.dev || current.ino !== identity.ino) fail('unsafe-path');
|
|
154
|
+
fs.unlinkSync(file);
|
|
155
|
+
syncDirectory(path.dirname(file));
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function validIdentity(value) {
|
|
160
|
+
return isObject(value) && typeof value.taskId === 'string' && value.taskId.length > 0 &&
|
|
161
|
+
Buffer.byteLength(value.taskId) <= 256 && UUID.test(value.requestId) &&
|
|
162
|
+
HASH.test(value.bindingDigest) && value.reservationUsd === HOLD;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function validToken(token) {
|
|
166
|
+
return validIdentity(token) && token.version === 1 && token.taskHash === hashTask(token.taskId);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function validateLedger(ledger) {
|
|
170
|
+
if (!isObject(ledger) || ledger.version !== 1 || !Number.isSafeInteger(ledger.lastObservedAt) ||
|
|
171
|
+
ledger.lastObservedAt < 0 || !Array.isArray(ledger.requests)) fail('ledger-invalid');
|
|
172
|
+
const identities = new Set();
|
|
173
|
+
for (const record of ledger.requests) {
|
|
174
|
+
if (!isObject(record) || !validToken(record.token) || !STATES.has(record.state) ||
|
|
175
|
+
!Number.isSafeInteger(record.createdAt) || record.createdAt < 0 ||
|
|
176
|
+
!Number.isSafeInteger(record.updatedAt) || record.updatedAt < record.createdAt ||
|
|
177
|
+
record.updatedAt > ledger.lastObservedAt || typeof record.sent !== 'boolean' ||
|
|
178
|
+
![0, HOLD].includes(record.reservationUsd) ||
|
|
179
|
+
!(record.costUsd === null || finiteCost(record.costUsd)) ||
|
|
180
|
+
!['known', 'Unknown', 'not-incurred', 'breach'].includes(record.costState)) fail('ledger-invalid');
|
|
181
|
+
if (record.evidence !== null && (!isObject(record.evidence) ||
|
|
182
|
+
!HASH.test(record.evidence.metadataSha256) || !HASH.test(record.evidence.reportSha256))) fail('ledger-invalid');
|
|
183
|
+
if (ACTIVE.has(record.state) && record.reservationUsd !== HOLD) fail('ledger-invalid');
|
|
184
|
+
if (!ACTIVE.has(record.state) && record.reservationUsd !== 0) fail('ledger-invalid');
|
|
185
|
+
if (['complete', 'breach'].includes(record.state) && !finiteCost(record.costUsd)) fail('ledger-invalid');
|
|
186
|
+
const key = `${record.token.taskHash}/${record.token.requestId}`;
|
|
187
|
+
if (identities.has(key)) fail('ledger-invalid');
|
|
188
|
+
identities.add(key);
|
|
189
|
+
}
|
|
190
|
+
return ledger;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function normalizedUsage(usage) {
|
|
194
|
+
if (!isObject(usage)) return null;
|
|
195
|
+
const raw = Object.hasOwn(usage, 'prompt_tokens') || Object.hasOwn(usage, 'completion_tokens');
|
|
196
|
+
const normalized = ['promptTokens', 'completionTokens', 'reasoningTokens', 'cacheReadTokens']
|
|
197
|
+
.some(key => Object.hasOwn(usage, key));
|
|
198
|
+
if (raw && normalized) return null;
|
|
199
|
+
if (!raw) return usage;
|
|
200
|
+
// Missing detail counters stay undefined so admission accounting cannot infer zero.
|
|
201
|
+
return { promptTokens: usage.prompt_tokens, completionTokens: usage.completion_tokens,
|
|
202
|
+
reasoningTokens: usage.completion_tokens_details?.reasoning_tokens,
|
|
203
|
+
cacheReadTokens: usage.prompt_tokens_details?.cached_tokens,
|
|
204
|
+
providerReportedCost: usage.cost === undefined ? null : usage.cost,
|
|
205
|
+
...(usage.total_tokens === undefined ? {} : { total_tokens: usage.total_tokens }) };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function safeUsage(value) {
|
|
209
|
+
const usage = normalizedUsage(value);
|
|
210
|
+
if (!usage) return null;
|
|
211
|
+
const result = {};
|
|
212
|
+
for (const [key, value] of Object.entries(usage)) {
|
|
213
|
+
if (USAGE_KEYS.has(key) && finiteCost(value)) result[key] = value;
|
|
214
|
+
if (key === 'providerReportedCost' && value === null) result[key] = null;
|
|
215
|
+
if (key === 'costProvenance' && typeof value === 'string' && /^[a-zA-Z0-9._:/-]{1,128}$/.test(value)) result[key] = value;
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function completeUsage(value) {
|
|
221
|
+
const usage = normalizedUsage(value);
|
|
222
|
+
if (!usage) return false;
|
|
223
|
+
const counters = ['promptTokens', 'completionTokens', 'reasoningTokens', 'cacheReadTokens'];
|
|
224
|
+
if (!counters.every(key => Number.isSafeInteger(usage[key]) && usage[key] >= 0)) return false;
|
|
225
|
+
if (usage.reasoningTokens > usage.completionTokens || usage.cacheReadTokens > usage.promptTokens) return false;
|
|
226
|
+
if (usage.total_tokens !== undefined && usage.total_tokens !== usage.promptTokens + usage.completionTokens) return false;
|
|
227
|
+
return Object.entries(usage).every(([key, value]) => !USAGE_KEYS.has(key) ||
|
|
228
|
+
(key === 'providerReportedCost' && value === null) || finiteCost(value));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function safeMetadata(metadata) {
|
|
232
|
+
const result = {};
|
|
233
|
+
if (!isObject(metadata)) return result;
|
|
234
|
+
if (Object.hasOwn(metadata, 'evidenceVersion')) {
|
|
235
|
+
if (metadata.evidenceVersion !== 2 || !['validated', 'invalid', 'incomplete'].includes(metadata.responseState) ||
|
|
236
|
+
!Number.isSafeInteger(metadata.deadlineMs) || metadata.deadlineMs <= 0 || metadata.deadlineMs > 180000 ||
|
|
237
|
+
metadata.callerOutcome !== 'not-recorded' || (metadata.responseState === 'validated'
|
|
238
|
+
? !finiteCost(metadata.validatedElapsedMs) || metadata.validatedElapsedMs > Number.MAX_SAFE_INTEGER
|
|
239
|
+
: metadata.validatedElapsedMs !== null) || metadata.status === 'complete') fail('evidence-invalid');
|
|
240
|
+
} else if (['responseState', 'deadlineMs', 'validatedElapsedMs', 'callerOutcome'].some(key => Object.hasOwn(metadata, key))) {
|
|
241
|
+
fail('evidence-invalid');
|
|
242
|
+
}
|
|
243
|
+
if (metadata.status === 'validated' && (metadata.evidenceVersion !== 2 || metadata.responseState !== 'validated' ||
|
|
244
|
+
metadata.reasonCode !== 'response-validated')) fail('evidence-invalid');
|
|
245
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
246
|
+
if (METADATA_KEYS.has(key)) {
|
|
247
|
+
if (value === null || typeof value === 'boolean' || finiteCost(value)) result[key] = value;
|
|
248
|
+
else if (typeof value === 'string' && Buffer.byteLength(value) <= 256 &&
|
|
249
|
+
(key === 'taskId' || /^[a-zA-Z0-9._:/ -]*$/.test(value))) result[key] = value;
|
|
250
|
+
}
|
|
251
|
+
if (key === 'metadataHashes' && isObject(value)) {
|
|
252
|
+
result.metadataHashes = Object.fromEntries(['models', 'endpoints', 'zdr']
|
|
253
|
+
.filter(name => typeof value[name] === 'string' && HASH.test(value[name]))
|
|
254
|
+
.map(name => [name, value[name]]));
|
|
255
|
+
}
|
|
256
|
+
if (key === 'usage') result.usage = safeUsage(value);
|
|
257
|
+
}
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function withinBudgets(ledger, token, timestamp) {
|
|
262
|
+
const date = new Date(timestamp).toISOString();
|
|
263
|
+
let task = HOLD, day = HOLD, month = HOLD;
|
|
264
|
+
for (const record of ledger.requests) {
|
|
265
|
+
const cost = record.costUsd ?? 0;
|
|
266
|
+
const reserve = record.reservationUsd;
|
|
267
|
+
if (record.token.taskHash === token.taskHash) task += cost + reserve;
|
|
268
|
+
const chargedDate = new Date(record.createdAt).toISOString();
|
|
269
|
+
day += reserve + (chargedDate.slice(0, 10) === date.slice(0, 10) ? cost : 0);
|
|
270
|
+
month += reserve + (chargedDate.slice(0, 7) === date.slice(0, 7) ? cost : 0);
|
|
271
|
+
}
|
|
272
|
+
return task <= 1 + Number.EPSILON && day <= 5 + Number.EPSILON && month <= 20 + Number.EPSILON;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Explicit roots are capabilities. Constructing a store never reads or creates state. */
|
|
276
|
+
export function createKimiReviewStore({ root, operatorRoot, now = () => Date.now() } = {}) {
|
|
277
|
+
if (typeof root !== 'string' || !path.isAbsolute(root) || typeof operatorRoot !== 'string' ||
|
|
278
|
+
!path.isAbsolute(operatorRoot) || typeof now !== 'function') throw new TypeError('Explicit absolute root and operatorRoot are required');
|
|
279
|
+
if (root !== path.resolve(root) || operatorRoot !== path.resolve(operatorRoot)) throw new TypeError('Roots must be canonical absolute paths');
|
|
280
|
+
const operatorFile = path.join(operatorRoot, 'kimi-review-ledger.json');
|
|
281
|
+
|
|
282
|
+
function paths(token) {
|
|
283
|
+
const taskDir = path.join(root, '.great_cto', 'kimi-review', token.taskHash);
|
|
284
|
+
const requestDir = path.join(taskDir, token.requestId);
|
|
285
|
+
return { taskDir, requestDir, taskFile: path.join(taskDir, 'ledger.json'),
|
|
286
|
+
metadataFile: path.join(requestDir, 'metadata.json'), reportFile: path.join(requestDir, 'report.json') };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function prepareRoots() {
|
|
290
|
+
checkedAncestors(root);
|
|
291
|
+
checkedDirectory(root);
|
|
292
|
+
checkedAncestors(path.dirname(operatorRoot));
|
|
293
|
+
ensurePrivateDirectory(operatorRoot);
|
|
294
|
+
ensurePrivateDirectory(path.join(root, '.great_cto'));
|
|
295
|
+
ensurePrivateDirectory(path.join(root, '.great_cto', 'kimi-review'));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function transaction(token, callback) {
|
|
299
|
+
const timestamp = now();
|
|
300
|
+
if (!Number.isSafeInteger(timestamp) || timestamp < 0 || timestamp > 8640000000000000) fail('clock-invalid');
|
|
301
|
+
prepareRoots();
|
|
302
|
+
const unlockOperator = acquireLock(operatorFile + '.lock', timestamp);
|
|
303
|
+
let unlockTask;
|
|
304
|
+
let uncertainCommit = false;
|
|
305
|
+
try {
|
|
306
|
+
const locations = paths(token);
|
|
307
|
+
ensurePrivateDirectory(locations.taskDir);
|
|
308
|
+
unlockTask = acquireLock(locations.taskFile + '.lock', timestamp);
|
|
309
|
+
const loaded = readJson(operatorFile, { missing: true });
|
|
310
|
+
const ledger = loaded === MISSING ? { version: 1, lastObservedAt: timestamp, requests: [] } : validateLedger(loaded);
|
|
311
|
+
if (timestamp < ledger.lastObservedAt) fail('clock-backward');
|
|
312
|
+
const taskLedger = readJson(locations.taskFile, { missing: true });
|
|
313
|
+
if (taskLedger !== MISSING) validateLedger(taskLedger);
|
|
314
|
+
// A secondary record without its authority is a damaged installation, not a fresh budget.
|
|
315
|
+
if (loaded === MISSING && taskLedger !== MISSING) fail('ledger-invalid');
|
|
316
|
+
ledger.lastObservedAt = timestamp;
|
|
317
|
+
return callback(ledger, locations, timestamp);
|
|
318
|
+
} catch (error) {
|
|
319
|
+
uncertainCommit = error.commitUncertain === true;
|
|
320
|
+
throw error;
|
|
321
|
+
} finally {
|
|
322
|
+
// A sync failure leaves owned diagnostic locks for explicit recovery. Releasing
|
|
323
|
+
// those locks would permit admission against a commit whose durability is unknown.
|
|
324
|
+
if (!uncertainCommit) {
|
|
325
|
+
try { if (unlockTask) unlockTask(); } finally { unlockOperator(); }
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function writeTask(ledger, locations) {
|
|
331
|
+
atomicJson(locations.taskFile, { version: 1, lastObservedAt: ledger.lastObservedAt,
|
|
332
|
+
requests: ledger.requests.filter(record => record.token.taskHash === path.basename(locations.taskDir)) });
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function findRecord(ledger, token) {
|
|
336
|
+
const record = ledger.requests.find(item => item.token.taskHash === token.taskHash && item.token.requestId === token.requestId);
|
|
337
|
+
if (!record || Object.keys(record.token).some(key => record.token[key] !== token[key])) fail('invalid-token');
|
|
338
|
+
return record;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
async function begin(request) {
|
|
342
|
+
try {
|
|
343
|
+
if (!validIdentity(request)) fail('invalid-input');
|
|
344
|
+
const metadata = safeMetadata(request.metadata);
|
|
345
|
+
const token = { version: 1, taskId: request.taskId, taskHash: hashTask(request.taskId),
|
|
346
|
+
requestId: request.requestId, bindingDigest: request.bindingDigest, reservationUsd: HOLD };
|
|
347
|
+
return transaction(token, (ledger, locations, timestamp) => {
|
|
348
|
+
if (ledger.requests.some(record => record.token.requestId === token.requestId)) fail('duplicate-request');
|
|
349
|
+
if (ledger.requests.some(record => record.token.taskHash === token.taskHash &&
|
|
350
|
+
(ACTIVE.has(record.state) || record.state === 'breach'))) fail('task-unresolved');
|
|
351
|
+
if (!withinBudgets(ledger, token, timestamp)) fail('budget-denied');
|
|
352
|
+
const record = { token, state: 'preparing', sent: false, costState: 'Unknown',
|
|
353
|
+
costUsd: null, reservationUsd: HOLD, evidence: null, createdAt: timestamp, updatedAt: timestamp };
|
|
354
|
+
ledger.requests.push(record);
|
|
355
|
+
// Commit the full hold first. Every subsequent failure leaves it unresolved.
|
|
356
|
+
atomicJson(operatorFile, ledger);
|
|
357
|
+
// An existing UUID directory may be orphaned evidence. Never replace its bytes.
|
|
358
|
+
fs.mkdirSync(locations.requestDir, { mode: 0o700 });
|
|
359
|
+
checkedDirectory(locations.requestDir, { privateMode: true });
|
|
360
|
+
syncDirectory(locations.taskDir);
|
|
361
|
+
atomicJson(locations.metadataFile, { ...metadata, taskId: token.taskId,
|
|
362
|
+
requestId: token.requestId, bindingDigest: token.bindingDigest, reservationUsd: HOLD });
|
|
363
|
+
record.state = 'reserved';
|
|
364
|
+
writeTask(ledger, locations);
|
|
365
|
+
atomicJson(operatorFile, ledger);
|
|
366
|
+
return { ok: true, token };
|
|
367
|
+
});
|
|
368
|
+
} catch (error) { return deny(error); }
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async function markSent(token) {
|
|
372
|
+
try {
|
|
373
|
+
if (!validToken(token)) fail('invalid-token');
|
|
374
|
+
return transaction(token, (ledger, locations, timestamp) => {
|
|
375
|
+
const record = findRecord(ledger, token);
|
|
376
|
+
if (record.state !== 'reserved') fail('task-unresolved');
|
|
377
|
+
record.state = 'sent';
|
|
378
|
+
record.sent = true;
|
|
379
|
+
record.updatedAt = timestamp;
|
|
380
|
+
atomicJson(operatorFile, ledger);
|
|
381
|
+
const metadata = readJson(locations.metadataFile);
|
|
382
|
+
if (!isObject(metadata)) fail('ledger-invalid');
|
|
383
|
+
atomicJson(locations.metadataFile, { ...safeMetadata(metadata), status: 'sent' });
|
|
384
|
+
writeTask(ledger, locations);
|
|
385
|
+
return { ok: true };
|
|
386
|
+
});
|
|
387
|
+
} catch (error) { return deny(error); }
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async function finish(token, outcome) {
|
|
391
|
+
try {
|
|
392
|
+
if (!validToken(token) || !isObject(outcome)) fail('invalid-token');
|
|
393
|
+
return transaction(token, (ledger, locations, timestamp) => {
|
|
394
|
+
const record = findRecord(ledger, token);
|
|
395
|
+
if (!['reserved', 'sent'].includes(record.state)) fail('task-unresolved');
|
|
396
|
+
const provenNoSend = record.state === 'reserved' && !record.sent && outcome.sent === false &&
|
|
397
|
+
outcome.costState === 'not-incurred' && outcome.costUsd === 0;
|
|
398
|
+
const knownCost = ['known', 'breach'].includes(outcome.costState) && finiteCost(outcome.costUsd);
|
|
399
|
+
const usableReport = !['complete', 'validated'].includes(outcome.status) || isObject(outcome.report);
|
|
400
|
+
const validUsage = completeUsage(outcome.usage);
|
|
401
|
+
const canAccount = knownCost && usableReport && validUsage;
|
|
402
|
+
record.sent = record.sent || outcome.sent === true;
|
|
403
|
+
record.state = 'Unknown';
|
|
404
|
+
record.costState = 'Unknown';
|
|
405
|
+
record.evidence = null;
|
|
406
|
+
if (knownCost) record.costUsd = outcome.costUsd;
|
|
407
|
+
record.updatedAt = timestamp;
|
|
408
|
+
atomicJson(operatorFile, ledger);
|
|
409
|
+
const priorMetadata = readJson(locations.metadataFile);
|
|
410
|
+
if (!isObject(priorMetadata)) fail('ledger-invalid');
|
|
411
|
+
// Required evidence and the secondary accounting must be durable before a release.
|
|
412
|
+
const finalMetadata = { ...safeMetadata({ ...safeMetadata(priorMetadata), ...safeMetadata(outcome.metadata),
|
|
413
|
+
status: outcome.status }), costState: canAccount ? (outcome.costUsd > HOLD ? 'breach' : 'known') :
|
|
414
|
+
provenNoSend ? 'not-incurred' : 'Unknown', costUsd: canAccount ? outcome.costUsd : null,
|
|
415
|
+
usage: safeUsage(outcome.usage) };
|
|
416
|
+
const finalReport = outcome.report ?? null;
|
|
417
|
+
atomicJson(locations.metadataFile, finalMetadata);
|
|
418
|
+
atomicJson(locations.reportFile, finalReport);
|
|
419
|
+
const heldRecord = { ...record };
|
|
420
|
+
record.evidence = { metadataSha256: hashJson(finalMetadata), reportSha256: hashJson(finalReport) };
|
|
421
|
+
if (canAccount || provenNoSend) {
|
|
422
|
+
record.state = provenNoSend ? 'no-send' : outcome.costUsd > HOLD ? 'breach' : 'complete';
|
|
423
|
+
record.costState = provenNoSend ? 'not-incurred' : outcome.costUsd > HOLD ? 'breach' : 'known';
|
|
424
|
+
record.costUsd = provenNoSend ? 0 : outcome.costUsd;
|
|
425
|
+
record.reservationUsd = 0;
|
|
426
|
+
}
|
|
427
|
+
writeTask(ledger, locations);
|
|
428
|
+
try { atomicJson(operatorFile, ledger); }
|
|
429
|
+
catch (error) {
|
|
430
|
+
// Keep the known cost and the full hold if the final release was ambiguous.
|
|
431
|
+
// If storage stays broken, diagnostic locks remain the admission fence.
|
|
432
|
+
Object.assign(record, heldRecord);
|
|
433
|
+
try { atomicJson(operatorFile, ledger); } catch { /* Preserve the original safe error. */ }
|
|
434
|
+
throw error;
|
|
435
|
+
}
|
|
436
|
+
return { ok: true, privateReportPath: outcome.report == null ? null : locations.reportFile };
|
|
437
|
+
});
|
|
438
|
+
} catch (error) { return { ...deny(error), privateReportPath: null }; }
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function read(token) {
|
|
442
|
+
try {
|
|
443
|
+
if (!validToken(token)) fail('invalid-token');
|
|
444
|
+
return transaction(token, (ledger, locations) => {
|
|
445
|
+
const record = findRecord(ledger, token);
|
|
446
|
+
if (record.state === 'preparing') fail('persistence-failed');
|
|
447
|
+
const metadata = readJson(locations.metadataFile);
|
|
448
|
+
if (!isObject(metadata)) fail('ledger-invalid');
|
|
449
|
+
safeMetadata(metadata);
|
|
450
|
+
const loadedReport = readJson(locations.reportFile, { missing: true });
|
|
451
|
+
const report = loadedReport === MISSING ? null : loadedReport;
|
|
452
|
+
if (record.evidence !== null && (record.evidence.metadataSha256 !== hashJson(metadata) ||
|
|
453
|
+
loadedReport === MISSING || record.evidence.reportSha256 !== hashJson(report))) fail('evidence-invalid');
|
|
454
|
+
// Secondary evidence can be ahead of authority after a torn finish transaction.
|
|
455
|
+
// Its completed status must never turn an unresolved hold into a successful result.
|
|
456
|
+
const authoritativeMetadata = { ...metadata, costState: record.costState,
|
|
457
|
+
costUsd: record.costUsd, reservationUsd: record.reservationUsd };
|
|
458
|
+
if (ACTIVE.has(record.state) && record.evidence === null && ['complete', 'validated'].includes(metadata.status)) {
|
|
459
|
+
authoritativeMetadata.status = 'failed';
|
|
460
|
+
authoritativeMetadata.reasonCode = 'persistence-failed';
|
|
461
|
+
}
|
|
462
|
+
atomicJson(operatorFile, ledger);
|
|
463
|
+
return { ok: true, metadata: authoritativeMetadata, report, reservation: { ...record, token: { ...record.token } } };
|
|
464
|
+
});
|
|
465
|
+
} catch (error) { return deny(error); }
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return Object.freeze({ begin, markSent, finish, read });
|
|
469
|
+
}
|
|
@@ -46,8 +46,10 @@ export const GROUPS = Object.freeze([
|
|
|
46
46
|
{ key: 'evals', label: 'Evals', dirs: ['tests/eval'], ext: ['.md'], what: 'what each agent is measured against' },
|
|
47
47
|
]);
|
|
48
48
|
|
|
49
|
-
// Generated or third-party trees, plus _shared:
|
|
50
|
-
//
|
|
49
|
+
// Generated or third-party trees, plus _shared: the old home of the prompt fragments,
|
|
50
|
+
// which sat under agents/ and were counted as agents. They live in
|
|
51
|
+
// shared/agent-fragments/ now, outside every group's file type; the entry stays so a
|
|
52
|
+
// project still laid out the old way does not count its fragments as agents.
|
|
51
53
|
const SKIP = new Set(['node_modules', 'dist', 'build', '.git', 'coverage', 'vendor', '_shared']);
|
|
52
54
|
|
|
53
55
|
function filesIn(root, dir, ext, depth = 0, out = []) {
|
|
@@ -17,24 +17,25 @@ const ref = value => typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9/_-]{0,
|
|
|
17
17
|
const scopePath = value => typeof value === 'string' && value.length <= 300 && value.length > 0
|
|
18
18
|
&& !value.startsWith('/') && !value.startsWith('-') && !value.includes('\\') && !value.includes(':') && !value.includes(',')
|
|
19
19
|
&& !/[\x00-\x1f]/.test(value) && !value.split('/').some(p => !p || p === '.' || p === '..');
|
|
20
|
-
|
|
20
|
+
// `env` defaults to the real process env; callers inject one so the transport check sees the same env git runs with.
|
|
21
|
+
function command(bin, args, root, execute = execFileSync, env = process.env) {
|
|
21
22
|
try { return String(execute(bin, args, { cwd: root, encoding: 'utf8', timeout: 20000,
|
|
22
23
|
maxBuffer: 2 * 1024 * 1024, stdio: ['ignore', 'pipe', 'pipe'],
|
|
23
|
-
env: { ...
|
|
24
|
+
env: { ...env, GIT_TERMINAL_PROMPT: '0', GIT_NO_REPLACE_OBJECTS: '1', GH_PROMPT_DISABLED: '1' } })); }
|
|
24
25
|
catch { throw Error(`${bin} ${args[0]} failed; inspect local authentication, hooks or network (output withheld)`); }
|
|
25
26
|
}
|
|
26
|
-
function repository(url, root, execute) {
|
|
27
|
+
function repository(url, root, execute, env = process.env) {
|
|
27
28
|
const https = /^https:\/\/github\.com\/([A-Za-z0-9_-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?$/.exec(url);
|
|
28
29
|
if (https) return `${https[1]}/${https[2]}`;
|
|
29
30
|
const ssh = /^git@([A-Za-z0-9_.-]+):([A-Za-z0-9_-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?$/.exec(url);
|
|
30
31
|
if (!ssh || ssh[1].startsWith('-')) throw Error('publication requires a credential-free GitHub origin');
|
|
31
32
|
if (ssh[1] !== 'github.com') {
|
|
32
33
|
// Inspect OpenSSH's effective config without connecting; never echo identity paths.
|
|
33
|
-
const config = command('ssh', ['-G', '-o', 'CanonicalizeHostname=no', '-o', 'BatchMode=yes', `git@${ssh[1]}`], root, execute);
|
|
34
|
+
const config = command('ssh', ['-G', '-o', 'CanonicalizeHostname=no', '-o', 'BatchMode=yes', `git@${ssh[1]}`], root, execute, env);
|
|
34
35
|
const values = new Map(config.split('\n').map(line => line.trim().split(/\s+/, 2)));
|
|
35
36
|
if (values.get('hostname')?.toLowerCase() !== 'github.com' || values.get('user') !== 'git' || values.get('port') !== '22')
|
|
36
37
|
throw Error('SSH alias does not resolve to git@github.com:22');
|
|
37
|
-
if (
|
|
38
|
+
if (env.GIT_SSH || env.GIT_SSH_COMMAND || /(?:^|\0)(?:core\.sshcommand|ssh\.variant)\n/i.test(command('git', ['config', '--list', '--null'], root, execute, env)))
|
|
38
39
|
throw Error('custom SSH transport cannot be verified for publication');
|
|
39
40
|
}
|
|
40
41
|
return `${ssh[2]}/${ssh[3]}`;
|
|
@@ -49,7 +50,7 @@ function ready(task, root) {
|
|
|
49
50
|
|
|
50
51
|
export function previewPublication({ root, taskId, base = 'main', allow = null }, options = {}) {
|
|
51
52
|
const task = readWorkTask(taskId, { ...options, root }); ready(task, root);
|
|
52
|
-
const git = args => command('git', args, root, options.execute);
|
|
53
|
+
const git = args => command('git', args, root, options.execute, options.env);
|
|
53
54
|
if (realpathSync(git(['rev-parse', '--show-toplevel']).trim()) !== realpathSync(root)) throw Error('publication task must own the repository root, not a nested directory');
|
|
54
55
|
// Even metadata/log changes excluded from verification cannot accidentally ship.
|
|
55
56
|
if (git(['status', '--porcelain=v1', '--untracked-files=all']).trim()) throw Error('publication requires a fully clean worktree and index');
|
|
@@ -59,7 +60,7 @@ export function previewPublication({ root, taskId, base = 'main', allow = null }
|
|
|
59
60
|
const origins = git(['remote', 'get-url', '--all', 'origin']).trim().split('\n');
|
|
60
61
|
const pushOrigins = git(['remote', 'get-url', '--push', '--all', 'origin']).trim().split('\n');
|
|
61
62
|
if (origins.length !== 1 || pushOrigins.length !== 1 || origins[0] !== pushOrigins[0]) throw Error('origin must have exactly one identical fetch and push URL');
|
|
62
|
-
const repo = repository(origins[0], root, options.execute);
|
|
63
|
+
const repo = repository(origins[0], root, options.execute, options.env);
|
|
63
64
|
const head = git(['rev-parse', '--verify', 'HEAD^{commit}']).trim();
|
|
64
65
|
const baseHead = git(['rev-parse', '--verify', `refs/remotes/origin/${base}^{commit}`]).trim();
|
|
65
66
|
git(['merge-base', '--is-ancestor', baseHead, head]);
|
|
@@ -120,8 +121,8 @@ export function publishPublication({ root, taskId, base = 'main', allow = null,
|
|
|
120
121
|
const current = previewPublication({ root, taskId, base: existing.base, allow: existing.allow }, options);
|
|
121
122
|
if (digest(bindingOf(current, existing.approvedRevision)) !== approval
|
|
122
123
|
|| digest(bindingOf(existing, existing.approvedRevision)) !== approval) throw Error('publication inputs changed; refuse external writes');
|
|
123
|
-
const git = args => command('git', args, root, options.execute);
|
|
124
|
-
const gh = args => command('gh', args, root, options.execute);
|
|
124
|
+
const git = args => command('git', args, root, options.execute, options.env);
|
|
125
|
+
const gh = args => command('gh', args, root, options.execute, options.env);
|
|
125
126
|
const checkpoint = (state, more = {}) => {
|
|
126
127
|
task = mutateWorkTask(taskId, t => {
|
|
127
128
|
if (t.revision !== t.publication.guardRevision) throw Error('task revision changed during publication');
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* SK-001 error YAML frontmatter present and parses
|
|
11
11
|
* SK-002 error `name` equals the skill's directory
|
|
12
12
|
* SK-003 error `description` present, 40–1024 chars (the model routes on it)
|
|
13
|
-
* SK-004 warn SKILL.md over 20
|
|
13
|
+
* SK-004 warn SKILL.md over 20 KiB = 20,480 bytes (loaded whole into context) — HEAVY_ALLOWED exempts
|
|
14
14
|
* SK-005 error a relative link or backticked repo path points at a file that does not exist
|
|
15
15
|
* SK-006 error a private absolute path (/Users/<name>/…, /home/<name>/…)
|
|
16
16
|
* SK-007 error an unquoted frontmatter value strict YAML reads differently (`: `, ` #`, a reserved first character)
|
|
@@ -23,7 +23,7 @@ import fs from 'node:fs';
|
|
|
23
23
|
import path from 'node:path';
|
|
24
24
|
import { fileURLToPath } from 'node:url';
|
|
25
25
|
|
|
26
|
-
export const SIZE_BUDGET = 20 * 1024;
|
|
26
|
+
export const SIZE_BUDGET = 20 * 1024; // bytes: 20 KiB (20,480), not 20,000
|
|
27
27
|
export const DESC_MIN = 40;
|
|
28
28
|
export const DESC_MAX = 1024;
|
|
29
29
|
|
|
@@ -138,7 +138,7 @@ function lintOne(name, file, repoRoot) {
|
|
|
138
138
|
|
|
139
139
|
const bytes = Buffer.byteLength(text);
|
|
140
140
|
if (bytes > SIZE_BUDGET && !HEAVY_ALLOWED[name]) {
|
|
141
|
-
push(warnings, 'SK-004', `${(bytes / 1024).toFixed(1)}
|
|
141
|
+
push(warnings, 'SK-004', `${bytes} bytes (${(bytes / 1024).toFixed(1)} KiB) over the ${SIZE_BUDGET}-byte (${SIZE_BUDGET / 1024} KiB) budget — loaded whole into context`);
|
|
142
142
|
}
|
|
143
143
|
for (const msg of danglingRefs(text, path.dirname(file), repoRoot)) push(errors, 'SK-005', msg);
|
|
144
144
|
for (const m of text.matchAll(PRIVATE_PATH)) push(errors, 'SK-006', `private absolute path \`${m[0]}…\``);
|