@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.
- package/LICENSE +21 -21
- package/assets/bootstrap/AGENTS.md +1 -1
- package/assets/openspec/schemas/keel-spec-driven/schema.yaml +166 -166
- package/package.json +1 -1
- package/plugins/keel/.claude-plugin/plugin.json +1 -1
- package/plugins/keel/.codex-plugin/plugin.json +1 -1
- package/plugins/keel/hooks/hooks.json +30 -30
- package/plugins/keel/scripts/pretooluse-guard.js +156 -156
- package/plugins/keel/scripts/session-start.js +182 -182
- package/scripts/run_python.js +63 -63
- package/scripts/validate_plugin.py +2 -2
- package/src/core/capabilities.js +291 -291
- package/src/core/context.js +514 -514
- package/src/core/gates.js +643 -643
- package/src/core/goal.js +230 -230
- package/src/core/guard.js +295 -295
- package/src/core/helper.js +319 -319
- package/src/core/projection.js +195 -195
- package/src/core/task-contract.js +736 -736
- package/src/core/tasksview.js +123 -123
package/src/core/projection.js
CHANGED
|
@@ -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
|
+
};
|