kibi-opencode 0.12.1 → 0.14.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.
- package/README.md +4 -4
- package/dist/config.d.ts +1 -1
- package/dist/config.js +2 -2
- package/dist/enforcement-policy.d.ts +71 -0
- package/dist/enforcement-policy.js +269 -0
- package/dist/enforcement-scope.d.ts +15 -0
- package/dist/enforcement-scope.js +36 -0
- package/dist/file-operation-reminders.d.ts +13 -0
- package/dist/file-operation-reminders.js +24 -37
- package/dist/graph-narrator.d.ts +25 -0
- package/dist/graph-narrator.js +408 -0
- package/dist/guidance-cache.d.ts +3 -0
- package/dist/guidance-cache.js +1 -1
- package/dist/idle-brief-runtime.d.ts +7 -0
- package/dist/idle-brief-runtime.js +24 -6
- package/dist/kibi-checkpoint-runner.d.ts +83 -0
- package/dist/kibi-checkpoint-runner.js +254 -0
- package/dist/plugin.d.ts +1 -0
- package/dist/plugin.js +446 -167
- package/dist/prompt.d.ts +6 -0
- package/dist/prompt.js +25 -0
- package/dist/smart-enforcement.d.ts +6 -2
- package/dist/smart-enforcement.js +7 -1
- package/dist/utils/brief-marker.d.ts +19 -0
- package/dist/utils/brief-marker.js +101 -0
- package/dist/work-context-resolver.d.ts +21 -0
- package/dist/work-context-resolver.js +197 -0
- package/package.json +2 -2
package/dist/prompt.d.ts
CHANGED
|
@@ -47,6 +47,12 @@ export interface PromptContext {
|
|
|
47
47
|
lifecycleReminder: string | null;
|
|
48
48
|
e2eReminder: string | null;
|
|
49
49
|
};
|
|
50
|
+
/** Hard-mode checkpoint block that must take prompt priority over advisory guidance. */
|
|
51
|
+
hardGateBlock?: {
|
|
52
|
+
shownPaths: string[];
|
|
53
|
+
remainingCount: number;
|
|
54
|
+
reason?: string;
|
|
55
|
+
};
|
|
50
56
|
}
|
|
51
57
|
export declare function postureGuidance(posture: RepoPosture, capability?: InitKibiCommandCapability): string | null;
|
|
52
58
|
/**
|
package/dist/prompt.js
CHANGED
|
@@ -112,6 +112,28 @@ function buildBootstrapRequiredBody(capability = getInitKibiCommandCapability())
|
|
|
112
112
|
: "- This host does not support native `/init-kibi` injection. Kibi must fail closed and does not register a fake native alias; use `/kibi:init-kibi:mcp` instead.";
|
|
113
113
|
return `This repository does not appear to have Kibi initialized. Agents should:\n${commandBullet}\n- The workflow uses \`kb_autopilot_generate\` for read-only synthesis; always preview and get approval before writes.\n- Ask the user/operator to run setup or repair outside this session if bootstrap is insufficient.\n\nUse public MCP tools only: \`kb_autopilot_generate\`, \`kb_search\`, \`kb_query\`, \`kb_status\`, \`kb_find_gaps\`, \`kb_coverage\`, \`kb_graph\`, \`kb_upsert\`, \`kb_delete\`, \`kb_check\`.`;
|
|
114
114
|
}
|
|
115
|
+
function buildHardGateBlock(block) {
|
|
116
|
+
const pathLines = block.shownPaths.map((path) => `- \`${path}\``);
|
|
117
|
+
if (block.remainingCount > 0) {
|
|
118
|
+
pathLines.push(`- +${block.remainingCount} more dirty files`);
|
|
119
|
+
}
|
|
120
|
+
const reasonLine = block.reason ? `Reason: ${block.reason}.` : null;
|
|
121
|
+
return [
|
|
122
|
+
"🛑 Kibi hard gate blocked",
|
|
123
|
+
"STOP implementation until this authoritative Kibi checkpoint is satisfied.",
|
|
124
|
+
reasonLine,
|
|
125
|
+
"Affected files:",
|
|
126
|
+
...pathLines,
|
|
127
|
+
"MCP-only recovery steps:",
|
|
128
|
+
"- Run `kb_search` for impacted requirements, tests, ADRs, and facts.",
|
|
129
|
+
"- Run `kb_query` with `sourceFile` for each affected file.",
|
|
130
|
+
"- Run `kb_status` if branch or snapshot freshness matters.",
|
|
131
|
+
"- Run `kb_upsert` for required traceability, relationship, or fact updates.",
|
|
132
|
+
"- Run `kb_check` before continuing.",
|
|
133
|
+
]
|
|
134
|
+
.filter((line) => line !== null)
|
|
135
|
+
.join("\n");
|
|
136
|
+
}
|
|
115
137
|
// ── Guidance blocks by risk class ──────────────────────────────────────
|
|
116
138
|
const GUIDANCE_BY_RISK = {
|
|
117
139
|
safe_docs_only: null,
|
|
@@ -171,6 +193,9 @@ Root .kb/config.json exists but some configured KB targets are missing. Guidance
|
|
|
171
193
|
* Build prompt guidance block based on posture, risk class, and cache state.
|
|
172
194
|
*/
|
|
173
195
|
function buildContextualGuidance(context, capability = getInitKibiCommandCapability()) {
|
|
196
|
+
if (context.hardGateBlock) {
|
|
197
|
+
return `${SENTINEL}\n\n${buildHardGateBlock(context.hardGateBlock)}`;
|
|
198
|
+
}
|
|
174
199
|
const posture = context.posture ?? "root_active";
|
|
175
200
|
const riskClass = context.riskClass;
|
|
176
201
|
const readyAutoBriefingAvailable = context.autoBriefResult?.showManualCue === false;
|
|
@@ -5,14 +5,16 @@ import type { RepoPosture } from "./repo-posture.js";
|
|
|
5
5
|
* - "strict": plugin may escalate targeted checks, completion reminders, and
|
|
6
6
|
* structured logging. Hooks/checks remain the hard enforcement boundary
|
|
7
7
|
* regardless of mode.
|
|
8
|
+
* - "hard": authoritative root-KB postures may hard-block through durable
|
|
9
|
+
* hook/check boundaries, even when maintenance guidance is degraded.
|
|
8
10
|
*/
|
|
9
|
-
export type EffectiveMode = "advisory" | "strict";
|
|
11
|
+
export type EffectiveMode = "advisory" | "strict" | "hard";
|
|
10
12
|
/**
|
|
11
13
|
* Inputs required to determine the effective smart-enforcement mode.
|
|
12
14
|
*/
|
|
13
15
|
export interface ModeInputs {
|
|
14
16
|
/** Configured smart-enforcement mode. */
|
|
15
|
-
mode: "advisory" | "strict";
|
|
17
|
+
mode: "advisory" | "strict" | "hard";
|
|
16
18
|
/** When true, strict mode only activates for authoritative root KB postures. */
|
|
17
19
|
requireRootKbForStrict: boolean;
|
|
18
20
|
/** Current repository posture from detectPosture(). */
|
|
@@ -37,5 +39,7 @@ export declare function isStrictEligible(inputs: ModeInputs): boolean;
|
|
|
37
39
|
* - strict config + requireRootKbForStrict=false → strict may apply to all
|
|
38
40
|
* postures (but hooks/checks remain hard gate regardless)
|
|
39
41
|
* - maintenance-degraded → advisory regardless of config
|
|
42
|
+
* - hard config → hard only for authoritative root-KB postures, even when
|
|
43
|
+
* maintenance is degraded; non-authoritative postures stay advisory
|
|
40
44
|
*/
|
|
41
45
|
export declare function computeEffectiveMode(inputs: ModeInputs): EffectiveMode;
|
|
@@ -30,9 +30,15 @@ export function isStrictEligible(inputs) {
|
|
|
30
30
|
* - strict config + requireRootKbForStrict=false → strict may apply to all
|
|
31
31
|
* postures (but hooks/checks remain hard gate regardless)
|
|
32
32
|
* - maintenance-degraded → advisory regardless of config
|
|
33
|
+
* - hard config → hard only for authoritative root-KB postures, even when
|
|
34
|
+
* maintenance is degraded; non-authoritative postures stay advisory
|
|
33
35
|
*/
|
|
36
|
+
// implements REQ-opencode-worktree-hard-enforcement-v1
|
|
34
37
|
export function computeEffectiveMode(inputs) {
|
|
35
|
-
// implements REQ-opencode-smart-enforcement-v1
|
|
38
|
+
// implements REQ-opencode-smart-enforcement-v1, REQ-opencode-worktree-hard-enforcement-v1
|
|
39
|
+
if (inputs.mode === "hard") {
|
|
40
|
+
return STRICT_ELIGIBLE_POSTURES.has(inputs.posture) ? "hard" : "advisory";
|
|
41
|
+
}
|
|
36
42
|
// Maintenance-degraded always forces advisory
|
|
37
43
|
if (inputs.maintenanceDegraded) {
|
|
38
44
|
return "advisory";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface PendingBriefMarkerIssue {
|
|
2
|
+
filePath: string;
|
|
3
|
+
reason: "parse" | "schema" | "delete";
|
|
4
|
+
}
|
|
5
|
+
export interface PendingBriefMarkersResult {
|
|
6
|
+
entityIds: string[];
|
|
7
|
+
relationships: Array<{
|
|
8
|
+
from: string;
|
|
9
|
+
to: string;
|
|
10
|
+
type: string;
|
|
11
|
+
}>;
|
|
12
|
+
markerPaths: string[];
|
|
13
|
+
issues: PendingBriefMarkerIssue[];
|
|
14
|
+
}
|
|
15
|
+
export declare function loadPendingBriefMarkers(workspaceRoot: string, branch: string): PendingBriefMarkersResult;
|
|
16
|
+
export declare function deletePendingBriefMarkers(markerPaths: string[]): Promise<{
|
|
17
|
+
deletedCount: number;
|
|
18
|
+
issues: PendingBriefMarkerIssue[];
|
|
19
|
+
}>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
// implements REQ-opencode-kibi-briefing-v2
|
|
4
|
+
export function loadPendingBriefMarkers(workspaceRoot, branch) {
|
|
5
|
+
const pendingDir = path.join(workspaceRoot, ".kb", "briefs", "pending");
|
|
6
|
+
if (!fs.existsSync(pendingDir)) {
|
|
7
|
+
return { entityIds: [], relationships: [], markerPaths: [], issues: [] };
|
|
8
|
+
}
|
|
9
|
+
let entries;
|
|
10
|
+
try {
|
|
11
|
+
entries = fs.readdirSync(pendingDir, { withFileTypes: true });
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return { entityIds: [], relationships: [], markerPaths: [], issues: [] };
|
|
15
|
+
}
|
|
16
|
+
const issues = [];
|
|
17
|
+
const markerPaths = [];
|
|
18
|
+
const entityIds = [];
|
|
19
|
+
const seenEntityIds = new Set();
|
|
20
|
+
const relationshipMap = new Map();
|
|
21
|
+
for (const entry of entries
|
|
22
|
+
.filter((dirent) => dirent.isFile() && dirent.name.endsWith(".json"))
|
|
23
|
+
.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
24
|
+
const filePath = path.join(pendingDir, entry.name);
|
|
25
|
+
let payload;
|
|
26
|
+
try {
|
|
27
|
+
payload = parsePendingBriefMarker(JSON.parse(fs.readFileSync(filePath, "utf8")));
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
issues.push({
|
|
31
|
+
filePath,
|
|
32
|
+
reason: error instanceof SyntaxError ? "parse" : "schema",
|
|
33
|
+
});
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (payload.branch !== branch) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
markerPaths.push(filePath);
|
|
40
|
+
for (const entityId of payload.entityIds) {
|
|
41
|
+
if (seenEntityIds.has(entityId)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
seenEntityIds.add(entityId);
|
|
45
|
+
entityIds.push(entityId);
|
|
46
|
+
}
|
|
47
|
+
for (const relationship of payload.relationships) {
|
|
48
|
+
relationshipMap.set(`${relationship.type}\u0000${relationship.from}\u0000${relationship.to}`, relationship);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
entityIds,
|
|
53
|
+
relationships: [...relationshipMap.values()],
|
|
54
|
+
markerPaths,
|
|
55
|
+
issues,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
// implements REQ-opencode-kibi-briefing-v2
|
|
59
|
+
export async function deletePendingBriefMarkers(markerPaths) {
|
|
60
|
+
let deletedCount = 0;
|
|
61
|
+
const issues = [];
|
|
62
|
+
for (const markerPath of markerPaths) {
|
|
63
|
+
try {
|
|
64
|
+
const existedBeforeDelete = fs.existsSync(markerPath);
|
|
65
|
+
await fs.promises.rm(markerPath, { force: true });
|
|
66
|
+
if (existedBeforeDelete && !fs.existsSync(markerPath)) {
|
|
67
|
+
deletedCount += 1;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
issues.push({ filePath: markerPath, reason: "delete" });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { deletedCount, issues };
|
|
75
|
+
}
|
|
76
|
+
function parsePendingBriefMarker(value) {
|
|
77
|
+
if (!value || typeof value !== "object") {
|
|
78
|
+
throw new Error("Invalid marker payload");
|
|
79
|
+
}
|
|
80
|
+
const record = value;
|
|
81
|
+
const branch = typeof record.branch === "string" ? record.branch.trim() : "";
|
|
82
|
+
const entityIds = Array.isArray(record.entityIds)
|
|
83
|
+
? record.entityIds.filter((item) => typeof item === "string" && item.length > 0)
|
|
84
|
+
: null;
|
|
85
|
+
const relationships = Array.isArray(record.relationships)
|
|
86
|
+
? record.relationships
|
|
87
|
+
.filter((item) => !!item &&
|
|
88
|
+
typeof item === "object" &&
|
|
89
|
+
typeof item.from === "string" &&
|
|
90
|
+
typeof item.to === "string" &&
|
|
91
|
+
typeof item.type === "string" &&
|
|
92
|
+
item.from.length > 0 &&
|
|
93
|
+
item.to.length > 0 &&
|
|
94
|
+
item.type.length > 0)
|
|
95
|
+
.map((item) => ({ from: item.from, to: item.to, type: item.type }))
|
|
96
|
+
: [];
|
|
97
|
+
if (!branch || !entityIds) {
|
|
98
|
+
throw new Error("Invalid marker schema");
|
|
99
|
+
}
|
|
100
|
+
return { branch, entityIds, relationships };
|
|
101
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type RepoPosture } from "./repo-posture.js";
|
|
2
|
+
export interface ResolveWorkContextInput {
|
|
3
|
+
inputDirectory: string;
|
|
4
|
+
inputWorktree: string;
|
|
5
|
+
filePath?: string;
|
|
6
|
+
sessionId?: string;
|
|
7
|
+
agentIdentity?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface WorkContext {
|
|
10
|
+
worktreeRoot: string;
|
|
11
|
+
kibiAuthorityRoot: string;
|
|
12
|
+
branch: string;
|
|
13
|
+
repoRelativePath: string;
|
|
14
|
+
posture: RepoPosture;
|
|
15
|
+
isAuthoritative: boolean;
|
|
16
|
+
isLinkedWorktree: boolean;
|
|
17
|
+
sessionId: string | undefined;
|
|
18
|
+
agentIdentity: string;
|
|
19
|
+
}
|
|
20
|
+
declare const resolveWorkContext: (input: ResolveWorkContextInput) => WorkContext;
|
|
21
|
+
export { resolveWorkContext };
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep, } from "node:path";
|
|
3
|
+
import { detectPosture } from "./repo-posture.js";
|
|
4
|
+
function safeStat(path) {
|
|
5
|
+
try {
|
|
6
|
+
return statSync(path);
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function readFirstLine(path) {
|
|
13
|
+
try {
|
|
14
|
+
const [firstLine] = readFileSync(path, "utf8").split(/\r?\n/, 1);
|
|
15
|
+
const trimmed = firstLine?.trim();
|
|
16
|
+
return trimmed && trimmed.length > 0 ? trimmed : null;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function readLinkedGitDir(worktreeRoot, gitFilePath) {
|
|
23
|
+
const firstLine = readFirstLine(gitFilePath);
|
|
24
|
+
if (!firstLine?.startsWith("gitdir:")) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const rawGitDir = firstLine.slice("gitdir:".length).trim();
|
|
28
|
+
if (rawGitDir.length === 0) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return isAbsolute(rawGitDir)
|
|
32
|
+
? resolve(rawGitDir)
|
|
33
|
+
: resolve(worktreeRoot, rawGitDir);
|
|
34
|
+
}
|
|
35
|
+
function readCommonGitDir(gitDir) {
|
|
36
|
+
const rawCommonDir = readFirstLine(join(gitDir, "commondir"));
|
|
37
|
+
if (!rawCommonDir) {
|
|
38
|
+
return gitDir;
|
|
39
|
+
}
|
|
40
|
+
return isAbsolute(rawCommonDir)
|
|
41
|
+
? resolve(rawCommonDir)
|
|
42
|
+
: resolve(gitDir, rawCommonDir);
|
|
43
|
+
}
|
|
44
|
+
function directorySearchStart(candidatePath) {
|
|
45
|
+
const resolved = resolve(candidatePath);
|
|
46
|
+
const stats = safeStat(resolved);
|
|
47
|
+
return stats?.isDirectory() ? resolved : dirname(resolved);
|
|
48
|
+
}
|
|
49
|
+
function findGitMetadata(candidatePath) {
|
|
50
|
+
let current = directorySearchStart(candidatePath);
|
|
51
|
+
while (true) {
|
|
52
|
+
const dotGitPath = join(current, ".git");
|
|
53
|
+
const dotGitStats = safeStat(dotGitPath);
|
|
54
|
+
if (dotGitStats?.isFile()) {
|
|
55
|
+
const gitDir = readLinkedGitDir(current, dotGitPath);
|
|
56
|
+
if (gitDir) {
|
|
57
|
+
return {
|
|
58
|
+
worktreeRoot: current,
|
|
59
|
+
gitDir,
|
|
60
|
+
commonGitDir: readCommonGitDir(gitDir),
|
|
61
|
+
isLinkedWorktree: true,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (dotGitStats?.isDirectory()) {
|
|
66
|
+
return {
|
|
67
|
+
worktreeRoot: current,
|
|
68
|
+
gitDir: dotGitPath,
|
|
69
|
+
commonGitDir: dotGitPath,
|
|
70
|
+
isLinkedWorktree: false,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const parent = dirname(current);
|
|
74
|
+
if (parent === current) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
current = parent;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function hasRootKbConfig(root) {
|
|
81
|
+
return existsSync(join(root, ".kb", "config.json"));
|
|
82
|
+
}
|
|
83
|
+
function uniqueResolved(paths) {
|
|
84
|
+
const seen = new Set();
|
|
85
|
+
const result = [];
|
|
86
|
+
for (const path of paths) {
|
|
87
|
+
if (!path) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const resolved = resolve(path);
|
|
91
|
+
if (seen.has(resolved)) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
seen.add(resolved);
|
|
95
|
+
result.push(resolved);
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
function authorityRootFromCommonGitDir(commonGitDir) {
|
|
100
|
+
return basename(commonGitDir) === ".git" ? dirname(commonGitDir) : null;
|
|
101
|
+
}
|
|
102
|
+
function authorityRootFromLinkedGitDir(gitDir) {
|
|
103
|
+
const normalizedGitDir = resolve(gitDir);
|
|
104
|
+
const marker = `${sep}.git${sep}worktrees${sep}`;
|
|
105
|
+
const markerIndex = normalizedGitDir.indexOf(marker);
|
|
106
|
+
if (markerIndex < 0) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return normalizedGitDir.slice(0, markerIndex);
|
|
110
|
+
}
|
|
111
|
+
function ancestorKbRoots(start) {
|
|
112
|
+
const roots = [];
|
|
113
|
+
let current = resolve(start);
|
|
114
|
+
while (true) {
|
|
115
|
+
if (hasRootKbConfig(current)) {
|
|
116
|
+
roots.push(current);
|
|
117
|
+
}
|
|
118
|
+
const parent = dirname(current);
|
|
119
|
+
if (parent === current) {
|
|
120
|
+
return roots;
|
|
121
|
+
}
|
|
122
|
+
current = parent;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function resolveAuthorityRoot(git, inputDirectory) {
|
|
126
|
+
if (!git.isLinkedWorktree) {
|
|
127
|
+
return git.worktreeRoot;
|
|
128
|
+
}
|
|
129
|
+
const candidates = uniqueResolved([
|
|
130
|
+
authorityRootFromCommonGitDir(git.commonGitDir),
|
|
131
|
+
authorityRootFromLinkedGitDir(git.gitDir),
|
|
132
|
+
...ancestorKbRoots(git.worktreeRoot),
|
|
133
|
+
inputDirectory,
|
|
134
|
+
git.worktreeRoot,
|
|
135
|
+
]);
|
|
136
|
+
return (candidates.find((candidate) => hasRootKbConfig(candidate)) ??
|
|
137
|
+
candidates[0] ??
|
|
138
|
+
git.worktreeRoot);
|
|
139
|
+
}
|
|
140
|
+
function resolveBranch(git) {
|
|
141
|
+
if (!git) {
|
|
142
|
+
return "unknown";
|
|
143
|
+
}
|
|
144
|
+
const head = readFirstLine(join(git.gitDir, "HEAD"));
|
|
145
|
+
if (!head) {
|
|
146
|
+
return "unknown";
|
|
147
|
+
}
|
|
148
|
+
if (!head.startsWith("ref:")) {
|
|
149
|
+
return "HEAD";
|
|
150
|
+
}
|
|
151
|
+
const ref = head.slice("ref:".length).trim();
|
|
152
|
+
if (!ref.startsWith("refs/heads/")) {
|
|
153
|
+
return "HEAD";
|
|
154
|
+
}
|
|
155
|
+
const branch = ref.slice("refs/heads/".length);
|
|
156
|
+
return branch === "master" ? "main" : branch;
|
|
157
|
+
}
|
|
158
|
+
function normalizeRepoRelativePath(fromRoot, targetPath) {
|
|
159
|
+
const rawRelativePath = relative(fromRoot, targetPath);
|
|
160
|
+
if (rawRelativePath.length === 0) {
|
|
161
|
+
return ".";
|
|
162
|
+
}
|
|
163
|
+
return rawRelativePath.split(sep).join("/");
|
|
164
|
+
}
|
|
165
|
+
function authoritativeForPosture(posture) {
|
|
166
|
+
return posture === "root_active" || posture === "hybrid_root_plus_vendored";
|
|
167
|
+
}
|
|
168
|
+
// implements REQ-opencode-worktree-hard-enforcement-v1
|
|
169
|
+
const resolveWorkContext = function resolveWorkContext(input) {
|
|
170
|
+
const declaredWorktreeRoot = resolve(input.inputWorktree || input.inputDirectory);
|
|
171
|
+
const declaredDirectory = resolve(input.inputDirectory || input.inputWorktree);
|
|
172
|
+
const absoluteFilePath = input.filePath
|
|
173
|
+
? isAbsolute(input.filePath)
|
|
174
|
+
? resolve(input.filePath)
|
|
175
|
+
: resolve(declaredWorktreeRoot, input.filePath)
|
|
176
|
+
: undefined;
|
|
177
|
+
const git = (absoluteFilePath ? findGitMetadata(absoluteFilePath) : null) ??
|
|
178
|
+
findGitMetadata(declaredWorktreeRoot);
|
|
179
|
+
const worktreeRoot = git?.worktreeRoot ?? declaredWorktreeRoot;
|
|
180
|
+
const kibiAuthorityRoot = git
|
|
181
|
+
? resolveAuthorityRoot(git, declaredDirectory)
|
|
182
|
+
: declaredWorktreeRoot;
|
|
183
|
+
const posture = detectPosture(kibiAuthorityRoot).state;
|
|
184
|
+
const repoRelativePath = normalizeRepoRelativePath(worktreeRoot, absoluteFilePath ?? worktreeRoot);
|
|
185
|
+
return {
|
|
186
|
+
worktreeRoot,
|
|
187
|
+
kibiAuthorityRoot,
|
|
188
|
+
branch: resolveBranch(git),
|
|
189
|
+
repoRelativePath,
|
|
190
|
+
posture,
|
|
191
|
+
isAuthoritative: authoritativeForPosture(posture),
|
|
192
|
+
isLinkedWorktree: git?.isLinkedWorktree ?? false,
|
|
193
|
+
sessionId: input.sessionId,
|
|
194
|
+
agentIdentity: input.agentIdentity ?? "unknown",
|
|
195
|
+
};
|
|
196
|
+
};
|
|
197
|
+
export { resolveWorkContext };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kibi-opencode",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Kibi OpenCode plugin - thin adapter to integrate Kibi with OpenCode sessions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"@opencode-ai/plugin": "^1.4.7",
|
|
63
63
|
"@opentui/core": "^0.1.99",
|
|
64
64
|
"@opentui/solid": "^0.1.99",
|
|
65
|
-
"kibi-cli": "^0.
|
|
65
|
+
"kibi-cli": "^0.11.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/node": "latest",
|