@shrkcrft/inspector 0.1.0-alpha.31 → 0.1.0-alpha.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-task-prep.d.ts.map +1 -1
- package/dist/agent-task-prep.js +9 -1
- package/dist/codemod-assist.d.ts.map +1 -1
- package/dist/codemod-assist.js +3 -1
- package/dist/comment-syntax.d.ts +15 -0
- package/dist/comment-syntax.d.ts.map +1 -0
- package/dist/comment-syntax.js +15 -0
- package/dist/contribution-load-failures.d.ts +18 -2
- package/dist/contribution-load-failures.d.ts.map +1 -1
- package/dist/contribution-load-failures.js +20 -3
- package/dist/contributions-report.js +2 -2
- package/dist/convention-applicability.d.ts +46 -0
- package/dist/convention-applicability.d.ts.map +1 -0
- package/dist/convention-applicability.js +247 -0
- package/dist/convention-filter-level.d.ts +13 -0
- package/dist/convention-filter-level.d.ts.map +1 -0
- package/dist/convention-filter-level.js +13 -0
- package/dist/convention-registry.d.ts +29 -4
- package/dist/convention-registry.d.ts.map +1 -1
- package/dist/convention-registry.js +58 -23
- package/dist/custom-checks.d.ts.map +1 -1
- package/dist/custom-checks.js +6 -4
- package/dist/decision-records.d.ts +4 -0
- package/dist/decision-records.d.ts.map +1 -1
- package/dist/decision-records.js +165 -66
- package/dist/declared-reference-coverage.d.ts +1 -1
- package/dist/declared-reference-coverage.d.ts.map +1 -1
- package/dist/declared-reference-coverage.js +6 -1
- package/dist/feedback-ingestion.d.ts.map +1 -1
- package/dist/feedback-ingestion.js +7 -1
- package/dist/file-languages.d.ts +9 -0
- package/dist/file-languages.d.ts.map +1 -0
- package/dist/file-languages.js +91 -0
- package/dist/fix-preview.d.ts.map +1 -1
- package/dist/fix-preview.js +78 -17
- package/dist/fuzzy-impact.d.ts.map +1 -1
- package/dist/fuzzy-impact.js +2 -1
- package/dist/git-helpers.d.ts +29 -4
- package/dist/git-helpers.d.ts.map +1 -1
- package/dist/git-helpers.js +69 -10
- package/dist/i-contribution-file-validation.d.ts +8 -0
- package/dist/i-contribution-file-validation.d.ts.map +1 -1
- package/dist/i-convention-applicability-reason.d.ts +29 -0
- package/dist/i-convention-applicability-reason.d.ts.map +1 -0
- package/dist/i-convention-applicability-reason.js +1 -0
- package/dist/i-convention-applicability.d.ts +9 -0
- package/dist/i-convention-applicability.d.ts.map +1 -0
- package/dist/i-convention-applicability.js +1 -0
- package/dist/i-convention-scope.d.ts +19 -0
- package/dist/i-convention-scope.d.ts.map +1 -0
- package/dist/i-convention-scope.js +1 -0
- package/dist/i-file-language.d.ts +11 -0
- package/dist/i-file-language.d.ts.map +1 -0
- package/dist/i-file-language.js +1 -0
- package/dist/i-not-applicable-convention.d.ts +17 -0
- package/dist/i-not-applicable-convention.d.ts.map +1 -0
- package/dist/i-not-applicable-convention.js +1 -0
- package/dist/i-reference-pack-origin.d.ts +13 -0
- package/dist/i-reference-pack-origin.d.ts.map +1 -0
- package/dist/i-reference-pack-origin.js +1 -0
- package/dist/i-stale-path-hint-input.d.ts +18 -0
- package/dist/i-stale-path-hint-input.d.ts.map +1 -0
- package/dist/i-stale-path-hint-input.js +1 -0
- package/dist/i-unverifiable-remedy-group.d.ts +10 -0
- package/dist/i-unverifiable-remedy-group.d.ts.map +1 -0
- package/dist/i-unverifiable-remedy-group.js +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -0
- package/dist/knowledge-authoring.d.ts +1 -1
- package/dist/knowledge-authoring.d.ts.map +1 -1
- package/dist/knowledge-authoring.js +7 -6
- package/dist/knowledge-contribution-slots.d.ts +18 -0
- package/dist/knowledge-contribution-slots.d.ts.map +1 -0
- package/dist/knowledge-contribution-slots.js +25 -0
- package/dist/knowledge-entry-rejections.d.ts +17 -0
- package/dist/knowledge-entry-rejections.d.ts.map +1 -0
- package/dist/knowledge-entry-rejections.js +79 -0
- package/dist/knowledge-entry-verdict-record.d.ts +12 -0
- package/dist/knowledge-entry-verdict-record.d.ts.map +1 -1
- package/dist/knowledge-min-referenced-valve.d.ts +12 -0
- package/dist/knowledge-min-referenced-valve.d.ts.map +1 -0
- package/dist/knowledge-min-referenced-valve.js +12 -0
- package/dist/knowledge-rejected-entry.d.ts +28 -0
- package/dist/knowledge-rejected-entry.d.ts.map +1 -0
- package/dist/knowledge-rejected-entry.js +1 -0
- package/dist/knowledge-rename-command.d.ts +20 -0
- package/dist/knowledge-rename-command.d.ts.map +1 -0
- package/dist/knowledge-rename-command.js +34 -0
- package/dist/knowledge-rename-verb.d.ts +11 -0
- package/dist/knowledge-rename-verb.d.ts.map +1 -0
- package/dist/knowledge-rename-verb.js +11 -0
- package/dist/knowledge-rename.d.ts +1 -1
- package/dist/knowledge-rename.d.ts.map +1 -1
- package/dist/knowledge-rename.js +6 -5
- package/dist/knowledge-stale-gate.d.ts +2 -0
- package/dist/knowledge-stale-gate.d.ts.map +1 -1
- package/dist/knowledge-stale-gate.js +75 -3
- package/dist/knowledge-stale-quality-gate.d.ts.map +1 -1
- package/dist/knowledge-stale-quality-gate.js +34 -4
- package/dist/knowledge-stale.d.ts +10 -0
- package/dist/knowledge-stale.d.ts.map +1 -1
- package/dist/knowledge-stale.js +227 -33
- package/dist/knowledge-unverifiable-remedy.d.ts +56 -0
- package/dist/knowledge-unverifiable-remedy.d.ts.map +1 -0
- package/dist/knowledge-unverifiable-remedy.js +0 -0
- package/dist/pack-contributions-inventory.d.ts.map +1 -1
- package/dist/pack-contributions-inventory.js +4 -2
- package/dist/propose-knowledge.js +3 -2
- package/dist/reference-pack-origin.d.ts +18 -0
- package/dist/reference-pack-origin.d.ts.map +1 -0
- package/dist/reference-pack-origin.js +38 -0
- package/dist/repository-stats.d.ts.map +1 -1
- package/dist/repository-stats.js +3 -77
- package/dist/resolve-project-config.d.ts.map +1 -1
- package/dist/resolve-project-config.js +10 -1
- package/dist/self-config-doctor-v2.d.ts +1 -1
- package/dist/self-config-doctor-v2.d.ts.map +1 -1
- package/dist/self-config-doctor-v2.js +76 -12
- package/dist/self-config-doctor.d.ts.map +1 -1
- package/dist/self-config-doctor.js +2 -1
- package/dist/sharkcraft-inspector.d.ts.map +1 -1
- package/dist/sharkcraft-inspector.js +52 -12
- package/dist/stale-path-hint.d.ts +3 -0
- package/dist/stale-path-hint.d.ts.map +1 -0
- package/dist/stale-path-hint.js +47 -0
- package/dist/template-authoring.d.ts +1 -1
- package/dist/template-authoring.d.ts.map +1 -1
- package/dist/template-authoring.js +2 -1
- package/dist/validate-contribution-file.d.ts.map +1 -1
- package/dist/validate-contribution-file.js +15 -10
- package/dist/why-file.d.ts.map +1 -1
- package/dist/why-file.js +5 -4
- package/package.json +17 -17
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync } from 'node:fs';
|
|
7
7
|
import * as nodePath from 'node:path';
|
|
8
|
-
import { validateConvention, } from '@shrkcrft/plugin-api';
|
|
8
|
+
import { ConventionSeverity, validateConvention, } from '@shrkcrft/plugin-api';
|
|
9
|
+
import { conventionFilePath, conventionScope } from "./convention-applicability.js";
|
|
9
10
|
import { importModuleViaLoader, readContributionExport, RejectionCause, } from '@shrkcrft/core';
|
|
10
11
|
export const CONVENTION_REGISTRY_SCHEMA = 'sharkcraft.convention-registry/v1';
|
|
11
12
|
export var ConventionSource;
|
|
@@ -67,7 +68,7 @@ export async function loadConventions(inspection) {
|
|
|
67
68
|
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
68
69
|
for (const w of validateConvention(raw).warnings) {
|
|
69
70
|
issues.push({
|
|
70
|
-
severity:
|
|
71
|
+
severity: ConventionSeverity.Warning,
|
|
71
72
|
code: 'convention-shape',
|
|
72
73
|
message: `${w.field}: ${w.message}`,
|
|
73
74
|
conventionId: id,
|
|
@@ -78,7 +79,7 @@ export async function loadConventions(inspection) {
|
|
|
78
79
|
const reasons = conventionRejectionReasons(raw);
|
|
79
80
|
if (reasons.length > 0) {
|
|
80
81
|
for (const r of reasons) {
|
|
81
|
-
issues.push({ severity:
|
|
82
|
+
issues.push({ severity: ConventionSeverity.Error, code: 'invalid-convention', message: r, conventionId: id, source: sourceFile });
|
|
82
83
|
}
|
|
83
84
|
rejected.push({ ...at, ...(id !== undefined ? { entryId: id } : {}), reasons, cause: RejectionCause.Invalid });
|
|
84
85
|
return;
|
|
@@ -87,7 +88,7 @@ export async function loadConventions(inspection) {
|
|
|
87
88
|
const prev = seen.get(conv.id);
|
|
88
89
|
if (prev !== undefined) {
|
|
89
90
|
issues.push({
|
|
90
|
-
severity:
|
|
91
|
+
severity: ConventionSeverity.Error,
|
|
91
92
|
code: 'duplicate-id',
|
|
92
93
|
message: `Convention "${conv.id}" already loaded; skipping ${sourceFile}.`,
|
|
93
94
|
conventionId: conv.id,
|
|
@@ -124,7 +125,7 @@ export async function loadConventions(inspection) {
|
|
|
124
125
|
catch (e) {
|
|
125
126
|
unread.push(`${rel(file)} — failed to load`);
|
|
126
127
|
issues.push({
|
|
127
|
-
severity:
|
|
128
|
+
severity: ConventionSeverity.Warning,
|
|
128
129
|
code: 'load-failed',
|
|
129
130
|
message: `Failed to load ${file}: ${e.message}`,
|
|
130
131
|
source: file,
|
|
@@ -139,7 +140,7 @@ export async function loadConventions(inspection) {
|
|
|
139
140
|
if (!existsSync(file)) {
|
|
140
141
|
unread.push(`${rel(file)} — missing (declared by ${pack.packageName})`);
|
|
141
142
|
issues.push({
|
|
142
|
-
severity:
|
|
143
|
+
severity: ConventionSeverity.Warning,
|
|
143
144
|
code: 'missing-file',
|
|
144
145
|
message: `Pack ${pack.packageName} declares ${packRel} but file is missing.`,
|
|
145
146
|
source: file,
|
|
@@ -152,7 +153,7 @@ export async function loadConventions(inspection) {
|
|
|
152
153
|
catch (e) {
|
|
153
154
|
unread.push(`${rel(file)} — failed to load (${pack.packageName})`);
|
|
154
155
|
issues.push({
|
|
155
|
-
severity:
|
|
156
|
+
severity: ConventionSeverity.Warning,
|
|
156
157
|
code: 'load-failed',
|
|
157
158
|
message: `Pack ${pack.packageName} (${packRel}): ${e.message}`,
|
|
158
159
|
source: file,
|
|
@@ -174,26 +175,49 @@ export async function listConventionIssues(inspection) {
|
|
|
174
175
|
const { issues } = await loadConventions(inspection);
|
|
175
176
|
return issues;
|
|
176
177
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
178
|
+
/**
|
|
179
|
+
* Run the loaded conventions against `files`. Which files a convention covers
|
|
180
|
+
* is `conventionScope` — THE applicability authority (round 15), the same one
|
|
181
|
+
* the rule-graph bridge reads — so every `appliesTo` filter scopes, `**`
|
|
182
|
+
* spans zero or more segments, and a `!` glob subtracts (a private matcher
|
|
183
|
+
* here missed `src/a.ts` under `src/**\/*.ts` and ignored `!`). A convention
|
|
184
|
+
* that does not apply is never evaluated and is reported in `notApplicable`.
|
|
185
|
+
*
|
|
186
|
+
* Per covered file, each rule: `filePattern` and `expectMatch` must match the
|
|
187
|
+
* path (a miss is a hit), `forbidMatch` must not (a match is a hit).
|
|
188
|
+
*
|
|
189
|
+
* Every file is read in ONE spelling — `conventionFilePath` (project-relative,
|
|
190
|
+
* `/`-separated, no `./`), the one the scope test reads — so a rule pattern
|
|
191
|
+
* never tests a spelling the glob did not, and a hit names that path (round 15
|
|
192
|
+
* review: `./src/a.ts` was covered by `src/**` and then failed `^src/`).
|
|
193
|
+
*/
|
|
194
|
+
export async function checkConventionsAgainstFiles(inspection, inputFiles) {
|
|
195
|
+
const files = inputFiles.map((f) => conventionFilePath(inspection, f));
|
|
190
196
|
const entries = await listConventions(inspection);
|
|
191
197
|
const hits = [];
|
|
198
|
+
const notApplicable = [];
|
|
199
|
+
const filesInScope = {};
|
|
200
|
+
const covered = new Map();
|
|
201
|
+
for (const entry of entries) {
|
|
202
|
+
const c = entry.convention;
|
|
203
|
+
const scope = conventionScope(c, inspection, files);
|
|
204
|
+
if (!scope.applicable) {
|
|
205
|
+
notApplicable.push({
|
|
206
|
+
conventionId: c.id,
|
|
207
|
+
severity: c.severity,
|
|
208
|
+
sourceFile: entry.sourceFile,
|
|
209
|
+
...(entry.packageName ? { packageName: entry.packageName } : {}),
|
|
210
|
+
reasons: scope.reasons.filter((r) => !r.matched),
|
|
211
|
+
});
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
filesInScope[c.id] = scope.files.length;
|
|
215
|
+
covered.set(c.id, new Set(scope.files));
|
|
216
|
+
}
|
|
192
217
|
for (const f of files) {
|
|
193
218
|
for (const entry of entries) {
|
|
194
219
|
const c = entry.convention;
|
|
195
|
-
|
|
196
|
-
if (globs.length > 0 && !globs.some((g) => globMatch(f, g)))
|
|
220
|
+
if (!covered.get(c.id)?.has(f))
|
|
197
221
|
continue;
|
|
198
222
|
for (const r of c.rules) {
|
|
199
223
|
const sev = r.severity ?? c.severity;
|
|
@@ -206,6 +230,15 @@ export async function checkConventionsAgainstFiles(inspection, files) {
|
|
|
206
230
|
message: `File "${f}" does not match convention "${c.id}" rule "${r.id}": ${r.description}`,
|
|
207
231
|
});
|
|
208
232
|
}
|
|
233
|
+
if (r.expectMatch && !new RegExp(r.expectMatch).test(f)) {
|
|
234
|
+
hits.push({
|
|
235
|
+
conventionId: c.id,
|
|
236
|
+
ruleId: r.id,
|
|
237
|
+
file: f,
|
|
238
|
+
severity: sev,
|
|
239
|
+
message: `File "${f}" does not match the expected pattern /${r.expectMatch}/ of convention "${c.id}" rule "${r.id}": ${r.description}`,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
209
242
|
if (r.forbidMatch && new RegExp(r.forbidMatch).test(f)) {
|
|
210
243
|
hits.push({
|
|
211
244
|
conventionId: c.id,
|
|
@@ -222,6 +255,8 @@ export async function checkConventionsAgainstFiles(inspection, files) {
|
|
|
222
255
|
schema: 'sharkcraft.convention-check/v1',
|
|
223
256
|
filesScanned: files.length,
|
|
224
257
|
hits,
|
|
225
|
-
verdict: hits.some((h) => h.severity ===
|
|
258
|
+
verdict: hits.some((h) => h.severity === ConventionSeverity.Error) ? 'has-violations' : 'clean',
|
|
259
|
+
notApplicable,
|
|
260
|
+
filesInScope,
|
|
226
261
|
};
|
|
227
262
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom-checks.d.ts","sourceRoot":"","sources":["../src/custom-checks.ts"],"names":[],"mappings":"AA8BA,OAAO,
|
|
1
|
+
{"version":3,"file":"custom-checks.d.ts","sourceRoot":"","sources":["../src/custom-checks.ts"],"names":[],"mappings":"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"}
|
package/dist/custom-checks.js
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
import { spawnSync } from 'node:child_process';
|
|
29
29
|
import { existsSync, readFileSync } from 'node:fs';
|
|
30
30
|
import * as nodePath from 'node:path';
|
|
31
|
-
import { unsupportedFrontmatterKeys } from '@shrkcrft/knowledge';
|
|
31
|
+
import { KnowledgeSourceFormat, knowledgeSourceFormat, unsupportedFrontmatterKeys, } from '@shrkcrft/knowledge';
|
|
32
32
|
import { isRuleEntry } from '@shrkcrft/rules';
|
|
33
33
|
export const CUSTOM_CHECK_REPORT_SCHEMA = 'sharkcraft.custom-check/v1';
|
|
34
34
|
export const CUSTOM_CHECKS_REGISTRY_SCHEMA = 'sharkcraft.custom-checks-registry/v1';
|
|
@@ -138,8 +138,10 @@ export function buildCustomChecksRegistry(entries, options = {}) {
|
|
|
138
138
|
continue;
|
|
139
139
|
}
|
|
140
140
|
scannedRules += 1;
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
// THE source-format authority (round 15) — the one the stale-check remedy
|
|
142
|
+
// and the validator read, so "is this a Markdown entry?" has one answer.
|
|
143
|
+
if (knowledgeSourceFormat(e) === KnowledgeSourceFormat.Markdown) {
|
|
144
|
+
if (markdownDroppedChecks(e.source?.origin)) {
|
|
143
145
|
ignored.push({
|
|
144
146
|
entryId: e.id,
|
|
145
147
|
entryType: String(e.type),
|
|
@@ -241,7 +243,7 @@ export function customCheckScanSurface(inspection) {
|
|
|
241
243
|
add(nodePath.resolve(pack.packageRoot, rel), `pack ${pack.packageName} ${key}`);
|
|
242
244
|
}
|
|
243
245
|
}
|
|
244
|
-
const markdownRules = inspection.knowledgeEntries.filter((e) => isRuleEntry(e) && e
|
|
246
|
+
const markdownRules = inspection.knowledgeEntries.filter((e) => isRuleEntry(e) && knowledgeSourceFormat(e) === KnowledgeSourceFormat.Markdown).length;
|
|
245
247
|
return { files, markdownFiles, markdownRules };
|
|
246
248
|
}
|
|
247
249
|
export function doctorCustomChecks(registry) {
|
|
@@ -55,6 +55,10 @@ export declare function loadTsDecisions(inspection: ISharkcraftInspection): Prom
|
|
|
55
55
|
* {@link loadTsDecisions} with what did not take effect (round 12, 12.1): a
|
|
56
56
|
* decision file that failed to import (it used to be swallowed to `[]`), and
|
|
57
57
|
* every declared decision the loader refused — invalid, or a duplicate id.
|
|
58
|
+
* `rejected` also carries every Markdown record (`sharkcraft/decisions/*.md`,
|
|
59
|
+
* `docs/adr/*.md`) whose frontmatter THE parser cannot read as declared (round
|
|
60
|
+
* 15 follow-up, F6) — the same scan {@link listDecisions} reads, so a record is
|
|
61
|
+
* either listed or reported here, never neither.
|
|
58
62
|
*/
|
|
59
63
|
export declare function loadTsDecisionsWithIssues(inspection: ISharkcraftInspection): Promise<{
|
|
60
64
|
readonly decisions: readonly IDecisionRecord[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decision-records.d.ts","sourceRoot":"","sources":["../src/decision-records.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"decision-records.d.ts","sourceRoot":"","sources":["../src/decision-records.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AACvE,OAAO,EASL,KAAK,cAAc,EACpB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAE7E,eAAO,MAAM,sBAAsB,2BAA2B,CAAC;AAE/D,oBAAY,cAAc;IACxB,QAAQ,aAAa;IACrB,QAAQ,aAAa;IACrB,QAAQ,aAAa;IACrB,UAAU,eAAe;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,OAAO,sBAAsB,CAAC;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,cAAc,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,iBAAiB,EAAE,SAAS,MAAM,EAAE,CAAC;IACrC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,+FAA+F;IAC/F,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAyKD,wBAAgB,aAAa,CAAC,UAAU,EAAE,qBAAqB,GAAG,SAAS,eAAe,EAAE,CAmB3F;AA0BD;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,OAAO,GAAG,SAAS,MAAM,EAAE,CAIxE;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,qBAAqB,GAChC,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAErC;AAED;;;;;;;;GAQG;AACH,wBAAsB,yBAAyB,CAAC,UAAU,EAAE,qBAAqB,GAAG,OAAO,CAAC;IAC1F,QAAQ,CAAC,SAAS,EAAE,SAAS,eAAe,EAAE,CAAC;IAC/C,QAAQ,CAAC,MAAM,EAAE,SAAS,sBAAsB,EAAE,CAAC;IACnD,QAAQ,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAC;CAC9C,CAAC,CAuED;AAaD,wBAAgB,WAAW,CAAC,UAAU,EAAE,qBAAqB,EAAE,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAEtG;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,mBAAmB,GAAG,MAAM,CA8CvE;AAED,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAE/E"}
|
package/dist/decision-records.js
CHANGED
|
@@ -6,11 +6,20 @@
|
|
|
6
6
|
* these — `decisions new` produces a dry-run preview unless
|
|
7
7
|
* `--write-draft` is passed.
|
|
8
8
|
*
|
|
9
|
+
* The frontmatter is read by THE parser (`splitFrontmatter` +
|
|
10
|
+
* `parseFrontmatter`, @shrkcrft/core — round 15 follow-up, F6) in its `Text`
|
|
11
|
+
* scalar mode: a decision's `id` / `title` / `status` / `date` are strings by
|
|
12
|
+
* contract, read verbatim as the old line splitter read them (`id: 0001`
|
|
13
|
+
* stays `0001`, `title: Fix #12` keeps its `#12`). A record whose frontmatter
|
|
14
|
+
* cannot be read as declared is REJECTED through the round-12 channel
|
|
15
|
+
* ({@link loadTsDecisionsWithIssues} `rejected`) — never listed from a partial
|
|
16
|
+
* reading, never dropped silently.
|
|
17
|
+
*
|
|
9
18
|
* Read-only operations: list, get, link (preview).
|
|
10
19
|
*/
|
|
11
20
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
12
21
|
import * as nodePath from 'node:path';
|
|
13
|
-
import { importModuleViaLoader, RejectionCause } from '@shrkcrft/core';
|
|
22
|
+
import { formatFrontmatterScalar, FrontmatterScalarMode, importModuleViaLoader, parseFrontmatter, RejectionCause, splitFrontmatter, } from '@shrkcrft/core';
|
|
14
23
|
export const DECISION_RECORD_SCHEMA = 'sharkcraft.decision/v1';
|
|
15
24
|
export var DecisionStatus;
|
|
16
25
|
(function (DecisionStatus) {
|
|
@@ -20,20 +29,146 @@ export var DecisionStatus;
|
|
|
20
29
|
DecisionStatus["Superseded"] = "superseded";
|
|
21
30
|
})(DecisionStatus || (DecisionStatus = {}));
|
|
22
31
|
const DECISION_DIRS = ['sharkcraft/decisions', 'docs/adr'];
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
/**
|
|
33
|
+
* A decision's frontmatter values are strings by contract — read verbatim
|
|
34
|
+
* (`Text` scalars), exactly as {@link previewDecisionDraft} writes them. Only
|
|
35
|
+
* the four keys a record reads are parsed (`keys`): any other key — MADR's
|
|
36
|
+
* `decision makers:`, a `summary:` wrapped onto an indented line, a map nested
|
|
37
|
+
* two levels — is skipped unparsed. The old splitter never read those lines
|
|
38
|
+
* either, and YAML the parser does not speak under a key nobody reads must not
|
|
39
|
+
* cost the record (the Markdown knowledge loader skips a key it drops the same
|
|
40
|
+
* way). No key is read as a list (`listKeys: []`, round 15 closing A1): an
|
|
41
|
+
* inline `title: [WIP]` is the title `[WIP]`, verbatim, as the old splitter
|
|
42
|
+
* read it — it used to REJECT the record as "a list".
|
|
43
|
+
*/
|
|
44
|
+
const DECISION_FRONTMATTER = Object.freeze({
|
|
45
|
+
scalars: FrontmatterScalarMode.Text,
|
|
46
|
+
keys: Object.freeze(['id', 'title', 'status', 'date']),
|
|
47
|
+
listKeys: Object.freeze([]),
|
|
48
|
+
});
|
|
49
|
+
function shapeOf(value) {
|
|
50
|
+
if (!Array.isArray(value))
|
|
51
|
+
return 'a map';
|
|
52
|
+
return value.some((v) => v !== null && typeof v === 'object') ? 'a list of maps' : 'a list';
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Read one Markdown decision record through THE frontmatter parser. Frontmatter
|
|
56
|
+
* the parser refuses in its top-level structure (a stray line naming no key)
|
|
57
|
+
* or under a key the record reads (`title:` wrapped onto an indented line), or
|
|
58
|
+
* an `id` / `title` / `status` / `date` that is not a single value (a block
|
|
59
|
+
* list under `title:`; an inline `title: [WIP]` is text) REJECTS the record, every reason named: the
|
|
60
|
+
* old line splitter skipped such a line silently and let an indented ` id:`
|
|
61
|
+
* under any block overwrite the record's own id. A key the record does not
|
|
62
|
+
* read is never parsed ({@link DECISION_FRONTMATTER}).
|
|
63
|
+
*/
|
|
64
|
+
function readMarkdownDecision(full, fileName) {
|
|
65
|
+
const split = splitFrontmatter(readFileSync(full, 'utf8'));
|
|
66
|
+
const problems = [];
|
|
67
|
+
// A decision record opening with `---` and never closing it is broken
|
|
68
|
+
// frontmatter, not a thematic break: its `id:` would silently become the
|
|
69
|
+
// file name (the old splitter did exactly that).
|
|
70
|
+
if (split.unterminated)
|
|
71
|
+
problems.push('frontmatter: an opening --- line has no closing --- line');
|
|
72
|
+
let fields = {};
|
|
73
|
+
if (split.frontmatter !== undefined) {
|
|
74
|
+
const parsed = parseFrontmatter(split.frontmatter, { ...DECISION_FRONTMATTER, lineOffset: split.lineOffset });
|
|
75
|
+
if (parsed.ok)
|
|
76
|
+
fields = parsed.value;
|
|
77
|
+
else
|
|
78
|
+
problems.push(`frontmatter: ${parsed.error.message}`);
|
|
79
|
+
}
|
|
80
|
+
const text = (key) => {
|
|
81
|
+
const v = fields[key];
|
|
82
|
+
if (v === undefined || v === null)
|
|
83
|
+
return undefined;
|
|
84
|
+
if (typeof v === 'string')
|
|
85
|
+
return v;
|
|
86
|
+
if (typeof v === 'number' || typeof v === 'boolean')
|
|
87
|
+
return String(v);
|
|
88
|
+
problems.push(`${key}: must be a single value (got ${shapeOf(v)}) — quote it if it is text`);
|
|
89
|
+
return undefined;
|
|
90
|
+
};
|
|
91
|
+
const declaredId = text('id')?.trim();
|
|
92
|
+
const title = text('title');
|
|
93
|
+
const status = text('status');
|
|
94
|
+
const date = text('date');
|
|
95
|
+
if (problems.length > 0) {
|
|
96
|
+
return {
|
|
97
|
+
rejected: {
|
|
98
|
+
file: full,
|
|
99
|
+
index: -1,
|
|
100
|
+
...(declaredId ? { entryId: declaredId } : {}),
|
|
101
|
+
reasons: problems,
|
|
102
|
+
cause: RejectionCause.Invalid,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
// A key with no value (`id:`, `title:`, `status:`) is absent and falls back to
|
|
107
|
+
// its default — the old splitter read `id:` as the id "" and `title:` as the
|
|
108
|
+
// title "". An id that is empty after trimming falls back too: "" can never
|
|
109
|
+
// be referenced.
|
|
110
|
+
const id = declaredId || nodePath.basename(fileName, nodePath.extname(fileName)).trim();
|
|
111
|
+
const body = split.body;
|
|
112
|
+
return {
|
|
113
|
+
record: {
|
|
114
|
+
schema: DECISION_RECORD_SCHEMA,
|
|
115
|
+
id,
|
|
116
|
+
title: title ?? id,
|
|
117
|
+
status: status ?? DecisionStatus.Proposed,
|
|
118
|
+
context: sectionFromBody(body, 'Context'),
|
|
119
|
+
decision: sectionFromBody(body, 'Decision'),
|
|
120
|
+
consequences: sectionFromBody(body, 'Consequences'),
|
|
121
|
+
relatedRules: listLinesUnder(body, 'Related rules'),
|
|
122
|
+
relatedPolicies: listLinesUnder(body, 'Related policies'),
|
|
123
|
+
relatedConstructs: listLinesUnder(body, 'Related constructs'),
|
|
124
|
+
relatedFiles: listLinesUnder(body, 'Related files'),
|
|
125
|
+
date: date ?? '',
|
|
126
|
+
sourceFile: full,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Every Markdown decision record under {@link DECISION_DIRS}, in a
|
|
132
|
+
* deterministic order: the records read, and every file refused. THE one pass
|
|
133
|
+
* both {@link listDecisions} (records) and {@link loadTsDecisionsWithIssues}
|
|
134
|
+
* (rejected) read, so what is listed and what is reported cannot disagree.
|
|
135
|
+
*/
|
|
136
|
+
function scanMarkdownDecisions(projectRoot) {
|
|
137
|
+
const records = [];
|
|
138
|
+
const rejected = [];
|
|
139
|
+
for (const rel of DECISION_DIRS) {
|
|
140
|
+
const dir = nodePath.join(projectRoot, rel);
|
|
141
|
+
if (!existsSync(dir))
|
|
31
142
|
continue;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
143
|
+
let entries = [];
|
|
144
|
+
try {
|
|
145
|
+
// Sort so the decision-record list (and the doctor findings derived from
|
|
146
|
+
// it) is deterministic, not filesystem-order-dependent.
|
|
147
|
+
entries = readdirSync(dir).sort();
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
for (const f of entries) {
|
|
153
|
+
if (!/\.(md|markdown)$/i.test(f))
|
|
154
|
+
continue;
|
|
155
|
+
const full = nodePath.join(dir, f);
|
|
156
|
+
try {
|
|
157
|
+
const st = statSync(full);
|
|
158
|
+
if (!st.isFile())
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const read = readMarkdownDecision(full, f);
|
|
165
|
+
if ('record' in read)
|
|
166
|
+
records.push(read.record);
|
|
167
|
+
else
|
|
168
|
+
rejected.push(read.rejected);
|
|
169
|
+
}
|
|
35
170
|
}
|
|
36
|
-
return {
|
|
171
|
+
return { records, rejected };
|
|
37
172
|
}
|
|
38
173
|
function sectionFromBody(body, header) {
|
|
39
174
|
const lines = body.split('\n');
|
|
@@ -73,54 +208,10 @@ export function listDecisions(inspection) {
|
|
|
73
208
|
seenIds.add(rec.id);
|
|
74
209
|
out.push(rec);
|
|
75
210
|
};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
let entries = [];
|
|
81
|
-
try {
|
|
82
|
-
// Sort so the decision-record list (and the doctor findings derived from
|
|
83
|
-
// it) is deterministic, not filesystem-order-dependent.
|
|
84
|
-
entries = readdirSync(dir).sort();
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
for (const f of entries) {
|
|
90
|
-
if (!/\.(md|markdown)$/i.test(f))
|
|
91
|
-
continue;
|
|
92
|
-
const full = nodePath.join(dir, f);
|
|
93
|
-
try {
|
|
94
|
-
const st = statSync(full);
|
|
95
|
-
if (!st.isFile())
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
catch {
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
const text = readFileSync(full, 'utf8');
|
|
102
|
-
const { fm, body } = frontmatterAndBody(text);
|
|
103
|
-
const id = (fm['id'] ?? nodePath.basename(f, nodePath.extname(f))).trim();
|
|
104
|
-
const title = fm['title'] ?? id;
|
|
105
|
-
const status = fm['status'] ?? DecisionStatus.Proposed;
|
|
106
|
-
const date = fm['date'] ?? '';
|
|
107
|
-
addMd({
|
|
108
|
-
schema: DECISION_RECORD_SCHEMA,
|
|
109
|
-
id,
|
|
110
|
-
title,
|
|
111
|
-
status,
|
|
112
|
-
context: sectionFromBody(body, 'Context'),
|
|
113
|
-
decision: sectionFromBody(body, 'Decision'),
|
|
114
|
-
consequences: sectionFromBody(body, 'Consequences'),
|
|
115
|
-
relatedRules: listLinesUnder(body, 'Related rules'),
|
|
116
|
-
relatedPolicies: listLinesUnder(body, 'Related policies'),
|
|
117
|
-
relatedConstructs: listLinesUnder(body, 'Related constructs'),
|
|
118
|
-
relatedFiles: listLinesUnder(body, 'Related files'),
|
|
119
|
-
date,
|
|
120
|
-
sourceFile: full,
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
}
|
|
211
|
+
// A Markdown record whose frontmatter cannot be read as declared is not
|
|
212
|
+
// listed — `loadTsDecisionsWithIssues` reports it rejected (same scan).
|
|
213
|
+
for (const rec of scanMarkdownDecisions(inspection.projectRoot).records)
|
|
214
|
+
addMd(rec);
|
|
124
215
|
// Also include sync-cached TS decisions if present (loaded by
|
|
125
216
|
// listDecisionsTsCached on prior async warm-up). Best-effort sync read.
|
|
126
217
|
for (const r of getTsDecisionsCached(inspection.projectRoot)) {
|
|
@@ -158,11 +249,15 @@ export async function loadTsDecisions(inspection) {
|
|
|
158
249
|
* {@link loadTsDecisions} with what did not take effect (round 12, 12.1): a
|
|
159
250
|
* decision file that failed to import (it used to be swallowed to `[]`), and
|
|
160
251
|
* every declared decision the loader refused — invalid, or a duplicate id.
|
|
252
|
+
* `rejected` also carries every Markdown record (`sharkcraft/decisions/*.md`,
|
|
253
|
+
* `docs/adr/*.md`) whose frontmatter THE parser cannot read as declared (round
|
|
254
|
+
* 15 follow-up, F6) — the same scan {@link listDecisions} reads, so a record is
|
|
255
|
+
* either listed or reported here, never neither.
|
|
161
256
|
*/
|
|
162
257
|
export async function loadTsDecisionsWithIssues(inspection) {
|
|
163
258
|
const out = [];
|
|
164
259
|
const issues = [];
|
|
165
|
-
const rejected = [];
|
|
260
|
+
const rejected = [...scanMarkdownDecisions(inspection.projectRoot).rejected];
|
|
166
261
|
const seen = new Map();
|
|
167
262
|
const addInput = (raw, source, index) => {
|
|
168
263
|
const reasons = decisionRejectionReasons(raw);
|
|
@@ -248,12 +343,16 @@ export function getDecision(inspection, id) {
|
|
|
248
343
|
}
|
|
249
344
|
export function previewDecisionDraft(input) {
|
|
250
345
|
const id = input.id.trim();
|
|
346
|
+
// Each value is written so THE parser reads it back as written (round 15
|
|
347
|
+
// follow-up, F6): a raw `title: [WIP]` reads as a list, `title: "Quoted"` as
|
|
348
|
+
// `Quoted`. A value that already reads back stays bare.
|
|
349
|
+
const value = (v) => formatFrontmatterScalar(v, DECISION_FRONTMATTER);
|
|
251
350
|
const lines = [];
|
|
252
351
|
lines.push('---');
|
|
253
|
-
lines.push(`id: ${id}`);
|
|
254
|
-
lines.push(`title: ${input.title}`);
|
|
255
|
-
lines.push(`status: ${input.status ?? DecisionStatus.Proposed}`);
|
|
256
|
-
lines.push(`date: ${input.date ?? new Date().toISOString().slice(0, 10)}`);
|
|
352
|
+
lines.push(`id: ${value(id)}`);
|
|
353
|
+
lines.push(`title: ${value(input.title)}`);
|
|
354
|
+
lines.push(`status: ${value(input.status ?? DecisionStatus.Proposed)}`);
|
|
355
|
+
lines.push(`date: ${value(input.date ?? new Date().toISOString().slice(0, 10))}`);
|
|
257
356
|
lines.push('---');
|
|
258
357
|
lines.push('');
|
|
259
358
|
lines.push(`# ${input.title}`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { IVerdictCoverage } from '@shrkcrft/core';
|
|
2
2
|
import { type IKnowledgeAnchorCheck, type IKnowledgeReferenceCheck } from './knowledge-stale.js';
|
|
3
3
|
/** Why a malformed reference is unexamined — the words every surface prints. */
|
|
4
|
-
export declare const MALFORMED_REFERENCE_REASON = "malformed (a kind outside the vocabulary,
|
|
4
|
+
export declare const MALFORMED_REFERENCE_REASON = "malformed (a kind outside the vocabulary, a required field missing, a field of the wrong type, or a root that cannot resolve where it is declared), so never checked \u2014 each INVALID row names its problem, `shrk doctor` each knowledge entry\u2019s";
|
|
5
5
|
/**
|
|
6
6
|
* THE fold of declared references into one coverage record.
|
|
7
7
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"declared-reference-coverage.d.ts","sourceRoot":"","sources":["../src/declared-reference-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,EAGL,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC9B,MAAM,sBAAsB,CAAC;AAK9B,gFAAgF;AAChF,eAAO,MAAM,0BAA0B,
|
|
1
|
+
{"version":3,"file":"declared-reference-coverage.d.ts","sourceRoot":"","sources":["../src/declared-reference-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,EAGL,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC9B,MAAM,sBAAsB,CAAC;AAK9B,gFAAgF;AAChF,eAAO,MAAM,0BAA0B,8PAK4M,CAAC;AAEpP;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE;IAC/C,QAAQ,CAAC,UAAU,EAAE,SAAS,wBAAwB,EAAE,CAAC;IACzD,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,qBAAqB,EAAE,CAAC;CACrD,GAAG,gBAAgB,CA0BnB"}
|
|
@@ -3,7 +3,12 @@ import { isCheckableOutcome, ReferenceCheckOutcome, } from "./knowledge-stale.js
|
|
|
3
3
|
/** Unexamined labels carried on the coverage record (the full list is in the JSON). */
|
|
4
4
|
const LABEL_CAP = 20;
|
|
5
5
|
/** Why a malformed reference is unexamined — the words every surface prints. */
|
|
6
|
-
export const MALFORMED_REFERENCE_REASON =
|
|
6
|
+
export const MALFORMED_REFERENCE_REASON =
|
|
7
|
+
// A malformed reference is also one whose `root` cannot resolve where it is
|
|
8
|
+
// declared (`root: pack` on a local asset, an unknown root — round 15
|
|
9
|
+
// follow-up) or a field of the wrong type (`required: "yes"`): the INVALID
|
|
10
|
+
// row names the exact problem.
|
|
11
|
+
'malformed (a kind outside the vocabulary, a required field missing, a field of the wrong type, or a root that cannot resolve where it is declared), so never checked — each INVALID row names its problem, `shrk doctor` each knowledge entry’s';
|
|
7
12
|
/**
|
|
8
13
|
* THE fold of declared references into one coverage record.
|
|
9
14
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feedback-ingestion.d.ts","sourceRoot":"","sources":["../src/feedback-ingestion.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAyC,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC5F,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"feedback-ingestion.d.ts","sourceRoot":"","sources":["../src/feedback-ingestion.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
import { existsSync, readFileSync } from 'node:fs';
|
|
16
16
|
import * as nodePath from 'node:path';
|
|
17
17
|
import { importModuleViaLoader, RejectionCause } from '@shrkcrft/core';
|
|
18
|
+
import { knowledgeRenameCommand } from "./knowledge-rename-command.js";
|
|
19
|
+
import { KnowledgeRenameVerb } from "./knowledge-rename-verb.js";
|
|
18
20
|
export const FEEDBACK_INGESTION_SCHEMA = 'sharkcraft.feedback-ingestion/v1';
|
|
19
21
|
export var FeedbackBucket;
|
|
20
22
|
(function (FeedbackBucket) {
|
|
@@ -129,7 +131,11 @@ const KEYWORD_RULES = [
|
|
|
129
131
|
pattern: /\b(rename|move|moved|renamed)\b/i,
|
|
130
132
|
tags: ['rename'],
|
|
131
133
|
targetArea: 'knowledge-rename',
|
|
132
|
-
|
|
134
|
+
// THE rename-command helper: the previews are read-only and take no `--dry-run`.
|
|
135
|
+
suggestedCommands: [
|
|
136
|
+
knowledgeRenameCommand(KnowledgeRenameVerb.RenameFile),
|
|
137
|
+
knowledgeRenameCommand(KnowledgeRenameVerb.RenameSymbol),
|
|
138
|
+
],
|
|
133
139
|
},
|
|
134
140
|
{
|
|
135
141
|
pattern: /\b(trace|impact)\b/i,
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IFileLanguage } from './i-file-language.js';
|
|
2
|
+
export declare const FILE_LANGUAGES: readonly IFileLanguage[];
|
|
3
|
+
/** The language of `path` (absolute or project-relative): a basename override, else its extension; `null` when unknown. */
|
|
4
|
+
export declare function fileLanguageOf(path: string): IFileLanguage | null;
|
|
5
|
+
/** The language whose table row lists `extension` (`.ts` or `ts`), or `null`. */
|
|
6
|
+
export declare function fileLanguageForExtension(extension: string): IFileLanguage | null;
|
|
7
|
+
/** Every language id of the table, in table order — THE vocabulary `appliesTo.languages` may name. */
|
|
8
|
+
export declare function fileLanguageIds(): readonly string[];
|
|
9
|
+
//# sourceMappingURL=file-languages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-languages.d.ts","sourceRoot":"","sources":["../src/file-languages.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAE1D,eAAO,MAAM,cAAc,EAAE,SAAS,aAAa,EAsCvB,CAAC;AAiB7B,2HAA2H;AAC3H,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CASjE;AAED,iFAAiF;AACjF,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAGhF;AAED,sGAAsG;AACtG,wBAAgB,eAAe,IAAI,SAAS,MAAM,EAAE,CAEnD"}
|