@selvakumaresra/specship 0.4.0 → 0.5.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/README.md +11 -1
- package/commands/ss-brainstorm.md +68 -0
- package/dist/analytics/specship-impact.d.ts +72 -0
- package/dist/analytics/specship-impact.d.ts.map +1 -0
- package/dist/analytics/specship-impact.js +216 -0
- package/dist/analytics/specship-impact.js.map +1 -0
- package/dist/bin/specship.js +4 -4
- package/dist/bin/specship.js.map +1 -1
- package/dist/db/migrations.d.ts +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +15 -1
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/schema.sql +8 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -1
- package/dist/installer/index.d.ts +2 -2
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/targets/claude.d.ts.map +1 -1
- package/dist/installer/targets/claude.js +2 -0
- package/dist/installer/targets/claude.js.map +1 -1
- package/dist/server/ingest/impact-backfill.js +69 -0
- package/dist/server/ingest/impact-query.js +343 -0
- package/dist/server/ingest/index.js +2 -1
- package/dist/server/ingest/ingestor.js +41 -6
- package/dist/server/ingest/specship-classify.js +153 -0
- package/dist/server/routes/claude.js +32 -0
- package/dist/server/routes/spec.js +94 -0
- package/dist/server/server.js +26 -2
- package/dist/web/chunk-O7434ZMN.js +1 -0
- package/dist/web/{chunk-2YUJNZ2Y.js → chunk-ODX6CT3I.js} +6 -6
- package/dist/web/chunk-RASJHUXS.js +1 -0
- package/dist/web/chunk-TQ3P2QZO.js +1 -0
- package/dist/web/chunk-WCHGDXWC.js +1 -0
- package/dist/web/index.html +1 -1
- package/dist/web/main-X2KCYXZ4.js +1 -0
- package/package.json +1 -1
- package/dist/web/chunk-B3YPFY6A.js +0 -1
- package/dist/web/chunk-GWPVKJIY.js +0 -1
- package/dist/web/main-R53HA54V.js +0 -1
package/README.md
CHANGED
|
@@ -121,7 +121,7 @@ When Claude Code explores a codebase, it spawns **Explore agents** that scan fil
|
|
|
121
121
|
| **Always Fresh** | File watcher uses native OS events (FSEvents/inotify/ReadDirectoryChangesW) with debounced auto-sync — the graph stays current as you code, zero config |
|
|
122
122
|
| **20+ Languages** | TypeScript, JavaScript, Python, Go, Rust, Java, C#, PHP, Ruby, C, C++, Objective-C, Swift, Kotlin, Dart, Lua, Luau, Svelte, Liquid, Pascal/Delphi |
|
|
123
123
|
| **Framework-aware Routes** | Recognizes web-framework routing files and links URL patterns to their handlers across 14 frameworks |
|
|
124
|
-
| **Desktop UI** | Live dashboard for cost, drift, tool-call heatmap, cache analytics, plus a project picker that auto-discovers every project you've used Claude Code in |
|
|
124
|
+
| **Desktop UI** | Live dashboard for cost, drift, tool-call heatmap, cache analytics, SpecShip token impact, plus a project picker that auto-discovers every project you've used Claude Code in |
|
|
125
125
|
| **100% Local** | No data leaves your machine. No API keys. No external services. SQLite database only |
|
|
126
126
|
|
|
127
127
|
<details>
|
|
@@ -207,6 +207,15 @@ One big number — your **cache read rate** — plus a 2×2 breakdown: creation
|
|
|
207
207
|
|
|
208
208
|
> **Project & time scope.** Every numeric on this page respects the project picker's active selection and the range selector top-right. Switching projects re-fetches without a page reload.
|
|
209
209
|
|
|
210
|
+
#### SpecShip Impact · is SpecShip earning its keep?
|
|
211
|
+
|
|
212
|
+
A dedicated page that puts SpecShip's own token cost next to what it saved, per prompt → session → project → all-projects (the Session Detail page carries the same per-prompt chip and per-session line). Two numbers, deliberately kept apart:
|
|
213
|
+
|
|
214
|
+
- **Spend — measured.** The exact tokens SpecShip's own tool calls put into the conversation.
|
|
215
|
+
- **Saved — estimated.** For each code-graph query, the size of the files its symbols live in — what a `Read` of them would have cost — deduplicated per prompt.
|
|
216
|
+
|
|
217
|
+
**"Saved" is a conservative lower bound.** It credits only a single direct read of the named files, *not* the multi-call grep + read exploration (re-reads, dead-ends, extra turns) SpecShip actually replaces — the end-to-end win that the [benchmarks](#why-specship) measure with a with-vs-without comparison. So treat spend as exact and saved as a floor; a fixed per-session tool-definition overhead is shown separately, and every estimate is marked `est.`.
|
|
218
|
+
|
|
210
219
|
#### Beyond the dashboard
|
|
211
220
|
|
|
212
221
|
Same shell, same shortcuts, more depth:
|
|
@@ -219,6 +228,7 @@ Same shell, same shortcuts, more depth:
|
|
|
219
228
|
| **Workflows + Runs** | Run any bundled or project-tier workflow, watch its DAG advance live via SSE, approve / reject pause gates, inspect per-step artifacts (plan.md, diff.md, test_results.md). |
|
|
220
229
|
| **Chat** | A codegraph-aware companion chat with slash commands, collapsible tool calls, and per-turn cost footers. |
|
|
221
230
|
| **Sessions / Heatmap / Costs / Compare / Tips** | The Claude Code analytics suite — sessions deep-dive with per-prompt expand, file/tool/subagent heatmap drilldowns, per-day cost line + by-model donut, cross-project comparison. |
|
|
231
|
+
| **SpecShip Impact** | Measured tokens SpecShip's tools spent vs. an estimated (conservative) count of tokens saved, per prompt / session / project / all-projects, with a spend-vs-saved trend and per-tool breakdown. |
|
|
222
232
|
| **Memory** | The full `CLAUDE.md` hierarchy (managed / user / project / subdir) plus `@import` resolution plus `~/.claude/memory/*.md` agent-written notes. Sources view + Effective (merged-precedence) view. |
|
|
223
233
|
| **Design system** | The visual reference for the entire app — every token, palette swatch, semantic state, button/pill family, and the 4 px node-color legibility test that the graph relies on. |
|
|
224
234
|
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Brainstorm a requirement — analyse, ground in code, explore approaches, loop with you, and ONLY on your explicit confirmation write a design brief and hand off to /ss-spec-author. Nothing is written until you confirm.
|
|
3
|
+
argument-hint: <requirement to brainstorm>
|
|
4
|
+
allowed-tools: Read, Write, Edit, Bash, mcp__specship__specship_explore, mcp__specship__specship_search, mcp__specship__specship_node, mcp__specship__specship_files, mcp__specship__specship_spec
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# SpecShip Brainstorm: `$ARGUMENTS`
|
|
8
|
+
|
|
9
|
+
The **divergent** front of spec-driven development. You explore the problem with the human and DECIDE; `/ss-spec-author` formalizes the decision into a spec. Run this conversationally — do NOT batch.
|
|
10
|
+
|
|
11
|
+
## The hard rule (read first)
|
|
12
|
+
|
|
13
|
+
**Write NOTHING to disk until the human EXPLICITLY confirms.** No brief, no spec, no scratch file during the loop. Treat the default as no-write. A vague "maybe", "looks ok", silence, or a follow-up question is **not** confirmation — only an unambiguous "yes, write it" / "confirmed" / "go ahead" counts. If the human ends the conversation without confirming, you have produced zero files, and that is correct.
|
|
14
|
+
|
|
15
|
+
## The loop
|
|
16
|
+
|
|
17
|
+
1. **Scope check.** Refuse "brainstorm the whole app" — pick one feature area. If `$ARGUMENTS` is empty, ask what they want to brainstorm.
|
|
18
|
+
2. **Ground in code.** Call `mcp__specship__specship_explore` (and `specship_search`) on terms from `$ARGUMENTS` to find where similar features live, conventions to mirror, and which files the work will likely touch. Summarize what you found.
|
|
19
|
+
3. **Approaches.** Propose **2–3 distinct approaches** with trade-offs, and lead with your recommendation and why.
|
|
20
|
+
4. **Clarify.** Ask the human **one question at a time** about the things the graph can't tell you — UX, edge cases, acceptance criteria, non-goals. Don't dump a list.
|
|
21
|
+
5. **Iterate** 3–4 until the human is satisfied with a direction. Then ask: *"Want me to write this up as a brief and hand it to /ss-spec-author?"* — and WAIT for an explicit yes.
|
|
22
|
+
|
|
23
|
+
## On explicit confirmation (and only then)
|
|
24
|
+
|
|
25
|
+
1. Derive a kebab-case `<slug>` from the feature.
|
|
26
|
+
2. Write the brief to **`specs/<slug>/brief.md`** using the format below. Leave the `spec:` field **unset** for now.
|
|
27
|
+
3. Hand off: invoke **`/ss-spec-author`** with the brief — pass the brief's path so spec-author reads it and does NOT re-ground in code (the brief already has the grounding). spec-author assigns the real spec **ID** and writes `specs/<ID>.md`.
|
|
28
|
+
4. Once spec-author has written the spec: set the brief's `spec:` field to the new ID, and add a **`brief:`** field to the spec's frontmatter holding the path to the brief **relative to the spec file's own directory**. For the usual flat layout (`specs/<ID>.md`) that is exactly `brief: <slug>/brief.md`; if spec-author ever nests the spec (e.g. `specs/<area>/<ID>.md`), write the correct relative path instead (e.g. `../<slug>/brief.md`) so the dashboard can resolve it. This links the two both ways.
|
|
29
|
+
5. If spec-author fails, STOP and tell the human: the brief exists with `spec:` unset; retry is re-running `/ss-spec-author` with the same brief path. Do not hand-write a spec.
|
|
30
|
+
6. Point them at `/ss-spec-review <ID>` then `/ss-implement <ID>`.
|
|
31
|
+
|
|
32
|
+
## Brief format (`specs/<slug>/brief.md`)
|
|
33
|
+
|
|
34
|
+
```markdown
|
|
35
|
+
---
|
|
36
|
+
slug: <slug>
|
|
37
|
+
spec: # set to the REQ-… id after /ss-spec-author writes the spec
|
|
38
|
+
created: <date>
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
# Brainstorm: <feature>
|
|
42
|
+
|
|
43
|
+
## Problem
|
|
44
|
+
<what we're solving and why>
|
|
45
|
+
|
|
46
|
+
## Code grounding
|
|
47
|
+
<relevant files / symbols / conventions found via specship_explore>
|
|
48
|
+
|
|
49
|
+
## Approaches considered
|
|
50
|
+
1. <A> — <trade-offs>
|
|
51
|
+
2. <B> — <trade-offs>
|
|
52
|
+
**Chosen: <X>** — <rationale>
|
|
53
|
+
|
|
54
|
+
## Key decisions
|
|
55
|
+
<the calls made during the loop>
|
|
56
|
+
|
|
57
|
+
## Edge cases & non-goals
|
|
58
|
+
<…>
|
|
59
|
+
|
|
60
|
+
## Acceptance criteria
|
|
61
|
+
<…>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Anti-patterns
|
|
65
|
+
- **Writing before confirmation.** The single most important rule — see above.
|
|
66
|
+
- **Re-interviewing about taste / proposing your own variants without grounding.** Ground first, then propose.
|
|
67
|
+
- **Duplicating spec-author.** You decide; spec-author formats. Don't write the formal `specs/<ID>.md` yourself.
|
|
68
|
+
- **Treating a question as confirmation.** Only an explicit yes writes files.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure classifiers and symbol-extraction helpers for SpecShip Token Impact.
|
|
3
|
+
*
|
|
4
|
+
* No I/O, no DB, no imports beyond types. Intended to be imported by the
|
|
5
|
+
* ingestor (Task 4) and the aggregation endpoint (later tasks).
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Returns true iff `name` is routed through the SpecShip MCP server
|
|
9
|
+
* (i.e. starts with `mcp__specship__`).
|
|
10
|
+
*/
|
|
11
|
+
export declare function isSpecshipTool(name: string): boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Returns true iff the tool returns code-graph source that can displace a
|
|
14
|
+
* native Read/Grep. See SOURCE_RETURNING_TOOLS for the authoritative set.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isSourceReturningTool(name: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Extracts the symbol names a tool call was asking about from its serialised
|
|
19
|
+
* input JSON. Returns `[]` when no symbols are resolvable.
|
|
20
|
+
*
|
|
21
|
+
* Heuristic for `specship_explore` and `specship_search` (which both accept a
|
|
22
|
+
* free-form `query` string):
|
|
23
|
+
* - Tokenise on whitespace.
|
|
24
|
+
* - If ALL tokens match SYMBOL_TOKEN_RE AND there are ≤ MAX_SYMBOL_BAG_TOKENS
|
|
25
|
+
* tokens, treat as a symbol bag and return the token list.
|
|
26
|
+
* - Otherwise (natural-language prose detected) return [].
|
|
27
|
+
*
|
|
28
|
+
* Input keys verified against src/mcp/tools.ts:
|
|
29
|
+
* - specship_node / callers / callees / impact → `symbol` (string, required)
|
|
30
|
+
* - specship_explore → `query` (string, required)
|
|
31
|
+
* - specship_search → `query` (string, required)
|
|
32
|
+
* - specship_files / others → [] (no named-symbol input)
|
|
33
|
+
*/
|
|
34
|
+
export declare function extractRequestedSymbols(toolName: string, inputJson: string | null | undefined): string[];
|
|
35
|
+
/**
|
|
36
|
+
* Minimal interface for the graph-backed read-equivalent estimator.
|
|
37
|
+
* Keeps the ingestor and tests decoupled from the full SpecShip class.
|
|
38
|
+
*/
|
|
39
|
+
export interface GraphLike {
|
|
40
|
+
estimateReadEquivalent(symbols: string[]): {
|
|
41
|
+
files: {
|
|
42
|
+
path: string;
|
|
43
|
+
size: number;
|
|
44
|
+
}[];
|
|
45
|
+
resolved: boolean;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Classify one tool call and compute the three ingest columns:
|
|
50
|
+
* - `isSpecship` — 1 if this is any `mcp__specship__*` call, else 0.
|
|
51
|
+
* - `resolution` — 'resolved' | 'unresolved' | 'n/a' | null.
|
|
52
|
+
* - `displacedFiles` — JSON string `[[path,size],…]` or null.
|
|
53
|
+
*
|
|
54
|
+
* Logic:
|
|
55
|
+
* 1. Not a specship tool → { isSpecship:0, resolution:null, displacedFiles:null }.
|
|
56
|
+
* 2. Specship + source-returning + result has content:
|
|
57
|
+
* a. No symbols extractable → unresolved / null.
|
|
58
|
+
* b. No graph available → unresolved / null.
|
|
59
|
+
* c. graph.estimateReadEquivalent returns resolved=true → resolved + files JSON.
|
|
60
|
+
* d. resolved=false → unresolved / null.
|
|
61
|
+
* 3. Specship but not a source-returning call, or zero-length result → n/a / null.
|
|
62
|
+
*/
|
|
63
|
+
export declare function classifyToolCall(call: {
|
|
64
|
+
toolName: string;
|
|
65
|
+
inputJson: string | null | undefined;
|
|
66
|
+
resultLength: number;
|
|
67
|
+
}, graph: GraphLike | null): {
|
|
68
|
+
isSpecship: 0 | 1;
|
|
69
|
+
resolution: 'resolved' | 'unresolved' | 'n/a' | null;
|
|
70
|
+
displacedFiles: string | null;
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=specship-impact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"specship-impact.d.ts","sourceRoot":"","sources":["../../src/analytics/specship-impact.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAsEH;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAI3D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GACnC,MAAM,EAAE,CA+BV;AAMD;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG;QACzC,KAAK,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,EAAE,CAAC;QACxC,QAAQ,EAAE,OAAO,CAAC;KACnB,CAAC;CACH;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,EACtF,KAAK,EAAE,SAAS,GAAG,IAAI,GACtB;IAAE,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC;IAAC,UAAU,EAAE,UAAU,GAAG,YAAY,GAAG,KAAK,GAAG,IAAI,CAAC;IAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CA0C5G"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pure classifiers and symbol-extraction helpers for SpecShip Token Impact.
|
|
4
|
+
*
|
|
5
|
+
* No I/O, no DB, no imports beyond types. Intended to be imported by the
|
|
6
|
+
* ingestor (Task 4) and the aggregation endpoint (later tasks).
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.isSpecshipTool = isSpecshipTool;
|
|
10
|
+
exports.isSourceReturningTool = isSourceReturningTool;
|
|
11
|
+
exports.extractRequestedSymbols = extractRequestedSymbols;
|
|
12
|
+
exports.classifyToolCall = classifyToolCall;
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Constants
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
/** Prefix shared by every MCP tool routed through the specship server. */
|
|
17
|
+
const MCP_SPECSHIP_PREFIX = 'mcp__specship__';
|
|
18
|
+
/**
|
|
19
|
+
* Strip-prefix base names of SpecShip tools that return code-graph source.
|
|
20
|
+
* These are the tools a SpecShip call can displace a native Read/Grep with.
|
|
21
|
+
* Excluded: designer_*, specship_link_assert, specship_link_verify,
|
|
22
|
+
* specship_spec, specship_drifted, specship_status — they don't return
|
|
23
|
+
* indexed source symbols.
|
|
24
|
+
*/
|
|
25
|
+
const SOURCE_RETURNING_TOOLS = new Set([
|
|
26
|
+
'specship_explore',
|
|
27
|
+
'specship_node',
|
|
28
|
+
'specship_callers',
|
|
29
|
+
'specship_callees',
|
|
30
|
+
'specship_impact',
|
|
31
|
+
'specship_search',
|
|
32
|
+
'specship_files',
|
|
33
|
+
]);
|
|
34
|
+
/**
|
|
35
|
+
* Tools whose input carries a `symbol` key (single identifier string).
|
|
36
|
+
* Verified against src/mcp/tools.ts inputSchema `required: ['symbol']`.
|
|
37
|
+
*/
|
|
38
|
+
const SYMBOL_KEY_TOOLS = new Set([
|
|
39
|
+
'specship_node',
|
|
40
|
+
'specship_callers',
|
|
41
|
+
'specship_callees',
|
|
42
|
+
'specship_impact',
|
|
43
|
+
]);
|
|
44
|
+
/**
|
|
45
|
+
* Regex for a single valid identifier token (with optional Class.method
|
|
46
|
+
* qualifier). A token matches if every character is a JS/TS identifier char
|
|
47
|
+
* or a single interior dot separating two identifier segments.
|
|
48
|
+
*
|
|
49
|
+
* Class.method tokens are kept whole — downstream resolution will try the
|
|
50
|
+
* qualified name, which biases an overloaded name to the named type's own
|
|
51
|
+
* definition (e.g. `DataRequest.task` → DataRequest's `task`, not the
|
|
52
|
+
* abstract base).
|
|
53
|
+
*/
|
|
54
|
+
const SYMBOL_TOKEN_RE = /^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)?$/;
|
|
55
|
+
/**
|
|
56
|
+
* Regex that a token must match to be considered "code-ish" rather than
|
|
57
|
+
* natural-language prose. Pure lowercase ASCII words (e.g. "find", "the",
|
|
58
|
+
* "all") look like prose; real symbol names almost always contain at least
|
|
59
|
+
* one of: uppercase letter, underscore, dollar sign, digit, or a dot
|
|
60
|
+
* (qualifying separator). A token matches this regex if it has any of those
|
|
61
|
+
* signals.
|
|
62
|
+
*
|
|
63
|
+
* Examples that pass: AuthService handleRequest _private $el renderV2 UserService.login
|
|
64
|
+
* Examples that fail: find all the auth handlers does updating
|
|
65
|
+
*/
|
|
66
|
+
const CODE_SIGNAL_RE = /[A-Z_$\d.]/;
|
|
67
|
+
/** Cap on how many symbol-shaped tokens we pull from one query (bounds the
|
|
68
|
+
* downstream graph lookups; queries naming more than this are rare). */
|
|
69
|
+
const MAX_SYMBOLS_PER_QUERY = 16;
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Public API
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
/**
|
|
74
|
+
* Returns true iff `name` is routed through the SpecShip MCP server
|
|
75
|
+
* (i.e. starts with `mcp__specship__`).
|
|
76
|
+
*/
|
|
77
|
+
function isSpecshipTool(name) {
|
|
78
|
+
return name.startsWith(MCP_SPECSHIP_PREFIX);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Returns true iff the tool returns code-graph source that can displace a
|
|
82
|
+
* native Read/Grep. See SOURCE_RETURNING_TOOLS for the authoritative set.
|
|
83
|
+
*/
|
|
84
|
+
function isSourceReturningTool(name) {
|
|
85
|
+
if (!name.startsWith(MCP_SPECSHIP_PREFIX))
|
|
86
|
+
return false;
|
|
87
|
+
const base = name.slice(MCP_SPECSHIP_PREFIX.length);
|
|
88
|
+
return SOURCE_RETURNING_TOOLS.has(base);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Extracts the symbol names a tool call was asking about from its serialised
|
|
92
|
+
* input JSON. Returns `[]` when no symbols are resolvable.
|
|
93
|
+
*
|
|
94
|
+
* Heuristic for `specship_explore` and `specship_search` (which both accept a
|
|
95
|
+
* free-form `query` string):
|
|
96
|
+
* - Tokenise on whitespace.
|
|
97
|
+
* - If ALL tokens match SYMBOL_TOKEN_RE AND there are ≤ MAX_SYMBOL_BAG_TOKENS
|
|
98
|
+
* tokens, treat as a symbol bag and return the token list.
|
|
99
|
+
* - Otherwise (natural-language prose detected) return [].
|
|
100
|
+
*
|
|
101
|
+
* Input keys verified against src/mcp/tools.ts:
|
|
102
|
+
* - specship_node / callers / callees / impact → `symbol` (string, required)
|
|
103
|
+
* - specship_explore → `query` (string, required)
|
|
104
|
+
* - specship_search → `query` (string, required)
|
|
105
|
+
* - specship_files / others → [] (no named-symbol input)
|
|
106
|
+
*/
|
|
107
|
+
function extractRequestedSymbols(toolName, inputJson) {
|
|
108
|
+
if (!inputJson)
|
|
109
|
+
return [];
|
|
110
|
+
let parsed;
|
|
111
|
+
try {
|
|
112
|
+
parsed = JSON.parse(inputJson);
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
return [];
|
|
116
|
+
}
|
|
117
|
+
if (parsed === null || typeof parsed !== 'object')
|
|
118
|
+
return [];
|
|
119
|
+
const args = parsed;
|
|
120
|
+
if (!toolName.startsWith(MCP_SPECSHIP_PREFIX))
|
|
121
|
+
return [];
|
|
122
|
+
const base = toolName.slice(MCP_SPECSHIP_PREFIX.length);
|
|
123
|
+
// --- symbol-keyed tools (specship_node, callers, callees, impact) ---------
|
|
124
|
+
if (SYMBOL_KEY_TOOLS.has(base)) {
|
|
125
|
+
const sym = args['symbol'];
|
|
126
|
+
return typeof sym === 'string' && sym.length > 0 ? [sym] : [];
|
|
127
|
+
}
|
|
128
|
+
// --- query-keyed tools (specship_explore, specship_search) ----------------
|
|
129
|
+
if (base === 'specship_explore' || base === 'specship_search') {
|
|
130
|
+
const q = args['query'];
|
|
131
|
+
if (typeof q !== 'string' || q.length === 0)
|
|
132
|
+
return [];
|
|
133
|
+
return symbolBagFromQuery(q);
|
|
134
|
+
}
|
|
135
|
+
// --- all other specship tools (files, status, spec, drifted, designer_*…) -
|
|
136
|
+
return [];
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Classify one tool call and compute the three ingest columns:
|
|
140
|
+
* - `isSpecship` — 1 if this is any `mcp__specship__*` call, else 0.
|
|
141
|
+
* - `resolution` — 'resolved' | 'unresolved' | 'n/a' | null.
|
|
142
|
+
* - `displacedFiles` — JSON string `[[path,size],…]` or null.
|
|
143
|
+
*
|
|
144
|
+
* Logic:
|
|
145
|
+
* 1. Not a specship tool → { isSpecship:0, resolution:null, displacedFiles:null }.
|
|
146
|
+
* 2. Specship + source-returning + result has content:
|
|
147
|
+
* a. No symbols extractable → unresolved / null.
|
|
148
|
+
* b. No graph available → unresolved / null.
|
|
149
|
+
* c. graph.estimateReadEquivalent returns resolved=true → resolved + files JSON.
|
|
150
|
+
* d. resolved=false → unresolved / null.
|
|
151
|
+
* 3. Specship but not a source-returning call, or zero-length result → n/a / null.
|
|
152
|
+
*/
|
|
153
|
+
function classifyToolCall(call, graph) {
|
|
154
|
+
const { toolName, inputJson, resultLength } = call;
|
|
155
|
+
if (!isSpecshipTool(toolName)) {
|
|
156
|
+
return { isSpecship: 0, resolution: null, displacedFiles: null };
|
|
157
|
+
}
|
|
158
|
+
// It's a specship tool. Check if it returns source AND returned something.
|
|
159
|
+
if (isSourceReturningTool(toolName) && resultLength > 0) {
|
|
160
|
+
const symbols = extractRequestedSymbols(toolName, inputJson);
|
|
161
|
+
if (symbols.length === 0) {
|
|
162
|
+
return { isSpecship: 1, resolution: 'unresolved', displacedFiles: null };
|
|
163
|
+
}
|
|
164
|
+
if (graph === null) {
|
|
165
|
+
return { isSpecship: 1, resolution: 'unresolved', displacedFiles: null };
|
|
166
|
+
}
|
|
167
|
+
// A locked/corrupt index can make estimateReadEquivalent throw. The estimate
|
|
168
|
+
// is best-effort decoration on the tool call — it must NEVER break core
|
|
169
|
+
// transcript ingest. Degrade to 'unresolved' on any failure.
|
|
170
|
+
let files;
|
|
171
|
+
let resolved;
|
|
172
|
+
try {
|
|
173
|
+
({ files, resolved } = graph.estimateReadEquivalent(symbols));
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return { isSpecship: 1, resolution: 'unresolved', displacedFiles: null };
|
|
177
|
+
}
|
|
178
|
+
if (resolved) {
|
|
179
|
+
return {
|
|
180
|
+
isSpecship: 1,
|
|
181
|
+
resolution: 'resolved',
|
|
182
|
+
displacedFiles: JSON.stringify(files.map((f) => [f.path, f.size])),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
return { isSpecship: 1, resolution: 'unresolved', displacedFiles: null };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Specship tool but not source-returning, or returned nothing (designer/spec/mutating tools).
|
|
190
|
+
return { isSpecship: 1, resolution: 'n/a', displacedFiles: null };
|
|
191
|
+
}
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// Internal helpers
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
/**
|
|
196
|
+
* Pull the symbol-shaped tokens out of a free-form `query`.
|
|
197
|
+
*
|
|
198
|
+
* Real `explore`/`search` queries are MIXED bags — symbol names interleaved
|
|
199
|
+
* with lowercase keywords ("live", "save", "install", "the") and often 10+
|
|
200
|
+
* tokens long. So we FILTER (not all-or-nothing): keep each token that matches
|
|
201
|
+
* SYMBOL_TOKEN_RE (a valid identifier or `Class.method`) AND CODE_SIGNAL_RE
|
|
202
|
+
* (has an uppercase letter, underscore, dollar, digit, or dot — i.e. looks like
|
|
203
|
+
* code, not a plain lowercase word). Drop the rest.
|
|
204
|
+
*
|
|
205
|
+
* A pure natural-language question ("how does the user log in") has no code-ish
|
|
206
|
+
* tokens, so it yields [] — still correctly treated as "no symbols". Capped at
|
|
207
|
+
* MAX_SYMBOLS_PER_QUERY to bound the downstream graph lookups.
|
|
208
|
+
*/
|
|
209
|
+
function symbolBagFromQuery(query) {
|
|
210
|
+
const symbols = query
|
|
211
|
+
.trim()
|
|
212
|
+
.split(/\s+/)
|
|
213
|
+
.filter((t) => SYMBOL_TOKEN_RE.test(t) && CODE_SIGNAL_RE.test(t));
|
|
214
|
+
return symbols.slice(0, MAX_SYMBOLS_PER_QUERY);
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=specship-impact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"specship-impact.js","sourceRoot":"","sources":["../../src/analytics/specship-impact.ts"],"names":[],"mappings":"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"}
|
package/dist/bin/specship.js
CHANGED
|
@@ -1676,8 +1676,8 @@ function main() {
|
|
|
1676
1676
|
program
|
|
1677
1677
|
.command('install')
|
|
1678
1678
|
.description('Install specship MCP server into Claude Code')
|
|
1679
|
-
.option('-l, --location <where>', 'Install location: "global" or "local". Default: prompt')
|
|
1680
|
-
.option('-y, --yes', 'Non-interactive: defaults to --location=
|
|
1679
|
+
.option('-l, --location <where>', 'Install location: "global" or "local". Default: prompt (local)')
|
|
1680
|
+
.option('-y, --yes', 'Non-interactive: defaults to --location=local, auto-allow on')
|
|
1681
1681
|
.option('--no-permissions', 'Skip writing the auto-allow permissions list')
|
|
1682
1682
|
.option('--no-sdd', 'Skip the spec-driven-development steering (CLAUDE.md rule + spec-author nudge hook)')
|
|
1683
1683
|
.option('--print-config', 'Print MCP config snippet for Claude Code and exit (no file writes)')
|
|
@@ -1787,8 +1787,8 @@ function main() {
|
|
|
1787
1787
|
program
|
|
1788
1788
|
.command('uninstall')
|
|
1789
1789
|
.description('Remove specship from Claude Code')
|
|
1790
|
-
.option('-l, --location <where>', 'Uninstall location: "global" or "local". Default: prompt')
|
|
1791
|
-
.option('-y, --yes', 'Non-interactive: defaults to --location=
|
|
1790
|
+
.option('-l, --location <where>', 'Uninstall location: "global" or "local". Default: prompt (local)')
|
|
1791
|
+
.option('-y, --yes', 'Non-interactive: defaults to --location=local')
|
|
1792
1792
|
// vestigial — kept so existing `--target claude` invocations keep working.
|
|
1793
1793
|
.option('-t, --target <ids>', '(vestigial) accepted: "claude" | "auto" | "all" | "none"')
|
|
1794
1794
|
.action(async (opts) => {
|