forgehive 0.6.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/README.md +631 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +6265 -0
- package/dist/confirm.d.ts +1 -0
- package/dist/confirm.js +15 -0
- package/dist/fulfillment-catalog.d.ts +10 -0
- package/dist/fulfillment-catalog.js +119 -0
- package/dist/lookup-table.d.ts +16 -0
- package/dist/lookup-table.js +128 -0
- package/dist/rollback.d.ts +1 -0
- package/dist/rollback.js +9 -0
- package/dist/scanner.d.ts +2 -0
- package/dist/scanner.js +87 -0
- package/dist/types.d.ts +13 -0
- package/dist/types.js +1 -0
- package/dist/update.d.ts +7 -0
- package/dist/update.js +42 -0
- package/dist/writer.d.ts +3 -0
- package/dist/writer.js +63 -0
- package/forgehive/agents/eli.yaml +8 -0
- package/forgehive/agents/kai.yaml +8 -0
- package/forgehive/agents/nora.yaml +8 -0
- package/forgehive/agents/remy.yaml +8 -0
- package/forgehive/agents/sam.yaml +8 -0
- package/forgehive/agents/suki.yaml +8 -0
- package/forgehive/agents/vera.yaml +24 -0
- package/forgehive/agents/viktor.yaml +8 -0
- package/forgehive/commands/fh-hotfix.md +16 -0
- package/forgehive/commands/fh-review.md +16 -0
- package/forgehive/commands/fh-ship.md +18 -0
- package/forgehive/commands/fh-start-task.md +13 -0
- package/forgehive/hooks/guardrails.sh +17 -0
- package/forgehive/party/defaults.yaml +21 -0
- package/forgehive/skills/INDEX.yaml +64 -0
- package/forgehive/skills/expert/.gitkeep +0 -0
- package/forgehive/skills/expert/api-design.md +77 -0
- package/forgehive/skills/expert/auth-security.md +80 -0
- package/forgehive/skills/expert/clean-architecture.md +83 -0
- package/forgehive/skills/expert/code-review.md +81 -0
- package/forgehive/skills/expert/database-patterns.md +81 -0
- package/forgehive/skills/expert/error-handling.md +90 -0
- package/forgehive/skills/expert/gdpr-compliance.md +64 -0
- package/forgehive/skills/expert/git-conventions.md +84 -0
- package/forgehive/skills/expert/monorepo-patterns.md +91 -0
- package/forgehive/skills/expert/observability.md +98 -0
- package/forgehive/skills/expert/owasp-top10.md +153 -0
- package/forgehive/skills/expert/performance-patterns.md +79 -0
- package/forgehive/skills/expert/security-checklist.md +70 -0
- package/forgehive/skills/expert/testing-strategies.md +74 -0
- package/forgehive/skills/expert/typescript-patterns.md +62 -0
- package/forgehive/skills/templates/.gitkeep +0 -0
- package/forgehive/templates/claude-md.block.md +62 -0
- package/package.json +30 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function confirm(projectRoot: string): void;
|
package/dist/confirm.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
export function confirm(projectRoot) {
|
|
5
|
+
const capFile = path.join(projectRoot, ".claude", "capabilities.yaml");
|
|
6
|
+
if (!fs.existsSync(capFile)) {
|
|
7
|
+
throw new Error("capabilities.yaml nicht gefunden β fΓΌhre zuerst `framework-os init` aus");
|
|
8
|
+
}
|
|
9
|
+
const doc = yaml.load(fs.readFileSync(capFile, "utf8"));
|
|
10
|
+
if (doc.status === "confirmed") {
|
|
11
|
+
throw new Error("capabilities.yaml ist bereits confirmed");
|
|
12
|
+
}
|
|
13
|
+
doc.status = "confirmed";
|
|
14
|
+
fs.writeFileSync(capFile, "# Confirmed by framework-os confirm\n" + yaml.dump(doc), "utf8");
|
|
15
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
export const FULFILLMENT_CATALOG = {
|
|
2
|
+
nodejs: {
|
|
3
|
+
check: "node --version",
|
|
4
|
+
fulfill: {
|
|
5
|
+
intent: "Ensure Node.js is installed",
|
|
6
|
+
preferred: "nvm install --lts",
|
|
7
|
+
constraints: ["must not change system Node version without nvm"],
|
|
8
|
+
verify: "node --version",
|
|
9
|
+
},
|
|
10
|
+
},
|
|
11
|
+
vitest: {
|
|
12
|
+
check: "npx vitest --version",
|
|
13
|
+
fulfill: {
|
|
14
|
+
intent: "Set up Vitest as the test runner",
|
|
15
|
+
preferred: "npm install vitest --save-dev",
|
|
16
|
+
constraints: ["must match existing package manager", "must not break existing test scripts"],
|
|
17
|
+
verify: "npx vitest --version",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
jest: {
|
|
21
|
+
check: "npx jest --version",
|
|
22
|
+
fulfill: {
|
|
23
|
+
intent: "Set up Jest as the test runner",
|
|
24
|
+
preferred: "npm install jest @types/jest --save-dev",
|
|
25
|
+
constraints: ["must match existing package manager", "must not break existing test scripts"],
|
|
26
|
+
verify: "npx jest --version",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
playwright: {
|
|
30
|
+
check: "npx playwright --version",
|
|
31
|
+
fulfill: {
|
|
32
|
+
intent: "Set up Playwright for end-to-end testing",
|
|
33
|
+
preferred: "npm install @playwright/test --save-dev && npx playwright install",
|
|
34
|
+
constraints: ["must not overwrite existing playwright.config"],
|
|
35
|
+
verify: "npx playwright --version",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
eslint: {
|
|
39
|
+
check: "npx eslint --version",
|
|
40
|
+
fulfill: {
|
|
41
|
+
intent: "Set up ESLint for code linting",
|
|
42
|
+
preferred: "npm install eslint --save-dev",
|
|
43
|
+
constraints: ["must match project's existing eslint config if present"],
|
|
44
|
+
verify: "npx eslint --version",
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
typescript: {
|
|
48
|
+
check: "npx tsc --version",
|
|
49
|
+
fulfill: {
|
|
50
|
+
intent: "Add TypeScript to the project",
|
|
51
|
+
preferred: "npm install typescript --save-dev && npx tsc --init",
|
|
52
|
+
constraints: ["must not overwrite existing tsconfig.json"],
|
|
53
|
+
verify: "npx tsc --version",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
prisma: {
|
|
57
|
+
check: "npx prisma --version",
|
|
58
|
+
fulfill: {
|
|
59
|
+
intent: "Set up Prisma ORM",
|
|
60
|
+
preferred: "npm install prisma @prisma/client && npx prisma init",
|
|
61
|
+
constraints: ["must not overwrite existing prisma/schema.prisma"],
|
|
62
|
+
verify: "npx prisma --version",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
tailwind: {
|
|
66
|
+
check: "npx tailwindcss --version",
|
|
67
|
+
fulfill: {
|
|
68
|
+
intent: "Set up Tailwind CSS",
|
|
69
|
+
preferred: "npm install tailwindcss --save-dev && npx tailwindcss init",
|
|
70
|
+
constraints: ["must not overwrite existing tailwind.config"],
|
|
71
|
+
verify: "npx tailwindcss --version",
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
docker: {
|
|
75
|
+
check: "docker --version",
|
|
76
|
+
fulfill: {
|
|
77
|
+
intent: "Ensure Docker is available",
|
|
78
|
+
preferred: "Install Docker Desktop from https://docker.com",
|
|
79
|
+
constraints: ["do not auto-install system-level tools"],
|
|
80
|
+
verify: "docker --version",
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
"github-actions": {
|
|
84
|
+
check: "test -d .github/workflows",
|
|
85
|
+
fulfill: {
|
|
86
|
+
intent: "Set up GitHub Actions CI/CD",
|
|
87
|
+
preferred: "mkdir -p .github/workflows && create basic ci.yml",
|
|
88
|
+
constraints: ["must not overwrite existing workflow files"],
|
|
89
|
+
verify: "test -d .github/workflows",
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
"pre-commit": {
|
|
93
|
+
check: "pre-commit --version",
|
|
94
|
+
fulfill: {
|
|
95
|
+
intent: "Set up pre-commit hooks",
|
|
96
|
+
preferred: "pip install pre-commit && pre-commit install",
|
|
97
|
+
constraints: ["must not overwrite existing .pre-commit-config.yaml"],
|
|
98
|
+
verify: "pre-commit --version",
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
fastapi: {
|
|
102
|
+
check: "python -c 'import fastapi'",
|
|
103
|
+
fulfill: {
|
|
104
|
+
intent: "Install FastAPI",
|
|
105
|
+
preferred: "pip install fastapi uvicorn",
|
|
106
|
+
constraints: ["must use active virtualenv if present"],
|
|
107
|
+
verify: "python -c 'import fastapi; print(fastapi.__version__)'",
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
django: {
|
|
111
|
+
check: "python -m django --version",
|
|
112
|
+
fulfill: {
|
|
113
|
+
intent: "Install Django",
|
|
114
|
+
preferred: "pip install django",
|
|
115
|
+
constraints: ["must use active virtualenv if present"],
|
|
116
|
+
verify: "python -m django --version",
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { FoundSignal } from "./types.ts";
|
|
2
|
+
export interface Capability {
|
|
3
|
+
id: string;
|
|
4
|
+
source: string;
|
|
5
|
+
confidence: "confirmed" | "inferred";
|
|
6
|
+
}
|
|
7
|
+
export interface CapabilityMap {
|
|
8
|
+
confirmed: Capability[];
|
|
9
|
+
inferred: Capability[];
|
|
10
|
+
}
|
|
11
|
+
interface SignalInput {
|
|
12
|
+
signals: FoundSignal[];
|
|
13
|
+
packages: string[];
|
|
14
|
+
}
|
|
15
|
+
export declare function mapSignalsToCapabilities(input: SignalInput): CapabilityMap;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
// Filename β capability IDs
|
|
2
|
+
const FILENAME_TO_IDS = {
|
|
3
|
+
"package.json": ["nodejs", "javascript"],
|
|
4
|
+
"pyproject.toml": ["python"],
|
|
5
|
+
"Cargo.toml": ["rust"],
|
|
6
|
+
"go.mod": ["golang"],
|
|
7
|
+
"pom.xml": ["java", "maven"],
|
|
8
|
+
"Gemfile": ["ruby"],
|
|
9
|
+
"composer.json": ["php"],
|
|
10
|
+
"Dockerfile": ["docker"],
|
|
11
|
+
"docker-compose.yml": ["docker-compose"],
|
|
12
|
+
".gitlab-ci.yml": ["gitlab-ci", "ci-cd"],
|
|
13
|
+
"pytest.ini": ["pytest"],
|
|
14
|
+
"openapi.yaml": ["openapi"],
|
|
15
|
+
"openapi.yml": ["openapi"],
|
|
16
|
+
"openapi.json": ["openapi"],
|
|
17
|
+
".pre-commit-config.yaml": ["pre-commit"],
|
|
18
|
+
};
|
|
19
|
+
const TIER2_DIR_PREFIX_TO_IDS = {
|
|
20
|
+
".github/workflows": ["github-actions", "ci-cd"],
|
|
21
|
+
"terraform": ["terraform", "infrastructure-as-code"],
|
|
22
|
+
"k8s": ["kubernetes"],
|
|
23
|
+
};
|
|
24
|
+
const FILENAME_PREFIX_TO_IDS = [
|
|
25
|
+
[/^jest\.config\./, ["jest", "unit-testing"]],
|
|
26
|
+
[/^vitest\.config\./, ["vitest", "unit-testing"]],
|
|
27
|
+
[/^\.eslintrc/, ["eslint", "linting"]],
|
|
28
|
+
];
|
|
29
|
+
// npm/pip/cargo package name β capability IDs
|
|
30
|
+
const PACKAGE_TO_IDS = {
|
|
31
|
+
// JS Frameworks
|
|
32
|
+
"react": ["react"],
|
|
33
|
+
"react-dom": ["react"],
|
|
34
|
+
"next": ["nextjs"],
|
|
35
|
+
"nuxt": ["nuxt"],
|
|
36
|
+
"vue": ["vue"],
|
|
37
|
+
"@vue/core": ["vue"],
|
|
38
|
+
"svelte": ["svelte"],
|
|
39
|
+
"@sveltejs/kit": ["sveltekit"],
|
|
40
|
+
"remix": ["remix"],
|
|
41
|
+
"@remix-run/react": ["remix"],
|
|
42
|
+
"gatsby": ["gatsby"],
|
|
43
|
+
"astro": ["astro"],
|
|
44
|
+
// Backend JS
|
|
45
|
+
"express": ["express"],
|
|
46
|
+
"fastify": ["fastify"],
|
|
47
|
+
"hono": ["hono"],
|
|
48
|
+
"koa": ["koa"],
|
|
49
|
+
"nestjs": ["nestjs"],
|
|
50
|
+
"@nestjs/core": ["nestjs"],
|
|
51
|
+
// Database / ORM
|
|
52
|
+
"@prisma/client": ["prisma"],
|
|
53
|
+
"prisma": ["prisma"],
|
|
54
|
+
"drizzle-orm": ["drizzle"],
|
|
55
|
+
"typeorm": ["typeorm"],
|
|
56
|
+
"mongoose": ["mongoose"],
|
|
57
|
+
"sequelize": ["sequelize"],
|
|
58
|
+
// Testing JS
|
|
59
|
+
"vitest": ["vitest"],
|
|
60
|
+
"jest": ["jest"],
|
|
61
|
+
"@playwright/test": ["playwright"],
|
|
62
|
+
"playwright": ["playwright"],
|
|
63
|
+
"cypress": ["cypress"],
|
|
64
|
+
"storybook": ["storybook"],
|
|
65
|
+
"@storybook/react": ["storybook"],
|
|
66
|
+
// Build / Tooling
|
|
67
|
+
"vite": ["vite"],
|
|
68
|
+
"webpack": ["webpack"],
|
|
69
|
+
"esbuild": ["esbuild"],
|
|
70
|
+
"turbo": ["turborepo"],
|
|
71
|
+
"nx": ["nx"],
|
|
72
|
+
// Auth
|
|
73
|
+
"next-auth": ["next-auth"],
|
|
74
|
+
"@auth/core": ["auth-js"],
|
|
75
|
+
"lucia": ["lucia"],
|
|
76
|
+
// Styling
|
|
77
|
+
"tailwindcss": ["tailwind"],
|
|
78
|
+
"@tailwindcss/vite": ["tailwind"],
|
|
79
|
+
"styled-components": ["styled-components"],
|
|
80
|
+
// State
|
|
81
|
+
"zustand": ["zustand"],
|
|
82
|
+
"jotai": ["jotai"],
|
|
83
|
+
"@reduxjs/toolkit": ["redux"],
|
|
84
|
+
"redux": ["redux"],
|
|
85
|
+
// Python frameworks (from pip, often in pyproject.toml β detected via name in packages)
|
|
86
|
+
"fastapi": ["fastapi"],
|
|
87
|
+
"django": ["django"],
|
|
88
|
+
"flask": ["flask"],
|
|
89
|
+
"sqlalchemy": ["sqlalchemy"],
|
|
90
|
+
"pydantic": ["pydantic"],
|
|
91
|
+
"alembic": ["alembic"],
|
|
92
|
+
};
|
|
93
|
+
export function mapSignalsToCapabilities(input) {
|
|
94
|
+
const seen = new Set();
|
|
95
|
+
const confirmed = [];
|
|
96
|
+
const add = (id, source) => {
|
|
97
|
+
if (seen.has(id))
|
|
98
|
+
return;
|
|
99
|
+
seen.add(id);
|
|
100
|
+
confirmed.push({ id, source, confidence: "confirmed" });
|
|
101
|
+
};
|
|
102
|
+
for (const sig of input.signals) {
|
|
103
|
+
for (const id of resolveSignalIds(sig)) {
|
|
104
|
+
add(id, sig.path);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
for (const pkg of input.packages) {
|
|
108
|
+
const ids = PACKAGE_TO_IDS[pkg];
|
|
109
|
+
if (ids) {
|
|
110
|
+
for (const id of ids)
|
|
111
|
+
add(id, "package.json");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return { confirmed, inferred: [] };
|
|
115
|
+
}
|
|
116
|
+
function resolveSignalIds(sig) {
|
|
117
|
+
if (FILENAME_TO_IDS[sig.filename])
|
|
118
|
+
return FILENAME_TO_IDS[sig.filename];
|
|
119
|
+
for (const [prefix, ids] of Object.entries(TIER2_DIR_PREFIX_TO_IDS)) {
|
|
120
|
+
if (sig.path.startsWith(prefix + "/"))
|
|
121
|
+
return ids;
|
|
122
|
+
}
|
|
123
|
+
for (const [pattern, ids] of FILENAME_PREFIX_TO_IDS) {
|
|
124
|
+
if (pattern.test(sig.filename))
|
|
125
|
+
return ids;
|
|
126
|
+
}
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function rollback(projectRoot: string): void;
|
package/dist/rollback.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export function rollback(projectRoot) {
|
|
4
|
+
const claudeDir = path.join(projectRoot, ".claude");
|
|
5
|
+
if (!fs.existsSync(claudeDir)) {
|
|
6
|
+
throw new Error(".claude/ nicht gefunden β nichts zum RΓΌckgΓ€ngigmachen");
|
|
7
|
+
}
|
|
8
|
+
fs.rmSync(claudeDir, { recursive: true, force: true });
|
|
9
|
+
}
|
package/dist/scanner.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const TIER1 = [
|
|
4
|
+
"package.json",
|
|
5
|
+
"pyproject.toml",
|
|
6
|
+
"Cargo.toml",
|
|
7
|
+
"go.mod",
|
|
8
|
+
"pom.xml",
|
|
9
|
+
"Gemfile",
|
|
10
|
+
"composer.json",
|
|
11
|
+
];
|
|
12
|
+
const TIER2_FILES = [
|
|
13
|
+
"Dockerfile",
|
|
14
|
+
"docker-compose.yml",
|
|
15
|
+
".gitlab-ci.yml",
|
|
16
|
+
];
|
|
17
|
+
const TIER2_DIRS = [
|
|
18
|
+
".github/workflows",
|
|
19
|
+
"terraform",
|
|
20
|
+
"k8s",
|
|
21
|
+
];
|
|
22
|
+
const TIER3_PATTERNS = [
|
|
23
|
+
/^\.eslintrc(\.(json|js|yml|yaml|cjs))?$/,
|
|
24
|
+
/^jest\.config\.(js|ts|mjs|cjs|json)$/,
|
|
25
|
+
/^vitest\.config\.(js|ts|mjs)$/,
|
|
26
|
+
/^pytest\.ini$/,
|
|
27
|
+
/^openapi\.(yaml|yml|json)$/,
|
|
28
|
+
/^\.pre-commit-config\.yaml$/,
|
|
29
|
+
];
|
|
30
|
+
export function scan(projectRoot) {
|
|
31
|
+
const signals = [];
|
|
32
|
+
for (const filename of TIER1) {
|
|
33
|
+
if (fs.existsSync(path.join(projectRoot, filename))) {
|
|
34
|
+
signals.push(signal(1, filename, filename));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (const filename of TIER2_FILES) {
|
|
38
|
+
if (fs.existsSync(path.join(projectRoot, filename))) {
|
|
39
|
+
signals.push(signal(2, filename, path.basename(filename)));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
for (const dir of TIER2_DIRS) {
|
|
43
|
+
const abs = path.join(projectRoot, dir);
|
|
44
|
+
if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory())
|
|
45
|
+
continue;
|
|
46
|
+
for (const child of fs.readdirSync(abs)) {
|
|
47
|
+
const rel = path.join(dir, child);
|
|
48
|
+
signals.push(signal(2, rel, child));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
for (const entry of fs.readdirSync(projectRoot)) {
|
|
52
|
+
const abs = path.join(projectRoot, entry);
|
|
53
|
+
if (!fs.statSync(abs).isFile())
|
|
54
|
+
continue;
|
|
55
|
+
for (const pattern of TIER3_PATTERNS) {
|
|
56
|
+
if (pattern.test(entry)) {
|
|
57
|
+
signals.push(signal(3, entry, entry));
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
scanned_at: new Date().toISOString(),
|
|
64
|
+
project_root: projectRoot,
|
|
65
|
+
signals,
|
|
66
|
+
packages: extractPackages(projectRoot),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function extractPackages(projectRoot) {
|
|
70
|
+
const pkgPath = path.join(projectRoot, "package.json");
|
|
71
|
+
if (!fs.existsSync(pkgPath))
|
|
72
|
+
return [];
|
|
73
|
+
try {
|
|
74
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
75
|
+
const all = new Set([
|
|
76
|
+
...Object.keys(pkg.dependencies ?? {}),
|
|
77
|
+
...Object.keys(pkg.devDependencies ?? {}),
|
|
78
|
+
]);
|
|
79
|
+
return [...all];
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function signal(tier, filePath, filename) {
|
|
86
|
+
return { tier, path: filePath, filename, exists: true };
|
|
87
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type Tier = 1 | 2 | 3;
|
|
2
|
+
export interface FoundSignal {
|
|
3
|
+
tier: Tier;
|
|
4
|
+
path: string;
|
|
5
|
+
filename: string;
|
|
6
|
+
exists: true;
|
|
7
|
+
}
|
|
8
|
+
export interface ScanResult {
|
|
9
|
+
scanned_at: string;
|
|
10
|
+
project_root: string;
|
|
11
|
+
signals: FoundSignal[];
|
|
12
|
+
packages: string[];
|
|
13
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/update.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Capability, CapabilityMap } from "./lookup-table.ts";
|
|
2
|
+
export declare function computeHash(projectRoot: string): string;
|
|
3
|
+
export interface CapabilityDiff {
|
|
4
|
+
added: Capability[];
|
|
5
|
+
removed: Capability[];
|
|
6
|
+
}
|
|
7
|
+
export declare function scanDiff(oldMap: CapabilityMap, newMap: CapabilityMap): CapabilityDiff;
|
package/dist/update.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import crypto from "node:crypto";
|
|
4
|
+
const HASH_TARGETS = [
|
|
5
|
+
"package.json", "pyproject.toml", "Cargo.toml", "go.mod",
|
|
6
|
+
"pom.xml", "Gemfile", "composer.json",
|
|
7
|
+
"Dockerfile", "docker-compose.yml", ".gitlab-ci.yml",
|
|
8
|
+
".eslintrc", ".eslintrc.json", ".eslintrc.js", ".eslintrc.yml",
|
|
9
|
+
"pytest.ini", "openapi.yaml", "openapi.yml", ".pre-commit-config.yaml",
|
|
10
|
+
];
|
|
11
|
+
const HASH_DIRS = [".github/workflows", "terraform", "k8s"];
|
|
12
|
+
export function computeHash(projectRoot) {
|
|
13
|
+
const hash = crypto.createHash("sha256");
|
|
14
|
+
for (const filename of HASH_TARGETS) {
|
|
15
|
+
const abs = path.join(projectRoot, filename);
|
|
16
|
+
if (fs.existsSync(abs) && fs.statSync(abs).isFile()) {
|
|
17
|
+
hash.update(filename);
|
|
18
|
+
hash.update(fs.readFileSync(abs));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
for (const dir of HASH_DIRS) {
|
|
22
|
+
const abs = path.join(projectRoot, dir);
|
|
23
|
+
if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory())
|
|
24
|
+
continue;
|
|
25
|
+
for (const child of fs.readdirSync(abs).sort()) {
|
|
26
|
+
const childAbs = path.join(abs, child);
|
|
27
|
+
if (fs.statSync(childAbs).isFile()) {
|
|
28
|
+
hash.update(path.join(dir, child));
|
|
29
|
+
hash.update(fs.readFileSync(childAbs));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return hash.digest("hex");
|
|
34
|
+
}
|
|
35
|
+
export function scanDiff(oldMap, newMap) {
|
|
36
|
+
const oldIds = new Set(oldMap.confirmed.map(c => c.id));
|
|
37
|
+
const newIds = new Set(newMap.confirmed.map(c => c.id));
|
|
38
|
+
return {
|
|
39
|
+
added: newMap.confirmed.filter(c => !oldIds.has(c.id)),
|
|
40
|
+
removed: oldMap.confirmed.filter(c => !newIds.has(c.id)),
|
|
41
|
+
};
|
|
42
|
+
}
|
package/dist/writer.d.ts
ADDED
package/dist/writer.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
import { FULFILLMENT_CATALOG } from "./fulfillment-catalog.ts";
|
|
5
|
+
const CLAUDE_MD = `# framework-os Instructions
|
|
6
|
+
|
|
7
|
+
This project uses **framework-os** for structured AI-assisted development.
|
|
8
|
+
|
|
9
|
+
## Mandatory Behavior
|
|
10
|
+
|
|
11
|
+
**At the start of every session:**
|
|
12
|
+
1. Read \`.claude/capabilities.yaml\`
|
|
13
|
+
2. If \`status: draft\` β tell the user: "Run \`framework-os confirm\` to activate capabilities."
|
|
14
|
+
3. If \`status: confirmed\` β load the capabilities silently and apply them throughout the session
|
|
15
|
+
|
|
16
|
+
**During the session:**
|
|
17
|
+
- Only suggest tools, patterns, and libraries listed in \`capabilities.yaml\`
|
|
18
|
+
- If a capability has a \`check\` field: verify it before using it
|
|
19
|
+
- If a capability has a \`fulfill\` field and the check fails: fulfill it (intent + constraints + verify)
|
|
20
|
+
- Persist per-session notes to \`.claude/state/<YYYY-MM-DD>.md\`
|
|
21
|
+
- Never write files outside the project root
|
|
22
|
+
|
|
23
|
+
**Stack awareness:**
|
|
24
|
+
- Trust the detected stack in capabilities.yaml as ground truth
|
|
25
|
+
- When the user asks about setup, refer to the capabilities rather than guessing
|
|
26
|
+
- If the user adds a new dependency that seems important, suggest running \`framework-os scan --update\`
|
|
27
|
+
|
|
28
|
+
## Capability Reference
|
|
29
|
+
|
|
30
|
+
See \`.claude/capabilities.yaml\` for detected stack and fulfillment procedures.
|
|
31
|
+
See \`.claude/scan-result.yaml\` for raw scanner output.
|
|
32
|
+
Run \`framework-os scan --check\` to verify capabilities are up to date.
|
|
33
|
+
|
|
34
|
+
## Prohibited
|
|
35
|
+
|
|
36
|
+
- Writing to paths outside project root
|
|
37
|
+
- Modifying \`.claude/scan-result.yaml\` manually
|
|
38
|
+
- Skipping the session-start capability check
|
|
39
|
+
- Suggesting tools not in capabilities.yaml without explicitly noting they are outside the declared stack
|
|
40
|
+
`;
|
|
41
|
+
export function writeClaudeDir(projectRoot, scanResult, capMap) {
|
|
42
|
+
const claudeDir = path.join(projectRoot, ".claude");
|
|
43
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
44
|
+
fs.mkdirSync(path.join(claudeDir, "state"), { recursive: true });
|
|
45
|
+
fs.writeFileSync(path.join(claudeDir, "scan-result.yaml"), "# Generated by framework-os init β do not edit manually\n" +
|
|
46
|
+
yaml.dump(scanResult), "utf8");
|
|
47
|
+
fs.writeFileSync(path.join(claudeDir, "capabilities.yaml"), "# DRAFT β generated by framework-os init\n" +
|
|
48
|
+
"# Review each entry, then run: framework-os confirm\n" +
|
|
49
|
+
yaml.dump({
|
|
50
|
+
status: "draft",
|
|
51
|
+
capabilities: {
|
|
52
|
+
confirmed: capMap.confirmed.map(enrichWithFulfillment),
|
|
53
|
+
inferred: capMap.inferred,
|
|
54
|
+
},
|
|
55
|
+
}), "utf8");
|
|
56
|
+
fs.writeFileSync(path.join(claudeDir, "CLAUDE.md"), CLAUDE_MD, "utf8");
|
|
57
|
+
}
|
|
58
|
+
function enrichWithFulfillment(cap) {
|
|
59
|
+
const def = FULFILLMENT_CATALOG[cap.id];
|
|
60
|
+
if (!def)
|
|
61
|
+
return cap;
|
|
62
|
+
return { ...cap, check: def.check, fulfill: def.fulfill };
|
|
63
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
id: eli
|
|
2
|
+
name: Eli
|
|
3
|
+
title: Documentation Architect
|
|
4
|
+
icon: "π"
|
|
5
|
+
description: |
|
|
6
|
+
Turns complex systems into navigable knowledge structures. Believes documentation is
|
|
7
|
+
product, not afterthought β every word earns its place. Speaks like a patient
|
|
8
|
+
cartographer: every map earns its legend, every path its signpost.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
id: kai
|
|
2
|
+
name: Kai
|
|
3
|
+
title: Principal Engineer
|
|
4
|
+
icon: "β"
|
|
5
|
+
description: |
|
|
6
|
+
Test-first discipline, ship-ready code, zero tolerance for ambiguity. 100% passing
|
|
7
|
+
before any review. Speaks like a commit message β precise file paths, exact
|
|
8
|
+
assertions, no interpretation required.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
id: nora
|
|
2
|
+
name: Nora
|
|
3
|
+
title: Senior Research Analyst
|
|
4
|
+
icon: "π"
|
|
5
|
+
description: |
|
|
6
|
+
Applies structured evidence frameworks to every finding, surfaces what stakeholders
|
|
7
|
+
can't articulate, grounds every recommendation in verifiable data. Speaks with the
|
|
8
|
+
precision of a forensic investigator β every claim citable, every gap named.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
id: remy
|
|
2
|
+
name: Remy
|
|
3
|
+
title: Product Strategist
|
|
4
|
+
icon: "π―"
|
|
5
|
+
description: |
|
|
6
|
+
Jobs-to-be-done over feature lists, user value over technical elegance. Drives every
|
|
7
|
+
decision back to a genuine problem worth solving. Speaks like a sharp detective β
|
|
8
|
+
three questions and the real constraint surfaces.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
id: sam
|
|
2
|
+
name: Sam
|
|
3
|
+
title: Quality Architect
|
|
4
|
+
icon: "π§ͺ"
|
|
5
|
+
description: |
|
|
6
|
+
Risk-based testing strategy, fixture architecture, automation across the full stack.
|
|
7
|
+
Speaks in risk calculations and impact assessments β strong opinions, weakly held,
|
|
8
|
+
always with a reproducible test case.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
id: suki
|
|
2
|
+
name: Suki
|
|
3
|
+
title: Experience Designer
|
|
4
|
+
icon: "β¦"
|
|
5
|
+
description: |
|
|
6
|
+
Empathy-first, edge-case relentless. Designs with users, not for them. Starts with
|
|
7
|
+
the friction before the wireframe. Speaks in user scenarios that make you feel the
|
|
8
|
+
problem before a line of code is written.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
id: vera
|
|
2
|
+
name: Vera
|
|
3
|
+
title: Security Analyst
|
|
4
|
+
icon: "π"
|
|
5
|
+
description: |
|
|
6
|
+
Vera ist spezialisiert auf Application Security und Compliance.
|
|
7
|
+
Sie reviewed generierten Code auf OWASP Top 10 Vulnerabilities,
|
|
8
|
+
prΓΌft Auth-Flows, Datenschutz-Anforderungen und Audit-Trails.
|
|
9
|
+
Vera arbeitet mit dem Security-Report und Permissions-System von forgehive.
|
|
10
|
+
focus:
|
|
11
|
+
- OWASP Top 10 vulnerability scanning
|
|
12
|
+
- Authentication & authorization review
|
|
13
|
+
- GDPR / SOC2 / HIPAA compliance checks
|
|
14
|
+
- Secrets detection and remediation
|
|
15
|
+
- Dependency vulnerability assessment
|
|
16
|
+
- Security audit trail generation
|
|
17
|
+
instructions: |
|
|
18
|
+
Du bist Vera, Security Analyst. Bei jeder Code-Review:
|
|
19
|
+
1. PrΓΌfe auf OWASP Top 10 Patterns (Injection, XSS, IDOR, etc.)
|
|
20
|
+
2. Stelle sicher dass Authentifizierung korrekt implementiert ist
|
|
21
|
+
3. PrΓΌfe auf hardcoded Secrets oder unsichere Konfigurationen
|
|
22
|
+
4. Dokumentiere Findings in .forgehive/security-report.md
|
|
23
|
+
5. Gib konkrete Fixes mit Codebeispielen
|
|
24
|
+
Nutze `fh security scan` und `fh security report` als deine Tools.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
id: viktor
|
|
2
|
+
name: Viktor
|
|
3
|
+
title: Systems Architect
|
|
4
|
+
icon: "π"
|
|
5
|
+
description: |
|
|
6
|
+
Favors proven technology, treats developer experience as architecture, ties every
|
|
7
|
+
structural decision to a business constraint. Speaks like a seasoned engineer at the
|
|
8
|
+
whiteboard β measured, always laying out trade-offs rather than verdicts.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
You are running the ForgeHive hotfix protocol.
|
|
2
|
+
|
|
3
|
+
## Hotfix Rules
|
|
4
|
+
|
|
5
|
+
**Constraint:** A hotfix changes as few lines as possible. If the fix requires > 50 lines, it's a feature, not a hotfix.
|
|
6
|
+
|
|
7
|
+
## Steps
|
|
8
|
+
|
|
9
|
+
1. **Identify** β `git log --oneline -20` β find the commit that introduced the bug
|
|
10
|
+
2. **Branch** β `git checkout -b hotfix/<short-description> main`
|
|
11
|
+
3. **Minimal fix** β fix only the bug, no refactoring, no cleanup
|
|
12
|
+
4. **Regression test** β write one test that fails before the fix and passes after
|
|
13
|
+
5. **Verify** β run full test suite
|
|
14
|
+
6. **Ship** β use `/fh-ship` when ready
|
|
15
|
+
|
|
16
|
+
If this turns out to be more than 50 lines: stop, switch to `/fh-start-task` for a proper feature branch.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
You are running a ForgeHive code review. Load the review skill first.
|
|
2
|
+
|
|
3
|
+
## Review Process
|
|
4
|
+
|
|
5
|
+
1. Read `.forgehive/skills/expert/code-review.md`
|
|
6
|
+
2. Get the diff: `git diff main...HEAD`
|
|
7
|
+
3. Review in priority order:
|
|
8
|
+
|
|
9
|
+
**[BUG]** Correctness issues β will this break in production?
|
|
10
|
+
**[DESIGN]** Architecture issues β does it follow existing patterns?
|
|
11
|
+
**[NIT]** Minor style issues β use sparingly
|
|
12
|
+
**[Q]** Questions β non-blocking, want to understand intent
|
|
13
|
+
**[+]** Good work β acknowledge what's done well
|
|
14
|
+
|
|
15
|
+
4. For PRs > 400 lines: flag as too large, suggest how to split
|
|
16
|
+
5. End with: overall verdict (approve / request changes) and count of blocking issues
|