@afokapu/atdd-bun 0.10.27 → 0.10.29
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/delivery/delivery.config-schema.convention.yaml +1 -1
- package/conventions/delivery/delivery.model-allowed.convention.yaml +5 -17
- package/conventions/delivery/delivery.operating-model.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 +24 -18
- package/lib/scan.mjs +30 -0
- package/package.json +1 -1
- package/planner-schemas/delivery-config.schema.json +2 -2
- package/src/board.ts +14 -3
- package/src/delivery.ts +8 -17
|
@@ -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: >-
|
|
@@ -13,7 +13,7 @@ content:
|
|
|
13
13
|
summary: >-
|
|
14
14
|
The skill and the validators read the same policy. A misspelled key would be ignored by one and not the other, so a malformed block is a finding, and the rest of the run judges evidence against the package defaults.
|
|
15
15
|
normative_text: |
|
|
16
|
-
The policy decides which models may write and review each stage, in
|
|
16
|
+
The policy decides which models may write and review each stage (an allowed set, in no order; "*" allows any), how independent a reviewer must be, and when a model counts as unavailable. It is read by the delivery skill when it dispatches work and by this profile when it judges the record, so both must see the same, valid document.
|
|
17
17
|
fix_hint: |
|
|
18
18
|
Correct the key the finding names. The defaults are:
|
|
19
19
|
|
|
@@ -4,31 +4,19 @@ kind: rule
|
|
|
4
4
|
status: active
|
|
5
5
|
name: Authors and reviewers are allowed models, and every fallback says why
|
|
6
6
|
statement: >-
|
|
7
|
-
Each work entry's writer
|
|
7
|
+
Each work entry's writer is a model its stage allows for writers, and each review's reviewer a model it allows for reviewers. A stage's list is the set of allowed models, in no order; "*" allows any model. Which allowed model does the work is the operator's choice at any moment, so no fallback has to explain it; the record names the model that actually ran (REQUIRED).
|
|
8
8
|
terms:
|
|
9
9
|
- term_id: fallback
|
|
10
10
|
text: >-
|
|
11
11
|
a recorded switch from a preferred model to a later one in the stage's list, with role (writer or reviewer) and a reason: an outage, a rate limit, or no auditable report, counted by failures; or the operator's order, referenced by its ruling. A REQUEST CHANGES verdict is never a reason.
|
|
12
12
|
content:
|
|
13
13
|
summary: >-
|
|
14
|
-
The lists are the operator's choice of
|
|
14
|
+
The lists are the operator's choice of which models may do the work. Switching between allowed models needs no policy edit and no paperwork, so a rate limit or a preference change is handled on the spot; a model outside the list is still reported.
|
|
15
15
|
normative_text: |
|
|
16
|
-
The
|
|
17
|
-
|
|
16
|
+
The record must name the model that ran. Reviewer independence (a reviewer is never the model that wrote the work it reviews, under different-model) is judged by delivery.reviewer-independent, and a disputed finding upheld on a fresh review blocks the tranche, so switching reviewers cannot buy an approval.
|
|
17
|
+
A fallback entry (kind outage, rate_limit, no_report, timeout or operator) may still record why a model was used; it is optional and, when present, must be well formed.
|
|
18
18
|
fix_hint: |
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
fallback:
|
|
22
|
-
- { role: reviewer, from: codex, kind: rate_limit, failures: 3, window: { from: "2026-09-25T09:00:00Z", to: "2026-09-25T09:08:00Z" }, reason: "429 on 3 attempts in 10 minutes" }
|
|
23
|
-
|
|
24
|
-
or, for a substitution the operator ordered:
|
|
25
|
-
|
|
26
|
-
- { role: writer, from: claude-opus, kind: operator, decided_by: "the owner", decided_at: "2026-09-27T23:00:00Z", ruling: "atdd-frg-workstation #abc123", reason: "Standing substitution while Claude is rate-limited: kimi for Opus-tier work" }
|
|
27
|
-
|
|
28
|
-
or use a model the stage lists.
|
|
29
|
-
exceptions:
|
|
30
|
-
- >-
|
|
31
|
-
When a stage lists one model, no fallback is possible: an unavailable model blocks the tranche.
|
|
19
|
+
Use a model the stage lists, or have the operator add it (or "*") to the stage's list in atdd-bun.yaml; the integrity check reports an added model once for the operator to approve.
|
|
32
20
|
metadata:
|
|
33
21
|
aliases:
|
|
34
22
|
- DELIVERY-MODEL-ALLOWED-001
|
|
@@ -11,13 +11,13 @@ terms:
|
|
|
11
11
|
Owns throughput, never the work. It splits the program into tranches with explicit dependencies and writes why, the scope and the split in docs/delivery/index.adoc; activates a tranche once its own dependencies have merged (one still waiting may run plan and its review, nothing after); starts one driver per tranche in its own worktree; keeps provider health for the program, telling every driver to skip a model that is down; intervenes when a tranche is idle without being blocked, or has deliverables and no commit after two hours; and hands BLOCKED disputed-finding to a human with both sides. It never writes, reviews or merges.
|
|
12
12
|
- term_id: driver
|
|
13
13
|
text: >-
|
|
14
|
-
Owns one tranche end to end. For each stage of delivery.stages, in order, it starts the stage's writer (
|
|
14
|
+
Owns one tranche end to end. For each stage of delivery.stages, in order, it starts the stage's writer (any model in its writer list, as the operator chooses) in the tranche worktree, commits red on its own before green starts, passes the stage's gate, and appends a work entry; for each stage with a reviewer it starts a review. It never reviews.
|
|
15
15
|
- term_id: review_run
|
|
16
16
|
text: >-
|
|
17
17
|
Every review is a fresh process in its own detached worktree at the exact SHA (git worktree add --detach), launched with a prompt that gives it the delivery.review convention, the stage, the base SHA, the SHA and the RED commit. Afterwards git status --porcelain must be empty and HEAD still the SHA, or the review is a REVIEWER_FAILURE and does not count. The raw output is kept in the tranche folder and named in the review's report.
|
|
18
18
|
- term_id: fallback
|
|
19
19
|
text: >-
|
|
20
|
-
|
|
20
|
+
Any model a stage lists may be used, so a switch needs no fallback: when a model is down or rate-limited, the driver moves to another listed model and records the one that ran. A fallback entry may still say why (outage, rate limit, no auditable report, timeout, or the operator's order); it is optional. A request-changes verdict is never a reason to switch reviewers: a disputed finding upheld on a fresh review blocks the tranche. When no listed model is available, when_exhausted block emits BLOCKED provider-unavailable.
|
|
21
21
|
- term_id: repair
|
|
22
22
|
text: >-
|
|
23
23
|
For each finding of a request-changes review the writer either fixes it or writes one rebuttal with evidence; a fresh review of the same stage follows. If that reviewer upholds a disputed finding, the driver emits BLOCKED disputed-finding and never disputes it again. Any commit, regenerated file, conflict fix or rebase after an approval cancels it and the change goes back to that stage's review.
|
|
@@ -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.29",
|
|
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,13 +93,13 @@
|
|
|
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": "
|
|
97
|
-
"conventions/delivery/delivery.config-schema.convention.yaml": "
|
|
96
|
+
"conventions/delivery/delivery.board.convention.yaml": "fefc9f32f8ee85ee35b254cbc4aefec55a4b72eaa3a7b02b0ec21efb2074a013",
|
|
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",
|
|
100
100
|
"conventions/delivery/delivery.merge-gate.convention.yaml": "48745ec23625b68be9b8fe2c6dcdcba88b2b38924f6593abf6987391ff0ee243",
|
|
101
|
-
"conventions/delivery/delivery.model-allowed.convention.yaml": "
|
|
102
|
-
"conventions/delivery/delivery.operating-model.convention.yaml": "
|
|
101
|
+
"conventions/delivery/delivery.model-allowed.convention.yaml": "e9690e95f3604483f182b3032ae60eac20109eb458509f14651945ccf07692f2",
|
|
102
|
+
"conventions/delivery/delivery.operating-model.convention.yaml": "1a86a1fe84695968a2fb719a899629af9a8baadcd90bdb8776f1f0208cebbbb4",
|
|
103
103
|
"conventions/delivery/delivery.review.convention.yaml": "b033579c2d90fcb098d325dcc07c7b084e048cca43fbe8e28159ec08a412018a",
|
|
104
104
|
"conventions/delivery/delivery.reviewer-independent.convention.yaml": "8c436d66ef5b1f6d6bc5dd55a235caca2490020e88665eecadb2ca41cd4149c5",
|
|
105
105
|
"conventions/delivery/delivery.stages-complete.convention.yaml": "0e5dd8057ad51eff7906e3424a9686a81be1814447afb9a30acab43d39e49b4c",
|
|
@@ -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": "7a4410f3bc2e2ebbdddb4f3dd6268987a01b9685e94c8a18ea05b4056daf4ea8",
|
|
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",
|
|
@@ -1179,7 +1185,7 @@
|
|
|
1179
1185
|
"planner-schemas/binding-lock.schema.json": "f2455606702f45fafa194549126d4a66e6c286ba1c4ba9e6bdcf932286a6d34d",
|
|
1180
1186
|
"planner-schemas/component.schema.json": "d20d3502c155627a120cb10d5e9bdec4ba2c73af9c3e9a9d10e3c351f167c72a",
|
|
1181
1187
|
"planner-schemas/contract-registry.schema.json": "b9dbaf6c87d4f4d676e3b4251344b9eada523b1c5e739c0423cabe267012c12e",
|
|
1182
|
-
"planner-schemas/delivery-config.schema.json": "
|
|
1188
|
+
"planner-schemas/delivery-config.schema.json": "7cd9c15ea4345ad83195b9a1c7d09a0b66275e9a3fdf8b5d6905d96bbec579d3",
|
|
1183
1189
|
"planner-schemas/delivery-evidence.schema.json": "72ba1450abe5482c43f2069e9ee7d121398008a0ddef36c6b269d38a16872e0e",
|
|
1184
1190
|
"planner-schemas/feature.schema.json": "eb4aa40d89067ae5c86a079bb3d8e4f45c8727f9e15db2b86c08dacf8e4ae56a",
|
|
1185
1191
|
"planner-schemas/journey.schema.json": "1ff705bcf89a2823cf60c9e1597e254f97844e7a74dc58299d53192ad2c9f8c3",
|
|
@@ -1196,10 +1202,10 @@
|
|
|
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": "6f46cd890e0dfeb4a928d969e902474fb059844561a111ca8bbfeb4ff3e2d83b",
|
|
1200
1206
|
"src/ci.ts": "67e54d2cbfc44a9e42837d5e750af526d6255c539d3c452a4d5fca3226c0d9ac",
|
|
1201
1207
|
"src/cli.ts": "e5ab4d2435fdcbebf462b4c2b67b495bd975d5deb668b088d8cba04a9d8b78a5",
|
|
1202
|
-
"src/delivery.ts": "
|
|
1208
|
+
"src/delivery.ts": "2eaaf4fd87aea26dce70b4c88a4e0a2f25b9de165bc2e38330fd7eb449587901",
|
|
1203
1209
|
"src/docs-capability.ts": "8a7a4360976773fe462b1860eaf26182b016bcb1fbcd6a6d5423bd81379dbe67",
|
|
1204
1210
|
"src/enforce.ts": "41a6f8c058a2a034996c817f224191403cc66ccc4ed4e04bbefc430014c783d8",
|
|
1205
1211
|
"src/hooks.ts": "2feeec2674c1462c94256de02ce59ccc6e3ff671ec4dca1bf9903c32888b600b",
|
package/lib/scan.mjs
CHANGED
|
@@ -115,6 +115,23 @@ 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
|
+
|
|
118
135
|
export function maskLiteralsAndComments(text) {
|
|
119
136
|
const out = text.split("");
|
|
120
137
|
let i = 0;
|
|
@@ -135,6 +152,19 @@ export function maskLiteralsAndComments(text) {
|
|
|
135
152
|
j = j === -1 ? n : j + 2;
|
|
136
153
|
blank(i, j);
|
|
137
154
|
i = j;
|
|
155
|
+
} else if (ch === "/" && regexMayStart(text, i)) {
|
|
156
|
+
// A regex literal, masked like a string: up to the unescaped `/` outside a character class, never past the line.
|
|
157
|
+
let j = i + 1, inClass = false;
|
|
158
|
+
while (j < n && text[j] !== "\n") {
|
|
159
|
+
if (text[j] === "\\") { j += 2; continue; }
|
|
160
|
+
if (text[j] === "[") inClass = true;
|
|
161
|
+
else if (text[j] === "]") inClass = false;
|
|
162
|
+
else if (text[j] === "/" && !inClass) break;
|
|
163
|
+
j++;
|
|
164
|
+
}
|
|
165
|
+
if (j >= n || text[j] !== "/") { i++; continue; } // no closing slash on the line: not a regex
|
|
166
|
+
blank(i + 1, j);
|
|
167
|
+
i = j + 1;
|
|
138
168
|
} else if (ch === '"' || ch === "'" || ch === "`") {
|
|
139
169
|
let j = i + 1;
|
|
140
170
|
while (j < n) {
|
package/package.json
CHANGED
|
@@ -76,8 +76,8 @@
|
|
|
76
76
|
"type": "array",
|
|
77
77
|
"minItems": 1,
|
|
78
78
|
"uniqueItems": true,
|
|
79
|
-
"items": { "type": "string", "pattern": "^[a-z][a-z0-9-]
|
|
80
|
-
"description": "
|
|
79
|
+
"items": { "type": "string", "pattern": "^(\\*|[a-z][a-z0-9-]*)$" },
|
|
80
|
+
"description": "The models allowed for the role, in no order: any listed model may do the work, and \"*\" allows any model."
|
|
81
81
|
},
|
|
82
82
|
"stage": {
|
|
83
83
|
"type": "object",
|
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): 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. 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))); 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`);
|
package/src/delivery.ts
CHANGED
|
@@ -39,7 +39,7 @@ export type DeliveryPolicy = {
|
|
|
39
39
|
};
|
|
40
40
|
|
|
41
41
|
/** The default operating model: two reviews, the plan and the whole change; the stages between them are written and
|
|
42
|
-
* held by their deterministic gates.
|
|
42
|
+
* held by their deterministic gates. Each list is the set of models allowed for the role, in no order; "*" allows any. */
|
|
43
43
|
const DEFAULT_STAGES: Partial<Record<Stage, { writer?: string[]; reviewer?: string[] }>> = {
|
|
44
44
|
plan: { writer: ["codex", "claude-opus", "kimi"], reviewer: ["glm", "claude-opus", "codex", "kimi"] },
|
|
45
45
|
red: { writer: ["glm", "claude-sonnet", "deepseek-flash", "claude-opus", "codex", "kimi"] },
|
|
@@ -122,10 +122,7 @@ export function loosenedDelivery(base: unknown, current: unknown): string[] {
|
|
|
122
122
|
for (const role of ["reviewer", "writer"] as const) {
|
|
123
123
|
const added = c[role].filter(model => !b[role].includes(model));
|
|
124
124
|
if (added.length && b[role].length) out.push(`delivery.stages.${stage}.${role} adds ${added.join(", ")}`);
|
|
125
|
-
// The lists are
|
|
126
|
-
// fallback model usable without the fallback.
|
|
127
|
-
const promoted = c[role].filter(model => b[role].includes(model) && c[role].indexOf(model) < b[role].indexOf(model));
|
|
128
|
-
if (promoted.length) out.push(`delivery.stages.${stage}.${role} [${b[role].join(", ")}] → [${c[role].join(", ")}] promotes ${promoted.join(", ")}`);
|
|
125
|
+
// The lists are allowed sets: reordering one changes nothing; only an added model (or "*") is a loosening.
|
|
129
126
|
}
|
|
130
127
|
// A stage that had no writer list now accepting writers is a loosening too: before, nothing could be written there.
|
|
131
128
|
if (!b.writer.length && c.writer.length) out.push(`delivery.stages.${stage}.writer adds ${c.writer.join(", ")}`);
|
|
@@ -235,18 +232,12 @@ export async function loadEvidence(root: string, policy: DeliveryPolicy): Promis
|
|
|
235
232
|
return out;
|
|
236
233
|
}
|
|
237
234
|
|
|
238
|
-
/** A model must
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
function checkModel(file: string, at: string, stage: Stage, role: "writer" | "reviewer", actor: Actor, list: string[],
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (index < 0) return [finding("delivery.model-allowed", file, `${at}: ${role} model '${actor.model}' is not in ${stage}.${role} [${list.join(", ")}]`)];
|
|
245
|
-
// `author` is the 0.9 spelling of `writer`.
|
|
246
|
-
const recorded = fallbacks.filter(entry => ((entry.role === "author" ? "writer" : entry.role) ?? defaultRole) === role).map(entry => entry.from);
|
|
247
|
-
for (const skipped of list.slice(0, index)) if (!recorded.includes(skipped) && !ineligible.includes(skipped)) out.push(finding("delivery.model-allowed", file, `${at}: ${role} '${actor.model}' is a fallback, but no fallback from '${skipped}' records why it was unavailable`));
|
|
248
|
-
for (const from of recorded) if (!list.slice(0, index).includes(from)) out.push(finding("delivery.model-allowed", file, `${at}: ${role} fallback from '${from}' does not precede '${actor.model}' in [${list.join(", ")}]`));
|
|
249
|
-
return out;
|
|
235
|
+
/** A model must be one the stage allows: its list is the set of allowed models, in no order, and "*" allows any. Which
|
|
236
|
+
* listed model does the work is the operator's choice at any moment, so no fallback has to explain it; the record still
|
|
237
|
+
* names the model that ran, and reviewer independence is judged separately (delivery.reviewer-independent). */
|
|
238
|
+
function checkModel(file: string, at: string, stage: Stage, role: "writer" | "reviewer", actor: Actor, list: string[], _fallbacks: Fallback[], _defaultRole: "writer" | "reviewer", _fallback: DeliveryPolicy["fallback"], _ineligible: string[] = []): PlanFinding[] {
|
|
239
|
+
if (list.includes("*") || list.includes(actor.model)) return [];
|
|
240
|
+
return [finding("delivery.model-allowed", file, `${at}: ${role} model '${actor.model}' is not allowed for ${stage}.${role} [${list.join(", ")}]`)];
|
|
250
241
|
}
|
|
251
242
|
|
|
252
243
|
/** The count and window of every recorded fallback are the driver's claims, but explicit ones: outside the policy is out of policy. */
|