wicked-crew 0.3.0 → 0.3.2
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/dist/api/requirements.d.ts +57 -0
- package/dist/api/requirements.d.ts.map +1 -0
- package/dist/api/requirements.js +331 -0
- package/dist/api/requirements.js.map +1 -0
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +115 -2
- package/dist/api/routes.js.map +1 -1
- package/dist/core/adapter.d.ts +5 -0
- package/dist/core/adapter.d.ts.map +1 -1
- package/dist/core/adapter.js +50 -16
- package/dist/core/adapter.js.map +1 -1
- package/dist/core/types.d.ts +8 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/studio/assets/index-CjUiA3ex.css +32 -0
- package/dist/studio/assets/index-DlHYzFvv.js +420 -0
- package/dist/studio/index.html +2 -2
- package/package.json +2 -2
- package/dist/studio/assets/index-CckxWxam.js +0 -420
- package/dist/studio/assets/index-CjQFuKL4.css +0 -32
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export interface RequirementOverride {
|
|
2
|
+
title?: string | undefined;
|
|
3
|
+
notes?: string | undefined;
|
|
4
|
+
status?: string | undefined;
|
|
5
|
+
risk?: boolean | undefined;
|
|
6
|
+
}
|
|
7
|
+
export type RequirementCategory = 'functional' | 'config-data';
|
|
8
|
+
/** Product-functional requirements come from CODE; statements extracted from
|
|
9
|
+
* lockfiles, manifests, data fixtures, and docs are honest observations about
|
|
10
|
+
* those assets but are NOT product behavior — they class as config-data so the
|
|
11
|
+
* default view can focus on the product. */
|
|
12
|
+
export declare function categoryOf(file: string): RequirementCategory;
|
|
13
|
+
export interface RequirementSummary {
|
|
14
|
+
key: string;
|
|
15
|
+
domain: string;
|
|
16
|
+
reqId: string;
|
|
17
|
+
title: string;
|
|
18
|
+
category: RequirementCategory;
|
|
19
|
+
/** First business-rule statement — the requirement's actual content (empty when none). */
|
|
20
|
+
statement: string;
|
|
21
|
+
status: string;
|
|
22
|
+
risk: boolean;
|
|
23
|
+
riskSource: 'operator' | 'data' | null;
|
|
24
|
+
edited: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface RequirementDetail extends RequirementSummary {
|
|
27
|
+
description: string;
|
|
28
|
+
notes: string;
|
|
29
|
+
sourceTitle: string;
|
|
30
|
+
ruleCount: number;
|
|
31
|
+
componentCount: number;
|
|
32
|
+
validationCount: number;
|
|
33
|
+
errorPathCount: number;
|
|
34
|
+
businessRules: unknown[];
|
|
35
|
+
legacyComponents: unknown[];
|
|
36
|
+
}
|
|
37
|
+
export interface RequirementsQuery {
|
|
38
|
+
q?: string | undefined;
|
|
39
|
+
risk?: 'risk' | 'no-risk' | undefined;
|
|
40
|
+
domain?: string | undefined;
|
|
41
|
+
category?: RequirementCategory | undefined;
|
|
42
|
+
offset: number;
|
|
43
|
+
limit: number;
|
|
44
|
+
}
|
|
45
|
+
export interface RequirementsPage {
|
|
46
|
+
total: number;
|
|
47
|
+
corpus: number;
|
|
48
|
+
offset: number;
|
|
49
|
+
limit: number;
|
|
50
|
+
items: RequirementSummary[];
|
|
51
|
+
}
|
|
52
|
+
/** Tokenized AND-match: every whitespace-separated term must appear in the haystack. */
|
|
53
|
+
export declare function listRequirements(rootPath: string, query: RequirementsQuery): Promise<RequirementsPage | null>;
|
|
54
|
+
export declare function getRequirement(rootPath: string, key: string): Promise<RequirementDetail | null>;
|
|
55
|
+
/** Merge a patch into the overrides sidecar (atomic write) and return the fresh detail. */
|
|
56
|
+
export declare function patchRequirement(rootPath: string, key: string, patch: RequirementOverride): Promise<RequirementDetail | null>;
|
|
57
|
+
//# sourceMappingURL=requirements.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requirements.d.ts","sourceRoot":"","sources":["../../src/api/requirements.ts"],"names":[],"mappings":"AA4CA,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,IAAI,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;CAC5B;AAED,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG,aAAa,CAAC;AAE/D;;;4CAG4C;AAC5C,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,CAM5D;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,0FAA0F;IAC1F,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,UAAU,GAAG,MAAM,GAAG,IAAI,CAAC;IACvC,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,iBAAkB,SAAQ,kBAAkB;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,OAAO,EAAE,CAAC;IACzB,gBAAgB,EAAE,OAAO,EAAE,CAAC;CAC7B;AA0QD,MAAM,WAAW,iBAAiB;IAChC,CAAC,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,QAAQ,CAAC,EAAE,mBAAmB,GAAG,SAAS,CAAC;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,kBAAkB,EAAE,CAAC;CAC7B;AAED,wFAAwF;AACxF,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,iBAAiB,GACvB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAwBlC;AAED,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAmBnC;AAED,2FAA2F;AAC3F,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,mBAAmB,GACzB,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAmBnC"}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REQUIREMENTS SERVICE — server-side search + operator overrides over the LIVE
|
|
3
|
+
* estate store, with the `requirements_graph.json` artifact as fallback.
|
|
4
|
+
*
|
|
5
|
+
* Why the store is primary: the artifact is an evidence-gated SNAPSHOT — it only
|
|
6
|
+
* regenerates when `wicked-core domain-graph` passes its coverage bar, so mid-extraction
|
|
7
|
+
* it can lag the store by hours (observed: a UI serving day-old placeholder titles while
|
|
8
|
+
* the store held thousands of validated statements). Requirements content lives in
|
|
9
|
+
* estate (`nodes.requirement` + RULE-* annotations); the UI reads that truth directly
|
|
10
|
+
* (read-only `node:sqlite`) and falls back to the artifact for repos without a store
|
|
11
|
+
* or on runtimes without the sqlite builtin.
|
|
12
|
+
*
|
|
13
|
+
* Why server-side: 15k+ requirements — shipping them to the browser for JS-side
|
|
14
|
+
* filtering is not search. The daemon builds a flat index (cached, invalidated on
|
|
15
|
+
* store/artifact mtime with a small TTL guard against WAL-churn thrash), and queries
|
|
16
|
+
* run here: tokenized AND-match over id/domain/title/statements, risk + domain
|
|
17
|
+
* filters, offset/limit pagination.
|
|
18
|
+
*
|
|
19
|
+
* Why an OVERRIDES sidecar: the artifact is DERIVED (regenerated from the estate
|
|
20
|
+
* store), so operator edits written into it would be clobbered on the next
|
|
21
|
+
* `domain-graph` run. Edits live in `requirements_overrides.json` beside the artifact,
|
|
22
|
+
* keyed `domain::reqId`, and are merged at read time — the overlay survives
|
|
23
|
+
* regeneration and keeps provenance honest (`riskSource: operator` vs `data`).
|
|
24
|
+
*/
|
|
25
|
+
import { readFile, writeFile, rename, mkdir, stat } from 'node:fs/promises';
|
|
26
|
+
import { join, dirname } from 'node:path';
|
|
27
|
+
/** Product-functional requirements come from CODE; statements extracted from
|
|
28
|
+
* lockfiles, manifests, data fixtures, and docs are honest observations about
|
|
29
|
+
* those assets but are NOT product behavior — they class as config-data so the
|
|
30
|
+
* default view can focus on the product. */
|
|
31
|
+
export function categoryOf(file) {
|
|
32
|
+
const f = file.toLowerCase();
|
|
33
|
+
const base = f.slice(f.lastIndexOf('/') + 1);
|
|
34
|
+
if (/\.(json|ya?ml|lock|toml|ini|env|md|mdx|txt|csv)$/.test(base))
|
|
35
|
+
return 'config-data';
|
|
36
|
+
if (base.includes('pnpm-lock') || base.includes('package-lock'))
|
|
37
|
+
return 'config-data';
|
|
38
|
+
return 'functional';
|
|
39
|
+
}
|
|
40
|
+
const RISK_RE = /risk/i;
|
|
41
|
+
const RISK_PREFIX = '[RISK]';
|
|
42
|
+
/** WAL churn during extraction touches the db every few seconds — don't rebuild a
|
|
43
|
+
* 15k-row index per keystroke; a fresh-enough index is authoritative for this long. */
|
|
44
|
+
const REBUILD_TTL_MS = 5_000;
|
|
45
|
+
function storePath(rootPath) {
|
|
46
|
+
return join(rootPath, '.codegraph', 'estate.db');
|
|
47
|
+
}
|
|
48
|
+
function artifactPath(rootPath) {
|
|
49
|
+
return join(rootPath, '.wicked-estate', 'requirements', 'requirements_graph.json');
|
|
50
|
+
}
|
|
51
|
+
function overridesPath(rootPath) {
|
|
52
|
+
return join(rootPath, '.wicked-estate', 'requirements', 'requirements_overrides.json');
|
|
53
|
+
}
|
|
54
|
+
async function mtimeMs(path) {
|
|
55
|
+
try {
|
|
56
|
+
return (await stat(path)).mtimeMs;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return -1;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function readOverrides(rootPath) {
|
|
63
|
+
try {
|
|
64
|
+
const raw = await readFile(overridesPath(rootPath), 'utf8');
|
|
65
|
+
const parsed = JSON.parse(raw);
|
|
66
|
+
return typeof parsed === 'object' && parsed !== null
|
|
67
|
+
? parsed
|
|
68
|
+
: {};
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return {};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const cache = new Map();
|
|
75
|
+
let tmpSeq = 0;
|
|
76
|
+
/** Lazily resolved sqlite builtin — absent on older Node runtimes → artifact fallback. */
|
|
77
|
+
let sqliteMod;
|
|
78
|
+
async function sqlite() {
|
|
79
|
+
if (sqliteMod === undefined) {
|
|
80
|
+
try {
|
|
81
|
+
const name = 'node:sqlite';
|
|
82
|
+
sqliteMod = (await import(name));
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
sqliteMod = null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return sqliteMod;
|
|
89
|
+
}
|
|
90
|
+
/** Build the index from the LIVE estate store (read-only). Returns null when the
|
|
91
|
+
* store can't serve (no db, no sqlite builtin, schema mismatch) — caller falls back. */
|
|
92
|
+
async function buildStoreIndex(rootPath, sourceMtime, ovMtime) {
|
|
93
|
+
const mod = await sqlite();
|
|
94
|
+
if (mod === null)
|
|
95
|
+
return null;
|
|
96
|
+
const overrides = await readOverrides(rootPath);
|
|
97
|
+
let db = null;
|
|
98
|
+
const entries = [];
|
|
99
|
+
const byKey = new Map();
|
|
100
|
+
try {
|
|
101
|
+
db = new mod.DatabaseSync(storePath(rootPath), { readOnly: true });
|
|
102
|
+
const rules = new Map();
|
|
103
|
+
for (const r of db
|
|
104
|
+
.prepare("SELECT node_sym, value, confidence FROM annotations WHERE key LIKE 'RULE-%'")
|
|
105
|
+
.all()) {
|
|
106
|
+
const list = rules.get(r.node_sym) ?? [];
|
|
107
|
+
list.push({ statement: r.value ?? '', confidence: r.confidence });
|
|
108
|
+
rules.set(r.node_sym, list);
|
|
109
|
+
}
|
|
110
|
+
const rows = db
|
|
111
|
+
.prepare(`SELECT n.symbol AS sid, s.sym AS sym, n.name, n.file, n.requirement,
|
|
112
|
+
n.requirement_validated AS validated
|
|
113
|
+
FROM nodes n JOIN symbols s ON s.sid = n.symbol
|
|
114
|
+
WHERE n.requirement IS NOT NULL AND n.requirement != ''`)
|
|
115
|
+
.all();
|
|
116
|
+
for (const row of rows) {
|
|
117
|
+
const key = row.sym;
|
|
118
|
+
const ov = overrides[key];
|
|
119
|
+
const file = row.file ?? '';
|
|
120
|
+
const slash = file.lastIndexOf('/');
|
|
121
|
+
const domain = slash > 0 ? file.slice(0, slash) : '(root)';
|
|
122
|
+
const dataRisk = row.requirement.startsWith(RISK_PREFIX);
|
|
123
|
+
const risk = ov?.risk !== undefined ? ov.risk : dataRisk;
|
|
124
|
+
const riskSource = ov?.risk !== undefined ? 'operator' : dataRisk ? 'data' : null;
|
|
125
|
+
const title = ov?.title ?? (row.name !== null && row.name !== '' ? row.name : key);
|
|
126
|
+
const nodeRules = rules.get(row.sid) ?? [];
|
|
127
|
+
const summary = {
|
|
128
|
+
key,
|
|
129
|
+
domain,
|
|
130
|
+
reqId: key,
|
|
131
|
+
title,
|
|
132
|
+
category: categoryOf(file),
|
|
133
|
+
statement: row.requirement,
|
|
134
|
+
status: ov?.status ?? (row.validated === 1 ? 'validated' : 'active'),
|
|
135
|
+
risk,
|
|
136
|
+
riskSource,
|
|
137
|
+
edited: ov !== undefined,
|
|
138
|
+
};
|
|
139
|
+
const source = {
|
|
140
|
+
title,
|
|
141
|
+
description: file,
|
|
142
|
+
status: summary.status,
|
|
143
|
+
legacy_components: file === '' ? [] : [file],
|
|
144
|
+
business_rules: nodeRules,
|
|
145
|
+
};
|
|
146
|
+
const haystack = `${key} ${domain} ${title} ${row.requirement} ${nodeRules.map((r) => r.statement).join(' ')} ${ov?.notes ?? ''}`.toLowerCase();
|
|
147
|
+
const entry = { summary, haystack, source };
|
|
148
|
+
entries.push(entry);
|
|
149
|
+
byKey.set(key, entry);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return null; // schema drift or unreadable store — artifact fallback
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
db?.close();
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
entries,
|
|
160
|
+
byKey,
|
|
161
|
+
sourceMtimeMs: sourceMtime,
|
|
162
|
+
fromStore: true,
|
|
163
|
+
overridesMtimeMs: ovMtime,
|
|
164
|
+
builtAtMs: Date.now(),
|
|
165
|
+
total: entries.length,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
async function buildIndex(rootPath) {
|
|
169
|
+
const [dbMtime, walMtime, artMtime, ovMtime] = await Promise.all([
|
|
170
|
+
mtimeMs(storePath(rootPath)),
|
|
171
|
+
mtimeMs(`${storePath(rootPath)}-wal`),
|
|
172
|
+
mtimeMs(artifactPath(rootPath)),
|
|
173
|
+
mtimeMs(overridesPath(rootPath)),
|
|
174
|
+
]);
|
|
175
|
+
const storeMtime = Math.max(dbMtime, walMtime);
|
|
176
|
+
const cached = cache.get(rootPath);
|
|
177
|
+
// TTL applies ONLY to store-built indexes: extraction WAL churn changes the db
|
|
178
|
+
// mtime every few seconds and must not trigger a rebuild per request. Artifact
|
|
179
|
+
// mtime changes only on regen, so artifact-built indexes always mtime-check.
|
|
180
|
+
if (cached?.fromStore === true && Date.now() - cached.builtAtMs < REBUILD_TTL_MS)
|
|
181
|
+
return cached;
|
|
182
|
+
if (storeMtime >= 0) {
|
|
183
|
+
if (cached && cached.sourceMtimeMs === storeMtime && cached.overridesMtimeMs === ovMtime) {
|
|
184
|
+
cached.builtAtMs = Date.now();
|
|
185
|
+
return cached;
|
|
186
|
+
}
|
|
187
|
+
const fromStore = await buildStoreIndex(rootPath, storeMtime, ovMtime);
|
|
188
|
+
if (fromStore !== null) {
|
|
189
|
+
cache.set(rootPath, fromStore);
|
|
190
|
+
return fromStore;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (artMtime < 0)
|
|
194
|
+
return null; // no store, no artifact — requirements not generated yet
|
|
195
|
+
if (cached && cached.sourceMtimeMs === artMtime && cached.overridesMtimeMs === ovMtime) {
|
|
196
|
+
return cached;
|
|
197
|
+
}
|
|
198
|
+
const raw = await readFile(artifactPath(rootPath), 'utf8');
|
|
199
|
+
const graph = JSON.parse(raw);
|
|
200
|
+
const overrides = await readOverrides(rootPath);
|
|
201
|
+
const entries = [];
|
|
202
|
+
const byKey = new Map();
|
|
203
|
+
for (const [domain, dom] of Object.entries(graph.domains ?? {})) {
|
|
204
|
+
for (const [reqId, req] of Object.entries(dom.requirements ?? {})) {
|
|
205
|
+
const key = `${domain}::${reqId}`;
|
|
206
|
+
const ov = overrides[key];
|
|
207
|
+
// Data-derived risk: any business rule whose serialized form names risk (the
|
|
208
|
+
// extraction harness's RESOLVED-or-RISK floor surfaces here at current fidelity).
|
|
209
|
+
const dataRisk = (req.business_rules ?? []).some((r) => RISK_RE.test(JSON.stringify(r)));
|
|
210
|
+
const risk = ov?.risk !== undefined ? ov.risk : dataRisk;
|
|
211
|
+
const riskSource = ov?.risk !== undefined ? 'operator' : dataRisk ? 'data' : null;
|
|
212
|
+
const title = ov?.title ?? req.title ?? reqId;
|
|
213
|
+
const statements = (req.business_rules ?? [])
|
|
214
|
+
.map((r) => {
|
|
215
|
+
const st = r.statement;
|
|
216
|
+
return typeof st === 'string' ? st.trim() : '';
|
|
217
|
+
})
|
|
218
|
+
.filter((st) => st !== '');
|
|
219
|
+
const firstComponent = (req.legacy_components ?? []).find((c) => typeof c === 'string');
|
|
220
|
+
const summary = {
|
|
221
|
+
key,
|
|
222
|
+
domain,
|
|
223
|
+
reqId,
|
|
224
|
+
title,
|
|
225
|
+
category: categoryOf(firstComponent ?? ''),
|
|
226
|
+
statement: statements[0] ?? '',
|
|
227
|
+
status: ov?.status ?? req.status ?? 'active',
|
|
228
|
+
risk,
|
|
229
|
+
riskSource,
|
|
230
|
+
edited: ov !== undefined,
|
|
231
|
+
};
|
|
232
|
+
// Statements are part of the haystack: searching the requirements means
|
|
233
|
+
// searching the actual rule text, not just titles and ids.
|
|
234
|
+
const haystack = `${reqId} ${domain} ${title} ${req.description ?? ''} ${statements.join(' ')} ${ov?.notes ?? ''}`.toLowerCase();
|
|
235
|
+
const entry = { summary, haystack, source: req };
|
|
236
|
+
entries.push(entry);
|
|
237
|
+
byKey.set(key, entry);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
const index = {
|
|
241
|
+
entries,
|
|
242
|
+
byKey,
|
|
243
|
+
sourceMtimeMs: artMtime,
|
|
244
|
+
fromStore: false,
|
|
245
|
+
overridesMtimeMs: ovMtime,
|
|
246
|
+
builtAtMs: Date.now(),
|
|
247
|
+
total: entries.length,
|
|
248
|
+
};
|
|
249
|
+
cache.set(rootPath, index);
|
|
250
|
+
return index;
|
|
251
|
+
}
|
|
252
|
+
/** Tokenized AND-match: every whitespace-separated term must appear in the haystack. */
|
|
253
|
+
export async function listRequirements(rootPath, query) {
|
|
254
|
+
const index = await buildIndex(rootPath);
|
|
255
|
+
if (index === null)
|
|
256
|
+
return null;
|
|
257
|
+
const terms = (query.q ?? '')
|
|
258
|
+
.toLowerCase()
|
|
259
|
+
.split(/\s+/)
|
|
260
|
+
.filter((t) => t.length > 0);
|
|
261
|
+
const domainFilter = query.domain?.toLowerCase();
|
|
262
|
+
const matched = [];
|
|
263
|
+
for (const e of index.entries) {
|
|
264
|
+
if (query.category !== undefined && e.summary.category !== query.category)
|
|
265
|
+
continue;
|
|
266
|
+
if (query.risk === 'risk' && !e.summary.risk)
|
|
267
|
+
continue;
|
|
268
|
+
if (query.risk === 'no-risk' && e.summary.risk)
|
|
269
|
+
continue;
|
|
270
|
+
if (domainFilter !== undefined && !e.summary.domain.toLowerCase().includes(domainFilter))
|
|
271
|
+
continue;
|
|
272
|
+
if (terms.length > 0 && !terms.every((t) => e.haystack.includes(t)))
|
|
273
|
+
continue;
|
|
274
|
+
matched.push(e.summary);
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
total: matched.length,
|
|
278
|
+
corpus: index.total,
|
|
279
|
+
offset: query.offset,
|
|
280
|
+
limit: query.limit,
|
|
281
|
+
items: matched.slice(query.offset, query.offset + query.limit),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
export async function getRequirement(rootPath, key) {
|
|
285
|
+
const index = await buildIndex(rootPath);
|
|
286
|
+
const entry = index?.byKey.get(key);
|
|
287
|
+
if (index === null || entry === undefined)
|
|
288
|
+
return null;
|
|
289
|
+
const overrides = await readOverrides(rootPath);
|
|
290
|
+
const ov = overrides[key];
|
|
291
|
+
const src = entry.source;
|
|
292
|
+
return {
|
|
293
|
+
...entry.summary,
|
|
294
|
+
description: src.description ?? '',
|
|
295
|
+
notes: ov?.notes ?? '',
|
|
296
|
+
sourceTitle: src.title ?? entry.summary.reqId,
|
|
297
|
+
ruleCount: (src.business_rules ?? []).length,
|
|
298
|
+
componentCount: (src.legacy_components ?? []).length,
|
|
299
|
+
validationCount: (src.validations ?? []).length,
|
|
300
|
+
errorPathCount: (src.error_paths ?? []).length,
|
|
301
|
+
businessRules: (src.business_rules ?? []).slice(0, 10),
|
|
302
|
+
legacyComponents: (src.legacy_components ?? []).slice(0, 10),
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
/** Merge a patch into the overrides sidecar (atomic write) and return the fresh detail. */
|
|
306
|
+
export async function patchRequirement(rootPath, key, patch) {
|
|
307
|
+
const index = await buildIndex(rootPath);
|
|
308
|
+
if (index === null || !index.byKey.has(key))
|
|
309
|
+
return null;
|
|
310
|
+
const overrides = await readOverrides(rootPath);
|
|
311
|
+
const next = { ...overrides[key] };
|
|
312
|
+
if (patch.title !== undefined)
|
|
313
|
+
next.title = patch.title;
|
|
314
|
+
if (patch.notes !== undefined)
|
|
315
|
+
next.notes = patch.notes;
|
|
316
|
+
if (patch.status !== undefined)
|
|
317
|
+
next.status = patch.status;
|
|
318
|
+
if (patch.risk !== undefined)
|
|
319
|
+
next.risk = patch.risk;
|
|
320
|
+
overrides[key] = next;
|
|
321
|
+
const path = overridesPath(rootPath);
|
|
322
|
+
await mkdir(dirname(path), { recursive: true });
|
|
323
|
+
// Collision-proof temp name: pid alone can collide for concurrent in-process
|
|
324
|
+
// patches; a monotonic per-process counter disambiguates.
|
|
325
|
+
const tmp = `${path}.tmp-${process.pid}-${++tmpSeq}`;
|
|
326
|
+
await writeFile(tmp, JSON.stringify(overrides, null, 2), 'utf8');
|
|
327
|
+
await rename(tmp, path);
|
|
328
|
+
cache.delete(rootPath); // next read rebuilds with the new overrides
|
|
329
|
+
return getRequirement(rootPath, key);
|
|
330
|
+
}
|
|
331
|
+
//# sourceMappingURL=requirements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requirements.js","sourceRoot":"","sources":["../../src/api/requirements.ts"],"names":[],"mappings":"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"}
|
package/dist/api/routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/api/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/api/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAU/C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAwGjD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI,CA6pBrG"}
|
package/dist/api/routes.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { listRequirements, getRequirement, patchRequirement } from './requirements.js';
|
|
2
3
|
import { randomUUID } from 'node:crypto';
|
|
3
4
|
import { readFileSync, existsSync } from 'node:fs';
|
|
4
5
|
import { promises as fsp } from 'node:fs';
|
|
@@ -458,6 +459,80 @@ export function registerRoutes(app, adapter, gateCache) {
|
|
|
458
459
|
// ── Repo code graph (via wicked-estate graph-view) ──────────────────────────
|
|
459
460
|
// Delegates to the estate CLI so the query goes through the proper service
|
|
460
461
|
// layer (store-seam aware, overlay edges included). Postgres-safe.
|
|
462
|
+
// ── Requirements management (server-side search over requirements_graph.json +
|
|
463
|
+
// operator overrides sidecar; see api/requirements.ts) ─────────────────────
|
|
464
|
+
const ReqQuerySchema = z.object({
|
|
465
|
+
q: z.string().optional(),
|
|
466
|
+
risk: z.enum(['risk', 'no-risk']).optional(),
|
|
467
|
+
category: z.enum(['functional', 'config-data']).optional(),
|
|
468
|
+
domain: z.string().optional(),
|
|
469
|
+
offset: z.coerce.number().int().min(0).default(0),
|
|
470
|
+
limit: z.coerce.number().int().min(1).max(200).default(50),
|
|
471
|
+
});
|
|
472
|
+
app.get(`${V}/repos/:id/requirements`, async (req, reply) => {
|
|
473
|
+
const { id } = req.params;
|
|
474
|
+
const repos = await adapter.listRepos();
|
|
475
|
+
const repo = repos.find((r) => r.id === id);
|
|
476
|
+
if (!repo)
|
|
477
|
+
return reply.code(404).send({ error: `Repo ${id} not found` });
|
|
478
|
+
const parsed = ReqQuerySchema.safeParse(req.query);
|
|
479
|
+
if (!parsed.success) {
|
|
480
|
+
return reply.code(400).send({ error: 'Invalid query', details: parsed.error.issues });
|
|
481
|
+
}
|
|
482
|
+
const page = await listRequirements(repo.root_path, parsed.data);
|
|
483
|
+
if (page === null) {
|
|
484
|
+
return reply.code(404).send({ error: 'requirements_graph.json not generated for this repo yet' });
|
|
485
|
+
}
|
|
486
|
+
return page;
|
|
487
|
+
});
|
|
488
|
+
app.get(`${V}/repos/:id/requirements/:key`, async (req, reply) => {
|
|
489
|
+
const { id, key } = req.params;
|
|
490
|
+
const repos = await adapter.listRepos();
|
|
491
|
+
const repo = repos.find((r) => r.id === id);
|
|
492
|
+
if (!repo)
|
|
493
|
+
return reply.code(404).send({ error: `Repo ${id} not found` });
|
|
494
|
+
let decoded;
|
|
495
|
+
try {
|
|
496
|
+
decoded = decodeURIComponent(key);
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
return reply.code(400).send({ error: 'Malformed requirement key encoding' });
|
|
500
|
+
}
|
|
501
|
+
const detail = await getRequirement(repo.root_path, decoded);
|
|
502
|
+
if (detail === null)
|
|
503
|
+
return reply.code(404).send({ error: 'Requirement not found' });
|
|
504
|
+
return { requirement: detail };
|
|
505
|
+
});
|
|
506
|
+
const ReqPatchSchema = z
|
|
507
|
+
.object({
|
|
508
|
+
title: z.string().min(1).max(500).optional(),
|
|
509
|
+
notes: z.string().max(5000).optional(),
|
|
510
|
+
status: z.enum(['active', 'deprecated', 'review']).optional(),
|
|
511
|
+
risk: z.boolean().optional(),
|
|
512
|
+
})
|
|
513
|
+
.refine((b) => Object.keys(b).length > 0, { message: 'empty patch' });
|
|
514
|
+
app.patch(`${V}/repos/:id/requirements/:key`, async (req, reply) => {
|
|
515
|
+
const { id, key } = req.params;
|
|
516
|
+
const repos = await adapter.listRepos();
|
|
517
|
+
const repo = repos.find((r) => r.id === id);
|
|
518
|
+
if (!repo)
|
|
519
|
+
return reply.code(404).send({ error: `Repo ${id} not found` });
|
|
520
|
+
const parsed = ReqPatchSchema.safeParse(req.body);
|
|
521
|
+
if (!parsed.success) {
|
|
522
|
+
return reply.code(400).send({ error: 'Invalid patch', details: parsed.error.issues });
|
|
523
|
+
}
|
|
524
|
+
let decodedKey;
|
|
525
|
+
try {
|
|
526
|
+
decodedKey = decodeURIComponent(key);
|
|
527
|
+
}
|
|
528
|
+
catch {
|
|
529
|
+
return reply.code(400).send({ error: 'Malformed requirement key encoding' });
|
|
530
|
+
}
|
|
531
|
+
const detail = await patchRequirement(repo.root_path, decodedKey, parsed.data);
|
|
532
|
+
if (detail === null)
|
|
533
|
+
return reply.code(404).send({ error: 'Requirement not found' });
|
|
534
|
+
return { requirement: detail };
|
|
535
|
+
});
|
|
461
536
|
app.get(`${V}/repos/:id/graph`, async (req, reply) => {
|
|
462
537
|
const { id } = req.params;
|
|
463
538
|
const repos = await adapter.listRepos();
|
|
@@ -470,8 +545,21 @@ export function registerRoutes(app, adapter, gateCache) {
|
|
|
470
545
|
}
|
|
471
546
|
try {
|
|
472
547
|
const settings = await adapter.getSettings();
|
|
473
|
-
const
|
|
474
|
-
const
|
|
548
|
+
const q = req.query;
|
|
549
|
+
const parsedLimit = q.limit !== undefined ? Number.parseInt(q.limit, 10) : NaN;
|
|
550
|
+
const nodeLimit = String(Number.isFinite(parsedLimit) && parsedLimit > 0 && parsedLimit <= 1000
|
|
551
|
+
? parsedLimit
|
|
552
|
+
: settings.graphNodeLimit);
|
|
553
|
+
const args = ['graph-view', '--limit', nodeLimit, '--db', dbPath];
|
|
554
|
+
// FOCUS (ego-graph) mode: seed the slice from one symbol and expand its
|
|
555
|
+
// neighbourhood — the navigation primitive (estate graph-view --focus).
|
|
556
|
+
if (q.focus !== undefined && q.focus.trim() !== '') {
|
|
557
|
+
args.push('--focus', q.focus.trim());
|
|
558
|
+
}
|
|
559
|
+
const { stdout } = await execFileAsync('wicked-estate', args, {
|
|
560
|
+
timeout: 30_000,
|
|
561
|
+
cwd: repo.root_path,
|
|
562
|
+
});
|
|
475
563
|
const raw = JSON.parse(stdout);
|
|
476
564
|
const fileCount = new Set(raw.nodes.map((n) => n.file)).size;
|
|
477
565
|
return reply.send({
|
|
@@ -487,6 +575,31 @@ export function registerRoutes(app, adapter, gateCache) {
|
|
|
487
575
|
}
|
|
488
576
|
});
|
|
489
577
|
// ── Git history (last 20 commits via git log) ─────────────────────────────
|
|
578
|
+
// ── Blast radius for a symbol (via wicked-estate blast-radius --json).
|
|
579
|
+
// Carries the honesty contract through: dependents PLUS the unresolved-call
|
|
580
|
+
// count — an empty dependents list must never read as "safe to change".
|
|
581
|
+
app.get(`${V}/repos/:id/graph/blast-radius`, async (req, reply) => {
|
|
582
|
+
const { id } = req.params;
|
|
583
|
+
const q = req.query;
|
|
584
|
+
if (q.name === undefined || q.name.trim() === '') {
|
|
585
|
+
return reply.code(400).send({ error: 'name query parameter required' });
|
|
586
|
+
}
|
|
587
|
+
const repos = await adapter.listRepos();
|
|
588
|
+
const repo = repos.find((r) => r.id === id);
|
|
589
|
+
if (!repo)
|
|
590
|
+
return reply.code(404).send({ error: `Repo ${id} not found` });
|
|
591
|
+
const dbPath = join(repo.root_path, '.codegraph', 'estate.db');
|
|
592
|
+
if (!existsSync(dbPath)) {
|
|
593
|
+
return reply.code(404).send({ error: 'Code graph not built for this repo yet' });
|
|
594
|
+
}
|
|
595
|
+
try {
|
|
596
|
+
const { stdout } = await execFileAsync('wicked-estate', ['blast-radius', q.name.trim(), '--db', dbPath, '--json'], { timeout: 30_000, cwd: repo.root_path });
|
|
597
|
+
return reply.send(JSON.parse(stdout));
|
|
598
|
+
}
|
|
599
|
+
catch (err) {
|
|
600
|
+
return reply.code(500).send({ error: message(err) });
|
|
601
|
+
}
|
|
602
|
+
});
|
|
490
603
|
app.get(`${V}/repos/:id/git-history`, async (req, reply) => {
|
|
491
604
|
const { id } = req.params;
|
|
492
605
|
const repos = await adapter.listRepos();
|