@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.
Files changed (60) hide show
  1. package/lib/dist/activation/search-intercept.d.ts +59 -0
  2. package/lib/dist/activation/search-intercept.d.ts.map +1 -0
  3. package/lib/dist/activation/search-intercept.js +195 -0
  4. package/lib/dist/activation/search-intercept.js.map +1 -0
  5. package/lib/dist/bin/specship.js +70 -7
  6. package/lib/dist/bin/specship.js.map +1 -1
  7. package/lib/dist/bin/uninstall.d.ts +4 -3
  8. package/lib/dist/bin/uninstall.d.ts.map +1 -1
  9. package/lib/dist/bin/uninstall.js +4 -3
  10. package/lib/dist/bin/uninstall.js.map +1 -1
  11. package/lib/dist/installer/index.d.ts +20 -6
  12. package/lib/dist/installer/index.d.ts.map +1 -1
  13. package/lib/dist/installer/index.js +123 -63
  14. package/lib/dist/installer/index.js.map +1 -1
  15. package/lib/dist/installer/targets/claude.d.ts +9 -0
  16. package/lib/dist/installer/targets/claude.d.ts.map +1 -1
  17. package/lib/dist/installer/targets/claude.js +46 -0
  18. package/lib/dist/installer/targets/claude.js.map +1 -1
  19. package/lib/dist/installer/targets/gemini.d.ts +78 -0
  20. package/lib/dist/installer/targets/gemini.d.ts.map +1 -0
  21. package/lib/dist/installer/targets/gemini.js +200 -0
  22. package/lib/dist/installer/targets/gemini.js.map +1 -0
  23. package/lib/dist/installer/targets/registry.d.ts +5 -2
  24. package/lib/dist/installer/targets/registry.d.ts.map +1 -1
  25. package/lib/dist/installer/targets/registry.js +7 -3
  26. package/lib/dist/installer/targets/registry.js.map +1 -1
  27. package/lib/dist/installer/targets/types.d.ts +6 -2
  28. package/lib/dist/installer/targets/types.d.ts.map +1 -1
  29. package/lib/dist/jira/auto-publish.d.ts.map +1 -1
  30. package/lib/dist/jira/auto-publish.js +27 -3
  31. package/lib/dist/jira/auto-publish.js.map +1 -1
  32. package/lib/dist/jira/client.d.ts +7 -0
  33. package/lib/dist/jira/client.d.ts.map +1 -1
  34. package/lib/dist/jira/client.js +36 -1
  35. package/lib/dist/jira/client.js.map +1 -1
  36. package/lib/dist/jira/coverage.d.ts.map +1 -1
  37. package/lib/dist/jira/coverage.js +10 -0
  38. package/lib/dist/jira/coverage.js.map +1 -1
  39. package/lib/dist/jira/publish.d.ts +17 -0
  40. package/lib/dist/jira/publish.d.ts.map +1 -1
  41. package/lib/dist/jira/publish.js +0 -0
  42. package/lib/dist/jira/publish.js.map +1 -1
  43. package/lib/dist/jira/published-specs.d.ts +6 -0
  44. package/lib/dist/jira/published-specs.d.ts.map +1 -1
  45. package/lib/dist/jira/published-specs.js +50 -9
  46. package/lib/dist/jira/published-specs.js.map +1 -1
  47. package/lib/dist/jira/repo-config.d.ts +14 -0
  48. package/lib/dist/jira/repo-config.d.ts.map +1 -1
  49. package/lib/dist/jira/repo-config.js +27 -0
  50. package/lib/dist/jira/repo-config.js.map +1 -1
  51. package/lib/dist/mcp/jira-tools.d.ts.map +1 -1
  52. package/lib/dist/mcp/jira-tools.js +20 -4
  53. package/lib/dist/mcp/jira-tools.js.map +1 -1
  54. package/lib/dist/mcp/tools.d.ts +50 -0
  55. package/lib/dist/mcp/tools.d.ts.map +1 -1
  56. package/lib/dist/mcp/tools.js +205 -8
  57. package/lib/dist/mcp/tools.js.map +1 -1
  58. package/lib/node_modules/.package-lock.json +1 -1
  59. package/lib/package.json +1 -1
  60. package/package.json +1 -1
@@ -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
- const tokens = [...new Set(query.split(/[\s,()[\]]+/)
2008
- .map((t) => t.replace(FILE_EXT, '').trim())
2009
- .filter((t) => t.length >= 3 && /^[A-Za-z_$][\w$]*(?:(?:::|\.)[\w$]+)*$/.test(t)))].slice(0, 16);
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) => CALLABLE.has(n.kind) && !isTestPath(n.filePath))
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
- const nonLow = relevantFiles.filter(([p]) => !isLowValue(p));
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