@guidobuilds/forge-ai 0.1.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.
@@ -0,0 +1,50 @@
1
+ import { readdir, readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { parseFrontmatter } from './frontmatter.js';
4
+ import { diagnostic } from './diagnostics.js';
5
+ export async function discoverSources(source) {
6
+ const root = path.resolve(source);
7
+ const sources = [];
8
+ const diagnostics = [];
9
+ await discoverAgents(root, sources, diagnostics);
10
+ await discoverSkills(root, sources, diagnostics);
11
+ if (sources.length === 0)
12
+ diagnostics.push(diagnostic('error', 'NO_SOURCES', 'No canonical agents or skills found', { sourcePath: root }));
13
+ sources.sort((a, b) => a.sourcePath.localeCompare(b.sourcePath));
14
+ return { sources, diagnostics };
15
+ }
16
+ async function discoverAgents(root, sources, diagnostics) {
17
+ const dir = path.join(root, 'agents');
18
+ for (const entry of await safeReaddir(dir)) {
19
+ if (!entry.isFile() || !entry.name.endsWith('.md'))
20
+ continue;
21
+ const sourcePath = path.join(dir, entry.name);
22
+ await readSource('agent', sourcePath, path.basename(entry.name, '.md'), sources, diagnostics);
23
+ }
24
+ }
25
+ async function discoverSkills(root, sources, diagnostics) {
26
+ const dir = path.join(root, 'skills');
27
+ for (const entry of await safeReaddir(dir)) {
28
+ if (!entry.isDirectory())
29
+ continue;
30
+ const sourcePath = path.join(dir, entry.name, 'SKILL.md');
31
+ await readSource('skill', sourcePath, entry.name, sources, diagnostics);
32
+ }
33
+ }
34
+ async function readSource(kind, sourcePath, expectedName, sources, diagnostics) {
35
+ try {
36
+ const parsed = parseFrontmatter(await readFile(sourcePath, 'utf8'));
37
+ sources.push({ kind, sourcePath, expectedName, data: parsed.data, body: parsed.body });
38
+ }
39
+ catch (error) {
40
+ diagnostics.push(diagnostic('error', 'PARSE_ERROR', error instanceof Error ? error.message : String(error), { sourcePath }));
41
+ }
42
+ }
43
+ async function safeReaddir(dir) {
44
+ try {
45
+ return await readdir(dir, { withFileTypes: true });
46
+ }
47
+ catch {
48
+ return [];
49
+ }
50
+ }
@@ -0,0 +1,81 @@
1
+ export function parseFrontmatter(content) {
2
+ const normalized = content.replace(/^\uFEFF/, '').replace(/\r\n/g, '\n');
3
+ if (!normalized.startsWith('---\n')) {
4
+ return { data: {}, body: normalized.trim() };
5
+ }
6
+ const end = normalized.indexOf('\n---', 4);
7
+ if (end === -1)
8
+ throw new Error('Missing closing frontmatter delimiter');
9
+ const rawYaml = normalized.slice(4, end);
10
+ const body = normalized.slice(normalized.indexOf('\n', end + 1) + 1).trim();
11
+ return { data: parseSimpleYaml(rawYaml), body };
12
+ }
13
+ export function parseSimpleYaml(input) {
14
+ const root = {};
15
+ const stack = [{ indent: -1, object: root }];
16
+ const lines = input.split('\n');
17
+ for (let index = 0; index < lines.length; index += 1) {
18
+ const raw = lines[index];
19
+ if (!raw.trim() || raw.trimStart().startsWith('#'))
20
+ continue;
21
+ const indent = raw.match(/^ */)?.[0].length ?? 0;
22
+ const trimmed = raw.trim();
23
+ const match = trimmed.match(/^([A-Za-z0-9_-]+):(?:\s*(.*))?$/);
24
+ if (!match)
25
+ throw new Error(`Invalid YAML at line ${index + 1}`);
26
+ while (stack.length > 1 && indent <= stack[stack.length - 1].indent)
27
+ stack.pop();
28
+ const parent = stack[stack.length - 1].object;
29
+ const key = match[1];
30
+ const value = match[2] ?? '';
31
+ if (value === '') {
32
+ const child = {};
33
+ parent[key] = child;
34
+ stack.push({ indent, object: child });
35
+ }
36
+ else {
37
+ parent[key] = parseScalar(value);
38
+ }
39
+ }
40
+ return root;
41
+ }
42
+ function parseScalar(value) {
43
+ const trimmed = value.trim();
44
+ if (trimmed === 'true')
45
+ return true;
46
+ if (trimmed === 'false')
47
+ return false;
48
+ if (trimmed === 'null')
49
+ return null;
50
+ if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
51
+ const inner = trimmed.slice(1, -1).trim();
52
+ if (!inner)
53
+ return [];
54
+ return inner.split(',').map((item) => String(parseScalar(item.trim())));
55
+ }
56
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
57
+ return trimmed.slice(1, -1);
58
+ }
59
+ return trimmed;
60
+ }
61
+ export function stringifyYaml(data) {
62
+ const lines = Object.entries(data).flatMap(([key, value]) => stringifyYamlValue(key, value, 0));
63
+ return `---\n${lines.join('\n')}\n---\n\n`;
64
+ }
65
+ function stringifyYamlValue(key, value, indent) {
66
+ const prefix = ' '.repeat(indent);
67
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
68
+ return [`${prefix}${key}:`, ...Object.entries(value).flatMap(([childKey, childValue]) => stringifyYamlValue(childKey, childValue, indent + 2))];
69
+ }
70
+ if (Array.isArray(value))
71
+ return [`${prefix}${key}: [${value.map(formatScalar).join(', ')}]`];
72
+ return [`${prefix}${key}: ${formatScalar(value)}`];
73
+ }
74
+ function formatScalar(value) {
75
+ if (typeof value === 'boolean')
76
+ return value ? 'true' : 'false';
77
+ if (typeof value === 'number')
78
+ return String(value);
79
+ const text = String(value ?? '');
80
+ return /^[A-Za-z0-9_./,@* -]+$/.test(text) && text !== '' ? text : JSON.stringify(text);
81
+ }
@@ -0,0 +1,3 @@
1
+ export * from './model.js';
2
+ export * from './processor.js';
3
+ export * from './writer.js';
@@ -0,0 +1,4 @@
1
+ export const platforms = ['opencode', 'claude', 'codex'];
2
+ export function isPlatform(value) {
3
+ return platforms.includes(value);
4
+ }
@@ -0,0 +1,24 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ export function resolveOutputPath(platform, kind, scope, name, cwd = process.cwd(), home = os.homedir()) {
4
+ const base = scope === 'user' ? userBase(platform, kind, home) : projectBase(platform, kind, cwd);
5
+ return kind === 'agent' && platform === 'codex'
6
+ ? path.join(base, `${name}.toml`)
7
+ : kind === 'agent'
8
+ ? path.join(base, `${name}.md`)
9
+ : path.join(base, name, 'SKILL.md');
10
+ }
11
+ function userBase(platform, kind, home) {
12
+ if (platform === 'opencode')
13
+ return path.join(home, '.config', 'opencode', kind === 'agent' ? 'agents' : 'skills');
14
+ if (platform === 'claude')
15
+ return path.join(home, '.claude', kind === 'agent' ? 'agents' : 'skills');
16
+ return kind === 'agent' ? path.join(home, '.codex', 'agents') : path.join(home, '.agents', 'skills');
17
+ }
18
+ function projectBase(platform, kind, cwd) {
19
+ if (platform === 'opencode')
20
+ return path.join(cwd, '.opencode', kind === 'agent' ? 'agents' : 'skills');
21
+ if (platform === 'claude')
22
+ return path.join(cwd, '.claude', kind === 'agent' ? 'agents' : 'skills');
23
+ return kind === 'agent' ? path.join(cwd, '.codex', 'agents') : path.join(cwd, '.agents', 'skills');
24
+ }
@@ -0,0 +1,127 @@
1
+ import { access } from 'node:fs/promises';
2
+ import { constants } from 'node:fs';
3
+ import { renderClaudeAgent, renderClaudeSkill } from './adapters/claude.js';
4
+ import { renderCodexAgent, renderCodexSkill } from './adapters/codex.js';
5
+ import { renderOpenCodeAgent, renderOpenCodeSkill } from './adapters/opencode.js';
6
+ import { diagnostic } from './diagnostics.js';
7
+ import { discoverSources } from './discovery.js';
8
+ import { resolveOutputPath } from './paths.js';
9
+ import { isPlatform, platforms } from './model.js';
10
+ const namePattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
11
+ const platformKeys = new Set(['claude', 'opencode', 'codex']);
12
+ const allowedTopLevel = new Set(['name', 'description', 'claude', 'opencode', 'codex']);
13
+ const allowedProductKeys = new Set(['permissions', 'model']);
14
+ const allowedOpenCodeKeys = new Set([...allowedProductKeys, 'mode']);
15
+ const openCodeModes = new Set(['primary', 'subagent', 'all']);
16
+ export function resolvePlatforms(platform) {
17
+ return platform === 'all' ? platforms : [platform];
18
+ }
19
+ export function parsePlatform(value) {
20
+ return value === 'all' || isPlatform(value) ? value : undefined;
21
+ }
22
+ export function parseScope(value) {
23
+ return value === 'user' || value === 'project' ? value : undefined;
24
+ }
25
+ export async function buildWritePlan(options) {
26
+ const { sources, diagnostics } = await discoverSources(options.source);
27
+ const agents = [];
28
+ const skills = [];
29
+ const seenAgents = new Set();
30
+ const seenSkills = new Set();
31
+ for (const source of sources) {
32
+ const converted = convertSource(source);
33
+ diagnostics.push(...converted.diagnostics);
34
+ if (!converted.item)
35
+ continue;
36
+ const seen = source.kind === 'agent' ? seenAgents : seenSkills;
37
+ if (seen.has(converted.item.name)) {
38
+ diagnostics.push(diagnostic('error', 'DUPLICATE_NAME', `Duplicate ${source.kind} name ${converted.item.name}`, { sourcePath: source.sourcePath }));
39
+ continue;
40
+ }
41
+ seen.add(converted.item.name);
42
+ if (source.kind === 'agent')
43
+ agents.push(converted.item);
44
+ else
45
+ skills.push(converted.item);
46
+ }
47
+ const files = [];
48
+ if (!diagnostics.some((item) => item.severity === 'error')) {
49
+ for (const platform of resolvePlatforms(options.platform)) {
50
+ for (const agent of agents)
51
+ files.push(renderFile(platform, 'agent', agent, options, diagnostics));
52
+ for (const skill of skills)
53
+ files.push(renderFile(platform, 'skill', skill, options, diagnostics));
54
+ }
55
+ files.sort((a, b) => `${a.platform}:${a.kind}:${a.name}`.localeCompare(`${b.platform}:${b.kind}:${b.name}`));
56
+ if (options.checkCollisions)
57
+ diagnostics.push(...await collisionDiagnostics(files, Boolean(options.force)));
58
+ }
59
+ return { files, diagnostics, sourceCount: sources.length };
60
+ }
61
+ function convertSource(source) {
62
+ const diagnostics = [];
63
+ const data = source.data;
64
+ const name = typeof data.name === 'string' ? data.name : undefined;
65
+ const description = typeof data.description === 'string' ? data.description : undefined;
66
+ for (const key of Object.keys(data)) {
67
+ if (!allowedTopLevel.has(key))
68
+ diagnostics.push(diagnostic('error', 'UNSUPPORTED_FIELD', `Unsupported canonical field ${key}`, { sourcePath: source.sourcePath }));
69
+ }
70
+ for (const platform of platformKeys) {
71
+ const config = data[platform];
72
+ if (config === undefined)
73
+ continue;
74
+ if (!config || typeof config !== 'object' || Array.isArray(config)) {
75
+ diagnostics.push(diagnostic('error', 'INVALID_PLATFORM_BLOCK', `${platform} must be an object`, { sourcePath: source.sourcePath, platform: platform }));
76
+ continue;
77
+ }
78
+ for (const key of Object.keys(config)) {
79
+ const allowedKeys = platform === 'opencode' && source.kind === 'agent' ? allowedOpenCodeKeys : allowedProductKeys;
80
+ if (!allowedKeys.has(key))
81
+ diagnostics.push(diagnostic('error', 'UNSUPPORTED_PLATFORM_FIELD', `${platform}.${key} is not supported in the MVP`, { sourcePath: source.sourcePath, platform: platform }));
82
+ }
83
+ if ('model' in config && typeof config.model !== 'string') {
84
+ diagnostics.push(diagnostic('error', 'INVALID_PLATFORM_MODEL', `${platform}.model must be a string`, { sourcePath: source.sourcePath, platform: platform }));
85
+ }
86
+ if (platform === 'opencode' && source.kind === 'agent' && 'mode' in config && !openCodeModes.has(config.mode)) {
87
+ diagnostics.push(diagnostic('error', 'INVALID_OPENCODE_MODE', 'opencode.mode must be one of primary, subagent, all', { sourcePath: source.sourcePath, platform: 'opencode' }));
88
+ }
89
+ }
90
+ if (!name)
91
+ diagnostics.push(diagnostic('error', 'MISSING_NAME', `${source.kind} name is required`, { sourcePath: source.sourcePath }));
92
+ if (name && !namePattern.test(name))
93
+ diagnostics.push(diagnostic('error', 'INVALID_NAME', `${source.kind} name must be kebab-case`, { sourcePath: source.sourcePath }));
94
+ if (name && name !== source.expectedName)
95
+ diagnostics.push(diagnostic('error', 'NAME_MISMATCH', `${source.kind} name must match ${source.expectedName}`, { sourcePath: source.sourcePath }));
96
+ if (!description)
97
+ diagnostics.push(diagnostic('error', 'MISSING_DESCRIPTION', `${source.kind} description is required`, { sourcePath: source.sourcePath }));
98
+ if (!source.body.trim())
99
+ diagnostics.push(diagnostic('error', 'EMPTY_BODY', `${source.kind} body is required`, { sourcePath: source.sourcePath }));
100
+ if (!name || !description || !source.body.trim() || diagnostics.some((item) => item.severity === 'error'))
101
+ return { diagnostics };
102
+ const base = { name, description, claude: productConfig(data.claude), opencode: productConfig(data.opencode), codex: productConfig(data.codex) };
103
+ return { diagnostics, item: source.kind === 'agent' ? { ...base, definition: source.body } : { ...base, instructions: source.body } };
104
+ }
105
+ function productConfig(value) {
106
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
107
+ }
108
+ function renderFile(platform, kind, item, options, diagnostics) {
109
+ const rendered = kind === 'agent'
110
+ ? platform === 'opencode' ? renderOpenCodeAgent(item) : platform === 'claude' ? renderClaudeAgent(item) : renderCodexAgent(item)
111
+ : platform === 'opencode' ? renderOpenCodeSkill(item) : platform === 'claude' ? renderClaudeSkill(item) : renderCodexSkill(item);
112
+ diagnostics.push(...rendered.diagnostics);
113
+ return { platform, kind, scope: options.scope, name: item.name, path: resolveOutputPath(platform, kind, options.scope, item.name, options.cwd, options.home), content: rendered.content };
114
+ }
115
+ async function collisionDiagnostics(files, force) {
116
+ const diagnostics = [];
117
+ for (const file of files) {
118
+ try {
119
+ await access(file.path, constants.F_OK);
120
+ diagnostics.push(diagnostic(force ? 'warning' : 'error', force ? 'OVERWRITE_FORCED' : 'DESTINATION_EXISTS', force ? `--force will overwrite ${file.path}` : `Destination exists; use --force to overwrite ${file.path}`, { platform: file.platform }));
121
+ }
122
+ catch {
123
+ // Missing destination is safe.
124
+ }
125
+ }
126
+ return diagnostics;
127
+ }
@@ -0,0 +1,8 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ export async function writeOutputs(files) {
4
+ for (const file of files) {
5
+ await mkdir(path.dirname(file.path), { recursive: true });
6
+ await writeFile(file.path, file.content, 'utf8');
7
+ }
8
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@guidobuilds/forge-ai",
3
+ "version": "0.1.0",
4
+ "description": "Forge AI framework",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "forge-ai": "bin/forge-ai.mjs"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "dist/src",
13
+ "agents",
14
+ "skills",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc -p tsconfig.build.json",
20
+ "build:test": "tsc -p tsconfig.json",
21
+ "typecheck": "tsc -p tsconfig.json --noEmit",
22
+ "test": "npm run build:test && node --test dist/tests/*.test.js",
23
+ "prepack": "npm run build"
24
+ },
25
+ "engines": {
26
+ "node": ">=20"
27
+ },
28
+ "devDependencies": {
29
+ "@types/node": "^22.15.3",
30
+ "typescript": "^5.8.3"
31
+ },
32
+ "dependencies": {
33
+ "@clack/prompts": "^1.2.0",
34
+ "picocolors": "^1.1.1"
35
+ }
36
+ }
@@ -0,0 +1,80 @@
1
+ ---
2
+ name: forge-build
3
+ description: Implement approved scope from design and plan artifacts, then write build-log.md.
4
+ ---
5
+
6
+ # Forge Build Skill
7
+
8
+ ## Role
9
+ Implement only approved scope.
10
+
11
+ Build is for approved code implementation only.
12
+
13
+ ## Inputs
14
+
15
+ - Orchestrator prompt with the approved implementation scope
16
+ - Optional: `.forge/<feature-slug>/explore.md`
17
+ - Optional: `.forge/<feature-slug>/design.md`
18
+ - Optional: `.forge/<feature-slug>/plan.md`
19
+
20
+ ## Required output file
21
+
22
+ `.forge/<feature-slug>/build-log.md`
23
+
24
+ ## Build log format
25
+
26
+ - Executed plan steps
27
+ - Files changed
28
+ - Validation run and result
29
+ - Deviations from plan and rationale
30
+ - Follow-ups
31
+
32
+ ## Build rules
33
+
34
+ - If a plan exists, do not expand scope beyond plan.
35
+ - Before implementing, check whether `.forge/<feature-slug>/plan.md` exists.
36
+ - If a plan exists, review it critically before touching code.
37
+ - If a plan exists, require the orchestrator prompt to include evidence that the user explicitly approved starting build for that planned scope.
38
+ - If a plan exists and that approval is absent or ambiguous, stop and return `STATUS: blocked` with approval questions instead of implementing.
39
+ - If a plan exists and contains placeholders, missing dependencies, or non-buildable tasks, stop and return `STATUS: blocked` instead of guessing.
40
+ - If no plan exists, treat the orchestrator prompt as the approved scope and keep the change tightly bounded.
41
+ - If no plan exists, the direct-build path is allowed only when the orchestrator prompt clearly marks the request as a lightweight implementation.
42
+ - Non-development operational tasks are out of scope for build.
43
+ - Route non-development execution tasks such as git commit or git push to `forge-helper`.
44
+ - If a step is materially ambiguous during execution, stop and return blocked with questions.
45
+ - When a plan exists, record build-log progress against the reviewed plan rather than silently reshaping it.
46
+ - Implement the minimum code necessary to satisfy the approved design and plan.
47
+ - Do not perform adjacent refactors, cleanup passes, or abstraction work unless explicitly approved or required to complete the approved scope.
48
+ - Prefer existing patterns over introducing new layers, frameworks, or indirection.
49
+
50
+ ## Pre-implementation checklist
51
+
52
+ Before editing files, confirm:
53
+
54
+ - the goal being implemented
55
+ - the files expected to change
56
+ - the validation that should prove the goal
57
+
58
+ Apply this checklist on both plan-backed builds and lightweight direct-build paths.
59
+
60
+ ## Contract (strict)
61
+
62
+ Return only:
63
+
64
+ ```text
65
+ STATUS: success|partial|blocked
66
+ PHASE: BUILD
67
+ FEATURE_SLUG: <kebab-case>
68
+ ARTIFACTS:
69
+ - .forge/<feature-slug>/build-log.md
70
+ SUMMARY:
71
+ - <brief point>
72
+ NEXT_RECOMMENDED: none
73
+ RISKS:
74
+ - <risk or None>
75
+ QUESTIONS:
76
+ 1) <question>
77
+ 2) <question>
78
+ ```
79
+
80
+ Include `QUESTIONS` only when blocked.
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: forge-design
3
+ description: Create the canonical design artifact that merges product and technical design into design.md.
4
+ ---
5
+
6
+ # Forge Design Skill
7
+
8
+ ## Role
9
+ Close critical design decisions and then produce the single design artifact for the work item.
10
+
11
+ `design.md` is the default source of truth for both intended behavior and technical shape. There is no separate `tech.md` in this flow.
12
+
13
+ ## Inputs
14
+
15
+ - `.forge/<feature-slug>/explore.md`
16
+ - Feature request and user clarifications
17
+
18
+ ## Required output file
19
+
20
+ `.forge/<feature-slug>/design.md`, but only after the clarification gate is fully closed.
21
+
22
+ ## Clarification gate
23
+
24
+ Before writing `design.md`, review:
25
+
26
+ - `.forge/<feature-slug>/explore.md`
27
+ - the user request
28
+ - prior user clarifications in the current thread
29
+
30
+ Classify open questions into:
31
+
32
+ - critical decisions that materially change behavior, scope, interface, or technical shape
33
+ - non-critical details that can be fixed by a reasonable default
34
+
35
+ Rules:
36
+
37
+ - Resolve all critical decisions before writing `design.md`.
38
+ - Do not ask questions that can be answered from the repo, existing artifacts, or docs.
39
+ - Ask the smallest useful batch of independent questions.
40
+ - Every question must include:
41
+ - the decision to resolve
42
+ - a recommended answer
43
+ - brief impact of that recommendation
44
+ - If critical decisions remain, return `STATUS: blocked`, `NEXT_RECOMMENDED: design`, and do not write `design.md` yet.
45
+
46
+ ## Design format
47
+
48
+ The document must stay compact and optimized for LLM consumption.
49
+
50
+ Expected content:
51
+ - Objective
52
+ - Non-objectives
53
+ - Decision Log
54
+ - Requirements with stable `TASK-*` identifiers
55
+ - Technical shape for those same `TASK-*` items
56
+ - Constraints and dependencies that materially affect implementation
57
+ - Acceptance checks
58
+
59
+ ## Design rules
60
+
61
+ - Merge product and technical design into one artifact, but keep those concerns clearly separated by section.
62
+ - Include only resolved design-relevant decisions in the `Decision Log`.
63
+ - Reuse stable `TASK-*` identifiers across the artifact.
64
+ - Be concrete about files, modules, integration points, and constraints when they materially shape implementation.
65
+ - Do not turn `design.md` into an execution checklist; sequencing belongs in `plan.md`.
66
+ - Use reasonable defaults only for non-critical details.
67
+ - Do not write `design.md` with unresolved critical decisions.
68
+ - Prefer the simplest design that satisfies the requested outcome and acceptance checks.
69
+ - Avoid speculative abstractions or new indirection unless the request or current architecture requires them.
70
+ - Record the important tradeoffs and defaults that downstream planning and build must preserve.
71
+ - Escalate only decisions that materially change behavior, scope, interface, or technical shape.
72
+ - If a heavier alternative was considered and rejected because it would expand scope or complexity, note that briefly when it helps preserve the approved shape.
73
+
74
+ ## Phase intent
75
+
76
+ - `design.md` answers: what should change, and what is the intended technical shape?
77
+ - It is the design source of truth for downstream planning and implementation after the clarification gate has been closed.
78
+
79
+ ## Contract (strict)
80
+
81
+ Return only:
82
+
83
+ ```text
84
+ STATUS: success|partial|blocked
85
+ PHASE: DESIGN
86
+ FEATURE_SLUG: <kebab-case>
87
+ ARTIFACTS:
88
+ - .forge/<feature-slug>/design.md | None
89
+ SUMMARY:
90
+ - <brief point>
91
+ NEXT_RECOMMENDED: design|plan
92
+ RISKS:
93
+ - <risk or None>
94
+ QUESTIONS:
95
+ 1) Decision: <decision>
96
+ Recommendation: <recommended answer>
97
+ Impact: <brief why>
98
+ 2) Decision: <decision>
99
+ Recommendation: <recommended answer>
100
+ Impact: <brief why>
101
+ ```
102
+
103
+ Use `STATUS: blocked` when critical decisions still require user input.
104
+ Include `QUESTIONS` only when blocked.
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: forge-explore
3
+ description: Explore the requested feature and write the baseline exploration artifact.
4
+ ---
5
+
6
+ # Forge Explore Skill
7
+
8
+ ## Role
9
+ Explore the repository and produce a compact baseline for downstream design, planning, or build work.
10
+
11
+ ## Inputs
12
+
13
+ - Work item request from the orchestrator prompt
14
+ - Repository code and docs
15
+
16
+ ## Required output file
17
+
18
+ `.forge/<feature-slug>/explore.md`
19
+
20
+ ## Exploration rules
21
+
22
+ - Think before broadening the search. Prefer narrow reading and searching around likely files and symbols before wider repo scans.
23
+ - Distinguish observed facts from inferred conclusions.
24
+ - Capture assumptions, unknowns, tradeoffs, and critical decisions explicitly.
25
+ - Record only the repo intersections that materially shape later design or implementation.
26
+ - Escalate only missing information that meaningfully blocks design or safe execution.
27
+ - Do not redesign the solution in `explore`; identify what exists, what is missing, and what decisions remain.
28
+
29
+ ## Explore format
30
+
31
+ Keep the artifact compact and optimized for downstream LLM consumption.
32
+
33
+ Expected content:
34
+ - Problem framing
35
+ - What already exists and current state
36
+ - Relevant codepaths, modules, systems, and docs
37
+ - Intersections with adjacent areas that may be affected
38
+ - Assumptions
39
+ - Unknowns
40
+ - Tradeoffs
41
+ - Critical decisions
42
+ - Non-critical unknowns
43
+
44
+ ## Contract (strict)
45
+
46
+ Return only:
47
+
48
+ ```text
49
+ STATUS: success|partial|blocked
50
+ PHASE: EXPLORE
51
+ FEATURE_SLUG: <kebab-case>
52
+ ARTIFACTS:
53
+ - .forge/<feature-slug>/explore.md
54
+ SUMMARY:
55
+ - <brief point>
56
+ NEXT_RECOMMENDED: design
57
+ RISKS:
58
+ - <risk or None>
59
+ QUESTIONS:
60
+ 1) <question>
61
+ 2) <question>
62
+ ```
63
+
64
+ Use `STATUS: blocked` only if missing information blocks meaningful exploration.
65
+ Include `QUESTIONS` only when blocked.
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: forge-helper
3
+ description: Execute non-development helper tasks for the orchestrator.
4
+ ---
5
+
6
+ # Forge Helper Skill
7
+
8
+ ## Role
9
+ Execute bounded non-development tasks for the orchestrator.
10
+
11
+ ## Scope rules
12
+
13
+ - Do only the requested operational action.
14
+ - Do not write code, edit source files, or broaden into software-development implementation work.
15
+ - If the request is actually explore, design, plan, or build work, stop and tell the orchestrator to route it to the appropriate phase agent.
16
+ - Keep execution tightly bounded to the requested helper task.
17
+ - If the action could mutate protected or remote state, require explicit confirmation unless the orchestrator prompt already contains clear user intent for that exact action.
18
+ - Do not broaden into workflow advice or extra repo operations unless asked.
19
+
20
+ ## Typical examples
21
+
22
+ - create a git commit
23
+ - push a branch
24
+ - inspect non-development execution status needed by the orchestrator
25
+
26
+ ## Contract (strict)
27
+
28
+ Return only:
29
+
30
+ ```text
31
+ STATUS: success|partial|blocked
32
+ PHASE: HELPER
33
+ FEATURE_SLUG: <kebab-case>
34
+ ARTIFACTS:
35
+ - <path or None>
36
+ SUMMARY:
37
+ - <brief point>
38
+ NEXT_RECOMMENDED: none
39
+ RISKS:
40
+ - <risk or None>
41
+ QUESTIONS:
42
+ 1) <question>
43
+ 2) <question>
44
+ ```
45
+
46
+ Include `QUESTIONS` only when blocked.