@selvakumaresra/specship 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/README.md +17 -7
  2. package/commands/ss-brainstorm.md +68 -0
  3. package/dist/analytics/specship-impact.d.ts +72 -0
  4. package/dist/analytics/specship-impact.d.ts.map +1 -0
  5. package/dist/analytics/specship-impact.js +216 -0
  6. package/dist/analytics/specship-impact.js.map +1 -0
  7. package/dist/bin/specship.js +177 -4
  8. package/dist/bin/specship.js.map +1 -1
  9. package/dist/db/migrations.d.ts +1 -1
  10. package/dist/db/migrations.d.ts.map +1 -1
  11. package/dist/db/migrations.js +15 -1
  12. package/dist/db/migrations.js.map +1 -1
  13. package/dist/db/schema.sql +8 -0
  14. package/dist/extraction/specs/markdown-spec-extractor.d.ts +19 -0
  15. package/dist/extraction/specs/markdown-spec-extractor.d.ts.map +1 -1
  16. package/dist/extraction/specs/markdown-spec-extractor.js +132 -11
  17. package/dist/extraction/specs/markdown-spec-extractor.js.map +1 -1
  18. package/dist/index.d.ts +37 -0
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +64 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/installer/index.d.ts +2 -2
  23. package/dist/installer/index.d.ts.map +1 -1
  24. package/dist/installer/targets/claude.d.ts.map +1 -1
  25. package/dist/installer/targets/claude.js +2 -0
  26. package/dist/installer/targets/claude.js.map +1 -1
  27. package/dist/mcp/spec-tools.d.ts.map +1 -1
  28. package/dist/mcp/spec-tools.js +87 -6
  29. package/dist/mcp/spec-tools.js.map +1 -1
  30. package/dist/resolution/brief-link-resolver.d.ts +114 -0
  31. package/dist/resolution/brief-link-resolver.d.ts.map +1 -0
  32. package/dist/resolution/brief-link-resolver.js +261 -0
  33. package/dist/resolution/brief-link-resolver.js.map +1 -0
  34. package/dist/server/ingest/impact-backfill.js +69 -0
  35. package/dist/server/ingest/impact-query.js +343 -0
  36. package/dist/server/ingest/index.js +2 -1
  37. package/dist/server/ingest/ingestor.js +41 -6
  38. package/dist/server/ingest/specship-classify.js +153 -0
  39. package/dist/server/routes/claude.js +32 -0
  40. package/dist/server/routes/spec.js +103 -0
  41. package/dist/server/server.js +26 -2
  42. package/dist/types.d.ts +1 -1
  43. package/dist/types.d.ts.map +1 -1
  44. package/dist/types.js +4 -0
  45. package/dist/types.js.map +1 -1
  46. package/dist/web/chunk-JQ534IB6.js +6 -0
  47. package/dist/web/chunk-O7434ZMN.js +1 -0
  48. package/dist/web/chunk-RASJHUXS.js +1 -0
  49. package/dist/web/chunk-TQ3P2QZO.js +1 -0
  50. package/dist/web/chunk-WCHGDXWC.js +1 -0
  51. package/dist/web/index.html +1 -1
  52. package/dist/web/main-QAP4FTDP.js +1 -0
  53. package/package.json +1 -1
  54. package/dist/web/chunk-2YUJNZ2Y.js +0 -6
  55. package/dist/web/chunk-B3YPFY6A.js +0 -1
  56. package/dist/web/chunk-GWPVKJIY.js +0 -1
  57. package/dist/web/main-R53HA54V.js +0 -1
@@ -0,0 +1,261 @@
1
+ "use strict";
2
+ /**
3
+ * Brief ↔ spec link reconciliation (REQ-FUNNEL-002).
4
+ *
5
+ * A brainstorm brief (`specs/<slug>/brief.md`, indexed as a `brief`-kind spec by
6
+ * REQ-FUNNEL-001) is linked to the spec it produced by reconciling BOTH pointer
7
+ * directions:
8
+ *
9
+ * - the brief's own `spec:` frontmatter (carried in `brief.metadata.spec`),
10
+ * which MAY name a requirement — in which case it resolves UP to that
11
+ * requirement's enclosing document; and
12
+ * - a document spec whose `brief:` frontmatter points back at the brief's file.
13
+ *
14
+ * A link is established when EITHER direction resolves. With neither, the brief
15
+ * is an unlinked `idea`. When the two directions resolve to DIFFERENT documents,
16
+ * the link is `conflict` — surfaced rather than silently resolved to one side.
17
+ *
18
+ * The link is COMPUTED from current DB state (not materialized), so it is always
19
+ * fresh and needs no re-index bookkeeping.
20
+ */
21
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ var desc = Object.getOwnPropertyDescriptor(m, k);
24
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
25
+ desc = { enumerable: true, get: function() { return m[k]; } };
26
+ }
27
+ Object.defineProperty(o, k2, desc);
28
+ }) : (function(o, m, k, k2) {
29
+ if (k2 === undefined) k2 = k;
30
+ o[k2] = m[k];
31
+ }));
32
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
33
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
34
+ }) : function(o, v) {
35
+ o["default"] = v;
36
+ });
37
+ var __importStar = (this && this.__importStar) || (function () {
38
+ var ownKeys = function(o) {
39
+ ownKeys = Object.getOwnPropertyNames || function (o) {
40
+ var ar = [];
41
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
42
+ return ar;
43
+ };
44
+ return ownKeys(o);
45
+ };
46
+ return function (mod) {
47
+ if (mod && mod.__esModule) return mod;
48
+ var result = {};
49
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
50
+ __setModuleDefault(result, mod);
51
+ return result;
52
+ };
53
+ })();
54
+ Object.defineProperty(exports, "__esModule", { value: true });
55
+ exports.cleanSpecPointer = cleanSpecPointer;
56
+ exports.resolveToDocumentId = resolveToDocumentId;
57
+ exports.resolveBriefLink = resolveBriefLink;
58
+ exports.computeSpecFunnel = computeSpecFunnel;
59
+ exports.findBriefsForSpec = findBriefsForSpec;
60
+ exports.summarizeBriefFunnel = summarizeBriefFunnel;
61
+ const path = __importStar(require("path"));
62
+ /** Strip a trailing ` # comment` and surrounding quotes from a frontmatter value. */
63
+ function cleanSpecPointer(raw) {
64
+ if (typeof raw !== 'string')
65
+ return null;
66
+ let v = raw.trim();
67
+ if (!v)
68
+ return null;
69
+ if ((v.startsWith('"') && v.endsWith('"')) ||
70
+ (v.startsWith("'") && v.endsWith("'"))) {
71
+ v = v.slice(1, -1).trim();
72
+ }
73
+ else {
74
+ // Strip an unquoted trailing comment (whitespace + '#'), mirroring the
75
+ // server's parseBriefField — a '#fragment' with no leading space is kept.
76
+ const h = v.search(/\s#/);
77
+ if (h !== -1)
78
+ v = v.slice(0, h).trim();
79
+ }
80
+ return v || null;
81
+ }
82
+ const fwd = (p) => p.replace(/\\/g, '/');
83
+ /** Resolve a spec id up to its enclosing document id (walk parent_id to the root). */
84
+ function resolveToDocumentId(sq, id) {
85
+ let cur = sq.getSpecById(id);
86
+ if (!cur)
87
+ return null;
88
+ const seen = new Set();
89
+ while (cur.kind !== 'document' && cur.parentId && !seen.has(cur.id)) {
90
+ seen.add(cur.id);
91
+ const parent = sq.getSpecById(cur.parentId);
92
+ if (!parent)
93
+ break;
94
+ cur = parent;
95
+ }
96
+ return cur.id;
97
+ }
98
+ /** The document (if any) whose `brief:` frontmatter resolves to this brief's file. */
99
+ function documentPointingAtBrief(sq, brief) {
100
+ const target = fwd(brief.sourcePath);
101
+ for (const s of sq.getAllSpecs()) {
102
+ if (s.kind !== 'document' || !s.metadata || typeof s.metadata !== 'object')
103
+ continue;
104
+ const ref = cleanSpecPointer(s.metadata.brief);
105
+ if (!ref)
106
+ continue;
107
+ // `brief:` is relative to the spec file's own directory.
108
+ const resolved = path.posix.normalize(path.posix.join(path.posix.dirname(fwd(s.sourcePath)), fwd(ref)));
109
+ if (resolved === target)
110
+ return s.id;
111
+ }
112
+ return null;
113
+ }
114
+ /** Reconcile a brief's link to its spec from both pointer directions. */
115
+ function resolveBriefLink(sq, brief) {
116
+ const meta = brief.metadata && typeof brief.metadata === 'object'
117
+ ? brief.metadata
118
+ : {};
119
+ const ptr = cleanSpecPointer(meta.spec);
120
+ const briefSide = ptr ? resolveToDocumentId(sq, ptr) : null;
121
+ const specSide = documentPointingAtBrief(sq, brief);
122
+ if (briefSide && specSide && briefSide !== specSide) {
123
+ return { briefId: brief.id, linkedSpecId: null, state: 'conflict', briefSide, specSide };
124
+ }
125
+ const linkedSpecId = briefSide ?? specSide ?? null;
126
+ return {
127
+ briefId: brief.id,
128
+ linkedSpecId,
129
+ state: linkedSpecId ? 'specified' : 'idea',
130
+ briefSide,
131
+ specSide,
132
+ };
133
+ }
134
+ /** Implementation rollup for a single document's requirements. */
135
+ function documentRollup(sq, docId) {
136
+ const reqs = sq.getSpecsByParent(docId).filter((s) => s.kind === 'requirement');
137
+ const r = {
138
+ requirements: reqs.length,
139
+ implemented: 0,
140
+ verified: 0,
141
+ drifted: 0,
142
+ broken: 0,
143
+ orphaned: 0,
144
+ };
145
+ for (const req of reqs) {
146
+ for (const lk of sq.getLinksBySpec(req.id)) {
147
+ if (lk.state === 'implemented')
148
+ r.implemented++;
149
+ else if (lk.state === 'verified')
150
+ r.verified++;
151
+ else if (lk.state === 'drifted')
152
+ r.drifted++;
153
+ else if (lk.state === 'broken')
154
+ r.broken++;
155
+ else if (lk.state === 'orphaned')
156
+ r.orphaned++;
157
+ }
158
+ }
159
+ return r;
160
+ }
161
+ /** Compute the project-wide spec lifecycle funnel (idea → spec → implemented). */
162
+ function computeSpecFunnel(sq) {
163
+ const all = sq.getAllSpecs();
164
+ const documents = all.filter((s) => s.kind === 'document');
165
+ const requirements = all.filter((s) => s.kind === 'requirement');
166
+ const briefLinks = all.filter((s) => s.kind === 'brief').map((b) => resolveBriefLink(sq, b));
167
+ const links = { implemented: 0, verified: 0, drifted: 0, broken: 0, orphaned: 0 };
168
+ for (const req of requirements) {
169
+ for (const lk of sq.getLinksBySpec(req.id)) {
170
+ if (lk.state === 'implemented')
171
+ links.implemented++;
172
+ else if (lk.state === 'verified')
173
+ links.verified++;
174
+ else if (lk.state === 'drifted')
175
+ links.drifted++;
176
+ else if (lk.state === 'broken')
177
+ links.broken++;
178
+ else if (lk.state === 'orphaned')
179
+ links.orphaned++;
180
+ }
181
+ }
182
+ const ideas = briefLinks
183
+ .filter((l) => l.state === 'idea')
184
+ .map((l) => ({ briefId: l.briefId, title: sq.getSpecById(l.briefId)?.title ?? '' }));
185
+ const conflicts = briefLinks
186
+ .filter((l) => l.state === 'conflict')
187
+ .map((l) => ({ briefId: l.briefId, briefSide: l.briefSide, specSide: l.specSide }));
188
+ return {
189
+ summary: {
190
+ ideas: ideas.length,
191
+ specified: briefLinks.filter((l) => l.state === 'specified').length,
192
+ conflicts: conflicts.length,
193
+ documents: documents.length,
194
+ requirements: requirements.length,
195
+ links,
196
+ },
197
+ documents: documents.map((d) => ({ id: d.id, title: d.title, rollup: documentRollup(sq, d.id) })),
198
+ ideas,
199
+ conflicts,
200
+ };
201
+ }
202
+ /** All briefs that resolve (non-conflicting) to a given document. */
203
+ function findBriefsForSpec(sq, docId) {
204
+ const out = [];
205
+ for (const s of sq.getAllSpecs()) {
206
+ if (s.kind !== 'brief')
207
+ continue;
208
+ const link = resolveBriefLink(sq, s);
209
+ if (link.linkedSpecId === docId)
210
+ out.push(link);
211
+ }
212
+ return out;
213
+ }
214
+ /**
215
+ * Roll a brief up to its lifecycle state (REQ-FUNNEL-003): `idea` (unlinked),
216
+ * `conflict` (ambiguous link), or `specified` with an implementation rollup of
217
+ * the linked document's requirements. A document whose links are all
218
+ * drifted/broken/orphaned yields implemented=0 — degraded states are surfaced
219
+ * rather than mistaken for a working implementation.
220
+ */
221
+ function summarizeBriefFunnel(sq, brief) {
222
+ const link = resolveBriefLink(sq, brief);
223
+ if (link.state !== 'specified' || !link.linkedSpecId) {
224
+ return { briefId: brief.id, state: link.state, linkedSpecId: link.linkedSpecId, rollup: null };
225
+ }
226
+ const requirements = sq
227
+ .getSpecsByParent(link.linkedSpecId)
228
+ .filter((s) => s.kind === 'requirement');
229
+ const rollup = {
230
+ requirements: requirements.length,
231
+ implemented: 0,
232
+ verified: 0,
233
+ drifted: 0,
234
+ broken: 0,
235
+ orphaned: 0,
236
+ };
237
+ for (const req of requirements) {
238
+ for (const lk of sq.getLinksBySpec(req.id)) {
239
+ switch (lk.state) {
240
+ case 'implemented':
241
+ rollup.implemented++;
242
+ break;
243
+ case 'verified':
244
+ rollup.verified++;
245
+ break;
246
+ case 'drifted':
247
+ rollup.drifted++;
248
+ break;
249
+ case 'broken':
250
+ rollup.broken++;
251
+ break;
252
+ case 'orphaned':
253
+ rollup.orphaned++;
254
+ break;
255
+ default: break; // drafted / implementing — in-progress, not "implemented"
256
+ }
257
+ }
258
+ }
259
+ return { briefId: brief.id, state: 'specified', linkedSpecId: link.linkedSpecId, rollup };
260
+ }
261
+ //# sourceMappingURL=brief-link-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brief-link-resolver.js","sourceRoot":"","sources":["../../src/resolution/brief-link-resolver.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Lazy backfill of `displaced_files` / `resolution` for pre-existing rows.
3
+ *
4
+ * After the v9 migration, existing `claude_tool_calls` rows have `is_specship`
5
+ * set (from the SQL migration) but `resolution IS NULL` and
6
+ * `displaced_files IS NULL`. This module fills them in so the impact dashboard
7
+ * reflects historical SpecShip usage, not just calls ingested post-upgrade.
8
+ *
9
+ * Design notes:
10
+ * - Only touches rows where `is_specship = 1 AND resolution IS NULL`.
11
+ * - Resolves the graph once per distinct `project_path` (cached in a Map).
12
+ * - Calls `classifyToolCall` — same logic as live ingest (DRY).
13
+ * - Idempotent: a second run is always a no-op (no eligible rows remain).
14
+ * - The `resolveGraph` call is wrapped in try/catch — a locked or corrupt
15
+ * index falls back to null, which classifies the row as 'unresolved'.
16
+ * - Must never crash the caller. The server bootstrap wraps in try/catch.
17
+ */
18
+ import { classifyToolCall } from './specship-classify.js';
19
+ /**
20
+ * Backfill `resolution` and `displaced_files` for every `claude_tool_calls`
21
+ * row that has `is_specship = 1 AND resolution IS NULL`.
22
+ *
23
+ * @param db - The analytics SQLite handle (same type as ingestor uses).
24
+ * @param resolveGraph - Factory returning a `GraphLike` for a project path,
25
+ * or null if unavailable.
26
+ */
27
+ export function backfillDisplaced(db, resolveGraph) {
28
+ // Fetch all eligible rows in one query. JOIN to sessions to get project_path.
29
+ // We limit to is_specship=1 AND resolution IS NULL so already-classified rows
30
+ // are never re-processed, giving natural idempotency.
31
+ const rows = db.prepare(`
32
+ SELECT tc.id, tc.tool_name, tc.input_json, tc.result_length, s.project_path
33
+ FROM claude_tool_calls tc
34
+ JOIN claude_sessions s ON s.id = tc.session_id
35
+ -- NULL = never classified (pre-upgrade rows). 'unresolved' = classified but
36
+ -- the graph/symbols didn't resolve — retry those too, since the resolver and
37
+ -- the symbol extractor have both been fixed, and a row left unresolved while
38
+ -- its project wasn't primary should resolve once that project IS primary.
39
+ -- 'resolved' and 'n/a' are terminal and never reprocessed.
40
+ WHERE tc.is_specship = 1 AND (tc.resolution IS NULL OR tc.resolution = 'unresolved')
41
+ `).all();
42
+ if (rows.length === 0)
43
+ return;
44
+ // Cache resolved graphs so we open each project at most once per call.
45
+ const graphCache = new Map();
46
+ const updateStmt = db.prepare('UPDATE claude_tool_calls SET resolution = ?, displaced_files = ? WHERE id = ?');
47
+ // Wrap everything in a single transaction for speed + atomicity.
48
+ const runTxn = db.transaction(() => {
49
+ for (const row of rows) {
50
+ // Resolve the graph for this project path (cached).
51
+ let graph;
52
+ if (graphCache.has(row.project_path)) {
53
+ graph = graphCache.get(row.project_path);
54
+ }
55
+ else {
56
+ try {
57
+ graph = resolveGraph(row.project_path);
58
+ }
59
+ catch {
60
+ graph = null;
61
+ }
62
+ graphCache.set(row.project_path, graph);
63
+ }
64
+ const cls = classifyToolCall({ toolName: row.tool_name, inputJson: row.input_json, resultLength: row.result_length }, graph);
65
+ updateStmt.run(cls.resolution, cls.displacedFiles, row.id);
66
+ }
67
+ });
68
+ runTxn();
69
+ }