@entelligentsia/forgecli 0.11.2 → 0.11.3
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 +10 -0
- package/dist/CHANGELOG-forge-plugin.md +27 -0
- package/dist/bin/forge.js +0 -0
- package/dist/extensions/forgecli/ask-user-tool.js +32 -20
- package/dist/extensions/forgecli/ask-user-tool.js.map +1 -1
- package/dist/extensions/forgecli/config-layer.js +4 -1
- package/dist/extensions/forgecli/config-layer.js.map +1 -1
- package/dist/extensions/forgecli/config-writer.js +4 -1
- package/dist/extensions/forgecli/config-writer.js.map +1 -1
- package/dist/extensions/forgecli/fix-bug.js +31 -1
- package/dist/extensions/forgecli/fix-bug.js.map +1 -1
- package/dist/extensions/forgecli/health-check.d.ts +10 -0
- package/dist/extensions/forgecli/health-check.js +160 -8
- package/dist/extensions/forgecli/health-check.js.map +1 -1
- package/dist/extensions/forgecli/hook-dispatcher.js +4 -2
- package/dist/extensions/forgecli/hook-dispatcher.js.map +1 -1
- package/dist/extensions/forgecli/hooks/write-guard.js +5 -1
- package/dist/extensions/forgecli/hooks/write-guard.js.map +1 -1
- package/dist/extensions/forgecli/lib/store-error-remediation.d.ts +65 -0
- package/dist/extensions/forgecli/lib/store-error-remediation.js +298 -0
- package/dist/extensions/forgecli/lib/store-error-remediation.js.map +1 -0
- package/dist/extensions/forgecli/run-sprint.js +16 -1
- package/dist/extensions/forgecli/run-sprint.js.map +1 -1
- package/dist/extensions/forgecli/run-task.js +30 -8
- package/dist/extensions/forgecli/run-task.js.map +1 -1
- package/dist/extensions/forgecli/store-resolver.d.ts +18 -0
- package/dist/extensions/forgecli/store-resolver.js +44 -4
- package/dist/extensions/forgecli/store-resolver.js.map +1 -1
- package/dist/extensions/forgecli/store-validator.d.ts +3 -0
- package/dist/extensions/forgecli/store-validator.js +4 -2
- package/dist/extensions/forgecli/store-validator.js.map +1 -1
- package/dist/forge-payload/.base-pack/personas/supervisor.md +9 -0
- package/dist/forge-payload/.base-pack/workflows/enhance.md +17 -11
- package/dist/forge-payload/.claude-plugin/plugin.json +1 -1
- package/dist/forge-payload/.schemas/migrations.json +16 -0
- package/dist/forge-payload/meta/workflows/meta-enhance.md +17 -11
- package/dist/forge-payload/tools/build-persona-pack.cjs +120 -11
- package/package.json +1 -1
|
@@ -22,6 +22,99 @@ const fs = require('fs');
|
|
|
22
22
|
const path = require('path');
|
|
23
23
|
const crypto = require('crypto');
|
|
24
24
|
|
|
25
|
+
// ── Persona parser — two variants: base-pack (no frontmatter) and meta (YAML frontmatter) ──
|
|
26
|
+
//
|
|
27
|
+
// Base-pack personas: first line is a markdown heading, e.g. "# Architect" or
|
|
28
|
+
// "# Architect — subtitle". The rest is the body. id and role are derived from
|
|
29
|
+
// the filename. summary is the first sentence of the first non-heading line.
|
|
30
|
+
// responsibilities and outputs are derived from section headers in the body.
|
|
31
|
+
// file_ref is derived from the file path.
|
|
32
|
+
//
|
|
33
|
+
// Meta personas: YAML frontmatter block (--- ... ---) containing all required
|
|
34
|
+
// fields. The body is ignored.
|
|
35
|
+
|
|
36
|
+
function parsePersona(content, filePath) {
|
|
37
|
+
if (content.startsWith('---')) {
|
|
38
|
+
// Meta format — parse frontmatter then ignore body
|
|
39
|
+
const fm = parseFrontmatter(content, filePath);
|
|
40
|
+
// Re-validate required fields for meta personas
|
|
41
|
+
for (const field of REQUIRED_PERSONA_FIELDS) {
|
|
42
|
+
if (!(field in fm)) {
|
|
43
|
+
throw new Error(`${filePath}: frontmatter missing required field '${field}'`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return fm;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Base-pack format — no frontmatter, derive fields from content
|
|
50
|
+
const lines = content.split(/\r?\n/);
|
|
51
|
+
const fileName = path.basename(filePath, '.md'); // e.g. "architect"
|
|
52
|
+
|
|
53
|
+
// Derive id and role from filename (e.g. "meta-architect.md" → "architect", "architect.md" → "architect")
|
|
54
|
+
const id = fileName.replace(/^meta-/, '').replace(/-skills$/, '');
|
|
55
|
+
const role = id;
|
|
56
|
+
|
|
57
|
+
// Extract summary: first non-heading, non-empty line
|
|
58
|
+
let summary = '';
|
|
59
|
+
for (const line of lines) {
|
|
60
|
+
const trimmed = line.trim();
|
|
61
|
+
if (trimmed && !trimmed.startsWith('#') && !trimmed.startsWith('{{')) {
|
|
62
|
+
// Grab first sentence (up to first period + space or first line break)
|
|
63
|
+
const sentenceEnd = trimmed.search(/\.\s/);
|
|
64
|
+
summary = sentenceEnd >= 0 ? trimmed.slice(0, sentenceEnd + 1) : trimmed;
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Derive responsibilities and outputs from section headers
|
|
70
|
+
const responsibilities = [];
|
|
71
|
+
const outputs = [];
|
|
72
|
+
for (const line of lines) {
|
|
73
|
+
if (/^##\s+(What I Produce|Outputs?)/i.test(line)) {
|
|
74
|
+
// Collect bullet points under this header until next ## or end
|
|
75
|
+
let i = lines.indexOf(line) + 1;
|
|
76
|
+
while (i < lines.length && !lines[i].startsWith('##')) {
|
|
77
|
+
const m = lines[i].match(/^[-*]\s+(.+)/);
|
|
78
|
+
if (m) outputs.push(m[1].trim());
|
|
79
|
+
i++;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (/^##\s+(Capabilities?)/i.test(line)) {
|
|
83
|
+
let i = lines.indexOf(line) + 1;
|
|
84
|
+
while (i < lines.length && !lines[i].startsWith('##')) {
|
|
85
|
+
const m = lines[i].match(/^[-*]\s+(.+)/);
|
|
86
|
+
if (m) responsibilities.push(m[1].trim());
|
|
87
|
+
i++;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// file_ref: base-pack personas become .forge/personas/<id>.md
|
|
93
|
+
const file_ref = `.forge/personas/${id}.md`;
|
|
94
|
+
|
|
95
|
+
return { id, role, summary, responsibilities, outputs, file_ref };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── Skill parser — base-pack skills (with frontmatter, no file_ref) and meta skills (with file_ref) ──
|
|
99
|
+
|
|
100
|
+
function parseSkill(content, filePath) {
|
|
101
|
+
const fm = parseFrontmatter(content, filePath);
|
|
102
|
+
|
|
103
|
+
// Skills must have: id, applies_to, summary, capabilities
|
|
104
|
+
for (const field of ['id', 'applies_to', 'summary', 'capabilities']) {
|
|
105
|
+
if (!(field in fm)) {
|
|
106
|
+
throw new Error(`${filePath}: frontmatter missing required field '${field}'`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// file_ref is present in meta skills; derive it for base-pack skills from the path
|
|
111
|
+
if (!fm.file_ref) {
|
|
112
|
+
fm.file_ref = `.forge/skills/${path.basename(filePath)}`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return fm;
|
|
116
|
+
}
|
|
117
|
+
|
|
25
118
|
// ── YAML frontmatter parser ──────────────────────────────────────────────────
|
|
26
119
|
// Narrow-scope parser: handles scalars, folded scalars (`>`), block lists
|
|
27
120
|
// (`- item`) under a key, and inline flow lists (`[a, b]`). Anything else
|
|
@@ -123,30 +216,44 @@ function listMarkdown(dir) {
|
|
|
123
216
|
.map((f) => path.join(dir, f));
|
|
124
217
|
}
|
|
125
218
|
|
|
126
|
-
function
|
|
219
|
+
function loadPersonas(dir) {
|
|
127
220
|
const entries = {};
|
|
128
221
|
for (const filePath of listMarkdown(dir)) {
|
|
129
222
|
const content = fs.readFileSync(filePath, 'utf8');
|
|
130
|
-
let fm;
|
|
131
223
|
try {
|
|
132
|
-
|
|
224
|
+
const persona = parsePersona(content, filePath);
|
|
225
|
+
entries[persona.id] = persona;
|
|
133
226
|
} catch (err) {
|
|
134
|
-
// Re-throw with original path-bearing message intact.
|
|
135
227
|
throw err;
|
|
136
228
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
229
|
+
}
|
|
230
|
+
return entries;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function loadSkills(dir) {
|
|
234
|
+
const entries = {};
|
|
235
|
+
for (const filePath of listMarkdown(dir)) {
|
|
236
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
237
|
+
try {
|
|
238
|
+
const skill = parseSkill(content, filePath);
|
|
239
|
+
entries[skill.id] = skill;
|
|
240
|
+
} catch (err) {
|
|
241
|
+
throw err;
|
|
141
242
|
}
|
|
142
|
-
entries[fm.id] = fm;
|
|
143
243
|
}
|
|
144
244
|
return entries;
|
|
145
245
|
}
|
|
146
246
|
|
|
147
247
|
function buildPack({ personaDir, skillDir }) {
|
|
148
|
-
const personas =
|
|
149
|
-
const skills =
|
|
248
|
+
const personas = loadPersonas(personaDir);
|
|
249
|
+
const skills = loadSkills(skillDir);
|
|
250
|
+
|
|
251
|
+
const personaCount = Object.keys(personas).length;
|
|
252
|
+
const skillCount = Object.keys(skills).length;
|
|
253
|
+
if (personaCount === 0 && skillCount === 0) {
|
|
254
|
+
throw new Error('no personas or skills found; refusing to write empty pack');
|
|
255
|
+
}
|
|
256
|
+
|
|
150
257
|
return {
|
|
151
258
|
version: 1,
|
|
152
259
|
built_at: new Date().toISOString(),
|
|
@@ -220,6 +327,8 @@ if (require.main === module) {
|
|
|
220
327
|
|
|
221
328
|
module.exports = {
|
|
222
329
|
parseFrontmatter,
|
|
330
|
+
parsePersona,
|
|
331
|
+
parseSkill,
|
|
223
332
|
buildPack,
|
|
224
333
|
computeSourceHash,
|
|
225
334
|
writePack,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entelligentsia/forgecli",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.3",
|
|
4
4
|
"description": "Forge SDLC ported onto @earendil-works/pi-coding-agent — production launcher with three bin aliases (forge/forgecli/4ge). Bundles a curated fork of pi-coding-agent vendored under earendil-works names.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Entelligentsia",
|