@promptci/core 0.0.1
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/LICENSE +201 -0
- package/README.md +30 -0
- package/dist/agent-practices.d.ts +22 -0
- package/dist/agent-practices.d.ts.map +1 -0
- package/dist/agent-practices.js +582 -0
- package/dist/agent-practices.js.map +1 -0
- package/dist/baseline.d.ts +40 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +172 -0
- package/dist/baseline.js.map +1 -0
- package/dist/canonical-owner.d.ts +3 -0
- package/dist/canonical-owner.d.ts.map +1 -0
- package/dist/canonical-owner.js +131 -0
- package/dist/canonical-owner.js.map +1 -0
- package/dist/cheap-model-routing.d.ts +4 -0
- package/dist/cheap-model-routing.d.ts.map +1 -0
- package/dist/cheap-model-routing.js +43 -0
- package/dist/cheap-model-routing.js.map +1 -0
- package/dist/ci-alignment.d.ts +10 -0
- package/dist/ci-alignment.d.ts.map +1 -0
- package/dist/ci-alignment.js +343 -0
- package/dist/ci-alignment.js.map +1 -0
- package/dist/cli-output-filter.d.ts +16 -0
- package/dist/cli-output-filter.d.ts.map +1 -0
- package/dist/cli-output-filter.js +88 -0
- package/dist/cli-output-filter.js.map +1 -0
- package/dist/command-validity.d.ts +12 -0
- package/dist/command-validity.d.ts.map +1 -0
- package/dist/command-validity.js +543 -0
- package/dist/command-validity.js.map +1 -0
- package/dist/conflicts.d.ts +49 -0
- package/dist/conflicts.d.ts.map +1 -0
- package/dist/conflicts.js +1065 -0
- package/dist/conflicts.js.map +1 -0
- package/dist/context-analysis.d.ts +15 -0
- package/dist/context-analysis.d.ts.map +1 -0
- package/dist/context-analysis.js +24 -0
- package/dist/context-analysis.js.map +1 -0
- package/dist/context-bloat.d.ts +17 -0
- package/dist/context-bloat.d.ts.map +1 -0
- package/dist/context-bloat.js +148 -0
- package/dist/context-bloat.js.map +1 -0
- package/dist/context-cost.d.ts +4 -0
- package/dist/context-cost.d.ts.map +1 -0
- package/dist/context-cost.js +70 -0
- package/dist/context-cost.js.map +1 -0
- package/dist/context-optimizer.d.ts +14 -0
- package/dist/context-optimizer.d.ts.map +1 -0
- package/dist/context-optimizer.js +219 -0
- package/dist/context-optimizer.js.map +1 -0
- package/dist/context-recommender.d.ts +3 -0
- package/dist/context-recommender.d.ts.map +1 -0
- package/dist/context-recommender.js +329 -0
- package/dist/context-recommender.js.map +1 -0
- package/dist/dead-references.d.ts +33 -0
- package/dist/dead-references.d.ts.map +1 -0
- package/dist/dead-references.js +607 -0
- package/dist/dead-references.js.map +1 -0
- package/dist/detectors.d.ts +11 -0
- package/dist/detectors.d.ts.map +1 -0
- package/dist/detectors.js +160 -0
- package/dist/detectors.js.map +1 -0
- package/dist/duplicates.d.ts +36 -0
- package/dist/duplicates.d.ts.map +1 -0
- package/dist/duplicates.js +436 -0
- package/dist/duplicates.js.map +1 -0
- package/dist/fix-engine.d.ts +16 -0
- package/dist/fix-engine.d.ts.map +1 -0
- package/dist/fix-engine.js +179 -0
- package/dist/fix-engine.js.map +1 -0
- package/dist/framework-packs.d.ts +8 -0
- package/dist/framework-packs.d.ts.map +1 -0
- package/dist/framework-packs.js +22 -0
- package/dist/framework-packs.js.map +1 -0
- package/dist/frameworks/dotnet.d.ts +4 -0
- package/dist/frameworks/dotnet.d.ts.map +1 -0
- package/dist/frameworks/dotnet.js +77 -0
- package/dist/frameworks/dotnet.js.map +1 -0
- package/dist/frameworks/go.d.ts +4 -0
- package/dist/frameworks/go.d.ts.map +1 -0
- package/dist/frameworks/go.js +39 -0
- package/dist/frameworks/go.js.map +1 -0
- package/dist/frameworks/nextjs.d.ts +4 -0
- package/dist/frameworks/nextjs.d.ts.map +1 -0
- package/dist/frameworks/nextjs.js +66 -0
- package/dist/frameworks/nextjs.js.map +1 -0
- package/dist/frameworks/python.d.ts +4 -0
- package/dist/frameworks/python.d.ts.map +1 -0
- package/dist/frameworks/python.js +66 -0
- package/dist/frameworks/python.js.map +1 -0
- package/dist/frameworks/rust.d.ts +4 -0
- package/dist/frameworks/rust.d.ts.map +1 -0
- package/dist/frameworks/rust.js +72 -0
- package/dist/frameworks/rust.js.map +1 -0
- package/dist/frameworks/unity.d.ts +4 -0
- package/dist/frameworks/unity.d.ts.map +1 -0
- package/dist/frameworks/unity.js +74 -0
- package/dist/frameworks/unity.js.map +1 -0
- package/dist/health-score.d.ts +37 -0
- package/dist/health-score.d.ts.map +1 -0
- package/dist/health-score.js +164 -0
- package/dist/health-score.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/llm.d.ts +6 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +67 -0
- package/dist/llm.js.map +1 -0
- package/dist/manifest-consistency.d.ts +37 -0
- package/dist/manifest-consistency.d.ts.map +1 -0
- package/dist/manifest-consistency.js +659 -0
- package/dist/manifest-consistency.js.map +1 -0
- package/dist/mcp-detector.d.ts +4 -0
- package/dist/mcp-detector.d.ts.map +1 -0
- package/dist/mcp-detector.js +131 -0
- package/dist/mcp-detector.js.map +1 -0
- package/dist/missing-context.d.ts +15 -0
- package/dist/missing-context.d.ts.map +1 -0
- package/dist/missing-context.js +517 -0
- package/dist/missing-context.js.map +1 -0
- package/dist/native-tool-preference.d.ts +20 -0
- package/dist/native-tool-preference.d.ts.map +1 -0
- package/dist/native-tool-preference.js +94 -0
- package/dist/native-tool-preference.js.map +1 -0
- package/dist/output-verbosity.d.ts +3 -0
- package/dist/output-verbosity.d.ts.map +1 -0
- package/dist/output-verbosity.js +83 -0
- package/dist/output-verbosity.js.map +1 -0
- package/dist/product-boundary.d.ts +11 -0
- package/dist/product-boundary.d.ts.map +1 -0
- package/dist/product-boundary.js +301 -0
- package/dist/product-boundary.js.map +1 -0
- package/dist/project-type.d.ts +15 -0
- package/dist/project-type.d.ts.map +1 -0
- package/dist/project-type.js +116 -0
- package/dist/project-type.js.map +1 -0
- package/dist/prompt-cache.d.ts +3 -0
- package/dist/prompt-cache.d.ts.map +1 -0
- package/dist/prompt-cache.js +151 -0
- package/dist/prompt-cache.js.map +1 -0
- package/dist/repo-context.d.ts +37 -0
- package/dist/repo-context.d.ts.map +1 -0
- package/dist/repo-context.js +221 -0
- package/dist/repo-context.js.map +1 -0
- package/dist/report.d.ts +87 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +558 -0
- package/dist/report.js.map +1 -0
- package/dist/scan.d.ts +7 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +43 -0
- package/dist/scan.js.map +1 -0
- package/dist/scanner.d.ts +3 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +216 -0
- package/dist/scanner.js.map +1 -0
- package/dist/security-pack.d.ts +4 -0
- package/dist/security-pack.d.ts.map +1 -0
- package/dist/security-pack.js +403 -0
- package/dist/security-pack.js.map +1 -0
- package/dist/stale-instructions.d.ts +20 -0
- package/dist/stale-instructions.d.ts.map +1 -0
- package/dist/stale-instructions.js +269 -0
- package/dist/stale-instructions.js.map +1 -0
- package/dist/suppression.d.ts +64 -0
- package/dist/suppression.d.ts.map +1 -0
- package/dist/suppression.js +338 -0
- package/dist/suppression.js.map +1 -0
- package/dist/types.d.ts +128 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/vague-guidance.d.ts +12 -0
- package/dist/vague-guidance.d.ts.map +1 -0
- package/dist/vague-guidance.js +235 -0
- package/dist/vague-guidance.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,aAAa,EAAiB,QAAQ,EAAE,MAAM,YAAY,CAAC;AA4ExF;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAgBlF;AAID;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,EAAE,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,QAAQ,CAWnF;AAID;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,QAAQ,CAiB7E;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,aAAa,EAAE,EACvB,QAAQ,EAAE,QAAQ,EAClB,QAAQ,CAAC,EAAE,MAAM,GAChB;IAAE,SAAS,EAAE,aAAa,EAAE,CAAC;IAAC,eAAe,EAAE,aAAa,EAAE,CAAA;CAAE,CA2BlE"}
|
package/dist/baseline.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Baseline & Ratchet Support
|
|
5
|
+
*
|
|
6
|
+
* Provides stable fingerprinting of issues and filtering logic so CI can fail
|
|
7
|
+
* only on new or worsened issues — not pre-existing noise.
|
|
8
|
+
*
|
|
9
|
+
* Baseline file: `.promptci/baseline.json`
|
|
10
|
+
* Format: `BaselineEntry[]`
|
|
11
|
+
*
|
|
12
|
+
* Fingerprint stability contract:
|
|
13
|
+
* - Based on category + title + sorted file paths + sorted evidence.
|
|
14
|
+
* - Line numbers are excluded so minor file edits do not break the baseline.
|
|
15
|
+
* - Severity is stored separately and checked for worsening.
|
|
16
|
+
*/
|
|
17
|
+
// ── Severity ordering ─────────────────────────────────────────────────────────
|
|
18
|
+
const SEVERITY_ORDER = {
|
|
19
|
+
info: 0,
|
|
20
|
+
warning: 1,
|
|
21
|
+
high: 2,
|
|
22
|
+
critical: 3,
|
|
23
|
+
};
|
|
24
|
+
function severityValue(s) {
|
|
25
|
+
return SEVERITY_ORDER[s] ?? -1;
|
|
26
|
+
}
|
|
27
|
+
function normalizeSlashes(value) {
|
|
28
|
+
return value.replace(/\\/g, '/');
|
|
29
|
+
}
|
|
30
|
+
function normalizePathForRoot(filePath, repoRoot) {
|
|
31
|
+
if (!repoRoot)
|
|
32
|
+
return filePath;
|
|
33
|
+
try {
|
|
34
|
+
const relative = path.relative(repoRoot, filePath);
|
|
35
|
+
if (relative && !relative.startsWith('..') && !path.isAbsolute(relative)) {
|
|
36
|
+
return normalizeSlashes(relative);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Fall through to string normalization for unusual path formats.
|
|
41
|
+
}
|
|
42
|
+
const normalizedPath = normalizeSlashes(filePath);
|
|
43
|
+
const rootCandidates = [
|
|
44
|
+
normalizeSlashes(repoRoot).replace(/\/+$/, ''),
|
|
45
|
+
normalizeSlashes(path.resolve(repoRoot)).replace(/\/+$/, ''),
|
|
46
|
+
];
|
|
47
|
+
for (const normalizedRoot of rootCandidates) {
|
|
48
|
+
if (normalizedPath === normalizedRoot)
|
|
49
|
+
return '.';
|
|
50
|
+
if (normalizedPath.startsWith(`${normalizedRoot}/`)) {
|
|
51
|
+
return normalizedPath.slice(normalizedRoot.length + 1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return normalizedPath;
|
|
55
|
+
}
|
|
56
|
+
function normalizeEvidenceForRoot(evidence, repoRoot) {
|
|
57
|
+
if (!repoRoot)
|
|
58
|
+
return evidence;
|
|
59
|
+
const normalizedEvidence = normalizeSlashes(evidence);
|
|
60
|
+
const rootCandidates = [
|
|
61
|
+
normalizeSlashes(repoRoot).replace(/\/+$/, ''),
|
|
62
|
+
normalizeSlashes(path.resolve(repoRoot)).replace(/\/+$/, ''),
|
|
63
|
+
];
|
|
64
|
+
return rootCandidates.reduce((current, normalizedRoot) => current.replaceAll(normalizedRoot, '<repo>'), normalizedEvidence);
|
|
65
|
+
}
|
|
66
|
+
// ── Fingerprinting ────────────────────────────────────────────────────────────
|
|
67
|
+
/**
|
|
68
|
+
* Computes a stable fingerprint for an issue.
|
|
69
|
+
*
|
|
70
|
+
* Excluded from fingerprint:
|
|
71
|
+
* - `id` (may change between versions)
|
|
72
|
+
* - `severity` (tracked separately for worsening detection)
|
|
73
|
+
* - `locations` / line numbers (unstable across minor file edits)
|
|
74
|
+
* - `confidence` (heuristic, may fluctuate)
|
|
75
|
+
*
|
|
76
|
+
* Included:
|
|
77
|
+
* - `category`, `title`, sorted `filePaths`, sorted `evidence`
|
|
78
|
+
*/
|
|
79
|
+
export function computeFingerprint(issue, repoRoot) {
|
|
80
|
+
const normalizedFilePaths = issue.filePaths
|
|
81
|
+
.map((filePath) => normalizePathForRoot(filePath, repoRoot))
|
|
82
|
+
.sort();
|
|
83
|
+
const normalizedEvidence = issue.evidence
|
|
84
|
+
.map((evidence) => normalizeEvidenceForRoot(evidence, repoRoot))
|
|
85
|
+
.sort();
|
|
86
|
+
const content = [
|
|
87
|
+
issue.category,
|
|
88
|
+
issue.title,
|
|
89
|
+
...normalizedFilePaths,
|
|
90
|
+
...normalizedEvidence,
|
|
91
|
+
].join('|');
|
|
92
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
93
|
+
}
|
|
94
|
+
// ── Baseline creation ─────────────────────────────────────────────────────────
|
|
95
|
+
/**
|
|
96
|
+
* Creates a baseline from a list of active issues.
|
|
97
|
+
* Produces deterministic JSON when sorted by fingerprint.
|
|
98
|
+
*/
|
|
99
|
+
export function createBaseline(issues, repoRoot) {
|
|
100
|
+
return issues
|
|
101
|
+
.map((issue) => ({
|
|
102
|
+
id: issue.id,
|
|
103
|
+
category: issue.category,
|
|
104
|
+
title: issue.title,
|
|
105
|
+
filePaths: issue.filePaths.map((filePath) => normalizePathForRoot(filePath, repoRoot)).sort(),
|
|
106
|
+
severity: issue.severity,
|
|
107
|
+
fingerprint: computeFingerprint(issue, repoRoot),
|
|
108
|
+
}))
|
|
109
|
+
.sort((a, b) => a.fingerprint.localeCompare(b.fingerprint));
|
|
110
|
+
}
|
|
111
|
+
// ── Baseline validation ───────────────────────────────────────────────────────
|
|
112
|
+
/**
|
|
113
|
+
* Type-guard that asserts a parsed JSON value is a valid Baseline array.
|
|
114
|
+
* Throws a descriptive error if the shape is unexpected.
|
|
115
|
+
*/
|
|
116
|
+
export function assertValidBaseline(value) {
|
|
117
|
+
if (!Array.isArray(value)) {
|
|
118
|
+
throw new Error('Baseline file must contain a JSON array.');
|
|
119
|
+
}
|
|
120
|
+
for (let i = 0; i < value.length; i++) {
|
|
121
|
+
const entry = value[i];
|
|
122
|
+
if (typeof entry !== 'object' || entry === null) {
|
|
123
|
+
throw new Error(`Baseline entry at index ${i} is not an object.`);
|
|
124
|
+
}
|
|
125
|
+
const e = entry;
|
|
126
|
+
if (typeof e['fingerprint'] !== 'string') {
|
|
127
|
+
throw new Error(`Baseline entry at index ${i} is missing a "fingerprint" string field.`);
|
|
128
|
+
}
|
|
129
|
+
if (typeof e['category'] !== 'string') {
|
|
130
|
+
throw new Error(`Baseline entry at index ${i} is missing a "category" string field.`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// ── Filtering ─────────────────────────────────────────────────────────────────
|
|
135
|
+
/**
|
|
136
|
+
* Partitions a list of active issues into new and baselined issues.
|
|
137
|
+
*
|
|
138
|
+
* An issue is considered NEW (not baselined) if:
|
|
139
|
+
* - Its fingerprint is not in the baseline, OR
|
|
140
|
+
* - Its fingerprint IS in the baseline but its severity has worsened
|
|
141
|
+
* (e.g. was `warning`, now `high`).
|
|
142
|
+
*
|
|
143
|
+
* Issues with the same fingerprint and equal-or-lesser severity are
|
|
144
|
+
* considered baselined (pre-existing) and do not fail `--fail-on-new` CI.
|
|
145
|
+
*/
|
|
146
|
+
export function filterNewIssues(issues, baseline, repoRoot) {
|
|
147
|
+
// Build a map from fingerprint → baseline severity for worsening checks
|
|
148
|
+
const baselineMap = new Map();
|
|
149
|
+
for (const entry of baseline) {
|
|
150
|
+
baselineMap.set(entry.fingerprint, entry.severity ?? 'info');
|
|
151
|
+
}
|
|
152
|
+
const newIssues = [];
|
|
153
|
+
const baselinedIssues = [];
|
|
154
|
+
for (const issue of issues) {
|
|
155
|
+
const fp = computeFingerprint(issue, repoRoot);
|
|
156
|
+
const baselinedSeverity = baselineMap.get(fp);
|
|
157
|
+
if (baselinedSeverity === undefined) {
|
|
158
|
+
// Not in baseline at all → new
|
|
159
|
+
newIssues.push(issue);
|
|
160
|
+
}
|
|
161
|
+
else if (severityValue(issue.severity) > severityValue(baselinedSeverity)) {
|
|
162
|
+
// Severity worsened → treat as new/worsened
|
|
163
|
+
newIssues.push(issue);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
// Same or reduced severity → pre-existing
|
|
167
|
+
baselinedIssues.push(issue);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return { newIssues, baselinedIssues };
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=baseline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.js","sourceRoot":"","sources":["../src/baseline.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical-owner.d.ts","sourceRoot":"","sources":["../src/canonical-owner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAcjE,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,aAAa,EAAE,CAsI9E"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import * as crypto from 'node:crypto';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Canonical Owner and Forwarding Detector.
|
|
5
|
+
*/
|
|
6
|
+
const AUTHORITY_PHRASES = [
|
|
7
|
+
/canonical\s+(?:source\s+of\s+truth|instruction|guidance)/i,
|
|
8
|
+
/authoritative\s+(?:source|instruction|guidance)/i,
|
|
9
|
+
/primary\s+(?:source\s+of\s+truth|instruction|guidance)/i,
|
|
10
|
+
/source\s+of\s+truth\s+for\s+all\s+agents/i,
|
|
11
|
+
/master\s+instruction\s+file/i,
|
|
12
|
+
];
|
|
13
|
+
export function detectCanonicalOwner(files) {
|
|
14
|
+
if (files.length === 0)
|
|
15
|
+
return [];
|
|
16
|
+
const issues = [];
|
|
17
|
+
const filesWithAuthority = [];
|
|
18
|
+
for (const file of files) {
|
|
19
|
+
if (AUTHORITY_PHRASES.some((re) => re.test(file.content))) {
|
|
20
|
+
filesWithAuthority.push(file);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function issueId(name) {
|
|
24
|
+
const hash = crypto.createHash('sha1').update(name).digest('hex').slice(0, 12);
|
|
25
|
+
return `canonical-${name}-${hash}`;
|
|
26
|
+
}
|
|
27
|
+
if (filesWithAuthority.length > 1) {
|
|
28
|
+
issues.push({
|
|
29
|
+
id: issueId('ambiguous-authority'),
|
|
30
|
+
severity: 'warning',
|
|
31
|
+
category: 'structure',
|
|
32
|
+
title: 'Ambiguous authority: multiple files claim to be canonical',
|
|
33
|
+
summary: `Multiple files (${filesWithAuthority.map((f) => path.basename(f.path)).join(', ')}) ` +
|
|
34
|
+
`claim to be the "canonical" or "authoritative" source of truth. ` +
|
|
35
|
+
`This leads to confusion about which instructions take precedence.`,
|
|
36
|
+
filePaths: filesWithAuthority.map((f) => f.path),
|
|
37
|
+
locations: filesWithAuthority.map((f) => ({ filePath: f.path })),
|
|
38
|
+
// CO1: extract evidence from the SAME whole-content match the gate
|
|
39
|
+
// used, instead of re-scanning line-by-line. The gate tests against
|
|
40
|
+
// `\s`-containing patterns (e.g. "canonical\s+source of truth"), and
|
|
41
|
+
// `\s` matches newlines — so a phrase wrapped across two lines
|
|
42
|
+
// ("canonical\nsource of truth") passes the gate but no SINGLE line
|
|
43
|
+
// contains the whole phrase, so `lines.find(...)` returned `undefined`
|
|
44
|
+
// and evidence literally rendered the string "undefined".
|
|
45
|
+
evidence: filesWithAuthority.map((f) => {
|
|
46
|
+
let matchText;
|
|
47
|
+
for (const re of AUTHORITY_PHRASES) {
|
|
48
|
+
const m = re.exec(f.content);
|
|
49
|
+
if (m) {
|
|
50
|
+
matchText = m[0];
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const snippet = (matchText ?? '').replace(/\s+/g, ' ').trim();
|
|
55
|
+
return `${path.basename(f.path)}: "${snippet}"`;
|
|
56
|
+
}),
|
|
57
|
+
recommendation: 'Designate ONE file (usually AGENTS.md) as the canonical source. ' +
|
|
58
|
+
'Other files should use forwarding pointers and only contain tool-specific reminders.',
|
|
59
|
+
confidence: 0.9,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const agentsFile = files.find((f) => path.basename(f.path).toLowerCase() === 'agents.md');
|
|
63
|
+
const primaryCanonical = agentsFile || filesWithAuthority[0];
|
|
64
|
+
if (primaryCanonical) {
|
|
65
|
+
const getSectionBody = (text) => {
|
|
66
|
+
const lines = text.split('\n');
|
|
67
|
+
if (lines.length > 0 && lines[0].trim().startsWith('#')) {
|
|
68
|
+
return lines.slice(1).join('\n').trim();
|
|
69
|
+
}
|
|
70
|
+
return text.trim();
|
|
71
|
+
};
|
|
72
|
+
for (const file of files) {
|
|
73
|
+
if (file === primaryCanonical)
|
|
74
|
+
continue;
|
|
75
|
+
const baseName = path.basename(file.path).toLowerCase();
|
|
76
|
+
const normalizedPath = file.path.replace(/\\/g, '/').toLowerCase();
|
|
77
|
+
const isToolSpecific = baseName === 'claude.md' ||
|
|
78
|
+
baseName === '.cursorrules' ||
|
|
79
|
+
normalizedPath.includes('/.claude/') ||
|
|
80
|
+
normalizedPath.includes('/.cursor/rules/') ||
|
|
81
|
+
normalizedPath.includes('/.github/copilot-instructions.md') ||
|
|
82
|
+
normalizedPath.includes('/.github/instructions/');
|
|
83
|
+
if (!isToolSpecific)
|
|
84
|
+
continue;
|
|
85
|
+
if (file.lineCount > 50) {
|
|
86
|
+
for (const section of file.sections) {
|
|
87
|
+
if (section.endLine - section.startLine > 50) {
|
|
88
|
+
const isDuplicate = primaryCanonical.sections.some((s) => {
|
|
89
|
+
const sBody = getSectionBody(s.text).toLowerCase().replace(/\s+/g, ' ').trim();
|
|
90
|
+
const sectBody = getSectionBody(section.text).toLowerCase().replace(/\s+/g, ' ').trim();
|
|
91
|
+
return (sBody === sectBody ||
|
|
92
|
+
(sBody.length > 500 && sBody.slice(0, 500) === sectBody.slice(0, 500)));
|
|
93
|
+
});
|
|
94
|
+
if (isDuplicate) {
|
|
95
|
+
// CO2 / B1: agent-practices.ts independently derives an id from
|
|
96
|
+
// the exact same `behavior-duplication:${filePath}` hash input
|
|
97
|
+
// for a conceptually similar finding — the two detectors
|
|
98
|
+
// collide on id for the same file. Namespaced per-detector
|
|
99
|
+
// (mirroring the fix on the agent-practices.ts side) so ids
|
|
100
|
+
// stay distinct even for the same file path; the public id
|
|
101
|
+
// PREFIX is unchanged.
|
|
102
|
+
const hash = crypto.createHash('sha1').update(`canonical-owner:behavior-duplication:${file.path}`).digest('hex').slice(0, 12);
|
|
103
|
+
const canonicalLabel = path.basename(primaryCanonical.path);
|
|
104
|
+
const duplicateTokens = Math.round(section.text.length / 4);
|
|
105
|
+
issues.push({
|
|
106
|
+
id: `behavior-duplication-${hash}`,
|
|
107
|
+
severity: 'info',
|
|
108
|
+
category: 'duplicate',
|
|
109
|
+
title: 'Large duplicated behavior in tool-specific file',
|
|
110
|
+
summary: `The ${baseName} contains a large section (> 50 lines) that appears to be ` +
|
|
111
|
+
`duplicated from ${canonicalLabel}. This can lead to instruction drift.`,
|
|
112
|
+
filePaths: [file.path, primaryCanonical.path],
|
|
113
|
+
locations: [{ filePath: file.path, startLine: section.startLine, endLine: section.endLine }],
|
|
114
|
+
evidence: [
|
|
115
|
+
`Section "${section.heading ?? 'Untitled'}" is near-identical to one in ${canonicalLabel}`,
|
|
116
|
+
`Duplicate size: ~${duplicateTokens} tokens`,
|
|
117
|
+
],
|
|
118
|
+
recommendation: `Consider moving shared rules to ${canonicalLabel} and using ` +
|
|
119
|
+
`a forwarding pointer (e.g., "Follow ${canonicalLabel}") to save ~${duplicateTokens} tokens per session.`,
|
|
120
|
+
confidence: 0.8,
|
|
121
|
+
});
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return issues;
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=canonical-owner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical-owner.js","sourceRoot":"","sources":["../src/canonical-owner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cheap-model-routing.d.ts","sourceRoot":"","sources":["../src/cheap-model-routing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAWhD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,WAAW,GAAG,aAAa,EAAE,CAsC7E"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const ROUTING_KEYWORDS = [
|
|
2
|
+
/\b(?:cheap|cheaper|smaller|cost-aware|routing)\b.*\b(?:model|models|route|routing)\b/i,
|
|
3
|
+
/\b(?:escalate\s+to|escalation\s+path|stronger\s+model|expensive\s+model)\b/i,
|
|
4
|
+
/\bgpt-4o-mini\b/i,
|
|
5
|
+
/\bclaude-3-5-haiku\b|\bhaiku\b/i,
|
|
6
|
+
/\bgemini-2\.5-flash\b|\bgemini\s+flash\b|\bflash\s+model\b/i,
|
|
7
|
+
/\bmodel\s+routing\b/i,
|
|
8
|
+
];
|
|
9
|
+
export function detectCheapModelRouting(context) {
|
|
10
|
+
const { files, metrics } = context;
|
|
11
|
+
if (files.length === 0) {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
// Skip for small/simple repositories to avoid noise
|
|
15
|
+
if (metrics.estimatedInstructionTokens < 500) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
const combinedContent = files.map((f) => f.content).join('\n');
|
|
19
|
+
const hasRoutingMentions = ROUTING_KEYWORDS.some((pat) => pat.test(combinedContent));
|
|
20
|
+
if (!hasRoutingMentions) {
|
|
21
|
+
return [
|
|
22
|
+
{
|
|
23
|
+
id: 'cheap-model-routing-missing',
|
|
24
|
+
severity: 'info',
|
|
25
|
+
category: 'agent_practices',
|
|
26
|
+
title: 'Missing model routing guidance',
|
|
27
|
+
summary: 'No model routing guidelines found in instruction files. Using expensive frontier models for simple tasks (like linting or formatting) unnecessarily increases AI costs.',
|
|
28
|
+
filePaths: files.map((f) => f.path),
|
|
29
|
+
locations: [],
|
|
30
|
+
evidence: ['No mentions of cheaper models, model routing, or escalation conditions found in instructions.'],
|
|
31
|
+
recommendation: 'Add guidelines specifying when to use cheaper/smaller models (e.g. Gemini 2.5 Flash, GPT-4o-mini) versus escalating to stronger models.',
|
|
32
|
+
fixRecipe: [
|
|
33
|
+
'### Model Routing Guidance',
|
|
34
|
+
'- Use cheaper/smaller models (e.g. Gemini 2.5 Flash, GPT-4o-mini) for: formatting, linting, simple test additions, mechanical renames, and documentation updates.',
|
|
35
|
+
'- Escalate to stronger models (e.g. Gemini 2.5 Pro, Claude 3.5 Sonnet) for: complex multi-file debugging, security-sensitive changes, and core architecture decisions.'
|
|
36
|
+
].join('\n'),
|
|
37
|
+
confidence: 0.8,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=cheap-model-routing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cheap-model-routing.js","sourceRoot":"","sources":["../src/cheap-model-routing.ts"],"names":[],"mappings":"AAGA,MAAM,gBAAgB,GAAG;IACvB,uFAAuF;IACvF,6EAA6E;IAC7E,kBAAkB;IAClB,iCAAiC;IACjC,6DAA6D;IAC7D,sBAAsB;CACvB,CAAC;AAEF,MAAM,UAAU,uBAAuB,CAAC,OAAoB;IAC1D,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;IAEnC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,oDAAoD;IACpD,IAAI,OAAO,CAAC,0BAA0B,GAAG,GAAG,EAAE,CAAC;QAC7C,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,eAAe,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/D,MAAM,kBAAkB,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC;IAErF,IAAI,CAAC,kBAAkB,EAAE,CAAC;QACxB,OAAO;YACL;gBACE,EAAE,EAAE,6BAA6B;gBACjC,QAAQ,EAAE,MAAM;gBAChB,QAAQ,EAAE,iBAAiB;gBAC3B,KAAK,EAAE,gCAAgC;gBACvC,OAAO,EAAE,yKAAyK;gBAClL,SAAS,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;gBACnC,SAAS,EAAE,EAAE;gBACb,QAAQ,EAAE,CAAC,+FAA+F,CAAC;gBAC3G,cAAc,EAAE,yIAAyI;gBACzJ,SAAS,EAAE;oBACT,4BAA4B;oBAC5B,mKAAmK;oBACnK,wKAAwK;iBACzK,CAAC,IAAI,CAAC,IAAI,CAAC;gBACZ,UAAU,EAAE,GAAG;aAChB;SACF,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RepoContext } from './repo-context.js';
|
|
2
|
+
import type { PromptCiIssue } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Entry point for the CI and Workflow Alignment detector.
|
|
5
|
+
*
|
|
6
|
+
* Returns an empty array when no workflow files exist so the detector never
|
|
7
|
+
* creates noise in repos without GitHub Actions.
|
|
8
|
+
*/
|
|
9
|
+
export declare function detectCiAlignment(context: RepoContext): PromptCiIssue[];
|
|
10
|
+
//# sourceMappingURL=ci-alignment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ci-alignment.d.ts","sourceRoot":"","sources":["../src/ci-alignment.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAmB,MAAM,mBAAmB,CAAC;AACtE,OAAO,KAAK,EAAmB,aAAa,EAAE,MAAM,YAAY,CAAC;AAuWjE;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,aAAa,EAAE,CAQvE"}
|