@selvakumaresra/specship 0.9.0 → 0.10.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 (86) hide show
  1. package/commands/ss-domain.md +98 -0
  2. package/commands/ss-triage.md +177 -0
  3. package/dist/bin/specship.js +249 -0
  4. package/dist/bin/specship.js.map +1 -1
  5. package/dist/db/schema.sql +1 -1
  6. package/dist/db/spec-queries.d.ts +29 -0
  7. package/dist/db/spec-queries.d.ts.map +1 -1
  8. package/dist/db/spec-queries.js +63 -0
  9. package/dist/db/spec-queries.js.map +1 -1
  10. package/dist/enforce/enforce.d.ts +70 -0
  11. package/dist/enforce/enforce.d.ts.map +1 -0
  12. package/dist/enforce/enforce.js +125 -0
  13. package/dist/enforce/enforce.js.map +1 -0
  14. package/dist/extraction/specs/markdown-spec-extractor.d.ts +21 -0
  15. package/dist/extraction/specs/markdown-spec-extractor.d.ts.map +1 -1
  16. package/dist/extraction/specs/markdown-spec-extractor.js +144 -0
  17. package/dist/extraction/specs/markdown-spec-extractor.js.map +1 -1
  18. package/dist/fitness/fitness.d.ts +75 -0
  19. package/dist/fitness/fitness.d.ts.map +1 -0
  20. package/dist/fitness/fitness.js +204 -0
  21. package/dist/fitness/fitness.js.map +1 -0
  22. package/dist/graph/maintainability.d.ts +101 -0
  23. package/dist/graph/maintainability.d.ts.map +1 -0
  24. package/dist/graph/maintainability.js +278 -0
  25. package/dist/graph/maintainability.js.map +1 -0
  26. package/dist/index.d.ts +45 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +79 -1
  29. package/dist/index.js.map +1 -1
  30. package/dist/installer/instructions-template.d.ts +5 -4
  31. package/dist/installer/instructions-template.d.ts.map +1 -1
  32. package/dist/installer/instructions-template.js +10 -4
  33. package/dist/installer/instructions-template.js.map +1 -1
  34. package/dist/installer/targets/claude.d.ts.map +1 -1
  35. package/dist/installer/targets/claude.js +4 -0
  36. package/dist/installer/targets/claude.js.map +1 -1
  37. package/dist/installer/targets/shared.d.ts.map +1 -1
  38. package/dist/installer/targets/shared.js +4 -0
  39. package/dist/installer/targets/shared.js.map +1 -1
  40. package/dist/mcp/fitness-tool.d.ts +12 -0
  41. package/dist/mcp/fitness-tool.d.ts.map +1 -0
  42. package/dist/mcp/fitness-tool.js +46 -0
  43. package/dist/mcp/fitness-tool.js.map +1 -0
  44. package/dist/mcp/maintainability-tool.d.ts +13 -0
  45. package/dist/mcp/maintainability-tool.d.ts.map +1 -0
  46. package/dist/mcp/maintainability-tool.js +64 -0
  47. package/dist/mcp/maintainability-tool.js.map +1 -0
  48. package/dist/mcp/server-instructions.d.ts +1 -1
  49. package/dist/mcp/server-instructions.d.ts.map +1 -1
  50. package/dist/mcp/server-instructions.js +3 -2
  51. package/dist/mcp/server-instructions.js.map +1 -1
  52. package/dist/mcp/spec-tools.d.ts.map +1 -1
  53. package/dist/mcp/spec-tools.js +115 -1
  54. package/dist/mcp/spec-tools.js.map +1 -1
  55. package/dist/mcp/tools.d.ts +13 -0
  56. package/dist/mcp/tools.d.ts.map +1 -1
  57. package/dist/mcp/tools.js +75 -0
  58. package/dist/mcp/tools.js.map +1 -1
  59. package/dist/resolution/domain-gap-seed.d.ts +60 -0
  60. package/dist/resolution/domain-gap-seed.d.ts.map +1 -0
  61. package/dist/resolution/domain-gap-seed.js +87 -0
  62. package/dist/resolution/domain-gap-seed.js.map +1 -0
  63. package/dist/resolution/spec-link-resolver.d.ts +46 -1
  64. package/dist/resolution/spec-link-resolver.d.ts.map +1 -1
  65. package/dist/resolution/spec-link-resolver.js +78 -0
  66. package/dist/resolution/spec-link-resolver.js.map +1 -1
  67. package/dist/server/routes/domain.js +0 -0
  68. package/dist/server/routes/maintainability.js +18 -0
  69. package/dist/server/server.js +4 -0
  70. package/dist/types.d.ts +3 -1
  71. package/dist/types.d.ts.map +1 -1
  72. package/dist/types.js +6 -0
  73. package/dist/types.js.map +1 -1
  74. package/dist/web/chunk-EZZBWC7Z.js +1 -0
  75. package/dist/web/{chunk-UBOZGQNK.js → chunk-IZQXYQNQ.js} +1 -1
  76. package/dist/web/chunk-JQXCRIK2.js +1 -0
  77. package/dist/web/{chunk-ASZ77FMZ.js → chunk-JT2YIHPL.js} +1 -1
  78. package/dist/web/{chunk-WLIMNDS3.js → chunk-ONKHTXHJ.js} +1 -1
  79. package/dist/web/{chunk-FHZHD2ZG.js → chunk-P5JX67LT.js} +1 -1
  80. package/dist/web/{chunk-RJLLYZEQ.js → chunk-PDTQX2UL.js} +1 -1
  81. package/dist/web/{chunk-RASJHUXS.js → chunk-XWDR6MPC.js} +1 -1
  82. package/dist/web/{chunk-D5OCNEJA.js → chunk-ZG7H3XPL.js} +1 -1
  83. package/dist/web/index.html +1 -1
  84. package/dist/web/main-GYPY5V5H.js +1 -0
  85. package/package.json +1 -1
  86. package/dist/web/main-P33TUYUP.js +0 -1
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Architecture-fitness functions (REQ-FITNESS-001 / 002).
3
+ *
4
+ * Evaluates declarative architecture rules against the knowledge graph's
5
+ * dependency edges and reports concrete violations. Three rule types — forbidden
6
+ * dependency, layering allow-list, and module isolation (leaf/sink) — each an
7
+ * edge constraint. Selectors are globs over the project-relative file path
8
+ * (picomatch), so "module/dir A" addresses cleanly across languages.
9
+ *
10
+ * Deterministic (sorted output). A rule whose selector matches no node is
11
+ * reported as a CONFIG ERROR, never a silent pass (REQ-FITNESS-002.A3) — a typo
12
+ * can't produce a false-green. Pure read over QueryBuilder.
13
+ *
14
+ * Realizes the second harness-engineering expansion lane (REQ-STRATEGY-002).
15
+ */
16
+ import { QueryBuilder } from '../db/queries';
17
+ /** A glob over the project-relative file path (e.g. `src/ui/**`). */
18
+ export type Selector = string;
19
+ export interface ForbiddenRule {
20
+ type: 'forbidden';
21
+ name: string;
22
+ /** Dependencies FROM nodes matching this selector… */
23
+ from: Selector;
24
+ /** …TO nodes matching this selector are violations. */
25
+ to: Selector;
26
+ }
27
+ export interface LayersRule {
28
+ type: 'layers';
29
+ name: string;
30
+ /** layer name → selector that assigns nodes to it (first match wins, config order). */
31
+ layers: Record<string, Selector>;
32
+ /** layer name → layers it MAY depend on. Same-layer is always allowed. */
33
+ allow: Record<string, string[]>;
34
+ }
35
+ export interface IsolationRule {
36
+ type: 'isolation';
37
+ name: string;
38
+ module: Selector;
39
+ /** leaf: nothing outside may depend INTO the module. sink: it may depend on nothing outside. */
40
+ mode: 'leaf' | 'sink';
41
+ }
42
+ export type FitnessRule = ForbiddenRule | LayersRule | IsolationRule;
43
+ export interface FitnessViolation {
44
+ rule: string;
45
+ ruleType: FitnessRule['type'];
46
+ source: string;
47
+ target: string;
48
+ location: string;
49
+ edgeKind: string;
50
+ detail: string;
51
+ }
52
+ export interface FitnessConfigError {
53
+ rule: string;
54
+ message: string;
55
+ }
56
+ export interface FitnessReport {
57
+ ruleCount: number;
58
+ violations: FitnessViolation[];
59
+ configErrors: FitnessConfigError[];
60
+ /** True when there are no violations AND no config errors. */
61
+ clean: boolean;
62
+ }
63
+ /**
64
+ * Evaluate the rules against the graph. Pure read.
65
+ */
66
+ export declare function evaluateFitness(queries: QueryBuilder, rules: FitnessRule[]): FitnessReport;
67
+ /** Default name of the checked-in project config file at the project root. */
68
+ export declare const FITNESS_CONFIG_FILE = "specship.config.json";
69
+ /**
70
+ * Load fitness rules from the checked-in `specship.config.json` (`fitness.rules`).
71
+ * Missing/unparseable config → no rules. Each entry is taken as-is; an invalid
72
+ * shape surfaces later as a config error during evaluation.
73
+ */
74
+ export declare function loadFitnessRules(projectRoot: string): FitnessRule[];
75
+ //# sourceMappingURL=fitness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fitness.d.ts","sourceRoot":"","sources":["../../src/fitness/fitness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAG7C,qEAAqE;AACrE,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAE9B,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,IAAI,EAAE,QAAQ,CAAC;IACf,uDAAuD;IACvD,EAAE,EAAE,QAAQ,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,QAAQ,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,uFAAuF;IACvF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACjC,0EAA0E;IAC1E,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,QAAQ,CAAC;IACjB,gGAAgG;IAChG,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,UAAU,GAAG,aAAa,CAAC;AAErE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,gBAAgB,EAAE,CAAC;IAC/B,YAAY,EAAE,kBAAkB,EAAE,CAAC;IACnC,8DAA8D;IAC9D,KAAK,EAAE,OAAO,CAAC;CAChB;AAWD;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,aAAa,CAoE1F;AAkDD,8EAA8E;AAC9E,eAAO,MAAM,mBAAmB,yBAAyB,CAAC;AAE1D;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,EAAE,CAgBnE"}
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+ /**
3
+ * Architecture-fitness functions (REQ-FITNESS-001 / 002).
4
+ *
5
+ * Evaluates declarative architecture rules against the knowledge graph's
6
+ * dependency edges and reports concrete violations. Three rule types — forbidden
7
+ * dependency, layering allow-list, and module isolation (leaf/sink) — each an
8
+ * edge constraint. Selectors are globs over the project-relative file path
9
+ * (picomatch), so "module/dir A" addresses cleanly across languages.
10
+ *
11
+ * Deterministic (sorted output). A rule whose selector matches no node is
12
+ * reported as a CONFIG ERROR, never a silent pass (REQ-FITNESS-002.A3) — a typo
13
+ * can't produce a false-green. Pure read over QueryBuilder.
14
+ *
15
+ * Realizes the second harness-engineering expansion lane (REQ-STRATEGY-002).
16
+ */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || (function () {
34
+ var ownKeys = function(o) {
35
+ ownKeys = Object.getOwnPropertyNames || function (o) {
36
+ var ar = [];
37
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
38
+ return ar;
39
+ };
40
+ return ownKeys(o);
41
+ };
42
+ return function (mod) {
43
+ if (mod && mod.__esModule) return mod;
44
+ var result = {};
45
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
46
+ __setModuleDefault(result, mod);
47
+ return result;
48
+ };
49
+ })();
50
+ var __importDefault = (this && this.__importDefault) || function (mod) {
51
+ return (mod && mod.__esModule) ? mod : { "default": mod };
52
+ };
53
+ Object.defineProperty(exports, "__esModule", { value: true });
54
+ exports.FITNESS_CONFIG_FILE = void 0;
55
+ exports.evaluateFitness = evaluateFitness;
56
+ exports.loadFitnessRules = loadFitnessRules;
57
+ const fs = __importStar(require("fs"));
58
+ const path = __importStar(require("path"));
59
+ const picomatch_1 = __importDefault(require("picomatch"));
60
+ /** Edge kinds that are real dependencies (exclude structural containment/exports). */
61
+ function isDepEdge(kind) {
62
+ return kind !== 'contains' && kind !== 'exports';
63
+ }
64
+ function cmp(a, b) {
65
+ return a < b ? -1 : a > b ? 1 : 0;
66
+ }
67
+ /**
68
+ * Evaluate the rules against the graph. Pure read.
69
+ */
70
+ function evaluateFitness(queries, rules) {
71
+ const nodes = queries.getAllNodes();
72
+ const byId = new Map(nodes.map((n) => [n.id, n]));
73
+ const files = [...new Set(nodes.map((n) => n.filePath))];
74
+ const violations = [];
75
+ const configErrors = [];
76
+ // A selector is "live" if at least one indexed file matches it.
77
+ const matcherCache = new Map();
78
+ const matcher = (sel) => {
79
+ let m = matcherCache.get(sel);
80
+ if (!m) {
81
+ m = (0, picomatch_1.default)(sel, { dot: true });
82
+ matcherCache.set(sel, m);
83
+ }
84
+ return m;
85
+ };
86
+ const selectorMatchesAny = (sel) => {
87
+ const m = matcher(sel);
88
+ return files.some((f) => m(f));
89
+ };
90
+ // Pre-validate selectors → config errors (REQ-FITNESS-002.A3).
91
+ const liveRules = [];
92
+ for (const rule of rules) {
93
+ const dead = [];
94
+ if (rule.type === 'forbidden') {
95
+ if (!selectorMatchesAny(rule.from))
96
+ dead.push(`from: "${rule.from}"`);
97
+ if (!selectorMatchesAny(rule.to))
98
+ dead.push(`to: "${rule.to}"`);
99
+ }
100
+ else if (rule.type === 'isolation') {
101
+ if (!selectorMatchesAny(rule.module))
102
+ dead.push(`module: "${rule.module}"`);
103
+ }
104
+ else if (rule.type === 'layers') {
105
+ for (const [layer, sel] of Object.entries(rule.layers)) {
106
+ if (!selectorMatchesAny(sel))
107
+ dead.push(`layer ${layer}: "${sel}"`);
108
+ }
109
+ }
110
+ if (dead.length) {
111
+ configErrors.push({ rule: rule.name, message: `selector matches no indexed file — ${dead.join(', ')}` });
112
+ }
113
+ else {
114
+ liveRules.push(rule);
115
+ }
116
+ }
117
+ if (liveRules.length > 0) {
118
+ for (const n of nodes) {
119
+ const out = queries.getOutgoingEdges(n.id);
120
+ for (const e of out) {
121
+ if (!isDepEdge(e.kind))
122
+ continue;
123
+ const tgt = byId.get(e.target);
124
+ if (!tgt)
125
+ continue;
126
+ const loc = `${n.filePath}:${n.startLine ?? 0}`;
127
+ for (const rule of liveRules) {
128
+ const v = evalEdge(rule, n, tgt, e, loc, matcher);
129
+ if (v)
130
+ violations.push(v);
131
+ }
132
+ }
133
+ }
134
+ }
135
+ violations.sort((a, b) => cmp(a.rule, b.rule) || cmp(a.location, b.location) || cmp(a.source, b.source) || cmp(a.target, b.target));
136
+ configErrors.sort((a, b) => cmp(a.rule, b.rule) || cmp(a.message, b.message));
137
+ return {
138
+ ruleCount: rules.length,
139
+ violations,
140
+ configErrors,
141
+ clean: violations.length === 0 && configErrors.length === 0,
142
+ };
143
+ }
144
+ function evalEdge(rule, src, tgt, edge, loc, matcher) {
145
+ const base = { rule: rule.name, ruleType: rule.type, source: src.qualifiedName, target: tgt.qualifiedName, location: loc, edgeKind: edge.kind };
146
+ if (rule.type === 'forbidden') {
147
+ if (matcher(rule.from)(src.filePath) && matcher(rule.to)(tgt.filePath)) {
148
+ return { ...base, detail: `forbidden dependency: ${rule.from} → ${rule.to}` };
149
+ }
150
+ return null;
151
+ }
152
+ if (rule.type === 'isolation') {
153
+ const inMod = matcher(rule.module);
154
+ const srcIn = inMod(src.filePath);
155
+ const tgtIn = inMod(tgt.filePath);
156
+ if (rule.mode === 'leaf' && tgtIn && !srcIn) {
157
+ return { ...base, detail: `${rule.module} is a leaf — nothing outside may depend into it` };
158
+ }
159
+ if (rule.mode === 'sink' && srcIn && !tgtIn) {
160
+ return { ...base, detail: `${rule.module} is a sink — it may depend on nothing outside` };
161
+ }
162
+ return null;
163
+ }
164
+ // layers
165
+ const layerOf = (file) => {
166
+ for (const [layer, sel] of Object.entries(rule.layers)) {
167
+ if (matcher(sel)(file))
168
+ return layer;
169
+ }
170
+ return null;
171
+ };
172
+ const ls = layerOf(src.filePath);
173
+ const lt = layerOf(tgt.filePath);
174
+ if (ls && lt && ls !== lt) {
175
+ const allowed = rule.allow[ls] ?? [];
176
+ if (!allowed.includes(lt)) {
177
+ return { ...base, detail: `layer "${ls}" may not depend on "${lt}" (allowed: ${allowed.length ? allowed.join(', ') : 'none'})` };
178
+ }
179
+ }
180
+ return null;
181
+ }
182
+ /** Default name of the checked-in project config file at the project root. */
183
+ exports.FITNESS_CONFIG_FILE = 'specship.config.json';
184
+ /**
185
+ * Load fitness rules from the checked-in `specship.config.json` (`fitness.rules`).
186
+ * Missing/unparseable config → no rules. Each entry is taken as-is; an invalid
187
+ * shape surfaces later as a config error during evaluation.
188
+ */
189
+ function loadFitnessRules(projectRoot) {
190
+ try {
191
+ const raw = fs.readFileSync(path.join(projectRoot, exports.FITNESS_CONFIG_FILE), 'utf-8');
192
+ const cfg = JSON.parse(raw);
193
+ const rules = cfg?.fitness?.rules;
194
+ if (Array.isArray(rules)) {
195
+ return rules.filter((r) => !!r && typeof r === 'object' && typeof r.name === 'string'
196
+ && ['forbidden', 'layers', 'isolation'].includes(r.type ?? ''));
197
+ }
198
+ }
199
+ catch {
200
+ // no config / unparseable → no rules
201
+ }
202
+ return [];
203
+ }
204
+ //# sourceMappingURL=fitness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fitness.js","sourceRoot":"","sources":["../../src/fitness/fitness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Maintainability harness (REQ-MAINT-001).
3
+ *
4
+ * Derives four maintainability signals from the existing knowledge graph, with
5
+ * NO additional file parse — coupling, size hotspots, dependency cycles, and
6
+ * dead-code candidates. Pure read over `QueryBuilder`; deterministic (every
7
+ * output array is sorted by a stable key), so a re-run on an unchanged index is
8
+ * byte-identical and the report can underpin a future CI gate (REQ-STRATEGY-003).
9
+ *
10
+ * Realizes the first harness-engineering expansion lane (REQ-STRATEGY-001).
11
+ */
12
+ import { QueryBuilder } from '../db/queries';
13
+ import { NodeKind } from '../types';
14
+ /** Flag thresholds. Defaults work out of the box; a project may override (REQ-MAINT-002). */
15
+ export interface MaintainabilityThresholds {
16
+ /** A symbol/file with fan-in or fan-out ≥ this is a coupling hotspot. */
17
+ highDegree: number;
18
+ /** A symbol whose line span ≥ this is oversized. */
19
+ largeSymbolLines: number;
20
+ /** A file with ≥ this many defined symbols is a god-file. */
21
+ godFileSymbols: number;
22
+ }
23
+ export declare const DEFAULT_THRESHOLDS: MaintainabilityThresholds;
24
+ /**
25
+ * Generated / vendored files excluded from the analysis by default — they aren't
26
+ * source-of-truth, so flagging them is noise (e.g. a bundled `chunk-*.js` shows
27
+ * as a huge coupling hub, a `.d.ts` as dead code). A project may add more via
28
+ * `maintainability.exclude` in specship.config.json. Globs over the
29
+ * project-relative file path.
30
+ */
31
+ export declare const DEFAULT_EXCLUDE: string[];
32
+ export interface CouplingFinding {
33
+ nodeId: string;
34
+ name: string;
35
+ qualifiedName: string;
36
+ filePath: string;
37
+ kind: NodeKind;
38
+ fanIn: number;
39
+ fanOut: number;
40
+ /** Why this surfaced — the threshold it breached (REQ-MAINT-002.A3). */
41
+ reason: string;
42
+ }
43
+ export interface OversizedFinding {
44
+ nodeId: string;
45
+ name: string;
46
+ qualifiedName: string;
47
+ filePath: string;
48
+ kind: NodeKind;
49
+ startLine: number;
50
+ endLine: number;
51
+ lines: number;
52
+ reason: string;
53
+ }
54
+ export interface GodFileFinding {
55
+ filePath: string;
56
+ symbolCount: number;
57
+ reason: string;
58
+ }
59
+ export interface CycleFinding {
60
+ /** Files forming a dependency cycle (sorted). */
61
+ files: string[];
62
+ reason: string;
63
+ }
64
+ export interface DeadCodeFinding {
65
+ nodeId: string;
66
+ name: string;
67
+ qualifiedName: string;
68
+ filePath: string;
69
+ kind: NodeKind;
70
+ startLine: number;
71
+ reason: string;
72
+ }
73
+ export interface MaintainabilityReport {
74
+ thresholds: MaintainabilityThresholds;
75
+ coupling: CouplingFinding[];
76
+ oversized: OversizedFinding[];
77
+ godFiles: GodFileFinding[];
78
+ cycles: CycleFinding[];
79
+ deadCode: DeadCodeFinding[];
80
+ /** True when nothing crossed a threshold — an explicit clean result, not empty/ambiguous. */
81
+ clean: boolean;
82
+ }
83
+ /**
84
+ * Compute the maintainability report from the graph. Pure read — never writes.
85
+ */
86
+ export declare function computeMaintainability(queries: QueryBuilder, thresholds?: MaintainabilityThresholds, exclude?: string[]): MaintainabilityReport;
87
+ /** Default name of the checked-in project config file at the project root. */
88
+ export declare const CONFIG_FILE_NAME = "specship.config.json";
89
+ /**
90
+ * Resolve effective thresholds (REQ-MAINT-002): defaults < checked-in
91
+ * `specship.config.json` (`maintainability.thresholds`) < explicit override.
92
+ * A missing or unparseable config silently falls back to defaults.
93
+ */
94
+ export declare function resolveThresholds(projectRoot: string, override?: Partial<MaintainabilityThresholds>): MaintainabilityThresholds;
95
+ /**
96
+ * Resolve the exclude globs: the built-in DEFAULT_EXCLUDE plus any
97
+ * `maintainability.exclude` array from specship.config.json (additive — config
98
+ * extends the defaults rather than replacing them).
99
+ */
100
+ export declare function resolveExclude(projectRoot: string): string[];
101
+ //# sourceMappingURL=maintainability.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"maintainability.d.ts","sourceRoot":"","sources":["../../src/graph/maintainability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAc,QAAQ,EAAE,MAAM,UAAU,CAAC;AAEhD,6FAA6F;AAC7F,MAAM,WAAW,yBAAyB;IACxC,yEAAyE;IACzE,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,gBAAgB,EAAE,MAAM,CAAC;IACzB,6DAA6D;IAC7D,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,kBAAkB,EAAE,yBAIhC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,EASnC,CAAC;AAoBF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,iDAAiD;IACjD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,yBAAyB,CAAC;IACtC,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,6FAA6F;IAC7F,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,YAAY,EACrB,UAAU,GAAE,yBAA8C,EAC1D,OAAO,GAAE,MAAM,EAAoB,GAClC,qBAAqB,CAyFvB;AAgDD,8EAA8E;AAC9E,eAAO,MAAM,gBAAgB,yBAAyB,CAAC;AAEvD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,OAAO,CAAC,yBAAyB,CAAC,GAC5C,yBAAyB,CAe3B;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAW5D"}