@christang/keel 5.1.1 → 5.1.2

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,195 +1,195 @@
1
- "use strict";
2
-
3
- // Keel 4.1.0 one-way native projection contract.
4
-
5
- const { resolveContext } = require("./context");
6
- const { loadTaskContract } = require("./task-contract");
7
- const { probeCapabilities } = require("./capabilities");
8
-
9
- const EVENTS = new Set([
10
- "startup",
11
- "resume",
12
- "compaction",
13
- "goal",
14
- "task-view",
15
- "worktree",
16
- "subagent-start",
17
- "subagent-stop",
18
- ]);
19
-
20
- function blocked(target, event, reason, warnings = []) {
21
- return {
22
- schemaVersion: 1,
23
- status: "blocked",
24
- target,
25
- event,
26
- source: null,
27
- capability: {
28
- level: "manual",
29
- command: "keel context --json",
30
- },
31
- projection: null,
32
- reasons: [reason],
33
- warnings,
34
- };
35
- }
36
-
37
- function capabilityKey(event) {
38
- if (event === "startup") return "continuity.start";
39
- if (["resume", "compaction"].includes(event)) return "continuity.reinject";
40
- if (event === "goal") return "execution.goal";
41
- if (event === "task-view") return "execution.task-view";
42
- if (event === "worktree") return "execution.worktree";
43
- if (event === "subagent-start") return "delegation.context";
44
- return "delegation.return";
45
- }
46
-
47
- function projectRuntime(repo, options) {
48
- const event = options.projectionEvent;
49
- if (!EVENTS.has(event)) {
50
- throw new Error(`unsupported projection event: ${event || "<missing>"}`);
51
- }
52
- if (!["claude", "codex", "opencode"].includes(options.target)) {
53
- throw new Error(`unsupported target: ${options.target}`);
54
- }
55
-
56
- const context = resolveContext(repo, {
57
- change: options.change,
58
- task: options.task,
59
- });
60
- if (context.status !== "ready" || !context.selection) {
61
- return blocked(
62
- options.target,
63
- event,
64
- context.reasons.join(" ") || "Current OpenSpec context is not ready.",
65
- context.warnings
66
- );
67
- }
68
- const change = context.selection.change;
69
- const taskId = context.selection.task;
70
- if (!taskId) {
71
- return blocked(
72
- options.target,
73
- event,
74
- "Projection requires one selected executable task.",
75
- context.warnings
76
- );
77
- }
78
- const loaded = loadTaskContract(repo, change, taskId);
79
- if (!loaded || loaded.task.checked) {
80
- return blocked(
81
- options.target,
82
- event,
83
- "Selected durable task owner is missing or already complete.",
84
- context.warnings
85
- );
86
- }
87
- if (loaded.contract.diagnostics.length > 0) {
88
- return blocked(
89
- options.target,
90
- event,
91
- loaded.contract.diagnostics.map((item) => item.message).join(" "),
92
- context.warnings
93
- );
94
- }
95
-
96
- const owner = `openspec/changes/${change}/tasks.md#${taskId}`;
97
- if (
98
- event === "worktree"
99
- && (!options.expectedOwner || options.expectedOwner !== owner)
100
- ) {
101
- return blocked(
102
- options.target,
103
- event,
104
- `Current checkout owner ${owner} does not match the explicit expected owner.`,
105
- context.warnings
106
- );
107
- }
108
-
109
- const authorization = new Set(options.authorizations || []);
110
- const requiredAuthorization =
111
- event === "goal"
112
- ? "goal"
113
- : event === "task-view"
114
- ? "task-view"
115
- : event.startsWith("subagent-")
116
- ? "subagent"
117
- : null;
118
- if (requiredAuthorization && !authorization.has(requiredAuthorization)) {
119
- return blocked(
120
- options.target,
121
- event,
122
- `${event} projection requires explicit ${requiredAuthorization} authorization.`,
123
- context.warnings
124
- );
125
- }
126
-
127
- const contract = loaded.contract;
128
- const capsule = contract.capsule;
129
- const capabilities = probeCapabilities(repo, options.target);
130
- const capability = capabilities.capabilities[capabilityKey(event)];
131
- const warnings = [...context.warnings];
132
- if (options.nativeComplete) {
133
- warnings.push(
134
- "Native completion was ignored; only task-complete plus current-agent "
135
- + "durable updates can complete OpenSpec work."
136
- );
137
- }
138
- const projection = {
139
- objective: capsule.task.title,
140
- acceptance: capsule.acceptance,
141
- stopBoundary: [
142
- ...capsule.boundaries.stop,
143
- ...capsule.boundaries.autonomy,
144
- ],
145
- nextAction: context.nextAction,
146
- read: capsule.read,
147
- touch: capsule.touch,
148
- verification: capsule.verification,
149
- evidenceContract: capsule.verification.commands.map(
150
- (item) => `${item.label}: ${item.check}`
151
- ),
152
- owner: capsule.owner,
153
- helperAuthority: capsule.helperAuthority,
154
- fingerprint: contract.fingerprint,
155
- prohibitions: capsule.prohibitions,
156
- };
157
- if (event === "subagent-stop") {
158
- projection.returnAuthority = "report-and-evidence-only";
159
- }
160
-
161
- return {
162
- schemaVersion: 1,
163
- status: "ready",
164
- target: options.target,
165
- event,
166
- source: {
167
- authority: "OpenSpec",
168
- owner,
169
- change,
170
- task: taskId,
171
- },
172
- capability,
173
- contract,
174
- projection,
175
- reasons: [],
176
- warnings,
177
- };
178
- }
179
-
180
- function renderProjection(result) {
181
- const lines = [
182
- `Keel projection: ${result.status}`,
183
- `Target: ${result.target}`,
184
- `Event: ${result.event}`,
185
- ];
186
- if (result.source) lines.push(`Owner: ${result.source.owner}`);
187
- for (const reason of result.reasons) lines.push(`Reason: ${reason}`);
188
- for (const warning of result.warnings) lines.push(`Warning: ${warning}`);
189
- return `${lines.join("\n")}\n`;
190
- }
191
-
192
- module.exports = {
193
- projectRuntime,
194
- renderProjection,
195
- };
1
+ "use strict";
2
+
3
+ // Keel 4.1.0 one-way native projection contract.
4
+
5
+ const { resolveContext } = require("./context");
6
+ const { loadTaskContract } = require("./task-contract");
7
+ const { probeCapabilities } = require("./capabilities");
8
+
9
+ const EVENTS = new Set([
10
+ "startup",
11
+ "resume",
12
+ "compaction",
13
+ "goal",
14
+ "task-view",
15
+ "worktree",
16
+ "subagent-start",
17
+ "subagent-stop",
18
+ ]);
19
+
20
+ function blocked(target, event, reason, warnings = []) {
21
+ return {
22
+ schemaVersion: 1,
23
+ status: "blocked",
24
+ target,
25
+ event,
26
+ source: null,
27
+ capability: {
28
+ level: "manual",
29
+ command: "keel context --json",
30
+ },
31
+ projection: null,
32
+ reasons: [reason],
33
+ warnings,
34
+ };
35
+ }
36
+
37
+ function capabilityKey(event) {
38
+ if (event === "startup") return "continuity.start";
39
+ if (["resume", "compaction"].includes(event)) return "continuity.reinject";
40
+ if (event === "goal") return "execution.goal";
41
+ if (event === "task-view") return "execution.task-view";
42
+ if (event === "worktree") return "execution.worktree";
43
+ if (event === "subagent-start") return "delegation.context";
44
+ return "delegation.return";
45
+ }
46
+
47
+ function projectRuntime(repo, options) {
48
+ const event = options.projectionEvent;
49
+ if (!EVENTS.has(event)) {
50
+ throw new Error(`unsupported projection event: ${event || "<missing>"}`);
51
+ }
52
+ if (!["claude", "codex", "opencode"].includes(options.target)) {
53
+ throw new Error(`unsupported target: ${options.target}`);
54
+ }
55
+
56
+ const context = resolveContext(repo, {
57
+ change: options.change,
58
+ task: options.task,
59
+ });
60
+ if (context.status !== "ready" || !context.selection) {
61
+ return blocked(
62
+ options.target,
63
+ event,
64
+ context.reasons.join(" ") || "Current OpenSpec context is not ready.",
65
+ context.warnings
66
+ );
67
+ }
68
+ const change = context.selection.change;
69
+ const taskId = context.selection.task;
70
+ if (!taskId) {
71
+ return blocked(
72
+ options.target,
73
+ event,
74
+ "Projection requires one selected executable task.",
75
+ context.warnings
76
+ );
77
+ }
78
+ const loaded = loadTaskContract(repo, change, taskId);
79
+ if (!loaded || loaded.task.checked) {
80
+ return blocked(
81
+ options.target,
82
+ event,
83
+ "Selected durable task owner is missing or already complete.",
84
+ context.warnings
85
+ );
86
+ }
87
+ if (loaded.contract.diagnostics.length > 0) {
88
+ return blocked(
89
+ options.target,
90
+ event,
91
+ loaded.contract.diagnostics.map((item) => item.message).join(" "),
92
+ context.warnings
93
+ );
94
+ }
95
+
96
+ const owner = `openspec/changes/${change}/tasks.md#${taskId}`;
97
+ if (
98
+ event === "worktree"
99
+ && (!options.expectedOwner || options.expectedOwner !== owner)
100
+ ) {
101
+ return blocked(
102
+ options.target,
103
+ event,
104
+ `Current checkout owner ${owner} does not match the explicit expected owner.`,
105
+ context.warnings
106
+ );
107
+ }
108
+
109
+ const authorization = new Set(options.authorizations || []);
110
+ const requiredAuthorization =
111
+ event === "goal"
112
+ ? "goal"
113
+ : event === "task-view"
114
+ ? "task-view"
115
+ : event.startsWith("subagent-")
116
+ ? "subagent"
117
+ : null;
118
+ if (requiredAuthorization && !authorization.has(requiredAuthorization)) {
119
+ return blocked(
120
+ options.target,
121
+ event,
122
+ `${event} projection requires explicit ${requiredAuthorization} authorization.`,
123
+ context.warnings
124
+ );
125
+ }
126
+
127
+ const contract = loaded.contract;
128
+ const capsule = contract.capsule;
129
+ const capabilities = probeCapabilities(repo, options.target);
130
+ const capability = capabilities.capabilities[capabilityKey(event)];
131
+ const warnings = [...context.warnings];
132
+ if (options.nativeComplete) {
133
+ warnings.push(
134
+ "Native completion was ignored; only task-complete plus current-agent "
135
+ + "durable updates can complete OpenSpec work."
136
+ );
137
+ }
138
+ const projection = {
139
+ objective: capsule.task.title,
140
+ acceptance: capsule.acceptance,
141
+ stopBoundary: [
142
+ ...capsule.boundaries.stop,
143
+ ...capsule.boundaries.autonomy,
144
+ ],
145
+ nextAction: context.nextAction,
146
+ read: capsule.read,
147
+ touch: capsule.touch,
148
+ verification: capsule.verification,
149
+ evidenceContract: capsule.verification.commands.map(
150
+ (item) => `${item.label}: ${item.check}`
151
+ ),
152
+ owner: capsule.owner,
153
+ helperAuthority: capsule.helperAuthority,
154
+ fingerprint: contract.fingerprint,
155
+ prohibitions: capsule.prohibitions,
156
+ };
157
+ if (event === "subagent-stop") {
158
+ projection.returnAuthority = "report-and-evidence-only";
159
+ }
160
+
161
+ return {
162
+ schemaVersion: 1,
163
+ status: "ready",
164
+ target: options.target,
165
+ event,
166
+ source: {
167
+ authority: "OpenSpec",
168
+ owner,
169
+ change,
170
+ task: taskId,
171
+ },
172
+ capability,
173
+ contract,
174
+ projection,
175
+ reasons: [],
176
+ warnings,
177
+ };
178
+ }
179
+
180
+ function renderProjection(result) {
181
+ const lines = [
182
+ `Keel projection: ${result.status}`,
183
+ `Target: ${result.target}`,
184
+ `Event: ${result.event}`,
185
+ ];
186
+ if (result.source) lines.push(`Owner: ${result.source.owner}`);
187
+ for (const reason of result.reasons) lines.push(`Reason: ${reason}`);
188
+ for (const warning of result.warnings) lines.push(`Warning: ${warning}`);
189
+ return `${lines.join("\n")}\n`;
190
+ }
191
+
192
+ module.exports = {
193
+ projectRuntime,
194
+ renderProjection,
195
+ };