kibi-cli 0.6.2 → 0.8.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 (49) hide show
  1. package/dist/commands/check.d.ts.map +1 -1
  2. package/dist/commands/check.js +18 -0
  3. package/dist/commands/init-helpers.d.ts +1 -0
  4. package/dist/commands/init-helpers.d.ts.map +1 -1
  5. package/dist/commands/init-helpers.js +39 -6
  6. package/dist/commands/init.d.ts.map +1 -1
  7. package/dist/commands/init.js +2 -1
  8. package/dist/commands/sync/manifest.d.ts +1 -0
  9. package/dist/commands/sync/manifest.d.ts.map +1 -1
  10. package/dist/commands/sync/manifest.js +1 -1
  11. package/dist/commands/sync/persistence.d.ts.map +1 -1
  12. package/dist/commands/sync/persistence.js +4 -2
  13. package/dist/commands/sync/staging.d.ts +3 -0
  14. package/dist/commands/sync/staging.d.ts.map +1 -1
  15. package/dist/commands/sync/staging.js +58 -1
  16. package/dist/commands/sync.d.ts +18 -1
  17. package/dist/commands/sync.d.ts.map +1 -1
  18. package/dist/commands/sync.js +19 -4
  19. package/dist/extractors/markdown.d.ts +2 -0
  20. package/dist/extractors/markdown.d.ts.map +1 -1
  21. package/dist/extractors/symbols-coordinator.d.ts +34 -0
  22. package/dist/extractors/symbols-coordinator.d.ts.map +1 -1
  23. package/dist/extractors/symbols-coordinator.js +70 -1
  24. package/dist/extractors/symbols-ts.d.ts +2 -0
  25. package/dist/extractors/symbols-ts.d.ts.map +1 -1
  26. package/dist/extractors/symbols-ts.js +108 -1
  27. package/dist/public/brief-config.d.ts +4 -0
  28. package/dist/public/brief-config.d.ts.map +1 -0
  29. package/dist/public/brief-config.js +21 -0
  30. package/dist/public/extractors/symbols-coordinator.d.ts +1 -1
  31. package/dist/public/extractors/symbols-coordinator.d.ts.map +1 -1
  32. package/dist/public/extractors/symbols-coordinator.js +1 -1
  33. package/dist/public/operational-artifacts.d.ts +2 -0
  34. package/dist/public/operational-artifacts.d.ts.map +1 -0
  35. package/dist/public/operational-artifacts.js +4 -0
  36. package/dist/search-ranking.d.ts.map +1 -1
  37. package/dist/search-ranking.js +132 -25
  38. package/dist/utils/config.d.ts +18 -3
  39. package/dist/utils/config.d.ts.map +1 -1
  40. package/dist/utils/config.js +39 -0
  41. package/dist/utils/rule-registry.d.ts.map +1 -1
  42. package/dist/utils/rule-registry.js +6 -0
  43. package/package.json +10 -2
  44. package/schema/config.json +73 -0
  45. package/schema/entities.pl +1 -0
  46. package/schema/relationships.pl +4 -0
  47. package/src/public/brief-config.ts +25 -0
  48. package/src/public/extractors/symbols-coordinator.ts +7 -0
  49. package/src/public/operational-artifacts.ts +5 -0
@@ -17,7 +17,7 @@
17
17
  */
18
18
  import { access, readFile } from "node:fs/promises";
19
19
  import * as path from "node:path";
20
- import { Project, } from "ts-morph";
20
+ import { Project, ScriptKind, } from "ts-morph";
21
21
  const SUPPORTED_SOURCE_EXTENSIONS = new Set([
22
22
  ".ts",
23
23
  ".tsx",
@@ -28,6 +28,35 @@ const SUPPORTED_SOURCE_EXTENSIONS = new Set([
28
28
  ".mjs",
29
29
  ".cjs",
30
30
  ]);
31
+ // implements REQ-001
32
+ export function createTsMorphSourceAnalysisProvider() {
33
+ const project = new Project({
34
+ skipAddingFilesFromTsConfig: true,
35
+ });
36
+ return {
37
+ id: "ts-morph",
38
+ supportsFile(filePath) {
39
+ return SUPPORTED_SOURCE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
40
+ },
41
+ analyzeText(filePath, content) {
42
+ const sourceFile = project.createSourceFile(filePath, content, {
43
+ overwrite: true,
44
+ scriptKind: chooseScriptKind(filePath),
45
+ });
46
+ return {
47
+ sourceFile: filePath,
48
+ language: inferSourceLanguage(filePath),
49
+ providerId: "ts-morph",
50
+ module: {
51
+ title: inferModuleTitle(filePath),
52
+ language: inferSourceLanguage(filePath),
53
+ analysisMode: "parser",
54
+ },
55
+ symbols: collectSourceSymbols(sourceFile),
56
+ };
57
+ },
58
+ };
59
+ }
31
60
  export async function enrichSymbolCoordinatesWithTsMorph(entries, workspaceRoot) {
32
61
  // implements REQ-vscode-traceability
33
62
  const project = new Project({
@@ -142,6 +171,84 @@ async function enrichWithTextFallbackInternal(entry, absolutePath) {
142
171
  return entry;
143
172
  }
144
173
  }
174
+ function collectSourceSymbols(sourceFile) {
175
+ const symbols = [];
176
+ for (const decl of sourceFile.getFunctions()) {
177
+ if (!decl.isExported())
178
+ continue;
179
+ symbols.push(toSourceSymbolAnalysis(sourceFile, decl.getName() ?? "<anonymous>", "function", decl.getNameNode() ?? decl, decl, `${decl.getFullText()}\n${decl
180
+ .getJsDocs()
181
+ .map((doc) => doc.getFullText())
182
+ .join("\n")}`));
183
+ }
184
+ for (const decl of sourceFile.getClasses()) {
185
+ if (!decl.isExported())
186
+ continue;
187
+ symbols.push(toSourceSymbolAnalysis(sourceFile, decl.getName() ?? "<anonymous>", "class", decl.getNameNode() ?? decl, decl, `${decl.getText()}\n${decl
188
+ .getJsDocs()
189
+ .map((doc) => doc.getFullText())
190
+ .join("\n")}`));
191
+ }
192
+ for (const decl of sourceFile.getInterfaces()) {
193
+ if (!decl.isExported())
194
+ continue;
195
+ symbols.push(toSourceSymbolAnalysis(sourceFile, decl.getName() ?? "<anonymous>", "interface", decl.getNameNode() ?? decl, decl, decl.getText()));
196
+ }
197
+ for (const decl of sourceFile.getTypeAliases()) {
198
+ if (!decl.isExported())
199
+ continue;
200
+ symbols.push(toSourceSymbolAnalysis(sourceFile, decl.getName() ?? "<anonymous>", "type", decl.getNameNode() ?? decl, decl, decl.getText()));
201
+ }
202
+ for (const decl of sourceFile.getEnums()) {
203
+ if (!decl.isExported())
204
+ continue;
205
+ symbols.push(toSourceSymbolAnalysis(sourceFile, decl.getName() ?? "<anonymous>", "enum", decl.getNameNode() ?? decl, decl, decl.getText()));
206
+ }
207
+ for (const statement of sourceFile.getVariableStatements()) {
208
+ if (!statement.isExported())
209
+ continue;
210
+ for (const declaration of statement.getDeclarations()) {
211
+ symbols.push(toSourceSymbolAnalysis(sourceFile, declaration.getName(), "variable", declaration.getNameNode() ?? declaration, declaration, declaration.getText()));
212
+ }
213
+ }
214
+ return symbols;
215
+ }
216
+ function toSourceSymbolAnalysis(sourceFile, name, kind, startNode, endNode, directiveText) {
217
+ const start = sourceFile.getLineAndColumnAtPos(startNode.getStart());
218
+ const end = sourceFile.getLineAndColumnAtPos(endNode.getEnd());
219
+ return {
220
+ name,
221
+ kind,
222
+ startLine: start.line,
223
+ startColumn: Math.max(0, start.column - 1),
224
+ endLine: end.line,
225
+ endColumn: Math.max(0, end.column - 1),
226
+ directiveText,
227
+ };
228
+ }
229
+ function chooseScriptKind(filePath) {
230
+ const lower = filePath.toLowerCase();
231
+ if (lower.endsWith(".tsx"))
232
+ return ScriptKind.TSX;
233
+ if (lower.endsWith(".ts") || lower.endsWith(".mts") || lower.endsWith(".cts")) {
234
+ return ScriptKind.TS;
235
+ }
236
+ if (lower.endsWith(".jsx"))
237
+ return ScriptKind.JSX;
238
+ return ScriptKind.JS;
239
+ }
240
+ function inferSourceLanguage(filePath) {
241
+ const extension = path.extname(filePath).toLowerCase();
242
+ if ([".ts", ".tsx", ".mts", ".cts"].includes(extension)) {
243
+ return "typescript";
244
+ }
245
+ return "javascript";
246
+ }
247
+ function inferModuleTitle(filePath) {
248
+ const extension = path.extname(filePath);
249
+ const basename = path.basename(filePath, extension);
250
+ return basename.length > 0 ? basename : path.basename(filePath);
251
+ }
145
252
  function findNamedDeclaration(sourceFile, title) {
146
253
  const candidates = [];
147
254
  for (const decl of sourceFile.getFunctions()) {
@@ -0,0 +1,4 @@
1
+ import { type BriefsConfig } from "../utils/config.js";
2
+ export type { BriefsConfig } from "../utils/config.js";
3
+ export declare function loadBriefConfig(cwd?: string): BriefsConfig;
4
+ //# sourceMappingURL=brief-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"brief-config.d.ts","sourceRoot":"","sources":["../../src/public/brief-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEnE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,wBAAgB,eAAe,CAAC,GAAG,GAAE,MAAsB,GAAG,YAAY,CAoBzE"}
@@ -0,0 +1,21 @@
1
+ import { loadConfig } from "../utils/config.js";
2
+ export function loadBriefConfig(cwd = process.cwd()) {
3
+ const briefs = loadConfig(cwd).briefs;
4
+ return {
5
+ enabled: briefs?.enabled ?? true,
6
+ retention: {
7
+ maxPerBranch: briefs?.retention?.maxPerBranch ?? 200,
8
+ maxAgeDays: briefs?.retention?.maxAgeDays ?? 14,
9
+ keepUnread: briefs?.retention?.keepUnread ?? true,
10
+ },
11
+ channels: {
12
+ vscode: briefs?.channels?.vscode ?? true,
13
+ tui: briefs?.channels?.tui ?? true,
14
+ },
15
+ tui: {
16
+ toast: briefs?.tui?.toast ?? true,
17
+ appendPrompt: briefs?.tui?.appendPrompt ?? true,
18
+ idleDelayMs: briefs?.tui?.idleDelayMs ?? 1500,
19
+ },
20
+ };
21
+ }
@@ -1,2 +1,2 @@
1
- export { enrichSymbolCoordinates, type ManifestSymbolEntry, } from "../../extractors/symbols-coordinator.js";
1
+ export { analyzeSourceText, enrichSymbolCoordinates, type ManifestSymbolEntry, type AnalyzeSourceTextOptions, type SourceAnalysisProvider, type SourceAnalysisResult, type SourceModuleAnalysis, type SourceSymbolAnalysis, type SourceSymbolKind, } from "../../extractors/symbols-coordinator.js";
2
2
  //# sourceMappingURL=symbols-coordinator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"symbols-coordinator.d.ts","sourceRoot":"","sources":["../../../src/public/extractors/symbols-coordinator.ts"],"names":[],"mappings":"AAkBA,OAAO,EACL,uBAAuB,EACvB,KAAK,mBAAmB,GACzB,MAAM,yCAAyC,CAAC"}
1
+ {"version":3,"file":"symbols-coordinator.d.ts","sourceRoot":"","sources":["../../../src/public/extractors/symbols-coordinator.ts"],"names":[],"mappings":"AAkBA,OAAO,EACL,iBAAiB,EACjB,uBAAuB,EACvB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,GACtB,MAAM,yCAAyC,CAAC"}
@@ -15,4 +15,4 @@
15
15
  You should have received a copy of the GNU Affero General Public License
16
16
  along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
  */
18
- export { enrichSymbolCoordinates, } from "../../extractors/symbols-coordinator.js";
18
+ export { analyzeSourceText, enrichSymbolCoordinates, } from "../../extractors/symbols-coordinator.js";
@@ -0,0 +1,2 @@
1
+ export declare function isOperationalArtifactPath(pathLike: string): boolean;
2
+ //# sourceMappingURL=operational-artifacts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operational-artifacts.d.ts","sourceRoot":"","sources":["../../src/public/operational-artifacts.ts"],"names":[],"mappings":"AAAA,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAInE"}
@@ -0,0 +1,4 @@
1
+ export function isOperationalArtifactPath(pathLike) {
2
+ const normalized = pathLike.replaceAll("\\", "/");
3
+ return /(^|\/)\.sisyphus\//.test(normalized);
4
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"search-ranking.d.ts","sourceRoot":"","sources":["../src/search-ranking.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAGD,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACnC,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,WAAW,EAAE,CAAC,CAyBxB;AA+FD,wBAAsB,gBAAgB,CAEpC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA6BxB"}
1
+ {"version":3,"file":"search-ranking.d.ts","sourceRoot":"","sources":["../src/search-ranking.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAgDD,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACnC,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,WAAW,EAAE,CAAC,CA8BxB;AAmGD,wBAAsB,gBAAgB,CAEpC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA6BxB"}
@@ -1,10 +1,47 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
+ const SEARCH_STOP_WORDS = new Set([
4
+ "to",
5
+ "in",
6
+ "out",
7
+ "log",
8
+ "logged",
9
+ "unable",
10
+ "the",
11
+ "a",
12
+ "an",
13
+ "is",
14
+ "are",
15
+ "was",
16
+ "were",
17
+ "be",
18
+ "been",
19
+ "being",
20
+ "have",
21
+ "has",
22
+ "had",
23
+ "do",
24
+ "does",
25
+ "did",
26
+ "will",
27
+ "would",
28
+ "could",
29
+ "should",
30
+ "may",
31
+ "might",
32
+ "shall",
33
+ "can",
34
+ "not",
35
+ ]);
3
36
  // implements REQ-mcp-search-discovery, REQ-002, REQ-003
4
37
  export async function rankEntities(entities, query, workspaceRoot) {
38
+ const queryContext = buildSearchQueryContext(query);
39
+ if (!queryContext.rawTrimmedQuery || queryContext.signalTokens.length === 0) {
40
+ return [];
41
+ }
5
42
  const matches = [];
6
43
  for (const entity of entities) {
7
- const match = await rankEntity(entity, query, workspaceRoot);
44
+ const match = await rankEntity(entity, queryContext, workspaceRoot);
8
45
  if (match) {
9
46
  matches.push(match);
10
47
  }
@@ -22,9 +59,8 @@ export async function rankEntities(entities, query, workspaceRoot) {
22
59
  });
23
60
  return matches;
24
61
  }
25
- async function rankEntity(entity, query, workspaceRoot) {
26
- const normalizedQuery = normalize(query);
27
- const tokens = normalizedQuery.split(/\s+/).filter(Boolean);
62
+ // implements REQ-mcp-search-discovery
63
+ async function rankEntity(entity, queryContext, workspaceRoot) {
28
64
  const reasons = [];
29
65
  let score = 0;
30
66
  const id = String(entity.id ?? "");
@@ -37,55 +73,56 @@ async function rankEntity(entity, query, workspaceRoot) {
37
73
  const tags = Array.isArray(entity.tags)
38
74
  ? entity.tags.map((tag) => String(tag))
39
75
  : [];
40
- const normalizedTitle = normalize(title);
41
- const normalizedId = normalize(id);
42
- if (normalizedTitle === normalizedQuery) {
76
+ const titleForms = buildSearchTextForms(title);
77
+ const idForms = buildSearchTextForms(id);
78
+ if (isExactSearchMatch(titleForms, queryContext.phrase)) {
43
79
  score += 100;
44
80
  reasons.push("exact title match");
45
81
  }
46
- else if (normalizedTitle.includes(normalizedQuery)) {
82
+ else if (isPhraseSearchMatch(titleForms, queryContext.phrase)) {
47
83
  score += 60;
48
84
  reasons.push("title phrase match");
49
85
  }
50
- if (normalizedId === normalizedQuery) {
86
+ if (isExactSearchMatch(idForms, queryContext.phrase)) {
51
87
  score += 90;
52
88
  reasons.push("exact ID match");
53
89
  }
54
- else if (normalizedId.includes(normalizedQuery)) {
90
+ else if (isPhraseSearchMatch(idForms, queryContext.phrase)) {
55
91
  score += 55;
56
92
  reasons.push("ID match");
57
93
  }
58
94
  const metadataFields = [type, source, owner, priority, severity];
59
- const metadataMatched = metadataFields.some((field) => normalize(field).includes(normalizedQuery));
95
+ const metadataMatched = metadataFields.some((field) => isPhraseSearchMatch(buildSearchTextForms(field), queryContext.phrase));
60
96
  if (metadataMatched) {
61
97
  score += 20;
62
98
  reasons.push("metadata match");
63
99
  }
64
- const matchingTags = tags.filter((tag) => normalize(tag).includes(normalizedQuery));
100
+ const matchingTags = tags.filter((tag) => isPhraseSearchMatch(buildSearchTextForms(tag), queryContext.phrase));
65
101
  if (matchingTags.length > 0) {
66
102
  score += 30;
67
103
  reasons.push("tag match");
68
104
  }
69
- const titleTokenMatches = countTokenMatches(normalizedTitle, tokens);
105
+ const titleTokenMatches = countTokenMatches(titleForms, queryContext.signalTokens);
70
106
  if (titleTokenMatches > 0) {
71
107
  score += titleTokenMatches * 8;
72
108
  reasons.push("title token coverage");
73
109
  }
74
- const bodyText = await loadMarkdownBody(source, workspaceRoot);
110
+ const bodyText = (await loadMarkdownBody(source, workspaceRoot)) ??
111
+ getInlineBodyText(entity);
75
112
  let snippet;
76
113
  if (bodyText) {
77
- const normalizedBody = normalize(bodyText);
78
- if (normalizedBody.includes(normalizedQuery)) {
114
+ const bodyForms = buildSearchTextForms(bodyText);
115
+ if (isPhraseSearchMatch(bodyForms, queryContext.phrase)) {
79
116
  score += 15;
80
117
  reasons.push("markdown body match");
81
- snippet = buildSnippet(bodyText, query);
118
+ snippet = buildSnippet(bodyText, queryContext.phrase);
82
119
  }
83
120
  else {
84
- const bodyTokenMatches = countTokenMatches(normalizedBody, tokens);
121
+ const bodyTokenMatches = countTokenMatches(bodyForms, queryContext.signalTokens);
85
122
  if (bodyTokenMatches > 0) {
86
123
  score += bodyTokenMatches * 3;
87
124
  reasons.push("markdown body token coverage");
88
- snippet = buildSnippet(bodyText, query);
125
+ snippet = buildSnippet(bodyText, queryContext.phrase);
89
126
  }
90
127
  }
91
128
  }
@@ -126,6 +163,7 @@ source, workspaceRoot) {
126
163
  return null;
127
164
  }
128
165
  }
166
+ // implements REQ-mcp-search-discovery
129
167
  function stripFrontmatter(content) {
130
168
  const trimmedContent = content.trimStart();
131
169
  if (!trimmedContent.startsWith("---")) {
@@ -143,19 +181,88 @@ function stripFrontmatter(content) {
143
181
  }
144
182
  return trimmedContent.slice(match.index + match[0].length);
145
183
  }
146
- function normalize(value) {
147
- return value.trim().toLowerCase();
184
+ // implements REQ-mcp-search-discovery
185
+ function buildSearchQueryContext(query) {
186
+ return {
187
+ phrase: buildSearchTextForms(query),
188
+ signalTokens: tokenizeSignalTerms(query),
189
+ rawTrimmedQuery: query.trim(),
190
+ };
191
+ }
192
+ // implements REQ-mcp-search-discovery
193
+ function buildSearchTextForms(value) {
194
+ const normalized = normalizeSearchText(value);
195
+ return {
196
+ normalized,
197
+ compact: normalized.replace(/\s+/g, ""),
198
+ };
199
+ }
200
+ // implements REQ-mcp-search-discovery
201
+ function normalizeSearchText(value) {
202
+ return value
203
+ .trim()
204
+ .toLowerCase()
205
+ .replace(/[-_]+/g, " ")
206
+ .replace(/[^a-z0-9\s]+/g, " ")
207
+ .trim()
208
+ .split(/\s+/)
209
+ .filter(Boolean)
210
+ .map(singularizeSimplePlural)
211
+ .join(" ");
212
+ }
213
+ // implements REQ-mcp-search-discovery
214
+ function tokenizeSignalTerms(value) {
215
+ return Array.from(new Set(normalizeSearchText(value)
216
+ .split(/\s+/)
217
+ .filter((token) => token && !SEARCH_STOP_WORDS.has(token))));
148
218
  }
219
+ // implements REQ-mcp-search-discovery
220
+ function singularizeSimplePlural(token) {
221
+ if (token.length <= 4 ||
222
+ !token.endsWith("s") ||
223
+ token.endsWith("ss") ||
224
+ token.endsWith("us") ||
225
+ token.endsWith("is")) {
226
+ return token;
227
+ }
228
+ return token.slice(0, -1);
229
+ }
230
+ // implements REQ-mcp-search-discovery
231
+ function isExactSearchMatch(haystack, needle) {
232
+ return (haystack.normalized === needle.normalized ||
233
+ (needle.compact !== "" && haystack.compact === needle.compact));
234
+ }
235
+ // implements REQ-mcp-search-discovery
236
+ function isPhraseSearchMatch(haystack, needle) {
237
+ return (haystack.normalized.includes(needle.normalized) ||
238
+ (needle.compact !== "" && haystack.compact.includes(needle.compact)));
239
+ }
240
+ // implements REQ-mcp-search-discovery
149
241
  function countTokenMatches(haystack, tokens) {
150
- return tokens.filter((token) => haystack.includes(token)).length;
242
+ return tokens.filter((token) => haystack.normalized.includes(token) || haystack.compact.includes(token)).length;
243
+ }
244
+ // implements REQ-mcp-search-discovery
245
+ function getInlineBodyText(entity) {
246
+ const candidates = [
247
+ entity.body,
248
+ entity.markdownBody,
249
+ entity.markdown_body,
250
+ entity.content,
251
+ ];
252
+ for (const candidate of candidates) {
253
+ if (typeof candidate === "string" && candidate.trim() !== "") {
254
+ return candidate;
255
+ }
256
+ }
257
+ return null;
151
258
  }
152
- function buildSnippet(bodyText, query) {
259
+ // implements REQ-mcp-search-discovery
260
+ function buildSnippet(bodyText, queryForms) {
153
261
  const lines = bodyText
154
262
  .split(/\r?\n/)
155
263
  .map((line) => line.trim())
156
264
  .filter(Boolean);
157
- const normalizedQuery = normalize(query);
158
- const matchedLine = lines.find((line) => normalize(line).includes(normalizedQuery)) ?? lines[0];
265
+ const matchedLine = lines.find((line) => isPhraseSearchMatch(buildSearchTextForms(line), queryForms)) ?? lines[0];
159
266
  if (!matchedLine) {
160
267
  return undefined;
161
268
  }
@@ -12,12 +12,30 @@ export interface KbConfigPaths {
12
12
  facts?: string;
13
13
  symbols?: string;
14
14
  }
15
+ export interface BriefsConfig {
16
+ enabled: boolean;
17
+ retention?: {
18
+ maxPerBranch?: number;
19
+ maxAgeDays?: number;
20
+ keepUnread?: boolean;
21
+ };
22
+ channels: {
23
+ vscode: boolean;
24
+ tui: boolean;
25
+ };
26
+ tui: {
27
+ toast: boolean;
28
+ appendPrompt: boolean;
29
+ idleDelayMs?: number;
30
+ };
31
+ }
15
32
  /**
16
33
  * Shared configuration for Kibi.
17
34
  * Stored in .kb/config.json
18
35
  */
19
36
  export interface KbConfig {
20
37
  paths: KbConfigPaths;
38
+ briefs?: BriefsConfig;
21
39
  /**
22
40
  * @deprecated defaultBranch is deprecated. Branch lifecycle now follows git naturally
23
41
  * without requiring a configured default. This field is ignored but kept for compatibility.
@@ -26,9 +44,6 @@ export interface KbConfig {
26
44
  checks?: ChecksConfig;
27
45
  }
28
46
  export type { ChecksConfig, SymbolTraceabilityOptions };
29
- /**
30
- * Default configuration values for new repositories.
31
- */
32
47
  export declare const DEFAULT_CONFIG: KbConfig & {
33
48
  $schema: string;
34
49
  };
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAoBA,OAAO,EACL,KAAK,YAAY,EAEjB,KAAK,yBAAyB,EAC/B,MAAM,oBAAoB,CAAC;AAE5B;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,aAAa,CAAC;IACrB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,YAAY,EAAE,YAAY,EAAE,yBAAyB,EAAE,CAAC;AAExD;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,QAAQ,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAcxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,aAShC,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAsB,GAAG,QAAQ,CAoChE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAsB,GAAG,QAAQ,CAoCpE"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAoBA,OAAO,EACL,KAAK,YAAY,EAEjB,KAAK,yBAAyB,EAC/B,MAAM,oBAAoB,CAAC;AAE5B;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE;QACV,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,OAAO,CAAC;KACtB,CAAC;IACF,QAAQ,EAAE;QACR,MAAM,EAAE,OAAO,CAAC;QAChB,GAAG,EAAE,OAAO,CAAC;KACd,CAAC;IACF,GAAG,EAAE;QACH,KAAK,EAAE,OAAO,CAAC;QACf,YAAY,EAAE,OAAO,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,aAAa,CAAC;IACrB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,YAAY,EAAE,YAAY,EAAE,yBAAyB,EAAE,CAAC;AAwBxD,eAAO,MAAM,cAAc,EAAE,QAAQ,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAexD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,aAShC,CAAC;AAqBF;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAsB,GAAG,QAAQ,CAqChE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAsB,GAAG,QAAQ,CAqCpE"}
@@ -21,6 +21,24 @@ import { DEFAULT_CHECKS_CONFIG, } from "./rule-registry.js";
21
21
  /**
22
22
  * Default configuration values for new repositories.
23
23
  */
24
+ const DEFAULT_BRIEFS_CONFIG = {
25
+ enabled: true,
26
+ retention: {
27
+ maxPerBranch: 200,
28
+ maxAgeDays: 14,
29
+ keepUnread: true,
30
+ },
31
+ channels: {
32
+ vscode: true,
33
+ tui: true,
34
+ },
35
+ tui: {
36
+ toast: true,
37
+ appendPrompt: true,
38
+ idleDelayMs: 1500,
39
+ },
40
+ };
41
+ // implements REQ-003
24
42
  export const DEFAULT_CONFIG = {
25
43
  $schema: "https://raw.githubusercontent.com/Looted/kibi/master/packages/cli/schema/config.json",
26
44
  paths: {
@@ -33,6 +51,7 @@ export const DEFAULT_CONFIG = {
33
51
  facts: "documentation/facts",
34
52
  symbols: "documentation/symbols.yaml",
35
53
  },
54
+ briefs: DEFAULT_BRIEFS_CONFIG,
36
55
  checks: DEFAULT_CHECKS_CONFIG,
37
56
  };
38
57
  /**
@@ -48,6 +67,24 @@ export const DEFAULT_SYNC_PATHS = {
48
67
  facts: "facts/**/*.md",
49
68
  symbols: "symbols.yaml",
50
69
  };
70
+ function mergeBriefsConfig(userBriefs) {
71
+ return {
72
+ ...DEFAULT_BRIEFS_CONFIG,
73
+ ...userBriefs,
74
+ channels: {
75
+ ...DEFAULT_BRIEFS_CONFIG.channels,
76
+ ...userBriefs?.channels,
77
+ },
78
+ tui: {
79
+ ...DEFAULT_BRIEFS_CONFIG.tui,
80
+ ...userBriefs?.tui,
81
+ },
82
+ retention: {
83
+ ...DEFAULT_BRIEFS_CONFIG.retention,
84
+ ...userBriefs?.retention,
85
+ },
86
+ };
87
+ }
51
88
  /**
52
89
  * Load and parse the Kibi configuration from .kb/config.json.
53
90
  * Falls back to DEFAULT_CONFIG if the file doesn't exist or is invalid.
@@ -74,6 +111,7 @@ export function loadConfig(cwd = process.cwd()) {
74
111
  ...DEFAULT_CONFIG.paths,
75
112
  ...userConfig.paths,
76
113
  },
114
+ briefs: mergeBriefsConfig(userConfig.briefs),
77
115
  ...(userConfig.defaultBranch !== undefined
78
116
  ? { defaultBranch: userConfig.defaultBranch }
79
117
  : {}),
@@ -118,6 +156,7 @@ export function loadSyncConfig(cwd = process.cwd()) {
118
156
  ...DEFAULT_SYNC_PATHS,
119
157
  ...userConfig.paths,
120
158
  },
159
+ briefs: mergeBriefsConfig(userConfig.briefs),
121
160
  ...(userConfig.defaultBranch !== undefined
122
161
  ? { defaultBranch: userConfig.defaultBranch }
123
162
  : {}),
@@ -1 +1 @@
1
- {"version":3,"file":"rule-registry.d.ts","sourceRoot":"","sources":["../../src/utils/rule-registry.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AAEH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,UAAU,GAAG,WAAW,GAAG,WAAW,GAAG,cAAc,CAAC;CACnE;AAED,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,mCAAmC;AACnC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,kBAAkB,EAAE,yBAAyB,CAAC;CAC/C;AAED;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAE,SAAS,cAAc,EA8DjC,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,UAAU,aAAoC,CAAC;AAE5D;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,YAKnC,CAAC;AAEF;;;;;GAKG;AAEH,wBAAgB,iBAAiB,CAC/B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,QAAQ,CAAC,EAAE,MAAM,GAChB,GAAG,CAAC,MAAM,CAAC,CAsBb;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK5D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAC9B,YAAY,CAWd"}
1
+ {"version":3,"file":"rule-registry.d.ts","sourceRoot":"","sources":["../../src/utils/rule-registry.ts"],"names":[],"mappings":"AAkBA;;;GAGG;AAEH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;IACxB,QAAQ,EAAE,UAAU,GAAG,WAAW,GAAG,WAAW,GAAG,cAAc,CAAC;CACnE;AAED,MAAM,WAAW,yBAAyB;IACxC,UAAU,EAAE,OAAO,CAAC;CACrB;AAED,mCAAmC;AACnC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,kBAAkB,EAAE,yBAAyB,CAAC;CAC/C;AAED;;;GAGG;AACH,eAAO,MAAM,KAAK,EAAE,SAAS,cAAc,EAqEjC,CAAC;AAEX;;GAEG;AACH,eAAO,MAAM,UAAU,aAAoC,CAAC;AAE5D;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,YAKnC,CAAC;AAEF;;;;;GAKG;AAEH,wBAAgB,iBAAiB,CAC/B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACrC,QAAQ,CAAC,EAAE,MAAM,GAChB,GAAG,CAAC,MAAM,CAAC,CAsBb;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK5D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAC9B,YAAY,CAWd"}
@@ -75,6 +75,12 @@ export const RULES = [
75
75
  defaultEnabled: false,
76
76
  category: "integrity",
77
77
  },
78
+ {
79
+ name: "strict-req-fact-pairing",
80
+ description: "Detect requirements with incomplete strict subject/property fact pairing for contradiction-safe semantics",
81
+ defaultEnabled: false,
82
+ category: "integrity",
83
+ },
78
84
  ];
79
85
  /**
80
86
  * Set of all rule names for quick lookups.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-cli",
3
- "version": "0.6.2",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "description": "Kibi CLI for knowledge base management",
6
6
  "engines": {
@@ -74,6 +74,14 @@
74
74
  "./public/check-types": {
75
75
  "types": "./dist/public/check-types.d.ts",
76
76
  "default": "./dist/public/check-types.js"
77
+ },
78
+ "./brief-config": {
79
+ "types": "./dist/public/brief-config.d.ts",
80
+ "default": "./dist/public/brief-config.js"
81
+ },
82
+ "./operational-artifacts": {
83
+ "types": "./dist/public/operational-artifacts.d.ts",
84
+ "default": "./dist/public/operational-artifacts.js"
77
85
  }
78
86
  },
79
87
  "types": "./dist/cli.d.ts",
@@ -84,7 +92,7 @@
84
92
  "fast-glob": "^3.2.12",
85
93
  "gray-matter": "^4.0.3",
86
94
  "js-yaml": "^4.1.0",
87
- "kibi-core": "^0.5.1",
95
+ "kibi-core": "^0.5.2",
88
96
  "ts-morph": "^23.0.0"
89
97
  },
90
98
  "devDependencies": {