@guidobuilds/forge-ai 0.1.0 → 0.3.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/CHANGELOG.md +60 -0
- package/README.md +14 -8
- package/{agents → artifacts/forge}/forge.md +15 -5
- package/artifacts/forge-grill/forge-grill.md +93 -0
- package/{skills/forge-worker/SKILL.md → artifacts/forge-worker/forge-worker.md} +22 -2
- package/{skills/using-forge/SKILL.md → artifacts/using-forge/using-forge.md} +1 -0
- package/dist/src/adapters/claude-known.js +31 -0
- package/dist/src/adapters/claude.js +33 -21
- package/dist/src/adapters/codex.js +14 -14
- package/dist/src/adapters/opencode.js +15 -15
- package/dist/src/adapters/shared.js +6 -1
- package/dist/src/cli.js +112 -24
- package/dist/src/discovery.js +16 -18
- package/dist/src/frontmatter.js +8 -68
- package/dist/src/manifest.js +134 -0
- package/dist/src/model.js +3 -0
- package/dist/src/processor.js +88 -45
- package/dist/src/writer.js +13 -1
- package/package.json +12 -12
- package/agents/forge-worker.md +0 -60
- package/skills/forge-build/SKILL.md +0 -80
- package/skills/forge-design/SKILL.md +0 -104
- package/skills/forge-explore/SKILL.md +0 -65
- package/skills/forge-helper/SKILL.md +0 -46
- package/skills/forge-plan/SKILL.md +0 -81
package/dist/src/processor.js
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
-
import { access } from 'node:fs/promises';
|
|
1
|
+
import { access, readFile } from 'node:fs/promises';
|
|
2
2
|
import { constants } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
3
4
|
import { renderClaudeAgent, renderClaudeSkill } from './adapters/claude.js';
|
|
4
5
|
import { renderCodexAgent, renderCodexSkill } from './adapters/codex.js';
|
|
5
6
|
import { renderOpenCodeAgent, renderOpenCodeSkill } from './adapters/opencode.js';
|
|
6
7
|
import { diagnostic } from './diagnostics.js';
|
|
7
8
|
import { discoverSources } from './discovery.js';
|
|
9
|
+
import { lookupEntryByPath, resolveBackupPath, sha256 } from './manifest.js';
|
|
8
10
|
import { resolveOutputPath } from './paths.js';
|
|
9
11
|
import { isPlatform, platforms } from './model.js';
|
|
10
12
|
const namePattern = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
11
13
|
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 allowedTopLevel = new Set(['name', 'description', 'kind', 'claude', 'opencode', 'codex']);
|
|
15
|
+
const allowedProductKeys = new Set(['permissions', 'model', 'kind']);
|
|
14
16
|
const allowedOpenCodeKeys = new Set([...allowedProductKeys, 'mode']);
|
|
15
17
|
const openCodeModes = new Set(['primary', 'subagent', 'all']);
|
|
18
|
+
const artifactKinds = new Set(['agent', 'skill']);
|
|
16
19
|
export function resolvePlatforms(platform) {
|
|
17
20
|
return platform === 'all' ? platforms : [platform];
|
|
18
21
|
}
|
|
@@ -24,45 +27,43 @@ export function parseScope(value) {
|
|
|
24
27
|
}
|
|
25
28
|
export async function buildWritePlan(options) {
|
|
26
29
|
const { sources, diagnostics } = await discoverSources(options.source);
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
const seenAgents = new Set();
|
|
30
|
-
const seenSkills = new Set();
|
|
30
|
+
const artifacts = [];
|
|
31
|
+
const seen = new Set();
|
|
31
32
|
for (const source of sources) {
|
|
32
|
-
const converted = convertSource(source);
|
|
33
|
+
const converted = convertSource(source, options.source);
|
|
33
34
|
diagnostics.push(...converted.diagnostics);
|
|
34
35
|
if (!converted.item)
|
|
35
36
|
continue;
|
|
36
|
-
const seen = source.kind === 'agent' ? seenAgents : seenSkills;
|
|
37
37
|
if (seen.has(converted.item.name)) {
|
|
38
|
-
diagnostics.push(diagnostic('error', 'DUPLICATE_NAME', `Duplicate
|
|
38
|
+
diagnostics.push(diagnostic('error', 'DUPLICATE_NAME', `Duplicate artifact name ${converted.item.name}`, { sourcePath: source.sourcePath }));
|
|
39
39
|
continue;
|
|
40
40
|
}
|
|
41
41
|
seen.add(converted.item.name);
|
|
42
|
-
|
|
43
|
-
agents.push(converted.item);
|
|
44
|
-
else
|
|
45
|
-
skills.push(converted.item);
|
|
42
|
+
artifacts.push(converted.item);
|
|
46
43
|
}
|
|
47
44
|
const files = [];
|
|
45
|
+
const pending = { modifiedOverwrites: [], foreignOverwrites: [] };
|
|
48
46
|
if (!diagnostics.some((item) => item.severity === 'error')) {
|
|
49
47
|
for (const platform of resolvePlatforms(options.platform)) {
|
|
50
|
-
for (const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
for (const artifact of artifacts) {
|
|
49
|
+
const effectiveKind = artifact[platform]?.kind ?? artifact.kind;
|
|
50
|
+
files.push(renderFile(platform, effectiveKind, artifact, options, diagnostics));
|
|
51
|
+
}
|
|
54
52
|
}
|
|
55
53
|
files.sort((a, b) => `${a.platform}:${a.kind}:${a.name}`.localeCompare(`${b.platform}:${b.kind}:${b.name}`));
|
|
56
|
-
if (options.checkCollisions)
|
|
57
|
-
|
|
54
|
+
if (options.checkCollisions) {
|
|
55
|
+
const anchor = options.scope === 'user' ? (options.home ?? '') : (options.cwd ?? '');
|
|
56
|
+
diagnostics.push(...await classifyDestinations(files, options.manifest, options.backupRoot, anchor, pending));
|
|
57
|
+
}
|
|
58
58
|
}
|
|
59
|
-
return { files, diagnostics, sourceCount: sources.length };
|
|
59
|
+
return { files, diagnostics, pending, sourceCount: sources.length };
|
|
60
60
|
}
|
|
61
|
-
function convertSource(source) {
|
|
61
|
+
function convertSource(source, sourceRoot) {
|
|
62
62
|
const diagnostics = [];
|
|
63
63
|
const data = source.data;
|
|
64
64
|
const name = typeof data.name === 'string' ? data.name : undefined;
|
|
65
65
|
const description = typeof data.description === 'string' ? data.description : undefined;
|
|
66
|
+
const kind = data.kind;
|
|
66
67
|
for (const key of Object.keys(data)) {
|
|
67
68
|
if (!allowedTopLevel.has(key))
|
|
68
69
|
diagnostics.push(diagnostic('error', 'UNSUPPORTED_FIELD', `Unsupported canonical field ${key}`, { sourcePath: source.sourcePath }));
|
|
@@ -75,53 +76,95 @@ function convertSource(source) {
|
|
|
75
76
|
diagnostics.push(diagnostic('error', 'INVALID_PLATFORM_BLOCK', `${platform} must be an object`, { sourcePath: source.sourcePath, platform: platform }));
|
|
76
77
|
continue;
|
|
77
78
|
}
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
const record = config;
|
|
80
|
+
for (const key of Object.keys(record)) {
|
|
81
|
+
const allowedKeys = platform === 'opencode' ? allowedOpenCodeKeys : allowedProductKeys;
|
|
80
82
|
if (!allowedKeys.has(key))
|
|
81
83
|
diagnostics.push(diagnostic('error', 'UNSUPPORTED_PLATFORM_FIELD', `${platform}.${key} is not supported in the MVP`, { sourcePath: source.sourcePath, platform: platform }));
|
|
82
84
|
}
|
|
83
|
-
if ('model' in
|
|
85
|
+
if ('model' in record && typeof record.model !== 'string') {
|
|
84
86
|
diagnostics.push(diagnostic('error', 'INVALID_PLATFORM_MODEL', `${platform}.model must be a string`, { sourcePath: source.sourcePath, platform: platform }));
|
|
85
87
|
}
|
|
86
|
-
if (
|
|
87
|
-
diagnostics.push(diagnostic('error', '
|
|
88
|
+
if ('kind' in record && !artifactKinds.has(record.kind)) {
|
|
89
|
+
diagnostics.push(diagnostic('error', 'INVALID_PLATFORM_KIND', `${platform}.kind must be one of agent, skill`, { sourcePath: source.sourcePath, platform: platform }));
|
|
90
|
+
}
|
|
91
|
+
if (platform === 'opencode' && 'mode' in record) {
|
|
92
|
+
if (!openCodeModes.has(record.mode)) {
|
|
93
|
+
diagnostics.push(diagnostic('error', 'INVALID_OPENCODE_MODE', 'opencode.mode must be one of primary, subagent, all', { sourcePath: source.sourcePath, platform: 'opencode' }));
|
|
94
|
+
}
|
|
95
|
+
const effectiveKind = record.kind ?? kind;
|
|
96
|
+
if (effectiveKind !== 'agent') {
|
|
97
|
+
diagnostics.push(diagnostic('error', 'OPENCODE_MODE_ON_SKILL', 'opencode.mode is only valid when the OpenCode artifact kind is agent', { sourcePath: source.sourcePath, platform: 'opencode' }));
|
|
98
|
+
}
|
|
88
99
|
}
|
|
89
100
|
}
|
|
90
101
|
if (!name)
|
|
91
|
-
diagnostics.push(diagnostic('error', 'MISSING_NAME',
|
|
102
|
+
diagnostics.push(diagnostic('error', 'MISSING_NAME', 'artifact name is required', { sourcePath: source.sourcePath }));
|
|
92
103
|
if (name && !namePattern.test(name))
|
|
93
|
-
diagnostics.push(diagnostic('error', 'INVALID_NAME',
|
|
104
|
+
diagnostics.push(diagnostic('error', 'INVALID_NAME', 'artifact name must be kebab-case', { sourcePath: source.sourcePath }));
|
|
94
105
|
if (name && name !== source.expectedName)
|
|
95
|
-
diagnostics.push(diagnostic('error', 'NAME_MISMATCH',
|
|
106
|
+
diagnostics.push(diagnostic('error', 'NAME_MISMATCH', `artifact name must match ${source.expectedName}`, { sourcePath: source.sourcePath }));
|
|
96
107
|
if (!description)
|
|
97
|
-
diagnostics.push(diagnostic('error', 'MISSING_DESCRIPTION',
|
|
108
|
+
diagnostics.push(diagnostic('error', 'MISSING_DESCRIPTION', 'artifact description is required', { sourcePath: source.sourcePath }));
|
|
109
|
+
if (kind === undefined)
|
|
110
|
+
diagnostics.push(diagnostic('error', 'MISSING_KIND', 'artifact kind is required (agent or skill)', { sourcePath: source.sourcePath }));
|
|
111
|
+
else if (!artifactKinds.has(kind))
|
|
112
|
+
diagnostics.push(diagnostic('error', 'INVALID_KIND', 'artifact kind must be one of agent, skill', { sourcePath: source.sourcePath }));
|
|
98
113
|
if (!source.body.trim())
|
|
99
|
-
diagnostics.push(diagnostic('error', 'EMPTY_BODY',
|
|
100
|
-
if (!name || !description || !source.body.trim() || diagnostics.some((item) => item.severity === 'error'))
|
|
114
|
+
diagnostics.push(diagnostic('error', 'EMPTY_BODY', 'artifact body is required', { sourcePath: source.sourcePath }));
|
|
115
|
+
if (!name || !description || !artifactKinds.has(kind) || !source.body.trim() || diagnostics.some((item) => item.severity === 'error'))
|
|
101
116
|
return { diagnostics };
|
|
102
|
-
|
|
103
|
-
|
|
117
|
+
return {
|
|
118
|
+
diagnostics,
|
|
119
|
+
item: {
|
|
120
|
+
name,
|
|
121
|
+
description,
|
|
122
|
+
kind: kind,
|
|
123
|
+
body: source.body,
|
|
124
|
+
sourcePath: path.relative(path.resolve(sourceRoot), source.sourcePath),
|
|
125
|
+
claude: productConfig(data.claude),
|
|
126
|
+
opencode: productConfig(data.opencode),
|
|
127
|
+
codex: productConfig(data.codex)
|
|
128
|
+
}
|
|
129
|
+
};
|
|
104
130
|
}
|
|
105
131
|
function productConfig(value) {
|
|
106
132
|
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
107
133
|
}
|
|
108
|
-
function renderFile(platform, kind,
|
|
134
|
+
function renderFile(platform, kind, artifact, options, diagnostics) {
|
|
109
135
|
const rendered = kind === 'agent'
|
|
110
|
-
? platform === 'opencode' ? renderOpenCodeAgent(
|
|
111
|
-
: platform === 'opencode' ? renderOpenCodeSkill(
|
|
136
|
+
? platform === 'opencode' ? renderOpenCodeAgent(artifact) : platform === 'claude' ? renderClaudeAgent(artifact) : renderCodexAgent(artifact)
|
|
137
|
+
: platform === 'opencode' ? renderOpenCodeSkill(artifact) : platform === 'claude' ? renderClaudeSkill(artifact) : renderCodexSkill(artifact);
|
|
112
138
|
diagnostics.push(...rendered.diagnostics);
|
|
113
|
-
return { platform, kind, scope: options.scope, name:
|
|
139
|
+
return { platform, kind, scope: options.scope, name: artifact.name, sourcePath: artifact.sourcePath, path: resolveOutputPath(platform, kind, options.scope, artifact.name, options.cwd, options.home), content: rendered.content };
|
|
114
140
|
}
|
|
115
|
-
async function
|
|
141
|
+
async function classifyDestinations(files, manifest, backupRoot, anchor, pending) {
|
|
116
142
|
const diagnostics = [];
|
|
117
143
|
for (const file of files) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
144
|
+
const status = await classifyFile(file.path, manifest);
|
|
145
|
+
file.status = status;
|
|
146
|
+
if (status === 'managed-modified' && backupRoot) {
|
|
147
|
+
file.backupPath = resolveBackupPath(backupRoot, file.path, anchor);
|
|
148
|
+
pending.modifiedOverwrites.push(file);
|
|
149
|
+
diagnostics.push(diagnostic('warning', 'MANAGED_FILE_OVERWRITE', `Will overwrite locally edited Forge file ${file.path}; backup → ${file.backupPath}`, { platform: file.platform }));
|
|
121
150
|
}
|
|
122
|
-
|
|
123
|
-
|
|
151
|
+
else if (status === 'foreign') {
|
|
152
|
+
pending.foreignOverwrites.push(file);
|
|
153
|
+
diagnostics.push(diagnostic('warning', 'FOREIGN_FILE_OVERWRITE', `Will overwrite untracked file at ${file.path}`, { platform: file.platform }));
|
|
124
154
|
}
|
|
125
155
|
}
|
|
126
156
|
return diagnostics;
|
|
127
157
|
}
|
|
158
|
+
async function classifyFile(filePath, manifest) {
|
|
159
|
+
try {
|
|
160
|
+
await access(filePath, constants.F_OK);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return 'new';
|
|
164
|
+
}
|
|
165
|
+
const entry = lookupEntryByPath(manifest, filePath);
|
|
166
|
+
if (!entry)
|
|
167
|
+
return 'foreign';
|
|
168
|
+
const content = await readFile(filePath, 'utf8');
|
|
169
|
+
return sha256(content) === entry.checksum ? 'managed-unmodified' : 'managed-modified';
|
|
170
|
+
}
|
package/dist/src/writer.js
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
|
-
import { mkdir, writeFile } from 'node:fs/promises';
|
|
1
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { backupFile } from './manifest.js';
|
|
3
4
|
export async function writeOutputs(files) {
|
|
4
5
|
for (const file of files) {
|
|
6
|
+
if (file.backupPath) {
|
|
7
|
+
try {
|
|
8
|
+
const existing = await readFile(file.path, 'utf8');
|
|
9
|
+
await backupFile(file.backupPath, existing);
|
|
10
|
+
}
|
|
11
|
+
catch (error) {
|
|
12
|
+
if (error.code !== 'ENOENT')
|
|
13
|
+
throw error;
|
|
14
|
+
// Original file disappeared between classification and write; no backup needed.
|
|
15
|
+
}
|
|
16
|
+
}
|
|
5
17
|
await mkdir(path.dirname(file.path), { recursive: true });
|
|
6
18
|
await writeFile(file.path, file.content, 'utf8');
|
|
7
19
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guidobuilds/forge-ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Forge AI framework",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,18 +10,11 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"bin",
|
|
12
12
|
"dist/src",
|
|
13
|
-
"
|
|
14
|
-
"skills",
|
|
13
|
+
"artifacts",
|
|
15
14
|
"README.md",
|
|
15
|
+
"CHANGELOG.md",
|
|
16
16
|
"LICENSE"
|
|
17
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
18
|
"engines": {
|
|
26
19
|
"node": ">=20"
|
|
27
20
|
},
|
|
@@ -31,6 +24,13 @@
|
|
|
31
24
|
},
|
|
32
25
|
"dependencies": {
|
|
33
26
|
"@clack/prompts": "^1.2.0",
|
|
34
|
-
"picocolors": "^1.1.1"
|
|
27
|
+
"picocolors": "^1.1.1",
|
|
28
|
+
"yaml": "^2.8.4"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc -p tsconfig.build.json",
|
|
32
|
+
"build:test": "tsc -p tsconfig.json",
|
|
33
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
34
|
+
"test": "npm run build:test && node --test dist/tests/*.test.js"
|
|
35
35
|
}
|
|
36
|
-
}
|
|
36
|
+
}
|
package/agents/forge-worker.md
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: forge-worker
|
|
3
|
-
description: Forge universal worker for inspect, design, plan, build, operate, and verify work
|
|
4
|
-
claude:
|
|
5
|
-
permissions:
|
|
6
|
-
tools: [TodoWrite, Read, Write, Edit, Bash, Glob, Grep, LS, MultiEdit, WebFetch]
|
|
7
|
-
opencode:
|
|
8
|
-
mode: subagent
|
|
9
|
-
permissions:
|
|
10
|
-
todowrite: true
|
|
11
|
-
read: true
|
|
12
|
-
write: true
|
|
13
|
-
edit: true
|
|
14
|
-
bash: true
|
|
15
|
-
glob: true
|
|
16
|
-
grep: true
|
|
17
|
-
list: true
|
|
18
|
-
patch: true
|
|
19
|
-
skill: true
|
|
20
|
-
webfetch: true
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
You are the Forge worker.
|
|
24
|
-
|
|
25
|
-
Load and follow the `forge-worker` skill before doing work.
|
|
26
|
-
|
|
27
|
-
You are the only worker type in Forge. The orchestrator may launch multiple instances of you in parallel or sequence.
|
|
28
|
-
|
|
29
|
-
## Inputs
|
|
30
|
-
- Orchestrator prompt with the assigned subgoal, expected boundaries, and any approval context.
|
|
31
|
-
- Optional: `.forge/<feature-slug>/explore.md`
|
|
32
|
-
- Optional: `.forge/<feature-slug>/design.md`
|
|
33
|
-
- Optional: `.forge/<feature-slug>/plan.md`
|
|
34
|
-
- Optional: `.forge/<feature-slug>/build-log.md`
|
|
35
|
-
|
|
36
|
-
The skill defines routing by work type, artifact guidance, approval handling, bounded execution, escalation rules, and validation expectations.
|
|
37
|
-
|
|
38
|
-
## Contract (strict)
|
|
39
|
-
Return only:
|
|
40
|
-
|
|
41
|
-
```text
|
|
42
|
-
STATUS: success|partial|blocked
|
|
43
|
-
WORK_TYPE: inspect|design|plan|build|operate|verify|mixed
|
|
44
|
-
FEATURE_SLUG: <kebab-case>
|
|
45
|
-
ARTIFACTS:
|
|
46
|
-
- <path or None>
|
|
47
|
-
SUMMARY:
|
|
48
|
-
- <brief point>
|
|
49
|
-
NEXT_RECOMMENDED: inspect|design|plan|build|operate|verify|ask-user|none
|
|
50
|
-
RISKS:
|
|
51
|
-
- <risk or None>
|
|
52
|
-
QUESTIONS:
|
|
53
|
-
1) <question>
|
|
54
|
-
2) <question>
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Include `QUESTIONS` only when blocked.
|
|
58
|
-
|
|
59
|
-
Do not interact directly with the user. Escalate open decisions back to the orchestrator through the contract.
|
|
60
|
-
Do not add extra format outside the defined worker contract.
|
|
@@ -1,80 +0,0 @@
|
|
|
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.
|
|
@@ -1,104 +0,0 @@
|
|
|
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.
|
|
@@ -1,65 +0,0 @@
|
|
|
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.
|
|
@@ -1,46 +0,0 @@
|
|
|
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.
|