kibi-cursor 2.10.0 → 2.10.1

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kibi-cursor",
3
3
  "displayName": "Kibi",
4
- "version": "2.10.0",
4
+ "version": "2.10.1",
5
5
  "description": "Optional Cursor adapter for Kibi. Requires project-local kibi-cli, kibi-mcp, and kibi-core (SWI-Prolog is bundled on Linux and macOS) before the bundled MCP server can start.",
6
6
  "license": "AGPL-3.0-or-later",
7
7
  "author": {
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # kibi-cursor
2
2
 
3
+ ## 2.10.1
4
+
5
+ ### Patch Changes
6
+
7
+ - ce41f02: Agents now keep a signoff in the pull request it approves. The guidance used to time the signoff as an end-of-work step and said only that an agent must never set `approved_by` itself, so agents often opened the pull request first and then carried the person's answer in a separate follow-up pull request.
8
+ - kibi-usage 2.37.0, kibi-decisions 1.5.0 and kibi-commit-review 1.4.0 (and the signoff-views resource) say to ask for the signoff before opening or merging the pull request, and to record the person's answer (their name in `approved_by` or a `*_waiver_approved_by` field, their words in `origin.ref` or `approval_ref`) on the same branch and pull request. Writing down an answer the person gave is not self-approval. A pull request that only carries a signoff, firmness or waiver approval is ruled out.
9
+ - The stop-hook signoff reminder (Claude Code, Codex, Cursor, ZCode) adds the same rule. So do the `agent-requirement-unapproved` check action, the `dimension_unrepresented` repair hint and the waiver lines of `kibi proof explain`.
10
+
11
+ - 43d4366: Usage telemetry data is cleaner and more complete. Telemetry is now off when the home directory lies inside the temp directory, as eval, packaging and end-to-end harnesses set it, so each harness run no longer counts as a new install; Kibi's own eval and packed-test sandboxes also set `KIBI_TELEMETRY=0`. Events now name the host agent from `CLAUDECODE`, `CURSOR_AGENT`, `CODEX_SANDBOX`, `GEMINI_CLI`, `OPENCODE`, `AI_AGENT` or the MCP client name, carry a hashed host session id in every host, and say whether they come from a hosted cloud workspace. Failed CLI commands and MCP tool results now carry a fixed error category: the result's error code (for example `upsert_dry_run_invalid`), the failing `kibi doctor` checks, or the `kibi migrate` approval problem. `kibi migrate --apply-safe` now prints the current plan hash when the approved hash is missing or out of date.
12
+
13
+ Hooks in Claude Code, Codex, Cursor, ZCode and OpenCode now all send the anonymous `kibi_hook` event (before, only Claude Code did, and only in diagnostic mode for the others), and each hook remembers, as hashes in `telemetry-sessions.json`, which host session last ran in the workspace. CLI and MCP events take the session id from `KIBI_SESSION_ID`, `CLAUDE_CODE_SESSION_ID` or `CODEX_THREAD_ID` when the host exposes one, and otherwise from that workspace marker (at most 30 minutes old), so events from Cursor and OpenCode sessions, which expose no session variable, can be grouped too. Every host's hook also sends the queue when it is due, and right away at the end of a turn.
14
+
15
+ - Updated dependencies [ce41f02]
16
+ - Updated dependencies [43d4366]
17
+ - kibi-agent-core@0.11.0
18
+
3
19
  ## 2.10.0
4
20
 
5
21
  ### Minor Changes
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "kibi",
4
- "version": "2.10.0",
4
+ "version": "2.10.1",
5
5
  "description": "Repo-local, per-branch, queryable knowledge base. Portable Agent Plugin packaging Kibi's Agent Skills and MCP server.",
6
6
  "author": { "name": "Kibi" },
7
7
  "homepage": "https://looted.github.io/kibi/",
@@ -2,7 +2,7 @@
2
2
  id: kibi-commit-review
3
3
  name: kibi-commit-review
4
4
  description: "Use when kibi check --staged or the Kibi pre-commit hook blocks a commit (kibi_impact_evidence_missing, symbols_manifest_stale, symbol_granularity_violation, symbol-coverage, kibi check-generated drift, Staged impact review failed), or when preparing a commit or pull-request impact review. Covers reading the per-file Detail lines, authoring .kb/symbols.yaml symbol entries through kb_upsert, staging the tracked Kibi evidence with git add (symbol coordinates are derived and never staged), kb_prepare_impact_review for a staged or diff scope, the kibi signoff requirements summary to show before a commit, and how merge commits and migration renames are judged."
5
- version: 1.3.0
5
+ version: 1.4.0
6
6
  kibiCompatibility: ">=1.0.0"
7
7
  tags:
8
8
  - kibi
@@ -91,7 +91,11 @@ apps); never attach the `.html` as a file, which chat apps show as raw source;
91
91
  past 15 requirements the Markdown stops and only the page lists them all.
92
92
  Host details: `kibi-decisions`, `resources/signoff-views.md`. `--no-proof` skips the proof lookup, which reads coverage and is slow
93
93
  on a large knowledge base. The summary is not an approval: never set
94
- `approved_by` or `origin.ref` from it.
94
+ `approved_by` or `origin.ref` from it. Show it before you open or merge the
95
+ pull request. When the person answers, record their answer in the same
96
+ change (their name in `approved_by`, their words in `origin.ref` or
97
+ `approval_ref`) and push it to the same pull request; never move a signoff,
98
+ firmness or waiver approval into a separate pull request.
95
99
 
96
100
  ## Impact reviews
97
101
 
@@ -2,7 +2,7 @@
2
2
  id: kibi-decisions
3
3
  name: kibi-decisions
4
4
  description: "Use when the person states a rule or decision (must, never, always, from now on, we decided), when a Kibi hook asks to confirm or warns about an edit to code that implements a firm requirement, when the Stop hook says source files changed but nothing was recorded, when kb_check quotes requirement_statement_unverified, or when a request conflicts with a recorded requirement or ADR. Covers reading firmness and origin, recording a person's rule as firm with origin.ref, superseding with a Why this changed section, the kb_compile_intent unresolved/reviewCandidates/conflictReviews loop, asking before overriding a firm rule, and putting decisions to the person as one numbered kibi signoff --questions view."
5
- version: 1.4.0
5
+ version: 1.5.0
6
6
  kibiCompatibility: ">=1.0.0"
7
7
  tags:
8
8
  - kibi
@@ -115,7 +115,12 @@ again until none is open, then record each rule with their words in
115
115
  `origin.ref`. Fields and outputs: `resources/signoff-views.md`.
116
116
 
117
117
  Never invent words for `origin.ref`, never set `approved_by` yourself, and
118
- mark your own picks `default` and guesses `exploratory`. Payload shapes,
118
+ mark your own picks `default` and guesses `exploratory`. Once the person has
119
+ answered, recording their answer (their name in `approved_by` or a
120
+ `*_waiver_approved_by` field, their words in `origin.ref` or `approval_ref`)
121
+ is yours to do: put it on the branch of the change it approves and push it to
122
+ that pull request, never to a separate one. Ask before you open or merge the
123
+ pull request, so the answer lands in it. Payload shapes,
119
124
  refusal messages and the `kb_compile_intent` review loop are in
120
125
  `resources/decision-records.md`.
121
126
 
@@ -51,7 +51,9 @@ writes and opens the page; `--format json` prints the view.
51
51
  The person answers by number ("1 yes, 2 exploratory, 3 reword: ..."). Copy
52
52
  each answer into `answer`, render again, and record the rules only once every
53
53
  question is answered. An answer is the person's words: quote it in
54
- `origin.ref`; a view itself is never an approval.
54
+ `origin.ref`; a view itself is never an approval. Record the answers on the
55
+ branch of the change they approve and push them to its pull request, never to
56
+ a separate one.
55
57
 
56
58
  ## Summary contents
57
59
 
@@ -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.36.0
5
+ version: 2.37.0
6
6
  kibiCompatibility: ">=1.0.0"
7
7
  tags:
8
8
  - kibi
@@ -372,6 +372,14 @@ human sets `approved_by`; `kb_check` flags approvals an agent recorded without
372
372
  corroboration. Never fill in an approval yourself. Record why a requirement
373
373
  exists in `rationale`, or link the ADR that explains it.
374
374
 
375
+ A signoff belongs to the change it approves. Ask for it before you open or
376
+ merge the pull request, and when the person answers, record their answer
377
+ (`approved_by` or a `*_waiver_approved_by` field set to them, their words in
378
+ `origin.ref` or `approval_ref`) on the same branch and push it to the same pull
379
+ request. Writing down an answer the person gave is not approving it yourself.
380
+ Never open a separate pull request only to carry a signoff, firmness or waiver
381
+ approval.
382
+
375
383
  ## Decision firmness
376
384
 
377
385
  Requirements and ADRs carry `firmness`, which says what you may do with them.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kibi-cursor",
3
3
  "displayName": "Kibi",
4
- "version": "2.10.0",
4
+ "version": "2.10.1",
5
5
  "description": "Optional Cursor adapter for Kibi. Requires project-local kibi-cli, kibi-mcp, and kibi-core (SWI-Prolog is bundled on Linux and macOS) before the bundled MCP server can start.",
6
6
  "license": "AGPL-3.0-or-later",
7
7
  "author": {
@@ -4,7 +4,7 @@ import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { captureReminder, claimCaptureReminder, claimSignoffReminder, confirmFirmEdits, firmEditNotice, firmRequirementsTouched, hasDecisionFlag, holdRuleNote, isRequirementUpsert, markDecisionFlag, recordRequest, settleFirmRequirements, settledFirmRequirements, signoffReminder, takeRuleNote, } from "kibi-agent-core/decision-guard";
7
- import { appendHookUsageRows, editTraces, hookTelemetryEnabled, kbUsageTrace, readPackageVersion, } from "kibi-agent-core/hook-usage-log";
7
+ import { appendHookUsageRows, editTraces, finishHookTelemetry, hookTracesWanted, kbUsageTrace, readPackageVersion, } from "kibi-agent-core/hook-usage-log";
8
8
  import { stampKibiWorkspace } from "kibi-agent-core/kb-mcp-tools";
9
9
  import { createEntitySummarizer, editFocus, fileKnowledgeSnippet, implementedRequirementIds, } from "kibi-agent-core/snippets";
10
10
  import { preEditGuidance, readGuidance, writeGuidance } from "./guidance.js";
@@ -68,7 +68,7 @@ function recordToolTelemetry(input, workspaceRoot, stateDir, startedAt, env) {
68
68
  // rationale: appendHookUsageRows applies the same opt-in; this guard only
69
69
  // skips reading the index when telemetry is off.
70
70
  // Stryker disable next-line ConditionalExpression
71
- if (!hookTelemetryEnabled(env))
71
+ if (!hookTracesWanted(env))
72
72
  return;
73
73
  const kbUsage = kbUsageTrace(input.toolName, input.toolInput);
74
74
  if (!kbUsage && !isKnownEditableTool(input.toolName))
@@ -166,157 +166,89 @@ export async function runHook(rawInput, environment = {}) {
166
166
  stateDir,
167
167
  sessionId: input.conversationId,
168
168
  };
169
- switch (input.event) {
170
- case "sessionStart":
171
- if (!kibiReady) {
172
- const advisory = interfaceAdvisory(initialState.mcpState, workspaceTrusted);
173
- return {
174
- additional_context: advisory
175
- ? `${BOOTSTRAP_REMINDER}\n${advisory}`
176
- : BOOTSTRAP_REMINDER,
177
- };
178
- }
179
- {
180
- const advisory = interfaceAdvisory(initialState.mcpState, workspaceTrusted);
181
- return advisory ? { additional_context: advisory } : emptyResult();
182
- }
183
- case "preToolUse": {
184
- // Name the agent's workspace on every Kibi MCP call so the launcher
185
- // answers from it, also for agents working in a git worktree. The
186
- // per-call cwd wins over workspace_roots, which can be the window's
187
- // folders rather than the agent's checkout.
188
- const stamped = stampKibiWorkspace(input.toolName, input.toolInput, kibiWorkspaceFor(input.cwd ?? input.workspaceRoots?.[0]));
189
- if (stamped) {
190
- return { updated_input: stamped };
191
- }
192
- const explicitPaths = extractExplicitPathFields(input.toolInput);
193
- const hasDirectKbEdit = isEditLikeTool(input.toolName) && explicitPaths.some(isDirectKbPath);
194
- if (hasDirectKbEdit) {
195
- return {
196
- permission: "allow",
197
- agent_message: DIRECT_KB_EDIT_WARNING,
198
- };
199
- }
200
- const primaryPath = resolvePrimaryPath(input);
201
- if (!primaryPath || !kibiReady || !isKnownEditableTool(input.toolName)) {
202
- return emptyResult();
203
- }
204
- const relativePath = toRepoRelativePath(primaryPath, cwd);
205
- const state = loadHookState(stateDir);
206
- const linked = getSourceLinkedSymbols(cwd, primaryPath, stateDir);
207
- const focus = editFocus(path.resolve(cwd, primaryPath), input.toolInput);
208
- const ruleNote = takeRuleNote(session);
209
- const notice = firmNotice(cwd, linked, focus, session, environment.env ?? process.env);
210
- const guidance = hasGuidedPath(state, "pre-edit", relativePath)
211
- ? undefined
212
- : preEditGuidance(primaryPath, {
213
- cwd,
214
- hasKibi: kibiReady,
215
- mcpState: state.mcpState,
216
- workspaceTrusted,
217
- // A file that implements requirements always gets the shared
218
- // snippet, so the bare linked-requirement wording is never needed.
219
- knowledge: knowledgeFor(cwd, linked, "edit", focus),
220
- });
221
- if (guidance) {
222
- rememberGuidedPath(stateDir, "pre-edit", relativePath);
223
- }
224
- const parts = [ruleNote, notice, guidance].filter((part) => part !== undefined);
225
- return parts.length > 0
226
- ? { permission: "allow", agent_message: parts.join("\n\n") }
227
- : emptyResult();
228
- }
229
- // implements REQ-agent-hosts-decision-guard
230
- case "beforeSubmitPrompt": {
231
- // Cursor's prompt hook cannot add context: the request's rule note
232
- // waits for the next pre-edit message.
233
- if (kibiReady && input.prompt) {
234
- const rule = recordRequest(session, input.prompt);
235
- if (rule !== undefined)
236
- holdRuleNote(session, rule);
237
- }
238
- return { continue: true };
239
- }
240
- case "beforeReadFile": {
241
- const primaryPath = resolvePrimaryPath(input);
242
- if (!primaryPath || !kibiReady) {
243
- return { permission: "allow" };
244
- }
245
- const relativePath = toRepoRelativePath(primaryPath, cwd);
246
- const state = loadHookState(stateDir);
247
- if (hasGuidedPath(state, "read", relativePath)) {
248
- return { permission: "allow" };
249
- }
250
- const guidance = readGuidance(primaryPath, {
251
- cwd,
252
- hasKibi: kibiReady,
253
- mcpState: state.mcpState,
254
- workspaceTrusted,
255
- knowledge: knowledgeFor(cwd, getSourceLinkedSymbols(cwd, primaryPath, stateDir),
256
- // rationale: the snippet only special-cases "edit", so any other
257
- // surface literal renders the same read snippet.
258
- // Stryker disable next-line StringLiteral
259
- "read"),
260
- });
261
- if (!guidance) {
262
- return { permission: "allow" };
263
- }
264
- rememberGuidedPath(stateDir, "read", relativePath);
265
- return {
266
- permission: "allow",
267
- agent_message: guidance,
268
- };
269
- }
270
- case "postToolUse": {
271
- if (kibiReady) {
272
- try {
273
- recordToolTelemetry(input, cwd, stateDir, startedAt, environment.env);
169
+ try {
170
+ switch (input.event) {
171
+ case "sessionStart":
172
+ if (!kibiReady) {
173
+ const advisory = interfaceAdvisory(initialState.mcpState, workspaceTrusted);
174
+ return {
175
+ additional_context: advisory
176
+ ? `${BOOTSTRAP_REMINDER}\n${advisory}`
177
+ : BOOTSTRAP_REMINDER,
178
+ };
274
179
  }
275
- catch {
276
- // Telemetry is best effort and must never change the hook's output.
180
+ {
181
+ const advisory = interfaceAdvisory(initialState.mcpState, workspaceTrusted);
182
+ return advisory ? { additional_context: advisory } : emptyResult();
277
183
  }
278
- }
279
- const kbToolCall = extractKbMcpToolCall(input.toolName, input.toolInput);
280
- if (kbToolCall) {
281
- recordKbMcpTool(stateDir, kbToolCall.toolName, {
282
- impactCheckRun: kbToolCall.impactCheckRun,
283
- sourceFiles: kbToolCall.sourceFiles,
284
- });
285
- }
286
- if (isPlanDeliveryTool(input.toolName)) {
287
- recordPlanDelivered(stateDir);
288
- }
289
- const explicitPaths = extractExplicitPathFields(input.toolInput);
290
- if (isKnownEditableTool(input.toolName)) {
291
- const dirtyPaths = explicitPaths
292
- .map((candidate) => toRepoRelativePath(candidate, cwd))
293
- .filter((candidate) => isMeaningfulTrackedPath(candidate) ||
294
- isKbFreshnessRelevantPath(candidate));
295
- if (dirtyPaths.length > 0) {
296
- addDirtyPaths(stateDir, dirtyPaths);
184
+ case "preToolUse": {
185
+ // Name the agent's workspace on every Kibi MCP call so the launcher
186
+ // answers from it, also for agents working in a git worktree. The
187
+ // per-call cwd wins over workspace_roots, which can be the window's
188
+ // folders rather than the agent's checkout.
189
+ const stamped = stampKibiWorkspace(input.toolName, input.toolInput, kibiWorkspaceFor(input.cwd ?? input.workspaceRoots?.[0]));
190
+ if (stamped) {
191
+ return { updated_input: stamped };
297
192
  }
298
- if (dirtyPaths.some(isSourceImpactRelevantPath)) {
299
- markDecisionFlag(session, "source-edited");
193
+ const explicitPaths = extractExplicitPathFields(input.toolInput);
194
+ const hasDirectKbEdit = isEditLikeTool(input.toolName) && explicitPaths.some(isDirectKbPath);
195
+ if (hasDirectKbEdit) {
196
+ return {
197
+ permission: "allow",
198
+ agent_message: DIRECT_KB_EDIT_WARNING,
199
+ };
300
200
  }
301
- }
302
- if (kbToolCall?.toolName === "kb_upsert" ||
303
- kbUsageTrace(input.toolName, input.toolInput)?.kbOperation ===
304
- "kb_upsert") {
305
- markDecisionFlag(session, "kb-recorded");
306
- // implements REQ-kibi-signoff-views
307
- if (isRequirementUpsert(input.toolInput)) {
308
- markDecisionFlag(session, "requirement-recorded");
201
+ const primaryPath = resolvePrimaryPath(input);
202
+ if (!primaryPath ||
203
+ !kibiReady ||
204
+ !isKnownEditableTool(input.toolName)) {
205
+ return emptyResult();
309
206
  }
207
+ const relativePath = toRepoRelativePath(primaryPath, cwd);
208
+ const state = loadHookState(stateDir);
209
+ const linked = getSourceLinkedSymbols(cwd, primaryPath, stateDir);
210
+ const focus = editFocus(path.resolve(cwd, primaryPath), input.toolInput);
211
+ const ruleNote = takeRuleNote(session);
212
+ const notice = firmNotice(cwd, linked, focus, session, environment.env ?? process.env);
213
+ const guidance = hasGuidedPath(state, "pre-edit", relativePath)
214
+ ? undefined
215
+ : preEditGuidance(primaryPath, {
216
+ cwd,
217
+ hasKibi: kibiReady,
218
+ mcpState: state.mcpState,
219
+ workspaceTrusted,
220
+ // A file that implements requirements always gets the shared
221
+ // snippet, so the bare linked-requirement wording is never needed.
222
+ knowledge: knowledgeFor(cwd, linked, "edit", focus),
223
+ });
224
+ if (guidance) {
225
+ rememberGuidedPath(stateDir, "pre-edit", relativePath);
226
+ }
227
+ const parts = [ruleNote, notice, guidance].filter((part) => part !== undefined);
228
+ return parts.length > 0
229
+ ? { permission: "allow", agent_message: parts.join("\n\n") }
230
+ : emptyResult();
310
231
  }
311
- const primaryPath = resolvePrimaryPath(input);
312
- if (!primaryPath || !kibiReady) {
313
- return emptyResult();
232
+ // implements REQ-agent-hosts-decision-guard
233
+ case "beforeSubmitPrompt": {
234
+ // Cursor's prompt hook cannot add context: the request's rule note
235
+ // waits for the next pre-edit message.
236
+ if (kibiReady && input.prompt) {
237
+ const rule = recordRequest(session, input.prompt);
238
+ if (rule !== undefined)
239
+ holdRuleNote(session, rule);
240
+ }
241
+ return { continue: true };
314
242
  }
315
- const relativePath = toRepoRelativePath(primaryPath, cwd);
316
- const state = loadHookState(stateDir);
317
- if (isReadLikeTool(input.toolName)) {
243
+ case "beforeReadFile": {
244
+ const primaryPath = resolvePrimaryPath(input);
245
+ if (!primaryPath || !kibiReady) {
246
+ return { permission: "allow" };
247
+ }
248
+ const relativePath = toRepoRelativePath(primaryPath, cwd);
249
+ const state = loadHookState(stateDir);
318
250
  if (hasGuidedPath(state, "read", relativePath)) {
319
- return emptyResult();
251
+ return { permission: "allow" };
320
252
  }
321
253
  const guidance = readGuidance(primaryPath, {
322
254
  cwd,
@@ -324,77 +256,158 @@ export async function runHook(rawInput, environment = {}) {
324
256
  mcpState: state.mcpState,
325
257
  workspaceTrusted,
326
258
  knowledge: knowledgeFor(cwd, getSourceLinkedSymbols(cwd, primaryPath, stateDir),
327
- // rationale: as above, only "edit" changes the snippet.
259
+ // rationale: the snippet only special-cases "edit", so any other
260
+ // surface literal renders the same read snippet.
328
261
  // Stryker disable next-line StringLiteral
329
262
  "read"),
330
263
  });
331
264
  if (!guidance) {
332
- return emptyResult();
265
+ return { permission: "allow" };
333
266
  }
334
267
  rememberGuidedPath(stateDir, "read", relativePath);
335
- return { additional_context: guidance };
268
+ return {
269
+ permission: "allow",
270
+ agent_message: guidance,
271
+ };
336
272
  }
337
- if (isEditLikeTool(input.toolName)) {
338
- // rationale: the runner only ever passes "read" or "write"; an empty
339
- // kind selects the same write bucket.
340
- // Stryker disable StringLiteral
341
- if (hasGuidedPath(state, "write", relativePath)) {
342
- return emptyResult();
273
+ case "postToolUse": {
274
+ if (kibiReady) {
275
+ try {
276
+ recordToolTelemetry(input, cwd, stateDir, startedAt, environment.env);
277
+ }
278
+ catch {
279
+ // Telemetry is best effort and must never change the hook's output.
280
+ }
343
281
  }
344
- // Stryker restore
345
- const guidance = writeGuidance(primaryPath, {
346
- cwd,
347
- hasKibi: kibiReady,
348
- mcpState: state.mcpState,
349
- workspaceTrusted,
350
- });
351
- if (!guidance) {
282
+ const kbToolCall = extractKbMcpToolCall(input.toolName, input.toolInput);
283
+ if (kbToolCall) {
284
+ recordKbMcpTool(stateDir, kbToolCall.toolName, {
285
+ impactCheckRun: kbToolCall.impactCheckRun,
286
+ sourceFiles: kbToolCall.sourceFiles,
287
+ });
288
+ }
289
+ if (isPlanDeliveryTool(input.toolName)) {
290
+ recordPlanDelivered(stateDir);
291
+ }
292
+ const explicitPaths = extractExplicitPathFields(input.toolInput);
293
+ if (isKnownEditableTool(input.toolName)) {
294
+ const dirtyPaths = explicitPaths
295
+ .map((candidate) => toRepoRelativePath(candidate, cwd))
296
+ .filter((candidate) => isMeaningfulTrackedPath(candidate) ||
297
+ isKbFreshnessRelevantPath(candidate));
298
+ if (dirtyPaths.length > 0) {
299
+ addDirtyPaths(stateDir, dirtyPaths);
300
+ }
301
+ if (dirtyPaths.some(isSourceImpactRelevantPath)) {
302
+ markDecisionFlag(session, "source-edited");
303
+ }
304
+ }
305
+ if (kbToolCall?.toolName === "kb_upsert" ||
306
+ kbUsageTrace(input.toolName, input.toolInput)?.kbOperation ===
307
+ "kb_upsert") {
308
+ markDecisionFlag(session, "kb-recorded");
309
+ // implements REQ-kibi-signoff-views
310
+ if (isRequirementUpsert(input.toolInput)) {
311
+ markDecisionFlag(session, "requirement-recorded");
312
+ }
313
+ }
314
+ const primaryPath = resolvePrimaryPath(input);
315
+ if (!primaryPath || !kibiReady) {
352
316
  return emptyResult();
353
317
  }
354
- // rationale: same bucket equivalence as the lookup above.
355
- // Stryker disable StringLiteral
356
- rememberGuidedPath(stateDir, "write", relativePath);
357
- // Stryker restore
358
- return { additional_context: guidance };
318
+ const relativePath = toRepoRelativePath(primaryPath, cwd);
319
+ const state = loadHookState(stateDir);
320
+ if (isReadLikeTool(input.toolName)) {
321
+ if (hasGuidedPath(state, "read", relativePath)) {
322
+ return emptyResult();
323
+ }
324
+ const guidance = readGuidance(primaryPath, {
325
+ cwd,
326
+ hasKibi: kibiReady,
327
+ mcpState: state.mcpState,
328
+ workspaceTrusted,
329
+ knowledge: knowledgeFor(cwd, getSourceLinkedSymbols(cwd, primaryPath, stateDir),
330
+ // rationale: as above, only "edit" changes the snippet.
331
+ // Stryker disable next-line StringLiteral
332
+ "read"),
333
+ });
334
+ if (!guidance) {
335
+ return emptyResult();
336
+ }
337
+ rememberGuidedPath(stateDir, "read", relativePath);
338
+ return { additional_context: guidance };
339
+ }
340
+ if (isEditLikeTool(input.toolName)) {
341
+ // rationale: the runner only ever passes "read" or "write"; an empty
342
+ // kind selects the same write bucket.
343
+ // Stryker disable StringLiteral
344
+ if (hasGuidedPath(state, "write", relativePath)) {
345
+ return emptyResult();
346
+ }
347
+ // Stryker restore
348
+ const guidance = writeGuidance(primaryPath, {
349
+ cwd,
350
+ hasKibi: kibiReady,
351
+ mcpState: state.mcpState,
352
+ workspaceTrusted,
353
+ });
354
+ if (!guidance) {
355
+ return emptyResult();
356
+ }
357
+ // rationale: same bucket equivalence as the lookup above.
358
+ // Stryker disable StringLiteral
359
+ rememberGuidedPath(stateDir, "write", relativePath);
360
+ // Stryker restore
361
+ return { additional_context: guidance };
362
+ }
363
+ return emptyResult();
359
364
  }
360
- return emptyResult();
361
- }
362
- case "stop": {
363
- const state = loadHookState(stateDir);
364
- const shouldClearSession = state.dirtyPaths.length > 0 || state.kbCheckRun || state.planDelivered;
365
- if (input.status === "aborted" || input.status === "error") {
365
+ case "stop": {
366
+ const state = loadHookState(stateDir);
367
+ const shouldClearSession = state.dirtyPaths.length > 0 ||
368
+ state.kbCheckRun ||
369
+ state.planDelivered;
370
+ if (input.status === "aborted" || input.status === "error") {
371
+ if (shouldClearSession) {
372
+ clearSessionHookState(stateDir);
373
+ }
374
+ return emptyResult();
375
+ }
376
+ // implements REQ-agent-hosts-decision-guard
377
+ // Source changed and nothing was recorded: the follow-up asks for the
378
+ // decision to be recorded, once per conversation.
379
+ const capture = claimCaptureReminder(session)
380
+ ? captureReminder(hasDecisionFlag(session, "rule-prompt"))
381
+ : undefined;
382
+ // implements REQ-kibi-signoff-views
383
+ // Requirements were written without a signoff view since: ask once.
384
+ // A recorded requirement also counts as recorded knowledge, so this
385
+ // never coincides with the capture request above.
386
+ const signoff = claimSignoffReminder(session, cwd)
387
+ ? signoffReminder()
388
+ : undefined;
389
+ const followupMessage = [capture, signoff, stopFollowupMessage(state)]
390
+ .filter((part) => part !== undefined)
391
+ .join("\n");
392
+ if (followupMessage) {
393
+ clearSessionHookState(stateDir);
394
+ return { followup_message: followupMessage };
395
+ }
366
396
  if (shouldClearSession) {
367
397
  clearSessionHookState(stateDir);
368
398
  }
369
399
  return emptyResult();
370
400
  }
371
- // implements REQ-agent-hosts-decision-guard
372
- // Source changed and nothing was recorded: the follow-up asks for the
373
- // decision to be recorded, once per conversation.
374
- const capture = claimCaptureReminder(session)
375
- ? captureReminder(hasDecisionFlag(session, "rule-prompt"))
376
- : undefined;
377
- // implements REQ-kibi-signoff-views
378
- // Requirements were written without a signoff view since: ask once.
379
- // A recorded requirement also counts as recorded knowledge, so this
380
- // never coincides with the capture request above.
381
- const signoff = claimSignoffReminder(session, cwd)
382
- ? signoffReminder()
383
- : undefined;
384
- const followupMessage = [capture, signoff, stopFollowupMessage(state)]
385
- .filter((part) => part !== undefined)
386
- .join("\n");
387
- if (followupMessage) {
388
- clearSessionHookState(stateDir);
389
- return { followup_message: followupMessage };
390
- }
391
- if (shouldClearSession) {
392
- clearSessionHookState(stateDir);
393
- }
394
- return emptyResult();
401
+ default:
402
+ return emptyResult();
395
403
  }
396
- default:
397
- return emptyResult();
404
+ }
405
+ finally {
406
+ await finishHookTelemetry({
407
+ event: input.event,
408
+ workspaceRoot: kibiReady ? cwd : undefined,
409
+ sessionId: input.conversationId,
410
+ }, environment.env ?? process.env);
398
411
  }
399
412
  }
400
413
  export async function main() {
@@ -4,11 +4,11 @@
4
4
  "kibi-bootstrap/resources/branch-lifecycle.md": "c854264ce888439140883a3fc636db5e522f020dd27b30a5c1ecc4105b64ed13",
5
5
  "kibi-bootstrap/resources/operation-access.md": "b5a6a59e72d800c376e7fad79132897623c32a0ce152a1e8d73f9628b444db51",
6
6
  "kibi-bootstrap/resources/source-authoring.md": "bf296e62f69474e01797d6cb9469bf624551dca834dd188abb6e705bd96fa586",
7
- "kibi-commit-review/SKILL.md": "cd3a0d5ed5e2e7562995180e440dce1aa1607d3d072d3400e966e988725ed399",
7
+ "kibi-commit-review/SKILL.md": "bec768ad3e4cd4e782e9e13360b69bcd40a0964b305909818115d7f9e8d282d9",
8
8
  "kibi-commit-review/resources/staged-gate.md": "37f750f327bb9202c1f30989763f4f1205b906cf672318ca50887d7984bc3e08",
9
- "kibi-decisions/SKILL.md": "02685fa50f093025782986bfe009940e934c3a64d47858920815e7eb66b0c523",
9
+ "kibi-decisions/SKILL.md": "d91f784410251095e9d881bce751728cc4596858a518fb24f5e5d38c940bab50",
10
10
  "kibi-decisions/resources/decision-records.md": "b201e619c48daf6ebe5989991a96f6c0573adb6c37abf814b9d34b7a1cd423c6",
11
- "kibi-decisions/resources/signoff-views.md": "123da13a3f2b30b5672044a1284626e218d3088411f84f2692746f95e06c1393",
11
+ "kibi-decisions/resources/signoff-views.md": "84eb2b7123e54047205b84af6fbf60c910ebcaaf98023e5812f3de9b65a4871c",
12
12
  "kibi-freshness/SKILL.md": "b5c4f24bebdb5b4e0d04c2fa1176fc33ace35334b0c0b6ca943d6f445dc9b69e",
13
13
  "kibi-freshness/resources/branch-lifecycle.md": "ebc01b19fe3dca8df5fc0175978963f6f1204f71f7c805cb2a5fb1b277ac78e4",
14
14
  "kibi-freshness/resources/operation-access.md": "4e1db9294ee540cd29b8b3a2532a81e7894d5b4228b39e99ba48b37212f489f3",
@@ -18,7 +18,7 @@
18
18
  "kibi-traceability/resources/operation-access.md": "39f7972ab7f4ae7479e78f7072bb50e8e6c4cdf4f857425f69d2522d37db437c",
19
19
  "kibi-traceability/resources/source-authoring.md": "408c6ee2bef1b84e0194c0538ef929c26a0a617e631b2234247388140dbbdd08",
20
20
  "kibi-traceability/resources/traceability.md": "38c290a0bc71bd1692e115f22694743946e2f69630e134acfd43cee5f613fb88",
21
- "kibi-usage/SKILL.md": "d5c2e9e2e643f51f068a4e586002976d28125001c33e791e70994efdbbc8130f",
21
+ "kibi-usage/SKILL.md": "24c7148c78501a1fa8d9374e96c9a2c5503d04347c186c958cbd768cd67abd88",
22
22
  "kibi-usage/resources/branch-lifecycle.md": "f1bf359e9826e83c548f3cd0287f8175a62456527b0132b06b3f9f74aa59b7d6",
23
23
  "kibi-usage/resources/fact-lanes.md": "7d487ffb794f6a7c32afabc7756a265a258b072ca83510ddb40d275863d44648",
24
24
  "kibi-usage/resources/kb-improvement.md": "a8408fce27342bdce6415a6718599e53fe00bb33aa1fd3dc42353ec6f61adcd7",
@@ -2,7 +2,7 @@
2
2
  id: kibi-commit-review
3
3
  name: kibi-commit-review
4
4
  description: "Use when kibi check --staged or the Kibi pre-commit hook blocks a commit (kibi_impact_evidence_missing, symbols_manifest_stale, symbol_granularity_violation, symbol-coverage, kibi check-generated drift, Staged impact review failed), or when preparing a commit or pull-request impact review. Covers reading the per-file Detail lines, authoring .kb/symbols.yaml symbol entries through kb_upsert, staging the tracked Kibi evidence with git add (symbol coordinates are derived and never staged), kb_prepare_impact_review for a staged or diff scope, the kibi signoff requirements summary to show before a commit, and how merge commits and migration renames are judged."
5
- version: 1.3.0
5
+ version: 1.4.0
6
6
  kibiCompatibility: ">=1.0.0"
7
7
  tags:
8
8
  - kibi
@@ -91,7 +91,11 @@ apps); never attach the `.html` as a file, which chat apps show as raw source;
91
91
  past 15 requirements the Markdown stops and only the page lists them all.
92
92
  Host details: `kibi-decisions`, `resources/signoff-views.md`. `--no-proof` skips the proof lookup, which reads coverage and is slow
93
93
  on a large knowledge base. The summary is not an approval: never set
94
- `approved_by` or `origin.ref` from it.
94
+ `approved_by` or `origin.ref` from it. Show it before you open or merge the
95
+ pull request. When the person answers, record their answer in the same
96
+ change (their name in `approved_by`, their words in `origin.ref` or
97
+ `approval_ref`) and push it to the same pull request; never move a signoff,
98
+ firmness or waiver approval into a separate pull request.
95
99
 
96
100
  ## Impact reviews
97
101
 
@@ -2,7 +2,7 @@
2
2
  id: kibi-decisions
3
3
  name: kibi-decisions
4
4
  description: "Use when the person states a rule or decision (must, never, always, from now on, we decided), when a Kibi hook asks to confirm or warns about an edit to code that implements a firm requirement, when the Stop hook says source files changed but nothing was recorded, when kb_check quotes requirement_statement_unverified, or when a request conflicts with a recorded requirement or ADR. Covers reading firmness and origin, recording a person's rule as firm with origin.ref, superseding with a Why this changed section, the kb_compile_intent unresolved/reviewCandidates/conflictReviews loop, asking before overriding a firm rule, and putting decisions to the person as one numbered kibi signoff --questions view."
5
- version: 1.4.0
5
+ version: 1.5.0
6
6
  kibiCompatibility: ">=1.0.0"
7
7
  tags:
8
8
  - kibi
@@ -115,7 +115,12 @@ again until none is open, then record each rule with their words in
115
115
  `origin.ref`. Fields and outputs: `resources/signoff-views.md`.
116
116
 
117
117
  Never invent words for `origin.ref`, never set `approved_by` yourself, and
118
- mark your own picks `default` and guesses `exploratory`. Payload shapes,
118
+ mark your own picks `default` and guesses `exploratory`. Once the person has
119
+ answered, recording their answer (their name in `approved_by` or a
120
+ `*_waiver_approved_by` field, their words in `origin.ref` or `approval_ref`)
121
+ is yours to do: put it on the branch of the change it approves and push it to
122
+ that pull request, never to a separate one. Ask before you open or merge the
123
+ pull request, so the answer lands in it. Payload shapes,
119
124
  refusal messages and the `kb_compile_intent` review loop are in
120
125
  `resources/decision-records.md`.
121
126
 
@@ -51,7 +51,9 @@ writes and opens the page; `--format json` prints the view.
51
51
  The person answers by number ("1 yes, 2 exploratory, 3 reword: ..."). Copy
52
52
  each answer into `answer`, render again, and record the rules only once every
53
53
  question is answered. An answer is the person's words: quote it in
54
- `origin.ref`; a view itself is never an approval.
54
+ `origin.ref`; a view itself is never an approval. Record the answers on the
55
+ branch of the change they approve and push them to its pull request, never to
56
+ a separate one.
55
57
 
56
58
  ## Summary contents
57
59
 
@@ -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.36.0
5
+ version: 2.37.0
6
6
  kibiCompatibility: ">=1.0.0"
7
7
  tags:
8
8
  - kibi
@@ -372,6 +372,14 @@ human sets `approved_by`; `kb_check` flags approvals an agent recorded without
372
372
  corroboration. Never fill in an approval yourself. Record why a requirement
373
373
  exists in `rationale`, or link the ADR that explains it.
374
374
 
375
+ A signoff belongs to the change it approves. Ask for it before you open or
376
+ merge the pull request, and when the person answers, record their answer
377
+ (`approved_by` or a `*_waiver_approved_by` field set to them, their words in
378
+ `origin.ref` or `approval_ref`) on the same branch and push it to the same pull
379
+ request. Writing down an answer the person gave is not approving it yourself.
380
+ Never open a separate pull request only to carry a signoff, firmness or waiver
381
+ approval.
382
+
375
383
  ## Decision firmness
376
384
 
377
385
  Requirements and ADRs carry `firmness`, which says what you may do with them.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kibi-cursor",
3
- "version": "2.10.0",
3
+ "version": "2.10.1",
4
4
  "description": "Kibi Cursor plugin - thin adapter for Cursor MCP integration and editor hooks",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -59,7 +59,7 @@
59
59
  }
60
60
  },
61
61
  "dependencies": {
62
- "kibi-agent-core": "^0.10.0"
62
+ "kibi-agent-core": "^0.11.0"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@types/node": "latest",
@@ -4,11 +4,11 @@
4
4
  "kibi-bootstrap/resources/branch-lifecycle.md": "c854264ce888439140883a3fc636db5e522f020dd27b30a5c1ecc4105b64ed13",
5
5
  "kibi-bootstrap/resources/operation-access.md": "b5a6a59e72d800c376e7fad79132897623c32a0ce152a1e8d73f9628b444db51",
6
6
  "kibi-bootstrap/resources/source-authoring.md": "bf296e62f69474e01797d6cb9469bf624551dca834dd188abb6e705bd96fa586",
7
- "kibi-commit-review/SKILL.md": "cd3a0d5ed5e2e7562995180e440dce1aa1607d3d072d3400e966e988725ed399",
7
+ "kibi-commit-review/SKILL.md": "bec768ad3e4cd4e782e9e13360b69bcd40a0964b305909818115d7f9e8d282d9",
8
8
  "kibi-commit-review/resources/staged-gate.md": "37f750f327bb9202c1f30989763f4f1205b906cf672318ca50887d7984bc3e08",
9
- "kibi-decisions/SKILL.md": "02685fa50f093025782986bfe009940e934c3a64d47858920815e7eb66b0c523",
9
+ "kibi-decisions/SKILL.md": "d91f784410251095e9d881bce751728cc4596858a518fb24f5e5d38c940bab50",
10
10
  "kibi-decisions/resources/decision-records.md": "b201e619c48daf6ebe5989991a96f6c0573adb6c37abf814b9d34b7a1cd423c6",
11
- "kibi-decisions/resources/signoff-views.md": "123da13a3f2b30b5672044a1284626e218d3088411f84f2692746f95e06c1393",
11
+ "kibi-decisions/resources/signoff-views.md": "84eb2b7123e54047205b84af6fbf60c910ebcaaf98023e5812f3de9b65a4871c",
12
12
  "kibi-freshness/SKILL.md": "b5c4f24bebdb5b4e0d04c2fa1176fc33ace35334b0c0b6ca943d6f445dc9b69e",
13
13
  "kibi-freshness/resources/branch-lifecycle.md": "ebc01b19fe3dca8df5fc0175978963f6f1204f71f7c805cb2a5fb1b277ac78e4",
14
14
  "kibi-freshness/resources/operation-access.md": "4e1db9294ee540cd29b8b3a2532a81e7894d5b4228b39e99ba48b37212f489f3",
@@ -18,7 +18,7 @@
18
18
  "kibi-traceability/resources/operation-access.md": "39f7972ab7f4ae7479e78f7072bb50e8e6c4cdf4f857425f69d2522d37db437c",
19
19
  "kibi-traceability/resources/source-authoring.md": "408c6ee2bef1b84e0194c0538ef929c26a0a617e631b2234247388140dbbdd08",
20
20
  "kibi-traceability/resources/traceability.md": "38c290a0bc71bd1692e115f22694743946e2f69630e134acfd43cee5f613fb88",
21
- "kibi-usage/SKILL.md": "d5c2e9e2e643f51f068a4e586002976d28125001c33e791e70994efdbbc8130f",
21
+ "kibi-usage/SKILL.md": "24c7148c78501a1fa8d9374e96c9a2c5503d04347c186c958cbd768cd67abd88",
22
22
  "kibi-usage/resources/branch-lifecycle.md": "f1bf359e9826e83c548f3cd0287f8175a62456527b0132b06b3f9f74aa59b7d6",
23
23
  "kibi-usage/resources/fact-lanes.md": "7d487ffb794f6a7c32afabc7756a265a258b072ca83510ddb40d275863d44648",
24
24
  "kibi-usage/resources/kb-improvement.md": "a8408fce27342bdce6415a6718599e53fe00bb33aa1fd3dc42353ec6f61adcd7",
@@ -2,7 +2,7 @@
2
2
  id: kibi-commit-review
3
3
  name: kibi-commit-review
4
4
  description: "Use when kibi check --staged or the Kibi pre-commit hook blocks a commit (kibi_impact_evidence_missing, symbols_manifest_stale, symbol_granularity_violation, symbol-coverage, kibi check-generated drift, Staged impact review failed), or when preparing a commit or pull-request impact review. Covers reading the per-file Detail lines, authoring .kb/symbols.yaml symbol entries through kb_upsert, staging the tracked Kibi evidence with git add (symbol coordinates are derived and never staged), kb_prepare_impact_review for a staged or diff scope, the kibi signoff requirements summary to show before a commit, and how merge commits and migration renames are judged."
5
- version: 1.3.0
5
+ version: 1.4.0
6
6
  kibiCompatibility: ">=1.0.0"
7
7
  tags:
8
8
  - kibi
@@ -91,7 +91,11 @@ apps); never attach the `.html` as a file, which chat apps show as raw source;
91
91
  past 15 requirements the Markdown stops and only the page lists them all.
92
92
  Host details: `kibi-decisions`, `resources/signoff-views.md`. `--no-proof` skips the proof lookup, which reads coverage and is slow
93
93
  on a large knowledge base. The summary is not an approval: never set
94
- `approved_by` or `origin.ref` from it.
94
+ `approved_by` or `origin.ref` from it. Show it before you open or merge the
95
+ pull request. When the person answers, record their answer in the same
96
+ change (their name in `approved_by`, their words in `origin.ref` or
97
+ `approval_ref`) and push it to the same pull request; never move a signoff,
98
+ firmness or waiver approval into a separate pull request.
95
99
 
96
100
  ## Impact reviews
97
101
 
@@ -2,7 +2,7 @@
2
2
  id: kibi-decisions
3
3
  name: kibi-decisions
4
4
  description: "Use when the person states a rule or decision (must, never, always, from now on, we decided), when a Kibi hook asks to confirm or warns about an edit to code that implements a firm requirement, when the Stop hook says source files changed but nothing was recorded, when kb_check quotes requirement_statement_unverified, or when a request conflicts with a recorded requirement or ADR. Covers reading firmness and origin, recording a person's rule as firm with origin.ref, superseding with a Why this changed section, the kb_compile_intent unresolved/reviewCandidates/conflictReviews loop, asking before overriding a firm rule, and putting decisions to the person as one numbered kibi signoff --questions view."
5
- version: 1.4.0
5
+ version: 1.5.0
6
6
  kibiCompatibility: ">=1.0.0"
7
7
  tags:
8
8
  - kibi
@@ -115,7 +115,12 @@ again until none is open, then record each rule with their words in
115
115
  `origin.ref`. Fields and outputs: `resources/signoff-views.md`.
116
116
 
117
117
  Never invent words for `origin.ref`, never set `approved_by` yourself, and
118
- mark your own picks `default` and guesses `exploratory`. Payload shapes,
118
+ mark your own picks `default` and guesses `exploratory`. Once the person has
119
+ answered, recording their answer (their name in `approved_by` or a
120
+ `*_waiver_approved_by` field, their words in `origin.ref` or `approval_ref`)
121
+ is yours to do: put it on the branch of the change it approves and push it to
122
+ that pull request, never to a separate one. Ask before you open or merge the
123
+ pull request, so the answer lands in it. Payload shapes,
119
124
  refusal messages and the `kb_compile_intent` review loop are in
120
125
  `resources/decision-records.md`.
121
126
 
@@ -51,7 +51,9 @@ writes and opens the page; `--format json` prints the view.
51
51
  The person answers by number ("1 yes, 2 exploratory, 3 reword: ..."). Copy
52
52
  each answer into `answer`, render again, and record the rules only once every
53
53
  question is answered. An answer is the person's words: quote it in
54
- `origin.ref`; a view itself is never an approval.
54
+ `origin.ref`; a view itself is never an approval. Record the answers on the
55
+ branch of the change they approve and push them to its pull request, never to
56
+ a separate one.
55
57
 
56
58
  ## Summary contents
57
59
 
@@ -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.36.0
5
+ version: 2.37.0
6
6
  kibiCompatibility: ">=1.0.0"
7
7
  tags:
8
8
  - kibi
@@ -372,6 +372,14 @@ human sets `approved_by`; `kb_check` flags approvals an agent recorded without
372
372
  corroboration. Never fill in an approval yourself. Record why a requirement
373
373
  exists in `rationale`, or link the ADR that explains it.
374
374
 
375
+ A signoff belongs to the change it approves. Ask for it before you open or
376
+ merge the pull request, and when the person answers, record their answer
377
+ (`approved_by` or a `*_waiver_approved_by` field set to them, their words in
378
+ `origin.ref` or `approval_ref`) on the same branch and push it to the same pull
379
+ request. Writing down an answer the person gave is not approving it yourself.
380
+ Never open a separate pull request only to carry a signoff, firmness or waiver
381
+ approval.
382
+
375
383
  ## Decision firmness
376
384
 
377
385
  Requirements and ADRs carry `firmness`, which says what you may do with them.