kibi-opencode 0.15.0 → 0.15.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.
Files changed (57) hide show
  1. package/README.md +4 -12
  2. package/dist/config.d.ts +0 -3
  3. package/dist/config.js +0 -9
  4. package/dist/e2e-coverage-signals.js +1 -3
  5. package/dist/enforcement-policy.d.ts +1 -1
  6. package/dist/enforcement-policy.js +9 -4
  7. package/dist/enforcement-scope.js +1 -3
  8. package/dist/file-entity-links.js +2 -6
  9. package/dist/file-operation-reminders.d.ts +4 -4
  10. package/dist/file-operation-state.js +4 -2
  11. package/dist/init-kibi-alias.js +0 -1
  12. package/dist/init-kibi-capability.js +1 -5
  13. package/dist/kb-freshness-state.js +6 -2
  14. package/dist/kibi-checkpoint-runner.d.ts +1 -1
  15. package/dist/kibi-checkpoint-runner.js +6 -3
  16. package/dist/knowledge-classifier.js +2 -1
  17. package/dist/meaningful-change-classifier.d.ts +1 -1
  18. package/dist/meaningful-change-classifier.js +1 -2
  19. package/dist/plugin-startup.js +0 -1
  20. package/dist/plugin.js +45 -395
  21. package/dist/prompt.d.ts +2 -6
  22. package/dist/prompt.js +21 -84
  23. package/dist/reconcile-engine.d.ts +15 -1
  24. package/dist/reconcile-engine.js +1 -3
  25. package/dist/scheduler.js +16 -4
  26. package/dist/session-edit-state.js +2 -1
  27. package/dist/source-linked-guidance.js +3 -1
  28. package/dist/tui.js +1 -403
  29. package/dist/tui.jsx +1 -113
  30. package/dist/version-metadata.json +1 -1
  31. package/package.json +3 -5
  32. package/dist/brief-delivery-reasons.d.ts +0 -12
  33. package/dist/brief-delivery-reasons.js +0 -132
  34. package/dist/brief-intent.d.ts +0 -41
  35. package/dist/brief-intent.js +0 -142
  36. package/dist/briefing-runtime.d.ts +0 -24
  37. package/dist/briefing-runtime.js +0 -277
  38. package/dist/graph-narrator.d.ts +0 -25
  39. package/dist/graph-narrator.js +0 -408
  40. package/dist/idle-brief-audit.d.ts +0 -36
  41. package/dist/idle-brief-audit.js +0 -186
  42. package/dist/idle-brief-paths.d.ts +0 -6
  43. package/dist/idle-brief-paths.js +0 -120
  44. package/dist/idle-brief-reader.d.ts +0 -37
  45. package/dist/idle-brief-reader.js +0 -163
  46. package/dist/idle-brief-runtime.d.ts +0 -55
  47. package/dist/idle-brief-runtime.js +0 -496
  48. package/dist/idle-brief-store.d.ts +0 -113
  49. package/dist/idle-brief-store.js +0 -262
  50. package/dist/session-fingerprint.d.ts +0 -11
  51. package/dist/session-fingerprint.js +0 -21
  52. package/dist/tui-brief-delivery.d.ts +0 -67
  53. package/dist/tui-brief-delivery.js +0 -279
  54. package/dist/tui-brief-view-model.d.ts +0 -63
  55. package/dist/tui-brief-view-model.js +0 -209
  56. package/dist/utils/brief-marker.d.ts +0 -19
  57. package/dist/utils/brief-marker.js +0 -101
@@ -1,120 +0,0 @@
1
- import * as path from "node:path";
2
- import * as fs from "node:fs";
3
- import { loadBriefConfig } from "kibi-cli/brief-config";
4
- const TUI_SEEN_FILE = ".tui-seen.json";
5
- export function resolveBriefsDir(workspaceRoot) {
6
- return path.join(workspaceRoot, ".kb", "briefs");
7
- }
8
- export function resolveAuditLogPath(workspaceRoot, branch) {
9
- return path.join(workspaceRoot, ".kb", "branches", branch, "audit.log");
10
- }
11
- export function resolveBriefFilePath(workspaceRoot, timestamp) {
12
- return path.join(resolveBriefsDir(workspaceRoot), `${timestamp}_brief.json`);
13
- }
14
- export function resolveTempBriefPath(workspaceRoot, timestamp) {
15
- return path.join(resolveBriefsDir(workspaceRoot), `${timestamp}_brief.json.tmp`);
16
- }
17
- export function atomicWriteBrief(workspaceRoot, timestamp, content) {
18
- const briefsDir = resolveBriefsDir(workspaceRoot);
19
- if (!fs.existsSync(briefsDir)) {
20
- fs.mkdirSync(briefsDir, { recursive: true });
21
- }
22
- const tempPath = resolveTempBriefPath(workspaceRoot, timestamp);
23
- const finalPath = resolveBriefFilePath(workspaceRoot, timestamp);
24
- fs.writeFileSync(tempPath, content, "utf-8");
25
- fs.renameSync(tempPath, finalPath);
26
- }
27
- function extractTimestamp(fileName) {
28
- const match = /^(\d+)_brief\.json$/.exec(fileName);
29
- if (!match)
30
- return null;
31
- const n = Number(match[1]);
32
- return Number.isFinite(n) ? n : null;
33
- }
34
- export function pruneOldBriefs(workspaceRoot, branch) {
35
- const briefsDir = resolveBriefsDir(workspaceRoot);
36
- if (!fs.existsSync(briefsDir))
37
- return;
38
- const shared = loadBriefConfig(workspaceRoot);
39
- const maxPerBranch = Math.max(1, Number(shared.retention?.maxPerBranch ?? 200));
40
- const maxAgeDays = Math.max(1, Number(shared.retention?.maxAgeDays ?? 14));
41
- const keepUnread = shared.retention?.keepUnread ?? true;
42
- const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1000;
43
- const now = Date.now();
44
- const branchFiles = [];
45
- for (const file of fs.readdirSync(briefsDir)) {
46
- if (!file.endsWith("_brief.json") || file.endsWith(".tmp"))
47
- continue;
48
- const ts = extractTimestamp(file);
49
- if (ts === null)
50
- continue;
51
- const fullPath = path.join(briefsDir, file);
52
- let parsed = {};
53
- try {
54
- parsed = JSON.parse(fs.readFileSync(fullPath, "utf-8"));
55
- }
56
- catch {
57
- continue;
58
- }
59
- if (parsed.branch !== branch)
60
- continue;
61
- const nextItem = {
62
- fullPath,
63
- timestamp: ts,
64
- unread: parsed.unread === true,
65
- };
66
- if (typeof parsed.contentHash === "string") {
67
- nextItem.contentHash = parsed.contentHash;
68
- }
69
- branchFiles.push(nextItem);
70
- }
71
- branchFiles.sort((a, b) => b.timestamp - a.timestamp);
72
- const keepSet = new Set();
73
- for (const item of branchFiles.slice(0, maxPerBranch)) {
74
- keepSet.add(item.fullPath);
75
- }
76
- if (keepUnread) {
77
- for (const item of branchFiles) {
78
- if (item.unread)
79
- keepSet.add(item.fullPath);
80
- }
81
- }
82
- for (const item of branchFiles) {
83
- const olderThanThreshold = now - item.timestamp > maxAgeMs;
84
- if (olderThanThreshold && !(keepUnread && item.unread)) {
85
- keepSet.delete(item.fullPath);
86
- }
87
- }
88
- for (const item of branchFiles) {
89
- const shouldDelete = !keepSet.has(item.fullPath);
90
- if (!shouldDelete)
91
- continue;
92
- try {
93
- fs.unlinkSync(item.fullPath);
94
- }
95
- catch {
96
- // best-effort cleanup
97
- }
98
- }
99
- const remainingHashes = new Set(branchFiles
100
- .filter((item) => keepSet.has(item.fullPath))
101
- .map((item) => item.contentHash)
102
- .filter((hash) => typeof hash === "string"));
103
- const seenPath = path.join(briefsDir, TUI_SEEN_FILE);
104
- try {
105
- const parsed = JSON.parse(fs.readFileSync(seenPath, "utf-8"));
106
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
107
- return;
108
- const byBranch = parsed;
109
- const existing = byBranch[branch];
110
- if (!Array.isArray(existing))
111
- return;
112
- byBranch[branch] = existing.filter((entry) => typeof entry === "string" && remainingHashes.has(entry));
113
- const tempPath = `${seenPath}.tmp`;
114
- fs.writeFileSync(tempPath, JSON.stringify(byBranch, null, 2), "utf-8");
115
- fs.renameSync(tempPath, seenPath);
116
- }
117
- catch {
118
- // best-effort cleanup
119
- }
120
- }
@@ -1,37 +0,0 @@
1
- import { type IdleBriefEnvelope } from "./idle-brief-store.js";
2
- export declare function hasTuiSeenBrief(workspaceRoot: string, branch: string, contentHash: string): boolean;
3
- export declare function markBriefTuiSeen(workspaceRoot: string, branch: string, contentHash: string): void;
4
- /**
5
- * Select the latest unread brief for the given branch.
6
- *
7
- * Scans `.kb/briefs/` for `{timestamp}_brief.json` files, ignoring `.tmp` files
8
- * and invalid JSON. Filters by `branch`, supported schema version, and
9
- * `unread === true`. Returns the brief with the highest filename timestamp,
10
- * or null if no unread briefs exist.
11
- */
12
- export declare function selectLatestUnreadBrief(workspaceRoot: string, branch: string): {
13
- envelope: IdleBriefEnvelope;
14
- filePath: string;
15
- } | null;
16
- /**
17
- * Select the latest persisted brief for the given branch, regardless of read status.
18
- *
19
- * Same scanning logic as `selectLatestUnreadBrief` but returns the latest brief
20
- * even if all briefs have been read. Useful for route rendering where the user
21
- * wants to review the most recent brief.
22
- */
23
- export declare function selectLatestPersistedBrief(// implements REQ-opencode-kibi-briefing-v6
24
- workspaceRoot: string, branch: string): {
25
- envelope: IdleBriefEnvelope;
26
- filePath: string;
27
- } | null;
28
- /**
29
- * Atomically mark a brief as read by setting `unread` to false.
30
- *
31
- * Uses the write-to-temp-then-rename pattern to ensure atomicity.
32
- * Preserves ALL other envelope fields (contentHash, auditCursor, etc.).
33
- *
34
- * @param workspaceRoot - The root of the workspace
35
- * @param briefPath - Absolute path to the brief file to mark as read
36
- */
37
- export declare function markBriefRead(workspaceRoot: string, briefPath: string): void;
@@ -1,163 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- import { resolveBriefsDir } from "./idle-brief-paths.js";
4
- import { isIdleBriefEnvelope, } from "./idle-brief-store.js";
5
- const BRIEF_FILENAME_RE = /^(\d+)_brief\.json$/;
6
- const TUI_SEEN_FILE = ".tui-seen.json";
7
- function resolveTuiSeenPath(workspaceRoot) {
8
- return path.join(resolveBriefsDir(workspaceRoot), TUI_SEEN_FILE);
9
- }
10
- function readTuiSeenHashes(workspaceRoot, branch) {
11
- const seenPath = resolveTuiSeenPath(workspaceRoot);
12
- try {
13
- const parsed = JSON.parse(fs.readFileSync(seenPath, "utf-8"));
14
- if (!parsed || typeof parsed !== "object")
15
- return new Set();
16
- const byBranch = parsed;
17
- const values = byBranch[branch];
18
- if (!Array.isArray(values))
19
- return new Set();
20
- return new Set(values.filter((entry) => typeof entry === "string"));
21
- }
22
- catch {
23
- return new Set();
24
- }
25
- }
26
- export function hasTuiSeenBrief(workspaceRoot, branch, contentHash) {
27
- return readTuiSeenHashes(workspaceRoot, branch).has(contentHash);
28
- }
29
- export function markBriefTuiSeen(workspaceRoot, branch, contentHash) {
30
- const briefsDir = resolveBriefsDir(workspaceRoot);
31
- fs.mkdirSync(briefsDir, { recursive: true });
32
- const seenPath = resolveTuiSeenPath(workspaceRoot);
33
- let parsed = {};
34
- try {
35
- const raw = JSON.parse(fs.readFileSync(seenPath, "utf-8"));
36
- if (raw && typeof raw === "object" && !Array.isArray(raw)) {
37
- parsed = raw;
38
- }
39
- }
40
- catch { }
41
- const existing = Array.isArray(parsed[branch]) ? parsed[branch] : [];
42
- const next = [contentHash, ...existing.filter((entry) => entry !== contentHash)].slice(0, 100);
43
- parsed[branch] = next;
44
- const tempPath = `${seenPath}.tmp`;
45
- fs.writeFileSync(tempPath, JSON.stringify(parsed, null, 2), "utf-8");
46
- fs.renameSync(tempPath, seenPath);
47
- }
48
- /**
49
- * Extract the numeric timestamp prefix from a brief filename.
50
- * Returns null if the filename does not match the expected pattern.
51
- */
52
- function extractTimestamp(filename) {
53
- const match = filename.match(BRIEF_FILENAME_RE);
54
- if (!match)
55
- return null;
56
- return Number(match[1]);
57
- }
58
- /**
59
- * Internal shared scanner for brief files.
60
- *
61
- * @param workspaceRoot - The root of the workspace
62
- * @param branch - Branch name to filter by
63
- * @param filterUnread - If true, only return briefs with unread === true
64
- */
65
- function scanBriefs(workspaceRoot, branch, filterUnread) {
66
- const briefsDir = resolveBriefsDir(workspaceRoot);
67
- if (!fs.existsSync(briefsDir)) {
68
- return null;
69
- }
70
- const files = fs.readdirSync(briefsDir);
71
- const candidates = [];
72
- for (const file of files) {
73
- // Ignore .tmp files
74
- if (file.endsWith(".tmp"))
75
- continue;
76
- const timestamp = extractTimestamp(file);
77
- if (timestamp === null)
78
- continue;
79
- const filePath = path.join(briefsDir, file);
80
- let envelope;
81
- try {
82
- const raw = fs.readFileSync(filePath, "utf-8");
83
- const parsed = JSON.parse(raw);
84
- if (!isIdleBriefEnvelope(parsed)) {
85
- continue;
86
- }
87
- envelope = parsed;
88
- }
89
- catch {
90
- // Skip invalid JSON
91
- continue;
92
- }
93
- // Filter by branch, schemaVersion, and optionally unread status
94
- if (envelope.branch === branch &&
95
- (envelope.schemaVersion === "1.0" || envelope.schemaVersion === "2.0") &&
96
- (!filterUnread || envelope.unread === true)) {
97
- candidates.push({ timestamp, envelope, filePath });
98
- }
99
- }
100
- if (candidates.length === 0) {
101
- return null;
102
- }
103
- // Sort by filename timestamp descending — latest first
104
- candidates.sort((a, b) => b.timestamp - a.timestamp);
105
- const latest = candidates[0];
106
- if (!latest) {
107
- return null;
108
- }
109
- return {
110
- envelope: latest.envelope,
111
- filePath: latest.filePath,
112
- };
113
- }
114
- /**
115
- * Select the latest unread brief for the given branch.
116
- *
117
- * Scans `.kb/briefs/` for `{timestamp}_brief.json` files, ignoring `.tmp` files
118
- * and invalid JSON. Filters by `branch`, supported schema version, and
119
- * `unread === true`. Returns the brief with the highest filename timestamp,
120
- * or null if no unread briefs exist.
121
- */
122
- export function selectLatestUnreadBrief(
123
- // implements REQ-opencode-kibi-briefing-v4
124
- workspaceRoot, branch) {
125
- return scanBriefs(workspaceRoot, branch, true);
126
- }
127
- /**
128
- * Select the latest persisted brief for the given branch, regardless of read status.
129
- *
130
- * Same scanning logic as `selectLatestUnreadBrief` but returns the latest brief
131
- * even if all briefs have been read. Useful for route rendering where the user
132
- * wants to review the most recent brief.
133
- */
134
- export function selectLatestPersistedBrief(// implements REQ-opencode-kibi-briefing-v6
135
- workspaceRoot, branch) {
136
- return scanBriefs(workspaceRoot, branch, false);
137
- }
138
- /**
139
- * Atomically mark a brief as read by setting `unread` to false.
140
- *
141
- * Uses the write-to-temp-then-rename pattern to ensure atomicity.
142
- * Preserves ALL other envelope fields (contentHash, auditCursor, etc.).
143
- *
144
- * @param workspaceRoot - The root of the workspace
145
- * @param briefPath - Absolute path to the brief file to mark as read
146
- */
147
- export function markBriefRead(
148
- // implements REQ-opencode-kibi-briefing-v4
149
- workspaceRoot, briefPath) {
150
- const briefsDir = resolveBriefsDir(workspaceRoot);
151
- const resolvedBriefPath = path.resolve(briefPath);
152
- const resolvedBriefsDir = path.resolve(briefsDir);
153
- // Security: ensure the brief path is within the expected briefs directory
154
- if (!resolvedBriefPath.startsWith(resolvedBriefsDir + path.sep)) {
155
- throw new Error(`Invalid brief path: ${briefPath} is not inside ${briefsDir}`);
156
- }
157
- const raw = fs.readFileSync(briefPath, "utf-8");
158
- const brief = JSON.parse(raw);
159
- brief.unread = false;
160
- const tempPath = `${briefPath}.tmp`;
161
- fs.writeFileSync(tempPath, JSON.stringify(brief, null, 2), "utf-8");
162
- fs.renameSync(tempPath, briefPath);
163
- }
@@ -1,55 +0,0 @@
1
- import type { BriefingWorkspaceCtx } from "./briefing-runtime.js";
2
- import type { AuditDelta } from "./idle-brief-audit.js";
3
- import { type IdleBriefEnvelope } from "./idle-brief-store.js";
4
- export interface IdleBriefResult {
5
- success: boolean;
6
- briefPath: string | null;
7
- envelope: IdleBriefEnvelope | null;
8
- }
9
- export interface CheckResult {
10
- violations: Array<{
11
- rule: string;
12
- entityId: string;
13
- description: string;
14
- suggestion?: string;
15
- source?: string;
16
- }>;
17
- count: number;
18
- diagnostics: Array<{
19
- category: string;
20
- severity: string;
21
- message: string;
22
- file?: string;
23
- suggestion?: string;
24
- }>;
25
- }
26
- export interface IdleBriefStatement {
27
- statement: string;
28
- citationIds: string[];
29
- }
30
- export interface IdleBriefingResult {
31
- briefingState: string;
32
- tldr: string;
33
- promptBlock: string;
34
- citations: Array<{
35
- id: string;
36
- type?: string;
37
- title?: string;
38
- source?: string;
39
- textRef?: string;
40
- }>;
41
- constraints?: IdleBriefStatement[];
42
- regressionRisks?: IdleBriefStatement[];
43
- missingEvidence?: IdleBriefStatement[];
44
- }
45
- type IdleBriefRelationship = {
46
- from: string;
47
- to: string;
48
- type: string;
49
- };
50
- export declare function generateIdleBrief(client: unknown, workspaceCtx: BriefingWorkspaceCtx, auditDelta: AuditDelta, sessionId: string, options?: {
51
- sourceFiles?: string[];
52
- changedEntityIds?: string[];
53
- relationships?: IdleBriefRelationship[];
54
- }): Promise<IdleBriefResult>;
55
- export {};