@christang/keel 5.7.0 → 5.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/assets/bootstrap/AGENTS.md +1 -1
- package/assets/openspec/schemas/keel-spec-driven/templates/tasks.md +4 -2
- package/bin/keel.js +158 -58
- 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/agents/keel-single-task-goal-claude.md +3 -2
- package/plugins/keel/agents/keel-single-task-goal-codex.md +2 -2
- package/plugins/keel/scripts/pretooluse-guard.js +65 -2
- package/plugins/keel/scripts/session-start.js +87 -0
- package/plugins/keel/skills/keel-review-checklist/SKILL.md +25 -1
- package/plugins/keel/skills/keel-run-single-task-goal/SKILL.md +3 -3
- package/scripts/run_python.js +101 -5
- package/scripts/validate_plugin.py +7298 -3678
- package/src/core/config.js +58 -0
- package/src/core/context.js +41 -5
- package/src/core/gates.js +236 -35
- package/src/core/goal.js +13 -1
- package/src/core/helper.js +27 -1
- package/src/core/projection.js +55 -0
- package/src/core/task-contract.js +30 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: keel-run-single-task-goal
|
|
3
|
-
description: Use when the user explicitly authorizes automatic execution or resume of exactly one OpenSpec task on Codex or Claude. Drives the single-task native goal lifecycle through Keel gates, keeps the current agent the sole
|
|
3
|
+
description: Use when the user explicitly authorizes automatic execution or resume of exactly one OpenSpec task on Codex or Claude. Drives the single-task native goal lifecycle through Keel gates, keeps the current agent the sole holder of write authority, and stops at the task boundary. Never activate for ordinary apply, proposal work, ambiguous or multiple tasks, or OpenCode.
|
|
4
4
|
license: UNLICENSED
|
|
5
5
|
metadata:
|
|
6
6
|
keel-role: single-task-goal-activation
|
|
@@ -11,7 +11,7 @@ metadata:
|
|
|
11
11
|
|
|
12
12
|
## Purpose
|
|
13
13
|
|
|
14
|
-
Activate a native goal or subagent runtime to execute exactly one authorized OpenSpec task end to end, while OpenSpec, Git, the task-capsule fingerprint, and deterministic Keel gates stay the only durable authority. The current agent remains the sole
|
|
14
|
+
Activate a native goal or subagent runtime to execute exactly one authorized OpenSpec task end to end, while OpenSpec, Git, the task-capsule fingerprint, and deterministic Keel gates stay the only durable authority. The current agent remains the sole holder of write authority and owns Review, gate invocation, the task checkbox, and completion. Where delegation is declared, an authorized delegate may write inside the `Touch` boundary that authority already defined and acquires none of those decisions; the current agent re-runs each `M<n>` check itself before recording Evidence, because a delegate's reported result is a claim and the byte-identity check that validates a read-only helper cannot apply to a writer. A native evaluator declaring success never marks or reports the task complete.
|
|
15
15
|
|
|
16
16
|
## Authoritative sources and provenance
|
|
17
17
|
|
|
@@ -37,7 +37,7 @@ Activate only on an explicit, unambiguous request to automatically execute or re
|
|
|
37
37
|
- Proposal, design, or spec authoring before tasks are final.
|
|
38
38
|
- Ambiguous selection, multiple tasks, or a whole task group or change backlog.
|
|
39
39
|
- An unrelated native `/goal` use that is not a Keel OpenSpec task.
|
|
40
|
-
- Unrequested helpers or
|
|
40
|
+
- Unrequested helpers, or an undeclared delegation of implementation to another agent.
|
|
41
41
|
- OpenCode, which stays manual compatibility only with no v4 native activation.
|
|
42
42
|
|
|
43
43
|
If any of these hold, stop and use the normal manual Keel loop.
|
package/scripts/run_python.js
CHANGED
|
@@ -3,32 +3,108 @@
|
|
|
3
3
|
|
|
4
4
|
const { spawnSync } = require("child_process");
|
|
5
5
|
|
|
6
|
+
// The suite calls `tempfile.TemporaryDirectory(ignore_cleanup_errors=…)`,
|
|
7
|
+
// added in 3.10. Accepting any interpreter whose `--version` exits zero meant
|
|
8
|
+
// macOS system Python 3.9 ran the suite and failed ten scenarios with messages
|
|
9
|
+
// naming ten unrelated features — the reader is then debugging the suite
|
|
10
|
+
// instead of installing an interpreter.
|
|
11
|
+
//
|
|
12
|
+
// This is the one definition. `bin/keel.js` used to carry its own candidate
|
|
13
|
+
// list that asked only whether a command runs, so `keel --doctor` reported
|
|
14
|
+
// `python3: ok` for the same 3.9.6 this file refuses — two Keel surfaces
|
|
15
|
+
// answering one question with opposite verdicts, and the one a person runs
|
|
16
|
+
// deliberately to check their environment was the wrong one.
|
|
17
|
+
const MINIMUM_PYTHON = [3, 10];
|
|
18
|
+
|
|
19
|
+
// The newest minor worth looking for under a versioned name. Names are
|
|
20
|
+
// generated from MINIMUM_PYTHON up to this, newest first, so raising the
|
|
21
|
+
// minimum drops the names below it with no second edit; only this bound moves
|
|
22
|
+
// when a new Python is released.
|
|
23
|
+
const NEWEST_KNOWN_PYTHON_MINOR = 13;
|
|
24
|
+
|
|
25
|
+
// Versioned names matter because the unversioned one is often not the usable
|
|
26
|
+
// one. On macOS `python3` is the system 3.9 while Homebrew installs
|
|
27
|
+
// `python3.11` beside it, so a search that stops at `python3` refuses with
|
|
28
|
+
// "install a newer Python" addressed to someone who already did.
|
|
29
|
+
function versionedPythonNames() {
|
|
30
|
+
const names = [];
|
|
31
|
+
for (let minor = NEWEST_KNOWN_PYTHON_MINOR; minor >= MINIMUM_PYTHON[1]; minor -= 1) {
|
|
32
|
+
names.push({ command: `python${MINIMUM_PYTHON[0]}.${minor}`, prefixArgs: [] });
|
|
33
|
+
}
|
|
34
|
+
return names;
|
|
35
|
+
}
|
|
36
|
+
|
|
6
37
|
function pythonCandidates() {
|
|
38
|
+
// An explicit choice is not overridden by discovery.
|
|
7
39
|
if (process.env.KEEL_PYTHON) {
|
|
8
40
|
return [{ command: process.env.KEEL_PYTHON, prefixArgs: [] }];
|
|
9
41
|
}
|
|
10
42
|
|
|
43
|
+
// Unversioned names first, so a machine where the default interpreter
|
|
44
|
+
// already satisfies the minimum behaves exactly as it did before.
|
|
11
45
|
if (process.platform === "win32") {
|
|
12
46
|
return [
|
|
13
47
|
{ command: "py", prefixArgs: ["-3"] },
|
|
14
48
|
{ command: "python", prefixArgs: [] },
|
|
15
49
|
{ command: "python3", prefixArgs: [] },
|
|
50
|
+
...versionedPythonNames(),
|
|
16
51
|
];
|
|
17
52
|
}
|
|
18
53
|
|
|
19
54
|
return [
|
|
20
55
|
{ command: "python3", prefixArgs: [] },
|
|
21
56
|
{ command: "python", prefixArgs: [] },
|
|
57
|
+
...versionedPythonNames(),
|
|
22
58
|
];
|
|
23
59
|
}
|
|
24
60
|
|
|
25
|
-
function
|
|
61
|
+
function label(candidate) {
|
|
62
|
+
return [candidate.command, ...candidate.prefixArgs].join(" ");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function reportedVersion(candidate) {
|
|
26
66
|
const result = spawnSync(
|
|
27
67
|
candidate.command,
|
|
28
68
|
[...candidate.prefixArgs, "--version"],
|
|
29
69
|
{ encoding: "utf8" }
|
|
30
70
|
);
|
|
31
|
-
|
|
71
|
+
if (result.error || result.status !== 0) return null;
|
|
72
|
+
// Python 2 wrote `--version` to stderr, so read both streams: an old
|
|
73
|
+
// interpreter should be reported by its version rather than dismissed as
|
|
74
|
+
// unreadable.
|
|
75
|
+
const match = `${result.stdout || ""}${result.stderr || ""}`.match(
|
|
76
|
+
/(\d+)\.(\d+)(?:\.(\d+))?/
|
|
77
|
+
);
|
|
78
|
+
return match ? match[0] : null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function meetsMinimum(version) {
|
|
82
|
+
const parts = String(version || "").split(".").map(Number);
|
|
83
|
+
const [major, minor] = parts;
|
|
84
|
+
if (!Number.isFinite(major) || !Number.isFinite(minor)) return false;
|
|
85
|
+
if (major !== MINIMUM_PYTHON[0]) return major > MINIMUM_PYTHON[0];
|
|
86
|
+
return minor >= MINIMUM_PYTHON[1];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Resolve the interpreter both surfaces should agree about. Returns the chosen
|
|
90
|
+
// candidate and every candidate tried with the version it reported, so the
|
|
91
|
+
// caller can either run it or report why there is nothing to run.
|
|
92
|
+
function resolveInterpreter() {
|
|
93
|
+
const tried = [];
|
|
94
|
+
for (const option of pythonCandidates()) {
|
|
95
|
+
const version = reportedVersion(option);
|
|
96
|
+
tried.push({ label: label(option), version });
|
|
97
|
+
if (version && meetsMinimum(version)) {
|
|
98
|
+
return { candidate: option, version, tried };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { candidate: null, version: null, tried };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function describeTried(tried) {
|
|
105
|
+
return tried
|
|
106
|
+
.map((entry) => `${entry.label} (${entry.version || "not runnable"})`)
|
|
107
|
+
.join(", ");
|
|
32
108
|
}
|
|
33
109
|
|
|
34
110
|
function main() {
|
|
@@ -38,10 +114,15 @@ function main() {
|
|
|
38
114
|
return 2;
|
|
39
115
|
}
|
|
40
116
|
|
|
41
|
-
const candidate =
|
|
117
|
+
const { candidate, tried } = resolveInterpreter();
|
|
42
118
|
if (!candidate) {
|
|
119
|
+
const minimum = MINIMUM_PYTHON.join(".");
|
|
43
120
|
process.stderr.write(
|
|
44
|
-
|
|
121
|
+
`run_python: no Python ${minimum} or newer was found, and the suite `
|
|
122
|
+
+ `needs one — tempfile.TemporaryDirectory(ignore_cleanup_errors=…) `
|
|
123
|
+
+ `was added in ${minimum}.\n`
|
|
124
|
+
+ `run_python: tried ${describeTried(tried) || "nothing"}.\n`
|
|
125
|
+
+ "run_python: install a newer Python, or point KEEL_PYTHON at one.\n"
|
|
45
126
|
);
|
|
46
127
|
return 127;
|
|
47
128
|
}
|
|
@@ -60,4 +141,19 @@ function main() {
|
|
|
60
141
|
return result.status ?? 1;
|
|
61
142
|
}
|
|
62
143
|
|
|
63
|
-
|
|
144
|
+
// `bin/keel.js` requires this file for the definitions above, so the script
|
|
145
|
+
// only runs when it is the entry point. Two copies of the interpreter rule is
|
|
146
|
+
// what this change exists to remove; importing is what keeps it at one.
|
|
147
|
+
module.exports = {
|
|
148
|
+
MINIMUM_PYTHON,
|
|
149
|
+
pythonCandidates,
|
|
150
|
+
versionedPythonNames,
|
|
151
|
+
reportedVersion,
|
|
152
|
+
meetsMinimum,
|
|
153
|
+
resolveInterpreter,
|
|
154
|
+
describeTried,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
if (require.main === module) {
|
|
158
|
+
process.exit(main());
|
|
159
|
+
}
|