@specship/specship-win32-arm64 0.22.0 → 0.23.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/lib/dist/activation/search-intercept.d.ts +59 -0
- package/lib/dist/activation/search-intercept.d.ts.map +1 -0
- package/lib/dist/activation/search-intercept.js +195 -0
- package/lib/dist/activation/search-intercept.js.map +1 -0
- package/lib/dist/bin/specship.js +70 -7
- package/lib/dist/bin/specship.js.map +1 -1
- package/lib/dist/bin/uninstall.d.ts +4 -3
- package/lib/dist/bin/uninstall.d.ts.map +1 -1
- package/lib/dist/bin/uninstall.js +4 -3
- package/lib/dist/bin/uninstall.js.map +1 -1
- package/lib/dist/installer/index.d.ts +20 -6
- package/lib/dist/installer/index.d.ts.map +1 -1
- package/lib/dist/installer/index.js +123 -63
- package/lib/dist/installer/index.js.map +1 -1
- package/lib/dist/installer/targets/claude.d.ts +9 -0
- package/lib/dist/installer/targets/claude.d.ts.map +1 -1
- package/lib/dist/installer/targets/claude.js +46 -0
- package/lib/dist/installer/targets/claude.js.map +1 -1
- package/lib/dist/installer/targets/gemini.d.ts +78 -0
- package/lib/dist/installer/targets/gemini.d.ts.map +1 -0
- package/lib/dist/installer/targets/gemini.js +200 -0
- package/lib/dist/installer/targets/gemini.js.map +1 -0
- package/lib/dist/installer/targets/registry.d.ts +5 -2
- package/lib/dist/installer/targets/registry.d.ts.map +1 -1
- package/lib/dist/installer/targets/registry.js +7 -3
- package/lib/dist/installer/targets/registry.js.map +1 -1
- package/lib/dist/installer/targets/types.d.ts +6 -2
- package/lib/dist/installer/targets/types.d.ts.map +1 -1
- package/lib/dist/jira/auto-publish.d.ts.map +1 -1
- package/lib/dist/jira/auto-publish.js +27 -3
- package/lib/dist/jira/auto-publish.js.map +1 -1
- package/lib/dist/jira/client.d.ts +7 -0
- package/lib/dist/jira/client.d.ts.map +1 -1
- package/lib/dist/jira/client.js +36 -1
- package/lib/dist/jira/client.js.map +1 -1
- package/lib/dist/jira/coverage.d.ts.map +1 -1
- package/lib/dist/jira/coverage.js +10 -0
- package/lib/dist/jira/coverage.js.map +1 -1
- package/lib/dist/jira/publish.d.ts +17 -0
- package/lib/dist/jira/publish.d.ts.map +1 -1
- package/lib/dist/jira/publish.js +0 -0
- package/lib/dist/jira/publish.js.map +1 -1
- package/lib/dist/jira/published-specs.d.ts +6 -0
- package/lib/dist/jira/published-specs.d.ts.map +1 -1
- package/lib/dist/jira/published-specs.js +50 -9
- package/lib/dist/jira/published-specs.js.map +1 -1
- package/lib/dist/jira/repo-config.d.ts +14 -0
- package/lib/dist/jira/repo-config.d.ts.map +1 -1
- package/lib/dist/jira/repo-config.js +27 -0
- package/lib/dist/jira/repo-config.js.map +1 -1
- package/lib/dist/mcp/jira-tools.d.ts.map +1 -1
- package/lib/dist/mcp/jira-tools.js +20 -4
- package/lib/dist/mcp/jira-tools.js.map +1 -1
- package/lib/dist/mcp/tools.d.ts +50 -0
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +205 -8
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/node_modules/.package-lock.json +1 -1
- package/lib/package.json +1 -1
- package/package.json +1 -1
package/lib/dist/mcp/tools.js
CHANGED
|
@@ -5,8 +5,10 @@
|
|
|
5
5
|
* Defines the tools exposed by the SpecShip MCP server.
|
|
6
6
|
*/
|
|
7
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
-
exports.ToolHandler = exports.tools = void 0;
|
|
8
|
+
exports.ToolHandler = exports.tools = exports.MAX_PINNED_FILES = exports.SEEDABLE_KINDS = void 0;
|
|
9
9
|
exports.getExploreBudget = getExploreBudget;
|
|
10
|
+
exports.extractNamedTokens = extractNamedTokens;
|
|
11
|
+
exports.resolveNamedFilePaths = resolveNamedFilePaths;
|
|
10
12
|
exports.getExploreOutputBudget = getExploreOutputBudget;
|
|
11
13
|
exports.formatStaleBanner = formatStaleBanner;
|
|
12
14
|
exports.formatStaleFooter = formatStaleFooter;
|
|
@@ -87,6 +89,129 @@ function getExploreBudget(fileCount) {
|
|
|
87
89
|
return 4;
|
|
88
90
|
return 5;
|
|
89
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* File extensions stripped from a named token to recover its basename
|
|
94
|
+
* (`server-instructions.ts` → `server-instructions`). Symbols are indexed by
|
|
95
|
+
* basename, so both forms are tried — see `extractNamedTokens`.
|
|
96
|
+
*/
|
|
97
|
+
const FILE_EXT_RE = /\.(?:java|kt|kts|ts|tsx|js|jsx|mjs|cjs|cs|py|go|rb|php|swift|rs|cpp|cc|cxx|c|h|hpp|scala|lua|dart|vue|svelte)$/i;
|
|
98
|
+
/**
|
|
99
|
+
* A query token that could name a symbol or a file.
|
|
100
|
+
*
|
|
101
|
+
* `-` is admitted deliberately (REQ-EXPLORE-PIN-002): kebab-case is the
|
|
102
|
+
* dominant filename convention here and in most TS projects, and the previous
|
|
103
|
+
* `[\w$]`-only pattern silently discarded every hyphenated token — so naming
|
|
104
|
+
* `server-instructions.ts`, the most natural way to ask for that file, seeded
|
|
105
|
+
* nothing at all. Admitting `-` cannot pull in unrelated files on its own: a
|
|
106
|
+
* token only seeds when it RESOLVES against an indexed path or symbol name, so
|
|
107
|
+
* prose like "point-of-use" matches nothing and falls away.
|
|
108
|
+
*/
|
|
109
|
+
const NAMED_TOKEN_RE = /^[A-Za-z_$][\w$-]*(?:(?:::|[./])[\w$-]+)*$/;
|
|
110
|
+
/**
|
|
111
|
+
* NodeKinds a named token may seed the subgraph with (REQ-EXPLORE-PIN-003).
|
|
112
|
+
*
|
|
113
|
+
* Callables plus the type-ish kinds an agent routinely asks about by name — a
|
|
114
|
+
* configuration constant or an interface is as likely to be the subject of a
|
|
115
|
+
* question as a function is, and restricting to callables meant naming one had
|
|
116
|
+
* no effect on ranking whatsoever. Deliberately EXCLUDES `variable`,
|
|
117
|
+
* `property`, `field`, `parameter`, `import`, and `export`: they outnumber real
|
|
118
|
+
* answers by orders of magnitude and carry no standalone meaning, so seeding
|
|
119
|
+
* them would spend the output budget on noise. Overload floods stay bounded by
|
|
120
|
+
* the existing `cands.length <= 3` cap, which is kind-independent.
|
|
121
|
+
*/
|
|
122
|
+
exports.SEEDABLE_KINDS = new Set([
|
|
123
|
+
'method', 'function', 'component', 'constructor',
|
|
124
|
+
'class', 'interface', 'type_alias', 'constant', 'enum', 'struct',
|
|
125
|
+
'trait', 'protocol',
|
|
126
|
+
]);
|
|
127
|
+
/**
|
|
128
|
+
* Pull the tokens from a query that could name a symbol or file, in order of
|
|
129
|
+
* appearance, deduplicated and capped.
|
|
130
|
+
*
|
|
131
|
+
* A token carrying a file extension yields BOTH forms — the extension-bearing
|
|
132
|
+
* one (the strongest signal that the agent means a file, so it must survive to
|
|
133
|
+
* be matched against paths) and the stripped basename (how symbols are
|
|
134
|
+
* indexed). Stripping is therefore additive, never a precondition.
|
|
135
|
+
*/
|
|
136
|
+
function extractNamedTokens(query, limit = 16) {
|
|
137
|
+
const out = [];
|
|
138
|
+
const seen = new Set();
|
|
139
|
+
const push = (t) => {
|
|
140
|
+
if (t.length >= 3 && !seen.has(t)) {
|
|
141
|
+
seen.add(t);
|
|
142
|
+
out.push(t);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
for (const raw of query.split(/[\s,()[\]]+/)) {
|
|
146
|
+
const t = raw.trim();
|
|
147
|
+
if (!t || !NAMED_TOKEN_RE.test(t))
|
|
148
|
+
continue;
|
|
149
|
+
push(t);
|
|
150
|
+
const stripped = t.replace(FILE_EXT_RE, '');
|
|
151
|
+
if (stripped !== t)
|
|
152
|
+
push(stripped);
|
|
153
|
+
}
|
|
154
|
+
return out.slice(0, limit);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Cap on files pinned by name in a single explore call (REQ-EXPLORE-PIN-001).
|
|
158
|
+
* Pinned files take first claim on the output budget, so an unbounded count
|
|
159
|
+
* would let a name-heavy query consume the whole response and crowd out the
|
|
160
|
+
* ranked context that answers the rest of the question.
|
|
161
|
+
*/
|
|
162
|
+
exports.MAX_PINNED_FILES = 4;
|
|
163
|
+
/**
|
|
164
|
+
* Resolve query tokens that name an indexed FILE to their project-relative
|
|
165
|
+
* paths (REQ-EXPLORE-PIN-001).
|
|
166
|
+
*
|
|
167
|
+
* Symbol lookup alone cannot do this: `getNodesByName` / `findAllSymbols` match
|
|
168
|
+
* symbols, so naming a file by path found nothing and the file was left to the
|
|
169
|
+
* ranker — which drops it whenever it has low graph connectivity. Naming a file
|
|
170
|
+
* is the strongest possible relevance signal, so it gets resolved explicitly.
|
|
171
|
+
*
|
|
172
|
+
* Match order per token, first hit wins:
|
|
173
|
+
* 1. exact project-relative path
|
|
174
|
+
* 2. trailing path fragment (`mcp/server-instructions.ts`)
|
|
175
|
+
* 3. unique basename, with or without extension
|
|
176
|
+
*
|
|
177
|
+
* A bare basename matching MORE than one file resolves to nothing: guessing one
|
|
178
|
+
* of several `claude.ts` files would be worse than letting the ranker decide,
|
|
179
|
+
* and the agent can disambiguate by adding a path segment. Results preserve
|
|
180
|
+
* query order (earliest-named pins first) and are capped.
|
|
181
|
+
*/
|
|
182
|
+
function resolveNamedFilePaths(tokens, filePaths, cap = exports.MAX_PINNED_FILES) {
|
|
183
|
+
if (tokens.length === 0 || filePaths.length === 0)
|
|
184
|
+
return [];
|
|
185
|
+
const basename = (p) => p.slice(p.lastIndexOf('/') + 1);
|
|
186
|
+
const stripExt = (s) => s.replace(/\.[^./]+$/, '');
|
|
187
|
+
const out = [];
|
|
188
|
+
const seen = new Set();
|
|
189
|
+
for (const raw of tokens) {
|
|
190
|
+
if (out.length >= cap)
|
|
191
|
+
break;
|
|
192
|
+
const t = raw.toLowerCase();
|
|
193
|
+
if (!t)
|
|
194
|
+
continue;
|
|
195
|
+
let hit;
|
|
196
|
+
// 1. exact path, then 2. trailing fragment — both unambiguous by shape.
|
|
197
|
+
hit = filePaths.find((p) => p.toLowerCase() === t)
|
|
198
|
+
?? filePaths.find((p) => p.toLowerCase().endsWith(`/${t}`));
|
|
199
|
+
// 3. basename, with or without extension — only when it names ONE file.
|
|
200
|
+
if (!hit) {
|
|
201
|
+
const matches = filePaths.filter((p) => {
|
|
202
|
+
const b = basename(p).toLowerCase();
|
|
203
|
+
return b === t || stripExt(b) === stripExt(t);
|
|
204
|
+
});
|
|
205
|
+
if (matches.length === 1)
|
|
206
|
+
hit = matches[0];
|
|
207
|
+
}
|
|
208
|
+
if (hit && !seen.has(hit)) {
|
|
209
|
+
seen.add(hit);
|
|
210
|
+
out.push(hit);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return out;
|
|
214
|
+
}
|
|
90
215
|
function getExploreOutputBudget(fileCount) {
|
|
91
216
|
// Tiered budget, scaled to project size. The budget is a CEILING (relevance
|
|
92
217
|
// still gates WHAT is included), and it MUST stay under the agent's INLINE
|
|
@@ -1999,14 +2124,22 @@ class ToolHandler {
|
|
|
1999
2124
|
// trace endpoint picker uses) and inject it as an entry, so every symbol the
|
|
2000
2125
|
// agent explicitly named is in the subgraph and its file is scored.
|
|
2001
2126
|
const namedSeedIds = new Set();
|
|
2127
|
+
// Named-file pinning accounting (REQ-EXPLORE-PIN-001): which files the
|
|
2128
|
+
// agent named that we committed to render, which were dropped by the pin
|
|
2129
|
+
// cap, and which file-shaped tokens matched nothing — the render stage
|
|
2130
|
+
// reports the last two so a miss is never silent.
|
|
2131
|
+
const pinnedFilePaths = [];
|
|
2132
|
+
const pinnedOverCap = [];
|
|
2133
|
+
const namedPathMisses = [];
|
|
2002
2134
|
{
|
|
2003
|
-
const FILE_EXT = /\.(?:java|kt|kts|ts|tsx|js|jsx|mjs|cjs|cs|py|go|rb|php|swift|rs|cpp|cc|cxx|c|h|hpp|scala|lua|dart|vue|svelte)$/i;
|
|
2004
|
-
const CALLABLE = new Set(['method', 'function', 'component', 'constructor']);
|
|
2005
2135
|
const isTestPath = (p) => /(^|\/)(tests?|specs?|__tests__|testdata|mocks?|fixtures?)\//i.test(p) || /\.(test|spec)\.[a-z]+$/i.test(p);
|
|
2006
2136
|
const bodyLines = (n) => Math.max(0, (n.endLine ?? n.startLine) - n.startLine);
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2137
|
+
// Token shapes + seedable kinds are module-level and unit-tested
|
|
2138
|
+
// (REQ-EXPLORE-PIN-002 / -003): both filters used to silently discard
|
|
2139
|
+
// the most natural ways to name a target — kebab-case filenames and
|
|
2140
|
+
// non-callable kinds — so explore returned nothing for a symbol the
|
|
2141
|
+
// agent had named verbatim and it fell back to Grep.
|
|
2142
|
+
const tokens = extractNamedTokens(query);
|
|
2010
2143
|
// PascalCase tokens in the query are type/file disambiguators — when the
|
|
2011
2144
|
// agent writes "DataRequest task validate", the `task`/`validate` it wants
|
|
2012
2145
|
// are DataRequest's, NOT the same-named overloads in Validation.swift /
|
|
@@ -2026,7 +2159,7 @@ class ToolHandler {
|
|
|
2026
2159
|
const isQual = /[.\/]|::/.test(t);
|
|
2027
2160
|
const raw = isQual ? this.findAllSymbols(cg, t).nodes : cg.getNodesByName(t);
|
|
2028
2161
|
const cands = raw
|
|
2029
|
-
.filter((n) =>
|
|
2162
|
+
.filter((n) => exports.SEEDABLE_KINDS.has(n.kind) && !isTestPath(n.filePath))
|
|
2030
2163
|
.sort((a, b) => (bodyLines(b) > 1 ? 1 : 0) - (bodyLines(a) > 1 ? 1 : 0) || bodyLines(b) - bodyLines(a));
|
|
2031
2164
|
// A specific name (<=3 defs) injects all its defs. An overloaded name
|
|
2032
2165
|
// (`validate` = 10, `request` = 44) would flood the subgraph, so inject
|
|
@@ -2053,6 +2186,44 @@ class ToolHandler {
|
|
|
2053
2186
|
namedSeedIds.add(n.id);
|
|
2054
2187
|
}
|
|
2055
2188
|
}
|
|
2189
|
+
// Named FILES (REQ-EXPLORE-PIN-001). The symbol loop above resolves
|
|
2190
|
+
// tokens against symbol names, so a token naming a file by path matched
|
|
2191
|
+
// nothing and the file was left to the ranker — which drops it whenever
|
|
2192
|
+
// its graph connectivity is low, even though naming it is the strongest
|
|
2193
|
+
// relevance signal there is. Resolve those tokens against the file index
|
|
2194
|
+
// and seed the file's own symbols, so it inherits the same +50 score,
|
|
2195
|
+
// gate exemption, and first-place sort as a named symbol. Bounded by
|
|
2196
|
+
// MAX_PINNED_FILES so a name-heavy query can't consume the response.
|
|
2197
|
+
try {
|
|
2198
|
+
const allPaths = cg.getFiles().map((f) => f.path);
|
|
2199
|
+
const resolved = resolveNamedFilePaths(tokens, allPaths, Number.POSITIVE_INFINITY);
|
|
2200
|
+
pinnedFilePaths.push(...resolved.slice(0, exports.MAX_PINNED_FILES));
|
|
2201
|
+
pinnedOverCap.push(...resolved.slice(exports.MAX_PINNED_FILES));
|
|
2202
|
+
// A FILE-shaped token (path separator or extension — the agent named a
|
|
2203
|
+
// file, not a symbol) that resolves to nothing is reported, not
|
|
2204
|
+
// swallowed (REQ-EXPLORE-PIN-001.A5): a silent miss teaches the agent
|
|
2205
|
+
// that naming files does nothing, and the next lookup goes to Grep.
|
|
2206
|
+
for (const t of tokens) {
|
|
2207
|
+
if (!t.includes('/') && !FILE_EXT_RE.test(t))
|
|
2208
|
+
continue;
|
|
2209
|
+
if (resolveNamedFilePaths([t], allPaths, 1).length === 0)
|
|
2210
|
+
namedPathMisses.push(t);
|
|
2211
|
+
}
|
|
2212
|
+
for (const filePath of pinnedFilePaths) {
|
|
2213
|
+
for (const n of cg.getNodesInFile(filePath)) {
|
|
2214
|
+
// Same noise filter the file-grouping step applies below.
|
|
2215
|
+
if (n.kind === 'import' || n.kind === 'export')
|
|
2216
|
+
continue;
|
|
2217
|
+
if (!subgraph.nodes.has(n.id))
|
|
2218
|
+
subgraph.nodes.set(n.id, n);
|
|
2219
|
+
namedSeedIds.add(n.id);
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
catch {
|
|
2224
|
+
// A file-index hiccup must never fail the exploration — the ranked
|
|
2225
|
+
// path below still answers, just without the pin.
|
|
2226
|
+
}
|
|
2056
2227
|
}
|
|
2057
2228
|
// Step 2: Group nodes by file, score by relevance
|
|
2058
2229
|
const fileGroups = new Map();
|
|
@@ -2124,7 +2295,11 @@ class ToolHandler {
|
|
|
2124
2295
|
{
|
|
2125
2296
|
const queryMentionsTests = /\b(test|tests|testing|spec|verify|verifies)\b/i.test(query);
|
|
2126
2297
|
if (!queryMentionsTests) {
|
|
2127
|
-
|
|
2298
|
+
// A file the agent PINNED by name is exempt (REQ-EXPLORE-PIN-001.A2):
|
|
2299
|
+
// asking for a file by name outranks the low-value heuristic — the
|
|
2300
|
+
// agent asking for an icons/i18n/test file means it wants that file.
|
|
2301
|
+
const pinnedSet = new Set(pinnedFilePaths);
|
|
2302
|
+
const nonLow = relevantFiles.filter(([p]) => pinnedSet.has(p) || !isLowValue(p));
|
|
2128
2303
|
if (nonLow.length >= 2) {
|
|
2129
2304
|
relevantFiles = nonLow;
|
|
2130
2305
|
}
|
|
@@ -2372,6 +2547,7 @@ class ToolHandler {
|
|
|
2372
2547
|
let totalChars = lines.join('\n').length;
|
|
2373
2548
|
let filesIncluded = 0;
|
|
2374
2549
|
let anyFileTrimmed = false;
|
|
2550
|
+
const renderedFilePaths = new Set();
|
|
2375
2551
|
for (const [filePath, group] of sortedFiles) {
|
|
2376
2552
|
if (filesIncluded >= maxFiles)
|
|
2377
2553
|
break;
|
|
@@ -2523,6 +2699,7 @@ class ToolHandler {
|
|
|
2523
2699
|
lines.push(`#### ${filePath} — ${names} · ${tag}`, '', '```' + lang, skel.join('\n'), '```', '');
|
|
2524
2700
|
totalChars += skel.join('\n').length + 120;
|
|
2525
2701
|
filesIncluded++;
|
|
2702
|
+
renderedFilePaths.add(filePath);
|
|
2526
2703
|
continue;
|
|
2527
2704
|
}
|
|
2528
2705
|
}
|
|
@@ -2569,6 +2746,7 @@ class ToolHandler {
|
|
|
2569
2746
|
lines.push(wholeHeader, '', '```' + lang, wholeSection, '```', '');
|
|
2570
2747
|
totalChars += wholeSection.length + 200;
|
|
2571
2748
|
filesIncluded++;
|
|
2749
|
+
renderedFilePaths.add(filePath);
|
|
2572
2750
|
continue;
|
|
2573
2751
|
}
|
|
2574
2752
|
// Cluster nearby symbols to avoid reading huge gaps between distant symbols.
|
|
@@ -2806,6 +2984,25 @@ class ToolHandler {
|
|
|
2806
2984
|
lines.push('');
|
|
2807
2985
|
totalChars += fileSection.length + 200;
|
|
2808
2986
|
filesIncluded++;
|
|
2987
|
+
renderedFilePaths.add(filePath);
|
|
2988
|
+
}
|
|
2989
|
+
// Named-file accounting (REQ-EXPLORE-PIN-001.A3 / .A5). Emitted in EVERY
|
|
2990
|
+
// tier (not gated on includeCompletenessSignal): a named file that silently
|
|
2991
|
+
// fails to appear reads as "explore can't do targeted lookups" and sends
|
|
2992
|
+
// the agent to Grep — the exact decay this spec exists to stop.
|
|
2993
|
+
{
|
|
2994
|
+
const omittedNamed = [
|
|
2995
|
+
...pinnedFilePaths.filter((p) => !renderedFilePaths.has(p)),
|
|
2996
|
+
...pinnedOverCap,
|
|
2997
|
+
];
|
|
2998
|
+
if (omittedNamed.length > 0) {
|
|
2999
|
+
lines.push(`> Named files omitted for budget: ${omittedNamed.join(', ')} — specship_explore them by name in a follow-up call for their source.`);
|
|
3000
|
+
lines.push('');
|
|
3001
|
+
}
|
|
3002
|
+
if (namedPathMisses.length > 0) {
|
|
3003
|
+
lines.push(`> Named paths not found in the index (or ambiguous): ${[...new Set(namedPathMisses)].join(', ')}. Check the spelling, or add a directory segment to disambiguate.`);
|
|
3004
|
+
lines.push('');
|
|
3005
|
+
}
|
|
2809
3006
|
}
|
|
2810
3007
|
// Add remaining files as references (from both relevant and peripheral files).
|
|
2811
3008
|
// Small projects (per budget) skip this — the relevant story already fits
|