create-keel-app 0.2.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/LICENSE +21 -0
- package/README.md +38 -0
- package/dist/dep-emit.d.ts +6 -0
- package/dist/dep-emit.d.ts.map +1 -0
- package/dist/dep-emit.js +47 -0
- package/dist/dep-emit.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/scaffold.d.ts +13 -0
- package/dist/scaffold.d.ts.map +1 -0
- package/dist/scaffold.js +57 -0
- package/dist/scaffold.js.map +1 -0
- package/package.json +43 -0
- package/templates/ai-app/README.md.tpl +56 -0
- package/templates/ai-app/architecture/data.ts.tpl +76 -0
- package/templates/ai-app/extras/mcp.md.tpl +148 -0
- package/templates/ai-app/package.json.tpl +37 -0
- package/templates/ai-app/src/agents/thin-slice.ts.tpl +28 -0
- package/templates/ai-app/src/index.ts.tpl +20 -0
- package/templates/ai-app/src/tools/hello.test.ts.tpl +14 -0
- package/templates/ai-app/src/tools/hello.ts.tpl +9 -0
- package/templates/ai-app/src/tools/index.ts.tpl +6 -0
- package/templates/framework/README.md.tpl +31 -0
- package/templates/framework/package.json.tpl +30 -0
- package/templates/framework/src/index.test.ts.tpl +8 -0
- package/templates/framework/src/index.ts.tpl +1 -0
- package/templates/shared/.github/workflows/keel-verify.yml.tpl +29 -0
- package/templates/shared/.gitignore.tpl +10 -0
- package/templates/shared/.keel/local-skill.md.tpl +19 -0
- package/templates/shared/.keel/memory.md.tpl +15 -0
- package/templates/shared/CLAUDE.md.tpl +28 -0
- package/templates/shared/architecture/data.ts.tpl +45 -0
- package/templates/shared/eslint.config.js.tpl +42 -0
- package/templates/shared/principles/index.ts.tpl +54 -0
- package/templates/shared/tsconfig.json.tpl +22 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 jglasskatz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# create-keel-app
|
|
2
|
+
|
|
3
|
+
Scaffold a new Keel project with all conventions pre-wired.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx create-keel-app <project-name>
|
|
9
|
+
npx create-keel-app <project-name> --template ai-app # default
|
|
10
|
+
npx create-keel-app <project-name> --template framework
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The default template (`ai-app`) creates a project with a working MCP tool server,
|
|
14
|
+
thin-slice agent, architecture map, and all Keel conventions in place.
|
|
15
|
+
The `framework` template creates a minimal framework-only scaffold without the
|
|
16
|
+
agent and MCP demo wiring.
|
|
17
|
+
|
|
18
|
+
## Options
|
|
19
|
+
|
|
20
|
+
### `--template <name>`
|
|
21
|
+
|
|
22
|
+
Choose a project template. Valid values: `ai-app` (default) or `framework`.
|
|
23
|
+
|
|
24
|
+
### `--local <path-to-keel>`
|
|
25
|
+
|
|
26
|
+
Point the scaffolded project at a local Keel checkout via `link:` deps (pnpm link: protocol, also supported in npm 8+) instead of `"*"` npm version placeholders. Use this when the `@keel_flow/*` packages are not yet published to npm.
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx create-keel-app my-project --local /path/to/keel
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The resulting `package.json` will have entries like:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
"@keel_flow/core": "link:/path/to/keel/packages/core"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Running `pnpm install` inside the scaffolded project will then resolve all `@keel_flow/*` deps from your local checkout immediately, with no publish step required.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface KeelDepResolution {
|
|
2
|
+
versions: Record<string, string>;
|
|
3
|
+
}
|
|
4
|
+
export declare function resolveKeelDeps(localPath: string | undefined): KeelDepResolution;
|
|
5
|
+
export declare function applyKeelDeps(packageJsonContent: string, versions: Record<string, string>): string;
|
|
6
|
+
//# sourceMappingURL=dep-emit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dep-emit.d.ts","sourceRoot":"","sources":["../src/dep-emit.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAmBD,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,iBAAiB,CAoBhF;AAED,wBAAgB,aAAa,CAC3B,kBAAkB,EAAE,MAAM,EAC1B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,MAAM,CAQR"}
|
package/dist/dep-emit.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { existsSync } from "fs";
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
const PACKAGE_DEPS = [
|
|
4
|
+
"@keel_flow/cli",
|
|
5
|
+
"@keel_flow/core",
|
|
6
|
+
"@keel_flow/schema",
|
|
7
|
+
"@keel_flow/runtime",
|
|
8
|
+
"@keel_flow/adopt",
|
|
9
|
+
"@keel_flow/extend",
|
|
10
|
+
"@keel_flow/pack-general-se",
|
|
11
|
+
"@keel_flow/pack-agent-topology",
|
|
12
|
+
"@keel_flow/pack-kb-hygiene",
|
|
13
|
+
"@keel_flow/eslint-plugin",
|
|
14
|
+
];
|
|
15
|
+
const FOLDER_OVERRIDES = {
|
|
16
|
+
"@keel_flow/eslint-plugin": "eslint-plugin",
|
|
17
|
+
};
|
|
18
|
+
export function resolveKeelDeps(localPath) {
|
|
19
|
+
const versions = {};
|
|
20
|
+
if (!localPath) {
|
|
21
|
+
// Pin to the current minor (^0.2.0) rather than "*": a future breaking
|
|
22
|
+
// @keel_flow minor must not retroactively break already-scaffolded apps or a
|
|
23
|
+
// fresh `npx create-keel-app`.
|
|
24
|
+
for (const pkg of PACKAGE_DEPS)
|
|
25
|
+
versions[pkg] = "^0.2.0";
|
|
26
|
+
return { versions };
|
|
27
|
+
}
|
|
28
|
+
const root = resolve(localPath);
|
|
29
|
+
if (!existsSync(`${root}/packages/core/package.json`)) {
|
|
30
|
+
throw new Error(`--local path "${root}" does not look like a Keel checkout (no packages/core/package.json)`);
|
|
31
|
+
}
|
|
32
|
+
for (const pkg of PACKAGE_DEPS) {
|
|
33
|
+
const folder = FOLDER_OVERRIDES[pkg] ?? pkg.replace("@keel_flow/", "");
|
|
34
|
+
versions[pkg] = `link:${root}/packages/${folder}`;
|
|
35
|
+
}
|
|
36
|
+
return { versions };
|
|
37
|
+
}
|
|
38
|
+
export function applyKeelDeps(packageJsonContent, versions) {
|
|
39
|
+
let updated = packageJsonContent;
|
|
40
|
+
for (const [name, version] of Object.entries(versions)) {
|
|
41
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
42
|
+
const re = new RegExp(`("${escaped}"\\s*:\\s*)"[^"]*"`);
|
|
43
|
+
updated = updated.replace(re, `$1"${version}"`);
|
|
44
|
+
}
|
|
45
|
+
return updated;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=dep-emit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dep-emit.js","sourceRoot":"","sources":["../src/dep-emit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAM/B,MAAM,YAAY,GAAG;IACnB,gBAAgB;IAChB,iBAAiB;IACjB,mBAAmB;IACnB,oBAAoB;IACpB,kBAAkB;IAClB,mBAAmB;IACnB,4BAA4B;IAC5B,gCAAgC;IAChC,4BAA4B;IAC5B,0BAA0B;CAC3B,CAAC;AAEF,MAAM,gBAAgB,GAA2B;IAC/C,0BAA0B,EAAE,eAAe;CAC5C,CAAC;AAEF,MAAM,UAAU,eAAe,CAAC,SAA6B;IAC3D,MAAM,QAAQ,GAA2B,EAAE,CAAC;IAC5C,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,uEAAuE;QACvE,6EAA6E;QAC7E,+BAA+B;QAC/B,KAAK,MAAM,GAAG,IAAI,YAAY;YAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC;QACzD,OAAO,EAAE,QAAQ,EAAE,CAAC;IACtB,CAAC;IACD,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,IAAI,CAAC,UAAU,CAAC,GAAG,IAAI,6BAA6B,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CACb,iBAAiB,IAAI,sEAAsE,CAC5F,CAAC;IACJ,CAAC;IACD,KAAK,MAAM,GAAG,IAAI,YAAY,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,gBAAgB,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;QACvE,QAAQ,CAAC,GAAG,CAAC,GAAG,QAAQ,IAAI,aAAa,MAAM,EAAE,CAAC;IACpD,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,aAAa,CAC3B,kBAA0B,EAC1B,QAAgC;IAEhC,IAAI,OAAO,GAAG,kBAAkB,CAAC;IACjC,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QACvD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;QAC5D,MAAM,EAAE,GAAG,IAAI,MAAM,CAAC,KAAK,OAAO,oBAAoB,CAAC,CAAC;QACxD,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,OAAO,GAAG,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { resolve } from "path";
|
|
3
|
+
import { existsSync } from "fs";
|
|
4
|
+
import { scaffold, VALID_TEMPLATES, DEFAULT_TEMPLATE } from "./scaffold.js";
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
let localKeelPath;
|
|
7
|
+
const localFlagIdx = args.indexOf("--local");
|
|
8
|
+
if (localFlagIdx !== -1) {
|
|
9
|
+
const raw = args[localFlagIdx + 1];
|
|
10
|
+
if (!raw || raw.startsWith("--")) {
|
|
11
|
+
process.stderr.write("--local requires a path argument (pnpm link: protocol)\n");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
localKeelPath = resolve(raw);
|
|
15
|
+
if (!existsSync(`${localKeelPath}/packages/core/package.json`)) {
|
|
16
|
+
process.stderr.write(`--local path "${localKeelPath}" does not look like a Keel checkout (no packages/core/package.json)\n`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
args.splice(localFlagIdx, 2);
|
|
20
|
+
}
|
|
21
|
+
let template = DEFAULT_TEMPLATE;
|
|
22
|
+
const templateFlagIdx = args.indexOf("--template");
|
|
23
|
+
if (templateFlagIdx !== -1) {
|
|
24
|
+
const raw = args[templateFlagIdx + 1];
|
|
25
|
+
if (!raw || raw.startsWith("--")) {
|
|
26
|
+
process.stderr.write(`--template requires a value. Valid templates: ${VALID_TEMPLATES.join(", ")}\n`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
if (!VALID_TEMPLATES.includes(raw)) {
|
|
30
|
+
process.stderr.write(`Unknown template "${raw}". Valid templates: ${VALID_TEMPLATES.join(", ")}\n`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
template = raw;
|
|
34
|
+
args.splice(templateFlagIdx, 2);
|
|
35
|
+
}
|
|
36
|
+
const projectName = args[0];
|
|
37
|
+
if (!projectName) {
|
|
38
|
+
process.stderr.write("Usage: create-keel-app <project-name> [--template framework|ai-app] [--local <path-to-keel>]\n");
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
if (!/^[a-z0-9-_]+$/i.test(projectName)) {
|
|
42
|
+
process.stderr.write(`Invalid project name "${projectName}". Use only letters, numbers, hyphens, and underscores.\n`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
const targetDir = resolve(process.cwd(), projectName);
|
|
46
|
+
process.stdout.write(`Creating Keel project: ${projectName}\n`);
|
|
47
|
+
process.stdout.write(`Template: ${template}\n`);
|
|
48
|
+
process.stdout.write(`Target: ${targetDir}\n\n`);
|
|
49
|
+
const scaffoldOptions = { template };
|
|
50
|
+
if (localKeelPath)
|
|
51
|
+
scaffoldOptions.localKeelPath = localKeelPath;
|
|
52
|
+
scaffold(projectName, targetDir, scaffoldOptions);
|
|
53
|
+
process.stdout.write(`Done! Next steps:\n`);
|
|
54
|
+
process.stdout.write(` cd ${projectName}\n`);
|
|
55
|
+
process.stdout.write(` pnpm install\n`);
|
|
56
|
+
if (template === "ai-app") {
|
|
57
|
+
process.stdout.write(` export ANTHROPIC_API_KEY=sk-...\n`);
|
|
58
|
+
process.stdout.write(` pnpm dev\n`);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
process.stdout.write(` pnpm verify\n`);
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { relative, resolve } from "path";
|
|
2
|
+
import { statSync } from "fs";
|
|
3
|
+
export type TemplateName = "framework" | "ai-app";
|
|
4
|
+
export declare const VALID_TEMPLATES: TemplateName[];
|
|
5
|
+
export declare const DEFAULT_TEMPLATE: TemplateName;
|
|
6
|
+
export interface ScaffoldOptions {
|
|
7
|
+
template?: TemplateName;
|
|
8
|
+
localKeelPath?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function scaffold(projectName: string, targetDir: string, optionsOrLocalKeelPath?: ScaffoldOptions | string): void;
|
|
11
|
+
export declare function getTemplatesDir(): string;
|
|
12
|
+
export { relative, statSync, resolve };
|
|
13
|
+
//# sourceMappingURL=scaffold.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,QAAQ,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAExD,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,CAAC;AAK9B,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,QAAQ,CAAC;AAElD,eAAO,MAAM,eAAe,EAAE,YAAY,EAA4B,CAAC;AACvE,eAAO,MAAM,gBAAgB,EAAE,YAAuB,CAAC;AAsCvD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,QAAQ,CACtB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,sBAAsB,CAAC,EAAE,eAAe,GAAG,MAAM,GAChD,IAAI,CAyBN;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC"}
|
package/dist/scaffold.js
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync, readdirSync, readFileSync } from "fs";
|
|
2
|
+
import { join, dirname, relative, resolve } from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
import { statSync } from "fs";
|
|
5
|
+
import { resolveKeelDeps, applyKeelDeps } from "./dep-emit.js";
|
|
6
|
+
const TEMPLATES_DIR = join(dirname(fileURLToPath(import.meta.url)), "../templates");
|
|
7
|
+
export const VALID_TEMPLATES = ["framework", "ai-app"];
|
|
8
|
+
export const DEFAULT_TEMPLATE = "ai-app";
|
|
9
|
+
function substitute(content, projectName) {
|
|
10
|
+
return content.replaceAll("{{PROJECT_NAME}}", projectName);
|
|
11
|
+
}
|
|
12
|
+
function writeTemplate(templatePath, destPath, projectName) {
|
|
13
|
+
const content = readFileSync(templatePath, "utf-8");
|
|
14
|
+
const substituted = substitute(content, projectName);
|
|
15
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
16
|
+
writeFileSync(destPath, substituted, "utf-8");
|
|
17
|
+
}
|
|
18
|
+
function copyTemplates(srcDir, destDir, projectName, skipDirNames = new Set()) {
|
|
19
|
+
const entries = readdirSync(srcDir, { withFileTypes: true });
|
|
20
|
+
for (const entry of entries) {
|
|
21
|
+
if (entry.isDirectory() && skipDirNames.has(entry.name))
|
|
22
|
+
continue;
|
|
23
|
+
const srcPath = join(srcDir, entry.name);
|
|
24
|
+
if (entry.isDirectory()) {
|
|
25
|
+
copyTemplates(srcPath, join(destDir, entry.name), projectName, skipDirNames);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
const destName = entry.name.endsWith(".tpl")
|
|
29
|
+
? entry.name.slice(0, -4)
|
|
30
|
+
: entry.name;
|
|
31
|
+
writeTemplate(srcPath, join(destDir, destName), projectName);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function scaffold(projectName, targetDir, optionsOrLocalKeelPath) {
|
|
36
|
+
const options = typeof optionsOrLocalKeelPath === "string"
|
|
37
|
+
? { localKeelPath: optionsOrLocalKeelPath }
|
|
38
|
+
: (optionsOrLocalKeelPath ?? {});
|
|
39
|
+
const template = options.template ?? DEFAULT_TEMPLATE;
|
|
40
|
+
if (!VALID_TEMPLATES.includes(template)) {
|
|
41
|
+
throw new Error(`Unknown template "${template}". Valid templates: ${VALID_TEMPLATES.join(", ")}`);
|
|
42
|
+
}
|
|
43
|
+
mkdirSync(targetDir, { recursive: true });
|
|
44
|
+
const sharedDir = join(TEMPLATES_DIR, "shared");
|
|
45
|
+
copyTemplates(sharedDir, targetDir, projectName);
|
|
46
|
+
const templateDir = join(TEMPLATES_DIR, template);
|
|
47
|
+
copyTemplates(templateDir, targetDir, projectName, new Set(["extras"]));
|
|
48
|
+
const pkgJsonPath = join(targetDir, "package.json");
|
|
49
|
+
const pkgContent = readFileSync(pkgJsonPath, "utf-8");
|
|
50
|
+
const { versions } = resolveKeelDeps(options.localKeelPath);
|
|
51
|
+
writeFileSync(pkgJsonPath, applyKeelDeps(pkgContent, versions), "utf-8");
|
|
52
|
+
}
|
|
53
|
+
export function getTemplatesDir() {
|
|
54
|
+
return TEMPLATES_DIR;
|
|
55
|
+
}
|
|
56
|
+
export { relative, statSync, resolve };
|
|
57
|
+
//# sourceMappingURL=scaffold.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../src/scaffold.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-keel-app",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"templates",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"bin": {
|
|
15
|
+
"create-keel-app": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"require": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@keel_flow/schema": "0.2.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^25.9.1",
|
|
31
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
32
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
33
|
+
"eslint": "^9.0.0",
|
|
34
|
+
"typescript": "^5.5.0",
|
|
35
|
+
"vitest": "^2.0.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"test": "vitest run",
|
|
41
|
+
"lint": "eslint src"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
A Keel AI-app project. https://github.com/jglasskatz/keel
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm install
|
|
9
|
+
export ANTHROPIC_API_KEY=sk-...
|
|
10
|
+
pnpm dev
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Verify
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm verify
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Structure
|
|
20
|
+
|
|
21
|
+
- `architecture/data.ts` — architecture map (keep in sync with code)
|
|
22
|
+
- `principles/index.ts` — engineering principles registry
|
|
23
|
+
- `src/agents/` — agent definitions via `defineAgent`
|
|
24
|
+
- `src/tools/` — in-process tool definitions via `defineTool`
|
|
25
|
+
- `src/index.ts` — bootstrap entrypoint
|
|
26
|
+
|
|
27
|
+
## Tools (in-process)
|
|
28
|
+
|
|
29
|
+
Tools live in `src/tools/` and are defined with `defineTool` from
|
|
30
|
+
`@keel_flow/core`. Each tool exports a typed handler with a Zod
|
|
31
|
+
`inputSchema`. The agent loop in `@keel_flow/runtime` invokes them
|
|
32
|
+
directly in-process — no JSON-RPC, no child process, no extra
|
|
33
|
+
runtime cost.
|
|
34
|
+
|
|
35
|
+
### Adding a new tool
|
|
36
|
+
|
|
37
|
+
1. Create `src/tools/my-tool.ts` exporting a `defineTool({...})` value.
|
|
38
|
+
2. Register it in `src/tools/index.ts` so it's part of the tool
|
|
39
|
+
registry.
|
|
40
|
+
3. Pass it into `runThinSlice({ ..., tools: [...] })` (or whichever
|
|
41
|
+
agent invocation needs it).
|
|
42
|
+
|
|
43
|
+
### Opt-in: MCP
|
|
44
|
+
|
|
45
|
+
If you need cross-host tool reuse (third-party MCP servers, sharing
|
|
46
|
+
the same tool with a Claude Desktop install, multi-process
|
|
47
|
+
isolation), see `create-keel-app`'s `extras/mcp.md` reference doc
|
|
48
|
+
for the recipe. MCP is not included in the default scaffold — most
|
|
49
|
+
projects don't need it.
|
|
50
|
+
|
|
51
|
+
## Adding an agent
|
|
52
|
+
|
|
53
|
+
1. Create `src/agents/my-agent.ts` using `defineAgent` from `@keel_flow/core`.
|
|
54
|
+
2. Add the node to `architecture/data.ts`.
|
|
55
|
+
3. Wire it into `src/index.ts`.
|
|
56
|
+
4. Run `pnpm verify` — all checks must pass.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { ArchitectureMap } from "@keel_flow/schema";
|
|
2
|
+
import { SCHEMA_VERSION } from "@keel_flow/schema";
|
|
3
|
+
|
|
4
|
+
const data: ArchitectureMap = {
|
|
5
|
+
schemaVersion: SCHEMA_VERSION,
|
|
6
|
+
layers: [
|
|
7
|
+
{
|
|
8
|
+
id: "agents",
|
|
9
|
+
label: "Agents",
|
|
10
|
+
description: "AI agents for the {{PROJECT_NAME}} system",
|
|
11
|
+
color: "#4f46e5",
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: "tools",
|
|
15
|
+
label: "Tools",
|
|
16
|
+
description: "In-process tools called by agents via defineTool",
|
|
17
|
+
color: "#0891b2",
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
nodes: [
|
|
21
|
+
{
|
|
22
|
+
id: "thin-slice-agent",
|
|
23
|
+
label: "Thin Slice Agent",
|
|
24
|
+
layer: "agents",
|
|
25
|
+
tech: "TypeScript / Anthropic SDK",
|
|
26
|
+
description:
|
|
27
|
+
"Seed agent for {{PROJECT_NAME}} — replace with your domain agents",
|
|
28
|
+
keyFiles: ["src/agents/thin-slice.ts"],
|
|
29
|
+
keyFunctionality: ["accept prompt", "call in-process tools", "return response"],
|
|
30
|
+
fileCount: 1,
|
|
31
|
+
linesOfCode: "~40",
|
|
32
|
+
principles: {
|
|
33
|
+
upholds: [
|
|
34
|
+
{ id: "single-source-of-truth-per-tool", reason: "Each tool has one data path" },
|
|
35
|
+
],
|
|
36
|
+
violates: [],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: "hello-tool",
|
|
41
|
+
label: "Hello Tool",
|
|
42
|
+
layer: "tools",
|
|
43
|
+
tech: "TypeScript / Zod",
|
|
44
|
+
description:
|
|
45
|
+
"Example in-process tool defined via defineTool — replace with domain-specific tools",
|
|
46
|
+
keyFiles: ["src/tools/hello.ts"],
|
|
47
|
+
keyFunctionality: ["greet by name"],
|
|
48
|
+
fileCount: 1,
|
|
49
|
+
linesOfCode: "~10",
|
|
50
|
+
principles: {
|
|
51
|
+
upholds: [
|
|
52
|
+
{ id: "tool-schemas-are-load-bearing", reason: "Zod inputSchema is source of truth" },
|
|
53
|
+
],
|
|
54
|
+
violates: [],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
connections: [
|
|
59
|
+
{
|
|
60
|
+
from: "thin-slice-agent",
|
|
61
|
+
to: "hello-tool",
|
|
62
|
+
type: "tool-call",
|
|
63
|
+
label: "calls hello",
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
contexts: [
|
|
67
|
+
{
|
|
68
|
+
id: "thin-slice",
|
|
69
|
+
label: "Thin Slice",
|
|
70
|
+
description: "Initial runnable context — expand as you add domain agents",
|
|
71
|
+
nodeIds: ["thin-slice-agent", "hello-tool"],
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export default data;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Opt-in: Adding MCP to a Keel ai-app
|
|
2
|
+
|
|
3
|
+
The default ai-app scaffold uses **in-process tools** defined with
|
|
4
|
+
`defineTool` from `@keel_flow/core`. The agent loop calls them directly.
|
|
5
|
+
This is the simplest, fastest, and most debuggable arrangement, and
|
|
6
|
+
is the right default for almost every project.
|
|
7
|
+
|
|
8
|
+
You only need MCP (Model Context Protocol) if you have one of these
|
|
9
|
+
specific needs:
|
|
10
|
+
|
|
11
|
+
- **Cross-host tool reuse** — a tool server you want to share
|
|
12
|
+
between this app, Claude Desktop, and other agents.
|
|
13
|
+
- **Process isolation** — a tool that needs to run as a separate
|
|
14
|
+
process for sandboxing or because it uses a runtime your main
|
|
15
|
+
process cannot.
|
|
16
|
+
- **Third-party MCP servers** — you want to consume an existing
|
|
17
|
+
off-the-shelf MCP server (filesystem, GitHub, databases).
|
|
18
|
+
|
|
19
|
+
If none of those apply, **stick with in-process tools**. MCP adds a
|
|
20
|
+
JSON-RPC layer, a child process, and a schema-translation round
|
|
21
|
+
trip for zero functional benefit in the single-process case.
|
|
22
|
+
|
|
23
|
+
> See the Keel whitepaper sections on MCP for the framework-level
|
|
24
|
+
> rationale and the agent-topology principles that govern when to
|
|
25
|
+
> reach for it.
|
|
26
|
+
|
|
27
|
+
## Recipe (when you do need it)
|
|
28
|
+
|
|
29
|
+
### 1. Install the SDK
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pnpm add @modelcontextprotocol/sdk
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Create `src/mcp-server.ts`
|
|
36
|
+
|
|
37
|
+
A minimal stdio-transport MCP server that exposes the same
|
|
38
|
+
`helloTool` over the wire:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
42
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
43
|
+
import {
|
|
44
|
+
CallToolRequestSchema,
|
|
45
|
+
ListToolsRequestSchema,
|
|
46
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
47
|
+
|
|
48
|
+
const server = new Server(
|
|
49
|
+
{ name: "{{PROJECT_NAME}}-tools", version: "0.1.0" },
|
|
50
|
+
{ capabilities: { tools: {} } },
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
54
|
+
tools: [
|
|
55
|
+
{
|
|
56
|
+
name: "hello",
|
|
57
|
+
description: "Say hello to a name.",
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: { name: { type: "string" } },
|
|
61
|
+
required: ["name"],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
68
|
+
if (req.params.name === "hello") {
|
|
69
|
+
const input = req.params.arguments as { name: string };
|
|
70
|
+
return {
|
|
71
|
+
content: [{ type: "text", text: `Hello, ${input.name}!` }],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
throw new Error(`Unknown tool: ${req.params.name}`);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const transport = new StdioServerTransport();
|
|
78
|
+
await server.connect(transport);
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 3. Adapt the in-process tool through MCP
|
|
82
|
+
|
|
83
|
+
Create `src/mcp-client.ts` that spawns the server, lists tools, and
|
|
84
|
+
returns `ToolDefinition[]` for `@keel_flow/runtime`:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
88
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
89
|
+
import { defineTool } from "@keel_flow/core";
|
|
90
|
+
import { z } from "zod";
|
|
91
|
+
import type { ToolDefinition } from "@keel_flow/runtime";
|
|
92
|
+
|
|
93
|
+
export async function createMCPTools(
|
|
94
|
+
command: string[],
|
|
95
|
+
): Promise<ToolDefinition[]> {
|
|
96
|
+
const [cmd, ...args] = command;
|
|
97
|
+
if (!cmd) throw new Error("command must have at least one element");
|
|
98
|
+
|
|
99
|
+
const transport = new StdioClientTransport({ command: cmd, args });
|
|
100
|
+
const client = new Client(
|
|
101
|
+
{ name: "{{PROJECT_NAME}}-client", version: "0.1.0" },
|
|
102
|
+
{ capabilities: {} },
|
|
103
|
+
);
|
|
104
|
+
await client.connect(transport);
|
|
105
|
+
|
|
106
|
+
const { tools: mcpTools } = await client.listTools();
|
|
107
|
+
return mcpTools.map((t) =>
|
|
108
|
+
defineTool({
|
|
109
|
+
id: t.name,
|
|
110
|
+
description: t.description ?? t.name,
|
|
111
|
+
inputSchema: z.object({ name: z.string() }),
|
|
112
|
+
async handler(input: unknown) {
|
|
113
|
+
const result = await client.callTool({
|
|
114
|
+
name: t.name,
|
|
115
|
+
arguments: input as Record<string, unknown>,
|
|
116
|
+
});
|
|
117
|
+
return result.content;
|
|
118
|
+
},
|
|
119
|
+
}) as ToolDefinition,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 4. Wire it from `src/index.ts`
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
import { createProvider } from "@keel_flow/runtime";
|
|
128
|
+
import { runThinSlice } from "./agents/thin-slice.js";
|
|
129
|
+
import { createMCPTools } from "./mcp-client.js";
|
|
130
|
+
|
|
131
|
+
const provider = createProvider({ kind: "anthropic", apiKey: process.env["ANTHROPIC_API_KEY"] ?? "" });
|
|
132
|
+
const tools = await createMCPTools(["node", "./dist/mcp-server.js"]);
|
|
133
|
+
const result = await runThinSlice({ provider, tools, prompt: "Say hi to the world." });
|
|
134
|
+
console.log(result);
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 5. Update the architecture map
|
|
138
|
+
|
|
139
|
+
Add a `mcp-server` node + an `mcp-client` node to
|
|
140
|
+
`architecture/data.ts`. Mark the tool layer's tech as
|
|
141
|
+
`"TypeScript / MCP"` and add the connections explicitly. Verify
|
|
142
|
+
will block the merge until you do.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
**Note:** this document is reference material that ships with
|
|
147
|
+
`create-keel-app`. It is **not** emitted into your project by
|
|
148
|
+
default. Copy from it manually when you decide you need MCP.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "tsx watch ./src/index.ts",
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"typecheck": "tsc --noEmit",
|
|
9
|
+
"test": "vitest run",
|
|
10
|
+
"lint": "eslint .",
|
|
11
|
+
"verify": "keel verify --diff origin/main..HEAD"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@anthropic-ai/sdk": "*",
|
|
15
|
+
"@keel_flow/adopt": "^0.2.0",
|
|
16
|
+
"@keel_flow/cli": "^0.2.0",
|
|
17
|
+
"@keel_flow/core": "^0.2.0",
|
|
18
|
+
"@keel_flow/eslint-plugin": "^0.2.0",
|
|
19
|
+
"@keel_flow/extend": "^0.2.0",
|
|
20
|
+
"@keel_flow/runtime": "^0.2.0",
|
|
21
|
+
"@keel_flow/schema": "^0.2.0",
|
|
22
|
+
"@keel_flow/pack-agent-topology": "^0.2.0",
|
|
23
|
+
"@keel_flow/pack-general-se": "^0.2.0",
|
|
24
|
+
"@keel_flow/pack-kb-hygiene": "^0.2.0",
|
|
25
|
+
"next": "*",
|
|
26
|
+
"zod": "^3.23.0"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^20.0.0",
|
|
30
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
31
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
32
|
+
"eslint": "^9.0.0",
|
|
33
|
+
"tsx": "^4.0.0",
|
|
34
|
+
"typescript": "^5.5.0",
|
|
35
|
+
"vitest": "^2.0.0"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { defineAgent } from "@keel_flow/core";
|
|
2
|
+
import { runAgent } from "@keel_flow/runtime";
|
|
3
|
+
import type { ModelProvider, ToolDefinition } from "@keel_flow/runtime";
|
|
4
|
+
|
|
5
|
+
export const thinSliceAgent = defineAgent({
|
|
6
|
+
id: "thin-slice-agent",
|
|
7
|
+
description:
|
|
8
|
+
"Seed agent for {{PROJECT_NAME}} — accepts a prompt and invokes in-process tools",
|
|
9
|
+
tools: [],
|
|
10
|
+
prompt:
|
|
11
|
+
"You are a helpful assistant for the {{PROJECT_NAME}} project. Use the available tools to demonstrate end-to-end tool invocation.",
|
|
12
|
+
model: "claude-sonnet-4-6",
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export interface RunThinSliceArgs {
|
|
16
|
+
provider: ModelProvider;
|
|
17
|
+
tools: ToolDefinition[];
|
|
18
|
+
prompt: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function runThinSlice(args: RunThinSliceArgs) {
|
|
22
|
+
return runAgent({
|
|
23
|
+
agent: thinSliceAgent.config,
|
|
24
|
+
tools: args.tools,
|
|
25
|
+
prompt: args.prompt,
|
|
26
|
+
provider: args.provider,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createProvider } from "@keel_flow/runtime";
|
|
2
|
+
import { runThinSlice } from "./agents/thin-slice.js";
|
|
3
|
+
import { toolRegistry } from "./tools/index.js";
|
|
4
|
+
|
|
5
|
+
const provider = createProvider({
|
|
6
|
+
kind:
|
|
7
|
+
process.env["KEEL_PROVIDER_KIND"] === "openai-compatible"
|
|
8
|
+
? "openai-compatible"
|
|
9
|
+
: "anthropic",
|
|
10
|
+
apiKey:
|
|
11
|
+
process.env["ANTHROPIC_API_KEY"] ?? process.env["OPENAI_API_KEY"] ?? "",
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const result = await runThinSlice({
|
|
15
|
+
provider,
|
|
16
|
+
tools: toolRegistry,
|
|
17
|
+
prompt: "Say hi to the world.",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
console.log(result);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { helloTool } from "./hello.js";
|
|
3
|
+
|
|
4
|
+
describe("helloTool", () => {
|
|
5
|
+
it("returns a greeting for the given name", async () => {
|
|
6
|
+
const result = await helloTool.call({ name: "world" });
|
|
7
|
+
expect(result).toEqual({ greeting: "Hello, world!" });
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("has id 'hello' and a non-empty description", () => {
|
|
11
|
+
expect(helloTool.config.id).toBe("hello");
|
|
12
|
+
expect(helloTool.config.description.length).toBeGreaterThan(0);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineTool } from "@keel_flow/core";
|
|
3
|
+
|
|
4
|
+
export const helloTool = defineTool({
|
|
5
|
+
id: "hello",
|
|
6
|
+
description: "Say hello to a name",
|
|
7
|
+
inputSchema: z.object({ name: z.string() }),
|
|
8
|
+
handler: async ({ name }) => ({ greeting: `Hello, ${name}!` }),
|
|
9
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
A Keel-disciplined project (framework template). https://github.com/jglasskatz/keel
|
|
4
|
+
|
|
5
|
+
This template ships the Keel discipline only — `architecture/data.ts`,
|
|
6
|
+
`principles/index.ts`, the verify gate, eslint plugin, and the
|
|
7
|
+
principle packs. It does NOT include the agent runtime or any AI-app
|
|
8
|
+
scaffolding. Add `@keel_flow/runtime` + `@keel_flow/core` `defineAgent` /
|
|
9
|
+
`defineTool` patterns when you need them; until then, this project is
|
|
10
|
+
a plain TypeScript module under Keel's architectural guardrails.
|
|
11
|
+
|
|
12
|
+
## Quick start
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm install
|
|
16
|
+
pnpm verify
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Structure
|
|
20
|
+
|
|
21
|
+
- `architecture/data.ts` — architecture map (keep in sync with code)
|
|
22
|
+
- `principles/index.ts` — engineering principles registry
|
|
23
|
+
- `eslint.config.js` — editor-time principle checks via `@keel_flow/eslint-plugin`
|
|
24
|
+
- `src/index.ts` — your code
|
|
25
|
+
|
|
26
|
+
## Adding agents later
|
|
27
|
+
|
|
28
|
+
If you decide you want an AI app, scaffold a new project with
|
|
29
|
+
`create-keel-app <name> --template ai-app` and copy the relevant
|
|
30
|
+
files over. The two templates share the same shared/ baseline so the
|
|
31
|
+
disciplines align.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build": "tsc",
|
|
7
|
+
"typecheck": "tsc --noEmit",
|
|
8
|
+
"test": "vitest run",
|
|
9
|
+
"lint": "eslint .",
|
|
10
|
+
"verify": "keel verify --diff origin/main..HEAD"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@keel_flow/cli": "^0.2.0",
|
|
14
|
+
"@keel_flow/core": "^0.2.0",
|
|
15
|
+
"@keel_flow/schema": "^0.2.0",
|
|
16
|
+
"@keel_flow/eslint-plugin": "^0.2.0",
|
|
17
|
+
"@keel_flow/pack-agent-topology": "^0.2.0",
|
|
18
|
+
"@keel_flow/pack-general-se": "^0.2.0",
|
|
19
|
+
"@keel_flow/pack-kb-hygiene": "^0.2.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^20.0.0",
|
|
23
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
24
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
25
|
+
"eslint": "^9.0.0",
|
|
26
|
+
"tsx": "^4.0.0",
|
|
27
|
+
"typescript": "^5.5.0",
|
|
28
|
+
"vitest": "^2.0.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const projectName = "{{PROJECT_NAME}}";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Keel Verify
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
keel-verify:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
with:
|
|
14
|
+
fetch-depth: 0
|
|
15
|
+
|
|
16
|
+
- uses: pnpm/action-setup@v4
|
|
17
|
+
with:
|
|
18
|
+
version: 9
|
|
19
|
+
|
|
20
|
+
- uses: actions/setup-node@v4
|
|
21
|
+
with:
|
|
22
|
+
node-version: 20
|
|
23
|
+
cache: pnpm
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: pnpm install --frozen-lockfile
|
|
27
|
+
|
|
28
|
+
- name: Keel Verify
|
|
29
|
+
run: npx keel verify --diff origin/main..HEAD
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Local skill overlay for {{PROJECT_NAME}}.
|
|
3
|
+
|
|
4
|
+
pi-keel reads this file (and ~/.keel/local-skill.md) and merges its
|
|
5
|
+
contents into the active skill at session start. Use it to encode
|
|
6
|
+
project-specific tool guidance, naming conventions, or repository
|
|
7
|
+
invariants that should be in the agent's context for every session.
|
|
8
|
+
|
|
9
|
+
Lines starting with "<!-- -->" comments are ignored. Uncomment and
|
|
10
|
+
edit to take effect.
|
|
11
|
+
-->
|
|
12
|
+
|
|
13
|
+
<!--
|
|
14
|
+
## Project conventions
|
|
15
|
+
|
|
16
|
+
- All new tools live under `src/tools/` and use `defineTool`.
|
|
17
|
+
- All new agents live under `src/agents/` and use `defineAgent`.
|
|
18
|
+
- Update `architecture/data.ts` in the same commit as code changes.
|
|
19
|
+
-->
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Memory — {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
<!--
|
|
4
|
+
Append-only project memory consumed by pi-keel and @keel_flow/memory.
|
|
5
|
+
|
|
6
|
+
Use `keel memory add "..."` from the CLI to add entries (each
|
|
7
|
+
prefixed with a timestamped `## ` header). Edit-by-hand is fine
|
|
8
|
+
but breaks idempotent id derivation — prefer the CLI in practice.
|
|
9
|
+
-->
|
|
10
|
+
|
|
11
|
+
## {{PROJECT_NAME}} — project facts
|
|
12
|
+
|
|
13
|
+
Replace this block with the load-bearing facts about this project:
|
|
14
|
+
its domain, the primary agents, the data sources, the deployment
|
|
15
|
+
target. The agent reads this file at the top of every session.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# CLAUDE.md — {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
This project uses the Keel framework: https://github.com/jglasskatz/keel
|
|
4
|
+
|
|
5
|
+
## Conventions
|
|
6
|
+
|
|
7
|
+
- `architecture/data.ts` — the architecture map. Update it when you add/remove/rename any node.
|
|
8
|
+
- `principles/index.ts` — the principles registry. Add domain-specific principles here.
|
|
9
|
+
- `src/agents/` — one file per agent, using `defineAgent` from `@keel_flow/core`.
|
|
10
|
+
- `src/tools/` — one file per tool, using `defineTool` from `@keel_flow/core` with a Zod `inputSchema`.
|
|
11
|
+
- `src/index.ts` — the public entrypoint.
|
|
12
|
+
|
|
13
|
+
## Verify gate
|
|
14
|
+
|
|
15
|
+
Every PR runs `keel verify --diff origin/main..HEAD` in CI.
|
|
16
|
+
|
|
17
|
+
Critical violations block merge. Run `pnpm verify` locally before pushing.
|
|
18
|
+
|
|
19
|
+
## Env vars required
|
|
20
|
+
|
|
21
|
+
- `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` — required for agent invocations
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm install
|
|
27
|
+
ANTHROPIC_API_KEY=sk-... pnpm dev
|
|
28
|
+
```
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ArchitectureMap } from "@keel_flow/schema";
|
|
2
|
+
import { SCHEMA_VERSION } from "@keel_flow/schema";
|
|
3
|
+
|
|
4
|
+
const data: ArchitectureMap = {
|
|
5
|
+
schemaVersion: SCHEMA_VERSION,
|
|
6
|
+
layers: [
|
|
7
|
+
{
|
|
8
|
+
id: "core",
|
|
9
|
+
label: "Core",
|
|
10
|
+
description: "Primary modules of the {{PROJECT_NAME}} system",
|
|
11
|
+
color: "#4f46e5",
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
nodes: [
|
|
15
|
+
{
|
|
16
|
+
id: "{{PROJECT_NAME}}-root",
|
|
17
|
+
label: "{{PROJECT_NAME}} Root",
|
|
18
|
+
layer: "core",
|
|
19
|
+
tech: "TypeScript",
|
|
20
|
+
description:
|
|
21
|
+
"Seed node for {{PROJECT_NAME}} — replace with your domain modules and keep this map in sync with code",
|
|
22
|
+
keyFiles: ["src/index.ts"],
|
|
23
|
+
keyFunctionality: ["bootstrap"],
|
|
24
|
+
fileCount: 1,
|
|
25
|
+
linesOfCode: "~20",
|
|
26
|
+
principles: {
|
|
27
|
+
upholds: [
|
|
28
|
+
{ id: "single-source-of-truth", reason: "One canonical entry per concept" },
|
|
29
|
+
],
|
|
30
|
+
violates: [],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
connections: [],
|
|
35
|
+
contexts: [
|
|
36
|
+
{
|
|
37
|
+
id: "thin-slice",
|
|
38
|
+
label: "Thin Slice",
|
|
39
|
+
description: "Initial runnable context — expand as you add domain modules",
|
|
40
|
+
nodeIds: ["{{PROJECT_NAME}}-root"],
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export default data;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import tseslint from "@typescript-eslint/eslint-plugin";
|
|
2
|
+
import tsparser from "@typescript-eslint/parser";
|
|
3
|
+
import keelPlugin from "@keel_flow/eslint-plugin";
|
|
4
|
+
|
|
5
|
+
/** @type {import('eslint').Linter.Config[]} */
|
|
6
|
+
const config = [
|
|
7
|
+
{
|
|
8
|
+
ignores: ["**/dist/**", "**/node_modules/**", "**/.turbo/**"],
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
files: ["**/*.ts", "**/*.tsx"],
|
|
12
|
+
languageOptions: {
|
|
13
|
+
parser: tsparser,
|
|
14
|
+
parserOptions: {
|
|
15
|
+
ecmaVersion: 2022,
|
|
16
|
+
sourceType: "module",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
plugins: {
|
|
20
|
+
"@typescript-eslint": tseslint,
|
|
21
|
+
"@keel_flow": keelPlugin,
|
|
22
|
+
},
|
|
23
|
+
rules: {
|
|
24
|
+
...tseslint.configs.recommended.rules,
|
|
25
|
+
"@typescript-eslint/no-explicit-any": "error",
|
|
26
|
+
"@typescript-eslint/explicit-function-return-type": "off",
|
|
27
|
+
"@typescript-eslint/no-unused-vars": [
|
|
28
|
+
"error",
|
|
29
|
+
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
|
|
30
|
+
],
|
|
31
|
+
"@keel_flow/tests-as-contracts": "warn",
|
|
32
|
+
"@keel_flow/wrong-way-impossible": "error",
|
|
33
|
+
"@keel_flow/naming-is-design": "warn",
|
|
34
|
+
"@keel_flow/tool-schemas-are-load-bearing": "error",
|
|
35
|
+
"@keel_flow/tool-failures-are-typed": "warn",
|
|
36
|
+
"@keel_flow/chunk-size-bounded": "warn",
|
|
37
|
+
"@keel_flow/retrieval-confidence-floor": "warn",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
export default config;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createPrincipleRegistry } from "@keel_flow/core";
|
|
2
|
+
import type { PrincipleRegistry } from "@keel_flow/core";
|
|
3
|
+
import type { PrincipleWithCheck } from "@keel_flow/schema";
|
|
4
|
+
|
|
5
|
+
const generalSePrinciples: PrincipleWithCheck[] = [
|
|
6
|
+
{
|
|
7
|
+
id: "single-source-of-truth",
|
|
8
|
+
description: "Every fact has exactly one canonical home",
|
|
9
|
+
rationale: "Duplicated truth diverges over time",
|
|
10
|
+
severity: "critical",
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
id: "tests-as-contracts",
|
|
14
|
+
description: "Every public module has a co-located test",
|
|
15
|
+
rationale: "A test is the cheapest precise documentation",
|
|
16
|
+
severity: "critical",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "errors-at-boundaries",
|
|
20
|
+
description: "Validate and throw at every system boundary",
|
|
21
|
+
rationale: "Errors close to origin are cheap",
|
|
22
|
+
severity: "critical",
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
const agentTopologyPrinciples: PrincipleWithCheck[] = [
|
|
27
|
+
{
|
|
28
|
+
id: "single-source-of-truth-per-tool",
|
|
29
|
+
description: "Each tool reads/writes through exactly one data path",
|
|
30
|
+
rationale: "Multiple paths create state drift",
|
|
31
|
+
severity: "critical",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "tool-schemas-are-load-bearing",
|
|
35
|
+
description: "Tool input and output schemas are the source of truth",
|
|
36
|
+
rationale: "Prose descriptions drift; schemas are machine-checked",
|
|
37
|
+
severity: "critical",
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const kbHygienePrinciples: PrincipleWithCheck[] = [
|
|
42
|
+
{
|
|
43
|
+
id: "cite-or-decline",
|
|
44
|
+
description: "Every agent response derived from the KB must cite the source chunk",
|
|
45
|
+
rationale: "Uncited claims are indistinguishable from hallucinations",
|
|
46
|
+
severity: "critical",
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
export const keelPrinciples: PrincipleRegistry = createPrincipleRegistry([
|
|
51
|
+
{ principles: generalSePrinciples },
|
|
52
|
+
{ principles: agentTopologyPrinciples },
|
|
53
|
+
{ principles: kbHygienePrinciples },
|
|
54
|
+
]);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"strict": true,
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"module": "NodeNext",
|
|
6
|
+
"moduleResolution": "NodeNext",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"esModuleInterop": false,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"noUncheckedIndexedAccess": true,
|
|
13
|
+
"noImplicitAny": true,
|
|
14
|
+
"noImplicitOverride": true,
|
|
15
|
+
"exactOptionalPropertyTypes": true,
|
|
16
|
+
"outDir": "dist",
|
|
17
|
+
"lib": ["ES2022"],
|
|
18
|
+
"types": ["node"]
|
|
19
|
+
},
|
|
20
|
+
"include": ["src", "architecture", "principles"],
|
|
21
|
+
"exclude": ["**/*.test.ts", "dist"]
|
|
22
|
+
}
|