@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.
Files changed (179) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +30 -0
  3. package/dist/agent-practices.d.ts +22 -0
  4. package/dist/agent-practices.d.ts.map +1 -0
  5. package/dist/agent-practices.js +582 -0
  6. package/dist/agent-practices.js.map +1 -0
  7. package/dist/baseline.d.ts +40 -0
  8. package/dist/baseline.d.ts.map +1 -0
  9. package/dist/baseline.js +172 -0
  10. package/dist/baseline.js.map +1 -0
  11. package/dist/canonical-owner.d.ts +3 -0
  12. package/dist/canonical-owner.d.ts.map +1 -0
  13. package/dist/canonical-owner.js +131 -0
  14. package/dist/canonical-owner.js.map +1 -0
  15. package/dist/cheap-model-routing.d.ts +4 -0
  16. package/dist/cheap-model-routing.d.ts.map +1 -0
  17. package/dist/cheap-model-routing.js +43 -0
  18. package/dist/cheap-model-routing.js.map +1 -0
  19. package/dist/ci-alignment.d.ts +10 -0
  20. package/dist/ci-alignment.d.ts.map +1 -0
  21. package/dist/ci-alignment.js +343 -0
  22. package/dist/ci-alignment.js.map +1 -0
  23. package/dist/cli-output-filter.d.ts +16 -0
  24. package/dist/cli-output-filter.d.ts.map +1 -0
  25. package/dist/cli-output-filter.js +88 -0
  26. package/dist/cli-output-filter.js.map +1 -0
  27. package/dist/command-validity.d.ts +12 -0
  28. package/dist/command-validity.d.ts.map +1 -0
  29. package/dist/command-validity.js +543 -0
  30. package/dist/command-validity.js.map +1 -0
  31. package/dist/conflicts.d.ts +49 -0
  32. package/dist/conflicts.d.ts.map +1 -0
  33. package/dist/conflicts.js +1065 -0
  34. package/dist/conflicts.js.map +1 -0
  35. package/dist/context-analysis.d.ts +15 -0
  36. package/dist/context-analysis.d.ts.map +1 -0
  37. package/dist/context-analysis.js +24 -0
  38. package/dist/context-analysis.js.map +1 -0
  39. package/dist/context-bloat.d.ts +17 -0
  40. package/dist/context-bloat.d.ts.map +1 -0
  41. package/dist/context-bloat.js +148 -0
  42. package/dist/context-bloat.js.map +1 -0
  43. package/dist/context-cost.d.ts +4 -0
  44. package/dist/context-cost.d.ts.map +1 -0
  45. package/dist/context-cost.js +70 -0
  46. package/dist/context-cost.js.map +1 -0
  47. package/dist/context-optimizer.d.ts +14 -0
  48. package/dist/context-optimizer.d.ts.map +1 -0
  49. package/dist/context-optimizer.js +219 -0
  50. package/dist/context-optimizer.js.map +1 -0
  51. package/dist/context-recommender.d.ts +3 -0
  52. package/dist/context-recommender.d.ts.map +1 -0
  53. package/dist/context-recommender.js +329 -0
  54. package/dist/context-recommender.js.map +1 -0
  55. package/dist/dead-references.d.ts +33 -0
  56. package/dist/dead-references.d.ts.map +1 -0
  57. package/dist/dead-references.js +607 -0
  58. package/dist/dead-references.js.map +1 -0
  59. package/dist/detectors.d.ts +11 -0
  60. package/dist/detectors.d.ts.map +1 -0
  61. package/dist/detectors.js +160 -0
  62. package/dist/detectors.js.map +1 -0
  63. package/dist/duplicates.d.ts +36 -0
  64. package/dist/duplicates.d.ts.map +1 -0
  65. package/dist/duplicates.js +436 -0
  66. package/dist/duplicates.js.map +1 -0
  67. package/dist/fix-engine.d.ts +16 -0
  68. package/dist/fix-engine.d.ts.map +1 -0
  69. package/dist/fix-engine.js +179 -0
  70. package/dist/fix-engine.js.map +1 -0
  71. package/dist/framework-packs.d.ts +8 -0
  72. package/dist/framework-packs.d.ts.map +1 -0
  73. package/dist/framework-packs.js +22 -0
  74. package/dist/framework-packs.js.map +1 -0
  75. package/dist/frameworks/dotnet.d.ts +4 -0
  76. package/dist/frameworks/dotnet.d.ts.map +1 -0
  77. package/dist/frameworks/dotnet.js +77 -0
  78. package/dist/frameworks/dotnet.js.map +1 -0
  79. package/dist/frameworks/go.d.ts +4 -0
  80. package/dist/frameworks/go.d.ts.map +1 -0
  81. package/dist/frameworks/go.js +39 -0
  82. package/dist/frameworks/go.js.map +1 -0
  83. package/dist/frameworks/nextjs.d.ts +4 -0
  84. package/dist/frameworks/nextjs.d.ts.map +1 -0
  85. package/dist/frameworks/nextjs.js +66 -0
  86. package/dist/frameworks/nextjs.js.map +1 -0
  87. package/dist/frameworks/python.d.ts +4 -0
  88. package/dist/frameworks/python.d.ts.map +1 -0
  89. package/dist/frameworks/python.js +66 -0
  90. package/dist/frameworks/python.js.map +1 -0
  91. package/dist/frameworks/rust.d.ts +4 -0
  92. package/dist/frameworks/rust.d.ts.map +1 -0
  93. package/dist/frameworks/rust.js +72 -0
  94. package/dist/frameworks/rust.js.map +1 -0
  95. package/dist/frameworks/unity.d.ts +4 -0
  96. package/dist/frameworks/unity.d.ts.map +1 -0
  97. package/dist/frameworks/unity.js +74 -0
  98. package/dist/frameworks/unity.js.map +1 -0
  99. package/dist/health-score.d.ts +37 -0
  100. package/dist/health-score.d.ts.map +1 -0
  101. package/dist/health-score.js +164 -0
  102. package/dist/health-score.js.map +1 -0
  103. package/dist/index.d.ts +31 -0
  104. package/dist/index.d.ts.map +1 -0
  105. package/dist/index.js +23 -0
  106. package/dist/index.js.map +1 -0
  107. package/dist/llm.d.ts +6 -0
  108. package/dist/llm.d.ts.map +1 -0
  109. package/dist/llm.js +67 -0
  110. package/dist/llm.js.map +1 -0
  111. package/dist/manifest-consistency.d.ts +37 -0
  112. package/dist/manifest-consistency.d.ts.map +1 -0
  113. package/dist/manifest-consistency.js +659 -0
  114. package/dist/manifest-consistency.js.map +1 -0
  115. package/dist/mcp-detector.d.ts +4 -0
  116. package/dist/mcp-detector.d.ts.map +1 -0
  117. package/dist/mcp-detector.js +131 -0
  118. package/dist/mcp-detector.js.map +1 -0
  119. package/dist/missing-context.d.ts +15 -0
  120. package/dist/missing-context.d.ts.map +1 -0
  121. package/dist/missing-context.js +517 -0
  122. package/dist/missing-context.js.map +1 -0
  123. package/dist/native-tool-preference.d.ts +20 -0
  124. package/dist/native-tool-preference.d.ts.map +1 -0
  125. package/dist/native-tool-preference.js +94 -0
  126. package/dist/native-tool-preference.js.map +1 -0
  127. package/dist/output-verbosity.d.ts +3 -0
  128. package/dist/output-verbosity.d.ts.map +1 -0
  129. package/dist/output-verbosity.js +83 -0
  130. package/dist/output-verbosity.js.map +1 -0
  131. package/dist/product-boundary.d.ts +11 -0
  132. package/dist/product-boundary.d.ts.map +1 -0
  133. package/dist/product-boundary.js +301 -0
  134. package/dist/product-boundary.js.map +1 -0
  135. package/dist/project-type.d.ts +15 -0
  136. package/dist/project-type.d.ts.map +1 -0
  137. package/dist/project-type.js +116 -0
  138. package/dist/project-type.js.map +1 -0
  139. package/dist/prompt-cache.d.ts +3 -0
  140. package/dist/prompt-cache.d.ts.map +1 -0
  141. package/dist/prompt-cache.js +151 -0
  142. package/dist/prompt-cache.js.map +1 -0
  143. package/dist/repo-context.d.ts +37 -0
  144. package/dist/repo-context.d.ts.map +1 -0
  145. package/dist/repo-context.js +221 -0
  146. package/dist/repo-context.js.map +1 -0
  147. package/dist/report.d.ts +87 -0
  148. package/dist/report.d.ts.map +1 -0
  149. package/dist/report.js +558 -0
  150. package/dist/report.js.map +1 -0
  151. package/dist/scan.d.ts +7 -0
  152. package/dist/scan.d.ts.map +1 -0
  153. package/dist/scan.js +43 -0
  154. package/dist/scan.js.map +1 -0
  155. package/dist/scanner.d.ts +3 -0
  156. package/dist/scanner.d.ts.map +1 -0
  157. package/dist/scanner.js +216 -0
  158. package/dist/scanner.js.map +1 -0
  159. package/dist/security-pack.d.ts +4 -0
  160. package/dist/security-pack.d.ts.map +1 -0
  161. package/dist/security-pack.js +403 -0
  162. package/dist/security-pack.js.map +1 -0
  163. package/dist/stale-instructions.d.ts +20 -0
  164. package/dist/stale-instructions.d.ts.map +1 -0
  165. package/dist/stale-instructions.js +269 -0
  166. package/dist/stale-instructions.js.map +1 -0
  167. package/dist/suppression.d.ts +64 -0
  168. package/dist/suppression.d.ts.map +1 -0
  169. package/dist/suppression.js +338 -0
  170. package/dist/suppression.js.map +1 -0
  171. package/dist/types.d.ts +128 -0
  172. package/dist/types.d.ts.map +1 -0
  173. package/dist/types.js +3 -0
  174. package/dist/types.js.map +1 -0
  175. package/dist/vague-guidance.d.ts +12 -0
  176. package/dist/vague-guidance.d.ts.map +1 -0
  177. package/dist/vague-guidance.js +235 -0
  178. package/dist/vague-guidance.js.map +1 -0
  179. 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"}
@@ -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,3 @@
1
+ import type { InstructionFile, PromptCiIssue } from './types.js';
2
+ export declare function detectCanonicalOwner(files: InstructionFile[]): PromptCiIssue[];
3
+ //# sourceMappingURL=canonical-owner.d.ts.map
@@ -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,4 @@
1
+ import type { RepoContext } from './repo-context.js';
2
+ import type { PromptCiIssue } from './types.js';
3
+ export declare function detectCheapModelRouting(context: RepoContext): PromptCiIssue[];
4
+ //# sourceMappingURL=cheap-model-routing.d.ts.map
@@ -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"}