@logicseek/hello-cc 0.1.9 → 1.0.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/CHANGELOG.md +59 -0
- package/README.md +18 -3
- package/README.zh-CN.md +17 -3
- package/bin/hcc.mjs +3 -5656
- package/docs/README.md +8 -0
- package/docs/README.zh-CN.md +6 -0
- package/docs/architecture.md +29 -18
- package/docs/architecture.zh-CN.md +20 -10
- package/docs/commands.md +13 -4
- package/docs/commands.zh-CN.md +13 -4
- package/docs/current-state-review-2026-08.md +341 -0
- package/docs/defect-review-2026-07.md +382 -0
- package/docs/guide.md +39 -3
- package/docs/guide.zh-CN.md +34 -2
- package/docs/superpowers/plans/2026-08-03-runtime-safety-plan.md +294 -0
- package/docs/superpowers/plans/2026-08-03-v1-data-identity-plan.md +400 -0
- package/docs/superpowers/plans/2026-08-03-v1-release-verification-plan.md +284 -0
- package/docs/superpowers/plans/2026-08-03-web-api-hardening-plan.md +303 -0
- package/docs/superpowers/plans/2026-08-04-runtime-buffer-gc-two-phase.md +71 -0
- package/docs/superpowers/specs/2026-08-03-runtime-safety-design.md +117 -0
- package/docs/superpowers/specs/2026-08-03-v1-data-identity-design.md +120 -0
- package/docs/superpowers/specs/2026-08-03-v1-release-verification-design.md +117 -0
- package/docs/superpowers/specs/2026-08-03-web-api-hardening-design.md +104 -0
- package/lib/cli/app.mjs +642 -0
- package/lib/cli/commands/coordination.mjs +247 -0
- package/lib/cli/commands/doctor.mjs +81 -0
- package/lib/cli/commands/gc.mjs +852 -0
- package/lib/cli/commands/hook.mjs +205 -0
- package/lib/cli/commands/install.mjs +294 -0
- package/lib/cli/commands/lifecycle.mjs +101 -0
- package/lib/cli/commands/lock.mjs +197 -0
- package/lib/cli/commands/misc.mjs +61 -0
- package/lib/cli/commands/msg.mjs +152 -0
- package/lib/cli/commands/peer.mjs +233 -0
- package/lib/cli/commands/query.mjs +96 -0
- package/lib/cli/commands/run.mjs +463 -0
- package/lib/cli/commands/task.mjs +405 -0
- package/lib/cli/commands/team.mjs +131 -0
- package/lib/cli/commands/tmux.mjs +838 -0
- package/lib/cli/commands/up.mjs +135 -0
- package/lib/cli-args.mjs +16 -2
- package/lib/coordination-state.mjs +112 -51
- package/lib/core/coordination/automation.mjs +5 -8
- package/lib/core/coordination/clock-safety.mjs +323 -0
- package/lib/core/coordination/event-retention.mjs +17 -0
- package/lib/core/coordination/gc-plan.mjs +614 -0
- package/lib/core/coordination/lease-renewal.mjs +102 -0
- package/lib/core/coordination/lock-evidence.mjs +77 -0
- package/lib/core/coordination/optimistic-evidence.mjs +26 -0
- package/lib/core/coordination/tasks.mjs +156 -36
- package/lib/core/peers/evidence-runtime.mjs +312 -0
- package/lib/core/peers/evidence.mjs +103 -0
- package/lib/core/peers/hook-owner.mjs +32 -0
- package/lib/core/peers/liveness.mjs +44 -4
- package/lib/core/peers/peer-helpers.mjs +121 -0
- package/lib/core/peers/session.mjs +8 -2
- package/lib/core/peers/tmux-safety.mjs +290 -0
- package/lib/core/sessions/launch.mjs +13 -1
- package/lib/db/connection.mjs +104 -0
- package/lib/db/events.mjs +34 -0
- package/lib/db/migration-backup.mjs +225 -0
- package/lib/db/schema.mjs +102 -5
- package/lib/db/stores/peers.mjs +34 -0
- package/lib/integrations/peers/identity.mjs +62 -16
- package/lib/integrations/shims/script.mjs +111 -9
- package/lib/process/identity.mjs +192 -0
- package/lib/process/pty-lifecycle.mjs +149 -0
- package/lib/runtime/buffer-directory-lease.mjs +47 -0
- package/lib/runtime/buffer-evidence.mjs +336 -0
- package/lib/runtime/buffer-gc-protocol.mjs +167 -0
- package/lib/runtime/buffer-gc.mjs +270 -0
- package/lib/runtime/client.mjs +60 -7
- package/lib/runtime/fatal-shutdown.mjs +54 -0
- package/lib/runtime/project-path.mjs +139 -0
- package/lib/runtime/projects.mjs +320 -40
- package/lib/runtime/state.mjs +227 -27
- package/lib/shared/cleanup-error.mjs +54 -0
- package/lib/shared/clock-grace.mjs +106 -0
- package/lib/shared/errors.mjs +9 -0
- package/lib/shared/file-lock.mjs +370 -0
- package/lib/shared/json-file.mjs +6 -2
- package/lib/shared/redact.mjs +60 -0
- package/lib/shared/socket-lock-worker.mjs +201 -0
- package/lib/terminal/tmux.mjs +78 -4
- package/lib/tmux.mjs +4 -0
- package/lib/ui/help.mjs +53 -15
- package/lib/web/api-version.mjs +42 -0
- package/lib/web/auto-attach.mjs +185 -0
- package/lib/web/buffer-gc-runtime.mjs +175 -0
- package/lib/web/cookie-auth.mjs +119 -0
- package/lib/web/external-sessions.mjs +254 -0
- package/lib/web/http-routes.mjs +542 -0
- package/lib/web/http.mjs +135 -6
- package/lib/web/liveness-reaper.mjs +145 -0
- package/lib/web/peer-actions.mjs +132 -23
- package/lib/web/project-contexts.mjs +178 -0
- package/lib/web/pty-sessions.mjs +203 -0
- package/lib/web/runtime-main.mjs +714 -0
- package/lib/web/runtime.mjs +91 -36
- package/lib/web/session-serialize.mjs +0 -0
- package/lib/web/startup.mjs +333 -0
- package/lib/web/tls.mjs +365 -0
- package/lib/web/tmux-sessions.mjs +1361 -0
- package/lib/web/tmux-stream.mjs +166 -0
- package/lib/web/ui-template.mjs +152 -20
- package/package.json +5 -2
- package/scripts/check-free-identifiers.mjs +290 -0
- package/scripts/regression.mjs +5397 -263
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// Static free-identifier audit for create*() factory modules.
|
|
2
|
+
//
|
|
3
|
+
// A factory module (lib/**) receives every external value through its deps
|
|
4
|
+
// parameter. Any identifier used inside the factory body that is not a
|
|
5
|
+
// local declaration, a parameter, an import, or a JS language construct is
|
|
6
|
+
// a latent ReferenceError on the code path that touches it — the exact
|
|
7
|
+
// class of bug the cmdWeb extraction shipped twice (requestActorPeer,
|
|
8
|
+
// inspectProviderProcess) because the surrounding try/catch swallowed it.
|
|
9
|
+
//
|
|
10
|
+
// This scanner strips comments and string/template literals first, so
|
|
11
|
+
// identifiers mentioned in prose or error messages do not produce noise.
|
|
12
|
+
// It reports free identifiers per module and exits non-zero on findings.
|
|
13
|
+
//
|
|
14
|
+
// Usage: node scripts/check-free-identifiers.mjs [file...]
|
|
15
|
+
|
|
16
|
+
import fs from 'node:fs';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
|
|
20
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
21
|
+
|
|
22
|
+
// ── lexer: blank out comments and string/template contents ─────────────────
|
|
23
|
+
// Template literals keep their ${ } expression regions scannable; literal
|
|
24
|
+
// text is blanked. Regex literals are blanked conservatively (a `/` that
|
|
25
|
+
// follows a non-identifier token and pairs with a closing `/`).
|
|
26
|
+
function stripCommentsAndStrings(source) {
|
|
27
|
+
let out = '';
|
|
28
|
+
let i = 0;
|
|
29
|
+
const n = source.length;
|
|
30
|
+
let prevMeaningful = ''; // last non-space char emitted
|
|
31
|
+
const last = () => prevMeaningful;
|
|
32
|
+
const push = (ch) => { out += ch; if (!/\s/.test(ch)) prevMeaningful = ch; };
|
|
33
|
+
const blank = (ch) => { out += ch === '\n' ? '\n' : ' '; };
|
|
34
|
+
|
|
35
|
+
while (i < n) {
|
|
36
|
+
const c = source[i];
|
|
37
|
+
const c2 = source[i + 1];
|
|
38
|
+
if (c === '/' && c2 === '/') {
|
|
39
|
+
while (i < n && source[i] !== '\n') { blank(source[i]); i += 1; }
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (c === '/' && c2 === '*') {
|
|
43
|
+
blank(c); blank(c2); i += 2;
|
|
44
|
+
while (i < n && !(source[i] === '*' && source[i + 1] === '/')) { blank(source[i]); i += 1; }
|
|
45
|
+
blank('*'); blank('/'); i += 2;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (c === "'" || c === '"') {
|
|
49
|
+
blank(c); i += 1;
|
|
50
|
+
while (i < n && source[i] !== c) {
|
|
51
|
+
if (source[i] === '\\') { blank(source[i]); i += 1; if (i < n) { blank(source[i]); i += 1; } continue; }
|
|
52
|
+
if (source[i] === '\n') { out += '\n'; i += 1; continue; }
|
|
53
|
+
blank(source[i]); i += 1;
|
|
54
|
+
}
|
|
55
|
+
if (i < n) { blank(c); i += 1; }
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (c === '`') {
|
|
59
|
+
blank(c); i += 1;
|
|
60
|
+
while (i < n && source[i] !== '`') {
|
|
61
|
+
if (source[i] === '\\') { blank(source[i]); i += 1; if (i < n) { blank(source[i]); i += 1; } continue; }
|
|
62
|
+
if (source[i] === '$' && source[i + 1] === '{') {
|
|
63
|
+
// keep the expression: emit as-is, track brace depth to its close
|
|
64
|
+
push('$'); push('{'); i += 2;
|
|
65
|
+
let depth = 1;
|
|
66
|
+
while (i < n && depth > 0) {
|
|
67
|
+
const ch = source[i];
|
|
68
|
+
if (ch === '{') depth += 1;
|
|
69
|
+
if (ch === '}') { depth -= 1; if (depth === 0) { push('}'); i += 1; break; } }
|
|
70
|
+
out += ch; if (!/\s/.test(ch)) prevMeaningful = ch;
|
|
71
|
+
i += 1;
|
|
72
|
+
}
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (source[i] === '\n') { out += '\n'; i += 1; continue; }
|
|
76
|
+
blank(source[i]); i += 1;
|
|
77
|
+
}
|
|
78
|
+
if (i < n) { blank('`'); i += 1; }
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (c === '/' && !/[)\w$]/.test(last()) && last() !== '') {
|
|
82
|
+
// potential regex literal — find an unescaped closing /
|
|
83
|
+
let j = i + 1;
|
|
84
|
+
let closed = false;
|
|
85
|
+
let inClass = false;
|
|
86
|
+
while (j < n && source[j] !== '\n') {
|
|
87
|
+
if (source[j] === '\\') { j += 2; continue; }
|
|
88
|
+
if (source[j] === '[') inClass = true;
|
|
89
|
+
else if (source[j] === ']') inClass = false;
|
|
90
|
+
else if (source[j] === '/' && !inClass) { closed = true; break; }
|
|
91
|
+
j += 1;
|
|
92
|
+
}
|
|
93
|
+
if (closed) {
|
|
94
|
+
blank(c);
|
|
95
|
+
i += 1;
|
|
96
|
+
while (i <= j) { blank(source[i] === '\n' ? '\n' : ' '); i += 1; }
|
|
97
|
+
// flags
|
|
98
|
+
while (i < n && /[a-z]/.test(source[i])) { blank(source[i]); i += 1; }
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
// not a regex; fall through as division
|
|
102
|
+
}
|
|
103
|
+
push(c);
|
|
104
|
+
i += 1;
|
|
105
|
+
}
|
|
106
|
+
// Blank object-literal keys (identifier before ':' directly after '{' or
|
|
107
|
+
// ','). Shorthand `{ key }` is a real use and stays intact (no colon).
|
|
108
|
+
out = out.replace(/([{,]\s*)([A-Za-z_$][\w$]*)\s*:/g, (full, lead, ident) =>
|
|
109
|
+
lead + " ".repeat(ident.length + 1));
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── scope approximation ─────────────────────────────────────────────────────
|
|
114
|
+
// Collect: imports, deps destructuring, and every identifier declared inside
|
|
115
|
+
// the factory body (function names, const/let/var, function parameters,
|
|
116
|
+
// destructuring patterns, class names, catch params, for-of heads). This is
|
|
117
|
+
// deliberately over-inclusive — we only report identifiers that are declared
|
|
118
|
+
// nowhere in the file, which keeps false positives near zero.
|
|
119
|
+
function collectBindings(stripped, factoryStart) {
|
|
120
|
+
const bound = new Set();
|
|
121
|
+
const body = stripped;
|
|
122
|
+
|
|
123
|
+
// imports (whole file)
|
|
124
|
+
for (const m of body.matchAll(/import\s+(?:\*\s+as\s+)?([A-Za-z_$][\w$]*)/g)) bound.add(m[1]);
|
|
125
|
+
for (const m of body.matchAll(/import\s*\{([^}]*)\}/g)) {
|
|
126
|
+
for (const piece of m[1].split(',')) {
|
|
127
|
+
const name = piece.trim().split(/\s+as\s+/).pop()?.trim();
|
|
128
|
+
if (name && /^[A-Za-z_$][\w$]*$/.test(name)) bound.add(name);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// deps destructuring: const { ... } = deps;
|
|
133
|
+
const depsMatch = body.match(/const\s*\{([\s\S]*?)\}\s*=\s*deps\s*;/);
|
|
134
|
+
if (depsMatch) {
|
|
135
|
+
for (const piece of depsMatch[1].split(/[,\n]/)) {
|
|
136
|
+
const name = piece.trim().split(/:\s*/).pop()?.split(/\s+as\s+/).pop()?.trim();
|
|
137
|
+
if (name && /^[A-Za-z_$][\w$]*$/.test(name)) bound.add(name);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// function declarations + their parameters
|
|
142
|
+
for (const m of body.matchAll(/(?:^|\n)\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s*\*?\s*([A-Za-z_$][\w$]*)?\s*\(([^)]*)\)/g)) {
|
|
143
|
+
if (m[1]) bound.add(m[1]);
|
|
144
|
+
for (const p of m[2].split(',')) {
|
|
145
|
+
const name = p.trim().split(/[=\s]/)[0].replace(/^\.\.\./, '');
|
|
146
|
+
for (const leaf of destructLeaves(name)) bound.add(leaf);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// const/let/var with destructuring or plain names
|
|
150
|
+
for (const m of body.matchAll(/(?:^|[^.\w$])(?:const|let|var)\s+([A-Za-z_$][\w$]*|\{[^}]*\})\s*[=;]/g)) {
|
|
151
|
+
const decl = m[1];
|
|
152
|
+
if (decl.startsWith('{')) {
|
|
153
|
+
for (const leaf of destructLeaves(decl)) bound.add(leaf);
|
|
154
|
+
} else bound.add(decl);
|
|
155
|
+
}
|
|
156
|
+
// class declarations, catch params, labeled loop vars
|
|
157
|
+
for (const m of body.matchAll(/class\s+([A-Za-z_$][\w$]*)/g)) bound.add(m[1]);
|
|
158
|
+
for (const m of body.matchAll(/catch\s*(?:\(\s*([A-Za-z_$][\w$]*)\s*\))?/g)) if (m[1]) bound.add(m[1]);
|
|
159
|
+
for (const m of body.matchAll(/for\s*\(\s*(?:const|let|var)\s+([\w$,\s{}:]+?)\s+(?:of|in)\s/g)) {
|
|
160
|
+
for (const leaf of destructLeaves(m[1])) bound.add(leaf);
|
|
161
|
+
}
|
|
162
|
+
// arrow function parameters: (a, b) => / single a =>
|
|
163
|
+
for (const m of body.matchAll(/\(\s*([^()]*?)\s*\)\s*=>/g)) {
|
|
164
|
+
for (const p of m[1].split(',')) {
|
|
165
|
+
for (const leaf of destructLeaves(p.trim().split(/[=\s]/)[0].replace(/^\.\.\./, ''))) bound.add(leaf);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
for (const m of body.matchAll(/(?:^|[^.\w$])([A-Za-z_$][\w$]*)\s*=>/g)) bound.add(m[1]);
|
|
169
|
+
// object literal shorthand keys are uses of bindings, but `{ name }` in a
|
|
170
|
+
// destructuring is a binding — covered above; literal method names are fine.
|
|
171
|
+
|
|
172
|
+
return bound;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function destructLeaves(fragment) {
|
|
176
|
+
const leaves = [];
|
|
177
|
+
if (!fragment) return leaves;
|
|
178
|
+
const inner = fragment.replace(/^\{|\}$/g, '');
|
|
179
|
+
for (const piece of inner.split(',')) {
|
|
180
|
+
// take the binding side of `key: value`, or the shorthand itself
|
|
181
|
+
const side = piece.includes(':') ? piece.split(':').pop() : piece;
|
|
182
|
+
const name = side.trim().split(/[=\s]/)[0].replace(/^\.\.\./, '');
|
|
183
|
+
if (name && /^[A-Za-z_$][\w$]*$/.test(name)) leaves.push(name);
|
|
184
|
+
else if (name && name.startsWith('{')) leaves.push(...destructLeaves(name));
|
|
185
|
+
}
|
|
186
|
+
return leaves;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const JS_GLOBALS = new Set(('globalThis global console process require module exports Buffer URL URLSearchParams'
|
|
190
|
+
+ ' Array Object String Number Boolean Math JSON Date Promise Symbol BigInt Map Set WeakMap WeakSet'
|
|
191
|
+
+ ' RegExp Error TypeError RangeError SyntaxError ReferenceError EvalError URIError AggregateError'
|
|
192
|
+
+ ' parseInt parseFloat isNaN isFinite encodeURIComponent decodeURIComponent'
|
|
193
|
+
+ ' undefined NaN Infinity setTimeout setInterval clearTimeout setImmediate clearImmediate queueMicrotask'
|
|
194
|
+
+ ' fetch structuredClone atob btoa performance setTimeoutAsyncOfTimersPromises'
|
|
195
|
+
+ ' if else for while do switch case default break continue return function const let var class extends'
|
|
196
|
+
+ ' new delete typeof instanceof in of try catch finally throw await async yield this super null true false void'
|
|
197
|
+
+ ' arguments export import from as default eval'
|
|
198
|
+
+ ' clearInterval setInterval clearTimeout setImmediate clearImmediate'
|
|
199
|
+
+ ' $ __filename __dirname atob btoa').split(/\s+/));
|
|
200
|
+
|
|
201
|
+
function auditFile(file) {
|
|
202
|
+
const source = fs.readFileSync(file, 'utf8');
|
|
203
|
+
const stripped = stripCommentsAndStrings(source);
|
|
204
|
+
const bound = collectBindings(stripped);
|
|
205
|
+
// identifiers used as: calls, property-less reads (word boundary), assignments
|
|
206
|
+
const used = new Set();
|
|
207
|
+
for (const m of stripped.matchAll(/(?<![.\w$'"])([A-Za-z_$][\w$]*)/g)) used.add(m[1]);
|
|
208
|
+
const free = [...used].filter((n) => !bound.has(n) && !JS_GLOBALS.has(n));
|
|
209
|
+
return { file, free };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ── main ────────────────────────────────────────────────────────────────────
|
|
213
|
+
function listFactoryModules() {
|
|
214
|
+
const found = [];
|
|
215
|
+
const walk = (dir) => {
|
|
216
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
217
|
+
const full = path.join(dir, entry.name);
|
|
218
|
+
if (entry.isDirectory()) walk(full);
|
|
219
|
+
else if (entry.name.endsWith('.mjs')) {
|
|
220
|
+
const src = fs.readFileSync(full, 'utf8');
|
|
221
|
+
if (/export function create\w+\(/.test(src)) found.push(full);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
walk(path.join(repoRoot, 'lib'));
|
|
226
|
+
return found;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const files = process.argv.slice(2).length
|
|
230
|
+
? process.argv.slice(2)
|
|
231
|
+
: listFactoryModules();
|
|
232
|
+
|
|
233
|
+
// Known scanner gaps (NOT code defects). Every entry below was triaged by
|
|
234
|
+
// hand: the identifier is bound through a pattern the regex approximation
|
|
235
|
+
// cannot see — destructured function parameters with defaults, for-of array
|
|
236
|
+
// heads, object keys surviving template `${}` regions, shorthand in nested
|
|
237
|
+
// option objects, or embedded worker source. Keep this list short: a new
|
|
238
|
+
// finding that is not clearly one of these classes is probably a REAL
|
|
239
|
+
// missing dep (four such bugs were found and fixed by this scanner:
|
|
240
|
+
// requestActorPeer, inspectProviderProcess, ACTIVE_PEER_TTL, CLI_NAME,
|
|
241
|
+
// shutdown/tlsCredentials, CliError — all on cold error paths).
|
|
242
|
+
const KNOWN_FALSE_POSITIVES = {
|
|
243
|
+
'lib/cli/commands/coordination.mjs': ['Enter', 'and', 'input', 'locks', 'pressed', 'terminal'],
|
|
244
|
+
'lib/cli/commands/doctor.mjs': ['connectReadOnly', 'k', 'v', 'wal'],
|
|
245
|
+
'lib/cli/commands/gc.mjs': ['add', 'apply', 'dry', 'entries', 'gcCutoff', 'id', 'lock', 'run', 'to', 'yes'],
|
|
246
|
+
'lib/cli/commands/install.mjs': ['cc', 'n'],
|
|
247
|
+
'lib/cli/commands/peer.mjs': ['pid'],
|
|
248
|
+
'lib/cli/commands/task.mjs': ['child'],
|
|
249
|
+
'lib/cli/commands/team.mjs': ['assignee', 'count', 'owner', 'status'],
|
|
250
|
+
'lib/coordination-state.mjs': ['reply'],
|
|
251
|
+
'lib/core/coordination/gc-plan.mjs': ['GC', 'id', 'index'],
|
|
252
|
+
'lib/core/coordination/tasks.mjs': ['OR', 'assignee', 'force', 'owner', 'reason'],
|
|
253
|
+
'lib/core/peers/evidence-runtime.mjs': ['can', 'find', 'i', 'no', 'now', 'requested', 'running', 'server', 'session', 't'],
|
|
254
|
+
'lib/core/peers/peer-helpers.mjs': ['now'],
|
|
255
|
+
'lib/db/connection.mjs': ['now'],
|
|
256
|
+
'lib/db/events.mjs': ['now'],
|
|
257
|
+
'lib/runtime/buffer-gc-protocol.mjs': ['afterBatch', 'batchSize', 'collectEvidence', 'db', 'pendingCount', 'prepare', 'take'],
|
|
258
|
+
'lib/runtime/fatal-shutdown.mjs': ['cleanup'],
|
|
259
|
+
'lib/shared/file-lock.mjs': ['Atomics', 'Int32Array', 'SharedArrayBuffer', 'fn', 'nonblocking', 'retryMs', 'timeoutMs', 'withConfiguredFileLock', 'workerSource'],
|
|
260
|
+
'lib/ui/help.mjs': ['helpAsk', 'helpBroadcast', 'helpDown', 'helpEnv', 'helpEvent', 'helpGc', 'helpHandoff', 'helpInject', 'helpInstallHooks', 'helpJoin', 'helpLock', 'helpMain', 'helpMsg', 'helpPeer', 'helpRun', 'helpShim', 'helpState', 'helpTask', 'helpTeam', 'helpTmux', 'helpUninstall', 'helpUp', 'helpUpdate', 'helpWeb'],
|
|
261
|
+
'lib/web/buffer-gc-runtime.mjs': ['dryRun', 'entries', 'prepare'],
|
|
262
|
+
'lib/web/cookie-auth.mjs': ['k', 'now', 'rest'],
|
|
263
|
+
'lib/web/external-sessions.mjs': ['updateDatabase'],
|
|
264
|
+
'lib/web/peer-actions.mjs': ['locks'],
|
|
265
|
+
'lib/web/project-contexts.mjs': ['activity'],
|
|
266
|
+
'lib/web/runtime-main.mjs': ['idSet'],
|
|
267
|
+
'lib/web/session-serialize.mjs': ['sessions'],
|
|
268
|
+
'lib/web/startup.mjs': ['log', 'n', 'n$', 'nLast', 'proxy', 'tls', 'trust', 'with'],
|
|
269
|
+
'lib/web/tmux-stream.mjs': ['broadcast']
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
let failures = 0;
|
|
273
|
+
for (const file of files) {
|
|
274
|
+
const rel = path.relative(repoRoot, file);
|
|
275
|
+
const allow = new Set(KNOWN_FALSE_POSITIVES[rel] || []);
|
|
276
|
+
const { free } = auditFile(file);
|
|
277
|
+
const real = free.filter((n) => !allow.has(n));
|
|
278
|
+
const tolerated = free.filter((n) => allow.has(n));
|
|
279
|
+
if (real.length) {
|
|
280
|
+
failures += 1;
|
|
281
|
+
console.error(`✗ ${rel}: free identifiers → ${real.sort().join(', ')}`);
|
|
282
|
+
} else {
|
|
283
|
+
console.log(`✓ ${rel}${tolerated.length ? ` (${tolerated.length} known scanner gap(s))` : ''}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (failures) {
|
|
287
|
+
console.error(`\n${failures} module(s) use identifiers that are neither imports, deps, nor local declarations.`);
|
|
288
|
+
process.exit(1);
|
|
289
|
+
}
|
|
290
|
+
console.log(`\n${files.length} factory module(s) audited, no free identifiers.`);
|