@afokapu/atdd-bun 0.10.28 → 0.10.30
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/conventions/coder.bun/coder.bun.dead-code-reachability.convention.yaml +2 -1
- package/conventions/coder.bun/coder.bun.duplication-intra-layer.convention.yaml +1 -0
- package/conventions/coder.bun/coder.bun.logging-console.convention.yaml +1 -1
- package/conventions/coder.bun/coder.bun.runtime-server-is-bun-serve.convention.yaml +1 -1
- package/conventions/coder.bun/coder.bun.runtime-single-lockfile.convention.yaml +2 -2
- package/conventions/delivery/delivery.board.convention.yaml +2 -2
- package/conventions/tester.bun/tester.bun.test-imports-bun-test.convention.yaml +2 -2
- package/detectors/bun_ts_metrics_detector/checks/ts_dead_code.mjs +26 -3
- package/detectors/bun_ts_metrics_detector/fixtures/clean/domain/offset.ts +12 -0
- package/detectors/bun_ts_metrics_detector/fixtures/clean/domain/scale.ts +13 -0
- package/detectors/bun_ts_metrics_detector/fixtures/clean/domain/units.ts +7 -0
- package/detectors/bun_ts_metrics_detector/fixtures/clean/index.ts +3 -1
- package/detectors/bun_ts_metrics_detector/fixtures/clean/package.json +1 -0
- package/detectors/bun_ts_metrics_detector/fixtures/clean/tools/banner.ts +2 -0
- package/detectors/bun_ts_metrics_detector/fixtures/clean/tools/gen.ts +5 -0
- package/detectors/bun_ts_metrics_detector/ts_metrics.mjs +12 -0
- package/integrity.json +19 -13
- package/lib/scan.mjs +67 -1
- package/package.json +1 -1
- package/src/board.ts +14 -3
|
@@ -13,7 +13,8 @@ statement: >-
|
|
|
13
13
|
terms:
|
|
14
14
|
- term_id: structural_root
|
|
15
15
|
text: >-
|
|
16
|
-
a file the graph is walked from: index, wagon, composition, main or app
|
|
16
|
+
a file the graph is walked from: index, wagon, composition, main or app, or a file a script in the nearest
|
|
17
|
+
package.json runs (`bun run docs/gen.ts`), since the repository itself declares it as an entry point.
|
|
17
18
|
- term_id: type_only_import
|
|
18
19
|
text: >-
|
|
19
20
|
an `import type` edge. Erased at compile time, so it creates no runtime edge and cannot keep
|
|
@@ -31,6 +31,7 @@ content:
|
|
|
31
31
|
exceptions:
|
|
32
32
|
- Test, spec, and `.d.ts` files are excluded, as are `index.ts` barrels and `__tests__` directories.
|
|
33
33
|
- Trivial lines (bare braces, closing punctuation) are not counted toward the 7.
|
|
34
|
+
- Import and re-export declarations are not counted, because two files importing the same names from one shared module match because the logic was shared, which is the fix this rule asks for.
|
|
34
35
|
- Files whose layer cannot be determined are compared only against each other.
|
|
35
36
|
metadata:
|
|
36
37
|
aliases:
|
|
@@ -31,7 +31,7 @@ content:
|
|
|
31
31
|
exceptions:
|
|
32
32
|
- Occurrences inside comments or string literals are masked and never flagged.
|
|
33
33
|
- Test files and vendored/build directories are excluded.
|
|
34
|
-
- A CLI whose stdout IS its user interface
|
|
34
|
+
- A CLI whose stdout IS its user interface writes that output with `process.stdout.write` (or `process.stderr.write` for its errors), which says plainly that it is the interface, not a log; only `console.*` is matched. atdd-bun has no per-rule scope to exempt a file.
|
|
35
35
|
metadata:
|
|
36
36
|
aliases:
|
|
37
37
|
# LOGGING-PRINT-001 is the SHARED core alias, also carried by the agnostic node and its
|
|
@@ -50,7 +50,7 @@ content:
|
|
|
50
50
|
hold the stack decision, so it is not suppressible.
|
|
51
51
|
exceptions:
|
|
52
52
|
- A `createServer(` call that is not an HTTP server (a test double, an unrelated factory) is a known
|
|
53
|
-
imprecision of the name-based match; rename it
|
|
53
|
+
imprecision of the name-based match; rename it (atdd-bun has no per-rule scope).
|
|
54
54
|
- Test files are excluded — spinning a throwaway server in a test is not an application server.
|
|
55
55
|
- Names appearing inside strings or comments are masked and never flagged.
|
|
56
56
|
metadata:
|
|
@@ -43,8 +43,8 @@ content:
|
|
|
43
43
|
exceptions:
|
|
44
44
|
- A vendored or fixture lockfile inside an excluded tree (`node_modules`, `dist`, `build`,
|
|
45
45
|
`_generated`) is never scanned.
|
|
46
|
-
-
|
|
47
|
-
not
|
|
46
|
+
- atdd-bun has no per-rule scope, so a monorepo package that is deliberately NOT on Bun stays reported until it
|
|
47
|
+
moves to Bun or out of the scanned tree; the finding is the stack decision's open item, not something to suppress.
|
|
48
48
|
metadata:
|
|
49
49
|
aliases:
|
|
50
50
|
- BUN-SINGLE-LOCKFILE-001
|
|
@@ -27,12 +27,12 @@ terms:
|
|
|
27
27
|
atdd-bun chat post writes the header: from, to, participants and conversation always; kind (task, result, review-request, verdict, rebuttal, status, blocked, heartbeat), stage, reply-to, repo, branch, worktree, goal, base and sha when they apply. The body carries the whole instruction or answer, however long: the thread is the history. A heartbeat carries nothing else; anything that needs an answer goes as blocked, never inside a heartbeat.
|
|
28
28
|
- term_id: commands
|
|
29
29
|
text: >-
|
|
30
|
-
Post with the body on standard input; read what is on a topic; wait for the next message addressed to you on one or several topics (comma-separated), skipping kinds you do not act on (--skip heartbeat), bounded by --timeout so a tool call ends (exit 2: nothing yet). wait remembers where each identity stopped on each set of topics (~/.atdd-board/cursors), so re-arming a watcher is always the same command; --since still overrides it; follow is the listener for a host that wakes its agent on each line a running process prints (Claude Code's Monitor tool): it streams every message addressed to the identity as it arrives and keeps running, keeping the same saved place; a host that wakes its agent only when a process exits runs wait and re-arms it after each wake-up. One listener per identity. And, for a human, atdd-bun chat alone opens the board: every topic on the left, the selected conversation on the right, live.
|
|
30
|
+
Post with the body on standard input; read what is on a topic; wait for the next message addressed to you on one or several topics (comma-separated), skipping kinds you do not act on (--skip heartbeat), bounded by --timeout so a tool call ends (exit 2: nothing yet). wait remembers where each identity stopped on each set of topics (~/.atdd-board/cursors), so re-arming a watcher is always the same command; --since still overrides it; follow is the listener for a host that wakes its agent on each line a running process prints (Claude Code's Monitor tool): it streams every message addressed to the identity as it arrives and keeps running, keeping the same saved place; a host that wakes its agent only when a process exits runs wait and re-arms it after each wake-up. One listener per identity. When nothing arrives (wait's timeout, or follow's --check-in seconds, 20 minutes by default and 0 to turn it off), the listener prints the owner's check-in, asking where the agent is, whether it is moving or blocked and who knows, whether its stakeholder is informed, and whether it is avoiding ceremony and rabbit holes. The agent acts on it, moving the work or posting once to whoever it waits on, and never posts just to say it is still there. And, for a human, atdd-bun chat alone opens the board: every topic on the left, the selected conversation on the right, live.
|
|
31
31
|
values:
|
|
32
32
|
post: atdd-bun chat post <topic> --to <agent> --kind <kind> [--reply-to <id>] < message.md
|
|
33
33
|
read: atdd-bun chat read <topic> [--mine]
|
|
34
34
|
wait: atdd-bun chat wait <topic>[,<topic>...] [--since <id>] [--skip heartbeat] --timeout 540
|
|
35
|
-
follow: atdd-bun chat follow <topic>[,<topic>...] [--skip heartbeat,progress] [--timeout <seconds>]
|
|
35
|
+
follow: atdd-bun chat follow <topic>[,<topic>...] [--skip heartbeat,progress] [--timeout <seconds>] [--check-in <seconds>]
|
|
36
36
|
board: atdd-bun chat
|
|
37
37
|
- term_id: server
|
|
38
38
|
text: >-
|
|
@@ -42,8 +42,8 @@ content:
|
|
|
42
42
|
changed. `strict` — a test the gate cannot actually run is worse than no test.
|
|
43
43
|
exceptions:
|
|
44
44
|
- Non-test files are never scanned; a harness import outside a test file is a different smell.
|
|
45
|
-
-
|
|
46
|
-
|
|
45
|
+
- atdd-bun has no per-rule scope, so a test deliberately retained on a foreign runner during a migration stays
|
|
46
|
+
reported until it moves, since the finding is the migration's open item, explicit and reviewable rather than invisible.
|
|
47
47
|
metadata:
|
|
48
48
|
aliases:
|
|
49
49
|
- BUN-TEST-HARNESS-001
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
// Member check: coder.bun.dead-code-reachability (TypeScript metrics family)
|
|
3
3
|
//
|
|
4
4
|
// Cross-file, unlike its siblings: it builds the module import graph, walks it
|
|
5
|
-
// from the structural roots (index/wagon/composition/main/app
|
|
5
|
+
// from the structural roots (index/wagon/composition/main/app, and files package.json
|
|
6
|
+
// scripts run), and reports files
|
|
6
7
|
// reached by nothing in either direction.
|
|
7
8
|
//
|
|
8
9
|
// The import edges come from Bun's IN-PROCESS TypeScript parser rather than the
|
|
@@ -11,10 +12,32 @@
|
|
|
11
12
|
// obligation, measured correctly, and only possible because the runtime is Bun.
|
|
12
13
|
import { walk, readRoots, readExcludes, readText, emit } from "../../../lib/scan.mjs";
|
|
13
14
|
import * as M from "../ts_metrics.mjs";
|
|
14
|
-
import { basename } from "node:path";
|
|
15
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
16
|
+
import { existsSync } from "node:fs";
|
|
15
17
|
|
|
16
18
|
const RULE = "coder.bun.dead-code-reachability";
|
|
17
19
|
|
|
20
|
+
// A file a package.json script runs (`bun run docs/gen.ts`, `bun tools/x.ts`) is an entry point the
|
|
21
|
+
// repository itself declares, so it is a root too. The nearest package.json at or above the scan root.
|
|
22
|
+
function scriptRoots(root, all) {
|
|
23
|
+
for (let dir = resolve(root); ; dir = dirname(dir)) {
|
|
24
|
+
const manifest = join(dir, "package.json");
|
|
25
|
+
if (existsSync(manifest)) {
|
|
26
|
+
let scripts = {};
|
|
27
|
+
try { scripts = JSON.parse(readText(manifest) || "{}").scripts ?? {}; } catch { return []; }
|
|
28
|
+
const out = [];
|
|
29
|
+
for (const command of Object.values(scripts)) {
|
|
30
|
+
for (const [, path] of String(command).matchAll(/(?:^|[\s"'=])((?:\.\/)?[\w@./-]+\.tsx?)(?=$|[\s"';&|)])/g)) {
|
|
31
|
+
const file = join(dir, path);
|
|
32
|
+
if (all.has(file)) out.push(file);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
if (dirname(dir) === dir) return [];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
18
41
|
const violations = [];
|
|
19
42
|
const excludes = readExcludes();
|
|
20
43
|
for (const root of readRoots()) {
|
|
@@ -34,7 +57,7 @@ for (const root of readRoots()) {
|
|
|
34
57
|
graph.set(f, targets);
|
|
35
58
|
}
|
|
36
59
|
|
|
37
|
-
const roots = new Set(files.filter(M.isRootFile));
|
|
60
|
+
const roots = new Set([...files.filter(M.isRootFile), ...scriptRoots(root, all)]);
|
|
38
61
|
// No structural root means no anchor to measure reachability FROM; reporting
|
|
39
62
|
// every file as dead would be noise, not a finding. The Python sibling bails
|
|
40
63
|
// the same way.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// The twin of scale.ts's import list, with a different body.
|
|
2
|
+
import {
|
|
3
|
+
one,
|
|
4
|
+
two,
|
|
5
|
+
three,
|
|
6
|
+
four,
|
|
7
|
+
five,
|
|
8
|
+
six,
|
|
9
|
+
} from "./units";
|
|
10
|
+
|
|
11
|
+
// Shift a number by every unit.
|
|
12
|
+
export const offset = (n: number): number => n + one + two + three + four + five + six;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Two modules importing the same names from one shared module is sharing, not duplication:
|
|
2
|
+
// the identical specifier lists below must not be reported.
|
|
3
|
+
import {
|
|
4
|
+
one,
|
|
5
|
+
two,
|
|
6
|
+
three,
|
|
7
|
+
four,
|
|
8
|
+
five,
|
|
9
|
+
six,
|
|
10
|
+
} from "./units";
|
|
11
|
+
|
|
12
|
+
// Scale a number by every unit.
|
|
13
|
+
export const scale = (n: number): number => n * one * two * three * four * five * six;
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// Imports every module so nothing is unreachable.
|
|
3
3
|
import { total } from "./domain/total";
|
|
4
4
|
import { label } from "./domain/label";
|
|
5
|
+
import { scale } from "./domain/scale";
|
|
6
|
+
import { offset } from "./domain/offset";
|
|
5
7
|
|
|
6
8
|
// Run the tiny pipeline this fixture represents.
|
|
7
|
-
export const run = (xs: number[]): string => label(total(xs));
|
|
9
|
+
export const run = (xs: number[]): string => label(offset(scale(total(xs))));
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{ "private": true, "scripts": { "gen": "bun run tools/gen.ts --check" } }
|
|
@@ -228,12 +228,24 @@ export function normalizeLine(line) {
|
|
|
228
228
|
const TRIVIAL = new Set(["", "{", "}", "};", ");", "],", ")", "]", "});"]);
|
|
229
229
|
export const isTrivial = (n) => TRIVIAL.has(n);
|
|
230
230
|
|
|
231
|
+
// Import and re-export declarations are not code: two files that import the same names
|
|
232
|
+
// from one shared module match line for line BECAUSE the logic was shared, so the lines of
|
|
233
|
+
// a declaration (one line, or `import {` through `} from "…"`) are left out of the windows.
|
|
234
|
+
const DECLARATION = /^(import\b|export (type )?(\{|\*))/;
|
|
235
|
+
const DECLARATION_END = /\bfrom "S"|^import "S"|\}/;
|
|
236
|
+
|
|
231
237
|
// A 16-hex-char digest of each `minLines` window of non-trivial normalized lines —
|
|
232
238
|
// the same fingerprint shape the Python detector hashes with sha256[:16].
|
|
233
239
|
export function fragments(source, minLines, hasher) {
|
|
234
240
|
const nonTrivial = [];
|
|
241
|
+
let inDeclaration = false;
|
|
235
242
|
source.split("\n").forEach((line, i) => {
|
|
236
243
|
const n = normalizeLine(line);
|
|
244
|
+
if (!inDeclaration && DECLARATION.test(n)) inDeclaration = true;
|
|
245
|
+
if (inDeclaration) {
|
|
246
|
+
if (DECLARATION_END.test(n)) inDeclaration = false;
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
237
249
|
if (!isTrivial(n)) nonTrivial.push([i + 1, n]);
|
|
238
250
|
});
|
|
239
251
|
if (nonTrivial.length < minLines) return [];
|
package/integrity.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.10.
|
|
2
|
+
"version": "0.10.30",
|
|
3
3
|
"files": {
|
|
4
4
|
"HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
|
|
5
5
|
"PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"conventions/coder.bun/coder.bun.complexity-nesting.convention.yaml": "7d5a37ee0d5ccb9e97c0ba042f5c2e858e06f2361b6a47e687ec5af372c887b8",
|
|
31
31
|
"conventions/coder.bun/coder.bun.composition-consumer.convention.yaml": "5bd69cdf8cb6a73e692c73cad30dceb533f624b38a6a120fc4f8e660a2cf25e0",
|
|
32
32
|
"conventions/coder.bun/coder.bun.composition-root.convention.yaml": "74e30a8fa55b304ba2922fec59ecd8626d7a3c61411bb46bc5249188607f0de5",
|
|
33
|
-
"conventions/coder.bun/coder.bun.dead-code-reachability.convention.yaml": "
|
|
33
|
+
"conventions/coder.bun/coder.bun.dead-code-reachability.convention.yaml": "bce2c0f69c5c4f31a43278f3eabe993591e823cdb714f0841361ab470a134798",
|
|
34
34
|
"conventions/coder.bun/coder.bun.design-connected.convention.yaml": "21d9b1f11cb92f161acd163fe94d79022a0b35e269ea0db1c9bccbcfa4241b69",
|
|
35
35
|
"conventions/coder.bun/coder.bun.design-dependency-flow.convention.yaml": "e96b6850ce8d5811947a9d4b62855477a112c053524a1d97a4f7ba35efb17e07",
|
|
36
36
|
"conventions/coder.bun/coder.bun.design-hierarchy-import.convention.yaml": "067de2a858dba47ec11fab3ebbae052ea15d1481d9cb702a5eacc996c74c5922",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"conventions/coder.bun/coder.bun.dto-mapper.convention.yaml": "e6890bcb421e9c10e71788e92ba1a2f1f96eed222fd0f3259f81b175b28ec190",
|
|
44
44
|
"conventions/coder.bun/coder.bun.dto-placement.convention.yaml": "488576b8eba889b008e928357e62c323784da640bdcb986a06152ac4e4424568",
|
|
45
45
|
"conventions/coder.bun/coder.bun.dto-purity.convention.yaml": "2c87391f323b10e8545095c8b278547a5a4f3854ac02ddec1849f4a6c83676fc",
|
|
46
|
-
"conventions/coder.bun/coder.bun.duplication-intra-layer.convention.yaml": "
|
|
46
|
+
"conventions/coder.bun/coder.bun.duplication-intra-layer.convention.yaml": "5899b2b612108509a89be54db2c6f65f7e8fd798d6828ce81dd7d67e8ae4ae0c",
|
|
47
47
|
"conventions/coder.bun/coder.bun.error-response-bare-string.convention.yaml": "c2c0f859f6dd12702edf225c4148849e72b6c25e6d7cd28edd5d644ba277a23c",
|
|
48
48
|
"conventions/coder.bun/coder.bun.error-response-code-format.convention.yaml": "3048fed00b92c2559f7c52f73cfcce07b583eb47b12d04f0851e237ae3646567",
|
|
49
49
|
"conventions/coder.bun/coder.bun.green-header-order.convention.yaml": "8b00d89605cbd618c052805fb1129a7a65743c72a7627f7cd08f0685ddefa8aa",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"conventions/coder.bun/coder.bun.interlocking-runner-exists.convention.yaml": "f7fadc580d2896f7d8341f555e4447480809329f3901209d30c2e54181032f15",
|
|
64
64
|
"conventions/coder.bun/coder.bun.journey-runner-boundary.convention.yaml": "51df6caa810d887a84fc8196bc103465685400f12ba8a0504efef9b88c10fe33",
|
|
65
65
|
"conventions/coder.bun/coder.bun.layer-naming.convention.yaml": "009c21f6adbbc1e08d9781d7466b06e3d7fb7d935bf45b1df07fe37639a60a82",
|
|
66
|
-
"conventions/coder.bun/coder.bun.logging-console.convention.yaml": "
|
|
66
|
+
"conventions/coder.bun/coder.bun.logging-console.convention.yaml": "e85fc1038ac78799b36d1f4156b6547fb00441f1e3ac3ec93e59e7e895e37aef",
|
|
67
67
|
"conventions/coder.bun/coder.bun.logging-structured.convention.yaml": "a7c8bd90762bf1516d2af63d4fe731720bec9f58431009f69af1bfebc0398759",
|
|
68
68
|
"conventions/coder.bun/coder.bun.quality-comments.convention.yaml": "fcd42efd152c4815cd3fd7811b091321e4a0d5e13f9c251cfd82b248f27c9fd8",
|
|
69
69
|
"conventions/coder.bun/coder.bun.quality-mi.convention.yaml": "13dd95a87d67cb9b765b41cebcc3d33b117725edc28798a7d258cd6a66363621",
|
|
@@ -71,8 +71,8 @@
|
|
|
71
71
|
"conventions/coder.bun/coder.bun.responsive-no-fixed-width.convention.yaml": "09bb70ccb5350bd46007ba1cf7da55447bdb87b8b31677dacba2bddba5ea63ab",
|
|
72
72
|
"conventions/coder.bun/coder.bun.responsive-viewport-meta.convention.yaml": "705b1ed788a9c5b6d6a555d96662269e09c4ba5b30a7000d37ddf91393a65a88",
|
|
73
73
|
"conventions/coder.bun/coder.bun.runtime-executes-the-declaration.convention.yaml": "ed1643e92878a746298a20bfbc301c1f469d123d71ca272de1384d2f7cc03ba1",
|
|
74
|
-
"conventions/coder.bun/coder.bun.runtime-server-is-bun-serve.convention.yaml": "
|
|
75
|
-
"conventions/coder.bun/coder.bun.runtime-single-lockfile.convention.yaml": "
|
|
74
|
+
"conventions/coder.bun/coder.bun.runtime-server-is-bun-serve.convention.yaml": "43f3a709e59bf564eee5691ee57a92b4c92d9bea6cedbcdf060b692c14d47663",
|
|
75
|
+
"conventions/coder.bun/coder.bun.runtime-single-lockfile.convention.yaml": "93a90a41e39fa08461f86dc5585a30459120db69957b35f3840b44c949395dec",
|
|
76
76
|
"conventions/coder.bun/coder.bun.runtime-typecheck-enforced.convention.yaml": "375bce699e7ed25c8d4024bc3af3ad7ce007bb6260b096c212a063e2a7841744",
|
|
77
77
|
"conventions/coder.bun/coder.bun.security-hardcoded-secret.convention.yaml": "1a57d91aa91c52cc1efdf4168a2f3f147e7f25e979c47161d9757e1839ebbddd",
|
|
78
78
|
"conventions/coder.bun/coder.bun.security-missing-auth.convention.yaml": "2b937475e7a1f3f2718be6a3f0d94fa261ca65e72bbf8e3f2ca39ba143b90544",
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"conventions/coder.htmx/coder.htmx.verb-endpoint-same-origin.convention.yaml": "2128d26153e2d1f19c65a1e1665d4f82c78395daedb336d6ef1256e82b74aedb",
|
|
94
94
|
"conventions/coder.htmx/coder.htmx.verb-mutation-signals-progress.convention.yaml": "ae85c5ea01a09a3c41b939988088a8e5da91848d2710a1f0a1e0c86308638ac9",
|
|
95
95
|
"conventions/delivery/delivery.approved-sha-resolves.convention.yaml": "68f2ecc10870e780abb05bb31d211b1923d47b56672df4b3950f437aa9cf8ca8",
|
|
96
|
-
"conventions/delivery/delivery.board.convention.yaml": "
|
|
96
|
+
"conventions/delivery/delivery.board.convention.yaml": "fefc9f32f8ee85ee35b254cbc4aefec55a4b72eaa3a7b02b0ec21efb2074a013",
|
|
97
97
|
"conventions/delivery/delivery.config-schema.convention.yaml": "b59a69b7789ad0c3d0b1ac9ea2b955fbbb5dfa07a9ee169a037b406a198813f0",
|
|
98
98
|
"conventions/delivery/delivery.evidence-schema.convention.yaml": "9c6a22495dfb3e2fcbca700392866c3b5660084b1f5594230eb320b499d5e5cb",
|
|
99
99
|
"conventions/delivery/delivery.findings-resolved.convention.yaml": "f6574e93a9a2d4bdd6d6f5645720b1a647fc0048d95f7675fe783d2ddb62c05b",
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
"conventions/tester.bun/tester.bun.telemetry-test-binding.convention.yaml": "4566199fd401cd8666e08466cd3a5236f23ef356364e673b36a2e3c5ec78638d",
|
|
140
140
|
"conventions/tester.bun/tester.bun.telemetry-timing-semantics.convention.yaml": "5494744872deb34c175d741dfc79e9cfa720808dd7efaf118f709af417a1dd39",
|
|
141
141
|
"conventions/tester.bun/tester.bun.test-carries-urn-identity.convention.yaml": "4c5846bae7f9671eabf808f43ee3f134a67214113f0e15e9a60705dbdcbe8d10",
|
|
142
|
-
"conventions/tester.bun/tester.bun.test-imports-bun-test.convention.yaml": "
|
|
142
|
+
"conventions/tester.bun/tester.bun.test-imports-bun-test.convention.yaml": "6eef1c745315a69357d23bf07d318e504d425f76fcfdc120166b8ef2d4c820d6",
|
|
143
143
|
"conventions/tester.bun/tester.bun.test-isolation-no-live-state.convention.yaml": "3488af16431ea461e9128d15dbb0ad86324f4446bf36d5a662fab73687e8860e",
|
|
144
144
|
"conventions/tester.bun/tester.bun.test-no-self-skip.convention.yaml": "23fca575611f9c0740bcf64849a3c1600ef4cc1cc64df3c106d328b67283d8a3",
|
|
145
145
|
"conventions/tester.bun/tester.bun.test-phase-declared.convention.yaml": "cd22101bfcbc2eeca3da1aa2907ba6c944674c31cfaac5305f2149c6c429cf87",
|
|
@@ -649,14 +649,20 @@
|
|
|
649
649
|
"detectors/bun_ts_metrics_detector/checks/ts_complexity_cyclomatic.mjs": "56640b3a0f565c45a73b7c174adb1d6b4b6ac24d286a907bc1426f013cd6039e",
|
|
650
650
|
"detectors/bun_ts_metrics_detector/checks/ts_complexity_length.mjs": "e49fe93dbf2c52a91fe97b1e0b810d60f7d34d43581dfb96d4827640e6f5e3fd",
|
|
651
651
|
"detectors/bun_ts_metrics_detector/checks/ts_complexity_nesting.mjs": "8d8f61ea1748f33b037beb3f3fe5c84acbce395575e24fb1d01432813762e4d1",
|
|
652
|
-
"detectors/bun_ts_metrics_detector/checks/ts_dead_code.mjs": "
|
|
652
|
+
"detectors/bun_ts_metrics_detector/checks/ts_dead_code.mjs": "154fdf7b8e896f73ae86d7a9609a953fcb8d33c1b61eabcb6d190b06d64d97b4",
|
|
653
653
|
"detectors/bun_ts_metrics_detector/checks/ts_duplication.mjs": "12b5087c34741ce3fde573059d1afbaafc80ce1bfe8a235780113fd558dbbc7d",
|
|
654
654
|
"detectors/bun_ts_metrics_detector/checks/ts_quality_comments.mjs": "872d71d2165eba4cb39c975b09365cf67a64f1db5c55c0df9e86ff411e02ea31",
|
|
655
655
|
"detectors/bun_ts_metrics_detector/checks/ts_quality_mi.mjs": "69765d88441e98f4f38b7da0a416b7dab8b7f0072e562ea9d40d399802452067",
|
|
656
656
|
"detectors/bun_ts_metrics_detector/detect.mjs": "c98af0088388328db9fa014fc804fb4239627a85598d3654acdf66209cea619a",
|
|
657
657
|
"detectors/bun_ts_metrics_detector/fixtures/clean/domain/label.ts": "51e6fce80de70c168c750a2a71d93cde98a4a7fe2f29b4ff593ea2a531e1611a",
|
|
658
|
+
"detectors/bun_ts_metrics_detector/fixtures/clean/domain/offset.ts": "04b765ce74910a128e02c2a08676fd5b673cb952f2b8e2ee32fa5502a8ecb4a8",
|
|
659
|
+
"detectors/bun_ts_metrics_detector/fixtures/clean/domain/scale.ts": "fba087fb7b9a87a450f42a7d22119c8180b2093d25f819029f3004c815ce0863",
|
|
658
660
|
"detectors/bun_ts_metrics_detector/fixtures/clean/domain/total.ts": "af50d27155a6cb98acffad732eecdfe1c0d56c26efa0f2fcfc76e5f7f16b429d",
|
|
659
|
-
"detectors/bun_ts_metrics_detector/fixtures/clean/
|
|
661
|
+
"detectors/bun_ts_metrics_detector/fixtures/clean/domain/units.ts": "13d1013fe94ae57d2f455e882c93472caec4be862636763965f12cdbffd5ee5f",
|
|
662
|
+
"detectors/bun_ts_metrics_detector/fixtures/clean/index.ts": "8b023204f14afe36bd393bc6d3486ce1d737c7c1d6037153d7e53083de041da6",
|
|
663
|
+
"detectors/bun_ts_metrics_detector/fixtures/clean/package.json": "abd8243a7b615fc3765a9d1657965c515d2f5a971b7c76157d171d36fc5e84cf",
|
|
664
|
+
"detectors/bun_ts_metrics_detector/fixtures/clean/tools/banner.ts": "60e99be98df81bd287f4be011559a4baa3badb73a5667511329e4ec406bed3ca",
|
|
665
|
+
"detectors/bun_ts_metrics_detector/fixtures/clean/tools/gen.ts": "7540d0baa666cc05ad65326fc7bcabc464d9efae13a228e3365b999be5863a1a",
|
|
660
666
|
"detectors/bun_ts_metrics_detector/fixtures/dirty/bare.ts": "9f221d09cb5ee237d82329ef7ea878a00cb214707d8907bbc45894c14a7804c0",
|
|
661
667
|
"detectors/bun_ts_metrics_detector/fixtures/dirty/big.ts": "3989b9170c02035b0394d6a12324eb66a78c68148431d18ae616392b59072cb6",
|
|
662
668
|
"detectors/bun_ts_metrics_detector/fixtures/dirty/consumer.ts": "f05d34e097667797806ecfdd34fab5def16983eb285c654924d92ae7ff3ccfb6",
|
|
@@ -667,7 +673,7 @@
|
|
|
667
673
|
"detectors/bun_ts_metrics_detector/fixtures/dirty/long.ts": "b87149b1b550a00bc2de2b2b21a4d9925572c002221941fae8135e8fe0c27cd1",
|
|
668
674
|
"detectors/bun_ts_metrics_detector/fixtures/dirty/orphan.ts": "0f7acd0dffe90902a0a43cc45771b8a473d079efc9693112b3b85645c191c23d",
|
|
669
675
|
"detectors/bun_ts_metrics_detector/fixtures/dirty/typed.ts": "7c27ee1a674af7d5b2cce3a65bfec2111602d84a75817fd60f737c7d63fc9c83",
|
|
670
|
-
"detectors/bun_ts_metrics_detector/ts_metrics.mjs": "
|
|
676
|
+
"detectors/bun_ts_metrics_detector/ts_metrics.mjs": "d5da8c768dd7fe1717cc7f29b4115f8b2573c945cf9fb2e6d7c2d76adc186417",
|
|
671
677
|
"detectors/delivery_evidence/atdd.implementation.yaml": "2f6fc30dc353bef97bcb32ea54e4317d3c87cbd7ae1443fcef2aefc46280dc09",
|
|
672
678
|
"detectors/delivery_evidence/detect.mjs": "e98664dee8090a0b280cd285a54e9a6fb9ba3572af2666b4e3656ca5b0555509",
|
|
673
679
|
"detectors/delivery_evidence/fixtures/clean/atdd-bun.yaml": "f701861e3530bb5a25a70d73e248549fb96642f48db1ff4d3e8e4546aaa825ee",
|
|
@@ -971,7 +977,7 @@
|
|
|
971
977
|
"lib/frontend.mjs": "d6d9b29c3d7f2d162cda03bb34b20a3224a82887b6be3db7cab375510913c9a3",
|
|
972
978
|
"lib/imports.mjs": "3db4979269488fa26f6e7aefded6ece9c646be6587a10e249821a5a208109962",
|
|
973
979
|
"lib/interlocking.mjs": "d0803d080ac7d7477c51be70112df1e85213d339fd3c6b71f75f26145fa8accd",
|
|
974
|
-
"lib/scan.mjs": "
|
|
980
|
+
"lib/scan.mjs": "02b0fa864cab24157a8f27b50a87a2bb66a875f90cf6632c753c4627d61456ef",
|
|
975
981
|
"planner-nodes/ENFORCEMENT_SCOPE.yaml": "d8ec7039c219857f3d77af5d4d84612b533b69c1a5fcef935fbf46470adafed4",
|
|
976
982
|
"planner-nodes/nodes/planner.acceptance.abstract-fields-required.convention.yaml": "bd565693f0ae861f0561ad2f7a91a8c55f9a209c3acd59c5750cfcffd96a85d8",
|
|
977
983
|
"planner-nodes/nodes/planner.acceptance.authoring-guidelines.convention.yaml": "16348dceed5868041b832bde766e9c8d4a950d4032f1f3066b357644081a3b17",
|
|
@@ -1196,7 +1202,7 @@
|
|
|
1196
1202
|
"relationships.yaml": "1cb713364aff54fb6e299e0737fe75a5d7b2e1dbd0a79b16b793aa1d269207b5",
|
|
1197
1203
|
"src/agent.ts": "65e4579adb5a54977f557f8c783aba0e2a2a1d5cf3cea9f017b67ffe598b2f65",
|
|
1198
1204
|
"src/board-ui.ts": "16a843705552f8f18e0e328e90e4a2a9148d771ca69f2708fe4a91302947dfad",
|
|
1199
|
-
"src/board.ts": "
|
|
1205
|
+
"src/board.ts": "4b9aa5b356d67c1b4ab4018796f9d09afba6d443dbebbb733b5d6c6a8d5a1c87",
|
|
1200
1206
|
"src/ci.ts": "67e54d2cbfc44a9e42837d5e750af526d6255c539d3c452a4d5fca3226c0d9ac",
|
|
1201
1207
|
"src/cli.ts": "e5ab4d2435fdcbebf462b4c2b67b495bd975d5deb668b088d8cba04a9d8b78a5",
|
|
1202
1208
|
"src/delivery.ts": "2eaaf4fd87aea26dce70b4c88a4e0a2f25b9de165bc2e38330fd7eb449587901",
|
package/lib/scan.mjs
CHANGED
|
@@ -115,6 +115,55 @@ export function readExcludes() {
|
|
|
115
115
|
// length, preserving every line and column offset. A check that greps raw source
|
|
116
116
|
// otherwise fires on the rule's own name inside a comment or a doc string — the
|
|
117
117
|
// classic detector false positive.
|
|
118
|
+
// A `/` starts a regex literal where an expression may begin: at the start, after an operator or opening
|
|
119
|
+
// punctuation, or after a keyword such as `return`. After a name, a number or `)` / `]` it is division.
|
|
120
|
+
const REGEX_AFTER_WORD = new Set(["return", "typeof", "case", "do", "else", "in", "of", "new", "delete", "void", "throw", "yield", "await", "instanceof"]);
|
|
121
|
+
function regexMayStart(text, i) {
|
|
122
|
+
let k = i - 1;
|
|
123
|
+
while (k >= 0 && /\s/.test(text[k])) k--;
|
|
124
|
+
if (k < 0) return true;
|
|
125
|
+
const prev = text[k];
|
|
126
|
+
if ("(,=:[!&|?{};+-*%>~^".includes(prev)) return true; // not `<`: in TSX, `</b>` closes a tag
|
|
127
|
+
if (/[\w$]/.test(prev)) {
|
|
128
|
+
let w = k;
|
|
129
|
+
while (w >= 0 && /[\w$]/.test(text[w])) w--;
|
|
130
|
+
return REGEX_AFTER_WORD.has(text.slice(w + 1, k + 1));
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// The index just past the template literal opening at `start`. A `${ … }` substitution is code: it may hold strings,
|
|
136
|
+
// braces and templates of its own (escaped backticks included), so the template ends only at a backtick outside them.
|
|
137
|
+
function templateEnd(text, start) {
|
|
138
|
+
const n = text.length;
|
|
139
|
+
let j = start + 1;
|
|
140
|
+
while (j < n) {
|
|
141
|
+
const c = text[j];
|
|
142
|
+
if (c === "\\") { j += 2; continue; }
|
|
143
|
+
if (c === "`") return j + 1;
|
|
144
|
+
if (c === "$" && text[j + 1] === "{") {
|
|
145
|
+
let depth = 1;
|
|
146
|
+
j += 2;
|
|
147
|
+
while (j < n && depth > 0) {
|
|
148
|
+
const d = text[j];
|
|
149
|
+
if (d === "`") { j = templateEnd(text, j); continue; }
|
|
150
|
+
if (d === '"' || d === "'") {
|
|
151
|
+
let k = j + 1;
|
|
152
|
+
while (k < n && text[k] !== d && text[k] !== "\n") k += text[k] === "\\" ? 2 : 1;
|
|
153
|
+
j = k + 1;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (d === "{") depth++;
|
|
157
|
+
else if (d === "}") depth--;
|
|
158
|
+
j++;
|
|
159
|
+
}
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
j++;
|
|
163
|
+
}
|
|
164
|
+
return n;
|
|
165
|
+
}
|
|
166
|
+
|
|
118
167
|
export function maskLiteralsAndComments(text) {
|
|
119
168
|
const out = text.split("");
|
|
120
169
|
let i = 0;
|
|
@@ -135,7 +184,24 @@ export function maskLiteralsAndComments(text) {
|
|
|
135
184
|
j = j === -1 ? n : j + 2;
|
|
136
185
|
blank(i, j);
|
|
137
186
|
i = j;
|
|
138
|
-
} else if (ch ===
|
|
187
|
+
} else if (ch === "/" && regexMayStart(text, i)) {
|
|
188
|
+
// A regex literal, masked like a string: up to the unescaped `/` outside a character class, never past the line.
|
|
189
|
+
let j = i + 1, inClass = false;
|
|
190
|
+
while (j < n && text[j] !== "\n") {
|
|
191
|
+
if (text[j] === "\\") { j += 2; continue; }
|
|
192
|
+
if (text[j] === "[") inClass = true;
|
|
193
|
+
else if (text[j] === "]") inClass = false;
|
|
194
|
+
else if (text[j] === "/" && !inClass) break;
|
|
195
|
+
j++;
|
|
196
|
+
}
|
|
197
|
+
if (j >= n || text[j] !== "/") { i++; continue; } // no closing slash on the line: not a regex
|
|
198
|
+
blank(i + 1, j);
|
|
199
|
+
i = j + 1;
|
|
200
|
+
} else if (ch === "`") {
|
|
201
|
+
const j = templateEnd(text, i);
|
|
202
|
+
blank(i + 1, j - 1 >= i + 1 ? j - 1 : i + 1);
|
|
203
|
+
i = j;
|
|
204
|
+
} else if (ch === '"' || ch === "'") {
|
|
139
205
|
let j = i + 1;
|
|
140
206
|
while (j < n) {
|
|
141
207
|
if (text[j] === "\\") { j += 2; continue; }
|
package/package.json
CHANGED
package/src/board.ts
CHANGED
|
@@ -111,6 +111,13 @@ export async function read(url: string, topics: string[], since = "all", signal?
|
|
|
111
111
|
/** The longest one poll may take. ntfy answers a poll at once, so only a stalled request ever reaches it. */
|
|
112
112
|
const POLL_LIMIT_MS = 10_000;
|
|
113
113
|
|
|
114
|
+
/** What a listener prints when nothing has arrived for a while: the owner's check-in, for the agent to act on (move
|
|
115
|
+
* the work, or tell whoever it waits on), never to answer with a "still here" post. */
|
|
116
|
+
export function checkIn(agent: string, quietSeconds: number, rearm = false): string {
|
|
117
|
+
const quiet = quietSeconds <= 0 ? "" : quietSeconds < 60 ? ` for ${quietSeconds} s` : ` for ${Math.round(quietSeconds / 60)} min`;
|
|
118
|
+
return `CHECK-IN for ${agent}: no message${quiet}. Where are you? Are you moving forward, or blocked on someone, and do they know? Is your stakeholder informed? Are you avoiding ceremony and rabbit holes, effectiveness first, then efficiency? Act on it: move the work, or post once to whoever you wait on; never post just to say you are still here.${rearm ? " Then re-arm this listener." : ""}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
114
121
|
export async function waitFor(url: string, topics: string[], agent: string, since = "all", timeoutSeconds = 0, skip: string[] = [], intervalMs = 500, seen: (id: string) => void = () => {}): Promise<Message | null> {
|
|
115
122
|
const deadline = timeoutSeconds > 0 ? Date.now() + timeoutSeconds * 1000 : Infinity;
|
|
116
123
|
for (;;) {
|
|
@@ -170,7 +177,7 @@ export async function chat(args: string[], env: Record<string, string | undefine
|
|
|
170
177
|
// Without --since, continue from where this identity last stopped on these topics; either way, remember the place.
|
|
171
178
|
const file = cursorFile(me, topics, env);
|
|
172
179
|
const message = await waitFor(url, topics, me, flag("since") || loadCursor(file) || seedCursor(me, env) || "all", Number(flag("timeout") ?? 0), list(flag("skip")), 500, id => saveCursor(file, id));
|
|
173
|
-
if (!message) { console.
|
|
180
|
+
if (!message) { console.log(checkIn(me, Number(flag("timeout") ?? 0), true)); return 2; }
|
|
174
181
|
console.log(format(message, topics.length > 1));
|
|
175
182
|
return 0;
|
|
176
183
|
}
|
|
@@ -180,11 +187,15 @@ export async function chat(args: string[], env: Record<string, string | undefine
|
|
|
180
187
|
const file = cursorFile(me, topics, env), skip = list(flag("skip")), total = Number(flag("timeout") ?? 0);
|
|
181
188
|
const deadline = total > 0 ? Date.now() + total * 1000 : Infinity;
|
|
182
189
|
let since = flag("since") || loadCursor(file) || seedCursor(me, env) || "all";
|
|
190
|
+
// After --check-in seconds without a message (default 20 minutes; 0 = never), the agent is asked to check itself.
|
|
191
|
+
const quietLimit = Number(flag("check-in") ?? 1200) * 1000;
|
|
192
|
+
let quietSince = Date.now();
|
|
183
193
|
for (;;) {
|
|
184
194
|
const left = Math.ceil((deadline - Date.now()) / 1000);
|
|
185
195
|
if (left <= 0) return 0;
|
|
186
|
-
const message = await waitFor(url, topics, me, since, Math.min(60, left), skip, 500, id => { since = id; saveCursor(file, id); });
|
|
187
|
-
if (message) console.log(format(message, topics.length > 1));
|
|
196
|
+
const message = await waitFor(url, topics, me, since, Math.min(60, left, quietLimit > 0 ? Math.max(1, Math.ceil((quietSince + quietLimit - Date.now()) / 1000)) : 60), skip, 500, id => { since = id; saveCursor(file, id); });
|
|
197
|
+
if (message) { console.log(format(message, topics.length > 1)); quietSince = Date.now(); }
|
|
198
|
+
else if (quietLimit > 0 && Date.now() - quietSince >= quietLimit) { console.log(checkIn(me, Math.round((Date.now() - quietSince) / 1000))); quietSince = Date.now(); }
|
|
188
199
|
}
|
|
189
200
|
}
|
|
190
201
|
if (topics.length !== 1) throw new Error(`${command} takes one topic`);
|