@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.
- package/README.md +11 -1
- package/commands/ss-brainstorm.md +68 -0
- package/commands/ss-design-implement.md +5 -0
- package/commands/ss-design-loop.md +125 -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 +70 -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/designer/artifact-store.js +54 -0
- package/dist/designer/browser.js +141 -0
- package/dist/designer/cdp-ensure.js +60 -0
- package/dist/designer/cdp-env.js +18 -0
- package/dist/designer/cdp-trace.js +599 -0
- package/dist/designer/cross-platform.js +74 -0
- package/dist/designer/designer-controller.js +1413 -0
- package/dist/designer/file-panel.js +39 -0
- package/dist/designer/interstitials.js +97 -0
- package/dist/designer/oopif-reader.js +176 -0
- package/dist/designer/package-meta.js +18 -0
- package/dist/designer/preview-host.js +50 -0
- package/dist/designer/repo-root.js +31 -0
- package/dist/designer/run-state.js +353 -0
- package/dist/designer/session-store.js +59 -0
- package/dist/designer/ui-anchors.js +651 -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 +7 -2
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +3 -2
- package/dist/installer/index.js.map +1 -1
- package/dist/installer/instructions-template.d.ts +17 -0
- package/dist/installer/instructions-template.d.ts.map +1 -1
- package/dist/installer/instructions-template.js +31 -1
- package/dist/installer/instructions-template.js.map +1 -1
- package/dist/installer/targets/claude.d.ts +19 -0
- package/dist/installer/targets/claude.d.ts.map +1 -1
- package/dist/installer/targets/claude.js +100 -1
- package/dist/installer/targets/claude.js.map +1 -1
- package/dist/installer/targets/shared.d.ts +14 -0
- package/dist/installer/targets/shared.d.ts.map +1 -1
- package/dist/installer/targets/shared.js +49 -0
- package/dist/installer/targets/shared.js.map +1 -1
- package/dist/installer/targets/types.d.ts +8 -0
- package/dist/installer/targets/types.d.ts.map +1 -1
- package/dist/mcp/designer-tools.d.ts +33 -0
- package/dist/mcp/designer-tools.d.ts.map +1 -0
- package/dist/mcp/designer-tools.js +313 -0
- package/dist/mcp/designer-tools.js.map +1 -0
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +22 -1
- package/dist/mcp/tools.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-JN6W7HCN.js → chunk-45QHGCB4.js} +1 -1
- package/dist/web/{chunk-RAAMPHPJ.js → chunk-A5R3MJMO.js} +1 -1
- package/dist/web/{chunk-2DHIGIOI.js → chunk-ASZ77FMZ.js} +1 -1
- package/dist/web/chunk-D5OCNEJA.js +2 -0
- package/dist/web/{chunk-3SEJX2BK.js → chunk-FHZHD2ZG.js} +1 -1
- package/dist/web/chunk-GR72OOCN.js +1 -0
- package/dist/web/{chunk-YAWCRPHV.js → chunk-NZEZCT65.js} +1 -1
- package/dist/web/chunk-O7434ZMN.js +1 -0
- package/dist/web/chunk-ODX6CT3I.js +6 -0
- package/dist/web/chunk-RASJHUXS.js +1 -0
- package/dist/web/chunk-TQ3P2QZO.js +1 -0
- package/dist/web/{chunk-BCZM5AXU.js → chunk-UBOZGQNK.js} +1 -1
- package/dist/web/chunk-WCHGDXWC.js +1 -0
- package/dist/web/{chunk-BPECIDVO.js → chunk-WCKHQIYN.js} +1 -1
- package/dist/web/{chunk-JFYVCXK3.js → chunk-WLIMNDS3.js} +1 -1
- package/dist/web/{chunk-LV4G6QFG.js → chunk-YAMRN47K.js} +1 -1
- package/dist/web/index.html +1 -1
- package/dist/web/main-X2KCYXZ4.js +1 -0
- package/dist/web/sw.js +69 -0
- package/dist/workflows/defaults/claude-design-implement.yaml +138 -49
- package/hooks/hooks.json +11 -0
- package/package.json +7 -3
- package/selectors.json +41 -0
- package/dist/web/chunk-2OKMB4KX.js +0 -2
- package/dist/web/chunk-4N5DWG46.js +0 -1
- package/dist/web/chunk-DA6SNNAF.js +0 -1
- package/dist/web/chunk-JT7P3DEK.js +0 -6
- package/dist/web/chunk-TWXZK6XM.js +0 -1
- 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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"}
|
package/dist/bin/specship.js
CHANGED
|
@@ -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=
|
|
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=
|
|
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,
|