kibi-cli 2.29.0 → 2.30.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 (81) hide show
  1. package/dist/cli-operation-metadata.d.ts +8 -8
  2. package/dist/cli-operation-metadata.js +8 -8
  3. package/dist/cli-register-reporting.d.ts.map +1 -1
  4. package/dist/cli-register-reporting.js +12 -0
  5. package/dist/commands/check.d.ts.map +1 -1
  6. package/dist/commands/check.js +4 -0
  7. package/dist/commands/signoff.d.ts +37 -0
  8. package/dist/commands/signoff.d.ts.map +1 -0
  9. package/dist/commands/signoff.js +152 -0
  10. package/dist/operations/modeling/predicate-applicability.d.ts +5 -1
  11. package/dist/operations/modeling/predicate-applicability.d.ts.map +1 -1
  12. package/dist/operations/modeling/predicate-applicability.js +35 -2
  13. package/dist/operations/modeling/suggest-predicates.d.ts.map +1 -1
  14. package/dist/operations/modeling/suggest-predicates.js +3 -1
  15. package/dist/operations/mutation/contradictions.d.ts +7 -5
  16. package/dist/operations/mutation/contradictions.d.ts.map +1 -1
  17. package/dist/operations/mutation/contradictions.js +8 -6
  18. package/dist/operations/mutation/source-authoring.d.ts +15 -0
  19. package/dist/operations/mutation/source-authoring.d.ts.map +1 -1
  20. package/dist/operations/mutation/source-authoring.js +19 -7
  21. package/dist/operations/mutation/types.d.ts +5 -0
  22. package/dist/operations/mutation/types.d.ts.map +1 -1
  23. package/dist/operations/mutation/upsert.d.ts +14 -0
  24. package/dist/operations/mutation/upsert.d.ts.map +1 -1
  25. package/dist/operations/mutation/upsert.js +42 -36
  26. package/dist/operations/mutation/validate-upsert.d.ts.map +1 -1
  27. package/dist/operations/mutation/validate-upsert.js +61 -37
  28. package/dist/public/operations/catalog.d.ts +29 -8
  29. package/dist/public/operations/catalog.d.ts.map +1 -1
  30. package/dist/public/operations/contracts.d.ts.map +1 -1
  31. package/dist/public/operations/contracts.js +2 -0
  32. package/dist/public/operations/specs/bootstrap.d.ts +1 -1
  33. package/dist/public/operations/specs/bootstrap.js +1 -1
  34. package/dist/public/operations/specs/composite.d.ts +1 -1
  35. package/dist/public/operations/specs/composite.js +1 -1
  36. package/dist/public/operations/specs/discovery.d.ts +1 -1
  37. package/dist/public/operations/specs/discovery.js +1 -1
  38. package/dist/public/operations/specs/impact-review-preparation.d.ts +2 -1
  39. package/dist/public/operations/specs/impact-review-preparation.d.ts.map +1 -1
  40. package/dist/public/operations/specs/impact-review-preparation.js +2 -1
  41. package/dist/public/operations/specs/planning.d.ts +1 -1
  42. package/dist/public/operations/specs/planning.js +1 -1
  43. package/dist/public/operations/specs/reporting.d.ts +23 -3
  44. package/dist/public/operations/specs/reporting.d.ts.map +1 -1
  45. package/dist/public/operations/specs/reporting.js +97 -21
  46. package/dist/public/skills/kibi-commit-review/SKILL.md +125 -0
  47. package/dist/public/skills/kibi-commit-review/resources/staged-gate.md +98 -0
  48. package/dist/public/skills/kibi-decisions/SKILL.md +130 -0
  49. package/dist/public/skills/kibi-decisions/resources/decision-records.md +142 -0
  50. package/dist/public/skills/kibi-decisions/resources/signoff-views.md +66 -0
  51. package/dist/public/skills/kibi-freshness/SKILL.md +2 -0
  52. package/dist/public/skills/kibi-traceability/SKILL.md +4 -0
  53. package/dist/public/skills/kibi-usage/SKILL.md +20 -15
  54. package/dist/signoff/collect.d.ts +39 -0
  55. package/dist/signoff/collect.d.ts.map +1 -0
  56. package/dist/signoff/collect.js +398 -0
  57. package/dist/signoff/html.d.ts +4 -0
  58. package/dist/signoff/html.d.ts.map +1 -0
  59. package/dist/signoff/html.js +210 -0
  60. package/dist/signoff/markdown.d.ts +20 -0
  61. package/dist/signoff/markdown.d.ts.map +1 -0
  62. package/dist/signoff/markdown.js +203 -0
  63. package/dist/signoff/model.d.ts +134 -0
  64. package/dist/signoff/model.d.ts.map +1 -0
  65. package/dist/signoff/model.js +138 -0
  66. package/package.json +2 -2
  67. package/src/public/operations/contracts.ts +2 -0
  68. package/src/public/operations/specs/bootstrap.ts +1 -1
  69. package/src/public/operations/specs/composite.ts +1 -1
  70. package/src/public/operations/specs/discovery.ts +1 -1
  71. package/src/public/operations/specs/impact-review-preparation.ts +3 -1
  72. package/src/public/operations/specs/planning.ts +1 -1
  73. package/src/public/operations/specs/reporting.ts +109 -21
  74. package/src/public/skills/kibi-commit-review/SKILL.md +125 -0
  75. package/src/public/skills/kibi-commit-review/resources/staged-gate.md +98 -0
  76. package/src/public/skills/kibi-decisions/SKILL.md +130 -0
  77. package/src/public/skills/kibi-decisions/resources/decision-records.md +142 -0
  78. package/src/public/skills/kibi-decisions/resources/signoff-views.md +66 -0
  79. package/src/public/skills/kibi-freshness/SKILL.md +2 -0
  80. package/src/public/skills/kibi-traceability/SKILL.md +4 -0
  81. package/src/public/skills/kibi-usage/SKILL.md +20 -15
@@ -0,0 +1,203 @@
1
+ import { SIGNOFF_CHANGE_LABELS, SIGNOFF_FENCES, approvalNumbers, approvalSentence, awaitsApproval, proofLabel, questionKind, questionOpen, statementSentenceDiff, } from "./model.js";
2
+ /** Rows shown in chat before the rest is left to the HTML view. */
3
+ export const SIGNOFF_MARKDOWN_LIMIT = 15;
4
+ const CHANGE_ORDER = [
5
+ "new",
6
+ "changed",
7
+ "affected",
8
+ "retired",
9
+ ];
10
+ function quote(text) {
11
+ return `> ${text.replace(/\s+/g, " ").trim()}`;
12
+ }
13
+ /**
14
+ * A rewritten statement as lines under a list item: the new statement with
15
+ * added sentences in bold and removed ones struck through when sentences
16
+ * survive the rewrite, or the old and new statements otherwise.
17
+ */
18
+ function rewriteLines(previous, statement, labels = ["Was", "Now"]) {
19
+ const diff = statementSentenceDiff(previous, statement);
20
+ if (diff === undefined) {
21
+ return [
22
+ `- ${labels[0]}:`,
23
+ ` ${quote(previous)}`,
24
+ `- ${labels[1]}:`,
25
+ ` ${quote(statement)}`,
26
+ ];
27
+ }
28
+ const added = diff.filter((part) => part.change === "added").length;
29
+ const removed = diff.filter((part) => part.change === "removed").length;
30
+ const counts = [
31
+ added > 0 ? `${added} added in bold` : undefined,
32
+ removed > 0 ? `${removed} removed struck through` : undefined,
33
+ ].filter(Boolean);
34
+ const text = diff
35
+ .map((part) => part.change === "added"
36
+ ? `**${part.text}**`
37
+ : part.change === "removed"
38
+ ? `~~${part.text}~~`
39
+ : part.text)
40
+ .join(" ");
41
+ return [
42
+ `- ${labels[1]}, sentences ${counts.join(", ")}:`,
43
+ ` ${quote(text)}`,
44
+ ];
45
+ }
46
+ function firmnessTag(firmness) {
47
+ return firmness === "firm"
48
+ ? "firm"
49
+ : firmness === "exploratory"
50
+ ? "exploratory"
51
+ : "default";
52
+ }
53
+ function originLine(requirement) {
54
+ const parts = [];
55
+ if (requirement.originKind)
56
+ parts.push(requirement.originKind);
57
+ if (requirement.originRef)
58
+ parts.push(`"${requirement.originRef}"`);
59
+ if (requirement.approvedBy)
60
+ parts.push(`approved by ${requirement.approvedBy}`);
61
+ if (awaitsApproval(requirement))
62
+ parts.push("**awaiting your approval**");
63
+ return parts.length > 0 ? `- Origin: ${parts.join(", ")}` : undefined;
64
+ }
65
+ function requirementBlock(requirement, number) {
66
+ const lines = [
67
+ `**${number}. ${requirement.id}** · ${firmnessTag(requirement.firmness)} · ${requirement.title}`,
68
+ ];
69
+ if (requirement.previousStatement !== undefined) {
70
+ lines.push(...rewriteLines(requirement.previousStatement, requirement.statement));
71
+ }
72
+ else {
73
+ lines.push(quote(requirement.statement));
74
+ }
75
+ const origin = originLine(requirement);
76
+ if (origin)
77
+ lines.push(origin);
78
+ for (const superseded of requirement.supersedes) {
79
+ lines.push(`- Replaces ${superseded.id} (${firmnessTag(superseded.firmness)}): ${superseded.statement}`);
80
+ }
81
+ if (requirement.symbols.length > 0) {
82
+ lines.push(`- Changed code: ${requirement.symbols.join(", ")}`);
83
+ }
84
+ if (requirement.change !== "retired") {
85
+ lines.push(requirement.scenarios.length > 0
86
+ ? `- Scenarios: ${requirement.scenarios.map((scenario) => (scenario.title === scenario.id ? scenario.id : `${scenario.id} (${scenario.title})`)).join("; ")}`
87
+ : "- Scenarios: none yet");
88
+ lines.push(`- Proof: ${proofLabel(requirement.proof)}`);
89
+ for (const conflict of requirement.proof?.contradictions ?? []) {
90
+ lines.push(`- Contradiction: ${conflict}`);
91
+ }
92
+ }
93
+ return lines;
94
+ }
95
+ function headline(summary) {
96
+ const counts = CHANGE_ORDER.map((change) => [
97
+ change,
98
+ summary.requirements.filter((row) => row.change === change).length,
99
+ ])
100
+ .filter(([, count]) => count > 0)
101
+ .map(([change, count]) => `${count} ${change}`);
102
+ return counts.length === 0
103
+ ? "## Requirements signoff: no requirement changes"
104
+ : `## Requirements signoff: ${counts.join(", ")}`;
105
+ }
106
+ // implements REQ-kibi-signoff-views
107
+ /** The end-of-work summary as Markdown, without the fence. */
108
+ export function renderSummaryMarkdownBody(summary, limit = SIGNOFF_MARKDOWN_LIMIT) {
109
+ const lines = [headline(summary)];
110
+ const approvals = approvalNumbers(CHANGE_ORDER.flatMap((change) => summary.requirements.filter((row) => row.change === change)));
111
+ if (approvals.length > 0)
112
+ lines.push("", `**${approvalSentence(approvals)}**`);
113
+ let number = 0;
114
+ for (const change of CHANGE_ORDER) {
115
+ const rows = summary.requirements.filter((row) => row.change === change);
116
+ const shown = rows.slice(0, Math.max(0, limit - number));
117
+ if (shown.length === 0)
118
+ continue;
119
+ lines.push("", `### ${SIGNOFF_CHANGE_LABELS[change]}`);
120
+ for (const row of shown) {
121
+ number += 1;
122
+ lines.push("", ...requirementBlock(row, number));
123
+ }
124
+ }
125
+ const hidden = summary.requirements.length - limit;
126
+ if (hidden > 0) {
127
+ lines.push("", `…and ${hidden} more. Run \`kibi signoff --format html --open\` to see them all.`);
128
+ }
129
+ if (approvals.length > 0) {
130
+ lines.push("", 'Reply by number, for example "approve 1" or "1: reword …".');
131
+ }
132
+ for (const note of summary.notes)
133
+ lines.push("", `Note: ${note}`);
134
+ return lines.join("\n");
135
+ }
136
+ // implements REQ-kibi-signoff-views
137
+ /** The mid-work decision questions as Markdown, without the fence. */
138
+ export function renderQuestionsMarkdownBody(view) {
139
+ const open = view.questions.filter(questionOpen).length;
140
+ const answered = view.questions.length - open;
141
+ const lines = [
142
+ `## Decisions to confirm: ${open} open${answered > 0 ? `, ${answered} answered` : ""}`,
143
+ ];
144
+ for (const question of view.questions) {
145
+ const title = question.title ?? question.current?.title ?? question.id;
146
+ const heading = `${question.number}. ${questionKind(question)}${title ? `: ${title}` : ""}`;
147
+ lines.push("");
148
+ if (!questionOpen(question)) {
149
+ lines.push(`~~**${heading}**~~ · answered: ${question.answer}`);
150
+ continue;
151
+ }
152
+ lines.push(`**${heading}**${question.firmness ? ` · would be ${question.firmness}` : ""}`);
153
+ if (question.current) {
154
+ lines.push(...rewriteLines(question.current.statement, question.statement, [
155
+ `Recorded now (${firmnessTag(question.current.firmness)})`,
156
+ "Proposed",
157
+ ]));
158
+ }
159
+ else {
160
+ lines.push(quote(question.statement));
161
+ }
162
+ if (question.quote)
163
+ lines.push(`- Your words: "${question.quote}"`);
164
+ if (question.governs?.length) {
165
+ lines.push(`- Governs: ${question.governs.join(", ")}`);
166
+ }
167
+ for (const conflict of question.conflicts) {
168
+ lines.push(`- Conflicts with ${conflict.id} (${firmnessTag(conflict.firmness)}): ${conflict.statement}`);
169
+ }
170
+ if (question.question)
171
+ lines.push(`- Question: ${question.question}`);
172
+ }
173
+ if (open > 0) {
174
+ lines.push("", 'Reply by number, for example "1 yes, 2 exploratory, 3 reword: …".');
175
+ }
176
+ for (const note of view.notes)
177
+ lines.push("", `Note: ${note}`);
178
+ return lines.join("\n");
179
+ }
180
+ // implements REQ-kibi-signoff-views
181
+ /**
182
+ * A signoff view as plain Markdown for a chat reply or a messaging tool
183
+ * (headings, quotes and lists that any Markdown surface renders), with no
184
+ * fence for a host renderer to find.
185
+ */
186
+ export function renderSignoffChatMarkdown(view) {
187
+ return view.kind === "summary"
188
+ ? renderSummaryMarkdownBody(view)
189
+ : renderQuestionsMarkdownBody(view);
190
+ }
191
+ // implements REQ-kibi-signoff-views
192
+ /**
193
+ * A signoff view as the fenced block agents paste into chat. Host renderers
194
+ * (the Claude Code display hook, the OpenCode TUI) key on the fence's info
195
+ * string; hosts without one show it as a code block, so they get `chat`.
196
+ */
197
+ export function renderSignoffMarkdown(view) {
198
+ const fence = SIGNOFF_FENCES[view.kind];
199
+ const body = renderSignoffChatMarkdown(view);
200
+ // A body line starting with ``` would close the fence early.
201
+ const safe = body.replace(/^```/gm, "'''");
202
+ return `\`\`\`${fence}\n${safe}\n\`\`\`\n`;
203
+ }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Requirement signoff views. A person signs off requirements at two moments:
3
+ * mid-work, when the agent asks them to confirm rules before recording them
4
+ * (questions), and at the end of the work, before the commit (summary). Both
5
+ * views are one JSON document that every host renders: the CLI prints it as a
6
+ * fenced Markdown block or a self-contained HTML page, and editor plugins read
7
+ * the copies written to `.kb/briefs/signoff/` (latest.json, latest.md and
8
+ * latest.html; `.kb/briefs/` is git-ignored and never synced).
9
+ */
10
+ export declare const SIGNOFF_VERSION = "kibi.signoff.v1";
11
+ /** The fence info strings host renderers key on. */
12
+ export declare const SIGNOFF_FENCES: {
13
+ readonly summary: "kibi-signoff";
14
+ readonly questions: "kibi-questions";
15
+ };
16
+ /** Where the latest signoff view is written, relative to the workspace. */
17
+ export declare const SIGNOFF_BRIEF_DIR = ".kb/briefs/signoff";
18
+ export declare const SIGNOFF_LATEST_JSON = ".kb/briefs/signoff/latest.json";
19
+ /** The latest view's Markdown body (no fence), for plain-text host views. */
20
+ export declare const SIGNOFF_LATEST_MARKDOWN = ".kb/briefs/signoff/latest.md";
21
+ /** The latest view as a self-contained page, for browsers and webviews. */
22
+ export declare const SIGNOFF_LATEST_HTML = ".kb/briefs/signoff/latest.html";
23
+ export type SignoffFirmness = "firm" | "default" | "exploratory";
24
+ /** How a requirement entered the change. */
25
+ export type SignoffChange = "new" | "changed" | "affected" | "retired";
26
+ export type SignoffStatementRef = Readonly<{
27
+ id: string;
28
+ title: string;
29
+ statement: string;
30
+ firmness?: SignoffFirmness;
31
+ }>;
32
+ export type SignoffProof = Readonly<{
33
+ /** Coverage proof status: proven, missing, unresolved or not_applicable. */
34
+ status: string;
35
+ /** The earliest unmet proof gate, or "proven". */
36
+ gate: string;
37
+ testCount: number;
38
+ contradictions: readonly string[];
39
+ }>;
40
+ export type SignoffRequirement = Readonly<{
41
+ id: string;
42
+ title: string;
43
+ change: SignoffChange;
44
+ status: string;
45
+ statement: string;
46
+ /** The statement at the base commit, when the change rewrote it. */
47
+ previousStatement?: string;
48
+ firmness?: SignoffFirmness;
49
+ originKind?: string;
50
+ originRef?: string;
51
+ approvedBy?: string;
52
+ supersedes: readonly SignoffStatementRef[];
53
+ scenarios: readonly Readonly<{
54
+ id: string;
55
+ title: string;
56
+ }>[];
57
+ /** Changed code symbols that implement it (affected requirements). */
58
+ symbols: readonly string[];
59
+ proof?: SignoffProof;
60
+ source: string;
61
+ }>;
62
+ export type SignoffSummary = Readonly<{
63
+ version: typeof SIGNOFF_VERSION;
64
+ kind: "summary";
65
+ scope: "staged" | "working";
66
+ generatedAt: string;
67
+ branch?: string;
68
+ requirements: readonly SignoffRequirement[];
69
+ /** Parts of the view Kibi could not compute, in plain words. */
70
+ notes: readonly string[];
71
+ }>;
72
+ /** One decision the agent asks the person to confirm. */
73
+ export type SignoffQuestionInput = Readonly<{
74
+ /** Existing or proposed requirement id. */
75
+ id?: string;
76
+ title?: string;
77
+ /** The rule as it would be recorded. */
78
+ statement: string;
79
+ /** The person's words that would go in origin.ref. */
80
+ quote?: string;
81
+ firmness?: SignoffFirmness;
82
+ /** Files or symbols the rule governs. */
83
+ governs?: readonly string[];
84
+ /** Recorded requirements the rule conflicts with. */
85
+ conflictsWith?: readonly string[];
86
+ /** What the agent asks, when it is more than "record this?". */
87
+ question?: string;
88
+ /** The person's answer, once given. */
89
+ answer?: string;
90
+ }>;
91
+ export type SignoffQuestion = SignoffQuestionInput & Readonly<{
92
+ number: number;
93
+ /** The recorded requirement this question would change. */
94
+ current?: SignoffStatementRef;
95
+ conflicts: readonly SignoffStatementRef[];
96
+ }>;
97
+ export type SignoffQuestions = Readonly<{
98
+ version: typeof SIGNOFF_VERSION;
99
+ kind: "questions";
100
+ generatedAt: string;
101
+ questions: readonly SignoffQuestion[];
102
+ notes: readonly string[];
103
+ }>;
104
+ export type SignoffView = SignoffSummary | SignoffQuestions;
105
+ export declare const SIGNOFF_CHANGE_LABELS: Readonly<Record<SignoffChange, string>>;
106
+ export declare const SIGNOFF_GATE_LABELS: Readonly<Record<string, string>>;
107
+ /** A short proof label for one requirement. */
108
+ export declare function proofLabel(proof: SignoffProof | undefined): string;
109
+ /** Whether a question still waits for the person. */
110
+ export declare function questionOpen(question: SignoffQuestionInput): boolean;
111
+ /** The kind of decision a question asks for. */
112
+ export declare function questionKind(question: SignoffQuestion): string;
113
+ /** One sentence of a rewritten statement and how the rewrite changed it. */
114
+ export type StatementSentence = Readonly<{
115
+ text: string;
116
+ change: "kept" | "added" | "removed";
117
+ }>;
118
+ /**
119
+ * A rewrite of a statement as its sentences in reading order, each kept,
120
+ * added or removed (a longest-common-subsequence diff), so the person reads
121
+ * what changed instead of comparing two paragraphs. Undefined when no
122
+ * sentence survives the rewrite, where before and after reads better.
123
+ */
124
+ export declare function statementSentenceDiff(previous: string, statement: string): readonly StatementSentence[] | undefined;
125
+ /**
126
+ * Whether the change asks the person to approve a requirement: the agent
127
+ * wrote or rewrote it and nobody has approved it.
128
+ */
129
+ export declare function awaitsApproval(requirement: SignoffRequirement): boolean;
130
+ /** The numbers (in view order) of the requirements awaiting approval. */
131
+ export declare function approvalNumbers(ordered: readonly SignoffRequirement[]): number[];
132
+ /** One sentence naming the requirements that wait for the person. */
133
+ export declare function approvalSentence(numbers: readonly number[]): string;
134
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../src/signoff/model.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Requirement signoff views. A person signs off requirements at two moments:
3
+ * mid-work, when the agent asks them to confirm rules before recording them
4
+ * (questions), and at the end of the work, before the commit (summary). Both
5
+ * views are one JSON document that every host renders: the CLI prints it as a
6
+ * fenced Markdown block or a self-contained HTML page, and editor plugins read
7
+ * the copies written to `.kb/briefs/signoff/` (latest.json, latest.md and
8
+ * latest.html; `.kb/briefs/` is git-ignored and never synced).
9
+ */
10
+ export const SIGNOFF_VERSION = "kibi.signoff.v1";
11
+ /** The fence info strings host renderers key on. */
12
+ export const SIGNOFF_FENCES = {
13
+ summary: "kibi-signoff",
14
+ questions: "kibi-questions",
15
+ };
16
+ /** Where the latest signoff view is written, relative to the workspace. */
17
+ export const SIGNOFF_BRIEF_DIR = ".kb/briefs/signoff";
18
+ export const SIGNOFF_LATEST_JSON = `${SIGNOFF_BRIEF_DIR}/latest.json`;
19
+ /** The latest view's Markdown body (no fence), for plain-text host views. */
20
+ export const SIGNOFF_LATEST_MARKDOWN = `${SIGNOFF_BRIEF_DIR}/latest.md`;
21
+ /** The latest view as a self-contained page, for browsers and webviews. */
22
+ export const SIGNOFF_LATEST_HTML = `${SIGNOFF_BRIEF_DIR}/latest.html`;
23
+ export const SIGNOFF_CHANGE_LABELS = {
24
+ new: "New",
25
+ changed: "Changed",
26
+ affected: "Affected by the code change (statement unchanged)",
27
+ retired: "Superseded or retired",
28
+ };
29
+ export const SIGNOFF_GATE_LABELS = {
30
+ semantic: "semantic model incomplete",
31
+ scenario: "no scenario yet",
32
+ implementation: "no owning code yet",
33
+ e2e: "no end-to-end test yet",
34
+ evidence: "end-to-end evidence not current",
35
+ proven: "proven",
36
+ };
37
+ /** A short proof label for one requirement. */
38
+ export function proofLabel(proof) {
39
+ if (proof === undefined)
40
+ return "not checked";
41
+ if (proof.status === "not_applicable")
42
+ return "not applicable";
43
+ const gate = SIGNOFF_GATE_LABELS[proof.gate] ?? proof.gate;
44
+ const tests = `${proof.testCount} ${proof.testCount === 1 ? "test" : "tests"}`;
45
+ return proof.gate === "proven" ? `proven, ${tests}` : `${gate}, ${tests}`;
46
+ }
47
+ /** Whether a question still waits for the person. */
48
+ export function questionOpen(question) {
49
+ return (question.answer ?? "").trim() === "";
50
+ }
51
+ /** The kind of decision a question asks for. */
52
+ export function questionKind(question) {
53
+ if (question.current)
54
+ return `Change ${question.current.id}`;
55
+ if (question.conflicts.length > 0)
56
+ return "Conflict with a recorded rule";
57
+ return "New rule";
58
+ }
59
+ function sentences(statement) {
60
+ return statement
61
+ .replace(/\s+/g, " ")
62
+ .trim()
63
+ .split(/(?<=[.!?])\s+(?=[A-Z0-9"'`(])/)
64
+ .filter((sentence) => sentence.length > 0);
65
+ }
66
+ // implements REQ-kibi-signoff-views
67
+ /**
68
+ * A rewrite of a statement as its sentences in reading order, each kept,
69
+ * added or removed (a longest-common-subsequence diff), so the person reads
70
+ * what changed instead of comparing two paragraphs. Undefined when no
71
+ * sentence survives the rewrite, where before and after reads better.
72
+ */
73
+ export function statementSentenceDiff(previous, statement) {
74
+ const before = sentences(previous);
75
+ const after = sentences(statement);
76
+ const table = before.map(() => new Array(after.length + 1).fill(0));
77
+ table.push(new Array(after.length + 1).fill(0));
78
+ for (let i = before.length - 1; i >= 0; i -= 1) {
79
+ for (let j = after.length - 1; j >= 0; j -= 1) {
80
+ const row = table[i];
81
+ const next = table[i + 1];
82
+ row[j] =
83
+ before[i] === after[j]
84
+ ? next[j + 1] + 1
85
+ : Math.max(next[j], row[j + 1]);
86
+ }
87
+ }
88
+ if ((table[0]?.[0] ?? 0) === 0)
89
+ return undefined;
90
+ const result = [];
91
+ let i = 0;
92
+ let j = 0;
93
+ while (i < before.length || j < after.length) {
94
+ if (i < before.length && j < after.length && before[i] === after[j]) {
95
+ result.push({ text: after[j], change: "kept" });
96
+ i += 1;
97
+ j += 1;
98
+ }
99
+ else if (j < after.length &&
100
+ (i >= before.length ||
101
+ table[i]?.[j + 1] >= table[i + 1]?.[j])) {
102
+ result.push({ text: after[j], change: "added" });
103
+ j += 1;
104
+ }
105
+ else {
106
+ result.push({ text: before[i], change: "removed" });
107
+ i += 1;
108
+ }
109
+ }
110
+ return result;
111
+ }
112
+ // implements REQ-kibi-signoff-views
113
+ /**
114
+ * Whether the change asks the person to approve a requirement: the agent
115
+ * wrote or rewrote it and nobody has approved it.
116
+ */
117
+ export function awaitsApproval(requirement) {
118
+ return ((requirement.change === "new" || requirement.change === "changed") &&
119
+ requirement.originKind === "agent" &&
120
+ !requirement.approvedBy);
121
+ }
122
+ // implements REQ-kibi-signoff-views
123
+ /** The numbers (in view order) of the requirements awaiting approval. */
124
+ export function approvalNumbers(ordered) {
125
+ return ordered.flatMap((requirement, index) => awaitsApproval(requirement) ? [index + 1] : []);
126
+ }
127
+ // implements REQ-kibi-signoff-views
128
+ /** One sentence naming the requirements that wait for the person. */
129
+ export function approvalSentence(numbers) {
130
+ if (numbers.length === 0)
131
+ return "";
132
+ const list = numbers.length === 1
133
+ ? `${numbers[0]}`
134
+ : `${numbers.slice(0, -1).join(", ")} and ${numbers.at(-1)}`;
135
+ return numbers.length === 1
136
+ ? `Requirement ${list} was written by the agent and waits for your approval.`
137
+ : `Requirements ${list} were written by the agent and wait for your approval.`;
138
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-cli",
3
- "version": "2.29.0",
3
+ "version": "2.30.0",
4
4
  "type": "module",
5
5
  "description": "Kibi CLI for knowledge base management",
6
6
  "engines": {
@@ -235,7 +235,7 @@
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.22.4",
238
+ "kibi-core": "^0.22.5",
239
239
  "kibi-plugin-builtin": "^0.5.3",
240
240
  "kibi-plugin-sdk": "^0.5.0",
241
241
  "kibi-swipl": "^1.0.1",
@@ -652,6 +652,7 @@ const BASE_DATA_SCHEMAS: Readonly<
652
652
  warnings: valueArray,
653
653
  semanticAdvisor: nullableJsonSchema(recordValue),
654
654
  normalizedPreview: nullableJsonSchema(recordValue),
655
+ errorCode: stringValue,
655
656
  stagedFacts: stringArray,
656
657
  whatIf: recordValue,
657
658
  }),
@@ -683,6 +684,7 @@ const BASE_DATA_SCHEMAS: Readonly<
683
684
  warnings: valueArray,
684
685
  semanticAdvisor: nullableJsonSchema(recordValue),
685
686
  normalizedPreview: nullableJsonSchema(recordValue),
687
+ errorCode: stringValue,
686
688
  stagedFacts: stringArray,
687
689
  whatIf: recordValue,
688
690
  dryRun: { const: true },
@@ -20,7 +20,7 @@ export const planBootstrapSpec = {
20
20
  name: "kb_plan_bootstrap",
21
21
  cliName: "plan-bootstrap",
22
22
  description:
23
- "Generate a deterministic, snapshot-bound kibi.bootstrap-plan.v1 for repository onboarding. Read-only analysis of the repository plus any declared knowledge sources and cited intent claims returns evidence, bounded context questions, exact dependency-ordered actions, a canonical plan hash, and no mutation side effects.",
23
+ "Generate a deterministic, snapshot-bound kibi.bootstrap-plan.v1 for repository onboarding. Use it only to onboard a repository whose KB is empty or incomplete (the kibi-bootstrap skill drives it); for a change in an onboarded project use kb_compile_intent. Read-only analysis of the repository plus any declared knowledge sources and cited intent claims returns evidence, bounded context questions, exact dependency-ordered actions, a canonical plan hash, and no mutation side effects.",
24
24
  businessInputSchema: {
25
25
  type: "object",
26
26
  properties: {
@@ -217,7 +217,7 @@ export const modelSpec = {
217
217
  name: "kb_model",
218
218
  cliName: "model",
219
219
  description:
220
- "Turn requirement prose into checkable structure without writing the KB. mode:analyze returns the semantic advisor receipt: the clause ledger, what is grounded, what is ambiguous and suggested modeling (pass clauses for compound prose and optional typed kibi.logic.v1 interpretations). mode:requirement converts one claim (subjectKey, propertyKey, operator, value, or typed logic) into a strict-lane write set and applyPlan. mode:predicates ranks ontology predicate schemas and returns an applicable predicate-fact plan only for reviewed bindings, otherwise an ontology-gap observation. With requirementId, mode:predicates also returns a hash-bound plan (fact write plus requires_predicate link) for kb_apply_plan, whose contradictionAnalysis is the what-if over the current KB; other returned plans apply with kb_upsert, and kb_upsert dryRun with plannedFacts previews a requirement before its facts exist.",
220
+ "Turn requirement prose into checkable structure without writing the KB. Use it for one requirement's prose; to plan a whole change request use kb_compile_intent. mode:analyze returns the semantic advisor receipt: the clause ledger, what is grounded, what is ambiguous and suggested modeling (pass clauses for compound prose and optional typed kibi.logic.v1 interpretations). mode:requirement converts one claim (subjectKey, propertyKey, operator, value, or typed logic) into a strict-lane write set and applyPlan. mode:predicates ranks ontology predicate schemas and returns an applicable predicate-fact plan only for reviewed bindings, otherwise an ontology-gap observation. With requirementId, mode:predicates also returns a hash-bound plan (fact write plus requires_predicate link) for kb_apply_plan, whose contradictionAnalysis is the what-if over the current KB; other returned plans apply with kb_upsert, and kb_upsert dryRun with plannedFacts previews a requirement before its facts exist.",
221
221
  businessInputSchema: {
222
222
  type: "object",
223
223
  required: ["mode", "text"],
@@ -259,7 +259,7 @@ export const statusSpec = {
259
259
  name: "kb_status",
260
260
  cliName: "status",
261
261
  description:
262
- "Report current branch, KB snapshot, freshness metadata, schema status, and a typed kibi.migration-plan.v2 action graph. Read-only status inspection with no mutation side effects; damaged stores are diagnosed without starting the engine.",
262
+ "Report current branch, KB snapshot, freshness metadata, schema status, and a typed kibi.migration-plan.v2 action graph. Call it first to orient on a branch or after a failed call; use kb_check for validation findings and kb_coverage for proof status. Read-only status inspection with no mutation side effects; damaged stores are diagnosed without starting the engine.",
263
263
  businessInputSchema: { type: "object", properties: {} },
264
264
  // Status must remain available when the branch store cannot be attached.
265
265
  requiresProlog: false,
@@ -5,13 +5,15 @@ export const prepareImpactReviewSpec = {
5
5
  name: "kb_prepare_impact_review",
6
6
  cliName: "prepare-impact-review",
7
7
  description:
8
- "Prepare complete content-bound impact-review authoring inputs from the current staged snapshot or explicitly selected immutable Git commits. Returns strict reviewer schemas and an unauthored template; it does not decide, approve, sign, stage, or prove the review.",
8
+ "Prepare complete content-bound impact-review authoring inputs from the current staged snapshot or explicitly selected immutable Git commits. Use it when kb_check reports that changed files need an impact review; it does not plan KB changes. Returns strict reviewer schemas and an unauthored template; it does not decide, approve, sign, stage, or prove the review.",
9
9
  businessInputSchema: {
10
10
  type: "object",
11
11
  additionalProperties: false,
12
12
  required: ["scope"],
13
13
  properties: {
14
14
  scope: {
15
+ description:
16
+ 'What to review: {"kind":"staged"} for the staged snapshot, or {"kind":"diff","baseCommit":"<sha>","headCommit":"<sha>"} with full commit hashes.',
15
17
  oneOf: [
16
18
  {
17
19
  type: "object",
@@ -35,7 +35,7 @@ export const compileIntentSpec = {
35
35
  name: "kb_compile_intent",
36
36
  cliName: "compile-intent",
37
37
  description:
38
- "Compile complete change intent into a deterministic, snapshot-bound read-only plan. Reuses intent-aware discovery and semantic modeling, accounts for every proposition, reports contradiction witnesses, proposes traceability links, and emits dependency-ordered kb_upsert-style steps only for resolved typed claims. contradictionAnalysis.outcome is no_conflict only when the check could decide: an unmodeled proposition or a related current requirement without logic leaves it unresolved with reasons and reviewCandidates to compare and judge through conflictReviews. No mutation side effects.",
38
+ "Compile complete change intent into a deterministic, snapshot-bound read-only plan. Use it before editing code for a change request that may touch several requirements; use kb_model for one requirement's prose and kb_plan_bootstrap for first-time onboarding. Reuses intent-aware discovery and semantic modeling, accounts for every proposition, reports contradiction witnesses, proposes traceability links, and emits dependency-ordered kb_upsert-style steps only for resolved typed claims. contradictionAnalysis.outcome is no_conflict only when the check could decide: an unmodeled proposition or a related current requirement without logic leaves it unresolved with reasons and reviewCandidates to compare and judge through conflictReviews. No mutation side effects.",
39
39
  businessInputSchema: {
40
40
  type: "object",
41
41
  required: ["intent", "mode"],