kibi-cli 2.17.0 → 2.19.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 (80) hide show
  1. package/dist/cli-operation-metadata.d.ts +1 -1
  2. package/dist/cli-operation-metadata.js +1 -1
  3. package/dist/cli-register-proof.js +9 -1
  4. package/dist/commands/doctor.d.ts.map +1 -1
  5. package/dist/commands/doctor.js +18 -6
  6. package/dist/commands/sync/manifest.d.ts +10 -0
  7. package/dist/commands/sync/manifest.d.ts.map +1 -1
  8. package/dist/commands/sync/manifest.js +27 -0
  9. package/dist/commands/sync/untracked-targets.d.ts +17 -0
  10. package/dist/commands/sync/untracked-targets.d.ts.map +1 -1
  11. package/dist/commands/sync/untracked-targets.js +81 -9
  12. package/dist/commands/sync.d.ts.map +1 -1
  13. package/dist/commands/sync.js +76 -6
  14. package/dist/engine.d.ts +4 -0
  15. package/dist/engine.d.ts.map +1 -1
  16. package/dist/engine.js +56 -33
  17. package/dist/operations/modeling/predicate-applicability.d.ts.map +1 -1
  18. package/dist/operations/modeling/predicate-applicability.js +5 -2
  19. package/dist/operations/modeling/predicate-catalog-4.d.ts.map +1 -1
  20. package/dist/operations/modeling/predicate-catalog-4.js +4 -0
  21. package/dist/operations/modeling/predicate-loader.d.ts.map +1 -1
  22. package/dist/operations/modeling/predicate-loader.js +3 -0
  23. package/dist/operations/modeling/predicate-score-3.js +1 -1
  24. package/dist/operations/modeling/predicate-types.d.ts +5 -0
  25. package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
  26. package/dist/operations/mutation/source-authoring.d.ts +9 -0
  27. package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
  28. package/dist/operations/mutation/source-authoring.js +30 -1
  29. package/dist/operations/planning/apply-plan.d.ts.map +1 -1
  30. package/dist/operations/planning/apply-plan.js +10 -1
  31. package/dist/operations/semantic-advisor/clauses.d.ts.map +1 -1
  32. package/dist/operations/semantic-advisor/clauses.js +7 -1
  33. package/dist/prolog/store-lock.d.ts +41 -0
  34. package/dist/prolog/store-lock.d.ts.map +1 -1
  35. package/dist/prolog/store-lock.js +91 -1
  36. package/dist/prolog.d.ts +7 -1
  37. package/dist/prolog.d.ts.map +1 -1
  38. package/dist/prolog.js +21 -3
  39. package/dist/proof/inspect.d.ts +8 -1
  40. package/dist/proof/inspect.d.ts.map +1 -1
  41. package/dist/proof/inspect.js +19 -2
  42. package/dist/public/operations/catalog.d.ts +50 -1
  43. package/dist/public/operations/catalog.d.ts.map +1 -1
  44. package/dist/public/operations/contracts.d.ts.map +1 -1
  45. package/dist/public/operations/contracts.js +4 -4
  46. package/dist/public/operations/discovery-executors.d.ts +50 -1
  47. package/dist/public/operations/discovery-executors.d.ts.map +1 -1
  48. package/dist/public/operations/discovery-executors.js +418 -8
  49. package/dist/public/operations/migration-plan.d.ts +2 -0
  50. package/dist/public/operations/migration-plan.d.ts.map +1 -1
  51. package/dist/public/operations/migration-plan.js +31 -0
  52. package/dist/public/operations/source-relationship-parity.d.ts.map +1 -1
  53. package/dist/public/operations/source-relationship-parity.js +55 -1
  54. package/dist/public/operations/specs/discovery.d.ts +50 -1
  55. package/dist/public/operations/specs/discovery.d.ts.map +1 -1
  56. package/dist/public/operations/specs/discovery.js +56 -1
  57. package/dist/public/skills/kibi-usage/SKILL.md +20 -3
  58. package/dist/public/skills/kibi-usage/resources/proof.md +23 -0
  59. package/dist/runtime/cli-runtime.d.ts.map +1 -1
  60. package/dist/runtime/cli-runtime.js +7 -2
  61. package/dist/search-answer.d.ts +12 -3
  62. package/dist/search-answer.d.ts.map +1 -1
  63. package/dist/search-answer.js +51 -4
  64. package/dist/utils/branch-store.d.ts +8 -0
  65. package/dist/utils/branch-store.d.ts.map +1 -1
  66. package/dist/utils/branch-store.js +27 -1
  67. package/dist/utils/content-freshness.d.ts +13 -0
  68. package/dist/utils/content-freshness.d.ts.map +1 -0
  69. package/dist/utils/content-freshness.js +171 -0
  70. package/dist/utils/symbol-manifest-layout.d.ts +9 -0
  71. package/dist/utils/symbol-manifest-layout.d.ts.map +1 -0
  72. package/dist/utils/symbol-manifest-layout.js +18 -0
  73. package/package.json +2 -2
  74. package/src/public/operations/contracts.ts +7 -4
  75. package/src/public/operations/discovery-executors.ts +556 -6
  76. package/src/public/operations/migration-plan.ts +44 -0
  77. package/src/public/operations/source-relationship-parity.ts +60 -3
  78. package/src/public/operations/specs/discovery.ts +67 -1
  79. package/src/public/skills/kibi-usage/SKILL.md +20 -3
  80. package/src/public/skills/kibi-usage/resources/proof.md +23 -0
@@ -7,11 +7,71 @@ import { SwiplResolutionError } from "../../prolog/swipl-resolver.js";
7
7
  import { buildSearchAnswer } from "../../search-answer.js";
8
8
  import { rankEntities } from "../../search-ranking.js";
9
9
  import { resolveBranchAttachment } from "../../utils/branch-resolver.js";
10
- import { BRANCH_STORE_NOT_COMPILED, branchStoreReason, inspectBranchStore, storeLockJournalReason, uncompiledBranchStoreReason, } from "../../utils/branch-store.js";
10
+ import { BRANCH_STORE_NOT_COMPILED, branchStoreReason, inspectBranchStore, storeLockJournalReason, storeLockTakeoverReason, uncompiledBranchStoreReason, } from "../../utils/branch-store.js";
11
+ import { confirmSourceNewerReasons } from "../../utils/content-freshness.js";
11
12
  import { loadSearchCandidates, paginateResults, queryEntitiesViaQuery, reloadFullEntities, validateEntityType, } from "./discovery-entities.js";
12
13
  import { buildActionsFromStatus, readMigrationConfigStatus, } from "./migration-plan.js";
13
- import { OperationJsonDecodeError, runOperationJsonQuery, } from "./prolog-json.js";
14
+ import { OperationJsonDecodeError, runOperationJsonQuery, toPrologAtom, } from "./prolog-json.js";
14
15
  import { readWorkspaceSnapshot } from "./workspace-snapshot.js";
16
+ export const FACT_KINDS = [
17
+ "subject",
18
+ "property_value",
19
+ "predicate",
20
+ "predicate_schema",
21
+ "observation",
22
+ "meta",
23
+ "rule",
24
+ "rule_schema",
25
+ ];
26
+ /** Which typed filters each question mode reads; any other is refused. */
27
+ const MODE_FILTERS = {
28
+ schemas: new Set(["factKind", "predicate", "namespace"]),
29
+ predicate: new Set([
30
+ "predicate",
31
+ "namespace",
32
+ "where",
33
+ "args",
34
+ "polarity",
35
+ "scope",
36
+ "distinct",
37
+ ]),
38
+ property: new Set(["subjectKey", "propertyKey", "scope"]),
39
+ fact_kind: new Set(["factKind"]),
40
+ };
41
+ const TYPED_FACT_FILTERS = [
42
+ "factKind",
43
+ "predicate",
44
+ "namespace",
45
+ "where",
46
+ "args",
47
+ "polarity",
48
+ "scope",
49
+ "distinct",
50
+ "subjectKey",
51
+ "propertyKey",
52
+ ];
53
+ /**
54
+ * An empty result from a store that was never compiled says nothing about
55
+ * the authored requirements, so search reports the store state itself (the
56
+ * same reason kb_status and kb_check use) instead of a bare "no results".
57
+ */
58
+ // implements REQ-cli-status-pre-first-sync, REQ-mcp-search-discovery
59
+ function uncompiledSearchNotice(context) {
60
+ const attachment = context.branchAttachment;
61
+ if (attachment === undefined)
62
+ return null;
63
+ const store = inspectBranchStore(context.workspaceRoot, attachment.kbBranch);
64
+ const reason = uncompiledBranchStoreReason(context.workspaceRoot, store, attachment.kbBranch);
65
+ if (reason === null)
66
+ return null;
67
+ return {
68
+ state: "not_compiled",
69
+ code: BRANCH_STORE_NOT_COMPILED,
70
+ path: store.path,
71
+ detail: String(reason.detail),
72
+ remediation: reason.remediation,
73
+ };
74
+ }
15
75
  function requireProlog(context) {
16
76
  if (!context.prolog) {
17
77
  throw new Error("Discovery operation requires a Prolog context");
@@ -28,11 +88,334 @@ function isStatusPayload(value) {
28
88
  typeof record.dirty === "boolean" &&
29
89
  typeof record.syncState === "string");
30
90
  }
91
+ function hasTypedFactFilters(input) {
92
+ return TYPED_FACT_FILTERS.some((key) => input[key] !== undefined);
93
+ }
94
+ function requireAtomString(value, field) {
95
+ if (typeof value !== "string" || value.trim() === "") {
96
+ throw new Error(`${field} must be a non-empty string.`);
97
+ }
98
+ return value.trim();
99
+ }
100
+ function requireEnum(value, field, allowed) {
101
+ if (typeof value !== "string" || !allowed.includes(value)) {
102
+ throw new Error(`${field} must be one of: ${allowed.join(", ")}.`);
103
+ }
104
+ return value;
105
+ }
106
+ /**
107
+ * Builds the Prolog argument spec of a predicate question. Named `where`
108
+ * entries are resolved against the predicate's declared argument names in
109
+ * Prolog; positional `args` use `null` as a wildcard.
110
+ */
111
+ // implements REQ-kibi-query-fact-filters
112
+ function predicateArgSpec(input) {
113
+ if (input.where !== undefined && input.args !== undefined) {
114
+ throw new Error("Use either where (named arguments) or args (positional), not both.");
115
+ }
116
+ if (input.where !== undefined) {
117
+ if (input.where === null ||
118
+ typeof input.where !== "object" ||
119
+ Array.isArray(input.where)) {
120
+ throw new Error("where must be an object of argument name to value.");
121
+ }
122
+ const pairs = Object.entries(input.where).map(([key, value]) => {
123
+ const name = requireAtomString(key, "where argument name");
124
+ const atom = requireAtomString(value, `where.${key}`);
125
+ return `${toPrologAtom(name)}=${toPrologAtom(atom)}`;
126
+ });
127
+ return `where([${pairs.join(",")}])`;
128
+ }
129
+ if (input.args !== undefined) {
130
+ if (!Array.isArray(input.args)) {
131
+ throw new Error("args must be an array of constants (null = any).");
132
+ }
133
+ const entries = input.args.map((value, index) => value === null || value === "*"
134
+ ? "'$any'"
135
+ : toPrologAtom(requireAtomString(value, `args[${index}]`)));
136
+ return `[${entries.join(",")}]`;
137
+ }
138
+ return "[]";
139
+ }
140
+ function callText(args) {
141
+ return `kb_query(${JSON.stringify(args)})`;
142
+ }
143
+ /**
144
+ * Every typed answer names the exact call that makes sense next, so an
145
+ * agent never has to guess the follow-up from the shape of the data.
146
+ */
147
+ // implements REQ-kibi-query-fact-filters
148
+ function factQueryNext(input, response) {
149
+ const next = [];
150
+ const first = response.rows[0];
151
+ const firstId = typeof first?.id === "string" ? first.id : undefined;
152
+ switch (response.mode) {
153
+ case "predicate": {
154
+ if (firstId !== undefined) {
155
+ next.push({
156
+ call: callText({ id: firstId }),
157
+ why: "full fact text and relationships of one row",
158
+ });
159
+ }
160
+ const filters = {
161
+ predicate: input.predicate,
162
+ ...(input.namespace !== undefined
163
+ ? { namespace: input.namespace }
164
+ : {}),
165
+ ...(input.where !== undefined ? { where: input.where } : {}),
166
+ ...(input.args !== undefined ? { args: input.args } : {}),
167
+ ...(input.polarity !== undefined ? { polarity: input.polarity } : {}),
168
+ ...(input.scope !== undefined ? { scope: input.scope } : {}),
169
+ };
170
+ const names = Array.isArray(response.schema?.argumentNames)
171
+ ? response.schema.argumentNames.filter((name) => typeof name === "string")
172
+ : [];
173
+ const bound = new Set(Object.keys(input.where ?? {}));
174
+ const free = names.filter((name) => !bound.has(name));
175
+ if (input.distinct === undefined &&
176
+ response.count > 0 &&
177
+ free.length > 0) {
178
+ const argument = free[free.length - 1] ?? free[0];
179
+ next.push({
180
+ call: callText({ ...filters, distinct: argument }),
181
+ why: `count the distinct values of '${argument}' across these facts`,
182
+ });
183
+ }
184
+ if (response.count === 0 &&
185
+ response.suggestions &&
186
+ typeof response.suggestions === "object" &&
187
+ (response.warnings ?? []).length === 0) {
188
+ // Suggest a correction only for an argument whose requested value
189
+ // does not exist; a key whose value exists is not the cause.
190
+ for (const [key, values] of Object.entries(response.suggestions)) {
191
+ if (!Array.isArray(values))
192
+ continue;
193
+ const requested = input.where?.[key];
194
+ if (requested !== undefined && values.includes(requested))
195
+ continue;
196
+ const value = values[0];
197
+ if (typeof value === "string") {
198
+ next.push({
199
+ call: callText({
200
+ ...filters,
201
+ where: { ...(input.where ?? {}), [key]: value },
202
+ }),
203
+ why: `'${key}' has no fact with that value; '${value}' is one that exists`,
204
+ });
205
+ break;
206
+ }
207
+ }
208
+ }
209
+ if (next.length === 0) {
210
+ next.push({
211
+ call: callText({ factKind: "predicate_schema" }),
212
+ why: "list the predicates this KB can answer about and their argument names",
213
+ });
214
+ }
215
+ break;
216
+ }
217
+ case "property": {
218
+ if (firstId !== undefined) {
219
+ next.push({
220
+ call: callText({ id: firstId }),
221
+ why: "full fact text and relationships of one row",
222
+ });
223
+ }
224
+ if (input.propertyKey !== undefined) {
225
+ next.push({
226
+ call: callText({ subjectKey: input.subjectKey }),
227
+ why: "every property stated about this subject",
228
+ });
229
+ }
230
+ else if (response.count === 0) {
231
+ next.push({
232
+ call: callText({ factKind: "subject" }),
233
+ why: "list the subject keys that exist",
234
+ });
235
+ }
236
+ break;
237
+ }
238
+ case "schemas": {
239
+ const name = typeof first?.name === "string" ? first.name : undefined;
240
+ if (name !== undefined) {
241
+ next.push({
242
+ call: callText({ predicate: name }),
243
+ why: "every stored fact of the first listed predicate",
244
+ });
245
+ }
246
+ break;
247
+ }
248
+ case "fact_kind": {
249
+ if (input.factKind === "predicate") {
250
+ next.push({
251
+ call: callText({ factKind: "predicate_schema" }),
252
+ why: "argument names and constants for asking by name",
253
+ });
254
+ }
255
+ else if (input.factKind === "property_value" && first) {
256
+ const subjectKey = typeof first.subject_key === "string" ? first.subject_key : undefined;
257
+ if (subjectKey !== undefined) {
258
+ next.push({
259
+ call: callText({ subjectKey }),
260
+ why: "the properties of the first listed subject",
261
+ });
262
+ }
263
+ }
264
+ else if (firstId !== undefined) {
265
+ next.push({
266
+ call: callText({ id: firstId }),
267
+ why: "full fact text of one row",
268
+ });
269
+ }
270
+ break;
271
+ }
272
+ }
273
+ return next;
274
+ }
275
+ function factRowSummary(row) {
276
+ const id = String(row.id ?? "");
277
+ if (typeof row.name === "string" && Array.isArray(row.args)) {
278
+ const effect = typeof row.effect === "string" ? ` -> ${row.effect}` : "";
279
+ const polarity = row.polarity === "deny" ? " [deny]" : "";
280
+ return `${id}: ${row.name}(${row.args.map(String).join(", ")})${effect}${polarity}`;
281
+ }
282
+ if (typeof row.subjectKey === "string" &&
283
+ typeof row.propertyKey === "string") {
284
+ const unit = typeof row.unit === "string" && row.unit ? ` ${row.unit}` : "";
285
+ return `${id}: ${row.subjectKey} ${row.propertyKey} ${String(row.operator ?? "")} ${String(row.value ?? "")}${unit}`;
286
+ }
287
+ if (typeof row.name === "string" && typeof row.arity === "number") {
288
+ const names = Array.isArray(row.argumentNames)
289
+ ? row.argumentNames.map(String).join(", ")
290
+ : "";
291
+ return `${row.name}(${names})`;
292
+ }
293
+ return `${id} (${String(row.title ?? "")})`;
294
+ }
295
+ /**
296
+ * Typed fact questions run as validated Prolog goals in the discovery module;
297
+ * the executor never forwards caller-written Prolog.
298
+ */
299
+ // implements REQ-kibi-query-fact-filters
300
+ async function executeFactQuery(input, context, limit, offset) {
301
+ if (input.type !== undefined && input.type !== "fact") {
302
+ throw new Error("Typed fact filters (factKind, predicate, subjectKey, ...) apply to type 'fact' only.");
303
+ }
304
+ for (const key of ["id", "tags", "sourceFile"]) {
305
+ if (input[key] !== undefined) {
306
+ throw new Error(`${key} cannot be combined with typed fact filters; run a separate kb_query.`);
307
+ }
308
+ }
309
+ const prolog = requireProlog(context);
310
+ if (!Number.isFinite(limit) ||
311
+ !Number.isFinite(offset) ||
312
+ limit < 0 ||
313
+ offset < 0) {
314
+ throw new Error("limit and offset must be non-negative numbers.");
315
+ }
316
+ const page = `${Math.trunc(limit)}, ${Math.trunc(offset)}`;
317
+ const namespace = input.namespace === undefined
318
+ ? "none"
319
+ : toPrologAtom(requireAtomString(input.namespace, "namespace"));
320
+ const mode = input.factKind === "predicate_schema"
321
+ ? "schemas"
322
+ : input.predicate !== undefined
323
+ ? "predicate"
324
+ : input.subjectKey !== undefined
325
+ ? "property"
326
+ : input.factKind !== undefined
327
+ ? "fact_kind"
328
+ : undefined;
329
+ if (mode === undefined) {
330
+ throw new Error("Typed fact filters need a predicate, a subjectKey or a factKind; where/args/polarity/distinct/propertyKey refine one of those.");
331
+ }
332
+ const unused = TYPED_FACT_FILTERS.filter((key) => input[key] !== undefined && !MODE_FILTERS[mode].has(key));
333
+ if (unused.length > 0) {
334
+ throw new Error(`${unused.join(", ")} do${unused.length === 1 ? "es" : ""} not apply to a ${mode} question; allowed with it: ${[...MODE_FILTERS[mode]].join(", ")}.`);
335
+ }
336
+ const scope = input.scope === undefined
337
+ ? "all"
338
+ : requireEnum(input.scope, "scope", ["all", "current_requirements"]);
339
+ const factKind = input.factKind === undefined
340
+ ? undefined
341
+ : requireEnum(input.factKind, "factKind", FACT_KINDS);
342
+ let goal;
343
+ if (mode === "schemas") {
344
+ goal = `discovery:ask_schemas_json(${namespace}, ${input.predicate === undefined ? "none" : toPrologAtom(requireAtomString(input.predicate, "predicate"))}, ${page}, JsonString)`;
345
+ }
346
+ else if (mode === "predicate") {
347
+ const name = requireAtomString(input.predicate, "predicate");
348
+ const polarity = input.polarity === undefined
349
+ ? "none"
350
+ : toPrologAtom(requireEnum(input.polarity, "polarity", ["assert", "deny"]));
351
+ const distinct = input.distinct === undefined
352
+ ? "none"
353
+ : toPrologAtom(requireAtomString(input.distinct, "distinct"));
354
+ goal = `discovery:ask_predicate_json(${namespace}, ${toPrologAtom(name)}, ${predicateArgSpec(input)}, ${polarity}, ${scope}, ${distinct}, ${page}, JsonString)`;
355
+ }
356
+ else if (mode === "property") {
357
+ const subject = requireAtomString(input.subjectKey, "subjectKey");
358
+ const property = input.propertyKey === undefined
359
+ ? "none"
360
+ : toPrologAtom(requireAtomString(input.propertyKey, "propertyKey"));
361
+ goal = `discovery:ask_property_json(${toPrologAtom(subject)}, ${property}, ${scope}, ${page}, JsonString)`;
362
+ }
363
+ else {
364
+ goal = `discovery:ask_fact_kind_json(${toPrologAtom(factKind)}, ${page}, JsonString)`;
365
+ }
366
+ const response = await runOperationJsonQuery(prolog, "discovery.pl", goal, "Query execution", context.signal);
367
+ const rows = Array.isArray(response.rows) ? response.rows : [];
368
+ const count = typeof response.count === "number" ? response.count : rows.length;
369
+ const warnings = Array.isArray(response.warnings) ? response.warnings : [];
370
+ const factQuery = {
371
+ mode: response.mode,
372
+ ...(response.predicate ? { predicate: response.predicate } : {}),
373
+ ...(response.property ? { property: response.property } : {}),
374
+ ...(response.factKind ? { factKind: response.factKind } : {}),
375
+ ...(response.schema !== undefined ? { schema: response.schema } : {}),
376
+ ...(response.subjectFacts ? { subjectFacts: response.subjectFacts } : {}),
377
+ ...(response.distinct !== undefined ? { distinct: response.distinct } : {}),
378
+ ...(response.suggestions !== undefined
379
+ ? { suggestions: response.suggestions }
380
+ : {}),
381
+ warnings,
382
+ next: factQueryNext(input, { ...response, rows, count }),
383
+ };
384
+ const subject = response.mode === "predicate"
385
+ ? `${String(input.predicate)} facts`
386
+ : response.mode === "property"
387
+ ? `properties of ${String(input.subjectKey)}`
388
+ : response.mode === "schemas"
389
+ ? "predicate schemas"
390
+ : `${String(factKind)} facts`;
391
+ const lines = [
392
+ count === 0
393
+ ? `No ${subject} matched.`
394
+ : `${count} ${subject}. Showing ${rows.length} (offset ${offset}, limit ${limit}): ${rows.map(factRowSummary).join("; ")}`,
395
+ ...(factQuery.distinct && typeof factQuery.distinct === "object"
396
+ ? [
397
+ `Distinct ${String(factQuery.distinct.argument)}: ${JSON.stringify(factQuery.distinct.values ?? [])}`,
398
+ ]
399
+ : []),
400
+ ...(factQuery.suggestions && typeof factQuery.suggestions === "object"
401
+ ? [`Values that exist: ${JSON.stringify(factQuery.suggestions)}`]
402
+ : []),
403
+ ...warnings.map((warning) => `Warning: ${warning}`),
404
+ ...factQuery.next.map((entry) => `Next: ${entry.call} (${entry.why})`),
405
+ ];
406
+ return {
407
+ content: [{ type: "text", text: lines.join("\n") }],
408
+ structuredContent: { entities: rows, count, factQuery },
409
+ };
410
+ }
31
411
  export async function executeQuery(input, context) {
32
412
  // implements REQ-kibi-operation-interface-parity
33
413
  const { type, id, tags, sourceFile, limit = 100, offset = 0 } = input;
34
414
  try {
35
415
  validateEntityType(type);
416
+ if (hasTypedFactFilters(input)) {
417
+ return await executeFactQuery(input, context, limit, offset);
418
+ }
36
419
  const prolog = requireProlog(context);
37
420
  const signal = context.signal;
38
421
  const pageInput = {
@@ -118,6 +501,15 @@ export async function executeSearch(input, context) {
118
501
  validateEntityType(type);
119
502
  try {
120
503
  const prolog = requireProlog(context);
504
+ const branch = context.branchAttachment?.kbBranch ?? null;
505
+ const storeNotice = uncompiledSearchNotice(context);
506
+ const branchFields = {
507
+ branch,
508
+ ...(storeNotice ? { branchStore: storeNotice } : {}),
509
+ };
510
+ const storeNote = storeNotice
511
+ ? ` Note: ${storeNotice.detail} Run kibi sync before relying on search results.`
512
+ : "";
121
513
  const intentMode = rankingMode === "intent-v1" ||
122
514
  semanticFacets !== undefined ||
123
515
  sourceLocations !== undefined;
@@ -142,12 +534,12 @@ export async function executeSearch(input, context) {
142
534
  : undefined;
143
535
  // The structured payload carries every row; the text is a short
144
536
  // human-readable digest instead of a second copy of the results.
145
- const text = intentResult.matches.length === 0
537
+ const text = `${intentResult.matches.length === 0
146
538
  ? `No intent search results for '${trimmedQuery}' (abstained).`
147
539
  : `Found ${intentResult.matches.length} intent search results for '${trimmedQuery}'${intentResult.analysis.ambiguous ? " (top matches are close; confirm before relying on one)" : ""}. Top: ${paginated
148
540
  .slice(0, 5)
149
541
  .map((match) => String(match.entity.id ?? ""))
150
- .join(", ")}${answerLayer ? `. Governing: ${answerLayer.governing.map((req) => req.id).join(", ") || "none found"}` : ""}.`;
542
+ .join(", ")}${answerLayer ? `. Governing: ${answerLayer.governing.map((req) => req.id).join(", ") || "none found"}` : ""}.`}${storeNote}`;
151
543
  return {
152
544
  content: [{ type: "text", text }],
153
545
  structuredContent: {
@@ -156,6 +548,7 @@ export async function executeSearch(input, context) {
156
548
  truncated: offset + paginated.length < intentResult.matches.length,
157
549
  queryAnalysis: intentResult.analysis,
158
550
  ...(answerLayer ? { answer: answerLayer } : {}),
551
+ ...branchFields,
159
552
  },
160
553
  };
161
554
  }
@@ -168,17 +561,18 @@ export async function executeSearch(input, context) {
168
561
  }, context.signal);
169
562
  const matches = await rankEntities(entities, trimmedQuery, context.workspaceRoot);
170
563
  const paginated = paginateResults(matches, limit, offset);
171
- const text = matches.length === 0
564
+ const text = `${matches.length === 0
172
565
  ? `No search results for '${trimmedQuery}'.`
173
566
  : `Found ${matches.length} search results for '${trimmedQuery}'. Showing ${paginated.length} (offset ${offset}, limit ${limit}): ${paginated
174
567
  .map((match) => `${String(match.entity.id ?? "")} [${match.reasons.join(", ")}]`)
175
- .join(", ")}`;
568
+ .join(", ")}`}${storeNote}`;
176
569
  return {
177
570
  content: [{ type: "text", text }],
178
571
  structuredContent: {
179
572
  results: await projectMatches(prolog, paginated, fields),
180
573
  count: matches.length,
181
574
  truncated: offset + paginated.length < matches.length,
575
+ ...branchFields,
182
576
  },
183
577
  };
184
578
  }
@@ -272,7 +666,19 @@ export async function executeStatus(_input, context) {
272
666
  await ownedEngine?.terminate();
273
667
  }
274
668
  const snapshotEvidence = await readWorkspaceSnapshot(context);
275
- const existingReasons = payload.staleReasons ?? [];
669
+ // implements REQ-cli-status-pre-first-sync
670
+ // Prolog compares file times only. A fresh clone or a checkout rewrites
671
+ // every mtime without changing a byte, so confirm each "newer" source
672
+ // against the content hashes the last compilation recorded.
673
+ const prologReasons = payload.staleReasons ?? [];
674
+ const confirmed = confirmSourceNewerReasons(context.workspaceRoot, store.path, prologReasons);
675
+ const existingReasons = confirmed.kept;
676
+ if (confirmed.dropped.length > 0 &&
677
+ confirmed.kept.length === 0 &&
678
+ payload.staleReasonsTruncated !== true &&
679
+ payload.syncState === "stale") {
680
+ payload = { ...payload, syncState: "fresh", dirty: false };
681
+ }
276
682
  // implements REQ-cli-status-pre-first-sync
277
683
  // A missing or empty store beside authored sources gets the one reason
278
684
  // whose remediation works (kibi sync); `kibi branch ensure` alone would
@@ -280,6 +686,9 @@ export async function executeStatus(_input, context) {
280
686
  const storeReason = uncompiledBranchStoreReason(context.workspaceRoot, store, attachment.kbBranch) ?? branchStoreReason(store);
281
687
  const storeUncompiled = storeReason?.code === BRANCH_STORE_NOT_COMPILED;
282
688
  const lockReason = storeLockJournalReason(store.path);
689
+ // implements REQ-core-journaled-engine-persistence
690
+ // A stale-lock takeover heals itself; report it without making the KB stale.
691
+ const takeoverNotice = storeLockTakeoverReason(store.path);
283
692
  const compilerReason = compilerFingerprintReason(store.path);
284
693
  const engineReason = engineStatus
285
694
  ? {
@@ -318,6 +727,7 @@ export async function executeStatus(_input, context) {
318
727
  ...(storeUncompiled ? { syncState: "stale", dirty: true } : {}),
319
728
  branchAttachment: attachment,
320
729
  branchStore: store,
730
+ ...(takeoverNotice ? { notices: [takeoverNotice] } : {}),
321
731
  ...(engineStatus ? { engineStatus } : {}),
322
732
  staleReasons,
323
733
  staleReasonCount: staleReasons.length,
@@ -447,7 +857,7 @@ export async function executeStatus(_input, context) {
447
857
  content: [
448
858
  {
449
859
  type: "text",
450
- text: `Branch ${payload.branch} is ${enrichedPayload.syncState} (snapshot ${payload.snapshotId}, dirty=${enrichedPayload.dirty}, proofSnapshot=${enrichedPayload.proofSnapshot})${storeUncompiled ? `. Blocking: ${String(storeReason?.detail)} Run kibi sync.` : ""}`,
860
+ text: `Branch ${payload.branch} is ${enrichedPayload.syncState} (snapshot ${payload.snapshotId}, dirty=${enrichedPayload.dirty}, proofSnapshot=${enrichedPayload.proofSnapshot})${storeUncompiled ? `. Blocking: ${String(storeReason?.detail)} Run kibi sync.` : ""}${takeoverNotice ? `. Note: ${String(takeoverNotice.detail)}` : ""}`,
451
861
  },
452
862
  ],
453
863
  structuredContent: statusWithPlan,
@@ -94,6 +94,8 @@ export type MigrationConfigStatus = Readonly<{
94
94
  configHash: string | null;
95
95
  }>;
96
96
  export declare function readMigrationConfigStatus(workspaceRoot: string): MigrationConfigStatus;
97
+ /** Migration action code of the automatic recompile of changed sources. */
98
+ export declare const FRESHNESS_RESYNC = "freshness_resync";
97
99
  export declare function buildActionsFromStatus(input: {
98
100
  workspaceRoot: string;
99
101
  branchAttachment?: Readonly<Record<string, unknown>>;
@@ -1 +1 @@
1
- {"version":3,"file":"migration-plan.d.ts","sourceRoot":"","sources":["../../../src/public/operations/migration-plan.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"migration-plan.d.ts","sourceRoot":"","sources":["../../../src/public/operations/migration-plan.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@ import { existsSync, readFileSync } from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { BRANCH_STORE_NOT_COMPILED } from "../../utils/branch-store.js";
5
5
  import { legacyConfigExists } from "../../utils/config.js";
6
+ import { MTIME_SOURCE_REASON_CODES } from "../../utils/content-freshness.js";
6
7
  import { readKbManifestStatus } from "../../utils/kb-manifest.js";
7
8
  import { getSchemaVersionStatus, normalizeSchemaVersion, } from "../../utils/schema-version.js";
8
9
  import { lifecycleActionsFromViolations, schema6ReviewActionsFromCheck, } from "./schema6-check-actions.js";
@@ -438,6 +439,8 @@ function readLegacySchemaVersion(workspaceRoot) {
438
439
  }
439
440
  }
440
441
  // implements REQ-agent-guided-migration-orchestration
442
+ /** Migration action code of the automatic recompile of changed sources. */
443
+ export const FRESHNESS_RESYNC = "freshness_resync";
441
444
  export function buildActionsFromStatus(input) {
442
445
  const actions = [];
443
446
  const attachment = input.branchAttachment;
@@ -562,11 +565,39 @@ export function buildActionsFromStatus(input) {
562
565
  autoApplicable: true,
563
566
  }));
564
567
  }
568
+ // implements REQ-cli-status-pre-first-sync
569
+ // Sources whose content changed since the last compilation (kb_status has
570
+ // already dropped mtime-only reasons) are fixed by recompiling them: one
571
+ // automatic `kibi sync`, not a review per file. An uncompiled store is
572
+ // compiled by the step above, which also covers these files.
573
+ const resyncReasons = (input.staleReasons ?? []).filter((reason) => typeof reason.code === "string" &&
574
+ MTIME_SOURCE_REASON_CODES.has(reason.code));
575
+ if (resyncReasons.length > 0 && uncompiled === undefined) {
576
+ actions.push(migrationAction({
577
+ id: "freshness-resync",
578
+ code: FRESHNESS_RESYNC,
579
+ category: "freshness",
580
+ safety: "automatic",
581
+ invocation: { kind: "cli", command_argv: ["kibi", "sync"] },
582
+ affectedFiles: [
583
+ ...new Set(resyncReasons.flatMap((reason) => typeof reason.path === "string" ? [reason.path] : [])),
584
+ ].sort(),
585
+ evidence: { reasons: resyncReasons },
586
+ dependsOn: actions
587
+ .map((action) => action.id)
588
+ .filter((id) => id === "branch-store-ensure" || id === "schema-config-upgrade"),
589
+ postconditions: [{ syncState: "fresh" }],
590
+ autoApplicable: true,
591
+ }));
592
+ }
565
593
  for (const reason of input.staleReasons ?? []) {
566
594
  const code = typeof reason.code === "string" ? reason.code : "stale_source";
567
595
  // Planned above as an automatic compile step, not a review.
568
596
  if (code === BRANCH_STORE_NOT_COMPILED)
569
597
  continue;
598
+ // Planned above as one automatic resync (or covered by the compile).
599
+ if (MTIME_SOURCE_REASON_CODES.has(code))
600
+ continue;
570
601
  const file = typeof reason.path === "string" ? reason.path : "";
571
602
  actions.push(migrationAction({
572
603
  id: `freshness-${code}-${file}`,
@@ -1 +1 @@
1
- {"version":3,"file":"source-relationship-parity.d.ts","sourceRoot":"","sources":["../../../src/public/operations/source-relationship-parity.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CACpC,CAAC,CAAC;AAaH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,SAAS,wBAAwB,EAAE,EAC7C,QAAQ,EAAE,SAAS,wBAAwB,EAAE,GAC5C,SAAS,EAAE,CAwDb;AAuCD,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,wBAAwB,EAAE,CAmB5B;AA6BD,wBAAsB,yCAAyC,CAC7D,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,SAAS,EAAE,CAAC,CA0DtB"}
1
+ {"version":3,"file":"source-relationship-parity.d.ts","sourceRoot":"","sources":["../../../src/public/operations/source-relationship-parity.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,UAAU,GAAG,SAAS,CAAC;CACpC,CAAC,CAAC;AAaH,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,SAAS,wBAAwB,EAAE,EAC7C,QAAQ,EAAE,SAAS,wBAAwB,EAAE,GAC5C,SAAS,EAAE,CAwDb;AAuCD,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,wBAAwB,EAAE,CAmB5B;AA6BD,wBAAsB,yCAAyC,CAC7D,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,SAAS,EAAE,CAAC,CA0DtB"}