Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions docs/WORKER-PACKETS.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,11 @@ with the task receipt; inspect it before dispatch. Repeated anchors do not repea
the same source lines in the packet.

The planner selects the smallest enclosing supported function, method,
constructor, accessor, named arrow/function-expression owner, or statement-level
call with a direct function callback. A callback call is a syntactic category,
constructor, accessor, named arrow/function-expression owner, statement-level
call with a direct function callback, named class, interface, type alias or
enum. A field or signature line therefore selects its whole declaration, while a
line inside a method still selects only the method; imports and other top-level
statements are not planned. A callback call is a syntactic category,
not a guarantee that it is a test: nested `it(...)` inside `describe(...)` selects
the `it` call, but an anchor inside a smaller callback-bearing helper may select
that helper. Anchor the outer declaration/call when its whole block is needed.
Expand Down
6 changes: 6 additions & 0 deletions packages/context-tools/src/repository-packet-mcp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,9 @@ describe('repository packet compact rendering', () => {
} finally { await connection.close() }
})
})

describe('fixture repository isolation', () => {
it('runs without inherited Git hook variables', () => {
expect(Object.keys(process.env).filter((key) => key.startsWith('GIT_'))).toEqual([])
})
})
5 changes: 5 additions & 0 deletions packages/context-tools/src/source-packet.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,11 @@ function plannerCandidates(source) {
add(property, 'property', source.text[property.end] === ',' ? property.end + 1 : property.end);
}
if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression) && node.expression.arguments.some((argument) => ts.isArrowFunction(argument) || ts.isFunctionExpression(argument))) add(node, 'callbackCall');
// Declarations own the lines between their members (fields, signatures, enum members).
if (ts.isClassDeclaration(node) && node.name) add(node, 'class');
if (ts.isInterfaceDeclaration(node)) add(node, 'interface');
if (ts.isTypeAliasDeclaration(node)) add(node, 'type');
if (ts.isEnumDeclaration(node)) add(node, 'enum');
ts.forEachChild(node, visit);
};
visit(source);
Expand Down
2 changes: 1 addition & 1 deletion packages/context-tools/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import { defineConfig } from 'vitest/config'
export default defineConfig({ test: { include: ['src/**/*.test.ts'], environment: 'node' } })
export default defineConfig({ test: { include: ['src/**/*.test.ts'], environment: 'node', setupFiles: ['./vitest.setup.mjs'] } })
4 changes: 4 additions & 0 deletions packages/context-tools/vitest.setup.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Git exports GIT_DIR, GIT_INDEX_FILE and similar into hooks. Fixture repositories
// created by `git -C <tmp>` would otherwise write to the repository being pushed,
// including its shared config, so every test runs without them.
for (const key of Object.keys(process.env)) if (key.startsWith('GIT_')) delete process.env[key]
4 changes: 4 additions & 0 deletions test/test_ecosystem_snapshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
import ecosystem_snapshot as snapshot

CLI = Path(snapshot.__file__).resolve()
# Git exports GIT_DIR and similar into hooks; fixture repositories must not
# inherit them, or `git -C <tmp>` writes to the repository being pushed.
for _key in [key for key in os.environ if key.startswith('GIT_')]:
del os.environ[_key]


class SnapshotBoundaryTests(unittest.TestCase):
Expand Down
12 changes: 12 additions & 0 deletions test/worker-packet.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ test('planner rejects unsupported, malformed, signature-only and ambiguous same-
await assert.rejects(planPacket({ root, spec: await spec(root, planBase({ sources: [{ path: 'bad.ts', line: 1 }] })) }), /parse errors/);
});

test('planner selects class, interface, type and enum declarations for lines outside any function', async () => {
const root = await fixture();
await writeFile(join(root, 'src.ts'), `import { a } from './a';\n/** Doc. */\nexport class Owner extends Base {\n field = 1;\n method() {\n return a;\n }\n}\ninterface Shape {\n size: number;\n}\ntype Pair = {\n left: string;\n};\nenum Mode {\n On,\n}\n`);
const result = await planPacket({ root, spec: await spec(root, planBase({ sources: [
{ path: 'src.ts', line: 4 }, { path: 'src.ts', line: 6 }, { path: 'src.ts', line: 10 }, { path: 'src.ts', line: 13 }, { path: 'src.ts', line: 16 },
] })) });
assert.deepEqual(result.coverage.resolutions.map(({ kind, startLine, endLine }) => ({ kind, startLine, endLine })), [
{ kind: 'class', startLine: 2, endLine: 8 }, { kind: 'method', startLine: 5, endLine: 7 }, { kind: 'interface', startLine: 9, endLine: 11 }, { kind: 'type', startLine: 12, endLine: 14 }, { kind: 'enum', startLine: 15, endLine: 17 },
]);
await assert.rejects(planPacket({ root, spec: await spec(root, planBase({ sources: [{ path: 'src.ts', line: 1 }] })) }), /no supported/);
});

test('plan CLI writes the v1 packet and reports coverage', async () => {
const root = await fixture();
await writeFile(join(root, 'src.ts'), 'const named = () => {\n return 1;\n};\n');
Expand Down
Loading