@christang/keel 5.1.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.
- package/LICENSE +21 -0
- package/README.md +250 -0
- package/README.zh-CN.md +295 -0
- package/assets/bootstrap/AGENTS.md +9 -0
- package/assets/openspec/schemas/keel-spec-driven/schema.yaml +166 -0
- package/assets/openspec/schemas/keel-spec-driven/templates/design.md +52 -0
- package/assets/openspec/schemas/keel-spec-driven/templates/proposal.md +21 -0
- package/assets/openspec/schemas/keel-spec-driven/templates/spec.md +8 -0
- package/assets/openspec/schemas/keel-spec-driven/templates/tasks.md +68 -0
- package/bin/keel.js +1490 -0
- package/package.json +35 -0
- package/plugins/keel/.claude-plugin/plugin.json +17 -0
- package/plugins/keel/.codex-plugin/plugin.json +29 -0
- package/plugins/keel/agents/keel-single-task-goal-claude.md +16 -0
- package/plugins/keel/agents/keel-single-task-goal-codex.md +16 -0
- package/plugins/keel/hooks/hooks.json +30 -0
- package/plugins/keel/scripts/pretooluse-guard.js +156 -0
- package/plugins/keel/scripts/session-start.js +182 -0
- package/plugins/keel/skills/keel-align-expectations/SKILL.md +53 -0
- package/plugins/keel/skills/keel-align-expectations/references/hardware-dsl.md +21 -0
- package/plugins/keel/skills/keel-align-expectations/references/hardware.md +21 -0
- package/plugins/keel/skills/keel-align-expectations/references/web.md +21 -0
- package/plugins/keel/skills/keel-debug-failure/SKILL.md +41 -0
- package/plugins/keel/skills/keel-handoff/SKILL.md +45 -0
- package/plugins/keel/skills/keel-review-checklist/SKILL.md +73 -0
- package/plugins/keel/skills/keel-run-single-task-goal/SKILL.md +68 -0
- package/plugins/keel/skills/keel-tdd-or-test-first/SKILL.md +45 -0
- package/scripts/install_to_repo.py +1122 -0
- package/scripts/run_python.js +63 -0
- package/scripts/validate_plugin.py +9869 -0
- package/src/core/capabilities.js +291 -0
- package/src/core/context.js +514 -0
- package/src/core/gates.js +643 -0
- package/src/core/goal.js +230 -0
- package/src/core/guard.js +295 -0
- package/src/core/helper.js +319 -0
- package/src/core/projection.js +195 -0
- package/src/core/task-contract.js +736 -0
- package/src/core/tasksview.js +123 -0
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@christang/keel",
|
|
3
|
+
"displayName": "Keel",
|
|
4
|
+
"description": "Keel OpenSpec execution discipline CLI for Claude Code, Codex, and OpenCode.",
|
|
5
|
+
"version": "5.1.1",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/TanglmChris/keel.git"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"keel": "bin/keel.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin/",
|
|
16
|
+
"scripts/",
|
|
17
|
+
"src/core/",
|
|
18
|
+
"assets/",
|
|
19
|
+
"README.md",
|
|
20
|
+
"plugins/"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"validate": "node scripts/run_python.js scripts/validate_plugin.py",
|
|
24
|
+
"test": "node scripts/run_python.js scripts/validate_plugin.py --all"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@fission-ai/openspec": "^1.4.1"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=20.19.0"
|
|
31
|
+
},
|
|
32
|
+
"allowScripts": {
|
|
33
|
+
"@fission-ai/openspec@1.6.0": true
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "keel",
|
|
3
|
+
"version": "5.1.1",
|
|
4
|
+
"description": "Keel OpenSpec execution discipline: stateless continuity, task capsules, deterministic gates, and expectation alignment for Codex and Claude Code.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "TanglmChris",
|
|
7
|
+
"url": "https://github.com/TanglmChris"
|
|
8
|
+
},
|
|
9
|
+
"repository": "https://github.com/TanglmChris/keel",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"openspec",
|
|
13
|
+
"workflow",
|
|
14
|
+
"keel",
|
|
15
|
+
"discipline"
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "keel",
|
|
3
|
+
"version": "5.1.1",
|
|
4
|
+
"description": "Keel OpenSpec execution discipline: stateless continuity, task capsules, deterministic gates, and expectation alignment for Codex and Claude Code.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "TanglmChris",
|
|
7
|
+
"url": "https://github.com/TanglmChris"
|
|
8
|
+
},
|
|
9
|
+
"repository": "https://github.com/TanglmChris/keel",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"openspec",
|
|
13
|
+
"workflow",
|
|
14
|
+
"keel",
|
|
15
|
+
"discipline"
|
|
16
|
+
],
|
|
17
|
+
"skills": "./skills/",
|
|
18
|
+
"agents": "./agents/",
|
|
19
|
+
"interface": {
|
|
20
|
+
"displayName": "Keel",
|
|
21
|
+
"shortDescription": "OpenSpec execution discipline for coding agents",
|
|
22
|
+
"longDescription": "Keel packages reusable OpenSpec authoring and execution skills: expectation alignment before specs and tasks finalize, task-capsule discipline, deterministic gate usage, review, debugging, and handoff hygiene. The separately installed keel CLI owns context, gates, schema, and overlays.",
|
|
23
|
+
"developerName": "TanglmChris",
|
|
24
|
+
"category": "Productivity",
|
|
25
|
+
"capabilities": [
|
|
26
|
+
"Interactive"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: keel-single-task-goal-claude
|
|
3
|
+
description: Thin Claude activation adapter for one authorized OpenSpec task; single /goal within the 4,000-character budget with read-only subagent helpers only.
|
|
4
|
+
target: claude
|
|
5
|
+
role: single-task-goal-activation
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# keel-single-task-goal-claude
|
|
9
|
+
|
|
10
|
+
Thin Claude adapter for the `keel-run-single-task-goal` skill. It activates one `/goal` for exactly one authorized OpenSpec task and never introduces a scheduler, global Stop hook, agent team, or cross-task authority.
|
|
11
|
+
|
|
12
|
+
- Activation: compile the view with `keel project goal --target claude --change C --task T --json`; the goal condition stays within the 4,000-character budget, and Keel refuses activation rather than omit Acceptance, fingerprint, or stop authority.
|
|
13
|
+
- Evidence: Claude's evaluator is transcript-only, so surface every command result and gate outcome before any success claim; only `keel gate task-complete` plus the current agent's durable checkbox complete the task.
|
|
14
|
+
- Helpers: Claude subagents are used only as bounded read-only evidence producers via `keel project helper`; they carry no write, delegation, acceptance, or completion authority, and their returns are accepted only after repository byte identity.
|
|
15
|
+
- Stop: terminate after one task on completion, drift, blocker, or premature native success; continuing requires a new explicit authorization and start fingerprint.
|
|
16
|
+
- Fallback: with disabled hooks, managed policy, or missing trust, report and run the identical manual Keel loop.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: keel-single-task-goal-codex
|
|
3
|
+
description: Thin Codex activation adapter for one authorized OpenSpec task; bounded goal execution with read-only subagent helpers only.
|
|
4
|
+
target: codex
|
|
5
|
+
role: single-task-goal-activation
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# keel-single-task-goal-codex
|
|
9
|
+
|
|
10
|
+
Thin Codex adapter for the `keel-run-single-task-goal` skill. It activates one bounded goal for exactly one authorized OpenSpec task and never introduces a scheduler, global stop hook, agent team, or cross-task authority.
|
|
11
|
+
|
|
12
|
+
- Activation: compile the view with `keel project goal --target codex --change C --task T --json`, then follow the single-task goal lifecycle. Where no callable goal surface exists, surface the exact command and treat the capability as advisory.
|
|
13
|
+
- Ownership: the current agent stays the sole writer and owns Review, gates, the checkbox, and completion; a native evaluator success never completes the task.
|
|
14
|
+
- Helpers: Codex subagents are used only as bounded read-only evidence producers via `keel project helper`; they carry no write, delegation, acceptance, or completion authority, and their returns are accepted only after repository byte identity.
|
|
15
|
+
- Stop: terminate after one task on completion, drift, blocker, or premature native success; continuing requires a new explicit authorization and start fingerprint.
|
|
16
|
+
- Fallback: without a callable Codex surface, run the identical manual Keel loop.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Keel plugin hooks: non-blocking SessionStart projection and the manifest-gated PreToolUse write guard, both backed by the shared keel CLI contract.",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionStart": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node",
|
|
10
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/session-start.js"],
|
|
11
|
+
"timeout": 15
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"PreToolUse": [
|
|
17
|
+
{
|
|
18
|
+
"matcher": "Edit|Write|NotebookEdit",
|
|
19
|
+
"hooks": [
|
|
20
|
+
{
|
|
21
|
+
"type": "command",
|
|
22
|
+
"command": "node",
|
|
23
|
+
"args": ["${CLAUDE_PLUGIN_ROOT}/scripts/pretooluse-guard.js"],
|
|
24
|
+
"timeout": 10
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// Keel plugin PreToolUse write guard: deterministic denial of out-of-Touch
|
|
5
|
+
// file edits while an explicit keel/guard.json manifest is active. Absence of
|
|
6
|
+
// the manifest allows everything silently; a present-but-untrusted manifest
|
|
7
|
+
// fails closed. The hook never writes state, never spawns the keel CLI, and
|
|
8
|
+
// always exits 0 — denial is expressed only through hook output. Paths that
|
|
9
|
+
// resolve outside the repository root are not product writes and pass through.
|
|
10
|
+
|
|
11
|
+
const crypto = require("crypto");
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
const path = require("path");
|
|
14
|
+
|
|
15
|
+
const MANIFEST_SCHEMA = "keel-write-guard/v1";
|
|
16
|
+
const FILE_EDIT_TOOLS = new Map([
|
|
17
|
+
["Edit", "file_path"],
|
|
18
|
+
["Write", "file_path"],
|
|
19
|
+
["NotebookEdit", "notebook_path"],
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
function readStdin() {
|
|
23
|
+
try {
|
|
24
|
+
return fs.readFileSync(0, "utf8");
|
|
25
|
+
} catch {
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function deny(reason) {
|
|
31
|
+
process.stdout.write(
|
|
32
|
+
`${JSON.stringify({
|
|
33
|
+
hookSpecificOutput: {
|
|
34
|
+
hookEventName: "PreToolUse",
|
|
35
|
+
permissionDecision: "deny",
|
|
36
|
+
permissionDecisionReason: reason,
|
|
37
|
+
},
|
|
38
|
+
})}\n`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function sha256(buffer) {
|
|
43
|
+
return crypto.createHash("sha256").update(buffer).digest("hex");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function manifestShapeValid(manifest) {
|
|
47
|
+
return (
|
|
48
|
+
manifest
|
|
49
|
+
&& manifest.schema === MANIFEST_SCHEMA
|
|
50
|
+
&& typeof manifest.change === "string"
|
|
51
|
+
&& manifest.change !== ""
|
|
52
|
+
&& typeof manifest.task === "string"
|
|
53
|
+
&& manifest.task !== ""
|
|
54
|
+
&& Array.isArray(manifest.touch)
|
|
55
|
+
&& manifest.touch.length > 0
|
|
56
|
+
&& manifest.touch.every((item) => typeof item === "string" && item !== "")
|
|
57
|
+
&& Array.isArray(manifest.authority)
|
|
58
|
+
&& manifest.authority.length > 0
|
|
59
|
+
&& manifest.authority.every(
|
|
60
|
+
(item) =>
|
|
61
|
+
item
|
|
62
|
+
&& typeof item.path === "string"
|
|
63
|
+
&& /^[0-9a-f]{64}$/.test(String(item.sha256 || ""))
|
|
64
|
+
)
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function globPattern(value) {
|
|
69
|
+
const escaped = value.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
70
|
+
return new RegExp(
|
|
71
|
+
`^${escaped
|
|
72
|
+
.replace(/\*\*/g, "\u0000")
|
|
73
|
+
.replace(/\*/g, "[^/]*")
|
|
74
|
+
.replace(/\u0000/g, ".*")}$`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function pathAllowed(candidate, touch) {
|
|
79
|
+
return touch.some((entry) => {
|
|
80
|
+
const normalized = entry.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
81
|
+
if (normalized.endsWith("/")) return candidate.startsWith(normalized);
|
|
82
|
+
if (normalized.includes("*")) return globPattern(normalized).test(candidate);
|
|
83
|
+
return candidate === normalized;
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function main() {
|
|
88
|
+
let event = {};
|
|
89
|
+
try {
|
|
90
|
+
event = JSON.parse(readStdin() || "{}");
|
|
91
|
+
} catch {
|
|
92
|
+
event = {};
|
|
93
|
+
}
|
|
94
|
+
const repo =
|
|
95
|
+
typeof event.cwd === "string" && event.cwd ? event.cwd : process.cwd();
|
|
96
|
+
const manifestPath = path.join(repo, "keel", "guard.json");
|
|
97
|
+
if (!fs.existsSync(manifestPath)) return 0;
|
|
98
|
+
|
|
99
|
+
const pathField = FILE_EDIT_TOOLS.get(event.tool_name);
|
|
100
|
+
if (!pathField) return 0;
|
|
101
|
+
|
|
102
|
+
let manifest = null;
|
|
103
|
+
try {
|
|
104
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
105
|
+
} catch {
|
|
106
|
+
manifest = null;
|
|
107
|
+
}
|
|
108
|
+
if (!manifestShapeValid(manifest)) {
|
|
109
|
+
deny(
|
|
110
|
+
"Keel write guard: keel/guard.json is present but invalid, so file "
|
|
111
|
+
+ "edits fail closed. Run `keel guard clear`, then `keel gate "
|
|
112
|
+
+ "task-start` and `keel guard start` to reauthorize."
|
|
113
|
+
);
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
const pointer = `${manifest.change}#${manifest.task}`;
|
|
117
|
+
|
|
118
|
+
for (const entry of manifest.authority) {
|
|
119
|
+
const file = path.join(repo, entry.path);
|
|
120
|
+
let fresh = null;
|
|
121
|
+
try {
|
|
122
|
+
fresh = sha256(fs.readFileSync(file));
|
|
123
|
+
} catch {
|
|
124
|
+
fresh = null;
|
|
125
|
+
}
|
|
126
|
+
if (fresh !== entry.sha256) {
|
|
127
|
+
deny(
|
|
128
|
+
`Keel write guard: task authority drift detected for ${pointer} `
|
|
129
|
+
+ `(${entry.path} changed since guard start), so file edits fail `
|
|
130
|
+
+ "closed. Re-run `keel gate task-start` and `keel guard start` to "
|
|
131
|
+
+ "reauthorize, or `keel guard clear` to stop enforcement."
|
|
132
|
+
);
|
|
133
|
+
return 0;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const target = event.tool_input ? event.tool_input[pathField] : null;
|
|
138
|
+
if (typeof target !== "string" || !target) return 0;
|
|
139
|
+
const relative = path.relative(repo, path.resolve(repo, target));
|
|
140
|
+
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) {
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
const candidate = relative.replace(/\\/g, "/");
|
|
144
|
+
if (pathAllowed(candidate, manifest.touch)) return 0;
|
|
145
|
+
|
|
146
|
+
deny(
|
|
147
|
+
`Keel write guard: ${candidate} is outside Touch for ${pointer}. `
|
|
148
|
+
+ `Touch allows: ${manifest.touch.join(", ")}. Stop and report an `
|
|
149
|
+
+ "Out-of-scope Need, update the task authority and reauthorize via "
|
|
150
|
+
+ "`keel gate task-start` and `keel guard start`, or run "
|
|
151
|
+
+ "`keel guard clear` to stop enforcement."
|
|
152
|
+
);
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
process.exit(main());
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
// Keel plugin SessionStart projection: disposable context only.
|
|
5
|
+
// OpenSpec and Git stay the durable authority; this script never writes
|
|
6
|
+
// state, selects an ambiguous owner, records a fingerprint, creates a goal,
|
|
7
|
+
// or blocks the session. It always exits 0.
|
|
8
|
+
//
|
|
9
|
+
// The projection is source-aware: a compact start reinjects the recomputed
|
|
10
|
+
// task pointer (selection, recorded Contract fingerprint, next command) that
|
|
11
|
+
// a summary is most likely to lose, a resume start reinjects the selection,
|
|
12
|
+
// and startup, clear, or any unknown source falls back to the generic view.
|
|
13
|
+
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
const path = require("path");
|
|
16
|
+
const { spawnSync } = require("child_process");
|
|
17
|
+
|
|
18
|
+
const TIMEOUT_MS = Number(process.env.KEEL_HOOK_TIMEOUT_MS || 8000) || 8000;
|
|
19
|
+
const MAX_REASONS = 3;
|
|
20
|
+
const MAX_REASON_LENGTH = 300;
|
|
21
|
+
|
|
22
|
+
function readStdin() {
|
|
23
|
+
try {
|
|
24
|
+
return fs.readFileSync(0, "utf8");
|
|
25
|
+
} catch {
|
|
26
|
+
return "";
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function emit(context) {
|
|
31
|
+
process.stdout.write(
|
|
32
|
+
`${JSON.stringify({
|
|
33
|
+
hookSpecificOutput: {
|
|
34
|
+
hookEventName: "SessionStart",
|
|
35
|
+
additionalContext: context,
|
|
36
|
+
},
|
|
37
|
+
})}\n`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function runKeel(cwd, args) {
|
|
42
|
+
const cli = (process.env.KEEL_CLI || "keel").trim();
|
|
43
|
+
return spawnSync(`${cli} ${args.join(" ")}`, {
|
|
44
|
+
cwd,
|
|
45
|
+
shell: true,
|
|
46
|
+
encoding: "utf8",
|
|
47
|
+
timeout: TIMEOUT_MS,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function fallback(reason) {
|
|
52
|
+
emit(
|
|
53
|
+
`Keel hook fallback: ${reason} Run \`keel context\` manually; `
|
|
54
|
+
+ "OpenSpec and Git remain the durable authority."
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function main() {
|
|
59
|
+
let event = {};
|
|
60
|
+
try {
|
|
61
|
+
event = JSON.parse(readStdin() || "{}");
|
|
62
|
+
} catch {
|
|
63
|
+
event = {};
|
|
64
|
+
}
|
|
65
|
+
const cwd =
|
|
66
|
+
typeof event.cwd === "string" && event.cwd ? event.cwd : process.cwd();
|
|
67
|
+
|
|
68
|
+
if (!fs.existsSync(path.join(cwd, "openspec"))) {
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const version = runKeel(cwd, ["--version"]);
|
|
73
|
+
const versionMatch = String(version.stdout || "").match(/(\d+)\.\d+\.\d+/);
|
|
74
|
+
if (
|
|
75
|
+
version.error
|
|
76
|
+
|| version.status !== 0
|
|
77
|
+
|| !versionMatch
|
|
78
|
+
|| Number(versionMatch[1]) < 3
|
|
79
|
+
) {
|
|
80
|
+
fallback(
|
|
81
|
+
"the keel CLI is missing or incompatible with this plugin; install "
|
|
82
|
+
+ "@christang/keel (npm install -g @christang/keel)."
|
|
83
|
+
);
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const result = runKeel(cwd, ["context", "--json"]);
|
|
88
|
+
if (result.error || result.status !== 0 || !String(result.stdout || "").trim()) {
|
|
89
|
+
fallback("`keel context --json` failed or timed out.");
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
let context;
|
|
93
|
+
try {
|
|
94
|
+
context = JSON.parse(result.stdout);
|
|
95
|
+
} catch {
|
|
96
|
+
fallback("keel produced malformed context output.");
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const source = typeof event.source === "string" ? event.source : "";
|
|
101
|
+
const reinject = source === "compact" || source === "resume";
|
|
102
|
+
const header = source === "compact"
|
|
103
|
+
? "Keel post-compaction reinjection (disposable; recomputed from OpenSpec and Git):"
|
|
104
|
+
: source === "resume"
|
|
105
|
+
? "Keel resume reinjection (disposable; recomputed from OpenSpec and Git):"
|
|
106
|
+
: "Keel session projection (disposable; OpenSpec and Git are the durable authority):";
|
|
107
|
+
|
|
108
|
+
const lines = [header];
|
|
109
|
+
if (context.status === "ready" && context.selection) {
|
|
110
|
+
const task = context.selection.task ? `#${context.selection.task}` : "";
|
|
111
|
+
lines.push(
|
|
112
|
+
`- context ready: ${context.selection.change}${task} `
|
|
113
|
+
+ `(${context.selection.source}); next action: `
|
|
114
|
+
+ `${context.nextAction ? context.nextAction.kind : "unknown"}.`
|
|
115
|
+
);
|
|
116
|
+
if (reinject) {
|
|
117
|
+
const recorded = recordedContract(cwd, context.selection);
|
|
118
|
+
if (recorded) {
|
|
119
|
+
lines.push(
|
|
120
|
+
`- recorded Contract fingerprint: ${recorded} (recorded, not `
|
|
121
|
+
+ "verified; gates recompile and compare before any write)."
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
lines.push(
|
|
125
|
+
"- next: re-run `keel context --json`, then `keel gate task-start` "
|
|
126
|
+
+ "before continuing implementation; nothing was selected or "
|
|
127
|
+
+ "recorded by this projection."
|
|
128
|
+
);
|
|
129
|
+
} else {
|
|
130
|
+
if (Array.isArray(context.read) && context.read.length > 0) {
|
|
131
|
+
lines.push(`- read first: ${context.read.slice(0, 5).join(", ")}.`);
|
|
132
|
+
}
|
|
133
|
+
lines.push(
|
|
134
|
+
"- run `keel gate task-start` before implementation; this projection "
|
|
135
|
+
+ "selects nothing and records nothing."
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
lines.push(`- context status: ${context.status || "unknown"}.`);
|
|
140
|
+
for (const reason of (context.reasons || []).slice(0, MAX_REASONS)) {
|
|
141
|
+
lines.push(`- reason: ${String(reason).slice(0, MAX_REASON_LENGTH)}`);
|
|
142
|
+
}
|
|
143
|
+
lines.push(
|
|
144
|
+
"- next: run `keel context` and select an owner explicitly; this hook "
|
|
145
|
+
+ "does not guess among candidates."
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
emit(lines.join("\n"));
|
|
149
|
+
return 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function recordedContract(repo, selection) {
|
|
153
|
+
if (!selection || !selection.change || !selection.task) return null;
|
|
154
|
+
const tasksPath = path.join(
|
|
155
|
+
repo,
|
|
156
|
+
"openspec",
|
|
157
|
+
"changes",
|
|
158
|
+
selection.change,
|
|
159
|
+
"tasks.md"
|
|
160
|
+
);
|
|
161
|
+
let content = "";
|
|
162
|
+
try {
|
|
163
|
+
content = fs.readFileSync(tasksPath, "utf8");
|
|
164
|
+
} catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const wanted = String(selection.task);
|
|
168
|
+
let inTask = false;
|
|
169
|
+
for (const line of content.split(/\r?\n/)) {
|
|
170
|
+
const heading = line.match(/^\s*-\s+\[[ xX]\]\s+(\d+(?:\.\d+)+)\s+/);
|
|
171
|
+
if (heading) {
|
|
172
|
+
inTask = heading[1] === wanted;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (!inTask) continue;
|
|
176
|
+
const contract = line.match(/^\s*-\s*Contract:\s*(sha256:[0-9a-f]{64})\b/);
|
|
177
|
+
if (contract) return contract[1];
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
process.exit(main());
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: keel-align-expectations
|
|
3
|
+
description: Align explicit requirements, implicit expectations, non-goals, observable acceptance, evidence, and decision authority before OpenSpec specs and executable tasks finalize. Use when authoring or updating a proposal, design, specs, or tasks, or when a request hides a material product choice. Do not use for routine implementation of an already-authorized task, unrelated conversation, or facts the repository can answer.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# keel-align-expectations
|
|
7
|
+
|
|
8
|
+
## Purpose
|
|
9
|
+
|
|
10
|
+
Turn user intent and repository facts into durable OpenSpec authority before specs and executable tasks harden. Alignment is risk-scaled: clear, complete, low-risk requests pass through a compact quick path; material ambiguity switches to a focused deep path. This skill owns materiality routing, acceptance authority, and write-back ownership; how questions are presented interactively is the host runtime's concern, and it does not replace `/opsx:apply`, review, or OpenSpec artifact mechanics.
|
|
11
|
+
|
|
12
|
+
## Routing
|
|
13
|
+
|
|
14
|
+
Take the quick path when the request, repository facts, and intended observable outcome are coherent and no material choice is unresolved. Switch to the deep path when a choice can materially change: user-visible behavior, an external interface, acceptance, security/privacy/permission boundaries, data loss or migration, protocol/state/timing/reset semantics, generated equivalence, irreversible cost, a dependency or architecture commitment, or when a contradiction appears between user intent and repository facts.
|
|
15
|
+
|
|
16
|
+
Missing wording, stylistic preference, discoverable facts, and reversible implementation detail stay on the quick path. For routine implementation of an authorized task, unrelated conversation, or a factual question the repository answers, do not start a product interview and do not mutate OpenSpec artifacts.
|
|
17
|
+
|
|
18
|
+
## Quick path
|
|
19
|
+
|
|
20
|
+
State the extracted goals, non-goals, observable Acceptance, constraints, and evidence expectations compactly, label any non-material assumptions, and proceed. A complete low-risk request may satisfy the checkpoint in one compact confirmation without an interactive pause.
|
|
21
|
+
|
|
22
|
+
## Deep path
|
|
23
|
+
|
|
24
|
+
Pause spec/task finalization and ask one material decision at a time. Each question explains in plain terms why the decision matters and provides a recommended answer. Continue only until executable authority is clear; do not run a broad brainstorming session. A material choice that stays unresolved becomes a Q<n> with an owner or resolution gate, and no affected task becomes executable until it is accepted, verified, durably owned, or explicitly discarded.
|
|
25
|
+
|
|
26
|
+
## Implicit expectations are proposals
|
|
27
|
+
|
|
28
|
+
Label inferred expectations (accessibility, compatibility, failure behavior, migration, performance, security, and similar unstated candidates) as candidate expectations and explain their impact. A candidate becomes authority only when the user explicitly accepts it or durable repository/product authority verifies it. Silence does not authorize a material product choice, interface change, risk boundary, or acceptance downgrade.
|
|
29
|
+
|
|
30
|
+
## Repository facts before user questions
|
|
31
|
+
|
|
32
|
+
Before asking the user, inspect the relevant code, tests, docs, OpenSpec artifacts, issues, and verified runtime behavior. Record verified facts as F<n> with their basis; escalate only the user-owned product choice that remains after the facts are known. When sources disagree on an acceptance-relevant fact, surface the contradiction and ask who or what has authority instead of silently choosing one source.
|
|
33
|
+
|
|
34
|
+
## Write-back
|
|
35
|
+
|
|
36
|
+
Accepted alignment routes to existing OpenSpec owners; create no separate alignment ledger, chat-memory dependency, or HANDOFF payload:
|
|
37
|
+
|
|
38
|
+
- proposal.md owns motivation, scope, goals, and non-goals.
|
|
39
|
+
- design.md owns accepted D/F/A/Q statements, rationale, constraints, and risks.
|
|
40
|
+
- specs own observable requirements and positive/negative/edge/failure scenarios.
|
|
41
|
+
- tasks.md owns Covers, verification strategy and checks, scope, and stop boundaries that reference the accepted authority instead of duplicating chat prose.
|
|
42
|
+
|
|
43
|
+
## Domain references
|
|
44
|
+
|
|
45
|
+
When the change touches a specific domain, read only the applicable reference before asking domain questions; do not load the others:
|
|
46
|
+
|
|
47
|
+
- references/web.md for UI, API, routing, auth/session, persistence, or backend integration work.
|
|
48
|
+
- references/hardware.md for Verilog/SystemVerilog interface, protocol, reset, or verification work.
|
|
49
|
+
- references/hardware-dsl.md for hardware modeling DSL, generated RTL, or golden/equivalence work.
|
|
50
|
+
|
|
51
|
+
## Boundaries
|
|
52
|
+
|
|
53
|
+
Alignment does not implement code, expand Touch, change selected-task acceptance, replace `keel-review-checklist`, or own follow-ups. If implementation later discovers a new material expectation, stop and return to OpenSpec authoring before continuing.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Hardware DSL alignment reference
|
|
2
|
+
|
|
3
|
+
Domain lens for hardware modeling DSL work. Use during alignment when generated RTL, golden output, or model equivalence assumptions may be implicit.
|
|
4
|
+
|
|
5
|
+
## Material risk surface
|
|
6
|
+
|
|
7
|
+
Treat these as deep-path candidates when the change touches them: DSL syntax or semantics, generated RTL, generated tests, regeneration order, golden output, equivalence expectations, lowering rules, scheduling, reset semantics, valid-ready mapping, CSR generation, naming stability, and artifact ownership. Ask only questions that materially affect Acceptance, verification checks, Touch, specs, design decisions, or non-goals; stop when the task contract can be written without guessing.
|
|
8
|
+
|
|
9
|
+
## Durable placement
|
|
10
|
+
|
|
11
|
+
- specs for observable DSL behavior and generated artifact expectations.
|
|
12
|
+
- design.md for lowering decisions, regeneration policy, equivalence rationale, conflict authority, and baseline policy.
|
|
13
|
+
- tasks.md for Covers, verification, Touch, stop/autonomy details, and Coupling when source and generated artifacts must change together.
|
|
14
|
+
|
|
15
|
+
## Evidence expectations
|
|
16
|
+
|
|
17
|
+
Prefer reproducible evidence: golden output or snapshot evidence when generated artifacts are deterministic; regeneration commands when source changes must update generated RTL or tests; equivalence or behavioral checks when generated output must preserve model semantics; coupled-task evidence when DSL source, generated artifacts, and baselines must be evaluated as one candidate.
|
|
18
|
+
|
|
19
|
+
## Execution and review checks
|
|
20
|
+
|
|
21
|
+
While implementing and reviewing, keep source and generated artifacts moving as one candidate: after any DSL or lowering change, re-run the full regeneration command and commit-or-diff every regenerated artifact — a hand-edited generated file is a defect, not a fix. Prove semantics with equivalence or behavior checks rather than diff-silence alone, and treat golden output updates as decisions needing a recorded rationale. In review, reject changes where generated RTL, generated tests, or baselines moved without their DSL source (or vice versa), and confirm naming and interface stability expectations still hold after regeneration.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Hardware alignment reference
|
|
2
|
+
|
|
3
|
+
Domain lens for Verilog/SystemVerilog work. Use during alignment when hardware protocol, timing, reset, or verification assumptions may be implicit.
|
|
4
|
+
|
|
5
|
+
## Material risk surface
|
|
6
|
+
|
|
7
|
+
Treat these as deep-path candidates when the change touches them: signals, module interfaces, reset, valid-ready, backpressure, ordering, arbitration, CSR or protocol fields, CDC, reset crossing, timing assumptions, security/permission paths, random behavior, keys, authentication, and testbench/formal expectations. Ask only questions that materially affect Acceptance, verification checks, Touch, specs, design decisions, or non-goals; stop when the task contract can be written without guessing.
|
|
8
|
+
|
|
9
|
+
## Durable placement
|
|
10
|
+
|
|
11
|
+
- specs for observable protocol behavior, ordering guarantees, register/CSR behavior, and error cases.
|
|
12
|
+
- design.md for timing assumptions, reset/CDC rationale, arbitration choices, verification strategy, and trade-offs.
|
|
13
|
+
- tasks.md for Covers, verification, Touch, and stop/autonomy details.
|
|
14
|
+
|
|
15
|
+
## Evidence expectations
|
|
16
|
+
|
|
17
|
+
Prefer behavior evidence appropriate to the layer: simulation or testbench evidence for interface, reset, valid-ready, backpressure, ordering, and protocol behavior; lint or static checks for structural safety when they are part of Acceptance; formal or assertion evidence for invariants tests cannot exhaustively cover; golden traces or waveform references only when stable and tied to Acceptance.
|
|
18
|
+
|
|
19
|
+
## Execution and review checks
|
|
20
|
+
|
|
21
|
+
While implementing and reviewing, prove behavior at the interface: drive reset entry and exit, valid-ready handshakes including stall and backpressure, and ordering through simulation before trusting lint; a protocol or CSR field change needs testbench evidence for both legal and illegal accesses. Watch for silent X-propagation, unregistered CDC crossings, and new reset-domain assumptions introduced by the change. In review, reject waveform screenshots without a repeatable run command, require assertion or formal evidence for invariants simulation cannot exhaustively cover, and confirm any golden trace update carries an explicit rationale tied to Acceptance.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Web alignment reference
|
|
2
|
+
|
|
3
|
+
Domain lens for web frontend/backend work. Use during alignment when UI, API, routing, persistence, or integration behavior may hide product assumptions.
|
|
4
|
+
|
|
5
|
+
## Material risk surface
|
|
6
|
+
|
|
7
|
+
Treat these as deep-path candidates when the change touches them: UI-observable behavior, public interface contracts, routing, auth/session behavior, persistence, migrations, async state, loading/error states, accessibility expectations, browser compatibility, and backend integration boundaries. Ask only questions that materially affect Acceptance, verification checks, Touch, specs, design decisions, or non-goals; stop when the task contract can be written without guessing.
|
|
8
|
+
|
|
9
|
+
## Durable placement
|
|
10
|
+
|
|
11
|
+
- specs for user-visible behavior, API behavior, error handling, and scenarios.
|
|
12
|
+
- design.md for framework choices, data flow, migration notes, auth/session assumptions, and trade-offs.
|
|
13
|
+
- tasks.md for Covers, verification, Touch, and stop/autonomy details.
|
|
14
|
+
|
|
15
|
+
## Evidence expectations
|
|
16
|
+
|
|
17
|
+
Prefer public interface evidence: UI behavior through a rendered page or component workflow (no self-mocked internals); API behavior through route/client contract checks including user-affecting failure paths; data changes through migration/integration evidence when persistence semantics change; accessibility or responsive checks when Acceptance names them.
|
|
18
|
+
|
|
19
|
+
## Execution and review checks
|
|
20
|
+
|
|
21
|
+
While implementing and reviewing, prove behavior through the public interface: exercise UI changes in the rendered page, hit at least one user-affecting failure path (auth expiry, empty, error, slow response), and treat route, status-code, header, and payload shape as contract — a contract change needs a durable spec or task owner before review passes. Run persistence changes through a real forward migration and confirm auth/session boundaries did not silently widen. In review, reject build-only or component-shape evidence, and require explicit evidence for every loading, error, or empty state that Acceptance names.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: keel-debug-failure
|
|
3
|
+
description: Debug Keel agent command or validation failures under Keel's selected OpenSpec task contract. Use when `/opsx:apply` or a selected task reports failed commands, failing tests, validation errors, retry-fuse decisions, or needs an evidence-bound failure report.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# keel-debug-failure
|
|
7
|
+
## Purpose
|
|
8
|
+
|
|
9
|
+
Use this skill when a command or validation path fails. Keep the investigation tied to the selected OpenSpec task. This skill owns the retry fuse, the task-contract binding, checkout preservation, and the failure-report shape; generic diagnosis mechanics (how to reproduce, minimise, or instrument) belong to the host runtime and are not restated here.
|
|
10
|
+
|
|
11
|
+
## Context to read
|
|
12
|
+
|
|
13
|
+
Read the selected OpenSpec task's Commands, Acceptance, Coupling, Candidate Boundary, Autonomy boundary, Stop Rules, Evidence, Stop if, Read, Touch, and Mode fields. When `Coupling: required`, also read design.md's Coupled Iteration Contract. Read the failed command output and any repository files needed to reproduce or explain the failure.
|
|
14
|
+
|
|
15
|
+
## Domain reference
|
|
16
|
+
|
|
17
|
+
When the change's artifacts or the failing surface signal a supported domain (web, hardware, hardware-dsl), consult the matching reference's `Execution and review checks` section under `keel-align-expectations/references/` before locking a root-cause hypothesis — only the matching reference, never all of them. When no domain signal exists, load nothing.
|
|
18
|
+
|
|
19
|
+
## Fuse
|
|
20
|
+
|
|
21
|
+
Try the same failure at most 2 attempts. If it still fails, stop and report the original key error plus what changed during each attempt.
|
|
22
|
+
|
|
23
|
+
For `Coupling: required`, an allowed provisional failure inside a candidate does
|
|
24
|
+
not count as an attempt. Count one attempt only when the same final assertion
|
|
25
|
+
fails at the candidate completion gate. Follow the task's immediate task-stop
|
|
26
|
+
rules for scope breach, missing design authority, non-reproducible evidence,
|
|
27
|
+
or a baseline or acceptance change during a candidate.
|
|
28
|
+
|
|
29
|
+
Preserve the current checkout on verification failure or context pressure.
|
|
30
|
+
Only roll back to last-green when the selected task or human instruction
|
|
31
|
+
explicitly authorizes it, and keep the failure evidence available.
|
|
32
|
+
|
|
33
|
+
## Report boundary
|
|
34
|
+
|
|
35
|
+
Follow-ups are limited to directly observed blockers, risks, missing scope, or escalation needs. Do not include roadmap suggestions, new feature suggestions, unrelated architecture critique, or opportunistic refactor suggestions.
|
|
36
|
+
|
|
37
|
+
When the minimal fix needs files outside Touch, report an Out-of-scope Need with file or area, why needed, consequence if unchanged, minimal proposed change, and evidence. Do not modify those files.
|
|
38
|
+
|
|
39
|
+
## Standalone use
|
|
40
|
+
|
|
41
|
+
When used alone, report Summary, Failed command, Failure evidence, Attempts, Current hypothesis, and Required decision.
|