@specship/specship-win32-arm64 0.28.0 → 0.30.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/lib/dist/.claude-plugin/plugin.json +15 -3
- package/lib/dist/activation/cheatsheet.d.ts +10 -7
- package/lib/dist/activation/cheatsheet.d.ts.map +1 -1
- package/lib/dist/activation/cheatsheet.js +21 -5
- package/lib/dist/activation/cheatsheet.js.map +1 -1
- package/lib/dist/activation/hook-output.d.ts +75 -2
- package/lib/dist/activation/hook-output.d.ts.map +1 -1
- package/lib/dist/activation/hook-output.js +114 -3
- package/lib/dist/activation/hook-output.js.map +1 -1
- package/lib/dist/activation/search-intercept.d.ts +20 -7
- package/lib/dist/activation/search-intercept.d.ts.map +1 -1
- package/lib/dist/activation/search-intercept.js +27 -5
- package/lib/dist/activation/search-intercept.js.map +1 -1
- package/lib/dist/activation/steering.d.ts +7 -4
- package/lib/dist/activation/steering.d.ts.map +1 -1
- package/lib/dist/activation/steering.js +10 -8
- package/lib/dist/activation/steering.js.map +1 -1
- package/lib/dist/agents/detect.d.ts +32 -0
- package/lib/dist/agents/detect.d.ts.map +1 -0
- package/lib/dist/agents/detect.js +54 -0
- package/lib/dist/agents/detect.js.map +1 -0
- package/lib/dist/agents/recent-project.d.ts +18 -0
- package/lib/dist/agents/recent-project.d.ts.map +1 -0
- package/lib/dist/agents/recent-project.js +92 -0
- package/lib/dist/agents/recent-project.js.map +1 -0
- package/lib/dist/analytics/canonical-tools.d.ts +23 -0
- package/lib/dist/analytics/canonical-tools.d.ts.map +1 -0
- package/lib/dist/analytics/canonical-tools.js +36 -0
- package/lib/dist/analytics/canonical-tools.js.map +1 -0
- package/lib/dist/analytics/canonical-tools.json +16 -0
- package/lib/dist/analytics/specship-impact.d.ts +3 -2
- package/lib/dist/analytics/specship-impact.d.ts.map +1 -1
- package/lib/dist/analytics/specship-impact.js +6 -4
- package/lib/dist/analytics/specship-impact.js.map +1 -1
- package/lib/dist/bin/specship.js +252 -170
- package/lib/dist/bin/specship.js.map +1 -1
- package/lib/dist/bin/uninstall.js +2 -2
- package/lib/dist/bin/uninstall.js.map +1 -1
- package/lib/dist/claudemd/audit.d.ts +12 -3
- package/lib/dist/claudemd/audit.d.ts.map +1 -1
- package/lib/dist/claudemd/audit.js +65 -22
- package/lib/dist/claudemd/audit.js.map +1 -1
- package/lib/dist/commands/specship/spec.md +16 -1
- package/lib/dist/db/migrations.d.ts +1 -1
- package/lib/dist/db/migrations.d.ts.map +1 -1
- package/lib/dist/db/migrations.js +18 -4
- package/lib/dist/db/migrations.js.map +1 -1
- package/lib/dist/db/schema.sql +8 -0
- package/lib/dist/db/spec-queries.d.ts +13 -1
- package/lib/dist/db/spec-queries.d.ts.map +1 -1
- package/lib/dist/db/spec-queries.js +21 -0
- package/lib/dist/db/spec-queries.js.map +1 -1
- package/lib/dist/design-md/check.d.ts +51 -0
- package/lib/dist/design-md/check.d.ts.map +1 -0
- package/lib/dist/design-md/check.js +335 -0
- package/lib/dist/design-md/check.js.map +1 -0
- package/lib/dist/design-md/css.d.ts +40 -0
- package/lib/dist/design-md/css.d.ts.map +1 -0
- package/lib/dist/design-md/css.js +224 -0
- package/lib/dist/design-md/css.js.map +1 -0
- package/lib/dist/design-md/extract.d.ts +84 -0
- package/lib/dist/design-md/extract.d.ts.map +1 -0
- package/lib/dist/design-md/extract.js +595 -0
- package/lib/dist/design-md/extract.js.map +1 -0
- package/lib/dist/design-md/maintain.d.ts +45 -0
- package/lib/dist/design-md/maintain.d.ts.map +1 -0
- package/lib/dist/design-md/maintain.js +127 -0
- package/lib/dist/design-md/maintain.js.map +1 -0
- package/lib/dist/design-md/render.d.ts +28 -0
- package/lib/dist/design-md/render.d.ts.map +1 -0
- package/lib/dist/design-md/render.js +178 -0
- package/lib/dist/design-md/render.js.map +1 -0
- package/lib/dist/design-md/upkeep.d.ts +48 -0
- package/lib/dist/design-md/upkeep.d.ts.map +1 -0
- package/lib/dist/design-md/upkeep.js +169 -0
- package/lib/dist/design-md/upkeep.js.map +1 -0
- package/lib/dist/design-md/verify.d.ts +65 -0
- package/lib/dist/design-md/verify.d.ts.map +1 -0
- package/lib/dist/design-md/verify.js +311 -0
- package/lib/dist/design-md/verify.js.map +1 -0
- package/lib/dist/enforce/changed-files.d.ts +7 -0
- package/lib/dist/enforce/changed-files.d.ts.map +1 -1
- package/lib/dist/enforce/changed-files.js +17 -0
- package/lib/dist/enforce/changed-files.js.map +1 -1
- package/lib/dist/enforce/enforce.d.ts +35 -1
- package/lib/dist/enforce/enforce.d.ts.map +1 -1
- package/lib/dist/enforce/enforce.js +66 -2
- package/lib/dist/enforce/enforce.js.map +1 -1
- package/lib/dist/extraction/index.d.ts +50 -4
- package/lib/dist/extraction/index.d.ts.map +1 -1
- package/lib/dist/extraction/index.js +105 -13
- package/lib/dist/extraction/index.js.map +1 -1
- package/lib/dist/extraction/parse-warnings.d.ts +16 -0
- package/lib/dist/extraction/parse-warnings.d.ts.map +1 -0
- package/lib/dist/extraction/parse-warnings.js +30 -0
- package/lib/dist/extraction/parse-warnings.js.map +1 -0
- package/lib/dist/extraction/tree-sitter.d.ts +27 -0
- package/lib/dist/extraction/tree-sitter.d.ts.map +1 -1
- package/lib/dist/extraction/tree-sitter.js +168 -1
- package/lib/dist/extraction/tree-sitter.js.map +1 -1
- package/lib/dist/health/smoke-check.js +1 -1
- package/lib/dist/health/smoke-check.js.map +1 -1
- package/lib/dist/hooks/hooks.json +16 -1
- package/lib/dist/index.d.ts +63 -3
- package/lib/dist/index.d.ts.map +1 -1
- package/lib/dist/index.js +235 -9
- package/lib/dist/index.js.map +1 -1
- package/lib/dist/installer/agent-record.d.ts +80 -0
- package/lib/dist/installer/agent-record.d.ts.map +1 -0
- package/lib/dist/installer/agent-record.js +351 -0
- package/lib/dist/installer/agent-record.js.map +1 -0
- package/lib/dist/installer/auto-migrate.d.ts +56 -0
- package/lib/dist/installer/auto-migrate.d.ts.map +1 -0
- package/lib/dist/installer/auto-migrate.js +328 -0
- package/lib/dist/installer/auto-migrate.js.map +1 -0
- package/lib/dist/installer/index.d.ts +37 -8
- package/lib/dist/installer/index.d.ts.map +1 -1
- package/lib/dist/installer/index.js +156 -46
- package/lib/dist/installer/index.js.map +1 -1
- package/lib/dist/installer/install-defaults.d.ts +4 -3
- package/lib/dist/installer/install-defaults.d.ts.map +1 -1
- package/lib/dist/installer/install-defaults.js +6 -5
- package/lib/dist/installer/install-defaults.js.map +1 -1
- package/lib/dist/installer/{config-writer.d.ts → targets/claude-config-writer.d.ts} +1 -1
- package/lib/dist/installer/targets/claude-config-writer.d.ts.map +1 -0
- package/lib/dist/installer/{config-writer.js → targets/claude-config-writer.js} +7 -6
- package/lib/dist/installer/targets/claude-config-writer.js.map +1 -0
- package/lib/dist/installer/targets/claude-projects.d.ts +9 -0
- package/lib/dist/installer/targets/claude-projects.d.ts.map +1 -0
- package/lib/dist/installer/targets/claude-projects.js +151 -0
- package/lib/dist/installer/targets/claude-projects.js.map +1 -0
- package/lib/dist/installer/targets/claude.d.ts +29 -2
- package/lib/dist/installer/targets/claude.d.ts.map +1 -1
- package/lib/dist/installer/targets/claude.js +44 -20
- package/lib/dist/installer/targets/claude.js.map +1 -1
- package/lib/dist/installer/targets/feature-parity.d.ts +28 -0
- package/lib/dist/installer/targets/feature-parity.d.ts.map +1 -0
- package/lib/dist/installer/targets/feature-parity.js +32 -0
- package/lib/dist/installer/targets/feature-parity.js.map +1 -0
- package/lib/dist/installer/{gemini-commands.d.ts → targets/gemini-commands.d.ts} +7 -3
- package/lib/dist/installer/targets/gemini-commands.d.ts.map +1 -0
- package/lib/dist/installer/{gemini-commands.js → targets/gemini-commands.js} +37 -9
- package/lib/dist/installer/targets/gemini-commands.js.map +1 -0
- package/lib/dist/installer/targets/gemini-projects.d.ts +11 -0
- package/lib/dist/installer/targets/gemini-projects.d.ts.map +1 -0
- package/lib/dist/installer/targets/gemini-projects.js +84 -0
- package/lib/dist/installer/targets/gemini-projects.js.map +1 -0
- package/lib/dist/installer/targets/gemini.d.ts +94 -8
- package/lib/dist/installer/targets/gemini.d.ts.map +1 -1
- package/lib/dist/installer/targets/gemini.js +392 -33
- package/lib/dist/installer/targets/gemini.js.map +1 -1
- package/lib/dist/installer/targets/registry.d.ts +8 -1
- package/lib/dist/installer/targets/registry.d.ts.map +1 -1
- package/lib/dist/installer/targets/registry.js +14 -5
- package/lib/dist/installer/targets/registry.js.map +1 -1
- package/lib/dist/installer/targets/shared.d.ts +44 -1
- package/lib/dist/installer/targets/shared.d.ts.map +1 -1
- package/lib/dist/installer/targets/shared.js +130 -3
- package/lib/dist/installer/targets/shared.js.map +1 -1
- package/lib/dist/installer/targets/types.d.ts +23 -1
- package/lib/dist/installer/targets/types.d.ts.map +1 -1
- package/lib/dist/jira/auto-publish.d.ts +22 -2
- package/lib/dist/jira/auto-publish.d.ts.map +1 -1
- package/lib/dist/jira/auto-publish.js +54 -9
- package/lib/dist/jira/auto-publish.js.map +1 -1
- package/lib/dist/jira/board-first.d.ts +15 -2
- package/lib/dist/jira/board-first.d.ts.map +1 -1
- package/lib/dist/jira/board-first.js +16 -0
- package/lib/dist/jira/board-first.js.map +1 -1
- package/lib/dist/jira/ci-publish.d.ts +72 -0
- package/lib/dist/jira/ci-publish.d.ts.map +1 -0
- package/lib/dist/jira/ci-publish.js +162 -0
- package/lib/dist/jira/ci-publish.js.map +1 -0
- package/lib/dist/jira/publish.d.ts +23 -0
- package/lib/dist/jira/publish.d.ts.map +1 -1
- package/lib/dist/jira/publish.js +0 -0
- package/lib/dist/jira/publish.js.map +1 -1
- package/lib/dist/jira/regression-pack.d.ts.map +1 -1
- package/lib/dist/jira/regression-pack.js +7 -1
- package/lib/dist/jira/regression-pack.js.map +1 -1
- package/lib/dist/jira/repo-config.d.ts.map +1 -1
- package/lib/dist/jira/repo-config.js +8 -0
- package/lib/dist/jira/repo-config.js.map +1 -1
- package/lib/dist/jira/types.d.ts +8 -0
- package/lib/dist/jira/types.d.ts.map +1 -1
- package/lib/dist/jira/types.js.map +1 -1
- package/lib/dist/knowledge/enforce.d.ts.map +1 -1
- package/lib/dist/knowledge/enforce.js +6 -1
- package/lib/dist/knowledge/enforce.js.map +1 -1
- package/lib/dist/mcp/jira-tools.d.ts +5 -0
- package/lib/dist/mcp/jira-tools.d.ts.map +1 -1
- package/lib/dist/mcp/jira-tools.js +59 -4
- package/lib/dist/mcp/jira-tools.js.map +1 -1
- package/lib/dist/mcp/proxy.d.ts.map +1 -1
- package/lib/dist/mcp/proxy.js +2 -1
- package/lib/dist/mcp/proxy.js.map +1 -1
- package/lib/dist/mcp/server-instructions.d.ts +16 -10
- package/lib/dist/mcp/server-instructions.d.ts.map +1 -1
- package/lib/dist/mcp/server-instructions.js +69 -20
- package/lib/dist/mcp/server-instructions.js.map +1 -1
- package/lib/dist/mcp/session.d.ts +11 -0
- package/lib/dist/mcp/session.d.ts.map +1 -1
- package/lib/dist/mcp/session.js +43 -1
- package/lib/dist/mcp/session.js.map +1 -1
- package/lib/dist/mcp/tools.d.ts.map +1 -1
- package/lib/dist/mcp/tools.js +8 -0
- package/lib/dist/mcp/tools.js.map +1 -1
- package/lib/dist/reflect/apply.d.ts +2 -2
- package/lib/dist/reflect/apply.d.ts.map +1 -1
- package/lib/dist/reflect/apply.js +6 -5
- package/lib/dist/reflect/apply.js.map +1 -1
- package/lib/dist/reflect/miner.d.ts.map +1 -1
- package/lib/dist/reflect/miner.js +28 -15
- package/lib/dist/reflect/miner.js.map +1 -1
- package/lib/dist/reflect/session-outcomes.d.ts.map +1 -1
- package/lib/dist/reflect/session-outcomes.js +8 -3
- package/lib/dist/reflect/session-outcomes.js.map +1 -1
- package/lib/dist/reflect/targets.d.ts.map +1 -1
- package/lib/dist/reflect/targets.js +25 -5
- package/lib/dist/reflect/targets.js.map +1 -1
- package/lib/dist/reflect/types.d.ts +7 -0
- package/lib/dist/reflect/types.d.ts.map +1 -1
- package/lib/dist/resolution/spec-link-resolver.d.ts +14 -2
- package/lib/dist/resolution/spec-link-resolver.d.ts.map +1 -1
- package/lib/dist/resolution/spec-link-resolver.js +13 -3
- package/lib/dist/resolution/spec-link-resolver.js.map +1 -1
- package/lib/dist/server/canonical-tools.js +50 -0
- package/lib/dist/server/cli.js +2 -2
- package/lib/dist/server/host-profiles.js +39 -0
- package/lib/dist/server/ingest/index.js +2 -1
- package/lib/dist/server/ingest/ingestor.js +195 -76
- package/lib/dist/server/ingest/project-paths.js +8 -2
- package/lib/dist/server/ingest/recorded-projects.js +43 -0
- package/lib/dist/server/ingest/specship-classify.js +7 -2
- package/lib/dist/server/ingest/transcripts/claude-jsonl.js +54 -0
- package/lib/dist/server/ingest/transcripts/claude.js +64 -0
- package/lib/dist/server/ingest/transcripts/gemini.js +469 -0
- package/lib/dist/server/ingest/transcripts/index.js +14 -0
- package/lib/dist/server/ingest/{parser.js → transcripts/shared.js} +3 -49
- package/lib/dist/server/ingest/transcripts/types.js +4 -0
- package/lib/dist/server/ingest/watcher.js +31 -30
- package/lib/dist/server/routes/claude.js +27 -10
- package/lib/dist/server/routes/graph.js +64 -3
- package/lib/dist/server/routes/mcp.js +57 -15
- package/lib/dist/server/routes/memory.js +54 -35
- package/lib/dist/server/routes/projects.js +78 -7
- package/lib/dist/server/routes/spec.js +125 -0
- package/lib/dist/server/server.js +8 -2
- package/lib/dist/skills/design-md/SKILL.md +172 -0
- package/lib/dist/skills/design-md/references/authored-sections.md +90 -0
- package/lib/dist/skills/design-md/references/sources.md +71 -0
- package/lib/dist/skills/spec-author/SKILL.md +1 -1
- package/lib/dist/skills/spec-author/references/gap-questions.md +1 -1
- package/lib/dist/spec-lint/lint.d.ts +26 -1
- package/lib/dist/spec-lint/lint.d.ts.map +1 -1
- package/lib/dist/spec-lint/lint.js +96 -7
- package/lib/dist/spec-lint/lint.js.map +1 -1
- package/lib/dist/spec-quality/postcheck.d.ts +7 -1
- package/lib/dist/spec-quality/postcheck.d.ts.map +1 -1
- package/lib/dist/spec-quality/postcheck.js +31 -0
- package/lib/dist/spec-quality/postcheck.js.map +1 -1
- package/lib/dist/sync/worktree.d.ts +48 -0
- package/lib/dist/sync/worktree.d.ts.map +1 -1
- package/lib/dist/sync/worktree.js +109 -0
- package/lib/dist/sync/worktree.js.map +1 -1
- package/lib/dist/ui/assets/bricolage-latin-C23fTOVi.woff2 +0 -0
- package/lib/dist/ui/assets/bricolage-latin-ext-B82pxkPf.woff2 +0 -0
- package/lib/dist/ui/assets/index-54vorjyq.css +1 -0
- package/lib/dist/ui/assets/index-BZQonCVb.js +52 -0
- package/lib/dist/ui/assets/space-grotesk-latin-DPT1xrvW.woff2 +0 -0
- package/lib/dist/ui/assets/space-grotesk-latin-ext-wJGaI8U0.woff2 +0 -0
- package/lib/dist/ui/index.html +2 -2
- package/lib/dist/update/updater.d.ts +6 -0
- package/lib/dist/update/updater.d.ts.map +1 -1
- package/lib/dist/update/updater.js +13 -1
- package/lib/dist/update/updater.js.map +1 -1
- package/lib/dist/workflows/agents/claude.d.ts +9 -0
- package/lib/dist/workflows/agents/claude.d.ts.map +1 -1
- package/lib/dist/workflows/agents/claude.js +22 -3
- package/lib/dist/workflows/agents/claude.js.map +1 -1
- package/lib/dist/workflows/agents/gemini-version.d.ts +18 -0
- package/lib/dist/workflows/agents/gemini-version.d.ts.map +1 -0
- package/lib/dist/workflows/agents/gemini-version.js +81 -0
- package/lib/dist/workflows/agents/gemini-version.js.map +1 -0
- package/lib/dist/workflows/agents/gemini.d.ts +1 -1
- package/lib/dist/workflows/agents/gemini.d.ts.map +1 -1
- package/lib/dist/workflows/agents/gemini.js +12 -2
- package/lib/dist/workflows/agents/gemini.js.map +1 -1
- package/lib/dist/workflows/agents/index.d.ts +2 -0
- package/lib/dist/workflows/agents/index.d.ts.map +1 -1
- package/lib/dist/workflows/agents/index.js +5 -0
- package/lib/dist/workflows/agents/index.js.map +1 -1
- package/lib/dist/workflows/agents/types.d.ts +13 -0
- package/lib/dist/workflows/agents/types.d.ts.map +1 -1
- package/lib/dist/workflows/agents/types.js.map +1 -1
- package/lib/dist/workflows/condition-evaluator.d.ts +8 -0
- package/lib/dist/workflows/condition-evaluator.d.ts.map +1 -1
- package/lib/dist/workflows/condition-evaluator.js +7 -0
- package/lib/dist/workflows/condition-evaluator.js.map +1 -1
- package/lib/dist/workflows/defaults/claude-design-implement.yaml +44 -15
- package/lib/dist/workflows/defaults/spec-author.yaml +9 -9
- package/lib/dist/workflows/defaults/spec-fix.yaml +8 -8
- package/lib/dist/workflows/defaults/spec-implement.yaml +54 -23
- package/lib/dist/workflows/defaults/spec-relink.yaml +5 -5
- package/lib/dist/workflows/defaults/spec-verify.yaml +3 -3
- package/lib/dist/workflows/executor.d.ts +7 -0
- package/lib/dist/workflows/executor.d.ts.map +1 -1
- package/lib/dist/workflows/executor.js +22 -5
- package/lib/dist/workflows/executor.js.map +1 -1
- package/lib/node_modules/.package-lock.json +1 -1
- package/lib/package.json +3 -2
- package/package.json +1 -1
- package/lib/dist/installer/config-writer.d.ts.map +0 -1
- package/lib/dist/installer/config-writer.js.map +0 -1
- package/lib/dist/installer/gemini-commands.d.ts.map +0 -1
- package/lib/dist/installer/gemini-commands.js.map +0 -1
- package/lib/dist/ui/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/lib/dist/ui/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/lib/dist/ui/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/lib/dist/ui/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/lib/dist/ui/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/lib/dist/ui/assets/index-BzuUWUAE.js +0 -50
- package/lib/dist/ui/assets/index-ezwX2EfG.css +0 -1
|
@@ -16,10 +16,85 @@ import { writeSseHead } from './sse.js';
|
|
|
16
16
|
import { promises as fs, watch } from 'node:fs';
|
|
17
17
|
import os from 'node:os';
|
|
18
18
|
import path from 'node:path';
|
|
19
|
-
import { createSlugResolver } from '../ingest/project-paths.js';
|
|
19
|
+
import { createSlugResolver, encodeProjectSlug } from '../ingest/project-paths.js';
|
|
20
|
+
import { recordedProjects } from '../ingest/recorded-projects.js';
|
|
21
|
+
import { transcriptAdapters } from '../ingest/transcripts/index.js';
|
|
22
|
+
import { CLAUDE_PROFILE } from '../host-profiles.js';
|
|
20
23
|
const projectsBus = new EventEmitter();
|
|
21
24
|
projectsBus.setMaxListeners(0); // many SSE subscribers + internal
|
|
22
25
|
export async function enumerate(claudeRoot, resolverOpts) {
|
|
26
|
+
const fromTranscripts = await enumerateTranscriptProjects(claudeRoot, resolverOpts);
|
|
27
|
+
// Projects any agent has SpecShip installed in (REQ-AGENTSWITCH-011) — the
|
|
28
|
+
// only source for an agent whose session folders cannot be mapped back to a
|
|
29
|
+
// path. Listed once: a project already found above keeps that entry.
|
|
30
|
+
const seen = new Set(fromTranscripts.map((p) => p.path));
|
|
31
|
+
// Agents that keep their own project list (REQ-AGENTSWITCH-019.A1).
|
|
32
|
+
const fromAgents = [];
|
|
33
|
+
const home = resolverOpts?.home ?? os.homedir();
|
|
34
|
+
for (const adapter of transcriptAdapters()) {
|
|
35
|
+
let found = [];
|
|
36
|
+
try {
|
|
37
|
+
found = adapter.listProjects?.(home) ?? [];
|
|
38
|
+
}
|
|
39
|
+
catch { /* one agent's files never block another's */ }
|
|
40
|
+
for (const p of found) {
|
|
41
|
+
if (seen.has(p.path))
|
|
42
|
+
continue;
|
|
43
|
+
seen.add(p.path);
|
|
44
|
+
fromAgents.push({ ...(await projectState(p.path)), slug: encodeProjectSlug(p.path), path: p.path, sessionCount: p.sessionCount, lastModifiedMs: p.lastModifiedMs, driftCount: null });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const fromRecord = [];
|
|
48
|
+
for (const rec of recordedProjects(resolverOpts?.recordPath)) {
|
|
49
|
+
if (seen.has(rec.path))
|
|
50
|
+
continue;
|
|
51
|
+
seen.add(rec.path);
|
|
52
|
+
let exists = false;
|
|
53
|
+
let initialized = false;
|
|
54
|
+
try {
|
|
55
|
+
exists = (await fs.stat(rec.path)).isDirectory();
|
|
56
|
+
}
|
|
57
|
+
catch { /* gone */ }
|
|
58
|
+
if (exists) {
|
|
59
|
+
try {
|
|
60
|
+
initialized = (await fs.stat(path.join(rec.path, '.specship'))).isDirectory();
|
|
61
|
+
}
|
|
62
|
+
catch { /* not initialized */ }
|
|
63
|
+
}
|
|
64
|
+
fromRecord.push({
|
|
65
|
+
slug: encodeProjectSlug(rec.path),
|
|
66
|
+
path: rec.path,
|
|
67
|
+
exists,
|
|
68
|
+
initialized,
|
|
69
|
+
sessionCount: 0,
|
|
70
|
+
lastModifiedMs: rec.recordedAtMs,
|
|
71
|
+
driftCount: null,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return [...fromTranscripts, ...fromAgents, ...fromRecord].sort(byRecency);
|
|
75
|
+
}
|
|
76
|
+
async function projectState(p) {
|
|
77
|
+
let exists = false;
|
|
78
|
+
let initialized = false;
|
|
79
|
+
try {
|
|
80
|
+
exists = (await fs.stat(p)).isDirectory();
|
|
81
|
+
}
|
|
82
|
+
catch { /* gone */ }
|
|
83
|
+
if (exists) {
|
|
84
|
+
try {
|
|
85
|
+
initialized = (await fs.stat(path.join(p, '.specship'))).isDirectory();
|
|
86
|
+
}
|
|
87
|
+
catch { /* not initialized */ }
|
|
88
|
+
}
|
|
89
|
+
return { exists, initialized };
|
|
90
|
+
}
|
|
91
|
+
/** Recent activity first; ties broken by path. */
|
|
92
|
+
function byRecency(a, b) {
|
|
93
|
+
if (b.lastModifiedMs !== a.lastModifiedMs)
|
|
94
|
+
return b.lastModifiedMs - a.lastModifiedMs;
|
|
95
|
+
return a.path.localeCompare(b.path);
|
|
96
|
+
}
|
|
97
|
+
async function enumerateTranscriptProjects(claudeRoot, resolverOpts) {
|
|
23
98
|
let entries;
|
|
24
99
|
try {
|
|
25
100
|
entries = await fs.readdir(claudeRoot, { withFileTypes: true });
|
|
@@ -71,11 +146,7 @@ export async function enumerate(claudeRoot, resolverOpts) {
|
|
|
71
146
|
out.push({ slug, path: decoded, exists, initialized, sessionCount, lastModifiedMs, driftCount: null });
|
|
72
147
|
}
|
|
73
148
|
// Recent activity first; ties broken by path.
|
|
74
|
-
out.sort(
|
|
75
|
-
if (b.lastModifiedMs !== a.lastModifiedMs)
|
|
76
|
-
return b.lastModifiedMs - a.lastModifiedMs;
|
|
77
|
-
return a.path.localeCompare(b.path);
|
|
78
|
-
});
|
|
149
|
+
out.sort(byRecency);
|
|
79
150
|
return out;
|
|
80
151
|
}
|
|
81
152
|
/**
|
|
@@ -167,7 +238,7 @@ function stopWatcher() {
|
|
|
167
238
|
}
|
|
168
239
|
// --- Route registration -----------------------------------------------------
|
|
169
240
|
export async function registerProjectsRoutes(app) {
|
|
170
|
-
const claudeRoot = path.join(os.homedir(),
|
|
241
|
+
const claudeRoot = path.join(os.homedir(), CLAUDE_PROFILE.configDir, 'projects');
|
|
171
242
|
startWatcher(claudeRoot, false);
|
|
172
243
|
app.addHook('onClose', async () => { stopWatcher(); });
|
|
173
244
|
const driftSource = (slug) => app.projects.getBySlug(slug);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* boot-time primary). Returns 409 / `no_project` when neither is selectable.
|
|
8
8
|
*/
|
|
9
9
|
import * as fs from 'node:fs';
|
|
10
|
+
import * as os from 'node:os';
|
|
10
11
|
import * as path from 'node:path';
|
|
11
12
|
/**
|
|
12
13
|
* Resolve a project-relative path to an absolute path under the project
|
|
@@ -85,6 +86,119 @@ function atomicWriteFile(targetPath, content) {
|
|
|
85
86
|
fs.writeFileSync(tmp, content, 'utf-8');
|
|
86
87
|
fs.renameSync(tmp, targetPath);
|
|
87
88
|
}
|
|
89
|
+
const PLAN_DRIFT_STATES = ['drifted', 'broken', 'orphaned'];
|
|
90
|
+
const metaString = (meta, key) => {
|
|
91
|
+
const v = meta?.[key];
|
|
92
|
+
return typeof v === 'string' && v.trim() ? v.trim() : null;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* The JIRA base URL for issue links — and ONLY that. Reads
|
|
96
|
+
* `SPECSHIP_JIRA_BASE_URL`, else the `baseUrl` field of the user-level JIRA
|
|
97
|
+
* config (`SPECSHIP_JIRA_CONFIG` or `~/.specship/jira.json`). That file also
|
|
98
|
+
* holds credentials; nothing but `baseUrl` is ever read out of the parsed
|
|
99
|
+
* object, returned or logged. No JIRA request is made (REQ-DASHSIMPLE-003.A4).
|
|
100
|
+
*/
|
|
101
|
+
export function readJiraBaseUrl() {
|
|
102
|
+
const fromEnv = process.env.SPECSHIP_JIRA_BASE_URL;
|
|
103
|
+
let base = fromEnv && fromEnv.trim() ? fromEnv.trim() : null;
|
|
104
|
+
if (!base) {
|
|
105
|
+
const file = process.env.SPECSHIP_JIRA_CONFIG ?? path.join(os.homedir(), '.specship', 'jira.json');
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
108
|
+
const v = parsed && typeof parsed === 'object' ? parsed.baseUrl : undefined;
|
|
109
|
+
if (typeof v === 'string' && v.trim())
|
|
110
|
+
base = v.trim();
|
|
111
|
+
}
|
|
112
|
+
catch { /* absent or malformed → no links, plain-text keys */ }
|
|
113
|
+
}
|
|
114
|
+
if (!base || !/^https?:\/\//i.test(base))
|
|
115
|
+
return null;
|
|
116
|
+
return base.replace(/\/+$/, '');
|
|
117
|
+
}
|
|
118
|
+
const byFileOrder = (a, b) => a.sourcePath < b.sourcePath ? -1 : a.sourcePath > b.sourcePath ? 1 : (a.startLine ?? 0) - (b.startLine ?? 0);
|
|
119
|
+
/**
|
|
120
|
+
* Epic → story → task from the indexed specs alone (REQ-DASHSIMPLE-003).
|
|
121
|
+
* `epicKey` and `jira_issue_<REQ>` come from each document's stored metadata
|
|
122
|
+
* (its frontmatter, as indexed) — the spec files are not re-parsed here.
|
|
123
|
+
* Documents sharing an `epicKey` merge into one epic in file order; a document
|
|
124
|
+
* without one is its own group titled after the document.
|
|
125
|
+
*/
|
|
126
|
+
// @implements REQ-DASHSIMPLE-003
|
|
127
|
+
export function buildPlan(cg, jiraBaseUrl) {
|
|
128
|
+
const sq = cg.getSpecQueries();
|
|
129
|
+
const all = sq.getAllSpecs();
|
|
130
|
+
const coverage = new Map(cg.getSpecCoverage().requirements.map((r) => [r.specId, r]));
|
|
131
|
+
const docs = all.filter((s) => s.kind === 'document').sort(byFileOrder);
|
|
132
|
+
const docIds = new Set(docs.map((d) => d.id));
|
|
133
|
+
const reqsByDoc = new Map();
|
|
134
|
+
const looseByFile = new Map();
|
|
135
|
+
for (const s of all) {
|
|
136
|
+
if (s.kind !== 'requirement')
|
|
137
|
+
continue;
|
|
138
|
+
if (s.parentId && docIds.has(s.parentId)) {
|
|
139
|
+
const arr = reqsByDoc.get(s.parentId) ?? [];
|
|
140
|
+
arr.push(s);
|
|
141
|
+
reqsByDoc.set(s.parentId, arr);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
// A requirement outside any document still belongs in the plan.
|
|
145
|
+
const arr = looseByFile.get(s.sourcePath) ?? [];
|
|
146
|
+
arr.push(s);
|
|
147
|
+
looseByFile.set(s.sourcePath, arr);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const story = (s, meta) => {
|
|
151
|
+
const cov = coverage.get(s.id) ?? null;
|
|
152
|
+
return {
|
|
153
|
+
specId: s.id,
|
|
154
|
+
title: s.title,
|
|
155
|
+
body: s.body ?? '',
|
|
156
|
+
jiraKey: metaString(meta, `jira_issue_${s.id}`),
|
|
157
|
+
coverage: cov,
|
|
158
|
+
tasks: (cov?.criteria ?? []).map((c) => ({ specId: c.specId, title: c.title, verdict: c.verdict })),
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
const entries = [];
|
|
162
|
+
for (const d of docs) {
|
|
163
|
+
const reqs = (reqsByDoc.get(d.id) ?? []).sort(byFileOrder);
|
|
164
|
+
entries.push({
|
|
165
|
+
key: metaString(d.metadata, 'epicKey'),
|
|
166
|
+
title: d.title,
|
|
167
|
+
sourcePath: d.sourcePath,
|
|
168
|
+
startLine: d.startLine,
|
|
169
|
+
doc: { id: d.id, title: d.title, stories: reqs.map((r) => story(r, d.metadata)) },
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
for (const [file, reqs] of looseByFile) {
|
|
173
|
+
entries.push({
|
|
174
|
+
key: null, title: file, sourcePath: file,
|
|
175
|
+
doc: { id: file, title: file, stories: reqs.sort(byFileOrder).map((r) => story(r, undefined)) },
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
entries.sort(byFileOrder);
|
|
179
|
+
const epics = [];
|
|
180
|
+
const byKey = new Map();
|
|
181
|
+
for (const e of entries) {
|
|
182
|
+
if (e.key) {
|
|
183
|
+
const hit = byKey.get(e.key);
|
|
184
|
+
if (hit) {
|
|
185
|
+
hit.docs.push(e.doc);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const epic = { key: e.key, title: e.title, docs: [e.doc] };
|
|
189
|
+
byKey.set(e.key, epic);
|
|
190
|
+
epics.push(epic);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
epics.push({ key: null, title: e.title, docs: [e.doc] });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
const titles = new Map(all.map((s) => [s.id, s.title]));
|
|
197
|
+
const driftLinks = sq
|
|
198
|
+
.getLinksByState([...PLAN_DRIFT_STATES])
|
|
199
|
+
.map((l) => ({ ...l, specTitle: titles.get(l.specId) ?? null }));
|
|
200
|
+
return { jiraBaseUrl, epics, driftLinks };
|
|
201
|
+
}
|
|
88
202
|
async function resolveCg(app, req, reply) {
|
|
89
203
|
const cg = await app.activeCg(req);
|
|
90
204
|
if (!cg) {
|
|
@@ -148,6 +262,17 @@ export async function registerSpecRoutes(app) {
|
|
|
148
262
|
const scope = req.query.spec && req.query.spec.length > 0 ? req.query.spec : undefined;
|
|
149
263
|
return cg.getSpecCoverage(scope);
|
|
150
264
|
});
|
|
265
|
+
/**
|
|
266
|
+
* GET /api/spec/plan — epic → story → task for the Plan reader
|
|
267
|
+
* (REQ-DASHSIMPLE-003). Read-only, no JIRA call; the only JIRA config field
|
|
268
|
+
* it exposes is the base URL.
|
|
269
|
+
*/
|
|
270
|
+
app.get('/api/spec/plan', async (req, reply) => {
|
|
271
|
+
const cg = await resolveCg(app, req, reply);
|
|
272
|
+
if (!cg)
|
|
273
|
+
return;
|
|
274
|
+
return buildPlan(cg, readJiraBaseUrl());
|
|
275
|
+
});
|
|
151
276
|
app.get('/api/spec/:id', async (req, reply) => {
|
|
152
277
|
const cg = await resolveCg(app, req, reply);
|
|
153
278
|
if (!cg)
|
|
@@ -190,10 +190,16 @@ export async function createServer(options) {
|
|
|
190
190
|
const primaryPath = options.projectRoot ?? null;
|
|
191
191
|
const isPrimary = primaryPath ? primaryProjectMatcher(primaryPath) : () => false;
|
|
192
192
|
const resolveGraph = (projectPath) => primaryPath && isPrimary(projectPath) ? primaryCg : null;
|
|
193
|
-
|
|
193
|
+
// projectPaths lets hash-keyed transcripts (Gemini CLI) resolve to the
|
|
194
|
+
// primary project's real path (REQ-AGENTSWITCH-004).
|
|
195
|
+
watcher = startWatcher(dbHandle, {
|
|
196
|
+
verbose,
|
|
197
|
+
resolveGraph,
|
|
198
|
+
projectPaths: primaryPath ? [primaryPath] : [],
|
|
199
|
+
});
|
|
194
200
|
ownedWatcher = true;
|
|
195
201
|
if (verbose)
|
|
196
|
-
console.error('[specship-server]
|
|
202
|
+
console.error('[specship-server] transcript ingest watcher started');
|
|
197
203
|
// The specship-impact boot backfill is gone with the engine it fed
|
|
198
204
|
// (REQ-SURF-005) — nothing reads displaced_files/resolution any more.
|
|
199
205
|
// Re-cost prompts ingested while their model family was unpriced,
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: design-md
|
|
3
|
+
description: Turn a Claude Design project (claude.ai/design link or handoff bundle) or a Figma file (figma.com link) into specs/<slug>/design.md, a pixel-exact design reference that even a small model (Haiku, Gemini Flash) can build the UI from without guessing. Use this when someone shares a design to build or update, asks to "capture", "document" or "import" a design, or the design changed and design.md must catch up. Also use it to implement UI from an existing design.md.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# design.md: a pixel-exact design reference
|
|
7
|
+
|
|
8
|
+
design.md is one Markdown file per design, at `specs/<slug>/design.md`. It
|
|
9
|
+
states every visual value **verbatim** in a fixed layout, so whoever builds the
|
|
10
|
+
UI looks a value up instead of inferring it. A small model given the raw design
|
|
11
|
+
rounds `13.5px` to `14px`, swaps `#5B93F2` for "blue-500", and forgets the
|
|
12
|
+
hover state. Given design.md, it doesn't have to decide anything.
|
|
13
|
+
|
|
14
|
+
There are two halves, and the split is the point:
|
|
15
|
+
|
|
16
|
+
- **Values come from code, not from you.** `specship design extract` reads the
|
|
17
|
+
source and writes the *Generated reference*: every custom property, color,
|
|
18
|
+
font, size, spacing, radius, shadow, transition, breakpoint and style rule,
|
|
19
|
+
verbatim, with usage counts. **Never type a value into design.md yourself.**
|
|
20
|
+
Copy it from the generated block, which copied it from the source.
|
|
21
|
+
- **Structure comes from you.** You write the *authored* sections (screens,
|
|
22
|
+
components and states, motion, responsive, assets). They explain how the
|
|
23
|
+
values assemble, and they cite selectors and values from the generated block.
|
|
24
|
+
|
|
25
|
+
`specship design check` decides when you're done. It fails on a missing
|
|
26
|
+
section, an empty authored section, approximate wording, a stale source, or a
|
|
27
|
+
source value missing from the reference.
|
|
28
|
+
|
|
29
|
+
## The loop
|
|
30
|
+
|
|
31
|
+
### 1. Capture the source verbatim
|
|
32
|
+
|
|
33
|
+
Pick a slug from the design's name (kebab-case). Save everything under
|
|
34
|
+
`specs/<slug>/`. Read `references/sources.md` for each source's exact steps:
|
|
35
|
+
|
|
36
|
+
| The user gave you | Capture with | Save as |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `claude.ai/design/...` link | designer tools: `designer_session` → `designer_snapshot` | `snapshot.html` + every local stylesheet it links |
|
|
39
|
+
| a designer handoff bundle (`handoff-<ts>/`) | copy from `project/` (no fetch) | `snapshot.html` + linked CSS, decision record into `source.md` |
|
|
40
|
+
| `figma.com/design/...` link | Figma MCP: `get_metadata`, `get_design_context`, `get_variable_defs`, `get_screenshot` | `figma/<frame>.tsx`, `figma/variables.json`, `figma/<frame>.png`, `figma/metadata.xml` |
|
|
41
|
+
| exported HTML/CSS files | copy as-is | keep the file names |
|
|
42
|
+
|
|
43
|
+
Hard rules:
|
|
44
|
+
- **Never plain-fetch a design link.** A `claude.ai/design` URL returns a
|
|
45
|
+
1 KB loader shell, and a Figma URL returns a login page. If the tools
|
|
46
|
+
are missing, stop and give the setup step:
|
|
47
|
+
- designer tools: run `designer setup` once.
|
|
48
|
+
- Figma MCP: `claude mcp add --transport http figma https://mcp.figma.com/mcp`.
|
|
49
|
+
- Save tool output **byte for byte**. Don't reformat, summarise or "clean up"
|
|
50
|
+
a snapshot.
|
|
51
|
+
|
|
52
|
+
### 2. Extract
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
specship design extract specs/<slug>/snapshot.html \
|
|
56
|
+
--out specs/<slug>/design.md --title "<Design name>" --origin "<the link>"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For Figma, pass the captured files instead:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
specship design extract specs/<slug>/figma/<frame>.tsx specs/<slug>/figma/variables.json --out ...
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
If the generated block says **Missing linked stylesheet**, capture that file
|
|
66
|
+
next to the snapshot and run extract again with `--update`. The reference is
|
|
67
|
+
incomplete until then.
|
|
68
|
+
|
|
69
|
+
### 3. Write the authored sections
|
|
70
|
+
|
|
71
|
+
Fill **Screens & layout**, **Components & states**, **Interactions & motion**,
|
|
72
|
+
**Responsive behaviour** and **Assets**. `references/authored-sections.md`
|
|
73
|
+
gives the exact shape of each, with an example. The rules:
|
|
74
|
+
|
|
75
|
+
- Every number, color and font you write must appear in the Generated
|
|
76
|
+
reference (or Figma `metadata.xml` for frame sizes). Cite the selector:
|
|
77
|
+
"Card padding `16px 20px` (`.card`)".
|
|
78
|
+
- Name every state the source styles: `:hover`, `:focus-visible`, `:active`,
|
|
79
|
+
`:disabled`, `[aria-*]`, `.is-*` classes. For each one, list the values
|
|
80
|
+
that change.
|
|
81
|
+
- Look at the screenshot or rendered snapshot for order, alignment and
|
|
82
|
+
grouping. Only the generated block gives you the numbers.
|
|
83
|
+
- No approximate words: approximately, roughly, about 12, ~12, similar to,
|
|
84
|
+
etc., TBD. `check` rejects them. If the source doesn't say, write
|
|
85
|
+
"Not specified in the design; ask the designer" rather than inventing a value.
|
|
86
|
+
- Delete each `<!-- design-md: … -->` hint once its section is written.
|
|
87
|
+
|
|
88
|
+
### 4. Check until clean
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
specship design check specs/<slug>/design.md
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Fix every error and run it again. **You are finished only when it passes.**
|
|
95
|
+
|
|
96
|
+
### 5. Connect it to the spec
|
|
97
|
+
|
|
98
|
+
Add `design: specs/<slug>/design.md` to the frontmatter of the spec that
|
|
99
|
+
covers this UI. `/specship:spec implement` then has the implementer read
|
|
100
|
+
design.md before any UI edit. The spec keeps the contract (behaviour,
|
|
101
|
+
accessibility, data). design.md keeps the pixels. Never copy values into the
|
|
102
|
+
spec.
|
|
103
|
+
|
|
104
|
+
## When the design changes
|
|
105
|
+
|
|
106
|
+
Capture the new source over the old files (step 1), then:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
specship design extract <same sources> --update specs/<slug>/design.md
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Only the generated block is replaced. Your authored sections are kept, and a
|
|
113
|
+
dated **Change log** entry lists every token and value that was added, removed
|
|
114
|
+
or changed (`old → new`). Go through that entry and update each authored
|
|
115
|
+
sentence that cites a changed value. Then run `check` again.
|
|
116
|
+
|
|
117
|
+
## When code changes the UI
|
|
118
|
+
|
|
119
|
+
design.md describes the UI that ships, so it changes in the same commit as
|
|
120
|
+
the UI code. That applies to a new component, a changed state, layout or
|
|
121
|
+
token, and a copy change.
|
|
122
|
+
|
|
123
|
+
1. Update the authored sections that describe what moved.
|
|
124
|
+
2. Add a dated line under **Change log**: what changed and the requirement
|
|
125
|
+
id. For example, `- 2026-10-27: Sign In button gains a loading state
|
|
126
|
+
(REQ-AUTH-004).`
|
|
127
|
+
3. If the design source changed too (a new snapshot or Figma export), run
|
|
128
|
+
`specship design extract <sources> --update <design.md>` first. Only the
|
|
129
|
+
generated block is replaced, and the value changes are logged for you.
|
|
130
|
+
4. Run `specship design check <design.md>`.
|
|
131
|
+
|
|
132
|
+
`specship postcheck` enforces this, and `/specship:spec implement` runs it.
|
|
133
|
+
The check fails if a change touches UI files that a design-backed spec
|
|
134
|
+
governs, but that spec's design.md gets no new Change log entry, or no
|
|
135
|
+
longer passes `check`. A spec is design-backed when its frontmatter has
|
|
136
|
+
`design:`. The UI files it governs are the `.tsx`/`.jsx`/`.vue`/`.svelte`/
|
|
137
|
+
`.css`/`.html` files in its `implementations:` blocks. While you implement
|
|
138
|
+
that spec, every UI file you change counts.
|
|
139
|
+
|
|
140
|
+
## Building UI from design.md
|
|
141
|
+
|
|
142
|
+
This applies to you, or to any implementer you brief:
|
|
143
|
+
|
|
144
|
+
1. Read design.md top to bottom once: Fidelity rules first.
|
|
145
|
+
2. Rebuild the **Element outline** element by element: same nesting, each
|
|
146
|
+
element's inline style exactly, text exactly, and each `svg … = <svg …>`
|
|
147
|
+
pasted as is. For class selectors, use the declarations in **Style rules
|
|
148
|
+
(verbatim)** exactly. Use the **Design tokens** table to keep
|
|
149
|
+
`var(--…)` names when the project's stack supports CSS variables.
|
|
150
|
+
3. Reuse a project token only when its value is exactly equal. Otherwise add
|
|
151
|
+
the value as written.
|
|
152
|
+
4. Build every state in **Components & states**, and every breakpoint in
|
|
153
|
+
**Responsive behaviour**.
|
|
154
|
+
5. Add no CSS the design doesn't have. No resets, no normalizers, no
|
|
155
|
+
`button { font: inherit }` "fixes". Leave an element design.md doesn't
|
|
156
|
+
style on the browser default: the design was rendered with that default,
|
|
157
|
+
and one invented reset moves every pixel after it.
|
|
158
|
+
6. Anything design.md doesn't state is a question, not a guess.
|
|
159
|
+
7. **Verify, then fix, until it matches.** Render your UI to static HTML. A
|
|
160
|
+
plain `.html` build already is; for a framework, save the rendered page.
|
|
161
|
+
Then run:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
specship design verify <build.html> --against specs/<slug>/design.md
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
It compares your build's element outline with design.md's, line by line
|
|
168
|
+
(nesting, tags, classes, attributes, text and SVG), and checks that your
|
|
169
|
+
stylesheet rules keep design.md's order wherever a later rule would
|
|
170
|
+
override an earlier one. Fix each reported line or rule pair and run it
|
|
171
|
+
again. **You are done when it matches.** A small model that
|
|
172
|
+
misnests one container gets caught here and fixes it in one pass.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Writing the authored sections
|
|
2
|
+
|
|
3
|
+
The Generated reference has the values. The authored sections say how they
|
|
4
|
+
assemble into screens and components. Write them so a small model can build the
|
|
5
|
+
UI by following them in order.
|
|
6
|
+
|
|
7
|
+
Shared rules:
|
|
8
|
+
- Follow the **Element outline**'s nesting exactly. When you write
|
|
9
|
+
"Anatomy", list only the direct children of that element, in outline
|
|
10
|
+
order, and say which parent each part sits in. A part that is a sibling of
|
|
11
|
+
a row is never "in" that row. The outline is authoritative: if your prose
|
|
12
|
+
and the outline disagree, a builder follows the outline, and your prose
|
|
13
|
+
was wrong.
|
|
14
|
+
- Every value you write appears in the Generated reference, or in
|
|
15
|
+
`figma/metadata.xml` for frame sizes. Copy it exactly: `13.5px` stays
|
|
16
|
+
`13.5px`, and `#5B93F2` keeps its case.
|
|
17
|
+
- Cite the selector (or Figma layer name) behind every value, in backticks.
|
|
18
|
+
- One bullet per fact. Short, declarative, no adjectives that carry no value
|
|
19
|
+
("nice", "subtle", "slightly").
|
|
20
|
+
- When the source doesn't specify something, write: "Not specified in the
|
|
21
|
+
design; ask the designer."
|
|
22
|
+
- `specship design check` rejects these words: approximately, roughly,
|
|
23
|
+
about <n>, ~<n>, similar to, etc., TBD, something like.
|
|
24
|
+
|
|
25
|
+
## Screens & layout
|
|
26
|
+
|
|
27
|
+
One `###` per screen or frame. Cover:
|
|
28
|
+
- the frame size and the background,
|
|
29
|
+
- the layout model: grid or flex, direction, columns, gaps, max-width,
|
|
30
|
+
alignment,
|
|
31
|
+
- the regions top to bottom, or left to right, each with its size and the
|
|
32
|
+
selector that styles it.
|
|
33
|
+
|
|
34
|
+
```markdown
|
|
35
|
+
### Spec detail (1440 × 900)
|
|
36
|
+
|
|
37
|
+
- Background `#0B0D11` (`#boot-splash`); text `#E8EBF0`.
|
|
38
|
+
- Two columns: sidebar `280px` fixed (`div.col`) + content `1fr`; no gap.
|
|
39
|
+
- Content padding `28px 32px 56px` (`div.scroll-y`), scrolls vertically.
|
|
40
|
+
- Top to bottom: header row (`div.row.gap-8`, padding `15px 16px`), metadata line
|
|
41
|
+
(`div.sp-metaline`, margin-top `32px`), criteria list.
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Components & states
|
|
45
|
+
|
|
46
|
+
One `###` per component. Cover:
|
|
47
|
+
- its selector or selectors,
|
|
48
|
+
- its anatomy: the child parts in order,
|
|
49
|
+
- its exact box: size, padding, radius, border, shadow,
|
|
50
|
+
- its typography: family, size, weight, line height, letter spacing, color,
|
|
51
|
+
- then **one bullet per state**, naming the values that change. Cover every
|
|
52
|
+
state the source styles: default, `:hover`, `:focus-visible`, `:active`,
|
|
53
|
+
`:disabled`, `[aria-selected]` / `[aria-expanded]`, `.is-*`, loading, empty,
|
|
54
|
+
error.
|
|
55
|
+
|
|
56
|
+
```markdown
|
|
57
|
+
### Nav item (`a` in the sidebar)
|
|
58
|
+
|
|
59
|
+
- Height `32px`; padding `0px 10px`; gap `10px`; radius `6px`.
|
|
60
|
+
- Label `13px` / weight `450`, color `#8A93A3`.
|
|
61
|
+
- States:
|
|
62
|
+
- default: background transparent.
|
|
63
|
+
- `:hover`: background `rgba(255,255,255,0.06)`, label `#E8EBF0`.
|
|
64
|
+
- `[aria-current=page]`: background `rgba(91,147,242,0.12)`, label weight `600`.
|
|
65
|
+
- `:focus-visible`: outline `2px solid #5B93F2`, offset `2px`.
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Interactions & motion
|
|
69
|
+
|
|
70
|
+
For each transition or animation, give:
|
|
71
|
+
- the trigger,
|
|
72
|
+
- the animated properties,
|
|
73
|
+
- the duration, easing and delay,
|
|
74
|
+
- the keyframes name.
|
|
75
|
+
|
|
76
|
+
If there are none, write "None in this design."
|
|
77
|
+
|
|
78
|
+
## Responsive behaviour
|
|
79
|
+
|
|
80
|
+
For each breakpoint in the Generated reference's **Breakpoints** inventory,
|
|
81
|
+
say exactly what changes. If there are none, write "Single fixed layout."
|
|
82
|
+
|
|
83
|
+
## Assets
|
|
84
|
+
|
|
85
|
+
For each image, icon and font, give:
|
|
86
|
+
- the file or link,
|
|
87
|
+
- the rendered size,
|
|
88
|
+
- where it's used.
|
|
89
|
+
|
|
90
|
+
Include icon sets with the icon names used. If there are none, write "None."
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Capturing a design source
|
|
2
|
+
|
|
3
|
+
Every path ends with the source saved **byte for byte** under `specs/<slug>/`,
|
|
4
|
+
so `specship design extract` can read it and `specship design check` can tell
|
|
5
|
+
when it changes. Never plain-fetch a design URL.
|
|
6
|
+
|
|
7
|
+
## Claude Design: a `claude.ai/design` link
|
|
8
|
+
|
|
9
|
+
The link looks like `https://claude.ai/design/p/<project-id>/?file=<File+Name>.html`.
|
|
10
|
+
The file name comes from the `file=` parameter (`Data+Flow.html` is
|
|
11
|
+
`Data Flow.html`).
|
|
12
|
+
|
|
13
|
+
1. `designer_session({ action: "status" })`. If it reports that CDP is not up
|
|
14
|
+
or Chrome is not signed in, tell the user to run `designer setup` once and
|
|
15
|
+
**stop**.
|
|
16
|
+
2. If the session isn't on this project yet, `designer_session({ action: "adopt" })`
|
|
17
|
+
on an open tab, or `resume` the stored URL.
|
|
18
|
+
3. `designer_snapshot({ filename: "<File Name>.html" })`. It returns
|
|
19
|
+
`htmlPath`, the real rendered HTML read from inside the preview frame.
|
|
20
|
+
4. Copy it byte for byte: `cp "<htmlPath>" specs/<slug>/snapshot.html`.
|
|
21
|
+
5. Copy every **local** stylesheet the snapshot links
|
|
22
|
+
(`<link rel="stylesheet" href="styles.css">`) next to it, keeping the
|
|
23
|
+
relative path. Remote font links (Google Fonts) stay as links.
|
|
24
|
+
6. Write `specs/<slug>/source.md` with the link, the file name and today's
|
|
25
|
+
date.
|
|
26
|
+
|
|
27
|
+
## Claude Design: a handoff bundle
|
|
28
|
+
|
|
29
|
+
A bundle is `handoff-<ts>/` holding `project/` and `decision-record.md`. It is
|
|
30
|
+
already on disk, so nothing is fetched.
|
|
31
|
+
|
|
32
|
+
1. Choose the file: `project/<chosen>.html`. If there are several and the user
|
|
33
|
+
hasn't said which, ask.
|
|
34
|
+
2. `cp` it to `specs/<slug>/snapshot.html`, along with the CSS, JS and assets it
|
|
35
|
+
references from `project/`.
|
|
36
|
+
3. Write `specs/<slug>/source.md` with the bundle path, then append
|
|
37
|
+
`decision-record.md` verbatim. It records why the design looks the way it
|
|
38
|
+
does.
|
|
39
|
+
|
|
40
|
+
## Figma: a `figma.com` link
|
|
41
|
+
|
|
42
|
+
The link looks like `https://www.figma.com/design/<fileKey>/<name>?node-id=<a>-<b>`.
|
|
43
|
+
The node id is `<a>:<b>`. Use the Figma MCP server's tools. If they aren't in
|
|
44
|
+
the session, tell the user to run
|
|
45
|
+
`claude mcp add --transport http figma https://mcp.figma.com/mcp`, sign in,
|
|
46
|
+
and restart. Then **stop**.
|
|
47
|
+
|
|
48
|
+
For each frame (screen) in scope:
|
|
49
|
+
|
|
50
|
+
| Tool | Gives you | Save as |
|
|
51
|
+
|---|---|---|
|
|
52
|
+
| `get_metadata` | the layer tree with every frame's x, y, width, height | `specs/<slug>/figma/metadata.xml` |
|
|
53
|
+
| `get_design_context` | code for the frame, including every color, size and spacing as a literal | `specs/<slug>/figma/<frame>.tsx` |
|
|
54
|
+
| `get_variable_defs` | the variables (tokens) the frame uses, as name → value | `specs/<slug>/figma/variables.json` |
|
|
55
|
+
| `get_screenshot` | a rendered image of the frame | `specs/<slug>/figma/<frame>.png` |
|
|
56
|
+
|
|
57
|
+
- Save each response verbatim. When `get_design_context` returns code, write
|
|
58
|
+
the code exactly. Don't convert it to the project's framework yet; that
|
|
59
|
+
is implementation.
|
|
60
|
+
- If a frame is too large for one `get_design_context` call, call it on each
|
|
61
|
+
child frame named in `metadata.xml`. Save each one as its own `.tsx`.
|
|
62
|
+
- Then extract:
|
|
63
|
+
`specship design extract specs/<slug>/figma/*.tsx specs/<slug>/figma/variables.json --out specs/<slug>/design.md --origin "<link>"`.
|
|
64
|
+
- Frame sizes for **Screens & layout** come from `metadata.xml`. Look at the
|
|
65
|
+
`.png` to see order and grouping.
|
|
66
|
+
|
|
67
|
+
## Exported HTML / CSS
|
|
68
|
+
|
|
69
|
+
Copy the files into `specs/<slug>/` with their names unchanged, and pass the
|
|
70
|
+
HTML entry file to `extract`. Local stylesheets it links are read
|
|
71
|
+
automatically.
|
|
@@ -132,7 +132,7 @@ Next steps:
|
|
|
132
132
|
|
|
133
133
|
## When to use the workflow instead of the skill directly
|
|
134
134
|
|
|
135
|
-
This skill is the **lightweight path** — runs conversationally in
|
|
135
|
+
This skill is the **lightweight path** — runs conversationally in your coding agent, no formal approval gates. For team contexts that want the same discipline `spec-implement` enforces, point the user at:
|
|
136
136
|
|
|
137
137
|
```
|
|
138
138
|
specship workflow run spec-author --input DESCRIPTION="..."
|
|
@@ -102,7 +102,7 @@ In SpecShip itself (and in any project whose `CLAUDE.md` states equivalents), th
|
|
|
102
102
|
| Surface the spec touches | What the spec must account for |
|
|
103
103
|
|---|---|
|
|
104
104
|
| **MCP tools** (`src/mcp/`) — new tool, changed output, changed budget | Explore budgets are **monotonic**: a change may not shrink what a given budget returns. And `src/mcp/server-instructions.ts` is the **single source of truth** for agent-facing tool guidance — a spec that changes how the agent should use a tool must say that file is updated, not that guidance is duplicated elsewhere. |
|
|
105
|
-
| **Installer** (`src/installer/`) — new asset, new target, changed layout | Name **which targets** are affected (Claude Code
|
|
105
|
+
| **Installer** (`src/installer/`) — new asset, new target, changed layout | Name **which targets** are affected (Claude Code and Gemini CLI both get the full surface — MCP entry, steering, hooks, commands, skills, subagent — except the Claude-only status line; `specship install` with no target wires the agent it runs under). Say how **markers** are handled, so install is idempotent and uninstall fully reverses. Require a **CHANGELOG entry** — installer regressions break every new install silently. |
|
|
106
106
|
| **Retrieval** — anything that changes how an answer is assembled | The budget is **tool-call count + wall-clock latency**, not tokens. A flow question must still resolve in 1–5 specship calls with zero Read/Grep. An acceptance bullet that lets the answer get slower or need more calls is a regression, however good the output looks. |
|
|
107
107
|
| **Extraction** (`src/extraction/`) — new language, new node/edge type | Use the **exact `NodeKind` / `EdgeKind` strings** from `src/types.ts`; inventing a kind silently drops the data. Any new SQL or grammar `.wasm` must be added to **`copy-assets`** or it won't ship in the package. |
|
|
108
108
|
|
|
@@ -20,7 +20,7 @@ export type LintSeverity = 'error' | 'warning';
|
|
|
20
20
|
* code (`spec_missing_id`, `spec_stranded_id`, `spec_bare_path_ref`,
|
|
21
21
|
* `spec_acceptance_parent_mismatch`) so one vocabulary spans both layers.
|
|
22
22
|
*/
|
|
23
|
-
export type LintCode = 'spec_duplicate_id' | 'spec_orphan_acceptance_parent' | 'spec_requirement_without_acceptance' | 'spec_needs_review_marker' | 'spec_unknown_evidence_kind' | 'spec_external_fact_missing_provenance' | 'spec_malformed_enforced_rule' | 'spec_no_negative_case' | 'spec_missing_non_goals' | 'spec_missing_test_plan' | 'spec_requirement_without_rfc2119' | string;
|
|
23
|
+
export type LintCode = 'spec_duplicate_id' | 'spec_duplicate_id_cross_file' | 'spec_orphan_acceptance_parent' | 'spec_requirement_without_acceptance' | 'spec_needs_review_marker' | 'spec_unknown_evidence_kind' | 'spec_external_fact_missing_provenance' | 'spec_malformed_enforced_rule' | 'spec_no_negative_case' | 'spec_missing_non_goals' | 'spec_missing_test_plan' | 'spec_requirement_without_rfc2119' | string;
|
|
24
24
|
export interface LintFinding {
|
|
25
25
|
code: LintCode;
|
|
26
26
|
severity: LintSeverity;
|
|
@@ -47,6 +47,31 @@ export interface LintReport {
|
|
|
47
47
|
/** The exit-code predicate (A3): errors fail, warnings alone do not. */
|
|
48
48
|
hasErrors: boolean;
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Every `<!-- id: X -->` declaration in a spec file, in line order (1-indexed).
|
|
52
|
+
* A brief or design transcript declares nothing — it is not a spec, so its
|
|
53
|
+
* markers (quoted from elsewhere) must not collide with real ones.
|
|
54
|
+
*/
|
|
55
|
+
export declare function collectIdDeclarations(filePath: string, source: string): Array<{
|
|
56
|
+
id: string;
|
|
57
|
+
line: number;
|
|
58
|
+
}>;
|
|
59
|
+
/** Where one id is declared: a project-relative file and a 1-indexed line. */
|
|
60
|
+
export interface IdDeclarationSite {
|
|
61
|
+
filePath: string;
|
|
62
|
+
line: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Ids declared in more than one spec file (REQ-MULTIDEV-001). The persistence
|
|
66
|
+
* layer upserts by id, so after a merge where two branches picked the same id
|
|
67
|
+
* in different files, one section silently replaces the other. Repeats inside a
|
|
68
|
+
* single file are `spec_duplicate_id`'s job and are not reported here.
|
|
69
|
+
*
|
|
70
|
+
* Emits one error per colliding id per declaring file, anchored at that file's
|
|
71
|
+
* first declaration, with every `path:line` listed (sorted) in the message.
|
|
72
|
+
* Callers decide which of the returned findings belong to a linted file.
|
|
73
|
+
*/
|
|
74
|
+
export declare function findCrossFileDuplicates(decls: Map<string, IdDeclarationSite[]>): LintFinding[];
|
|
50
75
|
/**
|
|
51
76
|
* Lint one spec file's source. `filePath` is used verbatim in findings, so
|
|
52
77
|
* callers should pass a project-relative path.
|