New:Socket for Asana Is Now Available.Learn more
Get Started

@developerz.ai/aitm

Package Overview
Dependencies
Maintainers
2
Versions
57
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@developerz.ai/aitm - npm Package Compare versions

Comparing version
0.0.54
to
0.0.55
+5
-1
dist/agent-config/coding-style.d.ts

@@ -14,3 +14,6 @@ import { type LanguageModel, type TimeoutConfiguration } from 'ai';

};
export declare const TEST_GLOBS: readonly ["**/*.test.ts"];
export declare const SOURCE_SAMPLE_LIMIT = 5;
export declare const TEST_SAMPLE_LIMIT = 3;
export declare const SAMPLE_CHAR_LIMIT = 2500;
export declare const MAX_SAMPLE_FILE_BYTES = 200000;
export declare class StyleDistiller {

@@ -22,1 +25,2 @@ private readonly init;

export declare function composeStyleGuide(config: AgentConfig | null, digest: string): string;
export declare function isTestPath(relPath: string): boolean;

@@ -1,7 +0,78 @@

import { readdir, readFile } from 'node:fs/promises';
import { basename, join } from 'node:path';
import { readdir, readFile, stat } from 'node:fs/promises';
import { basename, extname, join, relative } from 'node:path';
import { withTimeout } from '@developerz.ai/ai-claude-compat';
import { generateText } from 'ai';
import { reportUsage } from "../observability/usage-sink.js";
export const TEST_GLOBS = ['**/*.test.ts'];
export const SOURCE_SAMPLE_LIMIT = 5;
export const TEST_SAMPLE_LIMIT = 3;
export const SAMPLE_CHAR_LIMIT = 2500;
export const MAX_SAMPLE_FILE_BYTES = 200_000;
const MAX_WALK_FILES = 4000;
const MAX_WALK_DEPTH = 8;
const IGNORED_DIRS = new Set([
'node_modules',
'dist',
'build',
'target',
'vendor',
'coverage',
'venv',
'__pycache__',
'out',
'Pods',
'tmp',
'testdata',
'fixtures',
'snapshots',
]);
const SOURCE_EXTENSIONS = new Set([
'.ts',
'.tsx',
'.js',
'.jsx',
'.mjs',
'.cjs',
'.rs',
'.py',
'.go',
'.rb',
'.java',
'.kt',
'.kts',
'.php',
'.cs',
'.swift',
'.c',
'.cc',
'.cpp',
'.h',
'.hpp',
'.ex',
'.exs',
'.scala',
'.sh',
'.sql',
'.vue',
'.svelte',
]);
const CONFIG_FILE_PATTERNS = [
/^biome\.jsonc?$/,
/^tsconfig.*\.json$/,
/^\.eslintrc.*$/,
/^\.prettierrc.*$/,
/^Cargo\.toml$/,
/^rustfmt\.toml$/,
/^clippy\.toml$/,
/^pyproject\.toml$/,
/^setup\.cfg$/,
/^ruff\.toml$/,
/^go\.mod$/,
/^\.golangci\.(ya?ml|toml)$/,
/^Gemfile$/,
/^\.rubocop\.ya?ml$/,
/^composer\.json$/,
/^Makefile$/,
/^justfile$/,
/^\.editorconfig$/,
];
const COMPLETION_MARKER = 'CODING_STYLE_COMPLETE';

@@ -15,5 +86,5 @@ const INTRO = [

'NOT restate or summarize its rules — the agent already has them. Your job is the conventions it',
'does not spell out: the patterns actually visible in the config files and scripts below (where',
'tests live and how they are named, the commands that gate a commit, what the formatter and',
'compiler enforce). Concrete paths and commands beat prose.',
'does not spell out: the patterns actually visible in the config files, scripts, and REAL SOURCE',
'FILES below (naming, error handling, module shape, where tests live and how they are named, the',
'commands that gate a commit). Concrete paths and commands beat prose.',
].join('\n');

@@ -89,9 +160,81 @@ const OUTPUT_FORMAT = [

signals.push({ label: 'package.json scripts', body: scripts });
for (const block of await gatherSourceSamples(repoRoot))
signals.push(block);
return signals;
}
async function gatherSourceSamples(repoRoot) {
const files = await walkSourceFiles(repoRoot);
if (files.length === 0)
return [];
const sized = await Promise.all(files.map(async (path) => ({
path,
size: (await stat(path).catch(() => null))?.size ?? 0,
})));
const plausible = sized.filter((f) => f.size > 0 && f.size <= MAX_SAMPLE_FILE_BYTES);
const tests = plausible.filter((f) => isTestPath(relative(repoRoot, f.path)));
const sources = plausible.filter((f) => !isTestPath(relative(repoRoot, f.path)));
const signals = [];
const source = await renderSamples(repoRoot, pickSamples(sources, SOURCE_SAMPLE_LIMIT));
if (source !== null)
signals.push({ label: 'source samples', body: source });
const test = await renderSamples(repoRoot, pickSamples(tests, TEST_SAMPLE_LIMIT));
if (test !== null)
signals.push({ label: 'test samples', body: test });
return signals;
}
function pickSamples(candidates, limit) {
return [...candidates]
.sort((a, b) => b.size - a.size || a.path.localeCompare(b.path))
.slice(0, limit);
}
async function renderSamples(repoRoot, picked) {
const blocks = [];
for (const file of picked) {
const body = await readIfPresent(file.path);
if (body === null || body.trim() === '')
continue;
blocks.push(`--- ${relative(repoRoot, file.path)} ---\n${body.slice(0, SAMPLE_CHAR_LIMIT)}`);
}
return blocks.length === 0 ? null : blocks.join('\n\n');
}
export function isTestPath(relPath) {
const lower = relPath.toLowerCase().replaceAll('\\\\', '/');
const base = lower.split('/').pop() ?? lower;
if (lower
.split('/')
.slice(0, -1)
.some((seg) => /^(tests?|specs?)$/.test(seg)))
return true;
return /[._-](test|spec)\./.test(base) || /^test_/.test(base) || /_(test|spec)\./.test(base);
}
async function walkSourceFiles(root) {
const found = [];
const queue = [{ dir: root, depth: 0 }];
while (queue.length > 0 && found.length < MAX_WALK_FILES) {
const next = queue.shift();
if (!next)
break;
const entries = await readdir(next.dir, { withFileTypes: true }).catch(() => []);
for (const entry of entries) {
if (entry.name.startsWith('.'))
continue;
const full = join(next.dir, entry.name);
if (entry.isDirectory()) {
if (!IGNORED_DIRS.has(entry.name) && next.depth < MAX_WALK_DEPTH) {
queue.push({ dir: full, depth: next.depth + 1 });
}
continue;
}
if (entry.isFile() && SOURCE_EXTENSIONS.has(extname(entry.name))) {
found.push(full);
if (found.length >= MAX_WALK_FILES)
break;
}
}
}
return found;
}
async function gatherConfigFiles(repoRoot) {
const names = await readdir(repoRoot).catch(() => []);
const picked = names
.filter((name) => name === 'biome.json' || /^tsconfig.*\.json$/.test(name))
.sort();
const picked = names.filter((name) => CONFIG_FILE_PATTERNS.some((re) => re.test(name))).sort();
const blocks = [];

@@ -128,12 +271,3 @@ for (const name of picked) {

.join('\n\n');
return [
INTRO,
'',
'## Raw style signals',
blocks,
'',
`Test file globs to account for: ${TEST_GLOBS.join(', ')}`,
'',
OUTPUT_FORMAT,
].join('\n');
return [INTRO, '', '## Raw style signals', blocks, '', OUTPUT_FORMAT].join('\n');
}

@@ -140,0 +274,0 @@ function cleanDigest(raw) {

+12
-18

@@ -93,20 +93,14 @@ export const PLANNER_SYSTEM_PREFIX = [

'wrote the manifest, so YOU decide how many editors the work divides into and what each one owns.',
'Say it with the `editor` field: give every entry a short label naming its owner, and entries sharing',
'a label go to one editor as a whole assignment. Group by what must be written TOGETHER — a route,',
'its service and its test are one editor even though they sit in three directories. There is no',
'grouping rule behind you: entries you leave untagged are ONE editor, because work you did not',
'divide is not divided.',
'- Judge each assignment before you send it. TOO BIG if it spans work that does not inform itself —',
' split it. TOO SMALL if it is a file another editor is already opening, or a one-line change — merge',
' it in. Keep splitting or merging until each editor owns one coherent, self-contained unit.',
'- An editor is not rationed: it reads and writes as much as its assignment needs, for as long as it',
' needs. So a 9-file assignment held together by one feature is ONE editor, not two — and TWO',
' editors is a fine answer for a task that only divides in two. Splitting is what costs; size the',
' team to the work.',
'- a very large file is still ONE leaf (one path = one owner); never two leaves on one path, they',
' clobber each other.',
'- carve DISJOINT, non-interfering scopes: the harness already enforces one owner per path, but YOU',
' keep the regions apart — no two leaves editing files that import each other, and no two leaves',
' running a shared side effect (install/migrate/test that mutates shared state). Overlapping',
' concerns mean you split on the wrong axis; do those inline instead.',
'Say it with the `editor` field: label every entry with its owner; entries sharing a label are one',
'editor. Group by what must be written TOGETHER — a route, its service, and its test are one editor',
'across three directories. Untagged entries are ONE editor: work you did not divide is not divided.',
'- TOO BIG if it spans work that does not inform itself — split. TOO SMALL if it is a file another',
' editor already opens, or a one-line change — merge in. Each editor owns one self-contained unit.',
'- An editor is NEVER rationed: it reads and writes as much as its assignment needs. A 9-file',
' assignment held together by one feature is ONE editor; two editors is a fine answer for work that',
' divides in two. Splitting is what costs — size the team to the work.',
'- One path = ONE leaf, however large the file. Two leaves on a path clobber each other.',
'- Carve DISJOINT scopes: no two leaves editing files that import each other, none sharing a side',
' effect (install/migrate/test that mutates shared state). Overlap means you split on the wrong',
' axis — do that work inline.',
'',

@@ -113,0 +107,0 @@ "Each manifest entry's `purpose` is the ENTIRE brief its leaf sees — it never sees the task, the",

{
"name": "@developerz.ai/aitm",
"version": "0.0.54",
"version": "0.0.55",
"description": "Autonomous task orchestrator. Goal in, merged PRs out.",

@@ -46,3 +46,3 @@ "license": "MIT",

"@ai-sdk/mcp": "^1.0.42",
"@developerz.ai/ai-claude-compat": "0.0.54",
"@developerz.ai/ai-claude-compat": "0.0.55",
"@openrouter/ai-sdk-provider": "^2.9.0",

@@ -49,0 +49,0 @@ "ai": "^6.0.182",