@really-knows-ai/foundry 1.0.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/.opencode/plugins/foundry.js +106 -0
- package/LICENSE +21 -0
- package/README.md +250 -0
- package/docs/concepts.md +55 -0
- package/docs/getting-started.md +78 -0
- package/docs/work-spec.md +193 -0
- package/package.json +44 -0
- package/scripts/lib/tags.js +108 -0
- package/scripts/sort.js +410 -0
- package/scripts/validate-tags.js +54 -0
- package/skills/add-appraiser/SKILL.md +101 -0
- package/skills/add-artefact-type/SKILL.md +147 -0
- package/skills/add-cycle/SKILL.md +131 -0
- package/skills/add-flow/SKILL.md +84 -0
- package/skills/add-law/SKILL.md +99 -0
- package/skills/appraise/SKILL.md +142 -0
- package/skills/cycle/SKILL.md +111 -0
- package/skills/flow/SKILL.md +38 -0
- package/skills/forge/SKILL.md +73 -0
- package/skills/hitl/SKILL.md +65 -0
- package/skills/init-foundry/SKILL.md +51 -0
- package/skills/quench/SKILL.md +55 -0
- package/skills/sort/SKILL.md +77 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# WORK.md Spec
|
|
2
|
+
|
|
3
|
+
WORK.md is created at the start of a foundry flow on a work branch. It is the shared state between all stages in all foundry cycles. It is transient — it exists only for the duration of the foundry flow.
|
|
4
|
+
|
|
5
|
+
## Frontmatter
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
---
|
|
9
|
+
flow: <flow-id>
|
|
10
|
+
cycle: <current-cycle-id>
|
|
11
|
+
stages: [forge:write-haiku, quench:check-syllables, appraise:evaluate-quality]
|
|
12
|
+
max-iterations: 3
|
|
13
|
+
---
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Fields:
|
|
17
|
+
- `flow` — the foundry flow being executed
|
|
18
|
+
- `cycle` — the current foundry cycle id
|
|
19
|
+
- `stages` — the ordered route for this foundry cycle, set when the foundry cycle starts. Each entry uses `base:alias` format where `base` is the stage type (`forge`, `quench`, `appraise`, or `hitl`) and `alias` is a human-readable name for what that stage does in this cycle. Determined from the artefact type: if `validation.md` exists, include `quench`; always include `forge` and `appraise`. A `hitl` stage can be included for human-in-the-loop checkpoints.
|
|
20
|
+
- `max-iterations` — how many forge passes before the foundry cycle is blocked (default: 3)
|
|
21
|
+
|
|
22
|
+
The `stages` list is the happy path. Sort follows it but loops back to `forge` when unresolved feedback demands it.
|
|
23
|
+
|
|
24
|
+
### Who sets what
|
|
25
|
+
|
|
26
|
+
- `flow` — set by the foundry flow skill at foundry flow start, never changes
|
|
27
|
+
- `cycle` — set by the foundry flow skill when starting each foundry cycle
|
|
28
|
+
- `stages` — set by the foundry cycle skill when starting each foundry cycle (reads artefact type to determine if quench is needed)
|
|
29
|
+
- `max-iterations` — set by the foundry cycle skill (default 3, could be overridden in foundry cycle definition)
|
|
30
|
+
|
|
31
|
+
## Sections
|
|
32
|
+
|
|
33
|
+
### Goal
|
|
34
|
+
|
|
35
|
+
Free text describing what the foundry flow is producing and any context the human provided. Written once at foundry flow start, not modified after.
|
|
36
|
+
|
|
37
|
+
### Artefacts
|
|
38
|
+
|
|
39
|
+
A table tracking every artefact produced by the foundry flow.
|
|
40
|
+
|
|
41
|
+
```markdown
|
|
42
|
+
# Artefacts
|
|
43
|
+
|
|
44
|
+
| File | Type | Cycle | Status |
|
|
45
|
+
|------|------|-------|--------|
|
|
46
|
+
| petitions/login-change.md | petition | write-petition | draft |
|
|
47
|
+
| features/login-change.feature | gherkin | petition-to-gherkin | draft |
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Statuses:
|
|
51
|
+
- `draft` — artefact exists but has not cleared all stages
|
|
52
|
+
- `done` — artefact has cleared all stages
|
|
53
|
+
- `blocked` — artefact hit iteration limit or a violation
|
|
54
|
+
|
|
55
|
+
### Feedback
|
|
56
|
+
|
|
57
|
+
Grouped by artefact file path. Each item is a checklist entry with a tag indicating its source.
|
|
58
|
+
|
|
59
|
+
```markdown
|
|
60
|
+
# Feedback
|
|
61
|
+
|
|
62
|
+
## petitions/login-change.md
|
|
63
|
+
|
|
64
|
+
- [ ] Missing "Acceptance Criteria" section #validation
|
|
65
|
+
- [x] Justification is circular #law:justified-change | approved
|
|
66
|
+
- [~] Could be more concise #law:clear-language | wont-fix: brevity would lose necessary context | approved
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### Tags
|
|
70
|
+
|
|
71
|
+
- `#validation` — from a deterministic quench command
|
|
72
|
+
- `#law:<law-id>` — from subjective appraise, tied to a specific law
|
|
73
|
+
- `#hitl` — from human-provided feedback at a hitl checkpoint
|
|
74
|
+
|
|
75
|
+
#### Lifecycle states
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
- [ ] issue #tag open, needs forge action
|
|
79
|
+
- [x] issue #tag actioned, needs approval
|
|
80
|
+
- [~] issue #tag | wont-fix: <reason> declined by forge, needs approval (appraise only)
|
|
81
|
+
- [x] issue #tag | approved resolved
|
|
82
|
+
- [~] issue #tag | wont-fix: <reason> | approved resolved
|
|
83
|
+
- [x] issue #tag | rejected: <reason> re-opened
|
|
84
|
+
- [~] issue #tag | wont-fix: <reason> | rejected re-opened
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### Rules
|
|
88
|
+
|
|
89
|
+
- Validation feedback (`#validation`) cannot be wont-fixed
|
|
90
|
+
- Feedback is never deleted — it stays as a record of the iteration history
|
|
91
|
+
- New feedback is appended, not inserted
|
|
92
|
+
- Items are grouped under the artefact they relate to
|
|
93
|
+
|
|
94
|
+
## Who writes what
|
|
95
|
+
|
|
96
|
+
| Section | Written by | Updated by |
|
|
97
|
+
|---------|-----------|------------|
|
|
98
|
+
| Frontmatter (`flow`) | foundry flow skill | nobody |
|
|
99
|
+
| Frontmatter (`cycle`, `stages`, `max-iterations`) | foundry cycle skill | foundry cycle skill (reset on each new cycle) |
|
|
100
|
+
| Goal | foundry flow skill | nobody |
|
|
101
|
+
| Artefacts | forge skill (registers new) | foundry cycle skill (status changes) |
|
|
102
|
+
| Feedback | quench skill, appraise skill, hitl skill | forge skill (actioned/wont-fix), quench/appraise/hitl skill (approved/rejected) |
|
|
103
|
+
|
|
104
|
+
## WORK.history.yaml
|
|
105
|
+
|
|
106
|
+
A separate file (`WORK.history.yaml`) alongside WORK.md. Append-only log of every stage execution.
|
|
107
|
+
|
|
108
|
+
```yaml
|
|
109
|
+
- timestamp: "2026-04-17T14:32:01Z"
|
|
110
|
+
cycle: write-petition
|
|
111
|
+
stage: forge:draft-petition
|
|
112
|
+
iteration: 1
|
|
113
|
+
comment: Initial petition draft created
|
|
114
|
+
|
|
115
|
+
- timestamp: "2026-04-17T14:32:45Z"
|
|
116
|
+
cycle: write-petition
|
|
117
|
+
stage: quench:validate-petition
|
|
118
|
+
iteration: 1
|
|
119
|
+
comment: 2 validation issues found
|
|
120
|
+
|
|
121
|
+
- timestamp: "2026-04-17T14:33:12Z"
|
|
122
|
+
cycle: write-petition
|
|
123
|
+
stage: forge:draft-petition
|
|
124
|
+
iteration: 2
|
|
125
|
+
comment: Addressed 2 validation issues
|
|
126
|
+
|
|
127
|
+
- timestamp: "2026-04-17T14:33:30Z"
|
|
128
|
+
cycle: write-petition
|
|
129
|
+
stage: quench:validate-petition
|
|
130
|
+
iteration: 2
|
|
131
|
+
comment: Validation passed
|
|
132
|
+
|
|
133
|
+
- timestamp: "2026-04-17T14:34:00Z"
|
|
134
|
+
cycle: write-petition
|
|
135
|
+
stage: appraise:review-petition
|
|
136
|
+
iteration: 2
|
|
137
|
+
comment: No issues found, cycle complete
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Fields
|
|
141
|
+
|
|
142
|
+
- `timestamp` — ISO 8601 UTC
|
|
143
|
+
- `cycle` — which foundry cycle this entry belongs to
|
|
144
|
+
- `stage` — which stage just completed, in `base:alias` format (e.g. `forge:draft-petition`, `quench:validate-petition`, `appraise:review-petition`, `hitl:human-review`)
|
|
145
|
+
- `iteration` — the current iteration number (increments each time forge runs within a cycle)
|
|
146
|
+
- `comment` — brief description of what happened
|
|
147
|
+
|
|
148
|
+
### Rules
|
|
149
|
+
|
|
150
|
+
- Append-only — never edit or delete entries
|
|
151
|
+
- Every stage skill appends an entry when it completes
|
|
152
|
+
- The sort script reads this to determine what has happened in the current foundry cycle
|
|
153
|
+
- Iteration is derived from counting forge entries for the current foundry cycle
|
|
154
|
+
|
|
155
|
+
### Who writes
|
|
156
|
+
|
|
157
|
+
Every stage skill (forge, quench, appraise, hitl) appends an entry when it finishes.
|
|
158
|
+
|
|
159
|
+
## Example
|
|
160
|
+
|
|
161
|
+
A complete WORK.md mid-foundry flow:
|
|
162
|
+
|
|
163
|
+
```markdown
|
|
164
|
+
---
|
|
165
|
+
flow: make-haiku
|
|
166
|
+
cycle: haiku-creation
|
|
167
|
+
stages: [forge:write-haiku, quench:check-syllables, appraise:evaluate-quality]
|
|
168
|
+
max-iterations: 3
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
# Goal
|
|
172
|
+
|
|
173
|
+
Write a haiku about autumn rain. Should evoke loneliness
|
|
174
|
+
and the sound of rain on leaves.
|
|
175
|
+
|
|
176
|
+
# Artefacts
|
|
177
|
+
|
|
178
|
+
| File | Type | Cycle | Status |
|
|
179
|
+
|------|------|-------|--------|
|
|
180
|
+
| petitions/autumn-rain-haiku.md | petition | haiku-ideation | done |
|
|
181
|
+
| haiku/autumn-rain.md | haiku | haiku-creation | draft |
|
|
182
|
+
|
|
183
|
+
# Feedback
|
|
184
|
+
|
|
185
|
+
## petitions/autumn-rain-haiku.md
|
|
186
|
+
|
|
187
|
+
- [x] Acceptance criteria should mention seasonal reference #law:clear-acceptance-criteria | approved
|
|
188
|
+
|
|
189
|
+
## haiku/autumn-rain.md
|
|
190
|
+
|
|
191
|
+
- [ ] Line 2 has 8 syllables, expected 7 #validation
|
|
192
|
+
- [x] No seasonal reference detected #law:seasonal-reference | approved
|
|
193
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@really-knows-ai/foundry",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A structured framework for AI-driven artefact creation with deterministic routing, quality gates, and iterative refinement cycles.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": ".opencode/plugins/foundry.js",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "Really Knows AI",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/really-knows-ai/foundry.git"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/really-knows-ai/foundry",
|
|
14
|
+
"keywords": [
|
|
15
|
+
"foundry",
|
|
16
|
+
"ai",
|
|
17
|
+
"artefact",
|
|
18
|
+
"cycle",
|
|
19
|
+
"routing",
|
|
20
|
+
"quality",
|
|
21
|
+
"opencode",
|
|
22
|
+
"plugin"
|
|
23
|
+
],
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18.3.0"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"test": "node --test tests/sort.test.js"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"js-yaml": "^4.1.0",
|
|
32
|
+
"minimatch": "^10.2.5"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
".opencode/",
|
|
36
|
+
"skills/",
|
|
37
|
+
"scripts/",
|
|
38
|
+
"docs/work-spec.md",
|
|
39
|
+
"docs/concepts.md",
|
|
40
|
+
"docs/getting-started.md",
|
|
41
|
+
"README.md",
|
|
42
|
+
"LICENSE"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared tag validation utilities used by sort.js and validate-tags.js.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync, existsSync, readdirSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Constants
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
export const VALID_TAG_RE = /^(#validation|#hitl|#law:[\w-]+)$/;
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Law collection
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
export function collectLawIds(foundryDir) {
|
|
19
|
+
const ids = new Set();
|
|
20
|
+
|
|
21
|
+
const lawsDir = join(foundryDir, 'laws');
|
|
22
|
+
if (existsSync(lawsDir)) {
|
|
23
|
+
for (const file of readdirSync(lawsDir)) {
|
|
24
|
+
if (!file.endsWith('.md')) continue;
|
|
25
|
+
const text = readFileSync(join(lawsDir, file), 'utf-8');
|
|
26
|
+
for (const id of extractLawHeadings(text)) ids.add(id);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const artefactsDir = join(foundryDir, 'artefacts');
|
|
31
|
+
if (existsSync(artefactsDir)) {
|
|
32
|
+
for (const typeDir of readdirSync(artefactsDir)) {
|
|
33
|
+
const lawsPath = join(artefactsDir, typeDir, 'laws.md');
|
|
34
|
+
if (!existsSync(lawsPath)) continue;
|
|
35
|
+
const text = readFileSync(lawsPath, 'utf-8');
|
|
36
|
+
for (const id of extractLawHeadings(text)) ids.add(id);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return ids;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function extractLawHeadings(text) {
|
|
44
|
+
const ids = [];
|
|
45
|
+
for (const line of text.split('\n')) {
|
|
46
|
+
const match = line.match(/^## (.+)$/);
|
|
47
|
+
if (match) ids.push(match[1].trim());
|
|
48
|
+
}
|
|
49
|
+
return ids;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Tag extraction
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Extract all hash-tags from a feedback line.
|
|
58
|
+
* Returns an array of strings like ['#validation', '#law:brevity'].
|
|
59
|
+
*/
|
|
60
|
+
export function extractAllTags(line) {
|
|
61
|
+
return (line.match(/#[\w][\w:-]*/g) || []);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Validation
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Validate all feedback tags in the Feedback section of WORK.md text.
|
|
70
|
+
*
|
|
71
|
+
* Returns an array of error strings. Empty array = all valid.
|
|
72
|
+
*/
|
|
73
|
+
export function validateTags(workText, foundryDir) {
|
|
74
|
+
const lawIds = collectLawIds(foundryDir);
|
|
75
|
+
const errors = [];
|
|
76
|
+
let inFeedback = false;
|
|
77
|
+
let lineNum = 0;
|
|
78
|
+
|
|
79
|
+
for (const line of workText.split('\n')) {
|
|
80
|
+
lineNum++;
|
|
81
|
+
const stripped = line.trim();
|
|
82
|
+
|
|
83
|
+
if (stripped === '# Feedback') { inFeedback = true; continue; }
|
|
84
|
+
if (inFeedback && stripped.startsWith('# ') && stripped !== '# Feedback') {
|
|
85
|
+
inFeedback = false; continue;
|
|
86
|
+
}
|
|
87
|
+
if (!inFeedback || !(/^- \[/.test(stripped))) continue;
|
|
88
|
+
|
|
89
|
+
const tags = extractAllTags(stripped);
|
|
90
|
+
if (tags.length === 0) {
|
|
91
|
+
errors.push({ line: lineNum, message: 'Feedback item has no tag', raw: stripped });
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (const tag of tags) {
|
|
96
|
+
if (!VALID_TAG_RE.test(tag)) {
|
|
97
|
+
errors.push({ line: lineNum, message: `Unknown tag: ${tag}`, raw: stripped });
|
|
98
|
+
} else if (tag.startsWith('#law:')) {
|
|
99
|
+
const lawId = tag.slice(5);
|
|
100
|
+
if (!lawIds.has(lawId)) {
|
|
101
|
+
errors.push({ line: lineNum, message: `Law not found: ${lawId}`, raw: stripped });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return errors;
|
|
108
|
+
}
|