@promptci/core 0.0.1 → 0.1.0
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 -201
- package/README.md +29 -30
- package/dist/agent-practices.d.ts +2 -0
- package/dist/agent-practices.d.ts.map +1 -1
- package/dist/agent-practices.js +91 -51
- package/dist/agent-practices.js.map +1 -1
- package/dist/ai-config.d.ts +139 -0
- package/dist/ai-config.d.ts.map +1 -0
- package/dist/ai-config.js +523 -0
- package/dist/ai-config.js.map +1 -0
- package/dist/baseline.d.ts +17 -1
- package/dist/baseline.d.ts.map +1 -1
- package/dist/baseline.js +5 -1
- package/dist/baseline.js.map +1 -1
- package/dist/buried-critical.d.ts +17 -0
- package/dist/buried-critical.d.ts.map +1 -0
- package/dist/buried-critical.js +112 -0
- package/dist/buried-critical.js.map +1 -0
- package/dist/canonical-owner.d.ts.map +1 -1
- package/dist/canonical-owner.js +18 -8
- package/dist/canonical-owner.js.map +1 -1
- package/dist/command-validity.d.ts.map +1 -1
- package/dist/command-validity.js +53 -74
- package/dist/command-validity.js.map +1 -1
- package/dist/conflicts.d.ts.map +1 -1
- package/dist/conflicts.js +1 -42
- package/dist/conflicts.js.map +1 -1
- package/dist/context-analysis.d.ts.map +1 -1
- package/dist/context-analysis.js +13 -2
- package/dist/context-analysis.js.map +1 -1
- package/dist/context-bloat.d.ts +19 -0
- package/dist/context-bloat.d.ts.map +1 -1
- package/dist/context-bloat.js +82 -9
- package/dist/context-bloat.js.map +1 -1
- package/dist/context-optimizer.d.ts.map +1 -1
- package/dist/context-optimizer.js +15 -54
- package/dist/context-optimizer.js.map +1 -1
- package/dist/copilot-instructions-detector.d.ts +24 -0
- package/dist/copilot-instructions-detector.d.ts.map +1 -0
- package/dist/copilot-instructions-detector.js +145 -0
- package/dist/copilot-instructions-detector.js.map +1 -0
- package/dist/cursor-rules-detector.d.ts +17 -0
- package/dist/cursor-rules-detector.d.ts.map +1 -0
- package/dist/cursor-rules-detector.js +110 -0
- package/dist/cursor-rules-detector.js.map +1 -0
- package/dist/custom-rules.d.ts +52 -0
- package/dist/custom-rules.d.ts.map +1 -0
- package/dist/custom-rules.js +298 -0
- package/dist/custom-rules.js.map +1 -0
- package/dist/dead-references.js +11 -9
- package/dist/dead-references.js.map +1 -1
- package/dist/detectors.d.ts +1 -3
- package/dist/detectors.d.ts.map +1 -1
- package/dist/detectors.js +46 -53
- package/dist/detectors.js.map +1 -1
- package/dist/duplicates.d.ts.map +1 -1
- package/dist/duplicates.js +10 -5
- package/dist/duplicates.js.map +1 -1
- package/dist/evidence.d.ts +28 -0
- package/dist/evidence.d.ts.map +1 -0
- package/dist/evidence.js +38 -0
- package/dist/evidence.js.map +1 -0
- package/dist/fix-engine.d.ts +11 -0
- package/dist/fix-engine.d.ts.map +1 -1
- package/dist/fix-engine.js +88 -71
- package/dist/fix-engine.js.map +1 -1
- package/dist/frameworks/python.d.ts.map +1 -1
- package/dist/frameworks/python.js +5 -2
- package/dist/frameworks/python.js.map +1 -1
- package/dist/health-score.d.ts.map +1 -1
- package/dist/health-score.js +1 -0
- package/dist/health-score.js.map +1 -1
- package/dist/hooks-settings-detector.d.ts +17 -0
- package/dist/hooks-settings-detector.d.ts.map +1 -0
- package/dist/hooks-settings-detector.js +228 -0
- package/dist/hooks-settings-detector.js.map +1 -0
- package/dist/index.d.ts +22 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -6
- package/dist/index.js.map +1 -1
- package/dist/manifest-consistency.d.ts +6 -0
- package/dist/manifest-consistency.d.ts.map +1 -1
- package/dist/manifest-consistency.js +26 -5
- package/dist/manifest-consistency.js.map +1 -1
- package/dist/markdown-fences.d.ts +112 -0
- package/dist/markdown-fences.d.ts.map +1 -0
- package/dist/markdown-fences.js +174 -0
- package/dist/markdown-fences.js.map +1 -0
- package/dist/mcp-config-detector.d.ts +20 -0
- package/dist/mcp-config-detector.d.ts.map +1 -0
- package/dist/mcp-config-detector.js +237 -0
- package/dist/mcp-config-detector.js.map +1 -0
- package/dist/missing-context.d.ts.map +1 -1
- package/dist/missing-context.js +2 -52
- package/dist/missing-context.js.map +1 -1
- package/dist/model-budgets.d.ts +69 -0
- package/dist/model-budgets.d.ts.map +1 -0
- package/dist/model-budgets.js +110 -0
- package/dist/model-budgets.js.map +1 -0
- package/dist/negative-instructions.d.ts +26 -0
- package/dist/negative-instructions.d.ts.map +1 -0
- package/dist/negative-instructions.js +174 -0
- package/dist/negative-instructions.js.map +1 -0
- package/dist/path-containment.d.ts +12 -0
- package/dist/path-containment.d.ts.map +1 -0
- package/dist/path-containment.js +53 -0
- package/dist/path-containment.js.map +1 -0
- package/dist/project-type.d.ts +12 -2
- package/dist/project-type.d.ts.map +1 -1
- package/dist/project-type.js +25 -12
- package/dist/project-type.js.map +1 -1
- package/dist/prompt-cache.d.ts.map +1 -1
- package/dist/prompt-cache.js +26 -19
- package/dist/prompt-cache.js.map +1 -1
- package/dist/promptci-gitignore.d.ts +34 -0
- package/dist/promptci-gitignore.d.ts.map +1 -0
- package/dist/promptci-gitignore.js +42 -0
- package/dist/promptci-gitignore.js.map +1 -0
- package/dist/repo-context.d.ts +27 -0
- package/dist/repo-context.d.ts.map +1 -1
- package/dist/repo-context.js +42 -11
- package/dist/repo-context.js.map +1 -1
- package/dist/report.d.ts +1 -0
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +132 -61
- package/dist/report.js.map +1 -1
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +7 -3
- package/dist/scan.js.map +1 -1
- package/dist/scanner.d.ts +13 -1
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +49 -37
- package/dist/scanner.js.map +1 -1
- package/dist/security-pack.d.ts +20 -0
- package/dist/security-pack.d.ts.map +1 -1
- package/dist/security-pack.js +27 -10
- package/dist/security-pack.js.map +1 -1
- package/dist/skills-detector.d.ts +19 -0
- package/dist/skills-detector.d.ts.map +1 -0
- package/dist/skills-detector.js +203 -0
- package/dist/skills-detector.js.map +1 -0
- package/dist/stale-instructions.d.ts +4 -1
- package/dist/stale-instructions.d.ts.map +1 -1
- package/dist/stale-instructions.js +51 -54
- package/dist/stale-instructions.js.map +1 -1
- package/dist/subagents-detector.d.ts +19 -0
- package/dist/subagents-detector.d.ts.map +1 -0
- package/dist/subagents-detector.js +155 -0
- package/dist/subagents-detector.js.map +1 -0
- package/dist/suppression.d.ts.map +1 -1
- package/dist/suppression.js +11 -45
- package/dist/suppression.js.map +1 -1
- package/dist/types.d.ts +28 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +25 -1
- package/dist/types.js.map +1 -1
- package/dist/vague-guidance.d.ts +15 -2
- package/dist/vague-guidance.d.ts.map +1 -1
- package/dist/vague-guidance.js +42 -40
- package/dist/vague-guidance.js.map +1 -1
- package/dist/windsurf-detector.d.ts +22 -0
- package/dist/windsurf-detector.d.ts.map +1 -0
- package/dist/windsurf-detector.js +87 -0
- package/dist/windsurf-detector.js.map +1 -0
- package/dist/within-section-dedup.d.ts +16 -0
- package/dist/within-section-dedup.d.ts.map +1 -0
- package/dist/within-section-dedup.js +153 -0
- package/dist/within-section-dedup.js.map +1 -0
- package/package.json +11 -8
- package/dist/cheap-model-routing.d.ts +0 -4
- package/dist/cheap-model-routing.d.ts.map +0 -1
- package/dist/cheap-model-routing.js +0 -43
- package/dist/cheap-model-routing.js.map +0 -1
- package/dist/cli-output-filter.d.ts +0 -16
- package/dist/cli-output-filter.d.ts.map +0 -1
- package/dist/cli-output-filter.js +0 -88
- package/dist/cli-output-filter.js.map +0 -1
- package/dist/context-cost.d.ts +0 -4
- package/dist/context-cost.d.ts.map +0 -1
- package/dist/context-cost.js +0 -70
- package/dist/context-cost.js.map +0 -1
- package/dist/context-recommender.d.ts +0 -3
- package/dist/context-recommender.d.ts.map +0 -1
- package/dist/context-recommender.js +0 -329
- package/dist/context-recommender.js.map +0 -1
- package/dist/llm.d.ts +0 -6
- package/dist/llm.d.ts.map +0 -1
- package/dist/llm.js +0 -67
- package/dist/llm.js.map +0 -1
- package/dist/mcp-detector.d.ts +0 -4
- package/dist/mcp-detector.d.ts.map +0 -1
- package/dist/mcp-detector.js +0 -131
- package/dist/mcp-detector.js.map +0 -1
- package/dist/native-tool-preference.d.ts +0 -20
- package/dist/native-tool-preference.d.ts.map +0 -1
- package/dist/native-tool-preference.js +0 -94
- package/dist/native-tool-preference.js.map +0 -1
- package/dist/output-verbosity.d.ts +0 -3
- package/dist/output-verbosity.d.ts.map +0 -1
- package/dist/output-verbosity.js +0 -83
- package/dist/output-verbosity.js.map +0 -1
- package/dist/product-boundary.d.ts +0 -11
- package/dist/product-boundary.d.ts.map +0 -1
- package/dist/product-boundary.js +0 -301
- package/dist/product-boundary.js.map +0 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Negative-instruction overload detector (FEAT-010).
|
|
3
|
+
*
|
|
4
|
+
* Models follow positive directives ("use const") more reliably than
|
|
5
|
+
* prohibitions ("don't use var"). A file whose directive sentences are
|
|
6
|
+
* overwhelmingly phrased as prohibitions is harder to follow than the same
|
|
7
|
+
* guidance framed positively.
|
|
8
|
+
*
|
|
9
|
+
* Heuristic: split the file into candidate sentences, classify each as a
|
|
10
|
+
* positive directive, a negative directive, or neither, and — when there are
|
|
11
|
+
* enough directives to be meaningful — flag files where more than 40% of the
|
|
12
|
+
* directives are negative. Info severity only; the wording is deliberately
|
|
13
|
+
* cautious and the recommendation offers a positive-rewrite hint.
|
|
14
|
+
*
|
|
15
|
+
* Deterministic and rule-based: no clock, network, or randomness.
|
|
16
|
+
*/
|
|
17
|
+
import * as crypto from 'node:crypto';
|
|
18
|
+
import * as path from 'node:path';
|
|
19
|
+
import { snippet } from './evidence.js';
|
|
20
|
+
/** Persistent, always-loaded instruction files this detector applies to. */
|
|
21
|
+
const TARGET_FILE_TYPES = new Set([
|
|
22
|
+
'claude',
|
|
23
|
+
'agents',
|
|
24
|
+
'cursor',
|
|
25
|
+
'windsurf',
|
|
26
|
+
'copilot',
|
|
27
|
+
'prompt',
|
|
28
|
+
]);
|
|
29
|
+
/**
|
|
30
|
+
* A sentence is a NEGATIVE directive when it contains one of these prohibition
|
|
31
|
+
* markers. Negative markers take precedence over positive ones, so "always
|
|
32
|
+
* avoid mutation" counts as negative — its operative instruction is a
|
|
33
|
+
* prohibition.
|
|
34
|
+
*/
|
|
35
|
+
const NEGATIVE_MARKERS = [
|
|
36
|
+
// Second-person imperative prohibitions only. Third-person "does not" /
|
|
37
|
+
// "doesn't" is descriptive narration ("this file does not repeat it"), not a
|
|
38
|
+
// directive, so it is deliberately excluded to avoid inflating the count.
|
|
39
|
+
/\bdo\s+not\b/i,
|
|
40
|
+
/\bdon['’]?t\b/i,
|
|
41
|
+
/\bnever\b/i,
|
|
42
|
+
/\bavoid\b/i,
|
|
43
|
+
/\brefrain\s+from\b/i,
|
|
44
|
+
/\bmust\s+not\b/i,
|
|
45
|
+
/\bmustn['’]?t\b/i,
|
|
46
|
+
/\bshould\s+not\b/i,
|
|
47
|
+
/\bshouldn['’]?t\b/i,
|
|
48
|
+
/\bcannot\b/i,
|
|
49
|
+
/\bcan['’]?t\b/i,
|
|
50
|
+
/\bwon['’]?t\b/i,
|
|
51
|
+
/\bno\s+longer\b/i,
|
|
52
|
+
];
|
|
53
|
+
/**
|
|
54
|
+
* A sentence is a POSITIVE directive when it starts with an imperative verb or
|
|
55
|
+
* carries a positive modal ("must"/"should"/"always" not already caught as
|
|
56
|
+
* negative above). This intentionally under-counts rather than over-counts:
|
|
57
|
+
* only clearly directive sentences enter the denominator.
|
|
58
|
+
*/
|
|
59
|
+
const POSITIVE_IMPERATIVE_START = /^(use|always|prefer|ensure|make\s+sure|keep|run|write|add|follow|call|set|return|check|verify|read|ask|include|place|put|store|document|test|update|create|define|import|export|name|group|split|choose|pick|apply|treat|surface|confirm|report|explain|favor|favour|default\s+to|stick\s+to|remember\s+to|be\s+sure\s+to)\b/i;
|
|
60
|
+
const POSITIVE_MODAL = /\b(must|should|always|shall)\b/i;
|
|
61
|
+
const MIN_DIRECTIVES = 8; // below this the ratio is too noisy to be meaningful
|
|
62
|
+
const NEGATIVE_RATIO_THRESHOLD = 0.4; // > 40% negative → finding
|
|
63
|
+
/** Strip a leading markdown list bullet / ordered-list marker / heading hash. */
|
|
64
|
+
function stripLeadingMarkers(line) {
|
|
65
|
+
return line
|
|
66
|
+
.replace(/^\s*#{1,6}\s+/, '')
|
|
67
|
+
.replace(/^\s*[-*+]\s+/, '')
|
|
68
|
+
.replace(/^\s*\d+[.)]\s+/, '')
|
|
69
|
+
.replace(/^\s*>\s+/, '')
|
|
70
|
+
.trim();
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Strip leading markdown emphasis (`**bold**`, `*italic*`, `` `code` ``) so the
|
|
74
|
+
* operative verb is visible to the imperative test — otherwise "**Report** ..."
|
|
75
|
+
* hides the "Report" that makes the sentence a positive directive.
|
|
76
|
+
*/
|
|
77
|
+
function stripLeadingEmphasis(text) {
|
|
78
|
+
return text.replace(/^[*_`]+/, '').trimStart();
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Split file content into candidate directive sentences. Fenced code blocks are
|
|
82
|
+
* dropped (a bash comment is not an instruction sentence). Each non-blank line
|
|
83
|
+
* is stripped of list/heading markers, then split on sentence terminators so a
|
|
84
|
+
* line holding two clauses is counted as two.
|
|
85
|
+
*/
|
|
86
|
+
export function splitSentences(content) {
|
|
87
|
+
const lines = content.split(/\r?\n/);
|
|
88
|
+
const sentences = [];
|
|
89
|
+
let inFence = false;
|
|
90
|
+
for (const raw of lines) {
|
|
91
|
+
const fenceToggle = /^\s*(```|~~~)/.test(raw);
|
|
92
|
+
if (fenceToggle) {
|
|
93
|
+
inFence = !inFence;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (inFence)
|
|
97
|
+
continue;
|
|
98
|
+
const cleaned = stripLeadingMarkers(raw);
|
|
99
|
+
if (!cleaned)
|
|
100
|
+
continue;
|
|
101
|
+
for (const part of cleaned.split(/(?<=[.!?])\s+/)) {
|
|
102
|
+
const trimmed = part.trim();
|
|
103
|
+
// Require a few words so table cells / fragments don't count as sentences.
|
|
104
|
+
if (trimmed.split(/\s+/).length >= 3)
|
|
105
|
+
sentences.push(trimmed);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return sentences;
|
|
109
|
+
}
|
|
110
|
+
function classify(sentence) {
|
|
111
|
+
const lead = stripLeadingEmphasis(sentence);
|
|
112
|
+
// The LEADING imperative governs: a positively-framed instruction that also
|
|
113
|
+
// mentions a prohibition ("Keep diffs focused; do not refactor unrelated
|
|
114
|
+
// code") is a positive directive, not a negative one. Only sentences whose
|
|
115
|
+
// primary verb is not a positive imperative fall through to the prohibition
|
|
116
|
+
// markers.
|
|
117
|
+
if (POSITIVE_IMPERATIVE_START.test(lead))
|
|
118
|
+
return 'positive';
|
|
119
|
+
for (const re of NEGATIVE_MARKERS) {
|
|
120
|
+
if (re.test(sentence))
|
|
121
|
+
return 'negative';
|
|
122
|
+
}
|
|
123
|
+
if (POSITIVE_MODAL.test(sentence))
|
|
124
|
+
return 'positive';
|
|
125
|
+
return 'none';
|
|
126
|
+
}
|
|
127
|
+
function issueId(filePath) {
|
|
128
|
+
const hash = crypto.createHash('sha1').update(`negative-overload:${filePath}`).digest('hex').slice(0, 12);
|
|
129
|
+
return `negative-overload-${hash}`;
|
|
130
|
+
}
|
|
131
|
+
export function detectNegativeInstructionOverload(files) {
|
|
132
|
+
const issues = [];
|
|
133
|
+
for (const file of files) {
|
|
134
|
+
if (!TARGET_FILE_TYPES.has(file.fileType))
|
|
135
|
+
continue;
|
|
136
|
+
const sentences = splitSentences(file.content);
|
|
137
|
+
const negatives = [];
|
|
138
|
+
let positiveCount = 0;
|
|
139
|
+
for (const sentence of sentences) {
|
|
140
|
+
const kind = classify(sentence);
|
|
141
|
+
if (kind === 'negative')
|
|
142
|
+
negatives.push(sentence);
|
|
143
|
+
else if (kind === 'positive')
|
|
144
|
+
positiveCount++;
|
|
145
|
+
}
|
|
146
|
+
const total = negatives.length + positiveCount;
|
|
147
|
+
if (total < MIN_DIRECTIVES)
|
|
148
|
+
continue;
|
|
149
|
+
const ratio = negatives.length / total;
|
|
150
|
+
if (ratio <= NEGATIVE_RATIO_THRESHOLD)
|
|
151
|
+
continue;
|
|
152
|
+
const fileName = path.basename(file.path);
|
|
153
|
+
const pct = Math.round(ratio * 100);
|
|
154
|
+
const examples = negatives.slice(0, 3).map((s) => snippet(s, 100));
|
|
155
|
+
issues.push({
|
|
156
|
+
id: issueId(file.path),
|
|
157
|
+
severity: 'info',
|
|
158
|
+
category: 'agent_practices',
|
|
159
|
+
title: 'High proportion of negative instructions',
|
|
160
|
+
summary: `About ${pct}% of the directive sentences in "${fileName}" are phrased as prohibitions ` +
|
|
161
|
+
`(${negatives.length} of ${total}). Models tend to follow positive directives more ` +
|
|
162
|
+
`reliably than prohibitions, so heavily negative guidance can be harder to apply.`,
|
|
163
|
+
filePaths: [file.path],
|
|
164
|
+
locations: [],
|
|
165
|
+
evidence: examples.map((e) => `Negative directive: "${e}"`),
|
|
166
|
+
recommendation: 'Where practical, rephrase prohibitions as positive directives — e.g. instead of ' +
|
|
167
|
+
'"don\'t use var", write "always use const or let". Keep prohibitions for the cases ' +
|
|
168
|
+
'where no positive form is possible.',
|
|
169
|
+
confidence: 0.5,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return issues;
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=negative-instructions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"negative-instructions.js","sourceRoot":"","sources":["../src/negative-instructions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when `candidatePath` resolves to `root` itself or a descendant of it.
|
|
3
|
+
* Both arguments are resolved to absolute form first, so either may be relative.
|
|
4
|
+
*/
|
|
5
|
+
export declare function isWithinRoot(root: string, candidatePath: string): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Resolve `candidatePath` — treated as relative to `root` unless it is itself
|
|
8
|
+
* absolute — and return the absolute path when it stays within `root`, or
|
|
9
|
+
* `null` when it escapes (via `..`, an absolute path, or a different drive).
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveWithinRoot(root: string, candidatePath: string): string | null;
|
|
12
|
+
//# sourceMappingURL=path-containment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-containment.d.ts","sourceRoot":"","sources":["../src/path-containment.ts"],"names":[],"mappings":"AAqBA;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAYzE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAUpF"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one path-containment check for the whole toolchain.
|
|
3
|
+
*
|
|
4
|
+
* Every place that resolves an externally-influenced path (a config value, an
|
|
5
|
+
* LLM-proposed edit target, a fix's file path) must confirm the result stays
|
|
6
|
+
* inside the repo/target root before touching the filesystem. This module is
|
|
7
|
+
* the single, tested implementation those checks share.
|
|
8
|
+
*
|
|
9
|
+
* The naive guard this replaces — `resolved.startsWith(root)` — is wrong two
|
|
10
|
+
* ways:
|
|
11
|
+
* - **Sibling prefix.** `/repo-evil` starts with `/repo`, so a sibling
|
|
12
|
+
* directory whose name merely shares a prefix reads as "contained".
|
|
13
|
+
* - **Separator / drive blind spots.** On Windows a raw string prefix ignores
|
|
14
|
+
* the path separator boundary and cross-drive absolutes.
|
|
15
|
+
*
|
|
16
|
+
* Comparing the *relative* path from the root sidesteps both: a contained path
|
|
17
|
+
* yields `''` (the root itself) or a forward walk with no leading `..`; any
|
|
18
|
+
* escape yields a leading `..` segment or an absolute path (a different drive).
|
|
19
|
+
*/
|
|
20
|
+
import * as path from 'node:path';
|
|
21
|
+
/**
|
|
22
|
+
* True when `candidatePath` resolves to `root` itself or a descendant of it.
|
|
23
|
+
* Both arguments are resolved to absolute form first, so either may be relative.
|
|
24
|
+
*/
|
|
25
|
+
export function isWithinRoot(root, candidatePath) {
|
|
26
|
+
const resolvedRoot = path.resolve(root);
|
|
27
|
+
const resolved = path.resolve(candidatePath);
|
|
28
|
+
const rel = path.relative(resolvedRoot, resolved);
|
|
29
|
+
// '' is the root itself. A genuine escape is exactly '..' or begins with
|
|
30
|
+
// '..<sep>' (a child literally named e.g. '..foo' is NOT an escape), or is
|
|
31
|
+
// absolute — which path.relative only returns when the target is on a
|
|
32
|
+
// different Windows drive and cannot be reached by walking up.
|
|
33
|
+
return (rel === '' ||
|
|
34
|
+
(rel !== '..' && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel)));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Resolve `candidatePath` — treated as relative to `root` unless it is itself
|
|
38
|
+
* absolute — and return the absolute path when it stays within `root`, or
|
|
39
|
+
* `null` when it escapes (via `..`, an absolute path, or a different drive).
|
|
40
|
+
*/
|
|
41
|
+
export function resolveWithinRoot(root, candidatePath) {
|
|
42
|
+
try {
|
|
43
|
+
const resolvedRoot = path.resolve(root);
|
|
44
|
+
const resolved = path.isAbsolute(candidatePath)
|
|
45
|
+
? path.resolve(candidatePath)
|
|
46
|
+
: path.resolve(resolvedRoot, candidatePath);
|
|
47
|
+
return isWithinRoot(resolvedRoot, resolved) ? resolved : null;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=path-containment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"path-containment.js","sourceRoot":"","sources":["../src/path-containment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,aAAqB;IAC9D,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC;IAC7C,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IAClD,yEAAyE;IACzE,2EAA2E;IAC3E,sEAAsE;IACtE,+DAA+D;IAC/D,OAAO,CACL,GAAG,KAAK,EAAE;QACV,CAAC,GAAG,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAC5E,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,aAAqB;IACnE,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;YAC7C,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC;YAC7B,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;QAC9C,OAAO,YAAY,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/dist/project-type.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { InstructionFile, ProjectType } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Detect the primary project type for a repo at repoRoot.
|
|
4
|
-
* Detection order: nextjs → typescript →
|
|
4
|
+
* Detection order: nextjs → unity → dotnet → typescript → python → go → rust → unknown
|
|
5
|
+
*
|
|
6
|
+
* Unity/.NET probes run before the package.json→typescript branch: Unity and .NET
|
|
7
|
+
* repos commonly carry a root package.json for JS tooling (husky, prettier), so the
|
|
8
|
+
* bare-package.json check must not shadow their more specific markers. This matches
|
|
9
|
+
* the unity-before-typescript ordering in detectProjectTypeFromContent.
|
|
5
10
|
*/
|
|
6
11
|
export declare function detectProjectType(repoRoot: string): Promise<ProjectType>;
|
|
7
12
|
/**
|
|
@@ -9,7 +14,12 @@ export declare function detectProjectType(repoRoot: string): Promise<ProjectType
|
|
|
9
14
|
* scan returns 'unknown' (e.g. repos that only contain instruction files, no source).
|
|
10
15
|
* Checks the combined content of all scanned instruction files for strong signals.
|
|
11
16
|
*
|
|
12
|
-
* Detection order: nextjs → unity →
|
|
17
|
+
* Detection order: nextjs → unity → dotnet → typescript → python → go → rust → unknown
|
|
18
|
+
*
|
|
19
|
+
* .NET is checked before typescript for the same reason as in the file-system
|
|
20
|
+
* probe above: .NET repos commonly mention JS tooling (pnpm, npm run, jest) in
|
|
21
|
+
* their instructions, so the broader typescript signals must not shadow the
|
|
22
|
+
* more specific .NET ones.
|
|
13
23
|
*/
|
|
14
24
|
export declare function detectProjectTypeFromContent(files: InstructionFile[]): ProjectType;
|
|
15
25
|
//# sourceMappingURL=project-type.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-type.d.ts","sourceRoot":"","sources":["../src/project-type.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAoB/D
|
|
1
|
+
{"version":3,"file":"project-type.d.ts","sourceRoot":"","sources":["../src/project-type.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAoB/D;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CA+D9E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,WAAW,CA0BlF"}
|
package/dist/project-type.js
CHANGED
|
@@ -21,7 +21,12 @@ async function dirExists(p) {
|
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* Detect the primary project type for a repo at repoRoot.
|
|
24
|
-
* Detection order: nextjs → typescript →
|
|
24
|
+
* Detection order: nextjs → unity → dotnet → typescript → python → go → rust → unknown
|
|
25
|
+
*
|
|
26
|
+
* Unity/.NET probes run before the package.json→typescript branch: Unity and .NET
|
|
27
|
+
* repos commonly carry a root package.json for JS tooling (husky, prettier), so the
|
|
28
|
+
* bare-package.json check must not shadow their more specific markers. This matches
|
|
29
|
+
* the unity-before-typescript ordering in detectProjectTypeFromContent.
|
|
25
30
|
*/
|
|
26
31
|
export async function detectProjectType(repoRoot) {
|
|
27
32
|
const root = path.resolve(repoRoot);
|
|
@@ -42,19 +47,17 @@ export async function detectProjectType(repoRoot) {
|
|
|
42
47
|
if (hasApp || hasPages)
|
|
43
48
|
return 'nextjs';
|
|
44
49
|
}
|
|
45
|
-
// ── TypeScript ───────────────────────────────────────────────────────────
|
|
46
|
-
// package.json or tsconfig.json present
|
|
47
|
-
const hasTsConfig = await exists(path.join(root, 'tsconfig.json'));
|
|
48
|
-
if (hasPkgJson || hasTsConfig)
|
|
49
|
-
return 'typescript';
|
|
50
50
|
// ── Unity ────────────────────────────────────────────────────────────────
|
|
51
|
-
// Assets/ directory AND ProjectSettings/ProjectVersion.txt both exist
|
|
51
|
+
// Assets/ directory AND ProjectSettings/ProjectVersion.txt both exist.
|
|
52
|
+
// Checked before the package.json→typescript branch so a Unity repo with a
|
|
53
|
+
// root package.json is not misclassified as typescript.
|
|
52
54
|
const hasAssets = await dirExists(path.join(root, 'Assets'));
|
|
53
55
|
const hasProjectVersion = await exists(path.join(root, 'ProjectSettings', 'ProjectVersion.txt'));
|
|
54
56
|
if (hasAssets && hasProjectVersion)
|
|
55
57
|
return 'unity';
|
|
56
58
|
// ── .NET ─────────────────────────────────────────────────────────────────
|
|
57
|
-
// any .sln or .csproj file at repo root level
|
|
59
|
+
// any .sln or .csproj file at repo root level. Also checked before the
|
|
60
|
+
// typescript branch for the same reason (JS tooling drops a package.json).
|
|
58
61
|
const dotnetFiles = await fg(['*.sln', '*.csproj'], {
|
|
59
62
|
cwd: root,
|
|
60
63
|
dot: false,
|
|
@@ -63,6 +66,11 @@ export async function detectProjectType(repoRoot) {
|
|
|
63
66
|
});
|
|
64
67
|
if (dotnetFiles.length > 0)
|
|
65
68
|
return 'dotnet';
|
|
69
|
+
// ── TypeScript ───────────────────────────────────────────────────────────
|
|
70
|
+
// package.json or tsconfig.json present
|
|
71
|
+
const hasTsConfig = await exists(path.join(root, 'tsconfig.json'));
|
|
72
|
+
if (hasPkgJson || hasTsConfig)
|
|
73
|
+
return 'typescript';
|
|
66
74
|
// ── Python ───────────────────────────────────────────────────────────────
|
|
67
75
|
const hasPyProject = await exists(path.join(root, 'pyproject.toml'));
|
|
68
76
|
const hasRequirements = await exists(path.join(root, 'requirements.txt'));
|
|
@@ -86,7 +94,12 @@ export async function detectProjectType(repoRoot) {
|
|
|
86
94
|
* scan returns 'unknown' (e.g. repos that only contain instruction files, no source).
|
|
87
95
|
* Checks the combined content of all scanned instruction files for strong signals.
|
|
88
96
|
*
|
|
89
|
-
* Detection order: nextjs → unity →
|
|
97
|
+
* Detection order: nextjs → unity → dotnet → typescript → python → go → rust → unknown
|
|
98
|
+
*
|
|
99
|
+
* .NET is checked before typescript for the same reason as in the file-system
|
|
100
|
+
* probe above: .NET repos commonly mention JS tooling (pnpm, npm run, jest) in
|
|
101
|
+
* their instructions, so the broader typescript signals must not shadow the
|
|
102
|
+
* more specific .NET ones.
|
|
90
103
|
*/
|
|
91
104
|
export function detectProjectTypeFromContent(files) {
|
|
92
105
|
const combined = files.map((f) => f.content).join('\n');
|
|
@@ -96,12 +109,12 @@ export function detectProjectTypeFromContent(files) {
|
|
|
96
109
|
if ((/\bunity\b/i.test(combined) && /\b(lts|editor|hub|engine)\b/i.test(combined)) || /\bmonobehaviour\b|\bunityengine\b|\bgameobject\b|\bprefab\b|\bscriptableobject\b/i.test(combined)) {
|
|
97
110
|
return 'unity';
|
|
98
111
|
}
|
|
99
|
-
if (/\btypescript\b|\btsconfig\b|\btsc\s|\bpnpm\b|\bnpm\s+run\b|\bvitest\b|\bjest\b/i.test(combined)) {
|
|
100
|
-
return 'typescript';
|
|
101
|
-
}
|
|
102
112
|
if (/\bdotnet\b|\.sln\b|\.csproj\b|\bxunit\b|\bnunit\b|\bmstest\b/i.test(combined)) {
|
|
103
113
|
return 'dotnet';
|
|
104
114
|
}
|
|
115
|
+
if (/\btypescript\b|\btsconfig\b|\btsc\s|\bpnpm\b|\bnpm\s+run\b|\bvitest\b|\bjest\b/i.test(combined)) {
|
|
116
|
+
return 'typescript';
|
|
117
|
+
}
|
|
105
118
|
if (/\bpython\b|\bpip\b|\bpoetry\b|\bvenv\b|\bpytest\b|\bpyproject\.toml\b/i.test(combined)) {
|
|
106
119
|
return 'python';
|
|
107
120
|
}
|
package/dist/project-type.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-type.js","sourceRoot":"","sources":["../src/project-type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,MAAM,WAAW,CAAC;AAG3B,KAAK,UAAU,MAAM,CAAC,CAAS;IAC7B,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,CAAS;IAChC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC9B,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"project-type.js","sourceRoot":"","sources":["../src/project-type.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-cache.d.ts","sourceRoot":"","sources":["../src/prompt-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"prompt-cache.d.ts","sourceRoot":"","sources":["../src/prompt-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAmDjE,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,aAAa,EAAE,CAiIvF"}
|
package/dist/prompt-cache.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { matchEvidence } from './evidence.js';
|
|
1
2
|
import * as path from 'node:path';
|
|
2
3
|
import * as crypto from 'node:crypto';
|
|
3
4
|
/**
|
|
@@ -10,33 +11,39 @@ function promptCacheIssueId(kind, filePath) {
|
|
|
10
11
|
return `prompt-cache-${kind}-${hash}`;
|
|
11
12
|
}
|
|
12
13
|
const DATE_PATTERNS = [
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
{
|
|
15
|
+
re: /\b(?:last\s+updated|last\s+modified|updated\s+at|modified\s+at|date)\b\s*:\s*\b\d{4}-\d{2}-\d{2}\b/i,
|
|
16
|
+
label: 'Dated "last updated"/"modified" line',
|
|
17
|
+
},
|
|
18
|
+
{ re: /\b\d{4}-\d{2}-\d{2}t\d{2}:\d{2}:\d{2}\b/i, label: 'ISO 8601 timestamp' },
|
|
19
|
+
{
|
|
20
|
+
re: /\b(?:january|february|march|april|may|june|july|august|september|october|november|december)\s+\d{1,2}(?:st|nd|rd|th)?,\s+\d{4}\b/i,
|
|
21
|
+
label: 'Long-form calendar date',
|
|
22
|
+
},
|
|
16
23
|
];
|
|
17
24
|
const SCAN_REPORT_PATTERNS = [
|
|
18
|
-
/health\s+score\s*:\s*\d+/i,
|
|
19
|
-
/files\s+scanned\s*:\s*\d+/i,
|
|
20
|
-
/promptci\s+report/i,
|
|
21
|
-
/\bgenerated\s+by\s+promptci\b/i,
|
|
22
|
-
/latest\s+scan\s+results/i,
|
|
23
|
-
/scan\s+history/i,
|
|
25
|
+
{ re: /health\s+score\s*:\s*\d+/i, label: 'Health-score line from a scan report' },
|
|
26
|
+
{ re: /files\s+scanned\s*:\s*\d+/i, label: 'File-count line from a scan report' },
|
|
27
|
+
{ re: /promptci\s+report/i, label: 'PromptCI report heading' },
|
|
28
|
+
{ re: /\bgenerated\s+by\s+promptci\b/i, label: 'PromptCI generated-by marker' },
|
|
29
|
+
{ re: /latest\s+scan\s+results/i, label: 'Pasted "latest scan results" section' },
|
|
30
|
+
{ re: /scan\s+history/i, label: 'Pasted "scan history" section' },
|
|
24
31
|
];
|
|
25
32
|
const BRANCH_TASK_PATTERNS = [
|
|
26
|
-
/current\s+branch\s*:\s*\S+/i,
|
|
27
|
-
/git\s+branch\s*:\s*\S+/i,
|
|
28
|
-
/current\s+task\s*:\s*\S+/i,
|
|
33
|
+
{ re: /current\s+branch\s*:\s*\S+/i, label: 'Current-branch line' },
|
|
34
|
+
{ re: /git\s+branch\s*:\s*\S+/i, label: 'Git-branch line' },
|
|
35
|
+
{ re: /current\s+task\s*:\s*\S+/i, label: 'Current-task line' },
|
|
29
36
|
];
|
|
30
37
|
const LOCAL_PATH_PATTERN = /(?:[a-zA-Z]:\\users\\[a-zA-Z0-9_-]+)|(?:\/users\/[a-zA-Z0-9_-]+\/)|(?:\/home\/(?!runner)[a-zA-Z0-9_-]+\/)/i;
|
|
31
38
|
export function detectPromptCacheFriendliness(files) {
|
|
32
39
|
const issues = [];
|
|
33
40
|
// Focus only on persistent, always-loaded instruction files
|
|
34
|
-
const targetFiles = files.filter((f) => ['claude', 'agents', 'cursor', 'copilot', 'prompt'].includes(f.fileType));
|
|
41
|
+
const targetFiles = files.filter((f) => ['claude', 'agents', 'cursor', 'windsurf', 'copilot', 'prompt'].includes(f.fileType));
|
|
35
42
|
for (const file of targetFiles) {
|
|
36
43
|
const fileName = path.basename(file.path);
|
|
37
44
|
// 1. Timestamps and Dates
|
|
38
45
|
for (const pat of DATE_PATTERNS) {
|
|
39
|
-
if (pat.test(file.content)) {
|
|
46
|
+
if (pat.re.test(file.content)) {
|
|
40
47
|
issues.push({
|
|
41
48
|
id: promptCacheIssueId('volatile-timestamp', file.path),
|
|
42
49
|
severity: 'warning',
|
|
@@ -45,7 +52,7 @@ export function detectPromptCacheFriendliness(files) {
|
|
|
45
52
|
summary: `The instruction file "${fileName}" contains a volatile date or timestamp. Frequently changing dates in persistent instruction files invalidate prompt caches.`,
|
|
46
53
|
filePaths: [file.path],
|
|
47
54
|
locations: [],
|
|
48
|
-
evidence: [
|
|
55
|
+
evidence: [matchEvidence(file.content, pat.re, pat.label)],
|
|
49
56
|
recommendation: 'Move volatile dates to a separate task-specific prompt or scratch file, or remove them from canonical instructions.',
|
|
50
57
|
fixRecipe: 'Remove the "Last updated: <date>" or timestamp line from the file.',
|
|
51
58
|
confidence: 0.85,
|
|
@@ -55,7 +62,7 @@ export function detectPromptCacheFriendliness(files) {
|
|
|
55
62
|
}
|
|
56
63
|
// 2. Pasted Scan Outputs/Reports
|
|
57
64
|
for (const pat of SCAN_REPORT_PATTERNS) {
|
|
58
|
-
if (pat.test(file.content)) {
|
|
65
|
+
if (pat.re.test(file.content)) {
|
|
59
66
|
issues.push({
|
|
60
67
|
id: promptCacheIssueId('pasted-report', file.path),
|
|
61
68
|
severity: 'warning',
|
|
@@ -64,7 +71,7 @@ export function detectPromptCacheFriendliness(files) {
|
|
|
64
71
|
summary: `The instruction file "${fileName}" contains pasted scan results or reports. Committing generated reports into canonical instruction files breaks prompt caching and bloats context.`,
|
|
65
72
|
filePaths: [file.path],
|
|
66
73
|
locations: [],
|
|
67
|
-
evidence: [
|
|
74
|
+
evidence: [matchEvidence(file.content, pat.re, pat.label)],
|
|
68
75
|
recommendation: 'Link to the generated report file or read it on-demand instead of pasting its contents into canonical instructions.',
|
|
69
76
|
fixRecipe: 'Delete the pasted report summary block and reference the report file path instead.',
|
|
70
77
|
confidence: 0.9,
|
|
@@ -74,7 +81,7 @@ export function detectPromptCacheFriendliness(files) {
|
|
|
74
81
|
}
|
|
75
82
|
// 3. Current Branch or Task status
|
|
76
83
|
for (const pat of BRANCH_TASK_PATTERNS) {
|
|
77
|
-
if (pat.test(file.content)) {
|
|
84
|
+
if (pat.re.test(file.content)) {
|
|
78
85
|
issues.push({
|
|
79
86
|
id: promptCacheIssueId('volatile-branch-task', file.path),
|
|
80
87
|
severity: 'warning',
|
|
@@ -83,7 +90,7 @@ export function detectPromptCacheFriendliness(files) {
|
|
|
83
90
|
summary: `The instruction file "${fileName}" contains active git branch or task details. Frequently updated task status or branch names break prompt caching.`,
|
|
84
91
|
filePaths: [file.path],
|
|
85
92
|
locations: [],
|
|
86
|
-
evidence: [
|
|
93
|
+
evidence: [matchEvidence(file.content, pat.re, pat.label)],
|
|
87
94
|
recommendation: 'Move branch-specific notes and active task details to git commit messages, PR descriptions, or a temporary scratch file.',
|
|
88
95
|
fixRecipe: 'Remove active branch and task notes from the persistent instructions.',
|
|
89
96
|
confidence: 0.85,
|
package/dist/prompt-cache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-cache.js","sourceRoot":"","sources":["../src/prompt-cache.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,IAAY,EAAE,QAAgB;IACxD,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,gBAAgB,IAAI,IAAI,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7G,OAAO,gBAAgB,IAAI,IAAI,IAAI,EAAE,CAAC;AACxC,CAAC;
|
|
1
|
+
{"version":3,"file":"prompt-cache.js","sourceRoot":"","sources":["../src/prompt-cache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,IAAY,EAAE,QAAgB;IACxD,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,gBAAgB,IAAI,IAAI,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC7G,OAAO,gBAAgB,IAAI,IAAI,IAAI,EAAE,CAAC;AACxC,CAAC;AASD,MAAM,aAAa,GAAsB;IACvC;QACE,EAAE,EAAE,qGAAqG;QACzG,KAAK,EAAE,sCAAsC;KAC9C;IACD,EAAE,EAAE,EAAE,0CAA0C,EAAE,KAAK,EAAE,oBAAoB,EAAE;IAC/E;QACE,EAAE,EAAE,mIAAmI;QACvI,KAAK,EAAE,yBAAyB;KACjC;CACF,CAAC;AAEF,MAAM,oBAAoB,GAAsB;IAC9C,EAAE,EAAE,EAAE,2BAA2B,EAAE,KAAK,EAAE,sCAAsC,EAAE;IAClF,EAAE,EAAE,EAAE,4BAA4B,EAAE,KAAK,EAAE,oCAAoC,EAAE;IACjF,EAAE,EAAE,EAAE,oBAAoB,EAAE,KAAK,EAAE,yBAAyB,EAAE;IAC9D,EAAE,EAAE,EAAE,gCAAgC,EAAE,KAAK,EAAE,8BAA8B,EAAE;IAC/E,EAAE,EAAE,EAAE,0BAA0B,EAAE,KAAK,EAAE,sCAAsC,EAAE;IACjF,EAAE,EAAE,EAAE,iBAAiB,EAAE,KAAK,EAAE,+BAA+B,EAAE;CAClE,CAAC;AAEF,MAAM,oBAAoB,GAAsB;IAC9C,EAAE,EAAE,EAAE,6BAA6B,EAAE,KAAK,EAAE,qBAAqB,EAAE;IACnE,EAAE,EAAE,EAAE,yBAAyB,EAAE,KAAK,EAAE,iBAAiB,EAAE;IAC3D,EAAE,EAAE,EAAE,2BAA2B,EAAE,KAAK,EAAE,mBAAmB,EAAE;CAChE,CAAC;AAEF,MAAM,kBAAkB,GAAG,4GAA4G,CAAC;AAExI,MAAM,UAAU,6BAA6B,CAAC,KAAwB;IACpE,MAAM,MAAM,GAAoB,EAAE,CAAC;IAEnC,4DAA4D;IAC5D,MAAM,WAAW,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CACrC,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CACrF,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,WAAW,EAAE,CAAC;QAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE1C,0BAA0B;QAC1B,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;YAChC,IAAI,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC9B,MAAM,CAAC,IAAI,CAAC;oBACV,EAAE,EAAE,kBAAkB,CAAC,oBAAoB,EAAE,IAAI,CAAC,IAAI,CAAC;oBACvD,QAAQ,EAAE,SAAS;oBACnB,QAAQ,EAAE,eAAe;oBACzB,KAAK,EAAE,wCAAwC;oBAC/C,OAAO,EAAE,yBAAyB,QAAQ,8HAA8H;oBACxK,SAAS,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;oBACtB,SAAS,EAAE,EAAE;oBACb,QAAQ,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;oBAC1D,cAAc,EAAE,qHAAqH;oBACrI,SAAS,EAAE,oEAAoE;oBAC/E,UAAU,EAAE,IAAI;iBACjB,CAAC,CAAC;gBACH,MAAM,CAAC,gBAAgB;YACzB,CAAC;QACH,CAAC;QAED,iCAAiC;QACjC,KAAK,MAAM,GAAG,IAAI,oBAAoB,EAAE,CAAC;YACvC,IAAI,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC9B,MAAM,CAAC,IAAI,CAAC;oBACV,EAAE,EAAE,kBAAkB,CAAC,eAAe,EAAE,IAAI,CAAC,IAAI,CAAC;oBAClD,QAAQ,EAAE,SAAS;oBACnB,QAAQ,EAAE,eAAe;oBACzB,KAAK,EAAE,+CAA+C;oBACtD,OAAO,EAAE,yBAAyB,QAAQ,oJAAoJ;oBAC9L,SAAS,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;oBACtB,SAAS,EAAE,EAAE;oBACb,QAAQ,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;oBAC1D,cAAc,EAAE,qHAAqH;oBACrI,SAAS,EAAE,oFAAoF;oBAC/F,UAAU,EAAE,GAAG;iBAChB,CAAC,CAAC;gBACH,MAAM;YACR,CAAC;QACH,CAAC;QAED,mCAAmC;QACnC,KAAK,MAAM,GAAG,IAAI,oBAAoB,EAAE,CAAC;YACvC,IAAI,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC9B,MAAM,CAAC,IAAI,CAAC;oBACV,EAAE,EAAE,kBAAkB,CAAC,sBAAsB,EAAE,IAAI,CAAC,IAAI,CAAC;oBACzD,QAAQ,EAAE,SAAS;oBACnB,QAAQ,EAAE,eAAe;oBACzB,KAAK,EAAE,gDAAgD;oBACvD,OAAO,EAAE,yBAAyB,QAAQ,oHAAoH;oBAC9J,SAAS,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;oBACtB,SAAS,EAAE,EAAE;oBACb,QAAQ,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;oBAC1D,cAAc,EAAE,0HAA0H;oBAC1I,SAAS,EAAE,uEAAuE;oBAClF,UAAU,EAAE,IAAI;iBACjB,CAAC,CAAC;gBACH,MAAM;YACR,CAAC;QACH,CAAC;QAED,sBAAsB;QACtB,IAAI,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAChE,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,kBAAkB,CAAC,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC;gBAChD,QAAQ,EAAE,SAAS;gBACnB,QAAQ,EAAE,eAAe;gBACzB,KAAK,EAAE,qCAAqC;gBAC5C,OAAO,EAAE,yBAAyB,QAAQ,6CAA6C,KAAK,kGAAkG;gBAC9L,SAAS,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;gBACtB,SAAS,EAAE,EAAE;gBACb,QAAQ,EAAE,CAAC,8BAA8B,KAAK,EAAE,CAAC;gBACjD,cAAc,EAAE,wEAAwE;gBACxF,SAAS,EAAE,8DAA8D;gBACzE,UAAU,EAAE,GAAG;aAChB,CAAC,CAAC;QACL,CAAC;QAED,sCAAsC;QACtC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,YAAY,GAAG,CAAC,CAAC;QACrB,IAAI,gBAAgB,GAAG,CAAC,CAAC;QACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrD,IAAI,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;oBACnC,SAAS;gBACX,CAAC;gBACD,gBAAgB,EAAE,CAAC;YACrB,CAAC;iBAAM,CAAC;gBACN,IAAI,gBAAgB,GAAG,YAAY,EAAE,CAAC;oBACpC,YAAY,GAAG,gBAAgB,CAAC;gBAClC,CAAC;gBACD,gBAAgB,GAAG,CAAC,CAAC;YACvB,CAAC;QACH,CAAC;QACD,IAAI,gBAAgB,GAAG,YAAY,EAAE,CAAC;YACpC,YAAY,GAAG,gBAAgB,CAAC;QAClC,CAAC;QAED,IAAI,YAAY,GAAG,EAAE,EAAE,CAAC;YACtB,MAAM,CAAC,IAAI,CAAC;gBACV,EAAE,EAAE,kBAAkB,CAAC,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC;gBAChD,QAAQ,EAAE,SAAS;gBACnB,QAAQ,EAAE,eAAe;gBACzB,KAAK,EAAE,iCAAiC;gBACxC,OAAO,EAAE,yBAAyB,QAAQ,sCAAsC,YAAY,4EAA4E;gBACxK,SAAS,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;gBACtB,SAAS,EAAE,EAAE;gBACb,QAAQ,EAAE,CAAC,uBAAuB,YAAY,kBAAkB,QAAQ,GAAG,CAAC;gBAC5E,cAAc,EAAE,8JAA8J;gBAC9K,SAAS,EAAE,0DAA0D;gBACrE,UAAU,EAAE,GAAG;aAChB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The recommended `.gitignore` stanza for the `.promptci/` directory.
|
|
3
|
+
*
|
|
4
|
+
* BUG-7: the old recommendation everywhere — `init`, the security-pack
|
|
5
|
+
* detector, the docs, and this repo's own .gitignore — was a bare
|
|
6
|
+
* `.promptci/`, which ignores the entire directory including
|
|
7
|
+
* `.promptci/baseline.json`. The CI ratchet (`--baseline` / `--fail-on-new`)
|
|
8
|
+
* only works when the team's agreed baseline is committed and readable in CI,
|
|
9
|
+
* so the recommended pattern was defeating the feature the directory exists to
|
|
10
|
+
* serve. `config.json` is carved out for the same reason: a per-repo config
|
|
11
|
+
* that only lives on one developer's machine is not a per-repo config.
|
|
12
|
+
*
|
|
13
|
+
* `custom-rules.json` is carved out on the same principle: custom rules must be
|
|
14
|
+
* committed for a local scan and the GitHub Action to see the same rules — a
|
|
15
|
+
* gitignored rule file would make the two disagree.
|
|
16
|
+
*
|
|
17
|
+
* Two details of the pattern are load-bearing:
|
|
18
|
+
*
|
|
19
|
+
* - `.promptci/*` rather than `.promptci/`. Git does not descend into an
|
|
20
|
+
* ignored *directory*, so with a trailing-slash pattern the `!` negations
|
|
21
|
+
* below would never be evaluated. Matching the directory's *contents*
|
|
22
|
+
* leaves them reachable.
|
|
23
|
+
* - The leading globstar segment. A pattern containing a slash is anchored
|
|
24
|
+
* to the .gitignore's own directory, so an unprefixed `.promptci/*` would
|
|
25
|
+
* stop covering nested checkouts that the old unanchored `.promptci/` did
|
|
26
|
+
* cover — in a monorepo, every sub-package's generated reports would start
|
|
27
|
+
* showing up as untracked files.
|
|
28
|
+
*/
|
|
29
|
+
export declare const PROMPTCI_GITIGNORE_LINES: readonly string[];
|
|
30
|
+
/** The same stanza as text, LF-terminated. Callers on CRLF files should re-join. */
|
|
31
|
+
export declare const PROMPTCI_GITIGNORE_BLOCK: string;
|
|
32
|
+
/** Renders the stanza with the given line ending, trailing terminator included. */
|
|
33
|
+
export declare function renderPromptCiGitignore(lineEnding: string): string;
|
|
34
|
+
//# sourceMappingURL=promptci-gitignore.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promptci-gitignore.d.ts","sourceRoot":"","sources":["../src/promptci-gitignore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,wBAAwB,EAAE,SAAS,MAAM,EAMrD,CAAC;AAEF,oFAAoF;AACpF,eAAO,MAAM,wBAAwB,EAAE,MAAmD,CAAC;AAE3F,mFAAmF;AACnF,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAElE"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The recommended `.gitignore` stanza for the `.promptci/` directory.
|
|
3
|
+
*
|
|
4
|
+
* BUG-7: the old recommendation everywhere — `init`, the security-pack
|
|
5
|
+
* detector, the docs, and this repo's own .gitignore — was a bare
|
|
6
|
+
* `.promptci/`, which ignores the entire directory including
|
|
7
|
+
* `.promptci/baseline.json`. The CI ratchet (`--baseline` / `--fail-on-new`)
|
|
8
|
+
* only works when the team's agreed baseline is committed and readable in CI,
|
|
9
|
+
* so the recommended pattern was defeating the feature the directory exists to
|
|
10
|
+
* serve. `config.json` is carved out for the same reason: a per-repo config
|
|
11
|
+
* that only lives on one developer's machine is not a per-repo config.
|
|
12
|
+
*
|
|
13
|
+
* `custom-rules.json` is carved out on the same principle: custom rules must be
|
|
14
|
+
* committed for a local scan and the GitHub Action to see the same rules — a
|
|
15
|
+
* gitignored rule file would make the two disagree.
|
|
16
|
+
*
|
|
17
|
+
* Two details of the pattern are load-bearing:
|
|
18
|
+
*
|
|
19
|
+
* - `.promptci/*` rather than `.promptci/`. Git does not descend into an
|
|
20
|
+
* ignored *directory*, so with a trailing-slash pattern the `!` negations
|
|
21
|
+
* below would never be evaluated. Matching the directory's *contents*
|
|
22
|
+
* leaves them reachable.
|
|
23
|
+
* - The leading globstar segment. A pattern containing a slash is anchored
|
|
24
|
+
* to the .gitignore's own directory, so an unprefixed `.promptci/*` would
|
|
25
|
+
* stop covering nested checkouts that the old unanchored `.promptci/` did
|
|
26
|
+
* cover — in a monorepo, every sub-package's generated reports would start
|
|
27
|
+
* showing up as untracked files.
|
|
28
|
+
*/
|
|
29
|
+
export const PROMPTCI_GITIGNORE_LINES = [
|
|
30
|
+
'# PromptCI: ignore generated reports, keep the shared baseline, config, and custom rules',
|
|
31
|
+
'**/.promptci/*',
|
|
32
|
+
'!**/.promptci/baseline.json',
|
|
33
|
+
'!**/.promptci/config.json',
|
|
34
|
+
'!**/.promptci/custom-rules.json',
|
|
35
|
+
];
|
|
36
|
+
/** The same stanza as text, LF-terminated. Callers on CRLF files should re-join. */
|
|
37
|
+
export const PROMPTCI_GITIGNORE_BLOCK = `${PROMPTCI_GITIGNORE_LINES.join('\n')}\n`;
|
|
38
|
+
/** Renders the stanza with the given line ending, trailing terminator included. */
|
|
39
|
+
export function renderPromptCiGitignore(lineEnding) {
|
|
40
|
+
return `${PROMPTCI_GITIGNORE_LINES.join(lineEnding)}${lineEnding}`;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=promptci-gitignore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"promptci-gitignore.js","sourceRoot":"","sources":["../src/promptci-gitignore.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAsB;IACzD,0FAA0F;IAC1F,gBAAgB;IAChB,6BAA6B;IAC7B,2BAA2B;IAC3B,iCAAiC;CAClC,CAAC;AAEF,oFAAoF;AACpF,MAAM,CAAC,MAAM,wBAAwB,GAAW,GAAG,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AAE3F,mFAAmF;AACnF,MAAM,UAAU,uBAAuB,CAAC,UAAkB;IACxD,OAAO,GAAG,wBAAwB,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,UAAU,EAAE,CAAC;AACrE,CAAC"}
|