kibi-cli 2.18.0 → 2.20.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 (106) hide show
  1. package/dist/cli-operation-metadata.d.ts +2 -2
  2. package/dist/cli-operation-metadata.js +2 -2
  3. package/dist/cli-register-maintenance.js +1 -1
  4. package/dist/commands/doctor.js +11 -5
  5. package/dist/commands/engine.d.ts +2 -1
  6. package/dist/commands/engine.d.ts.map +1 -1
  7. package/dist/commands/engine.js +6 -1
  8. package/dist/commands/sync/manifest.d.ts +10 -0
  9. package/dist/commands/sync/manifest.d.ts.map +1 -1
  10. package/dist/commands/sync/manifest.js +27 -0
  11. package/dist/commands/sync/untracked-targets.d.ts +17 -0
  12. package/dist/commands/sync/untracked-targets.d.ts.map +1 -1
  13. package/dist/commands/sync/untracked-targets.js +43 -1
  14. package/dist/commands/sync.d.ts.map +1 -1
  15. package/dist/commands/sync.js +51 -2
  16. package/dist/engine.d.ts.map +1 -1
  17. package/dist/engine.js +33 -22
  18. package/dist/intent-search.d.ts +7 -0
  19. package/dist/intent-search.d.ts.map +1 -1
  20. package/dist/intent-search.js +66 -4
  21. package/dist/operations/modeling/predicate-applicability.d.ts.map +1 -1
  22. package/dist/operations/modeling/predicate-applicability.js +10 -4
  23. package/dist/operations/modeling/predicate-applyplan.d.ts.map +1 -1
  24. package/dist/operations/modeling/predicate-applyplan.js +16 -5
  25. package/dist/operations/modeling/predicate-catalog-4.d.ts.map +1 -1
  26. package/dist/operations/modeling/predicate-catalog-4.js +3 -0
  27. package/dist/operations/modeling/predicate-inference-1.d.ts.map +1 -1
  28. package/dist/operations/modeling/predicate-inference-1.js +34 -7
  29. package/dist/operations/modeling/predicate-score-3.js +1 -1
  30. package/dist/operations/modeling/predicate-types.d.ts +6 -0
  31. package/dist/operations/modeling/predicate-types.d.ts.map +1 -1
  32. package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
  33. package/dist/operations/modeling/suggest-predicates.js +111 -0
  34. package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
  35. package/dist/operations/mutation/source-authoring.js +5 -12
  36. package/dist/operations/mutation/types.d.ts +26 -0
  37. package/dist/operations/mutation/types.d.ts.map +1 -1
  38. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  39. package/dist/operations/mutation/upsert.js +8 -2
  40. package/dist/operations/mutation/validate-upsert.d.ts.map +1 -1
  41. package/dist/operations/mutation/validate-upsert.js +116 -4
  42. package/dist/operations/mutation/warnings.d.ts +10 -0
  43. package/dist/operations/mutation/warnings.d.ts.map +1 -1
  44. package/dist/operations/mutation/warnings.js +26 -0
  45. package/dist/operations/mutation/what-if.d.ts +25 -0
  46. package/dist/operations/mutation/what-if.d.ts.map +1 -0
  47. package/dist/operations/mutation/what-if.js +59 -0
  48. package/dist/operations/planning/compile-intent.d.ts +15 -20
  49. package/dist/operations/planning/compile-intent.d.ts.map +1 -1
  50. package/dist/operations/planning/compile-intent.js +64 -53
  51. package/dist/operations/semantic-advisor/clauses.d.ts.map +1 -1
  52. package/dist/operations/semantic-advisor/clauses.js +8 -7
  53. package/dist/prolog/janitor.d.ts +26 -2
  54. package/dist/prolog/janitor.d.ts.map +1 -1
  55. package/dist/prolog/janitor.js +55 -3
  56. package/dist/prolog/store-lock.d.ts +47 -3
  57. package/dist/prolog/store-lock.d.ts.map +1 -1
  58. package/dist/prolog/store-lock.js +148 -11
  59. package/dist/proof/inspect.d.ts +1 -1
  60. package/dist/proof/inspect.d.ts.map +1 -1
  61. package/dist/proof/inspect.js +9 -2
  62. package/dist/public/operations/catalog.d.ts +97 -4
  63. package/dist/public/operations/catalog.d.ts.map +1 -1
  64. package/dist/public/operations/check-executor.d.ts +2 -2
  65. package/dist/public/operations/check-executor.d.ts.map +1 -1
  66. package/dist/public/operations/check-executor.js +35 -0
  67. package/dist/public/operations/check-format-shared.d.ts +16 -0
  68. package/dist/public/operations/check-format-shared.d.ts.map +1 -1
  69. package/dist/public/operations/check-format-shared.js +19 -0
  70. package/dist/public/operations/contracts.d.ts.map +1 -1
  71. package/dist/public/operations/contracts.js +11 -4
  72. package/dist/public/operations/discovery-executors.d.ts +39 -0
  73. package/dist/public/operations/discovery-executors.d.ts.map +1 -1
  74. package/dist/public/operations/discovery-executors.js +368 -3
  75. package/dist/public/operations/specs/composite.d.ts +1 -1
  76. package/dist/public/operations/specs/composite.js +1 -1
  77. package/dist/public/operations/specs/discovery.d.ts +52 -3
  78. package/dist/public/operations/specs/discovery.d.ts.map +1 -1
  79. package/dist/public/operations/specs/discovery.js +58 -3
  80. package/dist/public/operations/specs/upsert.d.ts +44 -0
  81. package/dist/public/operations/specs/upsert.d.ts.map +1 -1
  82. package/dist/public/operations/specs/upsert.js +14 -0
  83. package/dist/public/skills/kibi-usage/SKILL.md +44 -1
  84. package/dist/public/skills/kibi-usage/resources/fact-lanes.md +4 -2
  85. package/dist/runtime/cli-runtime.d.ts.map +1 -1
  86. package/dist/runtime/cli-runtime.js +7 -2
  87. package/dist/search-answer.d.ts +25 -3
  88. package/dist/search-answer.d.ts.map +1 -1
  89. package/dist/search-answer.js +96 -8
  90. package/dist/utils/branch-store.d.ts.map +1 -1
  91. package/dist/utils/branch-store.js +4 -2
  92. package/dist/utils/content-freshness.d.ts.map +1 -1
  93. package/dist/utils/content-freshness.js +12 -2
  94. package/dist/utils/symbol-manifest-layout.d.ts +9 -0
  95. package/dist/utils/symbol-manifest-layout.d.ts.map +1 -0
  96. package/dist/utils/symbol-manifest-layout.js +18 -0
  97. package/package.json +3 -3
  98. package/src/public/operations/check-executor.ts +44 -1
  99. package/src/public/operations/check-format-shared.ts +35 -1
  100. package/src/public/operations/contracts.ts +15 -4
  101. package/src/public/operations/discovery-executors.ts +484 -2
  102. package/src/public/operations/specs/composite.ts +1 -1
  103. package/src/public/operations/specs/discovery.ts +70 -3
  104. package/src/public/operations/specs/upsert.ts +15 -0
  105. package/src/public/skills/kibi-usage/SKILL.md +44 -1
  106. package/src/public/skills/kibi-usage/resources/fact-lanes.md +4 -2
@@ -707,6 +707,50 @@ export declare const upsertSpec: {
707
707
  readonly default: false;
708
708
  readonly description: "Validate the payload and return schema/modeling errors, a normalized preview and the semantic advisor receipt without writing the KB or workspace. Use it as the preflight before the real write.";
709
709
  };
710
+ readonly plannedFacts: {
711
+ readonly type: "array";
712
+ readonly description: "dryRun only. Fact upsert payloads (type, id, properties, optional relationships) that will be written before this payload, typically the applyPlan entries kb_model returned. They are validated in order and staged with the payload, so a requires_predicate or requires_property target that does not exist yet resolves, its claim_key is read from the plan, and the contradiction preview runs against the KB as it would be after the whole batch (result: stagedFacts, whatIf). A conflict the batch would introduce, such as an allow and a deny permission_rule on the same key, is refused as the commit would refuse it. Rejected without dryRun: write the facts first.";
713
+ readonly items: {
714
+ readonly type: "object";
715
+ readonly required: readonly ["type", "id", "properties"];
716
+ readonly properties: {
717
+ readonly type: {
718
+ readonly type: "string";
719
+ readonly const: "fact";
720
+ };
721
+ readonly id: {
722
+ readonly type: "string";
723
+ readonly minLength: 1;
724
+ };
725
+ readonly properties: {
726
+ readonly type: "object";
727
+ };
728
+ readonly relationships: {
729
+ readonly type: "array";
730
+ readonly description: "Optional relationship rows to create in the same call. For requirement encoding, prefer `constrains` + `requires_property` for strict property facts or `requires_predicate` for ontology predicate facts. Side effect: asserts edges in KB.";
731
+ readonly items: {
732
+ readonly type: "object";
733
+ readonly required: readonly ["type", "from", "to"];
734
+ readonly properties: {
735
+ readonly type: {
736
+ readonly type: "string";
737
+ readonly enum: readonly ["depends_on", "specified_by", "verified_by", "validates", "implements", "covered_by", "executable_for", "constrained_by", "constrains", "requires_property", "requires_predicate", "requires_rule", "guards", "publishes", "consumes", "supersedes", "restates", "assumes", "exempts", "relates_to"];
738
+ readonly description: "Relationship type enum. Use only supported values. Direction semantics follow KB model (e.g., implements symbol->req, verified_by req/scenario->test, executable_for symbol->test).";
739
+ };
740
+ readonly from: {
741
+ readonly type: "string";
742
+ readonly description: "Source entity ID (must exist). Example: 'SYM-login-handler'.";
743
+ };
744
+ readonly to: {
745
+ readonly type: "string";
746
+ readonly description: "Target entity ID (must exist). Example: 'REQ-cli-gc'.";
747
+ };
748
+ };
749
+ };
750
+ };
751
+ };
752
+ };
753
+ };
710
754
  readonly document: {
711
755
  readonly type: "object";
712
756
  readonly additionalProperties: false;
@@ -1 +1 @@
1
- {"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../../../../src/public/operations/specs/upsert.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AAQvE,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2CuC,CAAC"}
1
+ {"version":3,"file":"upsert.d.ts","sourceRoot":"","sources":["../../../../src/public/operations/specs/upsert.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,aAAa,EAAE,MAAM,wCAAwC,CAAC;AAQvE,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0DuC,CAAC"}
@@ -24,6 +24,20 @@ export const upsertSpec = {
24
24
  default: false,
25
25
  description: "Validate the payload and return schema/modeling errors, a normalized preview and the semantic advisor receipt without writing the KB or workspace. Use it as the preflight before the real write.",
26
26
  },
27
+ plannedFacts: {
28
+ type: "array",
29
+ description: "dryRun only. Fact upsert payloads (type, id, properties, optional relationships) that will be written before this payload, typically the applyPlan entries kb_model returned. They are validated in order and staged with the payload, so a requires_predicate or requires_property target that does not exist yet resolves, its claim_key is read from the plan, and the contradiction preview runs against the KB as it would be after the whole batch (result: stagedFacts, whatIf). A conflict the batch would introduce, such as an allow and a deny permission_rule on the same key, is refused as the commit would refuse it. Rejected without dryRun: write the facts first.",
30
+ items: {
31
+ type: "object",
32
+ required: ["type", "id", "properties"],
33
+ properties: {
34
+ type: { type: "string", const: "fact" },
35
+ id: { type: "string", minLength: 1 },
36
+ properties: { type: "object" },
37
+ relationships: RELATIONSHIPS_SCHEMA,
38
+ },
39
+ },
40
+ },
27
41
  document: {
28
42
  type: "object",
29
43
  additionalProperties: false,
@@ -2,7 +2,7 @@
2
2
  id: kibi-usage
3
3
  name: Kibi Usage
4
4
  description: Use Kibi's source-first, exact-Git, migration-aware, proof-aware operations safely across MCP or the trusted local CLI, including partial completion repair.
5
- version: 2.14.0
5
+ version: 2.15.2
6
6
  kibiCompatibility: ">=1.0.0"
7
7
  tags:
8
8
  - kibi
@@ -82,6 +82,41 @@ different workspace than the one you asked for; its `detail.reason` says why.
82
82
  Use the CLI from your working directory instead: `npx --no-install kibi ...`
83
83
  runs where you are and needs no `workspaceRoot`.
84
84
 
85
+ ## Reading a hook snippet
86
+
87
+ On hosts where Kibi hooks are installed, a short block
88
+ arrives before you read or edit a file whose symbols are linked to
89
+ requirements. It is projected from `.kb/symbols.yaml`, not from the store, so
90
+ it is a pointer to the knowledge, never proof. Each line answers one question:
91
+
92
+ - `Kibi knowledge for <path> (symbol manifest):` and the `- REQ-… — <symbol
93
+ titles>` rows: the requirements the file's symbols implement, lead
94
+ requirement first. Read the lead requirement with `kb_query({id})` before
95
+ changing behavior.
96
+ - `REQ-… must keep true: FACT-…` (edit only): the ground facts the lead
97
+ requirement requires. An edit that breaks one of them is a requirement
98
+ change: supersede and remodel the requirement, do not code around the fact.
99
+ - `Decision: ADR-…` (edit only): the decision behind the lead requirement.
100
+ - `Covered by: TEST-…` and `Test code for: …`: the tests that prove the
101
+ file's symbols; run or update them with the edit.
102
+ - `The edit is inside <symbol>, which implements REQ-…`: the symbol the edit
103
+ lands in.
104
+ - `Next layer: …`: the exact `kb_query` and `kb_search(... sourceLocations)`
105
+ calls that return the full detail.
106
+ - The `kb_check({sourceFiles:[…], includeImpactDiagnostics:true,
107
+ includeWorkingTreeDiff:true})` line (edit only): run it after the edit. The
108
+ Stop hook reminds you once per session of files edited without it.
109
+
110
+ A file already shown this session gets only the one-line `Kibi: this edit to
111
+ <path> is inside <symbol>` update; the facts and tests from its first snippet
112
+ still apply. `Kibi: no symbol in <path> is linked to a requirement yet` means
113
+ the file is unowned: find the governing requirement with `kb_search` and
114
+ `sourceLocations`, then link a symbol to it with `implements` through
115
+ `kb_upsert`. Without a readable manifest (`Kibi: the hook could not read
116
+ .kb/symbols.yaml`) snippets are off for this session; `kb_query({sourceFile})`
117
+ still answers and `kibi sync` regenerates the manifest. The search tip and the
118
+ `.kb/` note appear once per session and need no action.
119
+
85
120
  ## Safe workflow
86
121
 
87
122
  1. Always discover before you mutate: start with `kb_search`, then exact-filter with `kb_query`. Use
@@ -409,6 +444,14 @@ Prolog `symbol-coverage` also requires the test to `validates` the requirement
409
444
  enough. Do not rely on legacy `// implements REQ-<area>-<behavior>` comments as the
410
445
  traceability record.
411
446
 
447
+ Set the symbol's `title` to the behavioral member the extractor can locate in
448
+ `sourceFile`: `ClassName.methodName`, an exported function, or a class, not a
449
+ file or module name. A traceability link (`implements`, `covered_by`, …) from
450
+ a symbol whose `title` names no such member is refused by the granularity gate
451
+ when the file already has granular behavioral symbols, unless the symbol sets
452
+ `granularity_reason` to `config-artifact`, `module-level-behavior`,
453
+ `extractor-miss`, `legacy-link` or `test-suite`.
454
+
412
455
  ## Complete Logical Coverage
413
456
 
414
457
  Decompose all atomic normative clauses into `claim_key`/`claim_text` entries in
@@ -68,14 +68,16 @@ id: FACT-SUSPENDED-PUBLISH-DENIED
68
68
  status: active
69
69
  fact_kind: predicate
70
70
  predicate_name: permission_rule
71
- predicate_args: [suspended_user, publish, articles, deny]
71
+ predicate_args: [suspended_user, publish, article, deny]
72
72
  polarity: deny
73
- canonical_key: permission_rule(suspended_user,publish,articles,deny)
73
+ canonical_key: permission_rule(suspended_user,publish,article,deny)
74
74
  claim_key: <advisor-issued key for this exact clause>
75
75
  claim_text: Suspended users must not publish articles.
76
76
  relationship: { type: requires_predicate, from: REQ-SUSPENDED-PUBLISH-DENIED, to: FACT-SUSPENDED-PUBLISH-DENIED }
77
77
  ```
78
78
 
79
+ Inferred bindings drop the article and singularise the resource (`articles` becomes `article`, `any analysis request` and `analysis requests` both become `analysis_request`), so an allow and a deny on the same actor, action and resource share one key. Grants read the same as `allowed`: `may`, `can`, `is allowed to`, `are permitted to`, `must be able to`, `are able to`, `have permission to` and `are entitled to` all select `permission_rule` with `polarity: assert`.
80
+
79
81
  If another current requirement links an `assert` fact with the same predicate namespace, name, and ordered arguments, `domain-contradictions` reports the pair. More complex semantic conflicts require a shared canonical schema and arguments; do not assume Kibi can prove arbitrary equivalence between differently shaped predicates.
80
82
 
81
83
  The full logical representation of a requirement is the conjunction of all of its linked ground property and predicate terms. Conditions, exceptions, permissions, and ordering constraints remain explicit arguments of their declared schemas. Contradiction detection therefore depends on equivalent prose reusing the same predicate name, argument roles, ordered canonical values, and opposing polarity rather than inventing synonymous terms.
@@ -1 +1 @@
1
- {"version":3,"file":"cli-runtime.d.ts","sourceRoot":"","sources":["../../src/runtime/cli-runtime.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAKV,gBAAgB,EAEhB,cAAc,EACf,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAEL,KAAK,qBAAqB,EAE3B,MAAM,6BAA6B,CAAC;AAOrC,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,qBAAqB,GAChC,MAAM,CAOR;AA2GD,wBAAgB,gBAAgB,CAC9B,OAAO,GAAE,cAAmB,GAC3B,gBAAgB,CA4IlB"}
1
+ {"version":3,"file":"cli-runtime.d.ts","sourceRoot":"","sources":["../../src/runtime/cli-runtime.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAKV,gBAAgB,EAEhB,cAAc,EACf,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAEL,KAAK,qBAAqB,EAE3B,MAAM,6BAA6B,CAAC;AAOrC,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,qBAAqB,GAChC,MAAM,CAOR;AA2GD,wBAAgB,gBAAgB,CAC9B,OAAO,GAAE,cAAmB,GAC3B,gBAAgB,CAgJlB"}
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import { retryAttachAfterBreakingStaleLock } from "../prolog/store-lock.js";
2
+ import { noteDeadHolderBeforeAttach, retryAttachAfterBreakingStaleLock, } from "../prolog/store-lock.js";
3
3
  import { EngineClient } from "../engine.js";
4
4
  import { CapabilityRegistryCache, ensureCapabilityRegistry, } from "../plugins/registry.js";
5
5
  import { nodeFilesystem, nodeGit, nodeNetwork, } from "../public/operations/node-ports.js";
@@ -186,8 +186,13 @@ export function createCliRuntime(options = {}) {
186
186
  await prolog.start?.();
187
187
  const kbPath = attachment.storePath;
188
188
  if (!usesEngine) {
189
+ // implements REQ-core-journaled-engine-persistence
190
+ // A killed holder never makes kb_attach fail, so note it first.
191
+ const recordTakeover = noteDeadHolderBeforeAttach(kbPath);
189
192
  let attached = await prolog.query(`kb_attach('${quoteProlog(kbPath)}')`);
190
- if (!attached.success) {
193
+ if (attached.success)
194
+ recordTakeover();
195
+ else {
191
196
  attached = await retryAttachAfterBreakingStaleLock(prolog, kbPath, attached);
192
197
  }
193
198
  if (!attached.success) {
@@ -66,12 +66,21 @@ export type SearchAnswerVerdict = Readonly<{
66
66
  export type SearchAnswerException = SearchAnswerEntity & Readonly<{
67
67
  approvedBy?: string;
68
68
  }>;
69
+ /**
70
+ * A linked fact says what it states in its typed form, so an agent can read
71
+ * `has_role(alice, admin)` or `user.role_assignment max_roles lte 2` without
72
+ * opening the entity, and `ask` is the exact kb_query that answers precise
73
+ * questions about the same predicate or subject.
74
+ */
75
+ export type SearchAnswerFact = SearchAnswerEntity & Readonly<{
76
+ factKind?: string;
77
+ claim?: string;
78
+ ask?: string;
79
+ }>;
69
80
  export type SearchAnswerRequirement = SearchAnswerEntity & Readonly<{
70
81
  score: number;
71
82
  via: string;
72
- facts: readonly (SearchAnswerEntity & {
73
- factKind?: string;
74
- })[];
83
+ facts: readonly SearchAnswerFact[];
75
84
  scenarios: readonly SearchAnswerEntity[];
76
85
  tests: readonly SearchAnswerTest[];
77
86
  adrs: readonly SearchAnswerEntity[];
@@ -85,6 +94,17 @@ export type SearchAnswerScope = Readonly<{
85
94
  snapshotId: string;
86
95
  syncedAt: string | null;
87
96
  }>;
97
+ /**
98
+ * A matched fact, scenario, test or symbol that no requirement owns, with
99
+ * its 1-based `rank` in the search results. When the top-ranked match is
100
+ * here, the governing list was reached through lower-ranked matches, and
101
+ * the answer says so instead of presenting those requirements as the top
102
+ * match's.
103
+ */
104
+ export type SearchAnswerUngoverned = SearchAnswerFact & Readonly<{
105
+ type: string;
106
+ rank: number;
107
+ }>;
88
108
  export type SearchAnswer = Readonly<{
89
109
  version: "kibi.search-answer.v1";
90
110
  governing: readonly SearchAnswerRequirement[];
@@ -93,6 +113,7 @@ export type SearchAnswer = Readonly<{
93
113
  supersededBy?: string;
94
114
  })[];
95
115
  observations: readonly SearchAnswerEntity[];
116
+ ungoverned: readonly SearchAnswerUngoverned[];
96
117
  scope: SearchAnswerScope;
97
118
  truncated: boolean;
98
119
  note: string;
@@ -110,6 +131,7 @@ export declare const SEARCH_ANSWER_LIMITS: {
110
131
  readonly rationale: 3;
111
132
  readonly notGoverning: 5;
112
133
  readonly observations: 3;
134
+ readonly ungoverned: 5;
113
135
  /** Entity rows loaded per answer, the matched seeds included. */
114
136
  readonly entityLoads: 70;
115
137
  readonly maxBytes: 16384;
@@ -1 +1 @@
1
- {"version":3,"file":"search-answer.d.ts","sourceRoot":"","sources":["../src/search-answer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"search-answer.d.ts","sourceRoot":"","sources":["../src/search-answer.ts"],"names":[],"mappings":"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"}
@@ -43,6 +43,19 @@ const ROW_KEYS = [
43
43
  "approved_by",
44
44
  "source",
45
45
  "sourceFile",
46
+ "predicate_namespace",
47
+ "predicate_name",
48
+ "predicate_args",
49
+ "polarity",
50
+ "subject_key",
51
+ "property_key",
52
+ "operator",
53
+ "value_type",
54
+ "value_string",
55
+ "value_int",
56
+ "value_number",
57
+ "value_bool",
58
+ "unit",
46
59
  ];
47
60
  // implements REQ-kibi-search-answer-layer-v2
48
61
  export const SEARCH_ANSWER_LIMITS = {
@@ -52,6 +65,7 @@ export const SEARCH_ANSWER_LIMITS = {
52
65
  rationale: 3,
53
66
  notGoverning: 5,
54
67
  observations: 3,
68
+ ungoverned: 5,
55
69
  /** Entity rows loaded per answer, the matched seeds included. */
56
70
  entityLoads: 70,
57
71
  maxBytes: 16_384,
@@ -76,6 +90,40 @@ function brief(entity) {
76
90
  ...(status ? { status } : {}),
77
91
  };
78
92
  }
93
+ function queryCall(args) {
94
+ return `kb_query(${JSON.stringify(args)})`;
95
+ }
96
+ /** Typed statement and follow-up query of a linked fact, by fact kind. */
97
+ // implements REQ-kibi-query-fact-filters
98
+ function factDetail(fact) {
99
+ const factKind = text(fact.fact_kind);
100
+ const base = factKind ? { factKind } : {};
101
+ const predicate = text(fact.predicate_name);
102
+ if (factKind === "predicate" && predicate) {
103
+ const args = Array.isArray(fact.predicate_args)
104
+ ? fact.predicate_args.map(text).join(", ")
105
+ : text(fact.predicate_args);
106
+ const polarity = text(fact.polarity) === "deny" ? " [deny]" : "";
107
+ return {
108
+ ...base,
109
+ claim: `${predicate}(${args})${polarity}`,
110
+ ask: queryCall({ predicate }),
111
+ };
112
+ }
113
+ const subjectKey = text(fact.subject_key);
114
+ const propertyKey = text(fact.property_key);
115
+ if (factKind === "property_value" && subjectKey && propertyKey) {
116
+ const unit = text(fact.unit);
117
+ const valueType = text(fact.value_type) || "string";
118
+ const value = text(fact[`value_${valueType}`]);
119
+ const claim = `${subjectKey} ${propertyKey} ${text(fact.operator) || "eq"} ${value}${unit ? ` ${unit}` : ""}`;
120
+ return { ...base, claim, ask: queryCall({ subjectKey, propertyKey }) };
121
+ }
122
+ if (factKind === "subject" && subjectKey) {
123
+ return { ...base, claim: subjectKey, ask: queryCall({ subjectKey }) };
124
+ }
125
+ return base;
126
+ }
79
127
  function edgesGoal(ids) {
80
128
  const idList = ids.map((id) => `'${escapeAtom(id)}'`).join(",");
81
129
  return `findall([Rel,From,To], (member(Rel, [${ANSWER_RELATIONSHIPS.join(",")}]), kb_relationship(Rel, From, To), (member(From, [${idList}]); member(To, [${idList}]))), Edges)`;
@@ -466,11 +514,12 @@ export async function buildSearchAnswer(prolog, matches, options = {}) {
466
514
  candidates.set(owner, { score, via });
467
515
  };
468
516
  const observations = [];
517
+ const ungoverned = [];
469
518
  const rationaleRows = new Map();
470
519
  // A matched test may verify a scenario rather than a requirement; its
471
520
  // targets are resolved to their governing requirements after the loop.
472
521
  const testTargets = [];
473
- for (const match of seeds) {
522
+ for (const [index, match] of seeds.entries()) {
474
523
  const id = text(match.entity.id);
475
524
  const entity = graph.row(id) ?? match.entity;
476
525
  const type = text(entity.type || match.entity.type);
@@ -501,6 +550,18 @@ export async function buildSearchAnswer(prolog, matches, options = {}) {
501
550
  testTargets.push({ id, score: match.score, targets: owners });
502
551
  continue;
503
552
  }
553
+ if (owners.length === 0 && ["fact", "scenario", "symbol"].includes(type)) {
554
+ if (ungoverned.length < limits.ungoverned)
555
+ ungoverned.push({
556
+ ...brief(entity),
557
+ ...(type === "fact" ? factDetail(entity) : {}),
558
+ type,
559
+ rank: index + 1,
560
+ });
561
+ else
562
+ truncated = true;
563
+ continue;
564
+ }
504
565
  for (const owner of owners)
505
566
  propose(owner, match.score * 0.9, `${type} ${id}`);
506
567
  }
@@ -515,6 +576,15 @@ export async function buildSearchAnswer(prolog, matches, options = {}) {
515
576
  });
516
577
  const targetEdges = graph.edgesOf(targetIds);
517
578
  for (const test of testTargets) {
579
+ if (test.targets.length === 0) {
580
+ const row = graph.row(test.id);
581
+ const rank = seedIds.indexOf(test.id) + 1;
582
+ if (row !== null && ungoverned.length < limits.ungoverned)
583
+ ungoverned.push({ ...brief(row), type: "test", rank });
584
+ else if (row !== null)
585
+ truncated = true;
586
+ continue;
587
+ }
518
588
  for (const target of test.targets) {
519
589
  // A target that some requirement specifies is a scenario: lift the
520
590
  // test to that requirement. Anything else is a directly verified
@@ -666,10 +736,10 @@ export async function buildSearchAnswer(prolog, matches, options = {}) {
666
736
  ...brief(req.entity),
667
737
  score: req.score,
668
738
  via: req.via,
669
- facts: rowsOf(plan.facts).map((fact) => {
670
- const factKind = text(fact.fact_kind);
671
- return { ...brief(fact), ...(factKind ? { factKind } : {}) };
672
- }),
739
+ facts: rowsOf(plan.facts).map((fact) => ({
740
+ ...brief(fact),
741
+ ...factDetail(fact),
742
+ })),
673
743
  scenarios: rowsOf(plan.scenarios).map(brief),
674
744
  tests: rowsOf(testIds[index] ?? []).map((row) => ({
675
745
  ...brief(row),
@@ -699,20 +769,36 @@ export async function buildSearchAnswer(prolog, matches, options = {}) {
699
769
  ? verdicts.scope.syncedAt
700
770
  : null,
701
771
  };
772
+ ungoverned.sort((left, right) => left.rank - right.rank);
702
773
  const answer = {
703
774
  version: "kibi.search-answer.v1",
704
775
  governing,
705
776
  rationale,
706
777
  notGoverning,
707
778
  observations: observations.slice(0, limits.observations),
779
+ ungoverned,
708
780
  scope,
709
781
  truncated: truncated || graph.truncated,
710
- note: governing.length === 0
711
- ? "No current requirement matched or owns a matched entity. Absence here is not evidence that nothing governs the change; refine the query or pass sourceLocations."
712
- : "Governing requirements are current requirements matched by the query or linked to a matched entity. Links are discovery, not proof. A verdict lists the contradiction and scenario-feasibility witnesses that name the requirement; none means no check named it, not that it is proven. Use kb_check and kb_coverage for consistency and proof status.",
782
+ note: answerNote(governing.length, ungoverned),
713
783
  };
714
784
  return fitSearchAnswer(answer, limits.maxBytes);
715
785
  }
786
+ // implements REQ-search-answer-hygiene
787
+ /**
788
+ * How to read the layer. When the top-ranked match is owned by no
789
+ * requirement, the note says so first: the governing list, if any, came from
790
+ * lower-ranked matches and is not the top match's requirement.
791
+ */
792
+ function answerNote(governingCount, ungoverned) {
793
+ const top = ungoverned.find((entry) => entry.rank === 1);
794
+ const lead = top === undefined
795
+ ? ""
796
+ : `The top-ranked match ${top.id} (${top.type}) is linked to no requirement${governingCount === 0 ? "" : "; the governing requirements below were reached through lower-ranked matches"}. `;
797
+ return (lead +
798
+ (governingCount === 0
799
+ ? "No current requirement matched or owns a matched entity. Absence here is not evidence that nothing governs the change; refine the query or pass sourceLocations."
800
+ : "Governing requirements are current requirements matched by the query or linked to a matched entity. Links are discovery, not proof. A verdict lists the contradiction and scenario-feasibility witnesses that name the requirement; none means no check named it, not that it is proven. Use kb_check and kb_coverage for consistency and proof status."));
801
+ }
716
802
  function byteSize(value) {
717
803
  return Buffer.byteLength(JSON.stringify(value), "utf8");
718
804
  }
@@ -737,6 +823,7 @@ function withoutExcerpt(entity) {
737
823
  const ANSWER_TRIM_ORDER = [
738
824
  "observations",
739
825
  "notGoverning",
826
+ "ungoverned",
740
827
  "rationale",
741
828
  ];
742
829
  const REQUIREMENT_TRIM_ORDER = [
@@ -791,6 +878,7 @@ export function fitSearchAnswer(answer, maxBytes = SEARCH_ANSWER_LIMITS.maxBytes
791
878
  rationale: current.rationale.map((adr) => clipRationale(adr, titleChars)),
792
879
  notGoverning: clipAll(current.notGoverning),
793
880
  observations: clipAll(current.observations),
881
+ ungoverned: clipAll(current.ungoverned),
794
882
  };
795
883
  // Excerpts are the most expendable text: drop them before any entry.
796
884
  for (let index = current.rationale.length - 1; index >= 0 && over(); index -= 1) {
@@ -1 +1 @@
1
- {"version":3,"file":"branch-store.d.ts","sourceRoot":"","sources":["../../src/utils/branch-store.ts"],"names":[],"mappings":"AA0BA,mEAAmE;AACnE,eAAO,MAAM,yBAAyB,8BAA8B,CAAC;AACrE,yCAAyC;AACzC,eAAO,MAAM,8BAA8B,8BAA8B,CAAC;AAE1E,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,SAAS,GACT,YAAY,GACZ,YAAY,CAAC;AAEjB,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,KAAK,EAAE,gBAAgB,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC,CAAC;AAEH;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,MAAM,GACb,qBAAqB,CA0GvB;AAED;;;;GAIG;AAEH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,GAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAiBhC;AAED;;;;;;GAMG;AAEH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EACjB,GAAG,GAAE,IAAiB,GACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAehC;AAED,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,qBAAqB,GAChC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAWhC;AA6BD;;;;GAIG;AAEH,wBAAgB,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAOpE;AAED,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,oEAAoE;IACpE,QAAQ,EAAE,OAAO,CAAC;IAClB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC,CAAC;AAEH;;;;;;GAMG;AAEH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,qBAAqB,GAChC,sBAAsB,CAwBxB;AAkBD;;;;;;GAMG;AAEH,wBAAgB,2BAA2B,CACzC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,qBAAqB,EACjC,MAAM,EAAE,MAAM,GACb,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAwBhC"}
1
+ {"version":3,"file":"branch-store.d.ts","sourceRoot":"","sources":["../../src/utils/branch-store.ts"],"names":[],"mappings":"AA2BA,mEAAmE;AACnE,eAAO,MAAM,yBAAyB,8BAA8B,CAAC;AACrE,yCAAyC;AACzC,eAAO,MAAM,8BAA8B,8BAA8B,CAAC;AAE1E,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,SAAS,GACT,YAAY,GACZ,YAAY,CAAC;AAEjB,MAAM,MAAM,qBAAqB,GAAG,QAAQ,CAAC;IAC3C,KAAK,EAAE,gBAAgB,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAAC,CAAC;AAEH;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,aAAa,EAAE,MAAM,EACrB,MAAM,EAAE,MAAM,GACb,qBAAqB,CA0GvB;AAED;;;;GAIG;AAEH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,GAChB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAiBhC;AAED;;;;;;GAMG;AAEH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EACjB,GAAG,GAAE,IAAiB,GACrB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAmBhC;AAED,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,qBAAqB,GAChC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAWhC;AA6BD;;;;GAIG;AAEH,wBAAgB,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAOpE;AAED,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,oEAAoE;IACpE,QAAQ,EAAE,OAAO,CAAC;IAClB,0EAA0E;IAC1E,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC,CAAC;AAEH;;;;;;GAMG;AAEH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,qBAAqB,GAChC,sBAAsB,CAwBxB;AAkBD;;;;;;GAMG;AAEH,wBAAgB,2BAA2B,CACzC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,qBAAqB,EACjC,MAAM,EAAE,MAAM,GACb,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAwBhC"}
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { existsSync, readFileSync, readdirSync, statSync, } from "node:fs";
6
6
  import * as path from "node:path";
7
- import { STORE_LOCK_TAKEOVER_VISIBLE_MS, classifyStoreLockHolder, readStoreLockOwner, readStoreLockTakeover, } from "../prolog/store-lock.js";
7
+ import { FOREIGN_STORE_LOCK_REASON_PREFIX, STORE_LOCK_TAKEOVER_VISIBLE_MS, classifyStoreLockHolder, readStoreLockOwner, readStoreLockTakeover, } from "../prolog/store-lock.js";
8
8
  import { branchStoreManifestMatches, branchStorePath, legacyBranchStorePath, } from "./branch-store-locator.js";
9
9
  import { ENTITY_LANES, KB_ROOT } from "./kb-paths.js";
10
10
  /** Stale-reason code of a branch store that was never compiled. */
@@ -170,7 +170,9 @@ export function storeLockTakeoverReason(storePath, now = new Date()) {
170
170
  brokenPid: record.brokenPid,
171
171
  reason: record.reason,
172
172
  at: record.at,
173
- detail: `Kibi broke a stale branch-store lock at ${record.at} (${record.reason || "holder no longer running"}) and reattached. Nothing needs repair; a holder that died while it held the store usually means an engine or kibi prove run crashed or was killed.`,
173
+ detail: `Kibi broke a stale branch-store lock at ${record.at} (${record.reason || "holder no longer running"}) and reattached. Nothing needs repair; ${record.reason.startsWith(FOREIGN_STORE_LOCK_REASON_PREFIX)
174
+ ? "the lock files were copied with the .kb directory (for example by cp -a) while another workspace's engine held its own store."
175
+ : "a holder that died while it held the store usually means an engine or kibi prove run crashed or was killed."}`,
174
176
  };
175
177
  }
176
178
  export function branchStoreReason(inspection) {
@@ -1 +1 @@
1
- {"version":3,"file":"content-freshness.d.ts","sourceRoot":"","sources":["../../src/utils/content-freshness.ts"],"names":[],"mappings":"AAYA,uDAAuD;AACvD,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAIxD,CAAC;AAEH,KAAK,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AA6IhD;;;GAGG;AAEH,wBAAgB,yBAAyB,CACvC,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAkBvC"}
1
+ {"version":3,"file":"content-freshness.d.ts","sourceRoot":"","sources":["../../src/utils/content-freshness.ts"],"names":[],"mappings":"AAYA,uDAAuD;AACvD,eAAO,MAAM,yBAAyB,EAAE,WAAW,CAAC,MAAM,CAIxD,CAAC;AAEH,KAAK,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAyJhD;;;GAGG;AAEH,wBAAgB,yBAAyB,CACvC,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAkBvC"}
@@ -7,7 +7,7 @@ import { existsSync, readFileSync } from "node:fs";
7
7
  import * as path from "node:path";
8
8
  import { load as parseYAML } from "js-yaml";
9
9
  import { hashManifestWithCoordinates } from "../commands/sync/cache.js";
10
- import { isSymbolsManifestPath } from "./kb-paths.js";
10
+ import { KB_PATHS, isSymbolsManifestPath } from "./kb-paths.js";
11
11
  /** Stale-reason codes that compare file times only. */
12
12
  export const MTIME_SOURCE_REASON_CODES = new Set([
13
13
  "indexed_source_newer",
@@ -35,6 +35,7 @@ function record(value) {
35
35
  * source hashes, and the per-symbol `sourceHash` of the generated coordinate
36
36
  * artifact. A file whose bytes match that evidence has not changed since it
37
37
  * was compiled, however new its mtime (a fresh clone, a checkout, a touch).
38
+ * Without either kind of evidence a reason is kept.
38
39
  */
39
40
  class CompiledContentEvidence {
40
41
  workspaceRoot;
@@ -85,10 +86,19 @@ class CompiledContentEvidence {
85
86
  if (this.coordinateHashes)
86
87
  return this.coordinateHashes;
87
88
  const byFile = new Map();
89
+ // The coordinate artifact is content evidence in its own right: kb_upsert
90
+ // and sync record each symbol's source hash there. Consult the canonical
91
+ // artifact even when its manifest was never hashed (an untracked,
92
+ // upsert-only `.kb/symbols.yaml` that sync could not see).
93
+ const artifacts = new Set([
94
+ path.resolve(this.workspaceRoot, KB_PATHS.symbolCoordinates),
95
+ ]);
88
96
  for (const key of Object.keys(this.hashes)) {
89
97
  if (!isSymbolsManifestPath(key) || !key.endsWith("symbols.yaml"))
90
98
  continue;
91
- const artifact = path.resolve(this.workspaceRoot, path.dirname(key), "symbol-coordinates.yaml");
99
+ artifacts.add(path.resolve(this.workspaceRoot, path.dirname(key), "symbol-coordinates.yaml"));
100
+ }
101
+ for (const artifact of artifacts) {
92
102
  if (!existsSync(artifact))
93
103
  continue;
94
104
  try {
@@ -0,0 +1,9 @@
1
+ import { type Document } from "yaml";
2
+ /**
3
+ * Render the `symbols` sequence of a parsed manifest, and every collection
4
+ * inside it, as block YAML. `kibi init` writes `symbols: []`, a flow
5
+ * sequence, and kibi-cli before 2.18.0 appended to it, so older manifests
6
+ * keep every symbol on that one line. Returns true when the layout changed.
7
+ */
8
+ export declare function blockifySymbolManifest(doc: Document): boolean;
9
+ //# sourceMappingURL=symbol-manifest-layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbol-manifest-layout.d.ts","sourceRoot":"","sources":["../../src/utils/symbol-manifest-layout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAA8B,MAAM,MAAM,CAAC;AAEjE;;;;;GAKG;AAEH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,QAAQ,GAAG,OAAO,CAO7D"}
@@ -0,0 +1,18 @@
1
+ import { isCollection, isSeq, visit } from "yaml";
2
+ /**
3
+ * Render the `symbols` sequence of a parsed manifest, and every collection
4
+ * inside it, as block YAML. `kibi init` writes `symbols: []`, a flow
5
+ * sequence, and kibi-cli before 2.18.0 appended to it, so older manifests
6
+ * keep every symbol on that one line. Returns true when the layout changed.
7
+ */
8
+ // implements REQ-kibi-hooks-flow-manifest, REQ-core-persistence
9
+ export function blockifySymbolManifest(doc) {
10
+ const symbols = doc.get("symbols", true);
11
+ if (!isSeq(symbols) || symbols.flow !== true)
12
+ return false;
13
+ visit(symbols, (_key, node) => {
14
+ if (isCollection(node))
15
+ node.flow = false;
16
+ });
17
+ return true;
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-cli",
3
- "version": "2.18.0",
3
+ "version": "2.20.0",
4
4
  "type": "module",
5
5
  "description": "Kibi CLI for knowledge base management",
6
6
  "engines": {
@@ -235,8 +235,8 @@
235
235
  "fast-glob": "^3.2.12",
236
236
  "gray-matter": "^4.0.3",
237
237
  "js-yaml": "^4.1.0",
238
- "kibi-core": "^0.19.0",
239
- "kibi-plugin-builtin": "^0.5.0",
238
+ "kibi-core": "^0.21.0",
239
+ "kibi-plugin-builtin": "^0.5.1",
240
240
  "kibi-plugin-sdk": "^0.5.0",
241
241
  "kibi-swipl": "^1.0.1",
242
242
  "saxes": "^6.0.0",