@skill-map/cli 0.56.0 → 0.57.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/dist/cli/tutorial/sm-tutorial/references/part-cli.md +1 -1
- package/dist/cli.js +969 -912
- package/dist/index.js +120 -33
- package/dist/kernel/index.d.ts +139 -63
- package/dist/kernel/index.js +120 -33
- package/dist/migrations/001_initial.sql +45 -0
- package/dist/ui/{chunk-W3Z3CZL4.js → chunk-5MK5Y6FQ.js} +2 -2
- package/dist/ui/{chunk-GHOVZAAV.js → chunk-EEDP2D5A.js} +1 -1
- package/dist/ui/chunk-EL6RUV24.js +3 -0
- package/dist/ui/chunk-P3SSH5JE.js +1 -0
- package/dist/ui/{chunk-DWBJCNC7.js → chunk-PTNXJKIX.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/dist/ui/{main-PL3BEVQI.js → main-ZNXBC7R5.js} +3 -3
- package/migrations/001_initial.sql +45 -0
- package/package.json +2 -2
- package/dist/ui/chunk-4ITL7E6U.js +0 -1
- package/dist/ui/chunk-VUNP5KNI.js +0 -3
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// cli/entry.ts
|
|
2
2
|
|
|
3
|
-
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="
|
|
3
|
+
!function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="e6038423-ea97-5e66-b371-0916d382d819")}catch(e){}}();
|
|
4
4
|
import { existsSync as existsSync33 } from "fs";
|
|
5
5
|
import { Builtins, Cli as Cli2 } from "clipanion";
|
|
6
6
|
|
|
@@ -250,7 +250,7 @@ function bucketByKind(kind, instance, bag) {
|
|
|
250
250
|
// package.json
|
|
251
251
|
var package_default = {
|
|
252
252
|
name: "@skill-map/cli",
|
|
253
|
-
version: "0.
|
|
253
|
+
version: "0.57.0",
|
|
254
254
|
description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
|
|
255
255
|
license: "MIT",
|
|
256
256
|
type: "module",
|
|
@@ -1815,16 +1815,25 @@ function applyAjvFormats(ajv) {
|
|
|
1815
1815
|
addFormats(ajv);
|
|
1816
1816
|
}
|
|
1817
1817
|
|
|
1818
|
+
// kernel/util/finding-format.ts
|
|
1819
|
+
function formatFinding(parts) {
|
|
1820
|
+
const head = parts.subject ? `\`${parts.subject}\`:
|
|
1821
|
+
` : "";
|
|
1822
|
+
const loc = parts.lines && parts.lines.length > 0 ? `L${parts.lines.join(", ")}: ` : "";
|
|
1823
|
+
return `${head}${loc}${parts.body}`;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1818
1826
|
// plugins/core/analyzers/annotation-field-unknown/text.ts
|
|
1819
1827
|
var ANNOTATION_FIELD_UNKNOWN_TEXTS = {
|
|
1820
|
-
//
|
|
1821
|
-
//
|
|
1828
|
+
// Diagnosis bodies (`<what>; <why>`). The shared `formatFinding` helper
|
|
1829
|
+
// owns the subject (the offending key, built before the call); the
|
|
1830
|
+
// affected node is the finding's own node, so its path never appears.
|
|
1822
1831
|
/** Key inside `annotations:` is not in the curated catalog. */
|
|
1823
|
-
unknownAnnotationKey: "Unknown sidecar key
|
|
1832
|
+
unknownAnnotationKey: "Unknown sidecar key; not in the annotations catalog",
|
|
1824
1833
|
/** Top-level key is neither reserved, nor a registered plugin namespace, nor a registered root key. */
|
|
1825
|
-
unknownRootKey: "Unknown
|
|
1834
|
+
unknownRootKey: "Unknown top-level sidecar key; not a reserved block, plugin namespace, or root contribution",
|
|
1826
1835
|
/** Value under a registered plugin namespace fails the contributed schema. */
|
|
1827
|
-
pluginNamespaceInvalid: "Sidecar block
|
|
1836
|
+
pluginNamespaceInvalid: "Sidecar block fails the plugin schema; {{errors}}",
|
|
1828
1837
|
// Tooltips for the per-node view-contribution badges. Singular vs
|
|
1829
1838
|
// plural keeps the count grammar correct without a sub-template.
|
|
1830
1839
|
alertTooltipSingle: "This node has 1 unknown field in its sidecar. Open the inspector for details.",
|
|
@@ -1877,9 +1886,9 @@ var annotationFieldUnknownAnalyzer = {
|
|
|
1877
1886
|
analyzerId: ID9,
|
|
1878
1887
|
severity: "warn",
|
|
1879
1888
|
nodeIds: [node.path],
|
|
1880
|
-
message:
|
|
1881
|
-
|
|
1882
|
-
|
|
1889
|
+
message: formatFinding({
|
|
1890
|
+
subject: key,
|
|
1891
|
+
body: tx(ANNOTATION_FIELD_UNKNOWN_TEXTS.unknownAnnotationKey)
|
|
1883
1892
|
}),
|
|
1884
1893
|
data: { surface: "annotations", key }
|
|
1885
1894
|
});
|
|
@@ -1904,11 +1913,11 @@ var annotationFieldUnknownAnalyzer = {
|
|
|
1904
1913
|
analyzerId: ID9,
|
|
1905
1914
|
severity: "warn",
|
|
1906
1915
|
nodeIds: [node.path],
|
|
1907
|
-
message:
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1916
|
+
message: formatFinding({
|
|
1917
|
+
subject: `${key}.${contribKey}`,
|
|
1918
|
+
body: tx(ANNOTATION_FIELD_UNKNOWN_TEXTS.pluginNamespaceInvalid, {
|
|
1919
|
+
errors
|
|
1920
|
+
})
|
|
1912
1921
|
}),
|
|
1913
1922
|
data: { surface: "plugin-namespace", pluginId: key, key: contribKey }
|
|
1914
1923
|
});
|
|
@@ -1920,9 +1929,9 @@ var annotationFieldUnknownAnalyzer = {
|
|
|
1920
1929
|
analyzerId: ID9,
|
|
1921
1930
|
severity: "warn",
|
|
1922
1931
|
nodeIds: [node.path],
|
|
1923
|
-
message:
|
|
1924
|
-
|
|
1925
|
-
|
|
1932
|
+
message: formatFinding({
|
|
1933
|
+
subject: key,
|
|
1934
|
+
body: tx(ANNOTATION_FIELD_UNKNOWN_TEXTS.unknownRootKey)
|
|
1926
1935
|
}),
|
|
1927
1936
|
data: { surface: "root", key }
|
|
1928
1937
|
});
|
|
@@ -1979,12 +1988,15 @@ function collectPluginIds(contributions) {
|
|
|
1979
1988
|
// plugins/core/analyzers/annotation-orphan/text.ts
|
|
1980
1989
|
var ANNOTATION_ORPHAN_TEXTS = {
|
|
1981
1990
|
/**
|
|
1982
|
-
*
|
|
1983
|
-
*
|
|
1984
|
-
*
|
|
1985
|
-
*
|
|
1991
|
+
* Diagnosis body (`<what>; <why>`). The shared `formatFinding` helper
|
|
1992
|
+
* wraps it with the backtick subject (the orphan sidecar file); the
|
|
1993
|
+
* expected markdown path IS the finding's `nodeIds[0]`, so it never
|
|
1994
|
+
* appears in the message. The remediation hint moves to
|
|
1995
|
+
* `Issue.fix.summary` below.
|
|
1986
1996
|
*/
|
|
1987
|
-
message: "
|
|
1997
|
+
message: "Orphan sidecar; no matching markdown node",
|
|
1998
|
+
/** Remediation hint surfaced via `Issue.fix.summary`. */
|
|
1999
|
+
fixSummary: "Run `sm sidecar prune` to remove orphan sidecars."
|
|
1988
2000
|
};
|
|
1989
2001
|
|
|
1990
2002
|
// plugins/core/analyzers/annotation-orphan/index.ts
|
|
@@ -2005,10 +2017,11 @@ var annotationOrphanAnalyzer = {
|
|
|
2005
2017
|
analyzerId: ID10,
|
|
2006
2018
|
severity: "warn",
|
|
2007
2019
|
nodeIds: [expectedMdRelative],
|
|
2008
|
-
message:
|
|
2009
|
-
|
|
2010
|
-
|
|
2020
|
+
message: formatFinding({
|
|
2021
|
+
subject: orphan.relativePath,
|
|
2022
|
+
body: tx(ANNOTATION_ORPHAN_TEXTS.message)
|
|
2011
2023
|
}),
|
|
2024
|
+
fix: { summary: tx(ANNOTATION_ORPHAN_TEXTS.fixSummary) },
|
|
2012
2025
|
data: {
|
|
2013
2026
|
sidecarPath: orphan.relativePath,
|
|
2014
2027
|
expectedMdPath: orphan.expectedMdPath
|
|
@@ -2021,14 +2034,17 @@ var annotationOrphanAnalyzer = {
|
|
|
2021
2034
|
|
|
2022
2035
|
// plugins/core/analyzers/annotation-stale/text.ts
|
|
2023
2036
|
var ANNOTATION_STALE_TEXTS = {
|
|
2024
|
-
//
|
|
2025
|
-
//
|
|
2037
|
+
// Diagnosis bodies (`<what>; <why>`). The shared `formatFinding` helper
|
|
2038
|
+
// emits no subject (the affected node IS the finding's own node); the
|
|
2039
|
+
// remediation hint moves to `Issue.fix.summary` below.
|
|
2026
2040
|
/** body changed since last bump */
|
|
2027
|
-
bodyDrift: "Sidecar
|
|
2041
|
+
bodyDrift: "Sidecar stale; body changed since last bump",
|
|
2028
2042
|
/** frontmatter changed since last bump */
|
|
2029
|
-
frontmatterDrift: "Sidecar
|
|
2043
|
+
frontmatterDrift: "Sidecar stale; frontmatter changed since last bump",
|
|
2030
2044
|
/** both body and frontmatter changed */
|
|
2031
|
-
bothDrift: "Sidecar
|
|
2045
|
+
bothDrift: "Sidecar stale; body and frontmatter changed since last bump",
|
|
2046
|
+
/** Remediation hint surfaced via `Issue.fix.summary`. */
|
|
2047
|
+
fixSummary: "Run `sm bump <path>` to refresh the sidecar.",
|
|
2032
2048
|
// Tooltips for the `card.footer.right` clock chip emitted alongside
|
|
2033
2049
|
// the issue. Lists only the drifted face(s), in-sync faces are
|
|
2034
2050
|
// omitted so the operator immediately sees what's modified without
|
|
@@ -2073,7 +2089,8 @@ var annotationStaleAnalyzer = {
|
|
|
2073
2089
|
analyzerId: ID11,
|
|
2074
2090
|
severity: "info",
|
|
2075
2091
|
nodeIds: [node.path],
|
|
2076
|
-
message: messageFor(status
|
|
2092
|
+
message: formatFinding({ body: messageFor(status) }),
|
|
2093
|
+
fix: { summary: tx(ANNOTATION_STALE_TEXTS.fixSummary) },
|
|
2077
2094
|
data: { status }
|
|
2078
2095
|
});
|
|
2079
2096
|
ctx.emitContribution(node.path, staleIcon, {
|
|
@@ -2093,14 +2110,14 @@ function staleStatus(overlay) {
|
|
|
2093
2110
|
if (status === void 0 || status === null || status === "fresh") return null;
|
|
2094
2111
|
return status;
|
|
2095
2112
|
}
|
|
2096
|
-
function messageFor(status
|
|
2113
|
+
function messageFor(status) {
|
|
2097
2114
|
switch (status) {
|
|
2098
2115
|
case "stale-body":
|
|
2099
|
-
return tx(ANNOTATION_STALE_TEXTS.bodyDrift
|
|
2116
|
+
return tx(ANNOTATION_STALE_TEXTS.bodyDrift);
|
|
2100
2117
|
case "stale-frontmatter":
|
|
2101
|
-
return tx(ANNOTATION_STALE_TEXTS.frontmatterDrift
|
|
2118
|
+
return tx(ANNOTATION_STALE_TEXTS.frontmatterDrift);
|
|
2102
2119
|
case "stale-both":
|
|
2103
|
-
return tx(ANNOTATION_STALE_TEXTS.bothDrift
|
|
2120
|
+
return tx(ANNOTATION_STALE_TEXTS.bothDrift);
|
|
2104
2121
|
}
|
|
2105
2122
|
}
|
|
2106
2123
|
function tooltipFor(status) {
|
|
@@ -2127,6 +2144,92 @@ var contributionOrphanAnalyzer = {
|
|
|
2127
2144
|
}
|
|
2128
2145
|
};
|
|
2129
2146
|
|
|
2147
|
+
// plugins/core/analyzers/extractor-collision/text.ts
|
|
2148
|
+
var EXTRACTOR_COLLISION_TEXTS = {
|
|
2149
|
+
/**
|
|
2150
|
+
* Per-Signal warn issue: two extractors detected something at
|
|
2151
|
+
* overlapping byte ranges within the same node and the resolver
|
|
2152
|
+
* dropped the loser. Surfaces WHO lost, WHO won, and the tiebreak
|
|
2153
|
+
* reason so the operator can understand why a candidate edge did NOT
|
|
2154
|
+
* become a Link (e.g. a `[link](path)` with `@path` inside the bracket
|
|
2155
|
+
* text: markdown-link wins and the at-directive silently disappears
|
|
2156
|
+
* without this warning).
|
|
2157
|
+
*/
|
|
2158
|
+
message: "Overlap collision; {{loserExtractor}} (at {{loserRange}}) lost to {{winnerExtractor}} (at {{winnerRange}}) by {{reason}}, only the winning edge persists",
|
|
2159
|
+
/**
|
|
2160
|
+
* Remediation hint for the range-overlap rejection, surfaced via
|
|
2161
|
+
* `Issue.fix.summary`. Not autofixable: the rule cannot tell which
|
|
2162
|
+
* detection the author meant, so it offers the two resolutions
|
|
2163
|
+
* (rephrase one token, or accept the winner).
|
|
2164
|
+
*/
|
|
2165
|
+
rejectedFixSummary: "Rephrase one of the overlapping tokens, or accept the winner."
|
|
2166
|
+
};
|
|
2167
|
+
|
|
2168
|
+
// plugins/core/analyzers/extractor-collision/index.ts
|
|
2169
|
+
var ID13 = "extractor-collision";
|
|
2170
|
+
function signalLines(signal) {
|
|
2171
|
+
return signal.range && typeof signal.range.line === "number" ? [signal.range.line] : void 0;
|
|
2172
|
+
}
|
|
2173
|
+
var extractorCollisionAnalyzer = {
|
|
2174
|
+
id: ID13,
|
|
2175
|
+
pluginId: CORE_PLUGIN_ID,
|
|
2176
|
+
kind: "analyzer",
|
|
2177
|
+
description: "Reports when two extractors detect something at the same span of body text and the resolver drops one.",
|
|
2178
|
+
mode: "deterministic",
|
|
2179
|
+
evaluate(ctx) {
|
|
2180
|
+
const signals = ctx.signals;
|
|
2181
|
+
if (!signals || signals.length === 0) return [];
|
|
2182
|
+
const issues = [];
|
|
2183
|
+
for (const signal of signals) {
|
|
2184
|
+
const issue = makeIssue(signal);
|
|
2185
|
+
if (issue) issues.push(issue);
|
|
2186
|
+
}
|
|
2187
|
+
return issues;
|
|
2188
|
+
}
|
|
2189
|
+
};
|
|
2190
|
+
function makeIssue(signal) {
|
|
2191
|
+
const resolution = signal.resolution;
|
|
2192
|
+
if (!resolution || resolution.outcome !== "rejected" || !resolution.rejectedBy) return null;
|
|
2193
|
+
const winner = resolution.rejectedBy;
|
|
2194
|
+
const loserCandidate = signal.candidates[resolution.winnerIndex ?? 0];
|
|
2195
|
+
const loserRange = signal.range ? `${signal.range.start}-${signal.range.end}` : "unknown";
|
|
2196
|
+
const winnerRange = `${winner.range.start}-${winner.range.end}`;
|
|
2197
|
+
return {
|
|
2198
|
+
analyzerId: ID13,
|
|
2199
|
+
severity: "warn",
|
|
2200
|
+
nodeIds: [signal.source],
|
|
2201
|
+
message: formatFinding({
|
|
2202
|
+
subject: signal.raw,
|
|
2203
|
+
lines: signalLines(signal),
|
|
2204
|
+
body: tx(EXTRACTOR_COLLISION_TEXTS.message, {
|
|
2205
|
+
loserExtractor: loserCandidate.extractorId,
|
|
2206
|
+
loserRange,
|
|
2207
|
+
winnerExtractor: winner.extractorId,
|
|
2208
|
+
winnerRange,
|
|
2209
|
+
reason: winner.reason
|
|
2210
|
+
})
|
|
2211
|
+
}),
|
|
2212
|
+
fix: { summary: tx(EXTRACTOR_COLLISION_TEXTS.rejectedFixSummary) },
|
|
2213
|
+
data: {
|
|
2214
|
+
loser: {
|
|
2215
|
+
extractorId: loserCandidate.extractorId,
|
|
2216
|
+
raw: signal.raw,
|
|
2217
|
+
range: signal.range ?? null,
|
|
2218
|
+
candidate: {
|
|
2219
|
+
kind: loserCandidate.kind,
|
|
2220
|
+
target: loserCandidate.target,
|
|
2221
|
+
confidence: loserCandidate.confidence
|
|
2222
|
+
}
|
|
2223
|
+
},
|
|
2224
|
+
winner: {
|
|
2225
|
+
extractorId: winner.extractorId,
|
|
2226
|
+
range: winner.range
|
|
2227
|
+
},
|
|
2228
|
+
reason: winner.reason
|
|
2229
|
+
}
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2130
2233
|
// plugins/core/analyzers/issue-counter/text.ts
|
|
2131
2234
|
var ISSUE_COUNTER_TEXTS = {
|
|
2132
2235
|
errorTooltipSingle: "1 error",
|
|
@@ -2136,7 +2239,7 @@ var ISSUE_COUNTER_TEXTS = {
|
|
|
2136
2239
|
};
|
|
2137
2240
|
|
|
2138
2241
|
// plugins/core/analyzers/issue-counter/index.ts
|
|
2139
|
-
var
|
|
2242
|
+
var ID14 = "issue-counter";
|
|
2140
2243
|
var warnCount = {
|
|
2141
2244
|
slot: "card.footer.right",
|
|
2142
2245
|
icon: "pi-exclamation-triangle",
|
|
@@ -2172,7 +2275,7 @@ function emitTierChips(ctx, ref, severity, counts, singleTooltip, manyTooltip) {
|
|
|
2172
2275
|
}
|
|
2173
2276
|
}
|
|
2174
2277
|
var issueCounterAnalyzer = {
|
|
2175
|
-
id:
|
|
2278
|
+
id: ID14,
|
|
2176
2279
|
pluginId: CORE_PLUGIN_ID,
|
|
2177
2280
|
kind: "analyzer",
|
|
2178
2281
|
description: "Emits one aggregate severity chip per node (error + warn counts) from the live issue accumulator.",
|
|
@@ -2203,121 +2306,6 @@ var issueCounterAnalyzer = {
|
|
|
2203
2306
|
}
|
|
2204
2307
|
};
|
|
2205
2308
|
|
|
2206
|
-
// plugins/core/analyzers/job-file-orphan/text.ts
|
|
2207
|
-
var JOB_FILE_ORPHAN_TEXTS = {
|
|
2208
|
-
/**
|
|
2209
|
-
* `<path>.md` lives under `.skill-map/jobs/` but no `state_jobs.filePath`
|
|
2210
|
-
* row references it. Compact finding grammar: the file IS the
|
|
2211
|
-
* finding's own node, so its path never appears in the message.
|
|
2212
|
-
*/
|
|
2213
|
-
message: "Orphan job file; not referenced by any job. Run `sm job prune --orphan-files` to remove it."
|
|
2214
|
-
};
|
|
2215
|
-
|
|
2216
|
-
// plugins/core/analyzers/job-file-orphan/index.ts
|
|
2217
|
-
var ID14 = "job-file-orphan";
|
|
2218
|
-
var jobFileOrphanAnalyzer = {
|
|
2219
|
-
id: ID14,
|
|
2220
|
-
pluginId: CORE_PLUGIN_ID,
|
|
2221
|
-
kind: "analyzer",
|
|
2222
|
-
description: "Flags leftover job result files (no live job references them). Clean up via `sm job prune --orphan-files`.",
|
|
2223
|
-
mode: "deterministic",
|
|
2224
|
-
evaluate(ctx) {
|
|
2225
|
-
const orphans = ctx.orphanJobFiles;
|
|
2226
|
-
if (!orphans || orphans.length === 0) return [];
|
|
2227
|
-
const issues = [];
|
|
2228
|
-
for (const filePath of orphans) {
|
|
2229
|
-
issues.push({
|
|
2230
|
-
analyzerId: ID14,
|
|
2231
|
-
severity: "warn",
|
|
2232
|
-
nodeIds: [filePath],
|
|
2233
|
-
message: tx(JOB_FILE_ORPHAN_TEXTS.message, { filePath }),
|
|
2234
|
-
data: { filePath }
|
|
2235
|
-
});
|
|
2236
|
-
}
|
|
2237
|
-
return issues;
|
|
2238
|
-
}
|
|
2239
|
-
};
|
|
2240
|
-
|
|
2241
|
-
// plugins/core/analyzers/link-conflict/text.ts
|
|
2242
|
-
var LINK_CONFLICT_TEXTS = {
|
|
2243
|
-
/**
|
|
2244
|
-
* Compact finding grammar: line 1 = the disputed target, line 2 =
|
|
2245
|
-
* the short diagnosis. The source is the finding's own node, so it
|
|
2246
|
-
* never appears in the message.
|
|
2247
|
-
*/
|
|
2248
|
-
message: "{{target}}:\nDetectors disagree on link kind ({{kindList}})."
|
|
2249
|
-
};
|
|
2250
|
-
|
|
2251
|
-
// plugins/core/analyzers/link-conflict/index.ts
|
|
2252
|
-
var ID15 = "link-conflict";
|
|
2253
|
-
var NON_CONFLICTING_KINDS = /* @__PURE__ */ new Set(["points"]);
|
|
2254
|
-
var linkConflictAnalyzer = {
|
|
2255
|
-
id: ID15,
|
|
2256
|
-
pluginId: CORE_PLUGIN_ID,
|
|
2257
|
-
kind: "analyzer",
|
|
2258
|
-
description: "Flags conflicting arrow meanings between extractors (e.g. `references` vs `invokes`).",
|
|
2259
|
-
mode: "deterministic",
|
|
2260
|
-
// Bucket links by (source, target), then per-bucket detect distinct
|
|
2261
|
-
// kinds. The branching is intrinsic to the per-bucket conflict
|
|
2262
|
-
// detection.
|
|
2263
|
-
// eslint-disable-next-line complexity
|
|
2264
|
-
evaluate(ctx) {
|
|
2265
|
-
const groups = /* @__PURE__ */ new Map();
|
|
2266
|
-
for (const link of ctx.links) {
|
|
2267
|
-
if (NON_CONFLICTING_KINDS.has(link.kind)) continue;
|
|
2268
|
-
const key = `${link.source}\0${link.target}`;
|
|
2269
|
-
const bucket = groups.get(key);
|
|
2270
|
-
if (bucket) bucket.push(link);
|
|
2271
|
-
else groups.set(key, [link]);
|
|
2272
|
-
}
|
|
2273
|
-
const issues = [];
|
|
2274
|
-
for (const [key, links] of groups) {
|
|
2275
|
-
if (links.length < 2) continue;
|
|
2276
|
-
const kinds = new Set(links.map((l) => l.kind));
|
|
2277
|
-
if (kinds.size < 2) continue;
|
|
2278
|
-
const variantByKind = /* @__PURE__ */ new Map();
|
|
2279
|
-
for (const link of links) {
|
|
2280
|
-
const existing = variantByKind.get(link.kind);
|
|
2281
|
-
if (existing) {
|
|
2282
|
-
for (const src of link.sources) {
|
|
2283
|
-
if (!existing.sources.includes(src)) existing.sources.push(src);
|
|
2284
|
-
}
|
|
2285
|
-
if (rankConfidence(link.confidence) > rankConfidence(existing.confidence)) {
|
|
2286
|
-
existing.confidence = link.confidence;
|
|
2287
|
-
}
|
|
2288
|
-
} else {
|
|
2289
|
-
variantByKind.set(link.kind, {
|
|
2290
|
-
kind: link.kind,
|
|
2291
|
-
sources: [...link.sources],
|
|
2292
|
-
confidence: link.confidence
|
|
2293
|
-
});
|
|
2294
|
-
}
|
|
2295
|
-
}
|
|
2296
|
-
for (const v of variantByKind.values()) v.sources.sort();
|
|
2297
|
-
const variants = [...variantByKind.values()].sort(
|
|
2298
|
-
(a, b) => a.kind.localeCompare(b.kind)
|
|
2299
|
-
);
|
|
2300
|
-
const [source, target] = key.split("\0");
|
|
2301
|
-
const kindList = variants.map((v) => v.kind).join(" / ");
|
|
2302
|
-
issues.push({
|
|
2303
|
-
analyzerId: ID15,
|
|
2304
|
-
severity: "warn",
|
|
2305
|
-
nodeIds: [source, target],
|
|
2306
|
-
message: tx(LINK_CONFLICT_TEXTS.message, {
|
|
2307
|
-
source,
|
|
2308
|
-
target,
|
|
2309
|
-
kindList
|
|
2310
|
-
}),
|
|
2311
|
-
data: { source, target, variants }
|
|
2312
|
-
});
|
|
2313
|
-
}
|
|
2314
|
-
return issues;
|
|
2315
|
-
}
|
|
2316
|
-
};
|
|
2317
|
-
function rankConfidence(c) {
|
|
2318
|
-
return c;
|
|
2319
|
-
}
|
|
2320
|
-
|
|
2321
2309
|
// kernel/util/link-lines.ts
|
|
2322
2310
|
function isSelfLoop(link) {
|
|
2323
2311
|
if (link.source === link.target) return true;
|
|
@@ -2336,32 +2324,37 @@ function linkLines(link) {
|
|
|
2336
2324
|
}
|
|
2337
2325
|
return [...lines].sort((a, b) => a - b);
|
|
2338
2326
|
}
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2327
|
+
|
|
2328
|
+
// plugins/core/analyzers/link-counter/text.ts
|
|
2329
|
+
var LINK_COUNTER_TEXTS = {
|
|
2330
|
+
/** Accessible label for the incoming-links chip. */
|
|
2331
|
+
linksInLabel: "incoming links",
|
|
2332
|
+
/** Accessible label for the outgoing-links chip. */
|
|
2333
|
+
linksOutLabel: "outgoing links",
|
|
2334
|
+
/** Tooltip header for the incoming breakdown (first line). */
|
|
2335
|
+
directionIn: "in",
|
|
2336
|
+
/** Tooltip header for the outgoing breakdown (first line). */
|
|
2337
|
+
directionOut: "out"
|
|
2338
|
+
};
|
|
2346
2339
|
|
|
2347
2340
|
// plugins/core/analyzers/link-counter/index.ts
|
|
2348
|
-
var
|
|
2341
|
+
var ID15 = "link-counter";
|
|
2349
2342
|
var linksIn = {
|
|
2350
2343
|
slot: "card.footer.left",
|
|
2351
2344
|
icon: "pi-download",
|
|
2352
|
-
label:
|
|
2345
|
+
label: LINK_COUNTER_TEXTS.linksInLabel,
|
|
2353
2346
|
emitWhenEmpty: false,
|
|
2354
2347
|
priority: 10
|
|
2355
2348
|
};
|
|
2356
2349
|
var linksOut = {
|
|
2357
2350
|
slot: "card.footer.left",
|
|
2358
2351
|
icon: "pi-upload",
|
|
2359
|
-
label:
|
|
2352
|
+
label: LINK_COUNTER_TEXTS.linksOutLabel,
|
|
2360
2353
|
emitWhenEmpty: false,
|
|
2361
2354
|
priority: 20
|
|
2362
2355
|
};
|
|
2363
2356
|
var linkCounterAnalyzer = {
|
|
2364
|
-
id:
|
|
2357
|
+
id: ID15,
|
|
2365
2358
|
pluginId: CORE_PLUGIN_ID,
|
|
2366
2359
|
kind: "analyzer",
|
|
2367
2360
|
description: "Counts incoming and outgoing links per node.",
|
|
@@ -2404,24 +2397,115 @@ function emitChip(ctx, nodePath, ref, direction, byKind) {
|
|
|
2404
2397
|
}
|
|
2405
2398
|
function formatBreakdown(byKind, direction) {
|
|
2406
2399
|
const lines = [...byKind.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([kind, n]) => `${kind}: ${n}`);
|
|
2407
|
-
|
|
2400
|
+
const dirLabel = direction === "in" ? LINK_COUNTER_TEXTS.directionIn : LINK_COUNTER_TEXTS.directionOut;
|
|
2401
|
+
return [dirLabel, ...lines].join("\n");
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
// plugins/core/analyzers/link-kind-conflict/text.ts
|
|
2405
|
+
var LINK_KIND_CONFLICT_TEXTS = {
|
|
2406
|
+
/**
|
|
2407
|
+
* Diagnosis body (`<what>; <why>`). The shared `formatFinding` helper
|
|
2408
|
+
* wraps it with the backtick subject (the disputed target); the source
|
|
2409
|
+
* is the finding's own node, so it never appears in the message.
|
|
2410
|
+
*/
|
|
2411
|
+
message: "Conflicting link kind; detectors disagree ({{kindList}})",
|
|
2412
|
+
/**
|
|
2413
|
+
* Remediation hint surfaced via `Issue.fix.summary`. Not autofixable:
|
|
2414
|
+
* the rule cannot tell which kind the author meant, so it offers the
|
|
2415
|
+
* two valid resolutions (drop one source, or accept the overlap on
|
|
2416
|
+
* purpose). Mirrors the `warn`-severity `link-self-loop` hint shape.
|
|
2417
|
+
*/
|
|
2418
|
+
fixSummary: "Remove one of the conflicting sources to settle on a single kind, or ignore the conflict deliberately."
|
|
2419
|
+
};
|
|
2420
|
+
|
|
2421
|
+
// plugins/core/analyzers/link-kind-conflict/index.ts
|
|
2422
|
+
var ID16 = "link-kind-conflict";
|
|
2423
|
+
var NON_CONFLICTING_KINDS = /* @__PURE__ */ new Set(["points"]);
|
|
2424
|
+
var linkKindConflictAnalyzer = {
|
|
2425
|
+
id: ID16,
|
|
2426
|
+
pluginId: CORE_PLUGIN_ID,
|
|
2427
|
+
kind: "analyzer",
|
|
2428
|
+
description: "Flags conflicting arrow meanings between extractors (e.g. `references` vs `invokes`).",
|
|
2429
|
+
mode: "deterministic",
|
|
2430
|
+
// Bucket links by (source, target), then per-bucket detect distinct
|
|
2431
|
+
// kinds. The branching is intrinsic to the per-bucket conflict
|
|
2432
|
+
// detection.
|
|
2433
|
+
// eslint-disable-next-line complexity
|
|
2434
|
+
evaluate(ctx) {
|
|
2435
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2436
|
+
for (const link of ctx.links) {
|
|
2437
|
+
if (NON_CONFLICTING_KINDS.has(link.kind)) continue;
|
|
2438
|
+
const key = `${link.source}\0${link.target}`;
|
|
2439
|
+
const bucket = groups.get(key);
|
|
2440
|
+
if (bucket) bucket.push(link);
|
|
2441
|
+
else groups.set(key, [link]);
|
|
2442
|
+
}
|
|
2443
|
+
const issues = [];
|
|
2444
|
+
for (const [key, links] of groups) {
|
|
2445
|
+
if (links.length < 2) continue;
|
|
2446
|
+
const kinds = new Set(links.map((l) => l.kind));
|
|
2447
|
+
if (kinds.size < 2) continue;
|
|
2448
|
+
const variantByKind = /* @__PURE__ */ new Map();
|
|
2449
|
+
for (const link of links) {
|
|
2450
|
+
const existing = variantByKind.get(link.kind);
|
|
2451
|
+
if (existing) {
|
|
2452
|
+
for (const src of link.sources) {
|
|
2453
|
+
if (!existing.sources.includes(src)) existing.sources.push(src);
|
|
2454
|
+
}
|
|
2455
|
+
if (rankConfidence(link.confidence) > rankConfidence(existing.confidence)) {
|
|
2456
|
+
existing.confidence = link.confidence;
|
|
2457
|
+
}
|
|
2458
|
+
} else {
|
|
2459
|
+
variantByKind.set(link.kind, {
|
|
2460
|
+
kind: link.kind,
|
|
2461
|
+
sources: [...link.sources],
|
|
2462
|
+
confidence: link.confidence
|
|
2463
|
+
});
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
for (const v of variantByKind.values()) v.sources.sort();
|
|
2467
|
+
const variants = [...variantByKind.values()].sort(
|
|
2468
|
+
(a, b) => a.kind.localeCompare(b.kind)
|
|
2469
|
+
);
|
|
2470
|
+
const [source, target] = key.split("\0");
|
|
2471
|
+
const kindList = variants.map((v) => v.kind).join(" / ");
|
|
2472
|
+
issues.push({
|
|
2473
|
+
analyzerId: ID16,
|
|
2474
|
+
severity: "warn",
|
|
2475
|
+
nodeIds: [source, target],
|
|
2476
|
+
message: formatFinding({
|
|
2477
|
+
subject: target,
|
|
2478
|
+
body: tx(LINK_KIND_CONFLICT_TEXTS.message, {
|
|
2479
|
+
kindList
|
|
2480
|
+
})
|
|
2481
|
+
}),
|
|
2482
|
+
fix: { summary: tx(LINK_KIND_CONFLICT_TEXTS.fixSummary) },
|
|
2483
|
+
data: { source, target, variants }
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
return issues;
|
|
2487
|
+
}
|
|
2488
|
+
};
|
|
2489
|
+
function rankConfidence(c) {
|
|
2490
|
+
return c;
|
|
2408
2491
|
}
|
|
2409
2492
|
|
|
2410
2493
|
// plugins/core/analyzers/link-self-loop/text.ts
|
|
2411
2494
|
var LINK_SELF_LOOP_TEXTS = {
|
|
2412
2495
|
/**
|
|
2413
|
-
* Per-edge warn: a node body references itself via the slash /
|
|
2496
|
+
* Per-edge warn body: a node body references itself via the slash /
|
|
2414
2497
|
* at-directive / markdown-link surface (most commonly because the
|
|
2415
2498
|
* file's heading IS the invocation token, e.g. `# /deploy` inside
|
|
2416
2499
|
* `commands/deploy.md`). The link is structurally valid but rarely
|
|
2417
2500
|
* the operator's intent; UI consumers MAY hide it by default and
|
|
2418
2501
|
* surface a count.
|
|
2419
2502
|
*/
|
|
2420
|
-
message: "
|
|
2421
|
-
/**
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2503
|
+
message: "Self-reference; the skill/command invokes itself, potential loop",
|
|
2504
|
+
/**
|
|
2505
|
+
* Remediation hint surfaced via `Issue.fix.summary` (the Inspector
|
|
2506
|
+
* renders it under the finding, separate from the diagnosis body).
|
|
2507
|
+
*/
|
|
2508
|
+
fixSummary: "Remove the token or ignore the self-reference deliberately."
|
|
2425
2509
|
};
|
|
2426
2510
|
|
|
2427
2511
|
// plugins/core/analyzers/link-self-loop/index.ts
|
|
@@ -2433,7 +2517,6 @@ var linkSelfLoopAnalyzer = {
|
|
|
2433
2517
|
description: "Flags links whose source is also their own resolved target (e.g. a body heading like `# /deploy` inside the file that defines `/deploy`).",
|
|
2434
2518
|
mode: "deterministic",
|
|
2435
2519
|
evaluate(ctx) {
|
|
2436
|
-
if (ctx.links.length === 0) return [];
|
|
2437
2520
|
const issues = [];
|
|
2438
2521
|
for (const link of ctx.links) {
|
|
2439
2522
|
if (!isSelfLoop(link)) continue;
|
|
@@ -2441,14 +2524,12 @@ var linkSelfLoopAnalyzer = {
|
|
|
2441
2524
|
analyzerId: ID17,
|
|
2442
2525
|
severity: "warn",
|
|
2443
2526
|
nodeIds: [link.source],
|
|
2444
|
-
message:
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
single: LINK_SELF_LOOP_TEXTS.whereSingle,
|
|
2449
|
-
plural: LINK_SELF_LOOP_TEXTS.wherePlural
|
|
2450
|
-
})
|
|
2527
|
+
message: formatFinding({
|
|
2528
|
+
subject: link.trigger?.originalTrigger ?? link.target,
|
|
2529
|
+
lines: linkLines(link),
|
|
2530
|
+
body: tx(LINK_SELF_LOOP_TEXTS.message)
|
|
2451
2531
|
}),
|
|
2532
|
+
fix: { summary: tx(LINK_SELF_LOOP_TEXTS.fixSummary) },
|
|
2452
2533
|
data: {
|
|
2453
2534
|
target: link.target,
|
|
2454
2535
|
resolvedTarget: link.resolvedTarget ?? link.target,
|
|
@@ -2460,166 +2541,97 @@ var linkSelfLoopAnalyzer = {
|
|
|
2460
2541
|
}
|
|
2461
2542
|
};
|
|
2462
2543
|
|
|
2463
|
-
//
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
if (normalised) out.push(normalised);
|
|
2474
|
-
}
|
|
2475
|
-
return out;
|
|
2476
|
-
}
|
|
2477
|
-
function readIdentifier(source, node) {
|
|
2478
|
-
if (source === "frontmatter.name") return readFrontmatterName(node);
|
|
2479
|
-
if (source === "filename-basename") return readFilenameBasename(node);
|
|
2480
|
-
return readDirname(node);
|
|
2481
|
-
}
|
|
2482
|
-
function readFrontmatterName(node) {
|
|
2483
|
-
const raw = node.frontmatter?.["name"];
|
|
2484
|
-
if (typeof raw !== "string") return null;
|
|
2485
|
-
return raw.length > 0 ? raw : null;
|
|
2486
|
-
}
|
|
2487
|
-
function readFilenameBasename(node) {
|
|
2488
|
-
const base = pathPosix4.basename(node.path);
|
|
2489
|
-
if (!base) return null;
|
|
2490
|
-
const ext = pathPosix4.extname(base);
|
|
2491
|
-
const stem = ext ? base.slice(0, -ext.length) : base;
|
|
2492
|
-
return stem.length > 0 ? stem : null;
|
|
2493
|
-
}
|
|
2494
|
-
function readDirname(node) {
|
|
2495
|
-
const dir = pathPosix4.dirname(node.path);
|
|
2496
|
-
if (!dir || dir === "." || dir === "/") return null;
|
|
2497
|
-
const base = pathPosix4.basename(dir);
|
|
2498
|
-
return base.length > 0 ? base : null;
|
|
2499
|
-
}
|
|
2544
|
+
// plugins/core/analyzers/name-collision/text.ts
|
|
2545
|
+
var NAME_COLLISION_TEXTS = {
|
|
2546
|
+
/**
|
|
2547
|
+
* Diagnosis body (`<what>; <why>: <evidence>`). The shared
|
|
2548
|
+
* `formatFinding` helper wraps it with the backtick subject (the
|
|
2549
|
+
* normalised name claimed by two or more nodes). The evidence is the
|
|
2550
|
+
* competing node paths.
|
|
2551
|
+
*/
|
|
2552
|
+
message: "Name collision; {{count}} nodes declare the same name: {{paths}}"
|
|
2553
|
+
};
|
|
2500
2554
|
|
|
2501
|
-
//
|
|
2502
|
-
var
|
|
2503
|
-
var
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
}
|
|
2532
|
-
|
|
2533
|
-
const byPath3 = /* @__PURE__ */ new Set();
|
|
2534
|
-
const byName = /* @__PURE__ */ new Map();
|
|
2535
|
-
const nodeByPath = /* @__PURE__ */ new Map();
|
|
2536
|
-
for (const node of nodes) {
|
|
2537
|
-
byPath3.add(node.path);
|
|
2538
|
-
nodeByPath.set(node.path, node);
|
|
2539
|
-
indexNode(node, ctx, byName);
|
|
2540
|
-
}
|
|
2541
|
-
return { byPath: byPath3, byName, nodeByPath };
|
|
2542
|
-
}
|
|
2543
|
-
function resolve2(link, indexes, ctx) {
|
|
2544
|
-
if (indexes.byPath.has(link.target)) return link.target;
|
|
2545
|
-
return resolveByName(link, indexes, ctx);
|
|
2546
|
-
}
|
|
2547
|
-
function isGenuinelyBroken(link, indexes) {
|
|
2548
|
-
if (indexes.byPath.has(link.target)) return false;
|
|
2549
|
-
const stripped = stripTriggerSigil(link.trigger?.normalizedTrigger);
|
|
2550
|
-
if (stripped !== null && indexes.byName.has(stripped)) return false;
|
|
2551
|
-
return true;
|
|
2552
|
-
}
|
|
2553
|
-
function resolveByName(link, indexes, ctx) {
|
|
2554
|
-
const stripped = stripTriggerSigil(link.trigger?.normalizedTrigger);
|
|
2555
|
-
if (stripped === null) return "none";
|
|
2556
|
-
const candidates = indexes.byName.get(stripped);
|
|
2557
|
-
if (!candidates?.length) return "none";
|
|
2558
|
-
const allowedKinds = lookupAllowedKinds(link, indexes, ctx);
|
|
2559
|
-
if (!allowedKinds?.length) return "none";
|
|
2560
|
-
const winner = candidates.find((c) => allowedKinds.includes(c.kind));
|
|
2561
|
-
return winner ? winner.path : "none";
|
|
2562
|
-
}
|
|
2563
|
-
function lookupAllowedKinds(link, _indexes, ctx) {
|
|
2564
|
-
if (ctx.activeProvider === null) return void 0;
|
|
2565
|
-
return ctx.providerResolution.get(ctx.activeProvider)?.[link.kind];
|
|
2566
|
-
}
|
|
2567
|
-
function stripTriggerSigil(normalized) {
|
|
2568
|
-
if (!normalized) return null;
|
|
2569
|
-
const trimmed = normalized.replace(/^[/@]/, "").trim();
|
|
2570
|
-
return trimmed.length === 0 ? null : trimmed;
|
|
2571
|
-
}
|
|
2572
|
-
function indexNode(node, ctx, byName) {
|
|
2573
|
-
const kindDescriptor = ctx.kindRegistry.get(kindKey(node));
|
|
2574
|
-
const normalised = deriveNodeIdentifiers(node, kindDescriptor);
|
|
2575
|
-
for (const name of normalised) {
|
|
2576
|
-
const entry = { kind: node.kind, path: node.path };
|
|
2577
|
-
const bucket = byName.get(name);
|
|
2578
|
-
if (bucket) {
|
|
2579
|
-
bucket.push(entry);
|
|
2580
|
-
} else {
|
|
2581
|
-
byName.set(name, [entry]);
|
|
2555
|
+
// plugins/core/analyzers/name-collision/index.ts
|
|
2556
|
+
var ID18 = "name-collision";
|
|
2557
|
+
var nameCollisionAnalyzer = {
|
|
2558
|
+
id: ID18,
|
|
2559
|
+
pluginId: CORE_PLUGIN_ID,
|
|
2560
|
+
kind: "analyzer",
|
|
2561
|
+
mode: "deterministic",
|
|
2562
|
+
description: "Flags two or more nodes that declare the same resolvable `name`.",
|
|
2563
|
+
// Pure projector of `ctx.nameCollisions` (computed once by the
|
|
2564
|
+
// orchestrator from the kind registry). One `error` per colliding name.
|
|
2565
|
+
evaluate(ctx) {
|
|
2566
|
+
const collisions = ctx.nameCollisions;
|
|
2567
|
+
if (!collisions || collisions.size === 0) return [];
|
|
2568
|
+
const issues = [];
|
|
2569
|
+
for (const [name, claims] of collisions) {
|
|
2570
|
+
const paths = claims.map((c) => c.path);
|
|
2571
|
+
issues.push({
|
|
2572
|
+
analyzerId: ID18,
|
|
2573
|
+
severity: "error",
|
|
2574
|
+
nodeIds: paths,
|
|
2575
|
+
message: formatFinding({
|
|
2576
|
+
subject: name,
|
|
2577
|
+
body: tx(NAME_COLLISION_TEXTS.message, {
|
|
2578
|
+
count: paths.length,
|
|
2579
|
+
paths: paths.join(", ")
|
|
2580
|
+
})
|
|
2581
|
+
}),
|
|
2582
|
+
data: {
|
|
2583
|
+
name,
|
|
2584
|
+
claims: claims.map((c) => ({ path: c.path, kind: c.kind }))
|
|
2585
|
+
}
|
|
2586
|
+
});
|
|
2582
2587
|
}
|
|
2588
|
+
return issues;
|
|
2583
2589
|
}
|
|
2584
|
-
}
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2590
|
+
};
|
|
2591
|
+
|
|
2592
|
+
// kernel/orchestrator/confidence-constants.ts
|
|
2593
|
+
var RESERVED_PENALTY = 0.9;
|
|
2594
|
+
var BROKEN_PENALTY = 0.5;
|
|
2588
2595
|
|
|
2589
2596
|
// plugins/core/analyzers/name-reserved/text.ts
|
|
2590
2597
|
var NAME_RESERVED_TEXTS = {
|
|
2591
2598
|
/**
|
|
2592
|
-
* Target-side
|
|
2593
|
-
* a runtime built-in.
|
|
2594
|
-
*
|
|
2599
|
+
* Target-side body (`<what>; <why>`): emitted on the user file that
|
|
2600
|
+
* collides with a runtime built-in. The shared `formatFinding` helper
|
|
2601
|
+
* adds no subject (the offending node IS the finding's own node); the
|
|
2602
|
+
* remediation hint moves to `Issue.fix.summary` below.
|
|
2595
2603
|
*/
|
|
2596
|
-
message: "
|
|
2604
|
+
message: "Reserved name; this {{kind}} name is shadowed by the {{provider}} runtime built-in",
|
|
2605
|
+
/** Remediation hint for the target-side finding. */
|
|
2606
|
+
fixSummary: "Rename the file or its frontmatter.name.",
|
|
2597
2607
|
/**
|
|
2598
|
-
* Source-side
|
|
2599
|
-
* whose target resolves to a reserved name.
|
|
2600
|
-
*
|
|
2601
|
-
*
|
|
2602
|
-
*
|
|
2608
|
+
* Source-side body (`<what>; <why>`): emitted on the node that
|
|
2609
|
+
* AUTHORED a link whose target resolves to a reserved name. Reports the
|
|
2610
|
+
* fact (the runtime built-in shadows this edge); it deliberately does
|
|
2611
|
+
* NOT assert a confidence number, since the value is owned by the
|
|
2612
|
+
* `score`-phase scorers and may vary or be absent. The shared
|
|
2613
|
+
* `formatFinding` helper wraps it with the backtick target subject and
|
|
2614
|
+
* the `L<line>:` location prefix.
|
|
2603
2615
|
*/
|
|
2604
|
-
linkMessage: "
|
|
2605
|
-
/**
|
|
2606
|
-
|
|
2607
|
-
/** Location suffix after the built-in parens, several detection sites. */
|
|
2608
|
-
wherePlural: " (lines {{lines}})"
|
|
2616
|
+
linkMessage: "Reserved name; resolves to the {{provider}} built-in ({{reservedKind}} `{{reservedPath}}`), the built-in shadows this edge",
|
|
2617
|
+
/** Remediation hint for the source-side finding. */
|
|
2618
|
+
linkFixSummary: "Rename the target file or its frontmatter.name."
|
|
2609
2619
|
};
|
|
2610
2620
|
|
|
2611
2621
|
// plugins/core/analyzers/name-reserved/index.ts
|
|
2612
|
-
var
|
|
2622
|
+
var ID19 = "name-reserved";
|
|
2613
2623
|
var nameReservedAnalyzer = {
|
|
2614
|
-
id:
|
|
2624
|
+
id: ID19,
|
|
2615
2625
|
pluginId: CORE_PLUGIN_ID,
|
|
2616
2626
|
kind: "analyzer",
|
|
2617
2627
|
description: "Flags two kinds of reserved-name collision: a file whose name shadows a built-in command of the active runtime, and a link that resolves to one of those reserved names.",
|
|
2618
2628
|
mode: "deterministic",
|
|
2629
|
+
phase: "score",
|
|
2619
2630
|
// eslint-disable-next-line complexity
|
|
2620
2631
|
evaluate(ctx) {
|
|
2621
2632
|
const reserved = ctx.reservedNodePaths;
|
|
2622
2633
|
if (!reserved || reserved.size === 0) return [];
|
|
2634
|
+
const adjust = ctx.adjustConfidence;
|
|
2623
2635
|
const byPath3 = /* @__PURE__ */ new Map();
|
|
2624
2636
|
for (const node of ctx.nodes) byPath3.set(node.path, node);
|
|
2625
2637
|
const issues = [];
|
|
@@ -2627,35 +2639,41 @@ var nameReservedAnalyzer = {
|
|
|
2627
2639
|
const node = byPath3.get(path);
|
|
2628
2640
|
if (!node) continue;
|
|
2629
2641
|
issues.push({
|
|
2630
|
-
analyzerId:
|
|
2642
|
+
analyzerId: ID19,
|
|
2631
2643
|
severity: "warn",
|
|
2632
2644
|
nodeIds: [node.path],
|
|
2633
|
-
message:
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2645
|
+
message: formatFinding({
|
|
2646
|
+
body: tx(NAME_RESERVED_TEXTS.message, {
|
|
2647
|
+
provider: node.provider,
|
|
2648
|
+
kind: node.kind
|
|
2649
|
+
})
|
|
2637
2650
|
}),
|
|
2651
|
+
fix: { summary: tx(NAME_RESERVED_TEXTS.fixSummary) },
|
|
2638
2652
|
data: { provider: node.provider, kind: node.kind, surface: "target" }
|
|
2639
2653
|
});
|
|
2640
2654
|
}
|
|
2641
2655
|
for (const link of ctx.links) {
|
|
2642
|
-
if (link.confidence !== RESERVED_TARGET_CONFIDENCE) continue;
|
|
2643
2656
|
const reservedPath = link.resolvedTarget;
|
|
2644
2657
|
if (!reservedPath || !reserved.has(reservedPath)) continue;
|
|
2645
2658
|
const reservedNode = byPath3.get(reservedPath);
|
|
2646
2659
|
if (!reservedNode) continue;
|
|
2660
|
+
if (adjust) {
|
|
2661
|
+
adjust(link, { kind: "delta", value: -RESERVED_PENALTY });
|
|
2662
|
+
}
|
|
2647
2663
|
issues.push({
|
|
2648
|
-
analyzerId:
|
|
2664
|
+
analyzerId: ID19,
|
|
2649
2665
|
severity: "warn",
|
|
2650
2666
|
nodeIds: [link.source],
|
|
2651
|
-
message:
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2667
|
+
message: formatFinding({
|
|
2668
|
+
subject: link.target,
|
|
2669
|
+
lines: linkLines(link),
|
|
2670
|
+
body: tx(NAME_RESERVED_TEXTS.linkMessage, {
|
|
2671
|
+
provider: reservedNode.provider,
|
|
2672
|
+
reservedKind: reservedNode.kind,
|
|
2673
|
+
reservedPath: reservedNode.path
|
|
2674
|
+
})
|
|
2658
2675
|
}),
|
|
2676
|
+
fix: { summary: tx(NAME_RESERVED_TEXTS.linkFixSummary) },
|
|
2659
2677
|
data: {
|
|
2660
2678
|
target: link.target,
|
|
2661
2679
|
kind: link.kind,
|
|
@@ -2669,15 +2687,13 @@ var nameReservedAnalyzer = {
|
|
|
2669
2687
|
return issues;
|
|
2670
2688
|
}
|
|
2671
2689
|
};
|
|
2672
|
-
function linkWhereSuffix(link) {
|
|
2673
|
-
return linkWhere(link, {
|
|
2674
|
-
single: NAME_RESERVED_TEXTS.whereSingle,
|
|
2675
|
-
plural: NAME_RESERVED_TEXTS.wherePlural
|
|
2676
|
-
});
|
|
2677
|
-
}
|
|
2678
2690
|
|
|
2679
2691
|
// plugins/core/analyzers/node-stability/text.ts
|
|
2680
2692
|
var NODE_STABILITY_TEXTS = {
|
|
2693
|
+
/** Issue body (`<what>; <why>`) for an experimental-marked node. */
|
|
2694
|
+
experimental: "Marked experimental; API may change",
|
|
2695
|
+
/** Issue body (`<what>; <why>`) for a deprecated-marked node. */
|
|
2696
|
+
deprecated: "Marked deprecated; avoid in new code",
|
|
2681
2697
|
/** Label of the inspector action button that sets the lifecycle stage. */
|
|
2682
2698
|
setLabel: "Set stability",
|
|
2683
2699
|
/** Prompt label for the enum-pick stability input. */
|
|
@@ -2691,7 +2707,7 @@ var NODE_STABILITY_TEXTS = {
|
|
|
2691
2707
|
};
|
|
2692
2708
|
|
|
2693
2709
|
// plugins/core/analyzers/node-stability/index.ts
|
|
2694
|
-
var
|
|
2710
|
+
var ID20 = "node-stability";
|
|
2695
2711
|
var EXPERIMENTAL_TOOLTIP = "Experimental: API may change";
|
|
2696
2712
|
var DEPRECATED_TOOLTIP = "Deprecated: avoid in new code";
|
|
2697
2713
|
var experimental = {
|
|
@@ -2713,7 +2729,7 @@ var setStabilityButton = {
|
|
|
2713
2729
|
priority: 15
|
|
2714
2730
|
};
|
|
2715
2731
|
var nodeStabilityAnalyzer = {
|
|
2716
|
-
id:
|
|
2732
|
+
id: ID20,
|
|
2717
2733
|
pluginId: CORE_PLUGIN_ID,
|
|
2718
2734
|
kind: "analyzer",
|
|
2719
2735
|
description: "Reports a node's stability stage (`experimental`, `deprecated`) on the card.",
|
|
@@ -2732,10 +2748,10 @@ var nodeStabilityAnalyzer = {
|
|
|
2732
2748
|
tooltip: EXPERIMENTAL_TOOLTIP
|
|
2733
2749
|
});
|
|
2734
2750
|
issues.push({
|
|
2735
|
-
analyzerId:
|
|
2751
|
+
analyzerId: ID20,
|
|
2736
2752
|
severity: "info",
|
|
2737
2753
|
nodeIds: [node.path],
|
|
2738
|
-
message:
|
|
2754
|
+
message: formatFinding({ body: tx(NODE_STABILITY_TEXTS.experimental) }),
|
|
2739
2755
|
data: { stability }
|
|
2740
2756
|
});
|
|
2741
2757
|
} else if (stability === "deprecated") {
|
|
@@ -2745,10 +2761,10 @@ var nodeStabilityAnalyzer = {
|
|
|
2745
2761
|
severity: "warn"
|
|
2746
2762
|
});
|
|
2747
2763
|
issues.push({
|
|
2748
|
-
analyzerId:
|
|
2764
|
+
analyzerId: ID20,
|
|
2749
2765
|
severity: "warn",
|
|
2750
2766
|
nodeIds: [node.path],
|
|
2751
|
-
message:
|
|
2767
|
+
message: formatFinding({ body: tx(NODE_STABILITY_TEXTS.deprecated) }),
|
|
2752
2768
|
data: { stability }
|
|
2753
2769
|
});
|
|
2754
2770
|
}
|
|
@@ -2794,17 +2810,17 @@ function emitSetStabilityButton(ctx, nodePath, current) {
|
|
|
2794
2810
|
// plugins/core/analyzers/node-superseded/text.ts
|
|
2795
2811
|
var NODE_SUPERSEDED_TEXTS = {
|
|
2796
2812
|
/**
|
|
2797
|
-
*
|
|
2798
|
-
*
|
|
2799
|
-
* so its path never appears
|
|
2813
|
+
* Diagnosis body (`<what>; <why>`). The shared `formatFinding` helper
|
|
2814
|
+
* wraps it with the backtick subject (the superseding artifact); the
|
|
2815
|
+
* superseded node is the finding's own node, so its path never appears.
|
|
2800
2816
|
*/
|
|
2801
|
-
message: "
|
|
2817
|
+
message: "Superseded; a newer node supersedes this one"
|
|
2802
2818
|
};
|
|
2803
2819
|
|
|
2804
2820
|
// plugins/core/analyzers/node-superseded/index.ts
|
|
2805
|
-
var
|
|
2821
|
+
var ID21 = "node-superseded";
|
|
2806
2822
|
var nodeSupersededAnalyzer = {
|
|
2807
|
-
id:
|
|
2823
|
+
id: ID21,
|
|
2808
2824
|
pluginId: CORE_PLUGIN_ID,
|
|
2809
2825
|
kind: "analyzer",
|
|
2810
2826
|
description: "Marks nodes replaced by a newer one via `supersededBy`.",
|
|
@@ -2821,12 +2837,12 @@ var nodeSupersededAnalyzer = {
|
|
|
2821
2837
|
const supersededBy = pickSupersededBy(node);
|
|
2822
2838
|
if (supersededBy === null) continue;
|
|
2823
2839
|
issues.push({
|
|
2824
|
-
analyzerId:
|
|
2840
|
+
analyzerId: ID21,
|
|
2825
2841
|
severity: "info",
|
|
2826
2842
|
nodeIds: [node.path],
|
|
2827
|
-
message:
|
|
2828
|
-
|
|
2829
|
-
|
|
2843
|
+
message: formatFinding({
|
|
2844
|
+
subject: supersededBy,
|
|
2845
|
+
body: tx(NODE_SUPERSEDED_TEXTS.message)
|
|
2830
2846
|
}),
|
|
2831
2847
|
data: { supersededBy }
|
|
2832
2848
|
});
|
|
@@ -2845,22 +2861,27 @@ function pickSupersededBy(node) {
|
|
|
2845
2861
|
}
|
|
2846
2862
|
|
|
2847
2863
|
// plugins/core/analyzers/reference-broken/index.ts
|
|
2848
|
-
import { resolve as
|
|
2864
|
+
import { resolve as resolve2 } from "path";
|
|
2849
2865
|
|
|
2850
2866
|
// plugins/core/analyzers/reference-broken/text.ts
|
|
2851
2867
|
var REFERENCE_BROKEN_TEXTS = {
|
|
2852
2868
|
/**
|
|
2853
|
-
*
|
|
2854
|
-
*
|
|
2855
|
-
*
|
|
2856
|
-
*
|
|
2857
|
-
|
|
2869
|
+
* Diagnosis body (`<what>; <why>`). The shared `formatFinding` helper
|
|
2870
|
+
* wraps it with the backtick subject (the unresolved target) and the
|
|
2871
|
+
* `L<line>:` location prefix; the source is the finding's own node, so
|
|
2872
|
+
* it never appears in the message.
|
|
2873
|
+
*/
|
|
2874
|
+
message: "Broken {{kindLabel}}; target not found in the graph or on disk",
|
|
2875
|
+
/**
|
|
2876
|
+
* Remediation hint surfaced via `Issue.fix.summary`. Not autofixable:
|
|
2877
|
+
* the rule cannot tell which resolution the author wants. The folder
|
|
2878
|
+
* option maps to `scan.referencePaths` ("Folders for link validation"
|
|
2879
|
+
* in Settings), the rule's own escape hatch: it clears only PATH-style
|
|
2880
|
+
* breaks (the file exists on disk outside the indexed graph). A
|
|
2881
|
+
* trigger-style `/cmd` / `@agent` break is settled by the path/name or
|
|
2882
|
+
* removal options instead.
|
|
2858
2883
|
*/
|
|
2859
|
-
|
|
2860
|
-
/** Location suffix, one detection site. */
|
|
2861
|
-
whereSingle: " (line {{lines}})",
|
|
2862
|
-
/** Location suffix, several detection sites. */
|
|
2863
|
-
wherePlural: " (lines {{lines}})",
|
|
2884
|
+
fixSummary: "Fix the path or name, remove the broken link, or add its folder under Folders for link validation.",
|
|
2864
2885
|
/**
|
|
2865
2886
|
* Human noun per link kind for the message above. Fallback for an
|
|
2866
2887
|
* off-catalog kind: `<kind> link` (composed in the analyzer).
|
|
@@ -2880,13 +2901,14 @@ var REFERENCE_BROKEN_TEXTS = {
|
|
|
2880
2901
|
};
|
|
2881
2902
|
|
|
2882
2903
|
// plugins/core/analyzers/reference-broken/index.ts
|
|
2883
|
-
var
|
|
2904
|
+
var ID22 = "reference-broken";
|
|
2884
2905
|
var referenceBrokenAnalyzer = {
|
|
2885
|
-
id:
|
|
2906
|
+
id: ID22,
|
|
2886
2907
|
pluginId: CORE_PLUGIN_ID,
|
|
2887
2908
|
kind: "analyzer",
|
|
2888
2909
|
description: "Flags arrows pointing at a node not part of the current scan.",
|
|
2889
2910
|
mode: "deterministic",
|
|
2911
|
+
phase: "score",
|
|
2890
2912
|
// No `ui` declaration: this analyzer used to emit a per-finding
|
|
2891
2913
|
// counter chip on `card.footer.right`, but that chip duplicated the
|
|
2892
2914
|
// aggregate severity counters now owned by `core/issue-counter`. The
|
|
@@ -2902,10 +2924,12 @@ var referenceBrokenAnalyzer = {
|
|
|
2902
2924
|
const broken = ctx.brokenLinks;
|
|
2903
2925
|
if (!broken || broken.size === 0) return [];
|
|
2904
2926
|
const refIndex = buildReferenceIndex(ctx);
|
|
2927
|
+
const adjust = ctx.adjustConfidence;
|
|
2905
2928
|
const issues = [];
|
|
2906
2929
|
for (const link of ctx.links) {
|
|
2907
2930
|
if (!broken.has(link)) continue;
|
|
2908
2931
|
if (refIndex && resolvesViaReferencePaths(link, refIndex)) continue;
|
|
2932
|
+
penalizeBrokenConfidence(adjust, link);
|
|
2909
2933
|
issues.push(buildIssue(link));
|
|
2910
2934
|
}
|
|
2911
2935
|
return issues;
|
|
@@ -2915,9 +2939,14 @@ function buildReferenceIndex(ctx) {
|
|
|
2915
2939
|
if (!ctx.referenceablePaths || ctx.referenceablePaths.size === 0 || !ctx.cwd) return null;
|
|
2916
2940
|
return { paths: ctx.referenceablePaths, cwd: ctx.cwd };
|
|
2917
2941
|
}
|
|
2942
|
+
function penalizeBrokenConfidence(adjust, link) {
|
|
2943
|
+
if (adjust) {
|
|
2944
|
+
adjust(link, { kind: "delta", value: -BROKEN_PENALTY });
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2918
2947
|
function buildIssue(link) {
|
|
2919
2948
|
return {
|
|
2920
|
-
analyzerId:
|
|
2949
|
+
analyzerId: ID22,
|
|
2921
2950
|
// `error`, not `warn`: a link whose target is not in the scan is a
|
|
2922
2951
|
// structural defect the operator must notice, and the card chip
|
|
2923
2952
|
// paints `danger` (red) to match. Per the chip-vs-issue policy in
|
|
@@ -2926,14 +2955,14 @@ function buildIssue(link) {
|
|
|
2926
2955
|
// and the global error count on the card.
|
|
2927
2956
|
severity: "error",
|
|
2928
2957
|
nodeIds: [link.source],
|
|
2929
|
-
message:
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
plural: REFERENCE_BROKEN_TEXTS.wherePlural
|
|
2958
|
+
message: formatFinding({
|
|
2959
|
+
subject: link.target,
|
|
2960
|
+
lines: linkLines(link),
|
|
2961
|
+
body: tx(REFERENCE_BROKEN_TEXTS.message, {
|
|
2962
|
+
kindLabel: REFERENCE_BROKEN_TEXTS.kindLabels[link.kind] ?? tx(REFERENCE_BROKEN_TEXTS.kindLabelFallback, { kind: link.kind })
|
|
2935
2963
|
})
|
|
2936
2964
|
}),
|
|
2965
|
+
fix: { summary: tx(REFERENCE_BROKEN_TEXTS.fixSummary) },
|
|
2937
2966
|
data: {
|
|
2938
2967
|
target: link.target,
|
|
2939
2968
|
kind: link.kind,
|
|
@@ -2943,7 +2972,7 @@ function buildIssue(link) {
|
|
|
2943
2972
|
}
|
|
2944
2973
|
function resolvesViaReferencePaths(link, refIndex) {
|
|
2945
2974
|
if (!isPathStyleLink(link)) return false;
|
|
2946
|
-
return refIndex.paths.has(
|
|
2975
|
+
return refIndex.paths.has(resolve2(refIndex.cwd, link.target));
|
|
2947
2976
|
}
|
|
2948
2977
|
function isPathStyleLink(link) {
|
|
2949
2978
|
const sigil = link.trigger?.normalizedTrigger?.charAt(0);
|
|
@@ -2954,29 +2983,36 @@ function isPathStyleLink(link) {
|
|
|
2954
2983
|
// plugins/core/analyzers/reference-redundant/text.ts
|
|
2955
2984
|
var REFERENCE_REDUNDANT_TEXTS = {
|
|
2956
2985
|
/**
|
|
2957
|
-
*
|
|
2958
|
-
*
|
|
2959
|
-
*
|
|
2960
|
-
*
|
|
2961
|
-
*
|
|
2962
|
-
*
|
|
2986
|
+
* Diagnosis body (`<what>; <why>`). Kind-agnostic wording ("links", not
|
|
2987
|
+
* "reference"): the redundancy can span different link kinds (e.g.
|
|
2988
|
+
* `invokes` + `references` to one node), so the message never names a
|
|
2989
|
+
* single kind. The shared `formatFinding` helper wraps it with the
|
|
2990
|
+
* backtick subject (the resolved target); no `L<line>:` prefix because
|
|
2991
|
+
* the per-occurrence line numbers stay inline in the rendered
|
|
2992
|
+
* occurrence list. Remediation lives in `fix.summary`, not the message.
|
|
2963
2993
|
* Occurrences are grouped BY TRIGGER: each distinct trigger text
|
|
2964
|
-
* appears once with its line numbers collapsed into one paren list.
|
|
2965
|
-
*
|
|
2994
|
+
* appears once with its line numbers collapsed into one paren list. The
|
|
2995
|
+
* source node is the finding's own node, so it never appears.
|
|
2966
2996
|
*/
|
|
2967
|
-
message: "
|
|
2997
|
+
message: "Redundant links; the target is reached {{count}} times: {{occurrences}}",
|
|
2998
|
+
/**
|
|
2999
|
+
* Remediation hint surfaced via `Issue.fix.summary`. Phrased as
|
|
3000
|
+
* optional: severity is `info` and keeping multiple forms can be
|
|
3001
|
+
* deliberate, so the hint offers consolidation OR keeping the overlap.
|
|
3002
|
+
*/
|
|
3003
|
+
fixSummary: "Consolidate the links into one, or keep the overlap deliberately.",
|
|
2968
3004
|
/** Inline separator between trigger groups in the message. */
|
|
2969
3005
|
occurrenceSeparator: ", ",
|
|
2970
3006
|
/** Per-trigger formatting: the trigger once, its lines grouped. */
|
|
2971
|
-
occurrence: "`{{trigger}}` ({{lines}})",
|
|
3007
|
+
occurrence: "`{{trigger}}` (L{{lines}})",
|
|
2972
3008
|
/** Placeholder for an occurrence whose extractor recorded no line. */
|
|
2973
3009
|
lineUnknown: "?"
|
|
2974
3010
|
};
|
|
2975
3011
|
|
|
2976
3012
|
// plugins/core/analyzers/reference-redundant/index.ts
|
|
2977
|
-
var
|
|
3013
|
+
var ID23 = "reference-redundant";
|
|
2978
3014
|
var referenceRedundantAnalyzer = {
|
|
2979
|
-
id:
|
|
3015
|
+
id: ID23,
|
|
2980
3016
|
pluginId: CORE_PLUGIN_ID,
|
|
2981
3017
|
kind: "analyzer",
|
|
2982
3018
|
description: "Flags when one node references the same target through two or more different links (e.g. a markdown link plus a `references:` entry).",
|
|
@@ -2999,14 +3035,17 @@ var referenceRedundantAnalyzer = {
|
|
|
2999
3035
|
const [source, resolvedTarget] = key.split("\0");
|
|
3000
3036
|
const flat = flattenOccurrences(links);
|
|
3001
3037
|
issues.push({
|
|
3002
|
-
analyzerId:
|
|
3038
|
+
analyzerId: ID23,
|
|
3003
3039
|
severity: "info",
|
|
3004
3040
|
nodeIds: [source],
|
|
3005
|
-
message:
|
|
3006
|
-
resolvedTarget,
|
|
3007
|
-
|
|
3008
|
-
|
|
3041
|
+
message: formatFinding({
|
|
3042
|
+
subject: resolvedTarget,
|
|
3043
|
+
body: tx(REFERENCE_REDUNDANT_TEXTS.message, {
|
|
3044
|
+
count: flat.length,
|
|
3045
|
+
occurrences: formatGroupedOccurrences(flat)
|
|
3046
|
+
})
|
|
3009
3047
|
}),
|
|
3048
|
+
fix: { summary: tx(REFERENCE_REDUNDANT_TEXTS.fixSummary) },
|
|
3010
3049
|
data: {
|
|
3011
3050
|
target: resolvedTarget,
|
|
3012
3051
|
resolvedTarget,
|
|
@@ -3069,7 +3108,7 @@ function formatGroupedOccurrences(occurrences) {
|
|
|
3069
3108
|
|
|
3070
3109
|
// kernel/adapters/schema-validators.ts
|
|
3071
3110
|
import { readFileSync as readFileSync2 } from "fs";
|
|
3072
|
-
import { dirname as dirname2, resolve as
|
|
3111
|
+
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
3073
3112
|
import { createRequire as createRequire2 } from "module";
|
|
3074
3113
|
import { Ajv2020 as Ajv20202 } from "ajv/dist/2020.js";
|
|
3075
3114
|
|
|
@@ -3137,14 +3176,14 @@ function buildSchemaValidators() {
|
|
|
3137
3176
|
});
|
|
3138
3177
|
applyAjvFormats(ajv);
|
|
3139
3178
|
for (const rel of SUPPORTING_SCHEMAS) {
|
|
3140
|
-
const file =
|
|
3179
|
+
const file = resolve3(specRoot, rel);
|
|
3141
3180
|
if (!existsSyncSafe(file)) continue;
|
|
3142
3181
|
const schema = JSON.parse(readFileSync2(file, "utf8"));
|
|
3143
3182
|
ajv.addSchema(schema);
|
|
3144
3183
|
}
|
|
3145
3184
|
const validators = /* @__PURE__ */ new Map();
|
|
3146
3185
|
for (const [name, rel] of Object.entries(SCHEMA_FILES)) {
|
|
3147
|
-
const file =
|
|
3186
|
+
const file = resolve3(specRoot, rel);
|
|
3148
3187
|
const schema = JSON.parse(readFileSync2(file, "utf8"));
|
|
3149
3188
|
const byId = typeof schema.$id === "string" ? ajv.getSchema(schema.$id) : void 0;
|
|
3150
3189
|
validators.set(name, byId ?? ajv.compile(schema));
|
|
@@ -3216,7 +3255,7 @@ function buildProviderFrontmatterValidator(providers) {
|
|
|
3216
3255
|
allowUnionTypes: true
|
|
3217
3256
|
});
|
|
3218
3257
|
applyAjvFormats(ajv);
|
|
3219
|
-
const baseFile =
|
|
3258
|
+
const baseFile = resolve3(specRoot, "schemas/frontmatter/base.schema.json");
|
|
3220
3259
|
const baseSchema = JSON.parse(readFileSync2(baseFile, "utf8"));
|
|
3221
3260
|
ajv.addSchema(baseSchema);
|
|
3222
3261
|
registerProviderAuxiliarySchemas(ajv, providers);
|
|
@@ -3315,16 +3354,25 @@ function existsSyncSafe(path) {
|
|
|
3315
3354
|
}
|
|
3316
3355
|
}
|
|
3317
3356
|
|
|
3357
|
+
// kernel/orchestrator/frontmatter-issue-ids.ts
|
|
3358
|
+
var FRONTMATTER_ISSUE_ANALYZERS = /* @__PURE__ */ new Set([
|
|
3359
|
+
"frontmatter-invalid",
|
|
3360
|
+
"frontmatter-malformed",
|
|
3361
|
+
"frontmatter-parse-error"
|
|
3362
|
+
]);
|
|
3363
|
+
|
|
3318
3364
|
// plugins/core/analyzers/schema-violation/text.ts
|
|
3319
3365
|
var SCHEMA_VIOLATION_TEXTS = {
|
|
3320
|
-
//
|
|
3321
|
-
//
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
/**
|
|
3325
|
-
|
|
3326
|
-
/** `
|
|
3327
|
-
|
|
3366
|
+
// Diagnosis bodies (`<what>; <why>`). The shared `formatFinding` helper
|
|
3367
|
+
// owns the subject / location chrome: the node + frontmatter findings
|
|
3368
|
+
// carry no subject (the affected node IS the finding's own node), the
|
|
3369
|
+
// link finding uses the link target as its subject.
|
|
3370
|
+
/** `Schema validation failed; <errors>` */
|
|
3371
|
+
nodeFailure: "Schema validation failed; {{errors}}",
|
|
3372
|
+
/** `<target>` subject + `Link failed schema validation; <errors>` */
|
|
3373
|
+
linkFailure: "Link failed schema validation; {{errors}}",
|
|
3374
|
+
/** `Missing required frontmatter; <missing>` */
|
|
3375
|
+
frontmatterBaseFailure: "Missing required frontmatter; {{missing}}",
|
|
3328
3376
|
/** Singular tooltip on the alert / chip when a node has exactly one validation failure. */
|
|
3329
3377
|
alertTooltipSingle: "Frontmatter or schema validation failed.",
|
|
3330
3378
|
/** Plural tooltip; `{{count}}` capped at 99 in the chip badge but the tooltip text shows the raw count. */
|
|
@@ -3332,9 +3380,9 @@ var SCHEMA_VIOLATION_TEXTS = {
|
|
|
3332
3380
|
};
|
|
3333
3381
|
|
|
3334
3382
|
// plugins/core/analyzers/schema-violation/index.ts
|
|
3335
|
-
var
|
|
3383
|
+
var ID24 = "schema-violation";
|
|
3336
3384
|
var schemaViolationAnalyzer = {
|
|
3337
|
-
id:
|
|
3385
|
+
id: ID24,
|
|
3338
3386
|
pluginId: CORE_PLUGIN_ID,
|
|
3339
3387
|
kind: "analyzer",
|
|
3340
3388
|
description: "Flags nodes or links that violate the project schemas.",
|
|
@@ -3355,10 +3403,11 @@ var schemaViolationAnalyzer = {
|
|
|
3355
3403
|
const validators = loadSchemaValidators();
|
|
3356
3404
|
const findings = [];
|
|
3357
3405
|
const perNode = /* @__PURE__ */ new Map();
|
|
3406
|
+
const kernelFlaggedNodes = collectKernelFlaggedNodes(ctx.accumulatedIssues);
|
|
3358
3407
|
for (const node of ctx.nodes) {
|
|
3359
3408
|
const before = findings.length;
|
|
3360
3409
|
collectNodeFindings(validators, node, findings);
|
|
3361
|
-
collectFrontmatterBaseFindings(node, findings);
|
|
3410
|
+
collectFrontmatterBaseFindings(node, findings, kernelFlaggedNodes);
|
|
3362
3411
|
if (findings.length > before) {
|
|
3363
3412
|
let worst = "warn";
|
|
3364
3413
|
for (let i = before; i < findings.length; i++) {
|
|
@@ -3385,17 +3434,27 @@ function collectNodeFindings(v, node, out) {
|
|
|
3385
3434
|
const result = v.validate("node", toNodeForSchema(node));
|
|
3386
3435
|
if (result.ok) return;
|
|
3387
3436
|
out.push({
|
|
3388
|
-
analyzerId:
|
|
3437
|
+
analyzerId: ID24,
|
|
3389
3438
|
severity: "error",
|
|
3390
3439
|
nodeIds: [node.path],
|
|
3391
|
-
message:
|
|
3392
|
-
|
|
3393
|
-
|
|
3440
|
+
message: formatFinding({
|
|
3441
|
+
body: tx(SCHEMA_VIOLATION_TEXTS.nodeFailure, {
|
|
3442
|
+
errors: result.errors
|
|
3443
|
+
})
|
|
3394
3444
|
}),
|
|
3395
3445
|
data: { target: "node", path: node.path }
|
|
3396
3446
|
});
|
|
3397
3447
|
}
|
|
3398
|
-
function
|
|
3448
|
+
function collectKernelFlaggedNodes(accumulated) {
|
|
3449
|
+
const flagged = /* @__PURE__ */ new Set();
|
|
3450
|
+
for (const issue of accumulated ?? []) {
|
|
3451
|
+
if (!FRONTMATTER_ISSUE_ANALYZERS.has(issue.analyzerId)) continue;
|
|
3452
|
+
for (const id of issue.nodeIds) flagged.add(id);
|
|
3453
|
+
}
|
|
3454
|
+
return flagged;
|
|
3455
|
+
}
|
|
3456
|
+
function collectFrontmatterBaseFindings(node, out, kernelFlagged) {
|
|
3457
|
+
if (kernelFlagged.has(node.path)) return;
|
|
3399
3458
|
if (node.provider === "markdown") return;
|
|
3400
3459
|
if (node.bytes.frontmatter === 0) return;
|
|
3401
3460
|
const fm = node.frontmatter ?? {};
|
|
@@ -3404,16 +3463,17 @@ function collectFrontmatterBaseFindings(node, out) {
|
|
|
3404
3463
|
if (isMissingStringField(fm, "description")) missing.push("description");
|
|
3405
3464
|
if (missing.length === 0) return;
|
|
3406
3465
|
out.push({
|
|
3407
|
-
analyzerId:
|
|
3466
|
+
analyzerId: ID24,
|
|
3408
3467
|
// `warn` (not `error`) so the default `sm scan` exit code stays
|
|
3409
3468
|
// 0 even when nodes are missing frontmatter base fields. Strict
|
|
3410
3469
|
// mode (`sm scan --strict`) still escalates to exit 1. Matches
|
|
3411
3470
|
// the `frontmatter-invalid` severity policy of the orchestrator.
|
|
3412
3471
|
severity: "warn",
|
|
3413
3472
|
nodeIds: [node.path],
|
|
3414
|
-
message:
|
|
3415
|
-
|
|
3416
|
-
|
|
3473
|
+
message: formatFinding({
|
|
3474
|
+
body: tx(SCHEMA_VIOLATION_TEXTS.frontmatterBaseFailure, {
|
|
3475
|
+
missing: missing.join(", ")
|
|
3476
|
+
})
|
|
3417
3477
|
}),
|
|
3418
3478
|
data: { target: "frontmatter", path: node.path, missing }
|
|
3419
3479
|
});
|
|
@@ -3426,13 +3486,14 @@ function collectLinkFindings(v, link, out) {
|
|
|
3426
3486
|
const result = v.validate("link", toLinkForSchema(link));
|
|
3427
3487
|
if (result.ok) return;
|
|
3428
3488
|
out.push({
|
|
3429
|
-
analyzerId:
|
|
3489
|
+
analyzerId: ID24,
|
|
3430
3490
|
severity: "error",
|
|
3431
3491
|
nodeIds: [link.source],
|
|
3432
|
-
message:
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3492
|
+
message: formatFinding({
|
|
3493
|
+
subject: link.target,
|
|
3494
|
+
body: tx(SCHEMA_VIOLATION_TEXTS.linkFailure, {
|
|
3495
|
+
errors: result.errors
|
|
3496
|
+
})
|
|
3436
3497
|
}),
|
|
3437
3498
|
data: { target: "link", source: link.source, to: link.target }
|
|
3438
3499
|
});
|
|
@@ -3453,292 +3514,16 @@ function toNodeForSchema(node) {
|
|
|
3453
3514
|
sidecar: node.sidecar ?? void 0
|
|
3454
3515
|
};
|
|
3455
3516
|
}
|
|
3456
|
-
function toLinkForSchema(link) {
|
|
3457
|
-
return {
|
|
3458
|
-
source: link.source,
|
|
3459
|
-
target: link.target,
|
|
3460
|
-
kind: link.kind,
|
|
3461
|
-
confidence: link.confidence,
|
|
3462
|
-
sources: link.sources,
|
|
3463
|
-
trigger: link.trigger ?? void 0,
|
|
3464
|
-
location: link.location ?? void 0,
|
|
3465
|
-
raw: link.raw ?? void 0
|
|
3466
|
-
};
|
|
3467
|
-
}
|
|
3468
|
-
|
|
3469
|
-
// plugins/core/analyzers/signal-collision/text.ts
|
|
3470
|
-
var SIGNAL_COLLISION_TEXTS = {
|
|
3471
|
-
/**
|
|
3472
|
-
* Per-Signal warn issue: two extractors detected something at
|
|
3473
|
-
* overlapping byte ranges within the same node and the resolver
|
|
3474
|
-
* dropped the loser. Surfaces WHO lost, WHO won, and the tiebreak
|
|
3475
|
-
* reason so the operator can understand why a candidate edge did NOT
|
|
3476
|
-
* become a Link.
|
|
3477
|
-
*
|
|
3478
|
-
* Placeholders are deliberately verbose because this is one of the
|
|
3479
|
-
* few diagnostic surfaces where the operator may need to disambiguate
|
|
3480
|
-
* a confusing graph (e.g. a `[link](path)` followed by `@path` inside
|
|
3481
|
-
* the same paragraph, the markdown-link wins and the at-directive
|
|
3482
|
-
* silently disappears without this warning).
|
|
3483
|
-
*/
|
|
3484
|
-
message: "`{{loserRaw}}`:\nOverlap collision: {{loserExtractor}} (at {{loserRange}}) lost to {{winnerExtractor}} (at {{winnerRange}}) by {{reason}}; only the winning edge persists.",
|
|
3485
|
-
/**
|
|
3486
|
-
* Same warn but for the rare case the resolver rejected a Signal
|
|
3487
|
-
* because the operator disabled its extractor via
|
|
3488
|
-
* `plugins.<id>.extensions.<extId>.enabled`. Phase 4+ stub: today the
|
|
3489
|
-
* filter is not wired so this template is unreachable from the
|
|
3490
|
-
* resolver; documented now so the analyzer stays forward-compatible
|
|
3491
|
-
* with the upcoming filter pass.
|
|
3492
|
-
*/
|
|
3493
|
-
messageExtractorDisabled: "`{{loserRaw}}`:\nDropped: extension `{{extractorId}}` is disabled. Re-enable it in Settings or via `sm plugins enable`.",
|
|
3494
|
-
/**
|
|
3495
|
-
* Same warn but for the future confidence floor case. Phase 4+ stub:
|
|
3496
|
-
* today the resolver materialises every winning candidate regardless
|
|
3497
|
-
* of confidence, so this template is unreachable; documented for
|
|
3498
|
-
* forward compatibility.
|
|
3499
|
-
*/
|
|
3500
|
-
messageBelowFloor: "`{{loserRaw}}`:\nDropped: confidence {{confidence}} is below the threshold {{threshold}}."
|
|
3501
|
-
};
|
|
3502
|
-
|
|
3503
|
-
// plugins/core/analyzers/signal-collision/index.ts
|
|
3504
|
-
var ID24 = "signal-collision";
|
|
3505
|
-
var signalCollisionAnalyzer = {
|
|
3506
|
-
id: ID24,
|
|
3507
|
-
pluginId: CORE_PLUGIN_ID,
|
|
3508
|
-
kind: "analyzer",
|
|
3509
|
-
description: "Reports when two extractors fight over the same span of body text, or when a candidate link is dropped (extractor disabled, confidence too low) before it reaches the graph.",
|
|
3510
|
-
mode: "deterministic",
|
|
3511
|
-
evaluate(ctx) {
|
|
3512
|
-
const signals = ctx.signals;
|
|
3513
|
-
if (!signals || signals.length === 0) return [];
|
|
3514
|
-
const issues = [];
|
|
3515
|
-
for (const signal of signals) {
|
|
3516
|
-
const issue = makeIssue(signal);
|
|
3517
|
-
if (issue) issues.push(issue);
|
|
3518
|
-
}
|
|
3519
|
-
return issues;
|
|
3520
|
-
}
|
|
3521
|
-
};
|
|
3522
|
-
function makeIssue(signal) {
|
|
3523
|
-
const resolution = signal.resolution;
|
|
3524
|
-
if (!resolution || resolution.outcome !== "rejected") return null;
|
|
3525
|
-
if (resolution.rejectedBy) {
|
|
3526
|
-
const winner = resolution.rejectedBy;
|
|
3527
|
-
const winnerCandidate = signal.candidates[resolution.winnerIndex ?? 0];
|
|
3528
|
-
const loserRange = signal.range ? `${signal.range.start}-${signal.range.end}` : "unknown";
|
|
3529
|
-
const winnerRange = `${winner.range.start}-${winner.range.end}`;
|
|
3530
|
-
return {
|
|
3531
|
-
analyzerId: ID24,
|
|
3532
|
-
severity: "warn",
|
|
3533
|
-
nodeIds: [signal.source],
|
|
3534
|
-
message: tx(SIGNAL_COLLISION_TEXTS.message, {
|
|
3535
|
-
loserExtractor: winnerCandidate.extractorId,
|
|
3536
|
-
loserRaw: signal.raw,
|
|
3537
|
-
loserRange,
|
|
3538
|
-
winnerExtractor: winner.extractorId,
|
|
3539
|
-
winnerRange,
|
|
3540
|
-
reason: winner.reason
|
|
3541
|
-
}),
|
|
3542
|
-
data: {
|
|
3543
|
-
loser: {
|
|
3544
|
-
extractorId: winnerCandidate.extractorId,
|
|
3545
|
-
raw: signal.raw,
|
|
3546
|
-
range: signal.range ?? null,
|
|
3547
|
-
candidate: {
|
|
3548
|
-
kind: winnerCandidate.kind,
|
|
3549
|
-
target: winnerCandidate.target,
|
|
3550
|
-
confidence: winnerCandidate.confidence
|
|
3551
|
-
}
|
|
3552
|
-
},
|
|
3553
|
-
winner: {
|
|
3554
|
-
extractorId: winner.extractorId,
|
|
3555
|
-
range: winner.range
|
|
3556
|
-
},
|
|
3557
|
-
reason: winner.reason
|
|
3558
|
-
}
|
|
3559
|
-
};
|
|
3560
|
-
}
|
|
3561
|
-
if (resolution.extractorDisabled) {
|
|
3562
|
-
const loserRange = signal.range ? `${signal.range.start}-${signal.range.end}` : "unknown";
|
|
3563
|
-
return {
|
|
3564
|
-
analyzerId: ID24,
|
|
3565
|
-
severity: "warn",
|
|
3566
|
-
nodeIds: [signal.source],
|
|
3567
|
-
message: tx(SIGNAL_COLLISION_TEXTS.messageExtractorDisabled, {
|
|
3568
|
-
extractorId: resolution.extractorDisabled.extractorId,
|
|
3569
|
-
loserRaw: signal.raw,
|
|
3570
|
-
loserRange
|
|
3571
|
-
}),
|
|
3572
|
-
data: {
|
|
3573
|
-
extractorDisabled: resolution.extractorDisabled,
|
|
3574
|
-
raw: signal.raw,
|
|
3575
|
-
range: signal.range ?? null
|
|
3576
|
-
}
|
|
3577
|
-
};
|
|
3578
|
-
}
|
|
3579
|
-
if (resolution.belowFloor) {
|
|
3580
|
-
const loserRange = signal.range ? `${signal.range.start}-${signal.range.end}` : "unknown";
|
|
3581
|
-
const topCandidate = signal.candidates[0];
|
|
3582
|
-
return {
|
|
3583
|
-
analyzerId: ID24,
|
|
3584
|
-
severity: "warn",
|
|
3585
|
-
nodeIds: [signal.source],
|
|
3586
|
-
message: tx(SIGNAL_COLLISION_TEXTS.messageBelowFloor, {
|
|
3587
|
-
loserRaw: signal.raw,
|
|
3588
|
-
loserRange,
|
|
3589
|
-
confidence: topCandidate.confidence,
|
|
3590
|
-
threshold: resolution.belowFloor.threshold
|
|
3591
|
-
}),
|
|
3592
|
-
data: {
|
|
3593
|
-
belowFloor: resolution.belowFloor,
|
|
3594
|
-
raw: signal.raw,
|
|
3595
|
-
range: signal.range ?? null
|
|
3596
|
-
}
|
|
3597
|
-
};
|
|
3598
|
-
}
|
|
3599
|
-
return null;
|
|
3600
|
-
}
|
|
3601
|
-
|
|
3602
|
-
// plugins/core/analyzers/trigger-collision/text.ts
|
|
3603
|
-
var TRIGGER_COLLISION_TEXTS = {
|
|
3604
|
-
/**
|
|
3605
|
-
* Top-level message when `analyzeTriggerBucket` accumulated exactly one
|
|
3606
|
-
* cause part. Used for the advertiser-ambiguous-only, invocation-
|
|
3607
|
-
* ambiguous-only, and cross-kind-only branches.
|
|
3608
|
-
*/
|
|
3609
|
-
messageOnePart: '"{{normalized}}":\nTrigger collision: {{part}}.',
|
|
3610
|
-
/**
|
|
3611
|
-
* Top-level message when `analyzeTriggerBucket` accumulated two cause
|
|
3612
|
-
* parts (advertiser-ambiguous AND invocation-ambiguous fire together).
|
|
3613
|
-
* The joiner lives inside the template so future locales can adapt it
|
|
3614
|
-
* (e.g. `'; y '` in Spanish) without touching the rule code.
|
|
3615
|
-
*/
|
|
3616
|
-
messageTwoParts: '"{{normalized}}":\nTrigger collision: {{first}}; and {{second}}.',
|
|
3617
|
-
/** `<n> advertisers: <list>` part, fires on the advertiser-ambiguous branch. */
|
|
3618
|
-
partAdvertisers: "{{count}} advertisers: {{paths}}",
|
|
3619
|
-
/** `<n> invocation forms: <list>` part, fires on the invocation-ambiguous branch. */
|
|
3620
|
-
partInvocations: "{{count}} invocation forms: {{forms}}",
|
|
3621
|
-
/** Singular cross-kind cause: `non-canonical invocation <form> against advertiser <path>`. */
|
|
3622
|
-
partNonCanonicalSingular: "non-canonical invocation {{forms}} against advertiser {{advertiser}}",
|
|
3623
|
-
/** Plural cross-kind cause: `non-canonical invocations <forms> against advertiser <path>`. */
|
|
3624
|
-
partNonCanonicalPlural: "non-canonical invocations {{forms}} against advertiser {{advertiser}}"
|
|
3625
|
-
};
|
|
3626
|
-
|
|
3627
|
-
// plugins/core/analyzers/trigger-collision/index.ts
|
|
3628
|
-
var ID25 = "trigger-collision";
|
|
3629
|
-
var ADVERTISING_KINDS = /* @__PURE__ */ new Set([
|
|
3630
|
-
"command",
|
|
3631
|
-
"skill",
|
|
3632
|
-
"agent"
|
|
3633
|
-
]);
|
|
3634
|
-
var triggerCollisionAnalyzer = {
|
|
3635
|
-
id: ID25,
|
|
3636
|
-
pluginId: CORE_PLUGIN_ID,
|
|
3637
|
-
kind: "analyzer",
|
|
3638
|
-
mode: "deterministic",
|
|
3639
|
-
description: "Flags two or more nodes that claim the same `/command` or `@agent` name.",
|
|
3640
|
-
// Two claim-collection passes (advertisement + invocation) feeding
|
|
3641
|
-
// the bucket map. Per-bucket analysis lives in `analyzeTriggerBucket`.
|
|
3642
|
-
// eslint-disable-next-line complexity
|
|
3643
|
-
evaluate(ctx) {
|
|
3644
|
-
const buckets = /* @__PURE__ */ new Map();
|
|
3645
|
-
const push = (key, claim) => {
|
|
3646
|
-
const bucket = buckets.get(key) ?? [];
|
|
3647
|
-
bucket.push(claim);
|
|
3648
|
-
buckets.set(key, bucket);
|
|
3649
|
-
};
|
|
3650
|
-
for (const node of ctx.nodes) {
|
|
3651
|
-
if (!ADVERTISING_KINDS.has(node.kind)) continue;
|
|
3652
|
-
const raw = node.frontmatter?.["name"];
|
|
3653
|
-
if (typeof raw !== "string" || raw.length === 0) continue;
|
|
3654
|
-
const normalized = `/${normalizeTrigger(raw)}`;
|
|
3655
|
-
if (normalized === "/") continue;
|
|
3656
|
-
push(normalized, {
|
|
3657
|
-
kind: "advertiser",
|
|
3658
|
-
token: node.path,
|
|
3659
|
-
nodeId: node.path,
|
|
3660
|
-
canonicalForm: `/${raw}`
|
|
3661
|
-
});
|
|
3662
|
-
}
|
|
3663
|
-
for (const link of ctx.links) {
|
|
3664
|
-
const normalized = link.trigger?.normalizedTrigger;
|
|
3665
|
-
if (!normalized) continue;
|
|
3666
|
-
push(normalized, {
|
|
3667
|
-
kind: "invocation",
|
|
3668
|
-
token: link.target,
|
|
3669
|
-
nodeId: link.source
|
|
3670
|
-
});
|
|
3671
|
-
}
|
|
3672
|
-
const issues = [];
|
|
3673
|
-
for (const [normalized, claims] of buckets) {
|
|
3674
|
-
const issue = analyzeTriggerBucket(normalized, claims);
|
|
3675
|
-
if (issue) issues.push(issue);
|
|
3676
|
-
}
|
|
3677
|
-
return issues;
|
|
3678
|
-
}
|
|
3679
|
-
};
|
|
3680
|
-
function analyzeTriggerBucket(normalized, claims) {
|
|
3681
|
-
const advertiserPaths = [
|
|
3682
|
-
...new Set(claims.filter((c) => c.kind === "advertiser").map((c) => c.token))
|
|
3683
|
-
].sort();
|
|
3684
|
-
const invocationTargets = [
|
|
3685
|
-
...new Set(claims.filter((c) => c.kind === "invocation").map((c) => c.token))
|
|
3686
|
-
].sort();
|
|
3687
|
-
const advertisers = claims.filter(
|
|
3688
|
-
(c) => c.kind === "advertiser"
|
|
3689
|
-
);
|
|
3690
|
-
const advertiserAmbiguous = advertiserPaths.length >= 2;
|
|
3691
|
-
const invocationAmbiguous = invocationTargets.length >= 2;
|
|
3692
|
-
const canonicalForms = new Set(advertisers.map((a) => a.canonicalForm));
|
|
3693
|
-
const nonCanonicalInvocations = invocationTargets.filter((t) => !canonicalForms.has(t));
|
|
3694
|
-
const crossKindAmbiguous = advertiserPaths.length === 1 && nonCanonicalInvocations.length >= 1;
|
|
3695
|
-
if (!advertiserAmbiguous && !invocationAmbiguous && !crossKindAmbiguous) {
|
|
3696
|
-
return null;
|
|
3697
|
-
}
|
|
3698
|
-
const nodeIds = [...new Set(claims.map((c) => c.nodeId))].sort();
|
|
3699
|
-
const parts = [];
|
|
3700
|
-
if (advertiserAmbiguous) {
|
|
3701
|
-
parts.push(
|
|
3702
|
-
tx(TRIGGER_COLLISION_TEXTS.partAdvertisers, {
|
|
3703
|
-
count: advertiserPaths.length,
|
|
3704
|
-
paths: advertiserPaths.join(", ")
|
|
3705
|
-
})
|
|
3706
|
-
);
|
|
3707
|
-
}
|
|
3708
|
-
if (invocationAmbiguous) {
|
|
3709
|
-
parts.push(
|
|
3710
|
-
tx(TRIGGER_COLLISION_TEXTS.partInvocations, {
|
|
3711
|
-
count: invocationTargets.length,
|
|
3712
|
-
forms: invocationTargets.join(", ")
|
|
3713
|
-
})
|
|
3714
|
-
);
|
|
3715
|
-
} else if (crossKindAmbiguous) {
|
|
3716
|
-
const template = nonCanonicalInvocations.length > 1 ? TRIGGER_COLLISION_TEXTS.partNonCanonicalPlural : TRIGGER_COLLISION_TEXTS.partNonCanonicalSingular;
|
|
3717
|
-
parts.push(
|
|
3718
|
-
tx(template, {
|
|
3719
|
-
forms: nonCanonicalInvocations.join(", "),
|
|
3720
|
-
advertiser: advertiserPaths[0]
|
|
3721
|
-
})
|
|
3722
|
-
);
|
|
3723
|
-
}
|
|
3724
|
-
const message = parts.length === 2 ? tx(TRIGGER_COLLISION_TEXTS.messageTwoParts, {
|
|
3725
|
-
normalized,
|
|
3726
|
-
first: parts[0],
|
|
3727
|
-
second: parts[1]
|
|
3728
|
-
}) : tx(TRIGGER_COLLISION_TEXTS.messageOnePart, {
|
|
3729
|
-
normalized,
|
|
3730
|
-
part: parts[0]
|
|
3731
|
-
});
|
|
3517
|
+
function toLinkForSchema(link) {
|
|
3732
3518
|
return {
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
}
|
|
3519
|
+
source: link.source,
|
|
3520
|
+
target: link.target,
|
|
3521
|
+
kind: link.kind,
|
|
3522
|
+
confidence: link.confidence,
|
|
3523
|
+
sources: link.sources,
|
|
3524
|
+
trigger: link.trigger ?? void 0,
|
|
3525
|
+
location: link.location ?? void 0,
|
|
3526
|
+
raw: link.raw ?? void 0
|
|
3742
3527
|
};
|
|
3743
3528
|
}
|
|
3744
3529
|
|
|
@@ -3770,13 +3555,13 @@ var ASCII_FORMATTER_TEXTS = {
|
|
|
3770
3555
|
};
|
|
3771
3556
|
|
|
3772
3557
|
// plugins/core/formatters/ascii/index.ts
|
|
3773
|
-
var
|
|
3558
|
+
var ID25 = "ascii";
|
|
3774
3559
|
var KIND_ORDER = ["agent", "command", "skill", "markdown"];
|
|
3775
3560
|
var asciiFormatter = {
|
|
3776
|
-
id:
|
|
3561
|
+
id: ID25,
|
|
3777
3562
|
pluginId: CORE_PLUGIN_ID,
|
|
3778
3563
|
kind: "formatter",
|
|
3779
|
-
formatId:
|
|
3564
|
+
formatId: ID25,
|
|
3780
3565
|
description: "Renders the scan as plain text in three sections: nodes (grouped by kind), arrows, and issues. Used by `sm scan --format ascii`.",
|
|
3781
3566
|
// ASCII tree formatter, header + per-kind sections + per-issue
|
|
3782
3567
|
// section. Each section iterates and renders; splitting per section
|
|
@@ -3870,13 +3655,13 @@ function renderSection(out, kind, group) {
|
|
|
3870
3655
|
}
|
|
3871
3656
|
|
|
3872
3657
|
// plugins/core/formatters/json/index.ts
|
|
3873
|
-
var
|
|
3658
|
+
var ID26 = "json";
|
|
3874
3659
|
var jsonFormatter = {
|
|
3875
|
-
id:
|
|
3660
|
+
id: ID26,
|
|
3876
3661
|
pluginId: CORE_PLUGIN_ID,
|
|
3877
3662
|
kind: "formatter",
|
|
3878
3663
|
description: "Renders the persisted scan as JSON (conforms to `scan-result.schema.json`). Used by `sm graph --format json` and `GET /api/graph?format=json`.",
|
|
3879
|
-
formatId:
|
|
3664
|
+
formatId: ID26,
|
|
3880
3665
|
format(ctx) {
|
|
3881
3666
|
if (ctx.scanResult !== void 0) {
|
|
3882
3667
|
return JSON.stringify(ctx.scanResult);
|
|
@@ -3891,7 +3676,7 @@ var jsonFormatter = {
|
|
|
3891
3676
|
|
|
3892
3677
|
// kernel/sidecar/parse.ts
|
|
3893
3678
|
import { existsSync, readFileSync as readFileSync3 } from "fs";
|
|
3894
|
-
import { dirname as dirname3, resolve as
|
|
3679
|
+
import { dirname as dirname3, resolve as resolve4 } from "path";
|
|
3895
3680
|
import { createRequire as createRequire3 } from "module";
|
|
3896
3681
|
import { Ajv2020 as Ajv20203 } from "ajv/dist/2020.js";
|
|
3897
3682
|
import yaml from "js-yaml";
|
|
@@ -3993,10 +3778,10 @@ function getSidecarValidator() {
|
|
|
3993
3778
|
applyAjvFormats(ajv);
|
|
3994
3779
|
const specRoot = resolveSpecRoot2();
|
|
3995
3780
|
const annotationsSchema = JSON.parse(
|
|
3996
|
-
readFileSync3(
|
|
3781
|
+
readFileSync3(resolve4(specRoot, "schemas/annotations.schema.json"), "utf8")
|
|
3997
3782
|
);
|
|
3998
3783
|
const sidecarSchema = JSON.parse(
|
|
3999
|
-
readFileSync3(
|
|
3784
|
+
readFileSync3(resolve4(specRoot, "schemas/sidecar.schema.json"), "utf8")
|
|
4000
3785
|
);
|
|
4001
3786
|
ajv.addSchema(annotationsSchema);
|
|
4002
3787
|
cachedSidecarValidator = ajv.compile(sidecarSchema);
|
|
@@ -4023,13 +3808,13 @@ var BUMP_TEXTS = {
|
|
|
4023
3808
|
};
|
|
4024
3809
|
|
|
4025
3810
|
// plugins/core/actions/node-bump/index.ts
|
|
4026
|
-
var
|
|
3811
|
+
var ID27 = "node-bump";
|
|
4027
3812
|
var bumpButton = {
|
|
4028
3813
|
slot: "inspector.action.button",
|
|
4029
3814
|
priority: 10
|
|
4030
3815
|
};
|
|
4031
3816
|
var nodeBumpAction = {
|
|
4032
|
-
id:
|
|
3817
|
+
id: ID27,
|
|
4033
3818
|
pluginId: CORE_PLUGIN_ID,
|
|
4034
3819
|
kind: "action",
|
|
4035
3820
|
description: "Marks a node as updated: bumps `annotations.version`, refreshes sidecar hashes, and records the timestamp.",
|
|
@@ -4109,9 +3894,9 @@ function pickCurrentVersion(overlay) {
|
|
|
4109
3894
|
|
|
4110
3895
|
// plugins/core/actions/node-set-stability/index.ts
|
|
4111
3896
|
var STABILITY_VALUES = ["experimental", "stable", "deprecated"];
|
|
4112
|
-
var
|
|
3897
|
+
var ID28 = "node-set-stability";
|
|
4113
3898
|
var nodeSetStabilityAction = {
|
|
4114
|
-
id:
|
|
3899
|
+
id: ID28,
|
|
4115
3900
|
pluginId: CORE_PLUGIN_ID,
|
|
4116
3901
|
kind: "action",
|
|
4117
3902
|
description: "Sets the lifecycle stage of the current node (writes `stability` to the sidecar).",
|
|
@@ -4159,13 +3944,13 @@ var TAGS_TEXTS = {
|
|
|
4159
3944
|
};
|
|
4160
3945
|
|
|
4161
3946
|
// plugins/core/actions/node-set-tags/index.ts
|
|
4162
|
-
var
|
|
3947
|
+
var ID29 = "node-set-tags";
|
|
4163
3948
|
var setTagsButton = {
|
|
4164
3949
|
slot: "inspector.action.button",
|
|
4165
3950
|
priority: 15
|
|
4166
3951
|
};
|
|
4167
3952
|
var nodeSetTagsAction = {
|
|
4168
|
-
id:
|
|
3953
|
+
id: ID29,
|
|
4169
3954
|
pluginId: CORE_PLUGIN_ID,
|
|
4170
3955
|
kind: "action",
|
|
4171
3956
|
description: "Sets the taxonomy tags of the current node (writes `tags` to the sidecar; whole-array replace).",
|
|
@@ -4242,13 +4027,13 @@ var SUPERSEDE_TEXTS = {
|
|
|
4242
4027
|
};
|
|
4243
4028
|
|
|
4244
4029
|
// plugins/core/actions/node-supersede/index.ts
|
|
4245
|
-
var
|
|
4030
|
+
var ID30 = "node-supersede";
|
|
4246
4031
|
var supersedeButton = {
|
|
4247
4032
|
slot: "inspector.action.button",
|
|
4248
4033
|
priority: 10
|
|
4249
4034
|
};
|
|
4250
4035
|
var nodeSupersedeAction = {
|
|
4251
|
-
id:
|
|
4036
|
+
id: ID30,
|
|
4252
4037
|
pluginId: CORE_PLUGIN_ID,
|
|
4253
4038
|
kind: "action",
|
|
4254
4039
|
description: "Declares the current node as superseded by another (writes `supersededBy` to the sidecar).",
|
|
@@ -4536,7 +4321,7 @@ function writeJsonAtomic(path, content) {
|
|
|
4536
4321
|
}
|
|
4537
4322
|
|
|
4538
4323
|
// core/paths/db-path.ts
|
|
4539
|
-
import { join as join2, resolve as
|
|
4324
|
+
import { join as join2, resolve as resolve5 } from "path";
|
|
4540
4325
|
|
|
4541
4326
|
// kernel/util/skill-map-paths.ts
|
|
4542
4327
|
import { join } from "path";
|
|
@@ -4564,17 +4349,17 @@ var GITIGNORE_ENTRIES = [
|
|
|
4564
4349
|
`${SKILL_MAP_DIR}/${DB_FILENAME}`
|
|
4565
4350
|
];
|
|
4566
4351
|
function resolveDbPath(options) {
|
|
4567
|
-
if (options.db) return
|
|
4568
|
-
return
|
|
4352
|
+
if (options.db) return resolve5(options.db);
|
|
4353
|
+
return resolve5(options.cwd, DEFAULT_DB_REL);
|
|
4569
4354
|
}
|
|
4570
4355
|
function defaultProjectDbPath(ctx) {
|
|
4571
|
-
return
|
|
4356
|
+
return resolve5(ctx.cwd, DEFAULT_DB_REL);
|
|
4572
4357
|
}
|
|
4573
4358
|
function defaultProjectJobsDir(ctx) {
|
|
4574
|
-
return
|
|
4359
|
+
return resolve5(ctx.cwd, SKILL_MAP_DIR, JOBS_DIRNAME);
|
|
4575
4360
|
}
|
|
4576
4361
|
function defaultProjectPluginsDir(ctx) {
|
|
4577
|
-
return
|
|
4362
|
+
return resolve5(ctx.cwd, SKILL_MAP_DIR, PLUGINS_DIRNAME);
|
|
4578
4363
|
}
|
|
4579
4364
|
function defaultDbPath(scopeRoot) {
|
|
4580
4365
|
return join2(scopeRoot, SKILL_MAP_DIR, DB_FILENAME);
|
|
@@ -4823,19 +4608,18 @@ var annotationFieldUnknownAnalyzer2 = { ...annotationFieldUnknownAnalyzer, plugi
|
|
|
4823
4608
|
var annotationOrphanAnalyzer2 = { ...annotationOrphanAnalyzer, pluginId: "core", version: VERSION };
|
|
4824
4609
|
var annotationStaleAnalyzer2 = { ...annotationStaleAnalyzer, pluginId: "core", version: VERSION };
|
|
4825
4610
|
var contributionOrphanAnalyzer2 = { ...contributionOrphanAnalyzer, pluginId: "core", version: VERSION };
|
|
4611
|
+
var extractorCollisionAnalyzer2 = { ...extractorCollisionAnalyzer, pluginId: "core", version: VERSION };
|
|
4826
4612
|
var issueCounterAnalyzer2 = { ...issueCounterAnalyzer, pluginId: "core", version: VERSION };
|
|
4827
|
-
var jobFileOrphanAnalyzer2 = { ...jobFileOrphanAnalyzer, pluginId: "core", version: VERSION };
|
|
4828
|
-
var linkConflictAnalyzer2 = { ...linkConflictAnalyzer, pluginId: "core", version: VERSION };
|
|
4829
4613
|
var linkCounterAnalyzer2 = { ...linkCounterAnalyzer, pluginId: "core", version: VERSION };
|
|
4614
|
+
var linkKindConflictAnalyzer2 = { ...linkKindConflictAnalyzer, pluginId: "core", version: VERSION };
|
|
4830
4615
|
var linkSelfLoopAnalyzer2 = { ...linkSelfLoopAnalyzer, pluginId: "core", version: VERSION };
|
|
4616
|
+
var nameCollisionAnalyzer2 = { ...nameCollisionAnalyzer, pluginId: "core", version: VERSION };
|
|
4831
4617
|
var nameReservedAnalyzer2 = { ...nameReservedAnalyzer, pluginId: "core", version: VERSION };
|
|
4832
4618
|
var nodeStabilityAnalyzer2 = { ...nodeStabilityAnalyzer, pluginId: "core", version: VERSION };
|
|
4833
4619
|
var nodeSupersededAnalyzer2 = { ...nodeSupersededAnalyzer, pluginId: "core", version: VERSION };
|
|
4834
4620
|
var referenceBrokenAnalyzer2 = { ...referenceBrokenAnalyzer, pluginId: "core", version: VERSION };
|
|
4835
4621
|
var referenceRedundantAnalyzer2 = { ...referenceRedundantAnalyzer, pluginId: "core", version: VERSION };
|
|
4836
4622
|
var schemaViolationAnalyzer2 = { ...schemaViolationAnalyzer, pluginId: "core", version: VERSION };
|
|
4837
|
-
var signalCollisionAnalyzer2 = { ...signalCollisionAnalyzer, pluginId: "core", version: VERSION };
|
|
4838
|
-
var triggerCollisionAnalyzer2 = { ...triggerCollisionAnalyzer, pluginId: "core", version: VERSION };
|
|
4839
4623
|
var asciiFormatter2 = { ...asciiFormatter, pluginId: "core", version: VERSION };
|
|
4840
4624
|
var jsonFormatter2 = { ...jsonFormatter, pluginId: "core", version: VERSION };
|
|
4841
4625
|
var nodeBumpAction2 = { ...nodeBumpAction, pluginId: "core", version: VERSION };
|
|
@@ -4889,19 +4673,18 @@ var builtInPlugins = [
|
|
|
4889
4673
|
annotationOrphanAnalyzer2,
|
|
4890
4674
|
annotationStaleAnalyzer2,
|
|
4891
4675
|
contributionOrphanAnalyzer2,
|
|
4676
|
+
extractorCollisionAnalyzer2,
|
|
4892
4677
|
issueCounterAnalyzer2,
|
|
4893
|
-
jobFileOrphanAnalyzer2,
|
|
4894
|
-
linkConflictAnalyzer2,
|
|
4895
4678
|
linkCounterAnalyzer2,
|
|
4679
|
+
linkKindConflictAnalyzer2,
|
|
4896
4680
|
linkSelfLoopAnalyzer2,
|
|
4681
|
+
nameCollisionAnalyzer2,
|
|
4897
4682
|
nameReservedAnalyzer2,
|
|
4898
4683
|
nodeStabilityAnalyzer2,
|
|
4899
4684
|
nodeSupersededAnalyzer2,
|
|
4900
4685
|
referenceBrokenAnalyzer2,
|
|
4901
4686
|
referenceRedundantAnalyzer2,
|
|
4902
4687
|
schemaViolationAnalyzer2,
|
|
4903
|
-
signalCollisionAnalyzer2,
|
|
4904
|
-
triggerCollisionAnalyzer2,
|
|
4905
4688
|
asciiFormatter2,
|
|
4906
4689
|
jsonFormatter2,
|
|
4907
4690
|
nodeBumpAction2,
|
|
@@ -5720,7 +5503,7 @@ import { Command as Command2, Option as Option2 } from "clipanion";
|
|
|
5720
5503
|
|
|
5721
5504
|
// core/config/helper.ts
|
|
5722
5505
|
import { homedir as osHomedir } from "os";
|
|
5723
|
-
import { isAbsolute, join as join4, resolve as
|
|
5506
|
+
import { isAbsolute, join as join4, resolve as resolve6, sep } from "path";
|
|
5724
5507
|
|
|
5725
5508
|
// kernel/config/loader.ts
|
|
5726
5509
|
import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
|
|
@@ -6138,13 +5921,13 @@ function projectPathExposure(inputs) {
|
|
|
6138
5921
|
return { expandsSurface: true, exposedPaths: exposed };
|
|
6139
5922
|
}
|
|
6140
5923
|
function resolveScanPathForExposure(raw, cwd) {
|
|
6141
|
-
if (raw.startsWith("~/")) return
|
|
6142
|
-
if (raw === "~") return
|
|
6143
|
-
if (isAbsolute(raw)) return
|
|
6144
|
-
return
|
|
5924
|
+
if (raw.startsWith("~/")) return resolve6(join4(osHomedir(), raw.slice(2)));
|
|
5925
|
+
if (raw === "~") return resolve6(osHomedir());
|
|
5926
|
+
if (isAbsolute(raw)) return resolve6(raw);
|
|
5927
|
+
return resolve6(cwd, raw);
|
|
6145
5928
|
}
|
|
6146
5929
|
function isUnderProject(absPath, cwd) {
|
|
6147
|
-
const projectRoot =
|
|
5930
|
+
const projectRoot = resolve6(cwd);
|
|
6148
5931
|
return absPath === projectRoot || absPath.startsWith(`${projectRoot}${sep}`);
|
|
6149
5932
|
}
|
|
6150
5933
|
|
|
@@ -6185,7 +5968,7 @@ function ensureSidecarWritesAllowed(opts) {
|
|
|
6185
5968
|
|
|
6186
5969
|
// kernel/sidecar/store.ts
|
|
6187
5970
|
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
|
|
6188
|
-
import { dirname as dirname5, resolve as
|
|
5971
|
+
import { dirname as dirname5, resolve as resolve7 } from "path";
|
|
6189
5972
|
import { createRequire as createRequire4 } from "module";
|
|
6190
5973
|
import { Ajv2020 as Ajv20204 } from "ajv/dist/2020.js";
|
|
6191
5974
|
import yaml2 from "js-yaml";
|
|
@@ -6293,10 +6076,10 @@ function getSidecarValidator2() {
|
|
|
6293
6076
|
applyAjvFormats(ajv);
|
|
6294
6077
|
const specRoot = resolveSpecRoot3();
|
|
6295
6078
|
const annotationsSchema = JSON.parse(
|
|
6296
|
-
readFileSync7(
|
|
6079
|
+
readFileSync7(resolve7(specRoot, "schemas/annotations.schema.json"), "utf8")
|
|
6297
6080
|
);
|
|
6298
6081
|
const sidecarSchema = JSON.parse(
|
|
6299
|
-
readFileSync7(
|
|
6082
|
+
readFileSync7(resolve7(specRoot, "schemas/sidecar.schema.json"), "utf8")
|
|
6300
6083
|
);
|
|
6301
6084
|
ajv.addSchema(annotationsSchema);
|
|
6302
6085
|
cachedValidator = ajv.compile(sidecarSchema);
|
|
@@ -6416,11 +6199,11 @@ async function confirm(question, streams, opts) {
|
|
|
6416
6199
|
// cli/util/git.ts
|
|
6417
6200
|
import { spawnSync } from "child_process";
|
|
6418
6201
|
import { existsSync as existsSync7 } from "fs";
|
|
6419
|
-
import { dirname as dirname6, resolve as
|
|
6202
|
+
import { dirname as dirname6, resolve as resolve8 } from "path";
|
|
6420
6203
|
function isInsideGitRepo(cwd) {
|
|
6421
6204
|
let current = cwd;
|
|
6422
6205
|
while (true) {
|
|
6423
|
-
if (existsSync7(
|
|
6206
|
+
if (existsSync7(resolve8(current, ".git"))) return true;
|
|
6424
6207
|
const parent = dirname6(current);
|
|
6425
6208
|
if (parent === current) return false;
|
|
6426
6209
|
current = parent;
|
|
@@ -6619,7 +6402,7 @@ import { existsSync as existsSync11 } from "fs";
|
|
|
6619
6402
|
|
|
6620
6403
|
// kernel/adapters/sqlite/storage-adapter.ts
|
|
6621
6404
|
import { mkdirSync as mkdirSync4 } from "fs";
|
|
6622
|
-
import { dirname as dirname8, resolve as
|
|
6405
|
+
import { dirname as dirname8, resolve as resolve11 } from "path";
|
|
6623
6406
|
import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
|
|
6624
6407
|
import { CamelCasePlugin, Kysely, sql as sql3 } from "kysely";
|
|
6625
6408
|
|
|
@@ -7178,7 +6961,7 @@ async function migrateNodeFavorites(trx, fromPath, toPath, report) {
|
|
|
7178
6961
|
}
|
|
7179
6962
|
|
|
7180
6963
|
// kernel/adapters/sqlite/jobs.ts
|
|
7181
|
-
import { resolve as
|
|
6964
|
+
import { resolve as resolve9 } from "path";
|
|
7182
6965
|
async function pruneTerminalJobs(db, status, cutoffMs) {
|
|
7183
6966
|
const rows = await db.selectFrom("state_jobs").select(["id", "filePath"]).where("status", "=", status).where("finishedAt", "is not", null).where("finishedAt", "<", cutoffMs).execute();
|
|
7184
6967
|
if (rows.length === 0) {
|
|
@@ -7193,14 +6976,14 @@ async function selectReferencedJobFilePaths(db) {
|
|
|
7193
6976
|
const rows = await db.selectFrom("state_jobs").select(["filePath"]).where("filePath", "is not", null).execute();
|
|
7194
6977
|
const out = /* @__PURE__ */ new Set();
|
|
7195
6978
|
for (const row of rows) {
|
|
7196
|
-
if (row.filePath !== null) out.add(
|
|
6979
|
+
if (row.filePath !== null) out.add(resolve9(row.filePath));
|
|
7197
6980
|
}
|
|
7198
6981
|
return out;
|
|
7199
6982
|
}
|
|
7200
6983
|
|
|
7201
6984
|
// kernel/adapters/sqlite/migrations.ts
|
|
7202
6985
|
import { copyFileSync, existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync8, readdirSync } from "fs";
|
|
7203
|
-
import { dirname as dirname7, join as join5, resolve as
|
|
6986
|
+
import { dirname as dirname7, join as join5, resolve as resolve10 } from "path";
|
|
7204
6987
|
import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
|
|
7205
6988
|
import { fileURLToPath } from "url";
|
|
7206
6989
|
|
|
@@ -7215,9 +6998,9 @@ var MIGRATIONS_TEXTS = {
|
|
|
7215
6998
|
var FILE_RE = /^(\d{3})_([a-z0-9_]+)\.sql$/;
|
|
7216
6999
|
function defaultMigrationsDir() {
|
|
7217
7000
|
const here = dirname7(fileURLToPath(import.meta.url));
|
|
7218
|
-
const flatLayout =
|
|
7001
|
+
const flatLayout = resolve10(here, "migrations");
|
|
7219
7002
|
if (existsSync8(flatLayout)) return flatLayout;
|
|
7220
|
-
return
|
|
7003
|
+
return resolve10(here, "..", "..", "..", "migrations");
|
|
7221
7004
|
}
|
|
7222
7005
|
function discoverMigrations(dir = defaultMigrationsDir()) {
|
|
7223
7006
|
if (!existsSync8(dir)) return [];
|
|
@@ -7287,7 +7070,7 @@ function resolveMigrationTarget(to, files) {
|
|
|
7287
7070
|
function writePreMigrateBackup(dbPath, target) {
|
|
7288
7071
|
return writeBackup(
|
|
7289
7072
|
dbPath,
|
|
7290
|
-
join5(dirname7(
|
|
7073
|
+
join5(dirname7(resolve10(dbPath)), "backups", `skill-map-pre-migrate-v${target}.db`)
|
|
7291
7074
|
);
|
|
7292
7075
|
}
|
|
7293
7076
|
function applyOneMigration(db, migration) {
|
|
@@ -7322,8 +7105,8 @@ function applyOneMigration(db, migration) {
|
|
|
7322
7105
|
}
|
|
7323
7106
|
function writeBackup(dbPath, destPath) {
|
|
7324
7107
|
if (dbPath === ":memory:") return null;
|
|
7325
|
-
const absoluteSource =
|
|
7326
|
-
const absoluteDest =
|
|
7108
|
+
const absoluteSource = resolve10(dbPath);
|
|
7109
|
+
const absoluteDest = resolve10(destPath);
|
|
7327
7110
|
mkdirSync3(dirname7(absoluteDest), { recursive: true });
|
|
7328
7111
|
const db = new DatabaseSync2(absoluteSource);
|
|
7329
7112
|
try {
|
|
@@ -8284,6 +8067,36 @@ function rowToContribution(row) {
|
|
|
8284
8067
|
};
|
|
8285
8068
|
}
|
|
8286
8069
|
|
|
8070
|
+
// kernel/adapters/sqlite/link-scores.ts
|
|
8071
|
+
async function replaceAllScanLinkScores(trx, linkScores) {
|
|
8072
|
+
await trx.deleteFrom("scan_link_scores").execute();
|
|
8073
|
+
if (linkScores.length === 0) return;
|
|
8074
|
+
const CHUNK = 90;
|
|
8075
|
+
for (let i = 0; i < linkScores.length; i += CHUNK) {
|
|
8076
|
+
const slice = linkScores.slice(i, i + CHUNK);
|
|
8077
|
+
const rows = slice.map(adjustmentToRow);
|
|
8078
|
+
await trx.insertInto("scan_link_scores").values(rows).execute();
|
|
8079
|
+
}
|
|
8080
|
+
}
|
|
8081
|
+
function adjustmentToRow(adj) {
|
|
8082
|
+
return {
|
|
8083
|
+
pluginId: adj.pluginId,
|
|
8084
|
+
extensionId: adj.extensionId,
|
|
8085
|
+
sourcePath: adj.link.source,
|
|
8086
|
+
target: adj.link.target,
|
|
8087
|
+
kind: adj.link.kind,
|
|
8088
|
+
normalizedTrigger: adj.link.trigger?.normalizedTrigger ?? null,
|
|
8089
|
+
opKind: adj.op.kind,
|
|
8090
|
+
opValue: adj.op.value,
|
|
8091
|
+
// FOLDED final confidence: by the time this writer runs, the
|
|
8092
|
+
// orchestrator has already applied every buffered op into
|
|
8093
|
+
// `link.confidence` (see `applyConfidenceAdjustments`). Denormalised
|
|
8094
|
+
// per row so the audit read needs no join.
|
|
8095
|
+
resultConfidence: adj.link.confidence,
|
|
8096
|
+
emittedAt: Date.now()
|
|
8097
|
+
};
|
|
8098
|
+
}
|
|
8099
|
+
|
|
8287
8100
|
// kernel/adapters/sqlite/schema-fingerprint.ts
|
|
8288
8101
|
import { createHash } from "crypto";
|
|
8289
8102
|
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
|
|
@@ -8370,8 +8183,18 @@ async function findNodesByTag(db, tag) {
|
|
|
8370
8183
|
}
|
|
8371
8184
|
|
|
8372
8185
|
// kernel/adapters/sqlite/scan-persistence.ts
|
|
8373
|
-
async function persistScanResult(db, result,
|
|
8186
|
+
async function persistScanResult(db, result, inputs = {}) {
|
|
8374
8187
|
const scannedAt = validateScannedAt(result.scannedAt);
|
|
8188
|
+
const {
|
|
8189
|
+
renameOps,
|
|
8190
|
+
extractorRuns,
|
|
8191
|
+
enrichments,
|
|
8192
|
+
contributions,
|
|
8193
|
+
registeredContributionKeys,
|
|
8194
|
+
freshlyRunTuples,
|
|
8195
|
+
contributionErrors,
|
|
8196
|
+
linkScores
|
|
8197
|
+
} = resolvePersistInputs(inputs);
|
|
8375
8198
|
const renames = [];
|
|
8376
8199
|
await db.transaction().execute(async (trx) => {
|
|
8377
8200
|
await applyRenames(trx, renameOps, renames);
|
|
@@ -8386,6 +8209,7 @@ async function persistScanResult(db, result, renameOps = [], extractorRuns = [],
|
|
|
8386
8209
|
freshlyRunTuples
|
|
8387
8210
|
);
|
|
8388
8211
|
await replaceAllScanContributionErrors(trx, contributionErrors);
|
|
8212
|
+
await replaceAllScanLinkScores(trx, linkScores);
|
|
8389
8213
|
const tagRecords = nodesToTagRecords(result.nodes);
|
|
8390
8214
|
await replaceAllScanTags(trx, tagRecords, livePathsForContrib);
|
|
8391
8215
|
await upsertEnrichmentLayer(trx, result, renameOps, enrichments);
|
|
@@ -8394,6 +8218,19 @@ async function persistScanResult(db, result, renameOps = [], extractorRuns = [],
|
|
|
8394
8218
|
await sql2`PRAGMA wal_checkpoint(TRUNCATE)`.execute(db);
|
|
8395
8219
|
return { renames };
|
|
8396
8220
|
}
|
|
8221
|
+
function resolvePersistInputs(inputs) {
|
|
8222
|
+
return {
|
|
8223
|
+
renameOps: [],
|
|
8224
|
+
extractorRuns: [],
|
|
8225
|
+
enrichments: [],
|
|
8226
|
+
contributions: [],
|
|
8227
|
+
registeredContributionKeys: /* @__PURE__ */ new Set(),
|
|
8228
|
+
freshlyRunTuples: /* @__PURE__ */ new Set(),
|
|
8229
|
+
contributionErrors: [],
|
|
8230
|
+
linkScores: [],
|
|
8231
|
+
...inputs
|
|
8232
|
+
};
|
|
8233
|
+
}
|
|
8397
8234
|
function validateScannedAt(scannedAt) {
|
|
8398
8235
|
if (!Number.isInteger(scannedAt) || scannedAt < 0) {
|
|
8399
8236
|
throw new Error(
|
|
@@ -8762,7 +8599,7 @@ var SqliteStorageAdapter = class {
|
|
|
8762
8599
|
if (this.#db) return;
|
|
8763
8600
|
const path = this.#options.databasePath;
|
|
8764
8601
|
if (path !== ":memory:") {
|
|
8765
|
-
const absolute =
|
|
8602
|
+
const absolute = resolve11(path);
|
|
8766
8603
|
mkdirSync4(dirname8(absolute), { recursive: true });
|
|
8767
8604
|
}
|
|
8768
8605
|
if (this.#options.autoMigrate !== false) {
|
|
@@ -8899,17 +8736,7 @@ var SqliteStorageAdapter = class {
|
|
|
8899
8736
|
};
|
|
8900
8737
|
async function persistScansThroughNonTx(db, result, opts) {
|
|
8901
8738
|
const defaults = applyPersistDefaults(opts);
|
|
8902
|
-
await persistScanResult(
|
|
8903
|
-
db,
|
|
8904
|
-
result,
|
|
8905
|
-
defaults.renameOps,
|
|
8906
|
-
defaults.extractorRuns,
|
|
8907
|
-
defaults.enrichments,
|
|
8908
|
-
defaults.contributions,
|
|
8909
|
-
defaults.registeredContributionKeys,
|
|
8910
|
-
defaults.freshlyRunTuples,
|
|
8911
|
-
defaults.contributionErrors
|
|
8912
|
-
);
|
|
8739
|
+
await persistScanResult(db, result, defaults);
|
|
8913
8740
|
}
|
|
8914
8741
|
function applyPersistDefaults(opts) {
|
|
8915
8742
|
return {
|
|
@@ -8920,6 +8747,7 @@ function applyPersistDefaults(opts) {
|
|
|
8920
8747
|
registeredContributionKeys: /* @__PURE__ */ new Set(),
|
|
8921
8748
|
freshlyRunTuples: /* @__PURE__ */ new Set(),
|
|
8922
8749
|
contributionErrors: [],
|
|
8750
|
+
linkScores: [],
|
|
8923
8751
|
...opts
|
|
8924
8752
|
};
|
|
8925
8753
|
}
|
|
@@ -9076,17 +8904,7 @@ function buildTxSubset(trx) {
|
|
|
9076
8904
|
scans: {
|
|
9077
8905
|
persist: (result, opts) => {
|
|
9078
8906
|
const d = applyPersistDefaults(opts);
|
|
9079
|
-
return persistScanResult(
|
|
9080
|
-
trx,
|
|
9081
|
-
result,
|
|
9082
|
-
d.renameOps,
|
|
9083
|
-
d.extractorRuns,
|
|
9084
|
-
d.enrichments,
|
|
9085
|
-
d.contributions,
|
|
9086
|
-
d.registeredContributionKeys,
|
|
9087
|
-
d.freshlyRunTuples,
|
|
9088
|
-
d.contributionErrors
|
|
9089
|
-
).then(() => void 0);
|
|
8907
|
+
return persistScanResult(trx, result, d).then(() => void 0);
|
|
9090
8908
|
}
|
|
9091
8909
|
},
|
|
9092
8910
|
issues: {
|
|
@@ -9380,16 +9198,16 @@ async function tryWithSqlite(options, fn) {
|
|
|
9380
9198
|
}
|
|
9381
9199
|
|
|
9382
9200
|
// cli/commands/bump-plan.ts
|
|
9383
|
-
import { resolve as
|
|
9201
|
+
import { resolve as resolve13 } from "path";
|
|
9384
9202
|
|
|
9385
9203
|
// core/paths/path-guard.ts
|
|
9386
9204
|
import { lstatSync } from "fs";
|
|
9387
|
-
import { isAbsolute as isAbsolute2, resolve as
|
|
9205
|
+
import { isAbsolute as isAbsolute2, resolve as resolve12, sep as sep2 } from "path";
|
|
9388
9206
|
function assertContained(cwd, rel) {
|
|
9389
9207
|
if (isAbsolute2(rel)) {
|
|
9390
9208
|
throw new Error(`node path is absolute, refusing to read: ${rel}`);
|
|
9391
9209
|
}
|
|
9392
|
-
const abs =
|
|
9210
|
+
const abs = resolve12(cwd, rel);
|
|
9393
9211
|
if (abs !== cwd && !abs.startsWith(cwd + sep2)) {
|
|
9394
9212
|
throw new Error(`node path escapes repo root: ${rel}`);
|
|
9395
9213
|
}
|
|
@@ -9424,7 +9242,7 @@ function planOne(node, options, invoker) {
|
|
|
9424
9242
|
let absPath;
|
|
9425
9243
|
try {
|
|
9426
9244
|
assertContained(options.cwd, node.path);
|
|
9427
|
-
absPath =
|
|
9245
|
+
absPath = resolve13(options.cwd, node.path);
|
|
9428
9246
|
} catch (err) {
|
|
9429
9247
|
return {
|
|
9430
9248
|
nodePath: node.path,
|
|
@@ -10047,18 +9865,18 @@ var PLUGIN_LOADER_TEXTS = {
|
|
|
10047
9865
|
// kernel/adapters/plugin-loader/index.ts
|
|
10048
9866
|
import { createRequire as createRequire5 } from "module";
|
|
10049
9867
|
import { existsSync as existsSync13, readFileSync as readFileSync13, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
|
|
10050
|
-
import { join as join8, resolve as
|
|
9868
|
+
import { join as join8, resolve as resolve16 } from "path";
|
|
10051
9869
|
import { pathToFileURL } from "url";
|
|
10052
9870
|
import semver from "semver";
|
|
10053
9871
|
|
|
10054
9872
|
// kernel/adapters/plugin-loader/id-utils.ts
|
|
10055
|
-
import { isAbsolute as isAbsolute3, relative, resolve as
|
|
9873
|
+
import { isAbsolute as isAbsolute3, relative, resolve as resolve14 } from "path";
|
|
10056
9874
|
function fail(path, id, status, reason) {
|
|
10057
9875
|
return { path, id, status, reason };
|
|
10058
9876
|
}
|
|
10059
9877
|
function isInsidePlugin(pluginPath, relEntry) {
|
|
10060
9878
|
if (isAbsolute3(relEntry)) return false;
|
|
10061
|
-
const abs =
|
|
9879
|
+
const abs = resolve14(pluginPath, relEntry);
|
|
10062
9880
|
const rel = relative(pluginPath, abs);
|
|
10063
9881
|
if (rel === "") return true;
|
|
10064
9882
|
if (rel.startsWith("..")) return false;
|
|
@@ -10399,7 +10217,7 @@ function isDirectorySafe(path, statSync12) {
|
|
|
10399
10217
|
|
|
10400
10218
|
// kernel/adapters/plugin-loader/storage-schemas.ts
|
|
10401
10219
|
import { readFileSync as readFileSync12 } from "fs";
|
|
10402
|
-
import { resolve as
|
|
10220
|
+
import { resolve as resolve15 } from "path";
|
|
10403
10221
|
import { Ajv2020 as Ajv20206 } from "ajv/dist/2020.js";
|
|
10404
10222
|
|
|
10405
10223
|
// kernel/adapters/plugin-store.ts
|
|
@@ -10463,7 +10281,7 @@ function compilePluginSchema(pluginPath, relPath) {
|
|
|
10463
10281
|
errDescription: tx(PLUGIN_LOADER_TEXTS.loadErrorSchemaPathEscapesPlugin, { relPath, pluginPath })
|
|
10464
10282
|
};
|
|
10465
10283
|
}
|
|
10466
|
-
const abs =
|
|
10284
|
+
const abs = resolve15(pluginPath, relPath);
|
|
10467
10285
|
let raw;
|
|
10468
10286
|
try {
|
|
10469
10287
|
raw = JSON.parse(readFileSync12(abs, "utf8"));
|
|
@@ -10505,7 +10323,7 @@ var PluginLoader = class {
|
|
|
10505
10323
|
if (!entry.isDirectory()) continue;
|
|
10506
10324
|
const candidate = join8(root, entry.name);
|
|
10507
10325
|
if (existsSync13(join8(candidate, "plugin.json"))) {
|
|
10508
|
-
out.push(
|
|
10326
|
+
out.push(resolve16(candidate));
|
|
10509
10327
|
}
|
|
10510
10328
|
}
|
|
10511
10329
|
}
|
|
@@ -10664,7 +10482,7 @@ var PluginLoader = class {
|
|
|
10664
10482
|
manifest
|
|
10665
10483
|
} };
|
|
10666
10484
|
}
|
|
10667
|
-
const abs =
|
|
10485
|
+
const abs = resolve16(pluginPath, relEntry);
|
|
10668
10486
|
if (!existsSync13(abs)) {
|
|
10669
10487
|
return { ok: false, failure: {
|
|
10670
10488
|
...fail(
|
|
@@ -10868,7 +10686,7 @@ function discoverExtensionEntries(pluginPath) {
|
|
|
10868
10686
|
return out;
|
|
10869
10687
|
}
|
|
10870
10688
|
function collectKindEntries(pluginPath, kindDir, out) {
|
|
10871
|
-
const kindAbs =
|
|
10689
|
+
const kindAbs = resolve16(pluginPath, kindDir);
|
|
10872
10690
|
if (!existsSync13(kindAbs)) return;
|
|
10873
10691
|
let entries;
|
|
10874
10692
|
try {
|
|
@@ -10879,7 +10697,7 @@ function collectKindEntries(pluginPath, kindDir, out) {
|
|
|
10879
10697
|
entries.sort();
|
|
10880
10698
|
for (const entry of entries) {
|
|
10881
10699
|
if (entry.startsWith(".")) continue;
|
|
10882
|
-
const entryAbs =
|
|
10700
|
+
const entryAbs = resolve16(kindAbs, entry);
|
|
10883
10701
|
if (!isDirectorySafe2(entryAbs)) continue;
|
|
10884
10702
|
const candidate = findIndexCandidate(entryAbs);
|
|
10885
10703
|
if (candidate !== null) {
|
|
@@ -10896,14 +10714,14 @@ function isDirectorySafe2(path) {
|
|
|
10896
10714
|
}
|
|
10897
10715
|
function findIndexCandidate(entryAbs) {
|
|
10898
10716
|
for (const candidate of INDEX_CANDIDATES) {
|
|
10899
|
-
if (existsSync13(
|
|
10717
|
+
if (existsSync13(resolve16(entryAbs, candidate))) return candidate;
|
|
10900
10718
|
}
|
|
10901
10719
|
return null;
|
|
10902
10720
|
}
|
|
10903
10721
|
function installedSpecVersion() {
|
|
10904
10722
|
const require2 = createRequire5(import.meta.url);
|
|
10905
10723
|
const indexPath = require2.resolve("@skill-map/spec/index.json");
|
|
10906
|
-
const pkgPath =
|
|
10724
|
+
const pkgPath = resolve16(indexPath, "..", "package.json");
|
|
10907
10725
|
const pkg = JSON.parse(readFileSync13(pkgPath, "utf8"));
|
|
10908
10726
|
return pkg.version;
|
|
10909
10727
|
}
|
|
@@ -11003,7 +10821,7 @@ import { join as join9, relative as relative2, sep as sep3 } from "path";
|
|
|
11003
10821
|
|
|
11004
10822
|
// kernel/scan/ignore.ts
|
|
11005
10823
|
import { existsSync as existsSync14, readFileSync as readFileSync14 } from "fs";
|
|
11006
|
-
import { dirname as dirname10, resolve as
|
|
10824
|
+
import { dirname as dirname10, resolve as resolve17 } from "path";
|
|
11007
10825
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
11008
10826
|
import ignoreFactory from "ignore";
|
|
11009
10827
|
function buildIgnoreFilter(opts = {}) {
|
|
@@ -11032,7 +10850,7 @@ function loadBundledIgnoreText() {
|
|
|
11032
10850
|
return loadDefaultsText();
|
|
11033
10851
|
}
|
|
11034
10852
|
function readIgnoreFileText(scopeRoot) {
|
|
11035
|
-
const path =
|
|
10853
|
+
const path = resolve17(scopeRoot, ".skillmapignore");
|
|
11036
10854
|
if (!existsSync14(path)) return void 0;
|
|
11037
10855
|
try {
|
|
11038
10856
|
return readFileSync14(path, "utf8");
|
|
@@ -11061,11 +10879,11 @@ function loadDefaultsText() {
|
|
|
11061
10879
|
function readDefaultsFromDisk() {
|
|
11062
10880
|
const here = dirname10(fileURLToPath2(import.meta.url));
|
|
11063
10881
|
const candidates = [
|
|
11064
|
-
|
|
10882
|
+
resolve17(here, "../../config/defaults/skillmapignore"),
|
|
11065
10883
|
// src/kernel/scan/ → src/config/defaults/
|
|
11066
|
-
|
|
10884
|
+
resolve17(here, "../config/defaults/skillmapignore"),
|
|
11067
10885
|
// dist/cli.js → dist/config/defaults/ (siblings)
|
|
11068
|
-
|
|
10886
|
+
resolve17(here, "config/defaults/skillmapignore")
|
|
11069
10887
|
];
|
|
11070
10888
|
for (const candidate of candidates) {
|
|
11071
10889
|
if (existsSync14(candidate)) {
|
|
@@ -11362,7 +11180,7 @@ function isExtensionInstance(v) {
|
|
|
11362
11180
|
}
|
|
11363
11181
|
|
|
11364
11182
|
// core/runtime/plugin-runtime/warnings.ts
|
|
11365
|
-
import { resolve as
|
|
11183
|
+
import { resolve as resolve18 } from "path";
|
|
11366
11184
|
|
|
11367
11185
|
// kernel/util/text.ts
|
|
11368
11186
|
function truncateHead(s, max) {
|
|
@@ -11412,7 +11230,7 @@ function resolveRuntimeContext(opts) {
|
|
|
11412
11230
|
return opts.runtimeContext ?? defaultRuntimeContext();
|
|
11413
11231
|
}
|
|
11414
11232
|
function resolveSearchPaths(opts, ctx) {
|
|
11415
|
-
if (opts.pluginDir) return [
|
|
11233
|
+
if (opts.pluginDir) return [resolve18(opts.pluginDir)];
|
|
11416
11234
|
return [defaultProjectPluginsDir(ctx)];
|
|
11417
11235
|
}
|
|
11418
11236
|
|
|
@@ -12679,7 +12497,7 @@ var CONFIG_COMMANDS = [
|
|
|
12679
12497
|
|
|
12680
12498
|
// cli/commands/conformance.ts
|
|
12681
12499
|
import { existsSync as existsSync19, readFileSync as readFileSync16 } from "fs";
|
|
12682
|
-
import { dirname as dirname12, resolve as
|
|
12500
|
+
import { dirname as dirname12, resolve as resolve21 } from "path";
|
|
12683
12501
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
12684
12502
|
import { Command as Command5, Option as Option5 } from "clipanion";
|
|
12685
12503
|
|
|
@@ -12687,7 +12505,7 @@ import { Command as Command5, Option as Option5 } from "clipanion";
|
|
|
12687
12505
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
12688
12506
|
import { cpSync, existsSync as existsSync17, mkdtempSync, readdirSync as readdirSync5, readFileSync as readFileSync15, rmSync, statSync as statSync3 } from "fs";
|
|
12689
12507
|
import { tmpdir } from "os";
|
|
12690
|
-
import { isAbsolute as isAbsolute5, join as join11, relative as relative3, resolve as
|
|
12508
|
+
import { isAbsolute as isAbsolute5, join as join11, relative as relative3, resolve as resolve19 } from "path";
|
|
12691
12509
|
|
|
12692
12510
|
// conformance/i18n/runner.texts.ts
|
|
12693
12511
|
var CONFORMANCE_RUNNER_TEXTS = {
|
|
@@ -12854,7 +12672,7 @@ function assertContained2(root, rel, label) {
|
|
|
12854
12672
|
tx(CONFORMANCE_RUNNER_TEXTS.pathMustBeRelative, { label, path: rel, anchor: root })
|
|
12855
12673
|
);
|
|
12856
12674
|
}
|
|
12857
|
-
const abs =
|
|
12675
|
+
const abs = resolve19(root, rel);
|
|
12858
12676
|
const r = relative3(root, abs);
|
|
12859
12677
|
if (r.startsWith("..") || isAbsolute5(r)) {
|
|
12860
12678
|
throw new Error(
|
|
@@ -12881,7 +12699,7 @@ function evaluateAssertion(a, ctx) {
|
|
|
12881
12699
|
} catch (err) {
|
|
12882
12700
|
return { ok: false, type: a.type, reason: formatErrorMessage(err) };
|
|
12883
12701
|
}
|
|
12884
|
-
const abs =
|
|
12702
|
+
const abs = resolve19(ctx.scope, a.path);
|
|
12885
12703
|
return existsSync17(abs) ? { ok: true, type: a.type } : {
|
|
12886
12704
|
ok: false,
|
|
12887
12705
|
type: a.type,
|
|
@@ -12896,7 +12714,7 @@ function evaluateAssertion(a, ctx) {
|
|
|
12896
12714
|
return { ok: false, type: a.type, reason: formatErrorMessage(err) };
|
|
12897
12715
|
}
|
|
12898
12716
|
const fixturePath = join11(ctx.fixturesRoot, a.fixture);
|
|
12899
|
-
const targetPath =
|
|
12717
|
+
const targetPath = resolve19(ctx.scope, a.path);
|
|
12900
12718
|
if (!existsSync17(targetPath)) {
|
|
12901
12719
|
return {
|
|
12902
12720
|
ok: false,
|
|
@@ -13079,7 +12897,7 @@ var CONFORMANCE_TEXTS = {
|
|
|
13079
12897
|
|
|
13080
12898
|
// cli/util/conformance-scopes.ts
|
|
13081
12899
|
import { existsSync as existsSync18, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
13082
|
-
import { dirname as dirname11, resolve as
|
|
12900
|
+
import { dirname as dirname11, resolve as resolve20 } from "path";
|
|
13083
12901
|
import { createRequire as createRequire6 } from "module";
|
|
13084
12902
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
13085
12903
|
function resolveSpecRoot4() {
|
|
@@ -13097,7 +12915,7 @@ function resolveCliWorkspaceRoot() {
|
|
|
13097
12915
|
const here = dirname11(fileURLToPath3(import.meta.url));
|
|
13098
12916
|
let cursor = here;
|
|
13099
12917
|
for (let depth = 0; depth < 6; depth += 1) {
|
|
13100
|
-
const candidate =
|
|
12918
|
+
const candidate = resolve20(cursor, "plugins");
|
|
13101
12919
|
if (existsSync18(candidate) && statSync4(candidate).isDirectory()) {
|
|
13102
12920
|
return cursor;
|
|
13103
12921
|
}
|
|
@@ -13117,12 +12935,12 @@ function collectProviderScopes(specRoot) {
|
|
|
13117
12935
|
} catch {
|
|
13118
12936
|
return out;
|
|
13119
12937
|
}
|
|
13120
|
-
const pluginsRoot =
|
|
12938
|
+
const pluginsRoot = resolve20(workspaceRoot, "plugins");
|
|
13121
12939
|
if (!existsSync18(pluginsRoot)) return out;
|
|
13122
12940
|
for (const pluginEntry of readdirSync6(pluginsRoot)) {
|
|
13123
|
-
const pluginDir =
|
|
12941
|
+
const pluginDir = resolve20(pluginsRoot, pluginEntry);
|
|
13124
12942
|
if (!isDir(pluginDir)) continue;
|
|
13125
|
-
const providersRoot =
|
|
12943
|
+
const providersRoot = resolve20(pluginDir, "providers");
|
|
13126
12944
|
if (!isDir(providersRoot)) continue;
|
|
13127
12945
|
collectPluginProviderScopes(providersRoot, specRoot, out);
|
|
13128
12946
|
}
|
|
@@ -13137,12 +12955,12 @@ function isDir(path) {
|
|
|
13137
12955
|
}
|
|
13138
12956
|
function collectPluginProviderScopes(providersRoot, specRoot, out) {
|
|
13139
12957
|
for (const entry of readdirSync6(providersRoot)) {
|
|
13140
|
-
const providerDir =
|
|
12958
|
+
const providerDir = resolve20(providersRoot, entry);
|
|
13141
12959
|
if (!isDir(providerDir)) continue;
|
|
13142
|
-
const conformanceDir =
|
|
12960
|
+
const conformanceDir = resolve20(providerDir, "conformance");
|
|
13143
12961
|
if (!existsSync18(conformanceDir)) continue;
|
|
13144
|
-
const casesDir =
|
|
13145
|
-
const fixturesDir =
|
|
12962
|
+
const casesDir = resolve20(conformanceDir, "cases");
|
|
12963
|
+
const fixturesDir = resolve20(conformanceDir, "fixtures");
|
|
13146
12964
|
if (!existsSync18(casesDir) || !existsSync18(fixturesDir)) continue;
|
|
13147
12965
|
out.push({
|
|
13148
12966
|
id: `provider:${entry}`,
|
|
@@ -13159,8 +12977,8 @@ function specScope(specRoot) {
|
|
|
13159
12977
|
id: "spec",
|
|
13160
12978
|
kind: "spec",
|
|
13161
12979
|
label: "spec",
|
|
13162
|
-
casesDir:
|
|
13163
|
-
fixturesDir:
|
|
12980
|
+
casesDir: resolve20(specRoot, "conformance", "cases"),
|
|
12981
|
+
fixturesDir: resolve20(specRoot, "conformance", "fixtures"),
|
|
13164
12982
|
specRoot
|
|
13165
12983
|
};
|
|
13166
12984
|
}
|
|
@@ -13182,7 +13000,7 @@ function selectConformanceScopes(scope) {
|
|
|
13182
13000
|
}
|
|
13183
13001
|
function listCaseFiles(scope) {
|
|
13184
13002
|
if (!existsSync18(scope.casesDir)) return [];
|
|
13185
|
-
return readdirSync6(scope.casesDir).filter((entry) => entry.endsWith(".json")).sort().map((entry) =>
|
|
13003
|
+
return readdirSync6(scope.casesDir).filter((entry) => entry.endsWith(".json")).sort().map((entry) => resolve20(scope.casesDir, entry));
|
|
13186
13004
|
}
|
|
13187
13005
|
|
|
13188
13006
|
// cli/commands/conformance.ts
|
|
@@ -13199,13 +13017,13 @@ function resolveBinary() {
|
|
|
13199
13017
|
const here = dirname12(fileURLToPath4(import.meta.url));
|
|
13200
13018
|
let cursor = here;
|
|
13201
13019
|
for (let depth = 0; depth < 6; depth += 1) {
|
|
13202
|
-
const candidate =
|
|
13020
|
+
const candidate = resolve21(cursor, "bin", "sm.js");
|
|
13203
13021
|
if (existsSync19(candidate)) return candidate;
|
|
13204
13022
|
const parent = dirname12(cursor);
|
|
13205
13023
|
if (parent === cursor) break;
|
|
13206
13024
|
cursor = parent;
|
|
13207
13025
|
}
|
|
13208
|
-
return
|
|
13026
|
+
return resolve21(here, "..", "..", "bin", "sm.js");
|
|
13209
13027
|
}
|
|
13210
13028
|
var ConformanceRunCommand = class extends SmCommand {
|
|
13211
13029
|
static paths = [["conformance", "run"]];
|
|
@@ -13458,7 +13276,7 @@ function writeStreamSnippet(stream, header, text) {
|
|
|
13458
13276
|
var CONFORMANCE_COMMANDS = [ConformanceRunCommand];
|
|
13459
13277
|
|
|
13460
13278
|
// cli/commands/db/backup.ts
|
|
13461
|
-
import { dirname as dirname13, join as join12, resolve as
|
|
13279
|
+
import { dirname as dirname13, join as join12, resolve as resolve22 } from "path";
|
|
13462
13280
|
import { Command as Command6, Option as Option6 } from "clipanion";
|
|
13463
13281
|
|
|
13464
13282
|
// cli/i18n/db.texts.ts
|
|
@@ -13571,7 +13389,7 @@ var DbBackupCommand = class extends SmCommand {
|
|
|
13571
13389
|
const exit = requireDbOrExit(path, this.context.stderr);
|
|
13572
13390
|
if (exit !== null) return exit;
|
|
13573
13391
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
13574
|
-
const outPath = this.out ?
|
|
13392
|
+
const outPath = this.out ? resolve22(this.out) : join12(dirname13(path), "backups", `${ts}.db`);
|
|
13575
13393
|
await withSqlite({ databasePath: path, autoMigrate: false }, async (storage) => {
|
|
13576
13394
|
storage.migrations.writeBackup(outPath);
|
|
13577
13395
|
});
|
|
@@ -13588,7 +13406,7 @@ var DbBackupCommand = class extends SmCommand {
|
|
|
13588
13406
|
|
|
13589
13407
|
// cli/commands/db/restore.ts
|
|
13590
13408
|
import { chmod, copyFile, mkdir, rm } from "fs/promises";
|
|
13591
|
-
import { dirname as dirname14, resolve as
|
|
13409
|
+
import { dirname as dirname14, resolve as resolve23 } from "path";
|
|
13592
13410
|
import { Command as Command7, Option as Option7 } from "clipanion";
|
|
13593
13411
|
|
|
13594
13412
|
// cli/util/fs.ts
|
|
@@ -13638,7 +13456,7 @@ var DbRestoreCommand = class extends SmCommand {
|
|
|
13638
13456
|
});
|
|
13639
13457
|
async run() {
|
|
13640
13458
|
const target = resolveDbPath({ db: this.db, ...defaultRuntimeContext() });
|
|
13641
|
-
const sourcePath =
|
|
13459
|
+
const sourcePath = resolve23(this.source);
|
|
13642
13460
|
const stderrAnsi = this.ansiFor("stderr");
|
|
13643
13461
|
const sourceStat = await statOrNull(sourcePath);
|
|
13644
13462
|
if (!sourceStat) {
|
|
@@ -13879,7 +13697,7 @@ var DbShellCommand = class extends SmCommand {
|
|
|
13879
13697
|
|
|
13880
13698
|
// cli/commands/db/browser.ts
|
|
13881
13699
|
import { spawn, spawnSync as spawnSync4 } from "child_process";
|
|
13882
|
-
import { resolve as
|
|
13700
|
+
import { resolve as resolve24 } from "path";
|
|
13883
13701
|
import { Command as Command10, Option as Option9 } from "clipanion";
|
|
13884
13702
|
var DbBrowserCommand = class extends SmCommand {
|
|
13885
13703
|
static paths = [["db", "browser"]];
|
|
@@ -13912,7 +13730,7 @@ var DbBrowserCommand = class extends SmCommand {
|
|
|
13912
13730
|
});
|
|
13913
13731
|
positional = Option9.String({ required: false });
|
|
13914
13732
|
async run() {
|
|
13915
|
-
const path = this.positional ?
|
|
13733
|
+
const path = this.positional ? resolve24(this.positional) : resolveDbPath({ db: this.db, ...defaultRuntimeContext() });
|
|
13916
13734
|
if (!assertDbExists(path, this.context.stderr)) {
|
|
13917
13735
|
this.printer.error(DB_TEXTS.browserRunScanFirstHint);
|
|
13918
13736
|
return ExitCode.NotFound;
|
|
@@ -14830,7 +14648,7 @@ var GraphCommand = class extends SmCommand {
|
|
|
14830
14648
|
// cli/commands/help.ts
|
|
14831
14649
|
import { readFileSync as readFileSync17 } from "fs";
|
|
14832
14650
|
import { createRequire as createRequire7 } from "module";
|
|
14833
|
-
import { resolve as
|
|
14651
|
+
import { resolve as resolve25 } from "path";
|
|
14834
14652
|
import { Command as Command15, Option as Option14 } from "clipanion";
|
|
14835
14653
|
|
|
14836
14654
|
// cli/i18n/help.texts.ts
|
|
@@ -15144,7 +14962,7 @@ function resolveSpecVersion() {
|
|
|
15144
14962
|
try {
|
|
15145
14963
|
const req = createRequire7(import.meta.url);
|
|
15146
14964
|
const indexPath = req.resolve("@skill-map/spec/index.json");
|
|
15147
|
-
const pkgPath =
|
|
14965
|
+
const pkgPath = resolve25(indexPath, "..", "package.json");
|
|
15148
14966
|
const pkg = JSON.parse(readFileSync17(pkgPath, "utf8"));
|
|
15149
14967
|
return pkg.version;
|
|
15150
14968
|
} catch {
|
|
@@ -15452,7 +15270,7 @@ function registeredVerbPaths(cli2) {
|
|
|
15452
15270
|
|
|
15453
15271
|
// cli/commands/hooks.ts
|
|
15454
15272
|
import { chmod as chmod2, mkdir as mkdir3, readFile as readFile2, stat as stat2, writeFile } from "fs/promises";
|
|
15455
|
-
import { dirname as dirname16, resolve as
|
|
15273
|
+
import { dirname as dirname16, resolve as resolve26 } from "path";
|
|
15456
15274
|
import { Command as Command16, Option as Option15 } from "clipanion";
|
|
15457
15275
|
|
|
15458
15276
|
// cli/i18n/hooks.texts.ts
|
|
@@ -15555,8 +15373,8 @@ var HooksInstallCommand = class extends SmCommand {
|
|
|
15555
15373
|
);
|
|
15556
15374
|
return ExitCode.NotFound;
|
|
15557
15375
|
}
|
|
15558
|
-
const hooksDir =
|
|
15559
|
-
const hookPath =
|
|
15376
|
+
const hooksDir = resolve26(repoRoot, ".git", "hooks");
|
|
15377
|
+
const hookPath = resolve26(hooksDir, "pre-commit");
|
|
15560
15378
|
const existing = await pathExists(hookPath) ? await readFile2(hookPath, "utf8") : null;
|
|
15561
15379
|
const planned2 = computePlannedHookContent(existing);
|
|
15562
15380
|
if (planned2.kind === "already-installed") {
|
|
@@ -15614,7 +15432,7 @@ var HooksInstallCommand = class extends SmCommand {
|
|
|
15614
15432
|
async function findGitRepoRoot(cwd) {
|
|
15615
15433
|
let current = cwd;
|
|
15616
15434
|
while (true) {
|
|
15617
|
-
if (await pathExists(
|
|
15435
|
+
if (await pathExists(resolve26(current, ".git"))) return current;
|
|
15618
15436
|
const parent = dirname16(current);
|
|
15619
15437
|
if (parent === current) return null;
|
|
15620
15438
|
current = parent;
|
|
@@ -15636,7 +15454,7 @@ var HOOKS_COMMANDS = [HooksInstallCommand];
|
|
|
15636
15454
|
|
|
15637
15455
|
// cli/commands/init.ts
|
|
15638
15456
|
import { mkdir as mkdir4, readFile as readFile3, unlink, writeFile as writeFile2 } from "fs/promises";
|
|
15639
|
-
import { join as
|
|
15457
|
+
import { join as join16 } from "path";
|
|
15640
15458
|
import { Command as Command17, Option as Option16 } from "clipanion";
|
|
15641
15459
|
|
|
15642
15460
|
// kernel/orchestrator/index.ts
|
|
@@ -16129,8 +15947,49 @@ function runActionProjections(actions, nodes, links, emitter) {
|
|
|
16129
15947
|
return { contributions, contributionErrors };
|
|
16130
15948
|
}
|
|
16131
15949
|
|
|
15950
|
+
// kernel/orchestrator/confidence-score.ts
|
|
15951
|
+
function foldConfidence(base, ops) {
|
|
15952
|
+
let running = base;
|
|
15953
|
+
for (const op of ops) {
|
|
15954
|
+
if (op.kind === "set") running = op.value;
|
|
15955
|
+
}
|
|
15956
|
+
for (const op of ops) {
|
|
15957
|
+
if (op.kind === "delta") running += op.value;
|
|
15958
|
+
}
|
|
15959
|
+
for (const op of ops) {
|
|
15960
|
+
if (op.kind === "floor") running = Math.max(running, op.value);
|
|
15961
|
+
}
|
|
15962
|
+
for (const op of ops) {
|
|
15963
|
+
if (op.kind === "ceil") running = Math.min(running, op.value);
|
|
15964
|
+
}
|
|
15965
|
+
return clamp01(running);
|
|
15966
|
+
}
|
|
15967
|
+
function clamp01(n) {
|
|
15968
|
+
if (n < 0) return 0;
|
|
15969
|
+
if (n > 1) return 1;
|
|
15970
|
+
return n;
|
|
15971
|
+
}
|
|
15972
|
+
function applyConfidenceAdjustments(adjustments) {
|
|
15973
|
+
if (adjustments.length === 0) return;
|
|
15974
|
+
const byLink = /* @__PURE__ */ new Map();
|
|
15975
|
+
for (const adj of adjustments) {
|
|
15976
|
+
const bucket = byLink.get(adj.link);
|
|
15977
|
+
if (bucket) bucket.push(adj);
|
|
15978
|
+
else byLink.set(adj.link, [adj]);
|
|
15979
|
+
}
|
|
15980
|
+
for (const [link, adjs] of byLink) {
|
|
15981
|
+
const ops = [...adjs].sort(compareAdjustments).map((a) => a.op);
|
|
15982
|
+
link.confidence = foldConfidence(link.confidence, ops);
|
|
15983
|
+
}
|
|
15984
|
+
}
|
|
15985
|
+
function compareAdjustments(a, b) {
|
|
15986
|
+
if (a.pluginId !== b.pluginId) return a.pluginId < b.pluginId ? -1 : 1;
|
|
15987
|
+
if (a.extensionId !== b.extensionId) return a.extensionId < b.extensionId ? -1 : 1;
|
|
15988
|
+
return 0;
|
|
15989
|
+
}
|
|
15990
|
+
|
|
16132
15991
|
// kernel/orchestrator/analyzers.ts
|
|
16133
|
-
async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions,
|
|
15992
|
+
async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sidecarRoots, annotationContributions, viewContributions, referenceablePaths, cwd, registeredActionIds, emitter, hookDispatcher, reservedNodePaths, brokenLinks, nameCollisions, signals, seedIssues = []) {
|
|
16134
15993
|
const issues = [...seedIssues];
|
|
16135
15994
|
const contributions = [];
|
|
16136
15995
|
const contributionErrors = [];
|
|
@@ -16141,7 +16000,16 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
|
|
|
16141
16000
|
expectedMdPath: o.expectedMdPath
|
|
16142
16001
|
}));
|
|
16143
16002
|
const scheduled = orderAnalyzersByPhase(analyzers);
|
|
16003
|
+
const scoreAdjustments = [];
|
|
16004
|
+
const scorableLinks = new Set(internalLinks);
|
|
16005
|
+
let scoresFolded = false;
|
|
16006
|
+
const foldScores = () => {
|
|
16007
|
+
if (scoresFolded) return;
|
|
16008
|
+
scoresFolded = true;
|
|
16009
|
+
applyConfidenceAdjustments(scoreAdjustments);
|
|
16010
|
+
};
|
|
16144
16011
|
for (const analyzer of scheduled) {
|
|
16012
|
+
if (analyzer.phase !== "score") foldScores();
|
|
16145
16013
|
const qualifiedId2 = qualifiedExtensionId(analyzer.pluginId, analyzer.id);
|
|
16146
16014
|
const declaredContributions = readDeclaredContributionRefs(analyzer);
|
|
16147
16015
|
const emitContribution = (nodePath, ref, payload) => {
|
|
@@ -16204,6 +16072,16 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
|
|
|
16204
16072
|
emittedAt: Date.now()
|
|
16205
16073
|
});
|
|
16206
16074
|
};
|
|
16075
|
+
const adjustConfidence = analyzer.phase === "score" ? (link, op) => {
|
|
16076
|
+
if (scorableLinks.has(link)) {
|
|
16077
|
+
scoreAdjustments.push({
|
|
16078
|
+
link,
|
|
16079
|
+
pluginId: analyzer.pluginId,
|
|
16080
|
+
extensionId: analyzer.id,
|
|
16081
|
+
op
|
|
16082
|
+
});
|
|
16083
|
+
}
|
|
16084
|
+
} : void 0;
|
|
16207
16085
|
const emitted = await analyzer.evaluate({
|
|
16208
16086
|
nodes,
|
|
16209
16087
|
links: internalLinks,
|
|
@@ -16215,7 +16093,6 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
|
|
|
16215
16093
|
sidecarRoots,
|
|
16216
16094
|
annotationContributions,
|
|
16217
16095
|
viewContributions,
|
|
16218
|
-
orphanJobFiles,
|
|
16219
16096
|
// `issues` is the live accumulator, mutated by `issues.push(...)`
|
|
16220
16097
|
// below as each analyzer's emission lands. Late-phase analyzers
|
|
16221
16098
|
// (`core/issue-counter`) read it to compute cross-analyzer
|
|
@@ -16225,7 +16102,9 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
|
|
|
16225
16102
|
...cwd ? { cwd } : {},
|
|
16226
16103
|
...reservedNodePaths ? { reservedNodePaths } : {},
|
|
16227
16104
|
...brokenLinks ? { brokenLinks } : {},
|
|
16105
|
+
...nameCollisions && nameCollisions.size > 0 ? { nameCollisions } : {},
|
|
16228
16106
|
...signals && signals.length > 0 ? { signals } : {},
|
|
16107
|
+
...adjustConfidence ? { adjustConfidence } : {},
|
|
16229
16108
|
emitContribution
|
|
16230
16109
|
});
|
|
16231
16110
|
for (const issue of emitted) {
|
|
@@ -16236,13 +16115,16 @@ async function runAnalyzers(analyzers, nodes, internalLinks, orphanSidecars, sid
|
|
|
16236
16115
|
emitter.emit(evt);
|
|
16237
16116
|
await hookDispatcher.dispatch("analyzer.completed", evt);
|
|
16238
16117
|
}
|
|
16239
|
-
|
|
16118
|
+
foldScores();
|
|
16119
|
+
return { issues, contributions, contributionErrors, linkScores: scoreAdjustments };
|
|
16240
16120
|
}
|
|
16241
16121
|
function orderAnalyzersByPhase(analyzers) {
|
|
16242
16122
|
return analyzers.slice().sort((a, b) => phaseRank(a) - phaseRank(b));
|
|
16243
16123
|
}
|
|
16244
16124
|
function phaseRank(a) {
|
|
16245
|
-
|
|
16125
|
+
if (a.phase === "score") return 0;
|
|
16126
|
+
if (a.phase === "aggregate") return 2;
|
|
16127
|
+
return 1;
|
|
16246
16128
|
}
|
|
16247
16129
|
function validateIssue(analyzer, issue, emitter) {
|
|
16248
16130
|
const severity = issue.severity;
|
|
@@ -16293,17 +16175,6 @@ function indexPriorLinks(links, priorNodePaths, byOriginating) {
|
|
|
16293
16175
|
else byOriginating.set(key, [link]);
|
|
16294
16176
|
}
|
|
16295
16177
|
}
|
|
16296
|
-
var FRONTMATTER_ISSUE_ANALYZERS = /* @__PURE__ */ new Set([
|
|
16297
|
-
"frontmatter-invalid",
|
|
16298
|
-
"frontmatter-malformed",
|
|
16299
|
-
// Audit L1: parser parse-error is emitted by
|
|
16300
|
-
// `buildFreshNodeAndValidateFrontmatter` from `raw.parseIssues`. The
|
|
16301
|
-
// raw.parseIssues only flows through the non-cache path; a cached
|
|
16302
|
-
// node skips the rebuild, so the prior issue MUST survive the
|
|
16303
|
-
// incremental scan or the warning silently disappears on a clean
|
|
16304
|
-
// re-scan of an unchanged file.
|
|
16305
|
-
"frontmatter-parse-error"
|
|
16306
|
-
]);
|
|
16307
16178
|
function indexPriorFrontmatterIssues(issues, byNode) {
|
|
16308
16179
|
for (const issue of issues) {
|
|
16309
16180
|
if (!FRONTMATTER_ISSUE_ANALYZERS.has(issue.analyzerId)) continue;
|
|
@@ -16452,6 +16323,157 @@ function classifyLinkSource(source, shortIdToQualified, cachedQualifiedIds, appl
|
|
|
16452
16323
|
return "obsolete";
|
|
16453
16324
|
}
|
|
16454
16325
|
|
|
16326
|
+
// kernel/orchestrator/node-identifiers.ts
|
|
16327
|
+
import { posix as pathPosix4 } from "path";
|
|
16328
|
+
function deriveNodeIdentifiers(node, kindDescriptor) {
|
|
16329
|
+
const sources = kindDescriptor?.identifiers;
|
|
16330
|
+
if (!sources || sources.length === 0) return [];
|
|
16331
|
+
const out = [];
|
|
16332
|
+
for (const source of sources) {
|
|
16333
|
+
const raw = readIdentifier(source, node);
|
|
16334
|
+
if (!raw) continue;
|
|
16335
|
+
const normalised = normalizeTrigger(raw);
|
|
16336
|
+
if (normalised) out.push(normalised);
|
|
16337
|
+
}
|
|
16338
|
+
return out;
|
|
16339
|
+
}
|
|
16340
|
+
function readIdentifier(source, node) {
|
|
16341
|
+
if (source === "frontmatter.name") return readFrontmatterName(node);
|
|
16342
|
+
if (source === "filename-basename") return readFilenameBasename(node);
|
|
16343
|
+
return readDirname(node);
|
|
16344
|
+
}
|
|
16345
|
+
function readFrontmatterName(node) {
|
|
16346
|
+
const raw = node.frontmatter?.["name"];
|
|
16347
|
+
if (typeof raw !== "string") return null;
|
|
16348
|
+
return raw.length > 0 ? raw : null;
|
|
16349
|
+
}
|
|
16350
|
+
function readFilenameBasename(node) {
|
|
16351
|
+
const base = pathPosix4.basename(node.path);
|
|
16352
|
+
if (!base) return null;
|
|
16353
|
+
const ext = pathPosix4.extname(base);
|
|
16354
|
+
const stem = ext ? base.slice(0, -ext.length) : base;
|
|
16355
|
+
return stem.length > 0 ? stem : null;
|
|
16356
|
+
}
|
|
16357
|
+
function readDirname(node) {
|
|
16358
|
+
const dir = pathPosix4.dirname(node.path);
|
|
16359
|
+
if (!dir || dir === "." || dir === "/") return null;
|
|
16360
|
+
const base = pathPosix4.basename(dir);
|
|
16361
|
+
return base.length > 0 ? base : null;
|
|
16362
|
+
}
|
|
16363
|
+
function collectNameCollisions(nodes, kindRegistry) {
|
|
16364
|
+
const byName = indexNameClaims(nodes, kindRegistry);
|
|
16365
|
+
const collisions = /* @__PURE__ */ new Map();
|
|
16366
|
+
for (const [name, claims] of byName) {
|
|
16367
|
+
const distinct = dedupeClaimsByPath(claims);
|
|
16368
|
+
if (distinct.length >= 2) collisions.set(name, distinct);
|
|
16369
|
+
}
|
|
16370
|
+
return collisions;
|
|
16371
|
+
}
|
|
16372
|
+
function indexNameClaims(nodes, kindRegistry) {
|
|
16373
|
+
const byName = /* @__PURE__ */ new Map();
|
|
16374
|
+
for (const node of nodes) {
|
|
16375
|
+
const name = resolvableName(node, kindRegistry);
|
|
16376
|
+
if (name === null) continue;
|
|
16377
|
+
const bucket = byName.get(name) ?? [];
|
|
16378
|
+
bucket.push({ path: node.path, kind: node.kind });
|
|
16379
|
+
byName.set(name, bucket);
|
|
16380
|
+
}
|
|
16381
|
+
return byName;
|
|
16382
|
+
}
|
|
16383
|
+
function resolvableName(node, kindRegistry) {
|
|
16384
|
+
const descriptor = kindRegistry.get(`${node.provider}/${node.kind}`);
|
|
16385
|
+
if (!descriptor?.identifiers?.includes("frontmatter.name")) return null;
|
|
16386
|
+
const raw = node.frontmatter?.["name"];
|
|
16387
|
+
if (typeof raw !== "string" || raw.length === 0) return null;
|
|
16388
|
+
const normalised = normalizeTrigger(raw);
|
|
16389
|
+
return normalised.length > 0 ? normalised : null;
|
|
16390
|
+
}
|
|
16391
|
+
function dedupeClaimsByPath(claims) {
|
|
16392
|
+
return [...new Map(claims.map((c) => [c.path, c])).values()].sort(
|
|
16393
|
+
(a, b) => a.path.localeCompare(b.path)
|
|
16394
|
+
);
|
|
16395
|
+
}
|
|
16396
|
+
|
|
16397
|
+
// kernel/orchestrator/lift-resolved-link-confidence.ts
|
|
16398
|
+
function liftResolvedLinkConfidence(links, nodes, ctx) {
|
|
16399
|
+
if (links.length === 0) return;
|
|
16400
|
+
const indexes = buildIndexes(nodes, ctx);
|
|
16401
|
+
for (const link of links) {
|
|
16402
|
+
link.confidence = 1;
|
|
16403
|
+
applyResolution(link, indexes, ctx);
|
|
16404
|
+
}
|
|
16405
|
+
}
|
|
16406
|
+
function collectBrokenLinks(links, nodes, ctx) {
|
|
16407
|
+
const broken = /* @__PURE__ */ new Set();
|
|
16408
|
+
if (links.length === 0) return broken;
|
|
16409
|
+
const indexes = buildIndexes(nodes, ctx);
|
|
16410
|
+
for (const link of links) {
|
|
16411
|
+
if (isGenuinelyBroken(link, indexes)) broken.add(link);
|
|
16412
|
+
}
|
|
16413
|
+
return broken;
|
|
16414
|
+
}
|
|
16415
|
+
function applyResolution(link, indexes, ctx) {
|
|
16416
|
+
const resolution = resolve27(link, indexes, ctx);
|
|
16417
|
+
if (resolution === "none") return;
|
|
16418
|
+
link.resolvedTarget = resolution;
|
|
16419
|
+
}
|
|
16420
|
+
function buildIndexes(nodes, ctx) {
|
|
16421
|
+
const byPath3 = /* @__PURE__ */ new Set();
|
|
16422
|
+
const byName = /* @__PURE__ */ new Map();
|
|
16423
|
+
const nodeByPath = /* @__PURE__ */ new Map();
|
|
16424
|
+
for (const node of nodes) {
|
|
16425
|
+
byPath3.add(node.path);
|
|
16426
|
+
nodeByPath.set(node.path, node);
|
|
16427
|
+
indexNode(node, ctx, byName);
|
|
16428
|
+
}
|
|
16429
|
+
return { byPath: byPath3, byName, nodeByPath };
|
|
16430
|
+
}
|
|
16431
|
+
function resolve27(link, indexes, ctx) {
|
|
16432
|
+
if (indexes.byPath.has(link.target)) return link.target;
|
|
16433
|
+
return resolveByName(link, indexes, ctx);
|
|
16434
|
+
}
|
|
16435
|
+
function isGenuinelyBroken(link, indexes) {
|
|
16436
|
+
if (indexes.byPath.has(link.target)) return false;
|
|
16437
|
+
const stripped = stripTriggerSigil(link.trigger?.normalizedTrigger);
|
|
16438
|
+
if (stripped !== null && indexes.byName.has(stripped)) return false;
|
|
16439
|
+
return true;
|
|
16440
|
+
}
|
|
16441
|
+
function resolveByName(link, indexes, ctx) {
|
|
16442
|
+
const stripped = stripTriggerSigil(link.trigger?.normalizedTrigger);
|
|
16443
|
+
if (stripped === null) return "none";
|
|
16444
|
+
const candidates = indexes.byName.get(stripped);
|
|
16445
|
+
if (!candidates?.length) return "none";
|
|
16446
|
+
const allowedKinds = lookupAllowedKinds(link, indexes, ctx);
|
|
16447
|
+
if (!allowedKinds?.length) return "none";
|
|
16448
|
+
const winner = candidates.find((c) => allowedKinds.includes(c.kind));
|
|
16449
|
+
return winner ? winner.path : "none";
|
|
16450
|
+
}
|
|
16451
|
+
function lookupAllowedKinds(link, _indexes, ctx) {
|
|
16452
|
+
if (ctx.activeProvider === null) return void 0;
|
|
16453
|
+
return ctx.providerResolution.get(ctx.activeProvider)?.[link.kind];
|
|
16454
|
+
}
|
|
16455
|
+
function stripTriggerSigil(normalized) {
|
|
16456
|
+
if (!normalized) return null;
|
|
16457
|
+
const trimmed = normalized.replace(/^[/@]/, "").trim();
|
|
16458
|
+
return trimmed.length === 0 ? null : trimmed;
|
|
16459
|
+
}
|
|
16460
|
+
function indexNode(node, ctx, byName) {
|
|
16461
|
+
const kindDescriptor = ctx.kindRegistry.get(kindKey(node));
|
|
16462
|
+
const normalised = deriveNodeIdentifiers(node, kindDescriptor);
|
|
16463
|
+
for (const name of normalised) {
|
|
16464
|
+
const entry = { kind: node.kind, path: node.path };
|
|
16465
|
+
const bucket = byName.get(name);
|
|
16466
|
+
if (bucket) {
|
|
16467
|
+
bucket.push(entry);
|
|
16468
|
+
} else {
|
|
16469
|
+
byName.set(name, [entry]);
|
|
16470
|
+
}
|
|
16471
|
+
}
|
|
16472
|
+
}
|
|
16473
|
+
function kindKey(node) {
|
|
16474
|
+
return `${node.provider}/${node.kind}`;
|
|
16475
|
+
}
|
|
16476
|
+
|
|
16455
16477
|
// kernel/orchestrator/post-walk-transforms.ts
|
|
16456
16478
|
var POST_WALK_TRANSFORMS = [
|
|
16457
16479
|
{
|
|
@@ -17448,6 +17470,7 @@ async function runScanInternal(_kernel, options) {
|
|
|
17448
17470
|
const postWalkCtx = buildPostWalkTransformCtx(exts.providers, walked.nodes, activeProviderId);
|
|
17449
17471
|
walked.internalLinks = applyPostWalkTransforms(walked.internalLinks, walked.nodes, postWalkCtx);
|
|
17450
17472
|
const brokenLinks = collectBrokenLinks(walked.internalLinks, walked.nodes, postWalkCtx);
|
|
17473
|
+
const nameCollisions = collectNameCollisions(walked.nodes, postWalkCtx.kindRegistry);
|
|
17451
17474
|
recomputeLinkCounts(walked.nodes, walked.internalLinks);
|
|
17452
17475
|
recomputeExternalRefsCount(walked.nodes, walked.externalLinks, walked.cachedPaths);
|
|
17453
17476
|
await dispatchExtractorCompleted(exts.extractors, emitter, hookDispatcher);
|
|
@@ -17462,7 +17485,6 @@ async function runScanInternal(_kernel, options) {
|
|
|
17462
17485
|
walked.sidecarRoots,
|
|
17463
17486
|
options.annotationContributions ?? [],
|
|
17464
17487
|
options.viewContributions ?? [],
|
|
17465
|
-
options.orphanJobFiles ?? [],
|
|
17466
17488
|
options.referenceablePaths,
|
|
17467
17489
|
options.cwd,
|
|
17468
17490
|
registeredActionIds,
|
|
@@ -17470,6 +17492,7 @@ async function runScanInternal(_kernel, options) {
|
|
|
17470
17492
|
hookDispatcher,
|
|
17471
17493
|
postWalkCtx.reservedNodePaths,
|
|
17472
17494
|
brokenLinks,
|
|
17495
|
+
nameCollisions,
|
|
17473
17496
|
walked.signals,
|
|
17474
17497
|
// Seed the accumulator with orchestrator-emitted frontmatter
|
|
17475
17498
|
// issues so the aggregate phase (`core/issue-counter`) counts
|
|
@@ -17492,7 +17515,7 @@ async function runScanInternal(_kernel, options) {
|
|
|
17492
17515
|
const scanCompletedEvent = makeEvent("scan.completed", { stats });
|
|
17493
17516
|
emitter.emit(scanCompletedEvent);
|
|
17494
17517
|
await hookDispatcher.dispatch("scan.completed", scanCompletedEvent);
|
|
17495
|
-
return buildScanReturn(walked, issues, renameOps, stats, options, setup);
|
|
17518
|
+
return buildScanReturn(walked, issues, renameOps, stats, options, setup, analyzerResult.linkScores);
|
|
17496
17519
|
}
|
|
17497
17520
|
function buildPostWalkTransformCtx(providers, nodes, activeProvider) {
|
|
17498
17521
|
const { kindRegistry, providerResolution, reservedNamesByProviderKind } = buildProviderIndexes(providers);
|
|
@@ -17620,7 +17643,7 @@ function buildScanStats(walked, issues, start) {
|
|
|
17620
17643
|
durationMs: Date.now() - start
|
|
17621
17644
|
};
|
|
17622
17645
|
}
|
|
17623
|
-
function buildScanReturn(walked, issues, renameOps, stats, options, setup) {
|
|
17646
|
+
function buildScanReturn(walked, issues, renameOps, stats, options, setup, linkScores) {
|
|
17624
17647
|
return {
|
|
17625
17648
|
result: {
|
|
17626
17649
|
schemaVersion: 1,
|
|
@@ -17642,6 +17665,7 @@ function buildScanReturn(walked, issues, renameOps, stats, options, setup) {
|
|
|
17642
17665
|
enrichments: walked.enrichments,
|
|
17643
17666
|
contributions: walked.contributions,
|
|
17644
17667
|
contributionErrors: walked.contributionErrors,
|
|
17668
|
+
linkScores,
|
|
17645
17669
|
freshlyRunTuples: walked.freshlyRunTuples
|
|
17646
17670
|
};
|
|
17647
17671
|
}
|
|
@@ -17887,33 +17911,6 @@ function createKernel() {
|
|
|
17887
17911
|
};
|
|
17888
17912
|
}
|
|
17889
17913
|
|
|
17890
|
-
// kernel/jobs/orphan-files.ts
|
|
17891
|
-
import { readdirSync as readdirSync8, statSync as statSync7 } from "fs";
|
|
17892
|
-
import { join as join14, resolve as resolve30 } from "path";
|
|
17893
|
-
function findOrphanJobFiles(jobsDir, referencedPaths) {
|
|
17894
|
-
let entries;
|
|
17895
|
-
try {
|
|
17896
|
-
const stat3 = statSync7(jobsDir);
|
|
17897
|
-
if (!stat3.isDirectory()) {
|
|
17898
|
-
return { orphanFilePaths: [], referencedCount: referencedPaths.size };
|
|
17899
|
-
}
|
|
17900
|
-
entries = readdirSync8(jobsDir, { withFileTypes: true });
|
|
17901
|
-
} catch {
|
|
17902
|
-
return { orphanFilePaths: [], referencedCount: referencedPaths.size };
|
|
17903
|
-
}
|
|
17904
|
-
const orphans = [];
|
|
17905
|
-
for (const entry of entries) {
|
|
17906
|
-
if (entry.isSymbolicLink()) continue;
|
|
17907
|
-
if (!entry.isFile()) continue;
|
|
17908
|
-
const name = entry.name;
|
|
17909
|
-
if (!name.endsWith(".md")) continue;
|
|
17910
|
-
const abs = resolve30(join14(jobsDir, name));
|
|
17911
|
-
if (!referencedPaths.has(abs)) orphans.push(abs);
|
|
17912
|
-
}
|
|
17913
|
-
orphans.sort();
|
|
17914
|
-
return { orphanFilePaths: orphans, referencedCount: referencedPaths.size };
|
|
17915
|
-
}
|
|
17916
|
-
|
|
17917
17914
|
// core/config/plugin-settings.ts
|
|
17918
17915
|
var defaultWarn = (message) => log.warn(message);
|
|
17919
17916
|
function resolveExtensionSettings(manifest, config, onWarn = defaultWarn) {
|
|
@@ -18218,9 +18215,9 @@ function resolveScanRoots(inputs) {
|
|
|
18218
18215
|
}
|
|
18219
18216
|
|
|
18220
18217
|
// core/runtime/reference-paths-walker.ts
|
|
18221
|
-
import { readdirSync as
|
|
18218
|
+
import { readdirSync as readdirSync8, statSync as statSync7 } from "fs";
|
|
18222
18219
|
import { homedir as osHomedir2 } from "os";
|
|
18223
|
-
import { isAbsolute as isAbsolute8, join as
|
|
18220
|
+
import { isAbsolute as isAbsolute8, join as join14, resolve as resolve30 } from "path";
|
|
18224
18221
|
var REFERENCE_WALK_MAX_FILES = 5e4;
|
|
18225
18222
|
var SKIPPED_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
18226
18223
|
"node_modules",
|
|
@@ -18228,10 +18225,10 @@ var SKIPPED_DIR_NAMES = /* @__PURE__ */ new Set([
|
|
|
18228
18225
|
SKILL_MAP_DIR
|
|
18229
18226
|
]);
|
|
18230
18227
|
function resolveScanPath(raw, cwd) {
|
|
18231
|
-
if (raw.startsWith("~/")) return
|
|
18232
|
-
if (raw === "~") return
|
|
18233
|
-
if (isAbsolute8(raw)) return
|
|
18234
|
-
return
|
|
18228
|
+
if (raw.startsWith("~/")) return resolve30(join14(osHomedir2(), raw.slice(2)));
|
|
18229
|
+
if (raw === "~") return resolve30(osHomedir2());
|
|
18230
|
+
if (isAbsolute8(raw)) return resolve30(raw);
|
|
18231
|
+
return resolve30(cwd, raw);
|
|
18235
18232
|
}
|
|
18236
18233
|
function walkReferencePaths(rawRoots, cwd) {
|
|
18237
18234
|
const paths = /* @__PURE__ */ new Set();
|
|
@@ -18253,14 +18250,14 @@ function walkInto(dir, out) {
|
|
|
18253
18250
|
if (out.size >= REFERENCE_WALK_MAX_FILES) return true;
|
|
18254
18251
|
let entries;
|
|
18255
18252
|
try {
|
|
18256
|
-
entries =
|
|
18253
|
+
entries = readdirSync8(dir, { withFileTypes: true });
|
|
18257
18254
|
} catch {
|
|
18258
18255
|
return false;
|
|
18259
18256
|
}
|
|
18260
18257
|
for (const entry of entries) {
|
|
18261
18258
|
if (out.size >= REFERENCE_WALK_MAX_FILES) return true;
|
|
18262
18259
|
if (entry.isSymbolicLink()) continue;
|
|
18263
|
-
const full =
|
|
18260
|
+
const full = join14(dir, entry.name);
|
|
18264
18261
|
if (entry.isDirectory()) {
|
|
18265
18262
|
if (SKIPPED_DIR_NAMES.has(entry.name)) continue;
|
|
18266
18263
|
if (walkInto(full, out)) return true;
|
|
@@ -18272,7 +18269,7 @@ function walkInto(dir, out) {
|
|
|
18272
18269
|
}
|
|
18273
18270
|
function safeStat(path) {
|
|
18274
18271
|
try {
|
|
18275
|
-
return
|
|
18272
|
+
return statSync7(path);
|
|
18276
18273
|
} catch {
|
|
18277
18274
|
return null;
|
|
18278
18275
|
}
|
|
@@ -18280,7 +18277,7 @@ function safeStat(path) {
|
|
|
18280
18277
|
|
|
18281
18278
|
// core/runtime/active-provider-bootstrap.ts
|
|
18282
18279
|
import { createInterface as createInterface3 } from "readline";
|
|
18283
|
-
import { isAbsolute as isAbsolute9, join as
|
|
18280
|
+
import { isAbsolute as isAbsolute9, join as join15 } from "path";
|
|
18284
18281
|
async function bootstrapActiveProvider(opts) {
|
|
18285
18282
|
const fromCwd = resolveActiveProvider(opts.cwd, opts.providers);
|
|
18286
18283
|
if (fromCwd.source === "config") {
|
|
@@ -18339,7 +18336,7 @@ function aggregateDetected(cwd, effectiveRoots, cwdDetected, providers) {
|
|
|
18339
18336
|
out.push(id);
|
|
18340
18337
|
}
|
|
18341
18338
|
for (const root of effectiveRoots) {
|
|
18342
|
-
const absRoot = isAbsolute9(root) ? root :
|
|
18339
|
+
const absRoot = isAbsolute9(root) ? root : join15(cwd, root);
|
|
18343
18340
|
const r = resolveActiveProvider(absRoot, providers);
|
|
18344
18341
|
for (const id of r.detected) {
|
|
18345
18342
|
if (seen.has(id)) continue;
|
|
@@ -18578,7 +18575,6 @@ async function runScanForCommand(opts) {
|
|
|
18578
18575
|
emitReferenceWalkAdvisory(walk3, opts);
|
|
18579
18576
|
}
|
|
18580
18577
|
const loadPrior = makePriorLoader(opts.noBuiltIns, strict);
|
|
18581
|
-
const jobsDir = defaultProjectJobsDir(ctx);
|
|
18582
18578
|
const lens = await resolveActiveLens(
|
|
18583
18579
|
opts,
|
|
18584
18580
|
ctx,
|
|
@@ -18603,7 +18599,7 @@ async function runScanForCommand(opts) {
|
|
|
18603
18599
|
cfg.tokenizer
|
|
18604
18600
|
);
|
|
18605
18601
|
const willPersist = !opts.noBuiltIns && !opts.dryRun;
|
|
18606
|
-
const scanned = await (willPersist ? runPersistPath(opts, dbPath,
|
|
18602
|
+
const scanned = await (willPersist ? runPersistPath(opts, dbPath, strict, loadPrior, runScanWith, extensions) : runEphemeralPath(opts, dbPath, strict, loadPrior, runScanWith));
|
|
18607
18603
|
return scanned.kind === "ok" ? { ...scanned, lensAutoDetected: lens.autoDetected } : scanned;
|
|
18608
18604
|
}
|
|
18609
18605
|
function detectionProviders(extensions) {
|
|
@@ -18716,7 +18712,7 @@ function makePriorLoader(noBuiltIns, strict) {
|
|
|
18716
18712
|
};
|
|
18717
18713
|
}
|
|
18718
18714
|
function makeScanRunner(kernel, opts, effectiveRoots, ignoreFilter, strict, extensions, referenceablePaths, scanCwd, activeProvider, recommendedNodeLimit, maxFileSizeBytes, tokenizer) {
|
|
18719
|
-
return async (prior, priorExtractorRuns
|
|
18715
|
+
return async (prior, priorExtractorRuns) => {
|
|
18720
18716
|
if (opts.changed && prior === null) {
|
|
18721
18717
|
opts.stderr.write(SCAN_RUNNER_TEXTS.changedNoPriorWarning);
|
|
18722
18718
|
}
|
|
@@ -18733,14 +18729,13 @@ function makeScanRunner(kernel, opts, effectiveRoots, ignoreFilter, strict, exte
|
|
|
18733
18729
|
recommendedNodeLimit,
|
|
18734
18730
|
maxFileSizeBytes,
|
|
18735
18731
|
tokenizer,
|
|
18736
|
-
...priorExtractorRuns ? { priorExtractorRuns } : {}
|
|
18737
|
-
...orphanJobFiles ? { orphanJobFiles } : {}
|
|
18732
|
+
...priorExtractorRuns ? { priorExtractorRuns } : {}
|
|
18738
18733
|
});
|
|
18739
18734
|
return runScanWithRenames(kernel, runOptions);
|
|
18740
18735
|
};
|
|
18741
18736
|
}
|
|
18742
18737
|
function buildRunScanOptions(args2) {
|
|
18743
|
-
const { opts, prior, priorExtractorRuns,
|
|
18738
|
+
const { opts, prior, priorExtractorRuns, referenceablePaths } = args2;
|
|
18744
18739
|
const runOptions = {
|
|
18745
18740
|
roots: args2.effectiveRoots.slice(),
|
|
18746
18741
|
tokenize: !opts.noTokens,
|
|
@@ -18748,11 +18743,6 @@ function buildRunScanOptions(args2) {
|
|
|
18748
18743
|
ignoreFilter: args2.ignoreFilter,
|
|
18749
18744
|
strict: args2.strict,
|
|
18750
18745
|
emitter: buildRunScanEmitter(opts),
|
|
18751
|
-
// Orphan job-file detection, empty list means "no orphans
|
|
18752
|
-
// visible from this caller" (legacy behaviour). The orchestrator
|
|
18753
|
-
// defaults to `[]` when the field is absent; we always pass the
|
|
18754
|
-
// array (possibly empty) to keep the wiring uniform.
|
|
18755
|
-
orphanJobFiles: orphanJobFiles ?? [],
|
|
18756
18746
|
activeProvider: args2.activeProvider,
|
|
18757
18747
|
recommendedNodeLimit: args2.recommendedNodeLimit,
|
|
18758
18748
|
overrideMaxNodes: opts.maxNodes ?? null,
|
|
@@ -18795,7 +18785,7 @@ async function rebuildOnDrift(opts, dbPath) {
|
|
|
18795
18785
|
})
|
|
18796
18786
|
};
|
|
18797
18787
|
}
|
|
18798
|
-
async function runPersistPath(opts, dbPath,
|
|
18788
|
+
async function runPersistPath(opts, dbPath, strict, loadPrior, runScanWith, extensions) {
|
|
18799
18789
|
const driftError = await rebuildOnDrift(opts, dbPath);
|
|
18800
18790
|
if (driftError) return driftError;
|
|
18801
18791
|
let outcome;
|
|
@@ -18803,11 +18793,9 @@ async function runPersistPath(opts, dbPath, jobsDir, strict, loadPrior, runScanW
|
|
|
18803
18793
|
outcome = await withSqlite({ databasePath: dbPath }, async (adapter) => {
|
|
18804
18794
|
const prior = await loadPrior(adapter);
|
|
18805
18795
|
const priorExtractorRuns = opts.changed && prior ? await adapter.scans.loadExtractorRuns() : void 0;
|
|
18806
|
-
const referencedJobFiles = await adapter.jobs.listReferencedFilePaths();
|
|
18807
|
-
const orphanJobFiles = findOrphanJobFiles(jobsDir, referencedJobFiles).orphanFilePaths;
|
|
18808
18796
|
let scanned;
|
|
18809
18797
|
try {
|
|
18810
|
-
scanned = await runScanWith(prior, priorExtractorRuns
|
|
18798
|
+
scanned = await runScanWith(prior, priorExtractorRuns);
|
|
18811
18799
|
} catch (err) {
|
|
18812
18800
|
return { kind: "scan-error", message: formatErrorMessage(err) };
|
|
18813
18801
|
}
|
|
@@ -18822,6 +18810,7 @@ async function runPersistPath(opts, dbPath, jobsDir, strict, loadPrior, runScanW
|
|
|
18822
18810
|
enrichments: scanned.enrichments,
|
|
18823
18811
|
contributions: scanned.contributions,
|
|
18824
18812
|
contributionErrors: scanned.contributionErrors,
|
|
18813
|
+
linkScores: scanned.linkScores,
|
|
18825
18814
|
registeredContributionKeys: collectRegisteredContributionKeys(extensions),
|
|
18826
18815
|
freshlyRunTuples: scanned.freshlyRunTuples
|
|
18827
18816
|
});
|
|
@@ -18938,7 +18927,7 @@ var InitCommand = class extends SmCommand {
|
|
|
18938
18927
|
async run() {
|
|
18939
18928
|
const ctx = defaultRuntimeContext();
|
|
18940
18929
|
const scopeRoot = ctx.cwd;
|
|
18941
|
-
const skillMapDir =
|
|
18930
|
+
const skillMapDir = join16(scopeRoot, SKILL_MAP_DIR);
|
|
18942
18931
|
const settingsPath = defaultSettingsPath(scopeRoot);
|
|
18943
18932
|
const localPath = defaultLocalSettingsPath(scopeRoot);
|
|
18944
18933
|
const ignorePath = defaultIgnoreFilePath(scopeRoot);
|
|
@@ -18984,7 +18973,7 @@ var InitCommand = class extends SmCommand {
|
|
|
18984
18973
|
const okGlyph = ansi.green("\u2713");
|
|
18985
18974
|
const updated = await ensureGitignoreEntries(scopeRoot, GITIGNORE_ENTRIES);
|
|
18986
18975
|
if (updated) {
|
|
18987
|
-
const gitignorePath =
|
|
18976
|
+
const gitignorePath = join16(scopeRoot, ".gitignore");
|
|
18988
18977
|
printer.info(
|
|
18989
18978
|
GITIGNORE_ENTRIES.length === 1 ? tx(INIT_TEXTS.gitignoreUpdatedSingular, { glyph: okGlyph, path: gitignorePath }) : tx(INIT_TEXTS.gitignoreUpdatedPlural, {
|
|
18990
18979
|
glyph: okGlyph,
|
|
@@ -19045,7 +19034,7 @@ async function safeUnlink(path) {
|
|
|
19045
19034
|
}
|
|
19046
19035
|
async function writeDryRunGitignorePlan(printer, scopeRoot) {
|
|
19047
19036
|
const wouldAdd = await previewGitignoreEntries(scopeRoot, GITIGNORE_ENTRIES);
|
|
19048
|
-
const gitignorePath =
|
|
19037
|
+
const gitignorePath = join16(scopeRoot, ".gitignore");
|
|
19049
19038
|
if (wouldAdd.length === 0) {
|
|
19050
19039
|
printer.info(tx(INIT_TEXTS.dryRunWouldLeaveGitignoreUnchanged, { path: gitignorePath }));
|
|
19051
19040
|
} else if (wouldAdd.length === 1) {
|
|
@@ -19150,7 +19139,7 @@ async function runFirstScan(scopeRoot, strict, printer, stderr, stdin, ansi) {
|
|
|
19150
19139
|
return hasErrors ? ExitCode.Issues : ExitCode.Ok;
|
|
19151
19140
|
}
|
|
19152
19141
|
async function previewGitignoreEntries(scopeRoot, entries) {
|
|
19153
|
-
const path =
|
|
19142
|
+
const path = join16(scopeRoot, ".gitignore");
|
|
19154
19143
|
const body = await pathExists(path) ? await readFile3(path, "utf8") : "";
|
|
19155
19144
|
const present = new Set(
|
|
19156
19145
|
body.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"))
|
|
@@ -19158,7 +19147,7 @@ async function previewGitignoreEntries(scopeRoot, entries) {
|
|
|
19158
19147
|
return entries.filter((entry) => !present.has(entry));
|
|
19159
19148
|
}
|
|
19160
19149
|
async function ensureGitignoreEntries(scopeRoot, entries) {
|
|
19161
|
-
const path =
|
|
19150
|
+
const path = join16(scopeRoot, ".gitignore");
|
|
19162
19151
|
let body = "";
|
|
19163
19152
|
if (await pathExists(path)) {
|
|
19164
19153
|
body = await readFile3(path, "utf8");
|
|
@@ -19699,6 +19688,33 @@ import { unlink as unlink2 } from "fs/promises";
|
|
|
19699
19688
|
import { relative as relative6 } from "path";
|
|
19700
19689
|
import { Command as Command19, Option as Option18 } from "clipanion";
|
|
19701
19690
|
|
|
19691
|
+
// kernel/jobs/orphan-files.ts
|
|
19692
|
+
import { readdirSync as readdirSync9, statSync as statSync8 } from "fs";
|
|
19693
|
+
import { join as join17, resolve as resolve31 } from "path";
|
|
19694
|
+
function findOrphanJobFiles(jobsDir, referencedPaths) {
|
|
19695
|
+
let entries;
|
|
19696
|
+
try {
|
|
19697
|
+
const stat3 = statSync8(jobsDir);
|
|
19698
|
+
if (!stat3.isDirectory()) {
|
|
19699
|
+
return { orphanFilePaths: [], referencedCount: referencedPaths.size };
|
|
19700
|
+
}
|
|
19701
|
+
entries = readdirSync9(jobsDir, { withFileTypes: true });
|
|
19702
|
+
} catch {
|
|
19703
|
+
return { orphanFilePaths: [], referencedCount: referencedPaths.size };
|
|
19704
|
+
}
|
|
19705
|
+
const orphans = [];
|
|
19706
|
+
for (const entry of entries) {
|
|
19707
|
+
if (entry.isSymbolicLink()) continue;
|
|
19708
|
+
if (!entry.isFile()) continue;
|
|
19709
|
+
const name = entry.name;
|
|
19710
|
+
if (!name.endsWith(".md")) continue;
|
|
19711
|
+
const abs = resolve31(join17(jobsDir, name));
|
|
19712
|
+
if (!referencedPaths.has(abs)) orphans.push(abs);
|
|
19713
|
+
}
|
|
19714
|
+
orphans.sort();
|
|
19715
|
+
return { orphanFilePaths: orphans, referencedCount: referencedPaths.size };
|
|
19716
|
+
}
|
|
19717
|
+
|
|
19702
19718
|
// cli/i18n/jobs.texts.ts
|
|
19703
19719
|
var JOBS_TEXTS = {
|
|
19704
19720
|
pruneErrorPrefix: "{{glyph}} sm job prune: {{message}}\n",
|
|
@@ -23899,6 +23915,7 @@ function createWatcherRuntime(opts) {
|
|
|
23899
23915
|
enrichments,
|
|
23900
23916
|
contributions,
|
|
23901
23917
|
contributionErrors,
|
|
23918
|
+
linkScores,
|
|
23902
23919
|
freshlyRunTuples
|
|
23903
23920
|
} = ran;
|
|
23904
23921
|
await withSqlite(
|
|
@@ -23909,6 +23926,7 @@ function createWatcherRuntime(opts) {
|
|
|
23909
23926
|
enrichments,
|
|
23910
23927
|
contributions,
|
|
23911
23928
|
contributionErrors,
|
|
23929
|
+
linkScores,
|
|
23912
23930
|
registeredContributionKeys: collectRegisteredContributionKeys(composed),
|
|
23913
23931
|
freshlyRunTuples
|
|
23914
23932
|
})
|
|
@@ -24391,7 +24409,7 @@ var ScanCommand = class extends SmCommand {
|
|
|
24391
24409
|
details: `
|
|
24392
24410
|
Walks the given roots with the built-in claude Provider, runs the
|
|
24393
24411
|
frontmatter / slash / at-directive / external-url-counter
|
|
24394
|
-
extractors per node, then the
|
|
24412
|
+
extractors per node, then the name-collision / broken-ref /
|
|
24395
24413
|
superseded analyzers over the full graph. Emits a ScanResult
|
|
24396
24414
|
conforming to scan-result.schema.json.
|
|
24397
24415
|
|
|
@@ -28494,6 +28512,43 @@ var WsBroadcaster = class {
|
|
|
28494
28512
|
}
|
|
28495
28513
|
};
|
|
28496
28514
|
|
|
28515
|
+
// server/heartbeat.ts
|
|
28516
|
+
var WS_HEARTBEAT_INTERVAL_MS = 3e4;
|
|
28517
|
+
function startWsHeartbeat(wss, opts = {}) {
|
|
28518
|
+
const intervalMs = opts.intervalMs ?? WS_HEARTBEAT_INTERVAL_MS;
|
|
28519
|
+
const alive = /* @__PURE__ */ new WeakMap();
|
|
28520
|
+
const onConnection = (socket) => {
|
|
28521
|
+
alive.set(socket, true);
|
|
28522
|
+
socket.on("pong", () => {
|
|
28523
|
+
alive.set(socket, true);
|
|
28524
|
+
});
|
|
28525
|
+
};
|
|
28526
|
+
wss.on("connection", onConnection);
|
|
28527
|
+
const timer = setInterval(() => {
|
|
28528
|
+
for (const socket of wss.clients) {
|
|
28529
|
+
if (alive.get(socket) === false) {
|
|
28530
|
+
socket.terminate();
|
|
28531
|
+
continue;
|
|
28532
|
+
}
|
|
28533
|
+
alive.set(socket, false);
|
|
28534
|
+
try {
|
|
28535
|
+
socket.ping();
|
|
28536
|
+
} catch {
|
|
28537
|
+
}
|
|
28538
|
+
}
|
|
28539
|
+
}, intervalMs);
|
|
28540
|
+
timer.unref?.();
|
|
28541
|
+
let stopped = false;
|
|
28542
|
+
return {
|
|
28543
|
+
stop() {
|
|
28544
|
+
if (stopped) return;
|
|
28545
|
+
stopped = true;
|
|
28546
|
+
clearInterval(timer);
|
|
28547
|
+
wss.off("connection", onConnection);
|
|
28548
|
+
}
|
|
28549
|
+
};
|
|
28550
|
+
}
|
|
28551
|
+
|
|
28497
28552
|
// server/kind-registry.ts
|
|
28498
28553
|
function buildKindRegistry(providers) {
|
|
28499
28554
|
const registry = {};
|
|
@@ -28777,6 +28832,7 @@ async function createServer(options, extra = {}) {
|
|
|
28777
28832
|
});
|
|
28778
28833
|
const wss = new WebSocketServer({ noServer: true });
|
|
28779
28834
|
const server = await listenAsync(app.fetch, wss, options.host, options.port);
|
|
28835
|
+
const heartbeat = startWsHeartbeat(wss);
|
|
28780
28836
|
const addr = server.address();
|
|
28781
28837
|
const address = normalizeAddress(addr, options.host, options.port);
|
|
28782
28838
|
let watcherService = null;
|
|
@@ -28810,6 +28866,7 @@ async function createServer(options, extra = {}) {
|
|
|
28810
28866
|
const close = async () => {
|
|
28811
28867
|
if (closed) return;
|
|
28812
28868
|
closed = true;
|
|
28869
|
+
heartbeat.stop();
|
|
28813
28870
|
if (watcherService) {
|
|
28814
28871
|
try {
|
|
28815
28872
|
await watcherService.stop();
|
|
@@ -31152,4 +31209,4 @@ function resolveBareDefault() {
|
|
|
31152
31209
|
process.exit(ExitCode.Error);
|
|
31153
31210
|
}
|
|
31154
31211
|
//# sourceMappingURL=cli.js.map
|
|
31155
|
-
//# debugId=
|
|
31212
|
+
//# debugId=e6038423-ea97-5e66-b371-0916d382d819
|