@sicilianwildcat/aiready 0.1.2 → 0.1.4
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/dist/cli.js +197 -35
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -133,6 +133,59 @@ function loadAgentEntry(targetDir) {
|
|
|
133
133
|
}
|
|
134
134
|
return null;
|
|
135
135
|
}
|
|
136
|
+
var GRAPHIFY_DIRECT_PATHS = ["graphify-out/graph.json", ".graphify/graph.json"];
|
|
137
|
+
function findGraphifyOutput(target) {
|
|
138
|
+
for (const p of GRAPHIFY_DIRECT_PATHS) {
|
|
139
|
+
const full = (0, import_path2.join)(target, p);
|
|
140
|
+
if (exists(full)) return full;
|
|
141
|
+
}
|
|
142
|
+
const datedBase = (0, import_path2.join)(target, "graphify-out");
|
|
143
|
+
if (exists(datedBase)) {
|
|
144
|
+
const subdirs = listDirs(datedBase).sort().reverse();
|
|
145
|
+
for (const subdir of subdirs) {
|
|
146
|
+
const candidate = (0, import_path2.join)(datedBase, subdir, "graph.json");
|
|
147
|
+
if (exists(candidate)) return candidate;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
function loadFromGraph(target, graphPath) {
|
|
153
|
+
const raw = readFile(graphPath);
|
|
154
|
+
if (!raw) return [];
|
|
155
|
+
let graph;
|
|
156
|
+
try {
|
|
157
|
+
graph = JSON.parse(raw);
|
|
158
|
+
} catch {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
const nodeDegree = /* @__PURE__ */ new Map();
|
|
162
|
+
for (const link of graph.links ?? []) {
|
|
163
|
+
nodeDegree.set(link.source, (nodeDegree.get(link.source) ?? 0) + 1);
|
|
164
|
+
nodeDegree.set(link.target, (nodeDegree.get(link.target) ?? 0) + 1);
|
|
165
|
+
}
|
|
166
|
+
const fileDegree = /* @__PURE__ */ new Map();
|
|
167
|
+
for (const node of graph.nodes ?? []) {
|
|
168
|
+
if (node.file_type !== "document") continue;
|
|
169
|
+
if (!node.source_file?.endsWith(".md")) continue;
|
|
170
|
+
const current = fileDegree.get(node.source_file) ?? 0;
|
|
171
|
+
fileDegree.set(node.source_file, current + (nodeDegree.get(node.id) ?? 0));
|
|
172
|
+
}
|
|
173
|
+
const topFiles = [...fileDegree.entries()].sort((a, b) => b[1] - a[1]).slice(0, 10).map(([filePath]) => filePath);
|
|
174
|
+
const repoFiles = [];
|
|
175
|
+
for (const filePath of topFiles) {
|
|
176
|
+
const full = (0, import_path2.join)(target, filePath);
|
|
177
|
+
if (!exists(full)) continue;
|
|
178
|
+
const content = readFile(full);
|
|
179
|
+
if (content === null) continue;
|
|
180
|
+
repoFiles.push({
|
|
181
|
+
path: filePath,
|
|
182
|
+
name: filePath.split("/").pop() ?? filePath,
|
|
183
|
+
preview: content.slice(0, 200),
|
|
184
|
+
fullContent: content
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
return repoFiles;
|
|
188
|
+
}
|
|
136
189
|
function loadRepo(targetDir) {
|
|
137
190
|
const read = (name) => readFile((0, import_path2.join)(targetDir, name));
|
|
138
191
|
const packageJsonRaw = read("package.json");
|
|
@@ -149,15 +202,27 @@ function loadRepo(targetDir) {
|
|
|
149
202
|
const progressMd = read("PROGRESS.md") ?? read("progress.md");
|
|
150
203
|
const sessionHandoffMd = read("SESSION-HANDOFF.md") ?? read("session-handoff.md");
|
|
151
204
|
const progressMdModifiedAt = statMtime((0, import_path2.join)(targetDir, "PROGRESS.md")) ?? statMtime((0, import_path2.join)(targetDir, "progress.md"));
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
205
|
+
const graphPath = findGraphifyOutput(targetDir);
|
|
206
|
+
let mdFiles;
|
|
207
|
+
let usedGraphify = false;
|
|
208
|
+
if (graphPath) {
|
|
209
|
+
mdFiles = loadFromGraph(targetDir, graphPath);
|
|
210
|
+
usedGraphify = true;
|
|
211
|
+
const rel = graphPath.startsWith(targetDir) ? graphPath.slice(targetDir.length + 1) : graphPath;
|
|
212
|
+
console.log(` Using Graphify knowledge graph (${rel})`);
|
|
213
|
+
console.log(` Top ${mdFiles.length} files by centrality selected`);
|
|
214
|
+
} else {
|
|
215
|
+
const rawMdFiles = walkMdFiles(targetDir);
|
|
216
|
+
mdFiles = rawMdFiles.map(({ relPath, name, fullContent }) => ({
|
|
217
|
+
path: relPath,
|
|
218
|
+
name,
|
|
219
|
+
preview: fullContent.slice(0, 200),
|
|
220
|
+
fullContent
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
159
223
|
return {
|
|
160
224
|
mdFiles,
|
|
225
|
+
usedGraphify,
|
|
161
226
|
agentsMd,
|
|
162
227
|
architectureMd,
|
|
163
228
|
constraintsMd,
|
|
@@ -173,6 +238,19 @@ function loadRepo(targetDir) {
|
|
|
173
238
|
}
|
|
174
239
|
|
|
175
240
|
// src/audit/mapper.ts
|
|
241
|
+
var TRIAGE_SYSTEM = `You are a repository analyst. Given a list of file paths,
|
|
242
|
+
identify which files could plausibly serve as harness artifacts for AI
|
|
243
|
+
coding agents.
|
|
244
|
+
|
|
245
|
+
Harness artifacts include: agent instructions, project progress tracking,
|
|
246
|
+
architecture documentation, constraints or rules, session state,
|
|
247
|
+
design decisions.
|
|
248
|
+
|
|
249
|
+
Exclude: changelogs, license files, generated API docs, package readmes,
|
|
250
|
+
dependency documentation, test fixtures, lock files.
|
|
251
|
+
|
|
252
|
+
Respond with valid JSON only:
|
|
253
|
+
{ "relevant": ["path/to/file.md", ...] }`;
|
|
176
254
|
var MAPPER_SYSTEM = `You are classifying repository markdown files by AI agent harness subsystem.
|
|
177
255
|
|
|
178
256
|
There are exactly 5 subsystems:
|
|
@@ -194,6 +272,20 @@ var VALID_SUBSYSTEMS = /* @__PURE__ */ new Set([
|
|
|
194
272
|
"memory",
|
|
195
273
|
"constraints"
|
|
196
274
|
]);
|
|
275
|
+
function firstLines(content, lineCount) {
|
|
276
|
+
return content.split("\n").slice(0, lineCount).join("\n");
|
|
277
|
+
}
|
|
278
|
+
function parseTriageResponse(text) {
|
|
279
|
+
try {
|
|
280
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
281
|
+
if (!jsonMatch) return [];
|
|
282
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
283
|
+
if (!Array.isArray(parsed.relevant)) return [];
|
|
284
|
+
return parsed.relevant.filter((p) => typeof p === "string");
|
|
285
|
+
} catch {
|
|
286
|
+
return [];
|
|
287
|
+
}
|
|
288
|
+
}
|
|
197
289
|
function parseMapperResponse(text) {
|
|
198
290
|
try {
|
|
199
291
|
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
@@ -210,15 +302,41 @@ function parseMapperResponse(text) {
|
|
|
210
302
|
return [];
|
|
211
303
|
}
|
|
212
304
|
}
|
|
213
|
-
async function
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
|
|
305
|
+
async function triageByFilename(mdFiles, provider) {
|
|
306
|
+
const fileList = mdFiles.map((f) => `- ${f.path}`).join("\n");
|
|
307
|
+
const text = await provider.chat(
|
|
308
|
+
TRIAGE_SYSTEM,
|
|
309
|
+
`Identify harness-relevant files from these paths:
|
|
218
310
|
|
|
219
|
-
${fileList}`,
|
|
311
|
+
${fileList}`,
|
|
312
|
+
{ fast: true }
|
|
313
|
+
);
|
|
314
|
+
return parseTriageResponse(text);
|
|
315
|
+
}
|
|
316
|
+
async function classifyFiles(relevantFiles, provider) {
|
|
317
|
+
const fileList = relevantFiles.map((f) => `- path: ${f.path}
|
|
318
|
+
preview: ${JSON.stringify(firstLines(f.fullContent, 50))}`).join("\n");
|
|
319
|
+
const text = await provider.chat(
|
|
320
|
+
MAPPER_SYSTEM,
|
|
321
|
+
`Classify these repository files:
|
|
322
|
+
|
|
323
|
+
${fileList}`,
|
|
324
|
+
{ fast: true }
|
|
325
|
+
);
|
|
220
326
|
return parseMapperResponse(text);
|
|
221
327
|
}
|
|
328
|
+
async function mapFiles(mdFiles, provider, usedGraphify = false) {
|
|
329
|
+
if (mdFiles.length === 0) return [];
|
|
330
|
+
if (usedGraphify) {
|
|
331
|
+
return classifyFiles(mdFiles, provider);
|
|
332
|
+
}
|
|
333
|
+
const relevantPaths = await triageByFilename(mdFiles, provider);
|
|
334
|
+
if (relevantPaths.length === 0) return [];
|
|
335
|
+
const pathSet = new Set(relevantPaths);
|
|
336
|
+
const relevantFiles = mdFiles.filter((f) => pathSet.has(f.path));
|
|
337
|
+
if (relevantFiles.length === 0) return [];
|
|
338
|
+
return classifyFiles(relevantFiles, provider);
|
|
339
|
+
}
|
|
222
340
|
|
|
223
341
|
// src/audit/cross-ref.ts
|
|
224
342
|
function extractNpmRunCommands(content) {
|
|
@@ -244,23 +362,28 @@ function extractDocumentedModules(content) {
|
|
|
244
362
|
}
|
|
245
363
|
return [...new Set(modules)];
|
|
246
364
|
}
|
|
247
|
-
function checkCommands(files) {
|
|
248
|
-
|
|
365
|
+
function checkCommands(files, mappings, mdFileMap) {
|
|
366
|
+
let contentToCheck = files.agentsMd;
|
|
367
|
+
if (!contentToCheck && mappings.length > 0) {
|
|
368
|
+
const mapped = mappings.filter((m) => m.subsystems.includes("identity") || m.subsystems.includes("verification")).map((m) => mdFileMap.get(m.path)).filter((c) => c !== void 0).join("\n\n");
|
|
369
|
+
if (mapped) contentToCheck = mapped;
|
|
370
|
+
}
|
|
371
|
+
if (!contentToCheck) {
|
|
249
372
|
return [
|
|
250
373
|
{
|
|
251
374
|
name: "commands-in-agents-exist-in-package",
|
|
252
375
|
passed: false,
|
|
253
|
-
detail: "No
|
|
376
|
+
detail: "No agent harness file found to extract commands from"
|
|
254
377
|
}
|
|
255
378
|
];
|
|
256
379
|
}
|
|
257
|
-
const commands = extractNpmRunCommands(
|
|
380
|
+
const commands = extractNpmRunCommands(contentToCheck);
|
|
258
381
|
if (commands.length === 0) {
|
|
259
382
|
return [
|
|
260
383
|
{
|
|
261
384
|
name: "commands-in-agents-exist-in-package",
|
|
262
385
|
passed: true,
|
|
263
|
-
detail: "No npm run commands found in
|
|
386
|
+
detail: "No npm run commands found in harness bash blocks"
|
|
264
387
|
}
|
|
265
388
|
];
|
|
266
389
|
}
|
|
@@ -272,30 +395,35 @@ function checkCommands(files) {
|
|
|
272
395
|
{
|
|
273
396
|
name: "commands-in-agents-exist-in-package",
|
|
274
397
|
passed: true,
|
|
275
|
-
detail: `All ${commands.length} npm run command(s) in
|
|
398
|
+
detail: `All ${commands.length} npm run command(s) in harness found in package.json scripts`
|
|
276
399
|
}
|
|
277
400
|
];
|
|
278
401
|
}
|
|
279
402
|
return missing.map((cmd) => ({
|
|
280
403
|
name: "commands-in-agents-exist-in-package",
|
|
281
404
|
passed: false,
|
|
282
|
-
detail: `\`npm run ${cmd}\` in
|
|
405
|
+
detail: `\`npm run ${cmd}\` in harness not found in package.json scripts`
|
|
283
406
|
}));
|
|
284
407
|
}
|
|
285
|
-
function checkModules(files) {
|
|
286
|
-
|
|
408
|
+
function checkModules(files, mappings, mdFileMap) {
|
|
409
|
+
let contentToCheck = files.architectureMd;
|
|
410
|
+
if (!contentToCheck && mappings.length > 0) {
|
|
411
|
+
const mapped = mappings.filter((m) => m.subsystems.includes("memory")).map((m) => mdFileMap.get(m.path)).filter((c) => c !== void 0).join("\n\n");
|
|
412
|
+
if (mapped) contentToCheck = mapped;
|
|
413
|
+
}
|
|
414
|
+
if (!contentToCheck) {
|
|
287
415
|
return {
|
|
288
416
|
name: "architecture-modules-match-src",
|
|
289
417
|
passed: false,
|
|
290
|
-
detail: "No
|
|
418
|
+
detail: "No architecture file found to extract documented modules from"
|
|
291
419
|
};
|
|
292
420
|
}
|
|
293
|
-
const documented = extractDocumentedModules(
|
|
421
|
+
const documented = extractDocumentedModules(contentToCheck);
|
|
294
422
|
if (documented.length === 0) {
|
|
295
423
|
return {
|
|
296
424
|
name: "architecture-modules-match-src",
|
|
297
425
|
passed: true,
|
|
298
|
-
detail: "No annotated module directories found in
|
|
426
|
+
detail: "No annotated module directories found in architecture file"
|
|
299
427
|
};
|
|
300
428
|
}
|
|
301
429
|
const missing = documented.filter((mod) => !files.srcDirs.includes(mod));
|
|
@@ -309,7 +437,7 @@ function checkModules(files) {
|
|
|
309
437
|
return {
|
|
310
438
|
name: "architecture-modules-match-src",
|
|
311
439
|
passed: false,
|
|
312
|
-
detail: `Module(s) in
|
|
440
|
+
detail: `Module(s) in architecture file not found in src/: ${missing.join(", ")}`
|
|
313
441
|
};
|
|
314
442
|
}
|
|
315
443
|
function checkProgressFreshness(files) {
|
|
@@ -334,11 +462,12 @@ function checkProgressFreshness(files) {
|
|
|
334
462
|
detail: `PROGRESS.md last updated ${Math.round(ageDays)} days ago (threshold: 7)`
|
|
335
463
|
};
|
|
336
464
|
}
|
|
337
|
-
function crossRef(files) {
|
|
465
|
+
function crossRef(files, mappings = []) {
|
|
466
|
+
const mdFileMap = new Map(files.mdFiles.map((f) => [f.path, f.fullContent]));
|
|
338
467
|
return {
|
|
339
468
|
checks: [
|
|
340
|
-
...checkCommands(files),
|
|
341
|
-
checkModules(files),
|
|
469
|
+
...checkCommands(files, mappings, mdFileMap),
|
|
470
|
+
checkModules(files, mappings, mdFileMap),
|
|
342
471
|
checkProgressFreshness(files)
|
|
343
472
|
]
|
|
344
473
|
};
|
|
@@ -424,7 +553,7 @@ ${contentSections}`,
|
|
|
424
553
|
const overall = Math.round(
|
|
425
554
|
(identity.score + verification.score + state.score + memory.score + constraints.score) / 5
|
|
426
555
|
);
|
|
427
|
-
const xref = crossRef(files);
|
|
556
|
+
const xref = crossRef(files, mappings);
|
|
428
557
|
return { identity, verification, state, memory, constraints, overall, crossRef: xref };
|
|
429
558
|
}
|
|
430
559
|
|
|
@@ -485,6 +614,7 @@ function report(scored, opts) {
|
|
|
485
614
|
if (opts.json) {
|
|
486
615
|
const out = {
|
|
487
616
|
overall: scored.overall,
|
|
617
|
+
...opts.tokenUsage !== void 0 ? { token_usage: opts.tokenUsage } : {},
|
|
488
618
|
subsystems: {
|
|
489
619
|
identity: { score: scored.identity.score, gaps: scored.identity.gaps, files: scored.identity.files },
|
|
490
620
|
verification: { score: scored.verification.score, gaps: scored.verification.gaps, files: scored.verification.files },
|
|
@@ -541,16 +671,28 @@ var OPENAI_FALLBACK_MODELS = [
|
|
|
541
671
|
{ id: "gpt-3.5-turbo", label: "GPT-3.5 Turbo" }
|
|
542
672
|
];
|
|
543
673
|
|
|
674
|
+
// src/utils/tokens.ts
|
|
675
|
+
function estimateTokens(text) {
|
|
676
|
+
return Math.ceil(text.length / 4);
|
|
677
|
+
}
|
|
678
|
+
|
|
544
679
|
// src/utils/llm.ts
|
|
680
|
+
function estimateChatTokens(system, user, response) {
|
|
681
|
+
return estimateTokens(system) + estimateTokens(user) + estimateTokens(response);
|
|
682
|
+
}
|
|
545
683
|
var ANTHROPIC_FAST = "claude-haiku-4-5-20251001";
|
|
546
684
|
var ANTHROPIC_QUALITY = "claude-sonnet-4-6";
|
|
547
685
|
var AnthropicProvider = class {
|
|
548
686
|
client;
|
|
549
687
|
modelId;
|
|
688
|
+
totalTokens = 0;
|
|
550
689
|
constructor(apiKey, modelId) {
|
|
551
690
|
this.client = new import_sdk.default({ apiKey });
|
|
552
691
|
this.modelId = modelId;
|
|
553
692
|
}
|
|
693
|
+
getTotalTokens() {
|
|
694
|
+
return this.totalTokens;
|
|
695
|
+
}
|
|
554
696
|
async chat(system, user, opts) {
|
|
555
697
|
const model = this.modelId ?? (opts?.fast ? ANTHROPIC_FAST : ANTHROPIC_QUALITY);
|
|
556
698
|
const maxTokens = opts?.fast ? 1024 : 2048;
|
|
@@ -561,7 +703,9 @@ var AnthropicProvider = class {
|
|
|
561
703
|
messages: [{ role: "user", content: user }]
|
|
562
704
|
});
|
|
563
705
|
const block = response.content.find((b) => b.type === "text");
|
|
564
|
-
|
|
706
|
+
const text = block && block.type === "text" ? block.text : "";
|
|
707
|
+
this.totalTokens += estimateChatTokens(system, user, text);
|
|
708
|
+
return text;
|
|
565
709
|
}
|
|
566
710
|
};
|
|
567
711
|
var OPENAI_FAST = "gpt-4o-mini";
|
|
@@ -569,11 +713,15 @@ var OPENAI_QUALITY = "gpt-4o";
|
|
|
569
713
|
var OpenAIProvider = class {
|
|
570
714
|
client;
|
|
571
715
|
modelId;
|
|
716
|
+
totalTokens = 0;
|
|
572
717
|
constructor(apiKey, modelId) {
|
|
573
718
|
const baseURL = process.env["OPENAI_BASE_URL"];
|
|
574
719
|
this.client = new import_openai.default({ apiKey, ...baseURL ? { baseURL } : {} });
|
|
575
720
|
this.modelId = modelId;
|
|
576
721
|
}
|
|
722
|
+
getTotalTokens() {
|
|
723
|
+
return this.totalTokens;
|
|
724
|
+
}
|
|
577
725
|
async chat(system, user, opts) {
|
|
578
726
|
const model = this.modelId ?? (opts?.fast ? OPENAI_FAST : OPENAI_QUALITY);
|
|
579
727
|
const response = await this.client.chat.completions.create({
|
|
@@ -583,12 +731,15 @@ var OpenAIProvider = class {
|
|
|
583
731
|
{ role: "user", content: user }
|
|
584
732
|
]
|
|
585
733
|
});
|
|
586
|
-
|
|
734
|
+
const text = response.choices[0]?.message?.content ?? "";
|
|
735
|
+
this.totalTokens += estimateChatTokens(system, user, text);
|
|
736
|
+
return text;
|
|
587
737
|
}
|
|
588
738
|
};
|
|
589
739
|
var OllamaProvider = class {
|
|
590
740
|
client;
|
|
591
741
|
model;
|
|
742
|
+
totalTokens = 0;
|
|
592
743
|
constructor() {
|
|
593
744
|
const host = process.env["OLLAMA_HOST"] ?? "http://localhost:11434";
|
|
594
745
|
this.model = process.env["OLLAMA_MODEL"] ?? "llama3";
|
|
@@ -597,6 +748,9 @@ var OllamaProvider = class {
|
|
|
597
748
|
baseURL: `${host}/v1`
|
|
598
749
|
});
|
|
599
750
|
}
|
|
751
|
+
getTotalTokens() {
|
|
752
|
+
return this.totalTokens;
|
|
753
|
+
}
|
|
600
754
|
async chat(system, user) {
|
|
601
755
|
const response = await this.client.chat.completions.create({
|
|
602
756
|
model: this.model,
|
|
@@ -605,7 +759,9 @@ var OllamaProvider = class {
|
|
|
605
759
|
{ role: "user", content: user }
|
|
606
760
|
]
|
|
607
761
|
});
|
|
608
|
-
|
|
762
|
+
const text = response.choices[0]?.message?.content ?? "";
|
|
763
|
+
this.totalTokens += estimateChatTokens(system, user, text);
|
|
764
|
+
return text;
|
|
609
765
|
}
|
|
610
766
|
};
|
|
611
767
|
var NON_CHAT_MODEL_SUBSTRINGS = [
|
|
@@ -747,9 +903,15 @@ async function runAudit(target, opts) {
|
|
|
747
903
|
const provider = createProvider(config.provider, config.apiKey, config.modelId);
|
|
748
904
|
const targetDir = (0, import_path3.resolve)(target);
|
|
749
905
|
const files = loadRepo(targetDir);
|
|
750
|
-
const mappings = await mapFiles(files.mdFiles, provider);
|
|
906
|
+
const mappings = await mapFiles(files.mdFiles, provider, files.usedGraphify);
|
|
751
907
|
const scored = await scoreRepo(files, mappings, provider);
|
|
752
|
-
|
|
908
|
+
const totalTokens = provider.getTotalTokens();
|
|
909
|
+
report(scored, { json: opts.json, tokenUsage: totalTokens });
|
|
910
|
+
if (!opts.json) {
|
|
911
|
+
const display = totalTokens >= 1e3 ? `~${Math.ceil(totalTokens / 1e3)}k` : `~${totalTokens}`;
|
|
912
|
+
console.log(`
|
|
913
|
+
Tokens used: ${display}`);
|
|
914
|
+
}
|
|
753
915
|
if (scored.overall < opts.minScore) {
|
|
754
916
|
process.exit(1);
|
|
755
917
|
}
|