@specship/specship-win32-arm64 0.15.0 → 0.17.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 (82) hide show
  1. package/lib/dist/activation/steering.d.ts +35 -0
  2. package/lib/dist/activation/steering.d.ts.map +1 -0
  3. package/lib/dist/activation/steering.js +92 -0
  4. package/lib/dist/activation/steering.js.map +1 -0
  5. package/lib/dist/bin/specship.js +78 -4
  6. package/lib/dist/bin/specship.js.map +1 -1
  7. package/lib/dist/extraction/specs/markdown-spec-extractor.d.ts +9 -7
  8. package/lib/dist/extraction/specs/markdown-spec-extractor.d.ts.map +1 -1
  9. package/lib/dist/extraction/specs/markdown-spec-extractor.js +17 -10
  10. package/lib/dist/extraction/specs/markdown-spec-extractor.js.map +1 -1
  11. package/lib/dist/extraction/specs/spec-file-writeback.d.ts +37 -0
  12. package/lib/dist/extraction/specs/spec-file-writeback.d.ts.map +1 -0
  13. package/lib/dist/extraction/specs/spec-file-writeback.js +159 -0
  14. package/lib/dist/extraction/specs/spec-file-writeback.js.map +1 -0
  15. package/lib/dist/extraction/tree-sitter-helpers.d.ts.map +1 -1
  16. package/lib/dist/extraction/tree-sitter-helpers.js +12 -1
  17. package/lib/dist/extraction/tree-sitter-helpers.js.map +1 -1
  18. package/lib/dist/installer/index.d.ts +7 -0
  19. package/lib/dist/installer/index.d.ts.map +1 -1
  20. package/lib/dist/installer/index.js +7 -1
  21. package/lib/dist/installer/index.js.map +1 -1
  22. package/lib/dist/installer/targets/claude.d.ts +11 -2
  23. package/lib/dist/installer/targets/claude.d.ts.map +1 -1
  24. package/lib/dist/installer/targets/claude.js +83 -7
  25. package/lib/dist/installer/targets/claude.js.map +1 -1
  26. package/lib/dist/installer/targets/shared.d.ts +10 -2
  27. package/lib/dist/installer/targets/shared.d.ts.map +1 -1
  28. package/lib/dist/installer/targets/shared.js +22 -11
  29. package/lib/dist/installer/targets/shared.js.map +1 -1
  30. package/lib/dist/installer/targets/types.d.ts +10 -0
  31. package/lib/dist/installer/targets/types.d.ts.map +1 -1
  32. package/lib/dist/mcp/model-context.d.ts +51 -0
  33. package/lib/dist/mcp/model-context.d.ts.map +1 -0
  34. package/lib/dist/mcp/model-context.js +234 -0
  35. package/lib/dist/mcp/model-context.js.map +1 -0
  36. package/lib/dist/mcp/session.d.ts +1 -0
  37. package/lib/dist/mcp/session.d.ts.map +1 -1
  38. package/lib/dist/mcp/session.js +13 -0
  39. package/lib/dist/mcp/session.js.map +1 -1
  40. package/lib/dist/mcp/spec-tools.d.ts.map +1 -1
  41. package/lib/dist/mcp/spec-tools.js +44 -1
  42. package/lib/dist/mcp/spec-tools.js.map +1 -1
  43. package/lib/dist/mcp/tools.d.ts +42 -0
  44. package/lib/dist/mcp/tools.d.ts.map +1 -1
  45. package/lib/dist/mcp/tools.js +176 -16
  46. package/lib/dist/mcp/tools.js.map +1 -1
  47. package/lib/dist/resolution/spec-link-resolver.d.ts.map +1 -1
  48. package/lib/dist/resolution/spec-link-resolver.js +42 -28
  49. package/lib/dist/resolution/spec-link-resolver.js.map +1 -1
  50. package/lib/dist/server/ingest/index.js +1 -1
  51. package/lib/dist/server/ingest/ingestor.js +24 -4
  52. package/lib/dist/server/routes/claude.js +31 -5
  53. package/lib/dist/server/routes/spec.js +13 -0
  54. package/lib/dist/server/routes/workflow.js +15 -1
  55. package/lib/dist/server/server.js +2 -2
  56. package/lib/dist/statusline/index.d.ts.map +1 -1
  57. package/lib/dist/statusline/index.js +6 -0
  58. package/lib/dist/statusline/index.js.map +1 -1
  59. package/lib/dist/statusline/paths.d.ts +2 -0
  60. package/lib/dist/statusline/paths.d.ts.map +1 -1
  61. package/lib/dist/statusline/paths.js +5 -0
  62. package/lib/dist/statusline/paths.js.map +1 -1
  63. package/lib/dist/types.d.ts +8 -3
  64. package/lib/dist/types.d.ts.map +1 -1
  65. package/lib/dist/ui/assets/index-CjjaftDw.js +50 -0
  66. package/lib/dist/ui/index.html +1 -1
  67. package/lib/dist/workflows/defaults/spec-implement.yaml +5 -0
  68. package/lib/dist/workflows/defaults/spec-verify.yaml +23 -9
  69. package/lib/dist/workflows/executor.d.ts +19 -0
  70. package/lib/dist/workflows/executor.d.ts.map +1 -1
  71. package/lib/dist/workflows/executor.js +131 -18
  72. package/lib/dist/workflows/executor.js.map +1 -1
  73. package/lib/dist/workflows/runners/bash.d.ts.map +1 -1
  74. package/lib/dist/workflows/runners/bash.js +14 -1
  75. package/lib/dist/workflows/runners/bash.js.map +1 -1
  76. package/lib/node_modules/.package-lock.json +1 -1
  77. package/lib/package.json +1 -1
  78. package/package.json +1 -1
  79. package/lib/dist/server/chat/classify.js +0 -130
  80. package/lib/dist/server/routes/chat-answer.js +0 -499
  81. package/lib/dist/server/routes/chat.js +0 -144
  82. package/lib/dist/ui/assets/index-CpBBlEO2.js +0 -51
@@ -10,9 +10,13 @@ exports.getExploreBudget = getExploreBudget;
10
10
  exports.getExploreOutputBudget = getExploreOutputBudget;
11
11
  exports.formatStaleBanner = formatStaleBanner;
12
12
  exports.formatStaleFooter = formatStaleFooter;
13
+ exports.integrationEnabled = integrationEnabled;
14
+ exports.toolIntegration = toolIntegration;
15
+ exports.filterIntegrationTools = filterIntegrationTools;
13
16
  exports.getStaticTools = getStaticTools;
14
17
  const directory_1 = require("../directory");
15
18
  const statusline_1 = require("../statusline");
19
+ const model_context_1 = require("./model-context");
16
20
  // Lazy-load the heavy SpecShip chain off the MCP startup path — see the same
17
21
  // helper in engine.ts. ToolHandler must load to answer tools/list (static
18
22
  // schemas), but it must NOT drag in sqlite/query layers before the daemon binds;
@@ -469,6 +473,25 @@ exports.tools = [
469
473
  // Independent of the code graph.
470
474
  ...jira_tools_1.jiraToolDefinitions,
471
475
  ];
476
+ function integrationEnabled(name, env = process.env) {
477
+ const raw = env.SPECSHIP_INTEGRATIONS ?? '';
478
+ return raw.split(',').map((s) => s.trim().toLowerCase()).includes(name);
479
+ }
480
+ /** Which integration (if any) a tool belongs to. Core tools return null. */
481
+ function toolIntegration(toolName) {
482
+ if (toolName.startsWith('designer_'))
483
+ return 'designer';
484
+ if (toolName.startsWith('specship_jira_'))
485
+ return 'jira';
486
+ return null;
487
+ }
488
+ /** Drop tools whose integration isn't enabled (REQ-INTEG-001.A1/A2). */
489
+ function filterIntegrationTools(list, env = process.env) {
490
+ return list.filter((t) => {
491
+ const integ = toolIntegration(t.name);
492
+ return integ === null || integrationEnabled(integ, env);
493
+ });
494
+ }
472
495
  /**
473
496
  * Allowlist-filtered tool definitions WITHOUT an engine — the static surface the
474
497
  * proxy answers `tools/list` with before any project is open. Mirrors
@@ -476,11 +499,12 @@ exports.tools = [
476
499
  * note in a description only adds once `cg` is loaded; the schemas are static).
477
500
  */
478
501
  function getStaticTools() {
502
+ const base = filterIntegrationTools(exports.tools);
479
503
  const raw = process.env.SPECSHIP_MCP_TOOLS;
480
504
  if (!raw || !raw.trim())
481
- return exports.tools;
505
+ return base;
482
506
  const allow = new Set(raw.split(',').map(s => s.trim().replace(/^specship_/, '')).filter(Boolean));
483
- return allow.size ? exports.tools.filter(t => allow.has(t.name.replace(/^specship_/, ''))) : exports.tools;
507
+ return allow.size ? base.filter(t => allow.has(t.name.replace(/^specship_/, ''))) : base;
484
508
  }
485
509
  /**
486
510
  * Tool handler that executes tools against a SpecShip instance
@@ -572,10 +596,13 @@ class ToolHandler {
572
596
  * allowlist so a trimmed surface is reflected in ListTools.
573
597
  */
574
598
  getTools() {
599
+ // Integration tier first (INTEG-TIER-DOC): jira/designer tools exist only
600
+ // when explicitly enabled, before any allowlist/size gating applies.
601
+ const base = filterIntegrationTools(exports.tools);
575
602
  const allow = this.toolAllowlist();
576
603
  let visible = allow
577
- ? exports.tools.filter(t => allow.has(t.name.replace(/^specship_/, '')))
578
- : exports.tools;
604
+ ? base.filter(t => allow.has(t.name.replace(/^specship_/, '')))
605
+ : base;
579
606
  if (!this.cg)
580
607
  return visible;
581
608
  try {
@@ -614,6 +641,22 @@ class ToolHandler {
614
641
  // economics that justify trimming don't apply, so they always survive.
615
642
  visible = visible.filter(t => TINY_REPO_CORE_TOOLS.has(t.name) || t.name.startsWith('designer_'));
616
643
  }
644
+ // Haiku-tier menu trim (LOWMODEL-DOC, REQ-LOWMODEL-004): small-model
645
+ // tool choice degrades with menu size, so the code-graph group trims
646
+ // to the core three; spec/link tools and enabled integrations are
647
+ // untouched. Composes with the tiny-repo gate as an intersection.
648
+ // Schemas never vary — only the list; `execute()` still answers a
649
+ // trimmed-away tool for clients that cached the full list (A2).
650
+ if ((0, model_context_1.detectModelTier)(this.cg.getProjectRoot()) === 'haiku') {
651
+ const HAIKU_CORE = new Set(['specship_explore', 'specship_search', 'specship_node']);
652
+ const CODE_GRAPH_GROUP = new Set([
653
+ 'specship_explore', 'specship_search', 'specship_node',
654
+ 'specship_callers', 'specship_callees', 'specship_impact',
655
+ 'specship_files', 'specship_status',
656
+ 'specship_maintainability', 'specship_fitness',
657
+ ]);
658
+ visible = visible.filter(t => !CODE_GRAPH_GROUP.has(t.name) || HAIKU_CORE.has(t.name));
659
+ }
617
660
  return visible.map(tool => {
618
661
  if (tool.name === 'specship_explore') {
619
662
  return {
@@ -798,6 +841,58 @@ class ToolHandler {
798
841
  * Cost when nothing is pending — the common case — is one boolean check.
799
842
  * No I/O, no parsing of markdown beyond a per-pending-file substring scan.
800
843
  */
844
+ /**
845
+ * Model-aware compaction (MODCTX-DOC, REQ-MODCTX-002/003). Resolves the
846
+ * session's model tier (status-line marker / SPECSHIP_MODEL; SPECSHIP_COMPACT=0
847
+ * disables) and, on haiku/sonnet, compresses the response's prose
848
+ * scaffolding — fenced code stays byte-verbatim. Full tier is identity.
849
+ */
850
+ /**
851
+ * Tier-change listeners (LOWMODEL-DOC, REQ-LOWMODEL-004.A3): the haiku
852
+ * menu trim changes `tools/list`, so a mid-session /model switch must ride
853
+ * an MCP `notifications/tools/list_changed`. Sessions subscribe here; the
854
+ * change is detected on the next code-graph call (the same funnel that
855
+ * reads the tier for compaction).
856
+ */
857
+ tierListeners = new Set();
858
+ lastTier = null;
859
+ addTierChangeListener(cb) {
860
+ this.tierListeners.add(cb);
861
+ return () => this.tierListeners.delete(cb);
862
+ }
863
+ noteTier(tier) {
864
+ if (this.lastTier !== null && tier !== this.lastTier) {
865
+ for (const cb of this.tierListeners) {
866
+ try {
867
+ cb(tier);
868
+ }
869
+ catch { /* a listener must never break a tool call */ }
870
+ }
871
+ }
872
+ this.lastTier = tier;
873
+ }
874
+ withModelCompaction(result) {
875
+ if (result.isError)
876
+ return result;
877
+ let root = null;
878
+ try {
879
+ root = this.cg?.getProjectRoot() ?? null;
880
+ }
881
+ catch {
882
+ root = null;
883
+ }
884
+ const tier = (0, model_context_1.detectModelTier)(root);
885
+ this.noteTier(tier);
886
+ if (tier === 'full')
887
+ return result;
888
+ const first = result.content?.[0];
889
+ if (!first || first.type !== 'text' || typeof first.text !== 'string')
890
+ return result;
891
+ return {
892
+ ...result,
893
+ content: [{ ...first, text: (0, model_context_1.compactToolResult)(first.text, tier) }, ...result.content.slice(1)],
894
+ };
895
+ }
801
896
  withStalenessNotice(result, projectPath) {
802
897
  if (result.isError)
803
898
  return result;
@@ -900,6 +995,13 @@ class ToolHandler {
900
995
  if (!this.isToolAllowed(toolName)) {
901
996
  return this.errorResult(`Tool ${toolName} is disabled via SPECSHIP_MCP_TOOLS`);
902
997
  }
998
+ // Integration tier (INTEG-TIER-DOC, REQ-INTEG-001): a jira/designer
999
+ // call without the opt-in is rejected even if a client cached the tool.
1000
+ const integ = toolIntegration(toolName);
1001
+ if (integ && !integrationEnabled(integ)) {
1002
+ return this.errorResult(`Tool ${toolName} belongs to the optional "${integ}" integration, which is not enabled. ` +
1003
+ `Enable it with: specship install --with-${integ} (it talks to ${integ === 'jira' ? 'your Atlassian instance' : 'claude.ai — experimental'}).`);
1004
+ }
903
1005
  // Cross-cutting input validation. All tools accept an optional
904
1006
  // `projectPath` and most accept either `query`, `task`, or
905
1007
  // `symbol` — bound their lengths centrally so individual handlers
@@ -1018,7 +1120,12 @@ class ToolHandler {
1018
1120
  return this.errorResult(`Unknown tool: ${toolName}`);
1019
1121
  }
1020
1122
  const withWorktree = this.withWorktreeNotice(result, args.projectPath);
1021
- return this.withStalenessNotice(withWorktree, args.projectPath);
1123
+ const withStaleness = this.withStalenessNotice(withWorktree, args.projectPath);
1124
+ // Model-aware compaction (MODCTX-DOC): on haiku/sonnet sessions the
1125
+ // prose scaffolding compresses; code stays byte-verbatim. Only the
1126
+ // code-graph tools reach this funnel — designer/jira returned above
1127
+ // (REQ-MODCTX-004).
1128
+ return this.withModelCompaction(withStaleness);
1022
1129
  }
1023
1130
  catch (err) {
1024
1131
  return this.errorResult(`Tool execution failed: ${err instanceof Error ? err.message : String(err)}`);
@@ -1040,7 +1147,7 @@ class ToolHandler {
1040
1147
  kinds: kind ? [kind] : undefined,
1041
1148
  });
1042
1149
  if (results.length === 0) {
1043
- return this.textResult(`No results found for "${query}"`);
1150
+ return this.missWithSuggestions(cg, 'Query', query);
1044
1151
  }
1045
1152
  // Down-rank generated files within the FTS-returned set so a search
1046
1153
  // for "Send" surfaces the hand-written keeper before .pb.go stubs
@@ -1053,6 +1160,47 @@ class ToolHandler {
1053
1160
  const formatted = this.formatSearchResults(ranked);
1054
1161
  return this.textResult(this.truncateOutput(formatted));
1055
1162
  }
1163
+ /**
1164
+ * Anti-flounder miss template (LOWMODEL-DOC, REQ-LOWMODEL-001): a "not
1165
+ * found" that just says "not found" leaves the agent — small models
1166
+ * especially — to guess its next move, and the guess is usually grep/Read.
1167
+ * Instead: name the nearest REAL symbols (token-level lookup, since the
1168
+ * full query already failed FTS+LIKE) and end with a copy-pasteable next
1169
+ * call. Applies at every tier.
1170
+ */
1171
+ missWithSuggestions(cg, kindLabel, query) {
1172
+ // Split on separators AND camelCase so "AuthServic.logn" still yields
1173
+ // useful probes ("Auth", "Servic", "logn" → LIKE finds AuthService/login).
1174
+ const tokens = [...new Set(query
1175
+ .split(/[\s._:\->#/\\]+/)
1176
+ .flatMap((t) => t.split(/(?=[A-Z])/))
1177
+ .map((t) => t.trim())
1178
+ .filter((t) => t.length >= 3))].slice(0, 6);
1179
+ const seen = new Set();
1180
+ const suggestions = [];
1181
+ for (const token of tokens) {
1182
+ for (const r of cg.searchNodes(token, { limit: 3 })) {
1183
+ const key = r.node.qualifiedName;
1184
+ if (seen.has(key))
1185
+ continue;
1186
+ seen.add(key);
1187
+ suggestions.push({ name: key, where: `${r.node.filePath}:${r.node.startLine}` });
1188
+ if (suggestions.length >= 4)
1189
+ break;
1190
+ }
1191
+ if (suggestions.length >= 4)
1192
+ break;
1193
+ }
1194
+ const lines = [`${kindLabel} "${query}" not found in the index.`];
1195
+ if (suggestions.length > 0) {
1196
+ lines.push(`Closest indexed symbols: ${suggestions.map((s) => `${s.name} (${s.where})`).join(' · ')}`);
1197
+ lines.push(`Next: specship_explore "${suggestions.map((s) => s.name).join(' ')}"`);
1198
+ }
1199
+ else {
1200
+ lines.push('Nothing close in the index either. Next: specship_explore with broader words from the question — it also matches file names and doc text.');
1201
+ }
1202
+ return this.textResult(lines.join('\n'));
1203
+ }
1056
1204
  /**
1057
1205
  * Handle specship_callers
1058
1206
  */
@@ -1064,7 +1212,7 @@ class ToolHandler {
1064
1212
  const limit = (0, utils_1.clamp)(args.limit || 20, 1, 100);
1065
1213
  const allMatches = this.findAllSymbols(cg, symbol);
1066
1214
  if (allMatches.nodes.length === 0) {
1067
- return this.textResult(`Symbol "${symbol}" not found in the codebase`);
1215
+ return this.missWithSuggestions(cg, 'Symbol', symbol);
1068
1216
  }
1069
1217
  // Aggregate callers across all matching symbols
1070
1218
  const seen = new Set();
@@ -1094,7 +1242,7 @@ class ToolHandler {
1094
1242
  const limit = (0, utils_1.clamp)(args.limit || 20, 1, 100);
1095
1243
  const allMatches = this.findAllSymbols(cg, symbol);
1096
1244
  if (allMatches.nodes.length === 0) {
1097
- return this.textResult(`Symbol "${symbol}" not found in the codebase`);
1245
+ return this.missWithSuggestions(cg, 'Symbol', symbol);
1098
1246
  }
1099
1247
  // Aggregate callees across all matching symbols
1100
1248
  const seen = new Set();
@@ -1124,7 +1272,7 @@ class ToolHandler {
1124
1272
  const depth = (0, utils_1.clamp)(args.depth || 2, 1, 10);
1125
1273
  const allMatches = this.findAllSymbols(cg, symbol);
1126
1274
  if (allMatches.nodes.length === 0) {
1127
- return this.textResult(`Symbol "${symbol}" not found in the codebase`);
1275
+ return this.missWithSuggestions(cg, 'Symbol', symbol);
1128
1276
  }
1129
1277
  // Aggregate impact across all matching symbols
1130
1278
  const mergedNodes = new Map();
@@ -1267,7 +1415,7 @@ class ToolHandler {
1267
1415
  * whose qualifiedName contains another named token (`PmsProductServiceImpl::list`),
1268
1416
  * dropping unrelated `OmsOrderService::list`.
1269
1417
  */
1270
- buildFlowFromNamedSymbols(cg, query) {
1418
+ buildFlowFromNamedSymbols(cg, query, tier = 'full') {
1271
1419
  const EMPTY = { text: '', pathNodeIds: new Set(), namedNodeIds: new Set(), uniqueNamedNodeIds: new Set() };
1272
1420
  try {
1273
1421
  const CALLABLE = new Set(['method', 'function', 'component', 'constructor']);
@@ -1402,11 +1550,23 @@ class ToolHandler {
1402
1550
  out.push('## Flow (call path among the symbols you queried)', '');
1403
1551
  for (let i = 0; i < best.length; i++) {
1404
1552
  const step = best[i];
1405
- if (step.edge) {
1406
- const sy = this.synthEdgeNote(step.edge);
1407
- out.push(` ↓ ${sy ? sy.compact : step.edge.kind}`);
1553
+ if (tier === 'haiku') {
1554
+ // LOWMODEL-DOC REQ-LOWMODEL-003: small models don't synthesize
1555
+ // the flow from evidence — render each hop as ONE explicit line
1556
+ // with its mechanism inline. Additive formatting only; the code
1557
+ // bodies below are identical to the full tier's (A2).
1558
+ const mech = step.edge
1559
+ ? ` — via ${(this.synthEdgeNote(step.edge)?.compact ?? step.edge.kind)}`
1560
+ : '';
1561
+ out.push(`${i + 1}. ${i > 0 ? '→ ' : ''}${step.node.name} (${step.node.filePath}:${step.node.startLine})${mech}`);
1562
+ }
1563
+ else {
1564
+ if (step.edge) {
1565
+ const sy = this.synthEdgeNote(step.edge);
1566
+ out.push(` ↓ ${sy ? sy.compact : step.edge.kind}`);
1567
+ }
1568
+ out.push(`${i + 1}. ${step.node.name} (${step.node.filePath}:${step.node.startLine})`);
1408
1569
  }
1409
- out.push(`${i + 1}. ${step.node.name} (${step.node.filePath}:${step.node.startLine})`);
1410
1570
  }
1411
1571
  out.push('');
1412
1572
  }
@@ -2016,7 +2176,7 @@ class ToolHandler {
2016
2176
  // Compute the flow spine once — used both to prepend the Flow section (below)
2017
2177
  // and to gate adaptive source sizing: files on the spine get full source,
2018
2178
  // off-spine peers skeletonize.
2019
- const flow = this.buildFlowFromNamedSymbols(cg, query);
2179
+ const flow = this.buildFlowFromNamedSymbols(cg, query, (0, model_context_1.detectModelTier)(cg.getProjectRoot()));
2020
2180
  // Polymorphic-sibling detector for adaptive sizing. A class that implements/
2021
2181
  // extends a supertype shared by >= MIN_SIBLINGS classes is one of many
2022
2182
  // INTERCHANGEABLE implementations (OkHttp's 14 `: Interceptor` classes —
@@ -2597,7 +2757,7 @@ class ToolHandler {
2597
2757
  const lineHint = typeof args.line === 'number' && args.line > 0 ? args.line : undefined;
2598
2758
  let matches = this.findSymbolMatches(cg, symbol);
2599
2759
  if (matches.length === 0) {
2600
- return this.textResult(`Symbol "${symbol}" not found in the codebase`);
2760
+ return this.missWithSuggestions(cg, 'Symbol', symbol);
2601
2761
  }
2602
2762
  // Disambiguate a heavily-overloaded name to a specific definition the caller
2603
2763
  // pinned by file/line (the `file:line` a trail or another tool showed it) —