Skip to content

Commit 698bab7

Browse files
committed
refactor: ban JSDoc in comment-free packages
The comment-free packages (agent-core-v2, agent-gateway, transcript) no longer allow JSDoc on exported symbols. The checker rejects every JSDoc block, the existing blocks are removed, and the package guides state the rule.
1 parent ff0582f commit 698bab7

162 files changed

Lines changed: 22 additions & 3459 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/skills/agent-core-dev/orient.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,10 @@ There is no domain-layer numbering — a domain may import any other domain, gui
6868

6969
## Comment convention
7070

71-
`packages/agent-core-v2/AGENTS.md` bans comments: no file headers, no section banners, no statement-level narration — the code is the source of truth. The only exception is JSDoc attached to exported symbols, which flows into the generated `.d.ts` and the consumers' IDE hover. Tooling directives (`eslint-disable`, `@ts-expect-error`, …) are banned too: fix the underlying lint/type problem instead, and put negative type-safety cases in compiler-asserted fixtures. DI scope is carried by registration: `LifecycleScope.App`, `LifecycleScope.Session`, or `LifecycleScope.Agent`. A `workspace*` filename marks workspace-domain ownership, not a DI scope (see service-authoring.md).
71+
`packages/agent-core-v2/AGENTS.md` bans comments entirely: no file headers, no section banners, no statement-level narration, no JSDoc (not even on exported symbols) — the code is the source of truth. The only exception is a load-bearing lint-suppression directive (`oxlint-disable` / `eslint-disable`) for a deliberate pattern; other tooling directives (`@ts-expect-error`, …) are banned: fix the underlying lint/type problem instead, and put negative type-safety cases in compiler-asserted fixtures. DI scope is carried by registration: `LifecycleScope.App`, `LifecycleScope.Session`, or `LifecycleScope.Agent`. A `workspace*` filename marks workspace-domain ownership, not a DI scope (see service-authoring.md).
7272

7373
## Red lines (this stage)
7474

7575
- Import via the `#/...` alias (mapped to `src/`); never reach into another domain's internals by relative path.
7676
- Short-lived may inject long-lived; never the reverse.
77-
- No comments — not file headers, not beside statements; exported-symbol JSDoc is the only exception.
77+
- No comments — not file headers, not beside statements, not JSDoc; a load-bearing lint-suppression directive is the only exception.

.agents/skills/agent-core-dev/service-authoring.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ Importing the package therefore fires every `register*` side effect, exactly as
296296

297297
## Comments
298298

299-
- **No comments** (orient.md): no file headers, no statement-level narration; the only exception is JSDoc attached to exported symbols.
299+
- **No comments** (orient.md): no file headers, no statement-level narration, no JSDoc; the only exception is a load-bearing lint-suppression directive.
300300
- **Methods and fields carry no comments by default.** Well-named identifiers and types say *what*; the code is the source of truth for *how*.
301301
- Write an inline comment only when the *why* is non-obvious (a hidden constraint, a subtle invariant, a workaround). One short line.
302302
- For unimplemented stubs, throw `NotImplementedError('feature')` rather than `throw new Error('TODO: …')` (errors.md).

.agents/skills/agent-core-dev/verify.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Walk the stages you touched and confirm:
2121
- **Design** — scope follows state identity; no `Map<sessionId, …>` at `App`; dependency arrows do not make a foundational layer know an upstream one; no cycle was routed around.
2222
- **Implement** — no `new` on `@IService`-carrying classes; `@IX` on constructor params only (service params after static params); interface + impl carry `_serviceBrand`; decorator names unique; coded errors only; flags for unreleased behavior.
2323
- **Test** — SUT resolved by interface; stubs under `test/`; scope tests re-register after `_clearScopedRegistryForTests()`; teardown through one `DisposableStore`.
24-
- **Files** — no comments (exported-symbol JSDoc excepted); registration runs from the impl file's top level; the new domain is exported from `src/index.ts`.
24+
- **Files** — no comments (no JSDoc either); registration runs from the impl file's top level; the new domain is exported from `src/index.ts`.
2525

2626
Then re-read the [global red lines](SKILL.md#global-red-lines) once — they catch most cross-stage mistakes in a single scan.
2727

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ The web bundle: `apps/pythinker-code/dist-web` is the committed, prebuilt bundle
8484
## Coding Rules
8585

8686
- English-only codebase. Use ASCII/Latin fixtures (e.g. `café`) for unicode tests.
87-
- `packages/agent-core-v2`, `packages/agent-gateway`, and `packages/transcript` are comment-free zones: no line/block comments; exceptions are JSDoc attached to exported symbols and load-bearing lint-suppression directives (`oxlint-disable` / `eslint-disable`), while other tooling directives (`@ts-expect-error`, …) stay banned. Enforced by `scripts/check-no-comments.mjs`, which runs as part of `pnpm lint`.
87+
- `packages/agent-core-v2`, `packages/agent-gateway`, and `packages/transcript` are comment-free zones: no line/block comments; no JSDoc either, not even on exported symbols; the only exception is a load-bearing lint-suppression directive (`oxlint-disable` / `eslint-disable`), while other tooling directives (`@ts-expect-error`, …) stay banned. Enforced by `scripts/check-no-comments.mjs`, which runs as part of `pnpm lint`.
8888
- `packages/acp-adapter`: pin `@agentclientprotocol/sdk` `^0.23.0` (0.24+ broke session-model API).
8989
- `tsgo` (`@typescript/native-preview`) available via `npx tsgo -p <tsconfig> --noEmit`; committed scripts use `tsc` — run both for type fixes.
9090
- Pass `undefined` directly for optional props — no conditional spread.

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ This is a TypeScript monorepo built for agent-assisted development. Keep the roo
5252

5353
## General Coding Rules
5454

55-
- `packages/agent-core-v2`, `packages/agent-gateway`, and `packages/transcript` are comment-free zones: no line/block comments; the exceptions are JSDoc attached to exported symbols and load-bearing lint-suppression directives (`oxlint-disable` / `eslint-disable`), while other tooling directives (`@ts-expect-error`, …) stay banned. Enforced by `scripts/check-no-comments.mjs`, which runs as part of `pnpm lint`.
55+
- `packages/agent-core-v2`, `packages/agent-gateway`, and `packages/transcript` are comment-free zones: no line/block comments; no JSDoc either, not even on exported symbols; the only exception is a load-bearing lint-suppression directive (`oxlint-disable` / `eslint-disable`), while other tooling directives (`@ts-expect-error`, …) stay banned. Enforced by `scripts/check-no-comments.mjs`, which runs as part of `pnpm lint`.
5656
- For optional object properties, pass `undefined` directly instead of using conditional spread.
5757
- YES: `{ user }`
5858
- NO: `{ ...(user ? { user } : undefined) }`

packages/agent-core-v2/AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ The four contribution seams (token → fold): config sections — `ConfigSection
2929

3030
## Comment conventions
3131

32-
- **No comments.** The code is the source of truth; do not write file headers, section banners, or implementation narration. The one exception is JSDoc attached to exported symbols (it flows into the generated `.d.ts` and the consumers' IDE hover); keep it focused on the public contract.
32+
- **No comments.** The code is the source of truth; do not write file headers, section banners, or implementation narration. No JSDoc either, not even on exported symbols. The one exception is a load-bearing lint-suppression directive (`oxlint-disable` / `eslint-disable`) for a deliberate pattern.
3333
- **Lint-suppression directives are the tooling exception.** `oxlint-disable` / `eslint-disable` comments are allowed where they suppress an active rule for a deliberate pattern (e.g. the Event2 class+payload-interface merging idiom). `@ts-expect-error`, `@ts-ignore`, and `ts-nocheck` stay banned — fix the underlying type problem instead; negative type-safety cases go into compiler-asserted fixtures.
3434

3535
## Telemetry

packages/agent-core-v2/scripts/check-import-boundaries.mjs

Lines changed: 0 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,4 @@
11
#!/usr/bin/env node
2-
/**
3-
* Import-boundary checker for `agent-core-v2`.
4-
*
5-
* Enforces two rules over `packages/agent-core-v2/src/**` (and the v1-import
6-
* ban over `test/**` too):
7-
*
8-
* 1. **No v1 imports** — v2 must never `import '@pymodel/agent-core'`
9-
* (or any subpath). v2 ports logic; it never depends on v1.
10-
* 2. **Kosong layering** — the `src/kosong/{contract,protocol,provider,model}`
11-
* subtree has strict internal rules:
12-
* - internal order: contract(L0) ← protocol(L1) ← provider/model(L2)
13-
* ← catalog(L3); a lower layer never imports a higher one (so L1
14-
* protocol never sees L2 — trait contexts carry only `providerId`).
15-
* - peer rule: `model` may import `provider`, never the reverse.
16-
* - purity: `contract` imports no other domain (only `_base` helpers)
17-
* and no external package at all (no SDKs, not even types);
18-
* `protocol` imports only `_base` + `contract` and no wire SDK.
19-
* All pure layers may additionally import the DI vocabulary modules
20-
* in `KOSONG_ALLOWED_VOCABULARY` (`app/scopes`).
21-
* - `provider/bases/` sub-boundary: base implementation files must not
22-
* import the registries (`protocolBase`, `protocolAdapterRegistry`),
23-
* `providerDefinition`, or any `*.contrib.ts` module. The
24-
* registration side lives in `*.contrib.ts` and in each base
25-
* directory's `index.ts` barrel (import = registration); both are
26-
* exempt.
27-
* Kosong directories that do not exist yet are skipped silently (later
28-
* refactor phases add them).
29-
*
30-
* Intra-package relative imports, `#/`-alias imports, and the package's
31-
* self-reference (`@pymodel/agent-core-v2/<path>` → `src/<path>`) are
32-
* resolved against `src/`. Sibling packages (`@pymodel/*` other than v1)
33-
* and third-party imports are out of scope (except for the kosong purity
34-
* bans above).
35-
*
36-
* Run: `node scripts/check-import-boundaries.mjs`. Exits non-zero on violation.
37-
*/
382

393
import { readFileSync, readdirSync, statSync } from 'node:fs';
404
import { dirname, join, relative, resolve } from 'node:path';
@@ -48,65 +12,23 @@ const TEST_ROOT = join(PKG_ROOT, 'test');
4812
const V1_PACKAGE = '@pymodel/agent-core';
4913
const SELF_PACKAGE_PREFIX = '@pymodel/agent-core-v2/';
5014

51-
/**
52-
* Scope directories introduced by the `src/{scope}/{domain}` layout. A path's
53-
* first segment is a scope tier, not a domain; the domain is the next segment.
54-
*/
5515
const SCOPE_DIRS = new Set(['app', 'workspace', 'session', 'agent', 'persistence', 'os', 'kosong']);
5616

57-
/**
58-
* Two-level scope directories: `persistence` and `os` use `{scope}/{tier}`
59-
* (e.g. `persistence/interface`, `os/backends`) as the domain key; `kosong`
60-
* uses `{scope}/{layer}` (e.g. `kosong/contract`) the same way.
61-
*/
6217
const TWO_LEVEL_SCOPES = new Set(['persistence', 'os', 'kosong']);
6318

64-
/**
65-
* Kosong-internal layer order: contract ← protocol ← provider/model.
66-
* A lower layer never imports a higher one; `model` → `provider`
67-
* is the only allowed peer edge. Keyed by the segment under `src/kosong/`.
68-
*/
6919
const KOSONG_LAYER = new Map([
7020
['contract', 0],
7121
['protocol', 1],
7222
['provider', 2],
7323
['model', 2],
7424
]);
7525

76-
/**
77-
* Kosong is a pure provider/model abstraction layer: NO kosong subdomain may
78-
* import another v2 domain outside kosong itself — only `_base` utilities
79-
* are allowed, plus the DI vocabulary modules in
80-
* `KOSONG_ALLOWED_VOCABULARY` (`app/scopes`: the `LifecycleScope` tier names
81-
* every self-registering Service needs). (`protocol` additionally sees
82-
* `kosong/contract`, handled by the internal-layer rule above.) Config
83-
* persistence, OAuth tokens, events,
84-
* and discovery orchestration all live in the upper `app/kosongConfig`
85-
* wrapper — kosong must never reach up to them.
86-
*/
8726
const KOSONG_BASE_ONLY_SUBDOMAINS = new Set(['contract', 'protocol', 'provider', 'model']);
8827

89-
/**
90-
* Non-`_base` modules the pure kosong layers may still import, keyed by
91-
* extensionless `src/`-relative path. `app/scopes` is DI vocabulary (the
92-
* scope tier names + topology declaration), not app orchestration, so a
93-
* kosong Service may read its registration tier from it.
94-
*/
9528
const KOSONG_ALLOWED_VOCABULARY = new Set(['app/scopes']);
9629

97-
/**
98-
* Wire SDK packages the pure kosong layers must never import — not even
99-
* types. `contract` in fact imports no external package at all; this list
100-
* covers the SDK ban for `protocol`.
101-
*/
10230
const KOSONG_BANNED_SDK_PACKAGES = ['@anthropic-ai/sdk', '@google/genai', 'openai'];
10331

104-
/**
105-
* Parse an absolute path under `src/kosong/` into its subdomain info.
106-
* Returns `undefined` for paths outside `src/kosong/`.
107-
* @param {string} absPath
108-
* @returns {{ sub: string | undefined, inBases: boolean, isContrib: boolean, isIndex: boolean } | undefined}
109-
*/
11032
function kosongInfoOf(absPath) {
11133
const rel = relative(SRC_ROOT, absPath);
11234
if (rel.startsWith('..') || rel === '') return undefined;
@@ -115,24 +37,13 @@ function kosongInfoOf(absPath) {
11537
const sub = segments[1];
11638
const last = segments[segments.length - 1] ?? '';
11739
return {
118-
// A file directly under `src/kosong/` has no subdomain.
11940
sub: sub === undefined || sub.endsWith('.ts') ? undefined : sub,
12041
inBases: sub === 'provider' && segments[2] === 'bases',
12142
isContrib: last.endsWith('.contrib.ts'),
12243
isIndex: last === 'index.ts',
12344
};
12445
}
12546

126-
/**
127-
* Whether an import target is off-limits to base implementation files under
128-
* `kosong/provider/bases/` (everything except `*.contrib.ts` and the
129-
* registration `index.ts` barrels): the base registry
130-
* (`kosong/protocol/protocolBase`), the adapter registry
131-
* (`kosong/provider/protocolAdapterRegistry`), the provider-definition
132-
* registry (`kosong/provider/providerDefinition`), or any contrib
133-
* side-effect module. Matches extensionless specifiers too.
134-
* @param {string} targetAbs
135-
*/
13647
function isKosongBasesBannedTarget(targetAbs) {
13748
const rel = relative(SRC_ROOT, targetAbs).split(/[\\/]/).join('/');
13849
const stripped = rel.endsWith('.ts') ? rel.slice(0, -'.ts'.length) : rel;
@@ -144,52 +55,30 @@ function isKosongBasesBannedTarget(targetAbs) {
14455
);
14556
}
14657

147-
/**
148-
* Resolve a `src/`-relative path to its domain, skipping the scope tier when
149-
* present. Returns `undefined` for top-level root files (e.g. the package
150-
* barrel `index.ts`, or the `errors`/`hooks` facades).
151-
* @param {string} rel
152-
*/
15358
function domainFromRel(rel) {
15459
const segments = rel.split(/[\\/]/);
15560
if (TWO_LEVEL_SCOPES.has(segments[0])) {
156-
// `src/{persistence|os}/{interface|backends}/…`
15761
return segments[1] ? `${segments[0]}/${segments[1]}` : segments[0];
15862
}
15963
if (SCOPE_DIRS.has(segments[0])) {
16064
if (segments.length === 2 && segments[1]?.endsWith('.ts')) return segments[0];
161-
// `src/{scope}/{domain}/…`
16265
if (segments[0] === 'agent' && segments[1] === 'task') return 'agentTask';
16366
if (segments[0] === 'agent' && segments[1] === 'plugin') return 'agentPlugin';
16467
return segments[1];
16568
}
16669
return segments[0];
16770
}
16871

169-
/**
170-
* Determine the v2 domain for an *import target* absolute path. A target may
171-
* resolve straight to a domain directory — e.g. the bare domain import
172-
* `#/turn` resolves to `src/agent/turn`, whose domain is `turn`.
173-
* @param {string} targetAbs
174-
*/
17572
function targetDomainOf(targetAbs) {
17673
const rel = relative(SRC_ROOT, targetAbs);
17774
if (rel.startsWith('..') || rel === '') return undefined;
17875
return domainFromRel(rel);
17976
}
18077

181-
/**
182-
* Resolve an import specifier to an absolute v2 `src/` path, or `undefined`
183-
* when the specifier is not an intra-v2 import.
184-
* @param {string} specifier
185-
* @param {string} fromFile absolute path of the importing file
186-
*/
18778
function resolveIntraV2(specifier, fromFile) {
18879
if (specifier.startsWith('#/')) {
18980
return join(SRC_ROOT, specifier.slice(2));
19081
}
191-
// The package's legal self-reference: `@pymodel/agent-core-v2/x` maps
192-
// to `src/x` via the `./*` export.
19382
if (specifier.startsWith(SELF_PACKAGE_PREFIX)) {
19483
return join(SRC_ROOT, specifier.slice(SELF_PACKAGE_PREFIX.length));
19584
}
@@ -199,22 +88,9 @@ function resolveIntraV2(specifier, fromFile) {
19988
return undefined;
20089
}
20190

202-
// Matches: import ... from 'x' | export ... from 'x' | import('x') | require('x')
20391
const IMPORT_RE =
20492
/(?:import|export)\s+(?:type\s+)?(?:[^'";]*?\s+from\s+)?['"]([^'"]+)['"]|(?:import|require)\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
20593

206-
/**
207-
* @typedef {{ file: string, line: number, message: string }} Violation
208-
*/
209-
210-
/**
211-
* Check source text for boundary violations. `absFile` is used only to
212-
* resolve relative specifiers and determine the source location; the file
213-
* need not exist on disk (handy for tests).
214-
* @param {string} source
215-
* @param {string} absFile
216-
* @returns {Violation[]}
217-
*/
21894
export function checkSource(source, absFile) {
21995
const violations = [];
22096
const inSrc = !relative(SRC_ROOT, absFile).startsWith('..');
@@ -226,7 +102,6 @@ export function checkSource(source, absFile) {
226102
if (!specifier) continue;
227103
const line = source.slice(0, match.index).split('\n').length;
228104

229-
// Rule 1: v2 must not import v1.
230105
if (specifier === V1_PACKAGE || specifier.startsWith(`${V1_PACKAGE}/`)) {
231106
violations.push({
232107
file: absFile,
@@ -236,15 +111,11 @@ export function checkSource(source, absFile) {
236111
continue;
237112
}
238113

239-
// Rule 2: kosong subtree (production code only).
240114
if (!inSrc) continue;
241115
const targetAbs = resolveIntraV2(specifier, absFile);
242116
const sourceKosong = kosongInfoOf(absFile);
243117
if (sourceKosong === undefined) continue;
244118

245-
// Rule 2a: kosong purity bans on external packages. The L0 contract
246-
// imports no external package at all (no SDKs, not even types); the L1
247-
// protocol layer is SDK-free but may use general-purpose packages.
248119
if (targetAbs === undefined) {
249120
if (sourceKosong.sub === 'contract') {
250121
violations.push({
@@ -267,9 +138,6 @@ export function checkSource(source, absFile) {
267138
continue;
268139
}
269140

270-
// Rule 2b: kosong-internal layering. Runs even for same-domain imports
271-
// because the provider/bases sub-boundary also bans same-domain targets
272-
// (registries and contrib modules live beside the bases).
273141
const targetKosong = kosongInfoOf(targetAbs);
274142
if (targetKosong !== undefined) {
275143
const sourceKosongLayer = KOSONG_LAYER.get(sourceKosong.sub);
@@ -304,11 +172,6 @@ export function checkSource(source, absFile) {
304172
continue;
305173
}
306174

307-
// Rule 2c: outside the kosong subtree, kosong code may only depend on
308-
// `_base` utilities plus the DI vocabulary in KOSONG_ALLOWED_VOCABULARY
309-
// (`protocol` additionally sees `kosong/contract`,
310-
// handled by Rule 2b above). This is what keeps kosong a pure
311-
// abstraction layer with no upward dependencies.
312175
if (KOSONG_BASE_ONLY_SUBDOMAINS.has(sourceKosong.sub)) {
313176
const targetDomain = targetDomainOf(targetAbs);
314177
const targetRel = relative(SRC_ROOT, targetAbs).split(/[\\/]/).join('/');
@@ -326,17 +189,11 @@ export function checkSource(source, absFile) {
326189
return violations;
327190
}
328191

329-
/**
330-
* Check a single source file for boundary violations.
331-
* @param {string} absFile
332-
* @returns {Violation[]}
333-
*/
334192
export function checkFile(absFile) {
335193
return checkSource(readFileSync(absFile, 'utf8'), absFile);
336194
}
337195

338196
function walk(dir) {
339-
/** @type {string[]} */
340197
const out = [];
341198
for (const entry of readdirSync(dir)) {
342199
if (entry === 'node_modules' || entry === 'dist') continue;

0 commit comments

Comments
 (0)