@selvakumaresra/specship 0.3.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.
Files changed (97) hide show
  1. package/README.md +11 -1
  2. package/commands/ss-brainstorm.md +68 -0
  3. package/commands/ss-design-implement.md +5 -0
  4. package/commands/ss-design-loop.md +125 -0
  5. package/dist/analytics/specship-impact.d.ts +72 -0
  6. package/dist/analytics/specship-impact.d.ts.map +1 -0
  7. package/dist/analytics/specship-impact.js +216 -0
  8. package/dist/analytics/specship-impact.js.map +1 -0
  9. package/dist/bin/specship.js +70 -4
  10. package/dist/bin/specship.js.map +1 -1
  11. package/dist/db/migrations.d.ts +1 -1
  12. package/dist/db/migrations.d.ts.map +1 -1
  13. package/dist/db/migrations.js +15 -1
  14. package/dist/db/migrations.js.map +1 -1
  15. package/dist/db/schema.sql +8 -0
  16. package/dist/designer/artifact-store.js +54 -0
  17. package/dist/designer/browser.js +141 -0
  18. package/dist/designer/cdp-ensure.js +60 -0
  19. package/dist/designer/cdp-env.js +18 -0
  20. package/dist/designer/cdp-trace.js +599 -0
  21. package/dist/designer/cross-platform.js +74 -0
  22. package/dist/designer/designer-controller.js +1413 -0
  23. package/dist/designer/file-panel.js +39 -0
  24. package/dist/designer/interstitials.js +97 -0
  25. package/dist/designer/oopif-reader.js +176 -0
  26. package/dist/designer/package-meta.js +18 -0
  27. package/dist/designer/preview-host.js +50 -0
  28. package/dist/designer/repo-root.js +31 -0
  29. package/dist/designer/run-state.js +353 -0
  30. package/dist/designer/session-store.js +59 -0
  31. package/dist/designer/ui-anchors.js +651 -0
  32. package/dist/index.d.ts +27 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +48 -0
  35. package/dist/index.js.map +1 -1
  36. package/dist/installer/index.d.ts +7 -2
  37. package/dist/installer/index.d.ts.map +1 -1
  38. package/dist/installer/index.js +3 -2
  39. package/dist/installer/index.js.map +1 -1
  40. package/dist/installer/instructions-template.d.ts +17 -0
  41. package/dist/installer/instructions-template.d.ts.map +1 -1
  42. package/dist/installer/instructions-template.js +31 -1
  43. package/dist/installer/instructions-template.js.map +1 -1
  44. package/dist/installer/targets/claude.d.ts +19 -0
  45. package/dist/installer/targets/claude.d.ts.map +1 -1
  46. package/dist/installer/targets/claude.js +100 -1
  47. package/dist/installer/targets/claude.js.map +1 -1
  48. package/dist/installer/targets/shared.d.ts +14 -0
  49. package/dist/installer/targets/shared.d.ts.map +1 -1
  50. package/dist/installer/targets/shared.js +49 -0
  51. package/dist/installer/targets/shared.js.map +1 -1
  52. package/dist/installer/targets/types.d.ts +8 -0
  53. package/dist/installer/targets/types.d.ts.map +1 -1
  54. package/dist/mcp/designer-tools.d.ts +33 -0
  55. package/dist/mcp/designer-tools.d.ts.map +1 -0
  56. package/dist/mcp/designer-tools.js +313 -0
  57. package/dist/mcp/designer-tools.js.map +1 -0
  58. package/dist/mcp/tools.d.ts.map +1 -1
  59. package/dist/mcp/tools.js +22 -1
  60. package/dist/mcp/tools.js.map +1 -1
  61. package/dist/server/ingest/impact-backfill.js +69 -0
  62. package/dist/server/ingest/impact-query.js +343 -0
  63. package/dist/server/ingest/index.js +2 -1
  64. package/dist/server/ingest/ingestor.js +41 -6
  65. package/dist/server/ingest/specship-classify.js +153 -0
  66. package/dist/server/routes/claude.js +32 -0
  67. package/dist/server/routes/spec.js +94 -0
  68. package/dist/server/server.js +26 -2
  69. package/dist/web/{chunk-JN6W7HCN.js → chunk-45QHGCB4.js} +1 -1
  70. package/dist/web/{chunk-RAAMPHPJ.js → chunk-A5R3MJMO.js} +1 -1
  71. package/dist/web/{chunk-2DHIGIOI.js → chunk-ASZ77FMZ.js} +1 -1
  72. package/dist/web/chunk-D5OCNEJA.js +2 -0
  73. package/dist/web/{chunk-3SEJX2BK.js → chunk-FHZHD2ZG.js} +1 -1
  74. package/dist/web/chunk-GR72OOCN.js +1 -0
  75. package/dist/web/{chunk-YAWCRPHV.js → chunk-NZEZCT65.js} +1 -1
  76. package/dist/web/chunk-O7434ZMN.js +1 -0
  77. package/dist/web/chunk-ODX6CT3I.js +6 -0
  78. package/dist/web/chunk-RASJHUXS.js +1 -0
  79. package/dist/web/chunk-TQ3P2QZO.js +1 -0
  80. package/dist/web/{chunk-BCZM5AXU.js → chunk-UBOZGQNK.js} +1 -1
  81. package/dist/web/chunk-WCHGDXWC.js +1 -0
  82. package/dist/web/{chunk-BPECIDVO.js → chunk-WCKHQIYN.js} +1 -1
  83. package/dist/web/{chunk-JFYVCXK3.js → chunk-WLIMNDS3.js} +1 -1
  84. package/dist/web/{chunk-LV4G6QFG.js → chunk-YAMRN47K.js} +1 -1
  85. package/dist/web/index.html +1 -1
  86. package/dist/web/main-X2KCYXZ4.js +1 -0
  87. package/dist/web/sw.js +69 -0
  88. package/dist/workflows/defaults/claude-design-implement.yaml +138 -49
  89. package/hooks/hooks.json +11 -0
  90. package/package.json +7 -3
  91. package/selectors.json +41 -0
  92. package/dist/web/chunk-2OKMB4KX.js +0 -2
  93. package/dist/web/chunk-4N5DWG46.js +0 -1
  94. package/dist/web/chunk-DA6SNNAF.js +0 -1
  95. package/dist/web/chunk-JT7P3DEK.js +0 -6
  96. package/dist/web/chunk-TWXZK6XM.js +0 -1
  97. package/dist/web/main-WVI3YTDU.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.
@@ -6,6 +6,11 @@ allowed-tools: Read, Write, Edit, Bash, mcp__specship__specship_explore, mcp__sp
6
6
 
7
7
  # SpecShip Design → Spec → Implement: `$ARGUMENTS`
8
8
 
9
+ > **Already settled on a design?** This command imports it by URL. If you instead want to *run
10
+ > the taste loop first* — iterate variants with the human via the `designer` MCP and only then
11
+ > spec the chosen one — use **`/ss-design-loop`**, which drives the loop and hands the resulting
12
+ > bundle to this same workflow via its `HANDOFF_DIR` input.
13
+
9
14
  Run the bundled `claude-design-implement` workflow against the Claude Design URL in `$ARGUMENTS`. The workflow:
10
15
 
11
16
  1. **Snapshots** the design source byte-for-byte into `specs/<slug>/snapshot.html` (zero-loss fidelity layer).
@@ -0,0 +1,125 @@
1
+ ---
2
+ description: Run the full design→code loop — taste a claude.ai/design with the human via the designer MCP, then snapshot → spec → review → hand off to /ss-implement. Two human gates.
3
+ argument-hint: [intent — what you want to design]
4
+ allowed-tools: Read, Write, Edit, Bash, mcp__specship__designer_session, mcp__specship__designer_prompt, mcp__specship__designer_ask, mcp__specship__designer_list, mcp__specship__designer_snapshot, mcp__specship__designer_handoff, mcp__specship__specship_explore, mcp__specship__specship_search, mcp__specship__specship_node, mcp__specship__specship_spec, mcp__specship__specship_files
5
+ ---
6
+
7
+ # SpecShip Design Loop: `$ARGUMENTS`
8
+
9
+ One continuous pipeline: **intent → taste → design → handoff → spec → `/ss-implement`**.
10
+ You (the orchestrator) drive `claude.ai/design` through the `designer` MCP while the
11
+ **human tastes** the variants, then promote the chosen design into a SpecShip spec via the
12
+ bundled `claude-design-implement` workflow. Two human gates:
13
+
14
+ - **Gate 1 (aesthetic):** the human says *"that's it"* in the taste loop. Driven here, conversationally.
15
+ - **Gate 2 (contract):** the human walks the spec's `[needs review]` markers at the workflow's approval gate.
16
+
17
+ This is the deeper companion to `/ss-design-implement` — that command imports a design you've
18
+ *already* settled on (by URL); this one runs the taste loop first, then hands the resulting
19
+ bundle to the same workflow. `$ARGUMENTS` is the human's opening intent (optional).
20
+
21
+ ## Preflight
22
+
23
+ 1. **Designer runtime ready?** The designer tools are part of SpecShip's MCP now
24
+ (`mcp__specship__designer_*`), so they're always present. Probe the *runtime* with
25
+ `designer_session({ action: "status" })`: a clean status means you're ready. If it errors
26
+ with "CDP not up" or "Not signed in", the one-time Chrome setup hasn't run — tell the human to
27
+ run `designer setup` (creates the debug-Chrome profile + login) and stop. Do NOT fall back to a
28
+ blind fetch; the taste loop needs the live browser.
29
+ 2. **SpecShip initialized?** `specship status` should succeed. If not, `specship init -i` first.
30
+
31
+ ## Phase A — Taste loop (Gate 1)
32
+
33
+ Follow the **`designer-loop` skill** (`~/.claude/skills/designer-loop/SKILL.md`) — it is the
34
+ authority on this loop. The condensed version, if the skill isn't installed:
35
+
36
+ > The human is the designer; Claude Design has taste; you are translation + plumbing. Don't
37
+ > propose your own variants, don't interview about aesthetics — scope questions only.
38
+
39
+ 1. **Read the room — capabilities drive the design.** Before relaying any intent, survey the
40
+ target repo for what it actually *does* and feed that into the prompt verbatim. Use
41
+ `specship_explore` / `specship_search` to pull: entities + their fields, operations /
42
+ endpoints, states (loading / empty / error / success), failure modes, hard constraints
43
+ (auth, rate limits), and existing design tokens. The human's intent tells Claude *how*; the
44
+ codebase tells it *what*. Transfer capability facts unabridged — summarizing is filtering.
45
+ 2. **Create / resume the session.** `designer_session({ key: "<slug>", action: "create",
46
+ name: "<seed intent>", fidelity: "highfi" })`. Reuse a stable `key` derived from the intent
47
+ so parallel loops don't collide.
48
+ 3. **Relay a minimal, faithful prompt.** `designer_prompt({ key, prompt })` — intent +
49
+ capability facts; let Claude's taste make the aesthetic calls. Ask for the variant shape you
50
+ want ("3 full-page files", "states as toggles") and lock any hard brand tokens explicitly.
51
+ 4. **Hand the human the URL** returned in `url`. That live surface has working tweak sliders and
52
+ the variant switcher — it is the default taste path. Ask *"what do you think?"*, not
53
+ "accept or reject?".
54
+ 5. **Interpret + iterate.** Translate each reaction into the next `designer_prompt` (or a cheap
55
+ `designer_ask` to consult Claude on a small adjustment). Repeat 3–4 until the human says
56
+ **"that's it."** Capture their final reaction verbatim — it goes in the record.
57
+
58
+ Stay in the loop until Gate 1 is explicitly passed. "Almost" is not "yes."
59
+
60
+ ## Bridge — promote the chosen design
61
+
62
+ 1. `designer_handoff({ key, openFile: "<chosen variant>.html" })`. This fetches the project
63
+ export zip into `./artifacts/<key>/handoff-<ts>/` — `project/*` (all variants + assets) plus
64
+ `decision-record.md` (the verbatim transcript + the human's final reaction). Note the
65
+ **absolute path** of that `handoff-<ts>/` directory and the **chosen variant filename**.
66
+ 2. Derive `FILE_LABEL` (human label) and `SLUG` (kebab-case) from the chosen file / intent. If
67
+ the slug is ambiguous, ask the human — one scope question, not an interview.
68
+
69
+ ## Phase B — Spec pipeline (Gate 2)
70
+
71
+ Run the bundled workflow against the bundle on disk (no re-fetch, no CDP):
72
+
73
+ ```bash
74
+ specship workflow run claude-design-implement \
75
+ --input HANDOFF_DIR="<absolute path to handoff-<ts>/>" \
76
+ --input CHOSEN_FILE="<chosen variant>.html" \
77
+ --input FILE_LABEL="<File Label>" \
78
+ --input SLUG="<slug>" \
79
+ --json
80
+ ```
81
+
82
+ (Add `--input OWNER="<team>"` / `--input PRIORITY="high|medium|low"` to populate frontmatter;
83
+ otherwise they default to empty and surface as `[needs review]`.)
84
+
85
+ The workflow runs headless to its approval gate, then **pauses** (status `paused`). It:
86
+ snapshots `project/<chosen>.html` byte-for-byte into `specs/<slug>/snapshot.html`, folds
87
+ `decision-record.md` into `specs/<slug>/source.md`, extracts `specs/<slug>/tokens.css`, and
88
+ drafts the spec.
89
+
90
+ **At the pause (Gate 2):** read the run's approval message / the drafted spec artifact, present
91
+ it to the human, and walk the `[needs review]` markers + gap-fill questions together. Then:
92
+
93
+ - **Approve:** `specship workflow approve <runId> --comment "<gap-fill answers>"` then
94
+ `specship workflow resume <runId>`. The answers are captured into the spec.
95
+ - **Needs changes:** collect the feedback and re-run the workflow (a reject cancels the run;
96
+ existing REQ IDs are preserved across re-imports, so in-flight work survives).
97
+
98
+ ## Phase C — Hand off to implementation
99
+
100
+ When the workflow completes it prints the bridge message. Relay it to the human:
101
+ - the spec path `specs/<slug>.md` and its REQ IDs,
102
+ - the reference files (`snapshot.html`, `tokens.css`, `source.md`),
103
+ - the exact next step: **`/ss-implement <first REQ ID>`**.
104
+
105
+ Remind them: the implementer reads `snapshot.html` for visual fidelity — the spec is
106
+ contract-only.
107
+
108
+ ## Anti-patterns
109
+
110
+ - **Skipping the capability survey.** Designing before reading what the repo *does* produces
111
+ designs that look good and don't fit. Phase A step 1 is load-bearing.
112
+ - **Proposing your own variants / interviewing about taste.** Claude Design proposes; you relay.
113
+ Scope questions only.
114
+ - **Auto-promoting.** Don't `designer_handoff` on every iteration — only once Gate 1 passes.
115
+ - **Re-fetching in the workflow.** Always pass `HANDOFF_DIR` (the bundle you just fetched), never
116
+ `CONNECTOR_URL` — that re-drives Chrome from a headless subprocess and is fragile. The URL path
117
+ is what `/ss-design-implement` is for.
118
+ - **Putting hex/pixels in the spec.** Reference tokens by name; values live in `tokens.css`.
119
+ - **Skipping Gate 2.** The gap-fill questions are where the static design's blind spots (failure
120
+ modes, real-time updates, keyboard order) get closed.
121
+
122
+ If `designer_session` errors with a CDP / sign-in problem, stop and route the human to
123
+ `designer setup` (debug-Chrome profile + login) — this command cannot run the taste loop without
124
+ the live browser. To import a design you've already settled on by URL, use
125
+ `/ss-design-implement <url>` instead.
@@ -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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"}
@@ -1676,9 +1676,10 @@ 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=global, auto-allow on')
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
+ .option('--no-sdd', 'Skip the spec-driven-development steering (CLAUDE.md rule + spec-author nudge hook)')
1682
1683
  .option('--print-config', 'Print MCP config snippet for Claude Code and exit (no file writes)')
1683
1684
  // -t/--target is vestigial — kept so existing `--target claude` / `--target auto`
1684
1685
  // invocations (including our own offline-install scripts) keep working.
@@ -1708,10 +1709,14 @@ function main() {
1708
1709
  : opts.yes
1709
1710
  ? true
1710
1711
  : undefined;
1712
+ // Commander's `--no-sdd` makes `opts.sdd === false`; omitting it leaves
1713
+ // it `true`. Spec-driven steering is on by default — forward `false`
1714
+ // only when the user explicitly opted out.
1711
1715
  await runInstallerWithOptions({
1712
1716
  target: opts.target,
1713
1717
  location: opts.location,
1714
1718
  autoAllow,
1719
+ sdd: opts.sdd === false ? false : undefined,
1715
1720
  yes: opts.yes,
1716
1721
  });
1717
1722
  }
@@ -1720,6 +1725,57 @@ function main() {
1720
1725
  process.exit(1);
1721
1726
  }
1722
1727
  });
1728
+ /**
1729
+ * specship spec-nudge (internal — installed as a UserPromptSubmit hook)
1730
+ *
1731
+ * Reads the UserPromptSubmit JSON payload from stdin and, when the prompt
1732
+ * looks like feature/bug work, prints a non-blocking reminder (as
1733
+ * `hookSpecificOutput.additionalContext`) to author the spec via spec-author
1734
+ * first. Always exits 0 so the prompt proceeds. Conservative: skips clear
1735
+ * questions / read-only asks so it doesn't nag (SDD-INSTALL-DOC, REQ-SDD-002).
1736
+ */
1737
+ program
1738
+ .command('spec-nudge')
1739
+ .description('Internal hook: nudge toward spec-author on feature/bug intent (UserPromptSubmit)')
1740
+ .action(async () => {
1741
+ const shouldNudge = (prompt) => {
1742
+ const text = (prompt || '').trim();
1743
+ if (text.length < 12)
1744
+ return false;
1745
+ if (/[?]\s*$/.test(text))
1746
+ return false; // a question
1747
+ const lower = text.toLowerCase();
1748
+ // Read-only / interrogative openers — never spec-shaped.
1749
+ if (/^(what|why|how|where|who|when|is|are|does|do|can|could|should|explain|show|list|describe|tell|summar|read|find|search|look|run|why)\b/.test(lower))
1750
+ return false;
1751
+ // Feature / bug-shaped intent verbs.
1752
+ return /\b(add|implement|build|create|introduce|support|enhance|fix|bug|broken|refactor|rewrite|migrate|change|modify|update|wire|integrate|feature|spec out)\b/.test(lower);
1753
+ };
1754
+ const chunks = [];
1755
+ try {
1756
+ for await (const c of process.stdin)
1757
+ chunks.push(c);
1758
+ }
1759
+ catch { /* no stdin — nothing to nudge */ }
1760
+ const raw = Buffer.concat(chunks).toString('utf-8').trim();
1761
+ let prompt = '';
1762
+ if (raw) {
1763
+ try {
1764
+ const parsed = JSON.parse(raw);
1765
+ prompt = typeof parsed?.prompt === 'string' ? parsed.prompt : raw;
1766
+ }
1767
+ catch {
1768
+ prompt = raw; // not JSON — treat as the raw prompt text
1769
+ }
1770
+ }
1771
+ if (shouldNudge(prompt)) {
1772
+ const additionalContext = 'This repo uses spec-driven development (SpecShip). Before any brainstorming or ' +
1773
+ 'planning skill, FIRST invoke spec-author to author the spec under specs/ for this ' +
1774
+ 'work — the spec is the contract; implement from it with /ss-implement.';
1775
+ process.stdout.write(JSON.stringify({ hookSpecificOutput: { hookEventName: 'UserPromptSubmit', additionalContext } }) + '\n');
1776
+ }
1777
+ process.exit(0);
1778
+ });
1723
1779
  /**
1724
1780
  * specship uninstall
1725
1781
  *
@@ -1731,8 +1787,8 @@ function main() {
1731
1787
  program
1732
1788
  .command('uninstall')
1733
1789
  .description('Remove specship from Claude Code')
1734
- .option('-l, --location <where>', 'Uninstall location: "global" or "local". Default: prompt')
1735
- .option('-y, --yes', 'Non-interactive: defaults to --location=global')
1790
+ .option('-l, --location <where>', 'Uninstall location: "global" or "local". Default: prompt (local)')
1791
+ .option('-y, --yes', 'Non-interactive: defaults to --location=local')
1736
1792
  // vestigial — kept so existing `--target claude` invocations keep working.
1737
1793
  .option('-t, --target <ids>', '(vestigial) accepted: "claude" | "auto" | "all" | "none"')
1738
1794
  .action(async (opts) => {
@@ -1891,6 +1947,16 @@ function main() {
1891
1947
  process.exit(1);
1892
1948
  }
1893
1949
  }
1950
+ // Apply declared defaults for any optional input not passed. This
1951
+ // makes the schema's `default` field actually take effect and means
1952
+ // a `$INPUT.X` reference to a declared-but-omitted optional input
1953
+ // resolves to its default (or "") instead of throwing OutputRefError
1954
+ // mid-run. Required inputs are already enforced above.
1955
+ for (const inp of loaded.workflow.inputs ?? []) {
1956
+ if (!(inp.name in inputs)) {
1957
+ inputs[inp.name] = inp.default ?? '';
1958
+ }
1959
+ }
1894
1960
  const result = await executor.start(loaded.workflow, {
1895
1961
  projectRoot,
1896
1962
  inputs,