godpowers 2.4.1 → 2.4.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 +52 -0
- package/README.md +9 -6
- package/RELEASE.md +35 -30
- package/agents/god-orchestrator.md +31 -1255
- package/bin/install.js +22 -19
- package/lib/README.md +1 -0
- package/lib/agent-validator.js +2 -27
- package/lib/checkpoint.js +4 -19
- package/lib/command-families.js +10 -2
- package/lib/context-budget.js +5 -34
- package/lib/design-spec.js +11 -13
- package/lib/extensions.js +12 -2
- package/lib/frontmatter.js +74 -0
- package/lib/installer-core.js +7 -52
- package/lib/intent.js +88 -6
- package/lib/pillars.js +2 -41
- package/lib/recipes.js +10 -2
- package/lib/router.js +10 -2
- package/lib/skill-surface.js +2 -11
- package/lib/workflow-parser.js +12 -4
- package/package.json +8 -3
- package/references/orchestration/GOD-NEXT-RUNBOOK.md +32 -0
- package/references/orchestration/GOD-ORCHESTRATOR-RUNBOOK.md +1259 -0
- package/references/orchestration/README.md +6 -0
- package/references/shared/DASHBOARD-CONTRACT.md +93 -0
- package/references/shared/LOCKING.md +15 -0
- package/references/shared/README.md +2 -0
- package/routing/god-roadmap-check.yaml +1 -1
- package/skills/god-arch.md +1 -12
- package/skills/god-build.md +1 -12
- package/skills/god-deploy.md +1 -12
- package/skills/god-design.md +1 -12
- package/skills/god-feature.md +1 -12
- package/skills/god-harden.md +1 -12
- package/skills/god-hotfix.md +1 -12
- package/skills/god-launch.md +1 -12
- package/skills/god-link.md +1 -12
- package/skills/god-migrate.md +1 -3
- package/skills/god-next.md +34 -410
- package/skills/god-observe.md +1 -12
- package/skills/god-prd.md +1 -12
- package/skills/god-redo.md +1 -12
- package/skills/god-refactor.md +1 -12
- package/skills/god-repair.md +1 -12
- package/skills/god-repo.md +1 -12
- package/skills/god-restore.md +1 -12
- package/skills/god-roadmap-check.md +5 -0
- package/skills/god-roadmap.md +1 -12
- package/skills/god-rollback.md +1 -12
- package/skills/god-scan.md +1 -12
- package/skills/god-skip.md +1 -12
- package/skills/god-stack.md +1 -12
- package/skills/god-status.md +27 -204
- package/skills/god-story-build.md +1 -12
- package/skills/god-story-close.md +1 -12
- package/skills/god-story.md +1 -12
- package/skills/god-sync.md +1 -12
- package/skills/god-undo.md +1 -12
- package/skills/god-update-deps.md +1 -12
- package/skills/god-upgrade.md +1 -12
package/bin/install.js
CHANGED
|
@@ -187,25 +187,20 @@ function runExtensionScaffoldCommand(opts) {
|
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
const COMMAND_RUNNERS = {
|
|
191
|
+
status: runDashboardCommand,
|
|
192
|
+
next: runDashboardCommand,
|
|
193
|
+
'quick-proof': runQuickProofCommand,
|
|
194
|
+
'automation-status': runAutomationCommand,
|
|
195
|
+
'automation-setup': runAutomationCommand,
|
|
196
|
+
dogfood: runDogfoodCommand,
|
|
197
|
+
'extension-scaffold': runExtensionScaffoldCommand
|
|
198
|
+
};
|
|
199
|
+
|
|
190
200
|
function runCommand(opts) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
if (opts.command === 'quick-proof') {
|
|
196
|
-
runQuickProofCommand(opts);
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
199
|
-
if (opts.command === 'automation-status' || opts.command === 'automation-setup') {
|
|
200
|
-
runAutomationCommand(opts);
|
|
201
|
-
return true;
|
|
202
|
-
}
|
|
203
|
-
if (opts.command === 'dogfood') {
|
|
204
|
-
runDogfoodCommand(opts);
|
|
205
|
-
return true;
|
|
206
|
-
}
|
|
207
|
-
if (opts.command === 'extension-scaffold') {
|
|
208
|
-
runExtensionScaffoldCommand(opts);
|
|
201
|
+
const runner = COMMAND_RUNNERS[opts.command];
|
|
202
|
+
if (runner) {
|
|
203
|
+
runner(opts);
|
|
209
204
|
return true;
|
|
210
205
|
}
|
|
211
206
|
return false;
|
|
@@ -299,11 +294,19 @@ function main() {
|
|
|
299
294
|
}
|
|
300
295
|
}
|
|
301
296
|
|
|
302
|
-
main
|
|
297
|
+
if (require.main === module) {
|
|
298
|
+
main();
|
|
299
|
+
}
|
|
303
300
|
|
|
304
301
|
module.exports = {
|
|
305
302
|
showHelp,
|
|
303
|
+
COMMAND_RUNNERS,
|
|
306
304
|
runCommand,
|
|
305
|
+
runAutomationCommand,
|
|
306
|
+
runDashboardCommand,
|
|
307
|
+
runDogfoodCommand,
|
|
308
|
+
runQuickProofCommand,
|
|
309
|
+
runExtensionScaffoldCommand,
|
|
307
310
|
applyDefaultRuntimeSelection,
|
|
308
311
|
runInstall,
|
|
309
312
|
runUninstall,
|
package/lib/README.md
CHANGED
|
@@ -11,6 +11,7 @@ package-level integrations.
|
|
|
11
11
|
| `state.js` | Read, initialize, validate, and write `.godpowers/state.json`. |
|
|
12
12
|
| `state-lock.js` | Coordinate state writes with a lock file. |
|
|
13
13
|
| `intent.js` | Read and validate `intent.yaml` from project roots or `.godpowers/`. |
|
|
14
|
+
| `frontmatter.js` | Parse shared markdown YAML frontmatter for skills, agents, Pillars, checkpoints, and design specs. |
|
|
14
15
|
| `checkpoint.js` | Create and inspect resumable checkpoint artifacts. |
|
|
15
16
|
| `feature-awareness.js` | Detect and refresh existing-project awareness after runtime upgrades. |
|
|
16
17
|
| `code-intelligence.js` | Detect optional `ast-grep`, `sg`, and LSP tooling for structural search, rewrite, and diagnostics guidance. |
|
package/lib/agent-validator.js
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
const fs = require('fs');
|
|
20
20
|
const path = require('path');
|
|
21
|
+
const frontmatter = require('./frontmatter');
|
|
21
22
|
|
|
22
23
|
const REQUIRED_FRONTMATTER = ['name', 'description'];
|
|
23
24
|
const RECOMMENDED_FRONTMATTER = ['tools'];
|
|
@@ -37,33 +38,7 @@ function parseAgentFile(filePath) {
|
|
|
37
38
|
raw
|
|
38
39
|
};
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
if (raw.startsWith('---')) {
|
|
42
|
-
const end = raw.indexOf('\n---', 3);
|
|
43
|
-
if (end > 0) {
|
|
44
|
-
const fmBlock = raw.slice(3, end).trim();
|
|
45
|
-
// Parse simple YAML (key: value, key: |, etc.)
|
|
46
|
-
let currentMultiline = null;
|
|
47
|
-
for (const line of fmBlock.split('\n')) {
|
|
48
|
-
if (currentMultiline) {
|
|
49
|
-
if (line.startsWith(' ') || line.startsWith('\t')) {
|
|
50
|
-
result.frontmatter[currentMultiline] += '\n' + line.trim();
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
currentMultiline = null;
|
|
54
|
-
}
|
|
55
|
-
const m = line.match(/^([\w-]+):\s*(.*)$/);
|
|
56
|
-
if (m) {
|
|
57
|
-
if (m[2] === '|' || m[2] === '>') {
|
|
58
|
-
currentMultiline = m[1];
|
|
59
|
-
result.frontmatter[m[1]] = '';
|
|
60
|
-
} else {
|
|
61
|
-
result.frontmatter[m[1]] = m[2].trim();
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
41
|
+
result.frontmatter = frontmatter.parse(raw, { strict: true, source: filePath });
|
|
67
42
|
|
|
68
43
|
// Sections: lines starting with # / ## / ###
|
|
69
44
|
const lines = raw.split('\n');
|
package/lib/checkpoint.js
CHANGED
|
@@ -50,6 +50,7 @@ const fs = require('fs');
|
|
|
50
50
|
const path = require('path');
|
|
51
51
|
const crypto = require('crypto');
|
|
52
52
|
const atomic = require('./atomic-write');
|
|
53
|
+
const frontmatterLib = require('./frontmatter');
|
|
53
54
|
|
|
54
55
|
const MAX_ACTIONS = 20;
|
|
55
56
|
const MAX_FACTS = 10;
|
|
@@ -74,25 +75,9 @@ function read(projectRoot) {
|
|
|
74
75
|
if (!fs.existsSync(file)) return null;
|
|
75
76
|
const raw = fs.readFileSync(file, 'utf8');
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
if (raw.startsWith('---')) {
|
|
81
|
-
const end = raw.indexOf('\n---', 3);
|
|
82
|
-
if (end > 0) {
|
|
83
|
-
const fmText = raw.slice(3, end).trim();
|
|
84
|
-
body = raw.slice(end + 4).trim();
|
|
85
|
-
for (const line of fmText.split('\n')) {
|
|
86
|
-
const m = line.match(/^([\w-]+):\s*(.*)$/);
|
|
87
|
-
if (m) {
|
|
88
|
-
let v = m[2].trim();
|
|
89
|
-
if (v === 'true') v = true;
|
|
90
|
-
else if (v === 'false') v = false;
|
|
91
|
-
frontmatter[m[1]] = v;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
78
|
+
const parsed = frontmatterLib.split(raw, { strict: true, source: file });
|
|
79
|
+
const frontmatter = parsed.frontmatter || {};
|
|
80
|
+
const body = parsed.body.trim();
|
|
96
81
|
|
|
97
82
|
// Section parse for actions and facts
|
|
98
83
|
const actions = parseList(body, 'Last actions');
|
package/lib/command-families.js
CHANGED
|
@@ -119,7 +119,6 @@ const COMMAND_FAMILIES = [
|
|
|
119
119
|
'/god-upgrade',
|
|
120
120
|
'/god-context',
|
|
121
121
|
'/god-context-scan',
|
|
122
|
-
'/god-roadmap-check',
|
|
123
122
|
'/god-roadmap-update'
|
|
124
123
|
]
|
|
125
124
|
},
|
|
@@ -198,6 +197,15 @@ const COMMAND_FAMILIES = [
|
|
|
198
197
|
'/god-help',
|
|
199
198
|
'/god-version'
|
|
200
199
|
]
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
id: 'compatibility',
|
|
203
|
+
label: 'Compatibility',
|
|
204
|
+
purpose: 'Deprecated full-profile commands kept for backward compatibility.',
|
|
205
|
+
visibility: 'hidden',
|
|
206
|
+
commands: [
|
|
207
|
+
'/god-roadmap-check'
|
|
208
|
+
]
|
|
201
209
|
}
|
|
202
210
|
];
|
|
203
211
|
|
|
@@ -279,7 +287,7 @@ function familyForCommand(command) {
|
|
|
279
287
|
return COMMAND_FAMILIES.find((family) => family.commands.includes(command)) || null;
|
|
280
288
|
}
|
|
281
289
|
|
|
282
|
-
function renderFamilyCards(families = COMMAND_FAMILIES) {
|
|
290
|
+
function renderFamilyCards(families = COMMAND_FAMILIES.filter((family) => family.visibility !== 'hidden')) {
|
|
283
291
|
return families.map((family) => (
|
|
284
292
|
`${family.label}: ${family.purpose} (${family.commands.join(', ')})`
|
|
285
293
|
));
|
package/lib/context-budget.js
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
const fs = require('fs');
|
|
27
27
|
const path = require('path');
|
|
28
|
+
const frontmatter = require('./frontmatter');
|
|
28
29
|
|
|
29
30
|
const BYTES_PER_TOKEN = 4; // rough English text heuristic
|
|
30
31
|
const DEFAULT_MAX_TOKENS = 80_000; // per-agent default; ~half of a 200K window
|
|
@@ -52,44 +53,14 @@ function parseAgentBudget(agentPath) {
|
|
|
52
53
|
return { required: [], optional: [], maxTokens: null };
|
|
53
54
|
}
|
|
54
55
|
const raw = fs.readFileSync(agentPath, 'utf8');
|
|
55
|
-
|
|
56
|
-
if (raw.startsWith('---')) {
|
|
57
|
-
const end = raw.indexOf('\n---', 3);
|
|
58
|
-
if (end > 0) fmText = raw.slice(3, end);
|
|
59
|
-
}
|
|
60
|
-
|
|
56
|
+
const metadata = frontmatter.parse(raw, { strict: true, source: agentPath });
|
|
61
57
|
const out = { required: [], optional: [], maxTokens: null };
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const line = lines[i].trim();
|
|
66
|
-
if (line.startsWith('required-context:')) {
|
|
67
|
-
out.required = parseListInline(line.slice('required-context:'.length), lines, i);
|
|
68
|
-
} else if (line.startsWith('optional-context:')) {
|
|
69
|
-
out.optional = parseListInline(line.slice('optional-context:'.length), lines, i);
|
|
70
|
-
} else if (line.startsWith('max-tokens:')) {
|
|
71
|
-
const n = parseInt(line.slice('max-tokens:'.length).trim(), 10);
|
|
72
|
-
if (Number.isFinite(n)) out.maxTokens = n;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
58
|
+
if (Array.isArray(metadata['required-context'])) out.required = metadata['required-context'];
|
|
59
|
+
if (Array.isArray(metadata['optional-context'])) out.optional = metadata['optional-context'];
|
|
60
|
+
if (Number.isFinite(metadata['max-tokens'])) out.maxTokens = metadata['max-tokens'];
|
|
75
61
|
return out;
|
|
76
62
|
}
|
|
77
63
|
|
|
78
|
-
function parseListInline(rest, lines, idx) {
|
|
79
|
-
const r = rest.trim();
|
|
80
|
-
if (r.startsWith('[') && r.endsWith(']')) {
|
|
81
|
-
return r.slice(1, -1).split(',').map(s => s.trim().replace(/^["']|["']$/g, '')).filter(Boolean);
|
|
82
|
-
}
|
|
83
|
-
// Block list: subsequent lines starting with '- '
|
|
84
|
-
const items = [];
|
|
85
|
-
for (let j = idx + 1; j < lines.length; j++) {
|
|
86
|
-
const l = lines[j];
|
|
87
|
-
if (!l.startsWith(' - ')) break;
|
|
88
|
-
items.push(l.slice(4).trim().replace(/^["']|["']$/g, ''));
|
|
89
|
-
}
|
|
90
|
-
return items;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
64
|
/**
|
|
94
65
|
* Compute total bytes + estimated tokens for a list of file paths.
|
|
95
66
|
* Missing files contribute 0; not an error.
|
package/lib/design-spec.js
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* lint(content) -> { findings, valid } // run all checks
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
const
|
|
18
|
+
const frontmatter = require('./frontmatter');
|
|
19
19
|
|
|
20
20
|
const VALID_SECTIONS = [
|
|
21
21
|
'Overview', 'Brand & Style',
|
|
@@ -48,23 +48,21 @@ const COMPONENT_PROPS = [
|
|
|
48
48
|
* Parse a DESIGN.md file: separate YAML frontmatter from markdown body.
|
|
49
49
|
*/
|
|
50
50
|
function parse(content) {
|
|
51
|
-
const errors = [];
|
|
52
51
|
if (!content.startsWith('---')) {
|
|
53
52
|
return { frontmatter: null, body: content, errors: ['Missing YAML frontmatter (file must start with `---`).'] };
|
|
54
53
|
}
|
|
55
|
-
const
|
|
56
|
-
if (
|
|
54
|
+
const parsed = frontmatter.split(content, { strict: true, source: 'DESIGN.md' });
|
|
55
|
+
if (!parsed.frontmatter) {
|
|
57
56
|
return { frontmatter: null, body: content, errors: ['Frontmatter not closed (missing closing `---`).'] };
|
|
58
57
|
}
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
frontmatter
|
|
64
|
-
|
|
65
|
-
errors
|
|
66
|
-
}
|
|
67
|
-
return { frontmatter, body, errors };
|
|
58
|
+
const errors = parsed.diagnostics.map((diagnostic) =>
|
|
59
|
+
`Frontmatter YAML warning: ${diagnostic.message} on line ${diagnostic.line}`
|
|
60
|
+
);
|
|
61
|
+
return {
|
|
62
|
+
frontmatter: errors.length > 0 ? null : parsed.frontmatter,
|
|
63
|
+
body: parsed.body.trim(),
|
|
64
|
+
errors
|
|
65
|
+
};
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
/**
|
package/lib/extensions.js
CHANGED
|
@@ -41,8 +41,18 @@ function parseManifest(yamlText) {
|
|
|
41
41
|
return { manifest: null, errors: ['empty manifest'] };
|
|
42
42
|
}
|
|
43
43
|
try {
|
|
44
|
-
const
|
|
45
|
-
|
|
44
|
+
const parsed = intentLib.parseSimpleYamlWithDiagnostics(yamlText, {
|
|
45
|
+
strict: true,
|
|
46
|
+
source: 'manifest.yaml',
|
|
47
|
+
unsafeKeySeverity: 'error'
|
|
48
|
+
});
|
|
49
|
+
if (parsed.diagnostics.length > 0) {
|
|
50
|
+
return {
|
|
51
|
+
manifest: null,
|
|
52
|
+
errors: parsed.diagnostics.map(intentLib.formatDiagnostic)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return { manifest: parsed.data, errors: [] };
|
|
46
56
|
} catch (e) {
|
|
47
57
|
return { manifest: null, errors: ['parse error: ' + e.message] };
|
|
48
58
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared YAML frontmatter helpers for markdown-backed Godpowers contracts.
|
|
3
|
+
*
|
|
4
|
+
* Frontmatter uses the same dependency-free YAML subset as intent, routing,
|
|
5
|
+
* recipes, workflows, and extension manifests.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { parseSimpleYamlWithDiagnostics } = require('./intent');
|
|
9
|
+
|
|
10
|
+
function hasOpeningFence(text) {
|
|
11
|
+
return typeof text === 'string' && text.startsWith('---');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function split(text, opts = {}) {
|
|
15
|
+
const source = opts.source || null;
|
|
16
|
+
if (!hasOpeningFence(text)) {
|
|
17
|
+
return {
|
|
18
|
+
frontmatter: null,
|
|
19
|
+
body: text,
|
|
20
|
+
rawFrontmatter: '',
|
|
21
|
+
diagnostics: opts.require ? [{
|
|
22
|
+
severity: 'warning',
|
|
23
|
+
line: 1,
|
|
24
|
+
source,
|
|
25
|
+
message: 'Missing YAML frontmatter fence'
|
|
26
|
+
}] : []
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const end = text.indexOf('\n---', 3);
|
|
31
|
+
if (end === -1) {
|
|
32
|
+
return {
|
|
33
|
+
frontmatter: null,
|
|
34
|
+
body: text,
|
|
35
|
+
rawFrontmatter: '',
|
|
36
|
+
diagnostics: [{
|
|
37
|
+
severity: 'warning',
|
|
38
|
+
line: 1,
|
|
39
|
+
source,
|
|
40
|
+
message: 'YAML frontmatter fence is not closed'
|
|
41
|
+
}]
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const rawFrontmatter = text.slice(3, end).trim();
|
|
46
|
+
const parsed = parseSimpleYamlWithDiagnostics(rawFrontmatter, {
|
|
47
|
+
strict: opts.strict === true,
|
|
48
|
+
source,
|
|
49
|
+
unsafeKeySeverity: opts.unsafeKeySeverity || 'warning',
|
|
50
|
+
onDiagnostic: opts.onDiagnostic
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
frontmatter: parsed.data,
|
|
55
|
+
body: text.slice(end + 4).trimStart(),
|
|
56
|
+
rawFrontmatter,
|
|
57
|
+
diagnostics: parsed.diagnostics
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parse(text, opts = {}) {
|
|
62
|
+
const result = split(text, opts);
|
|
63
|
+
return result.frontmatter || {};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function strip(text) {
|
|
67
|
+
return split(text).body.trim();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = {
|
|
71
|
+
split,
|
|
72
|
+
parse,
|
|
73
|
+
strip
|
|
74
|
+
};
|
package/lib/installer-core.js
CHANGED
|
@@ -5,6 +5,7 @@ const { ensureDir, copyRecursive, copyRuntimeBundle } = require('./installer-fil
|
|
|
5
5
|
const { resolveRuntime } = require('./installer-runtimes');
|
|
6
6
|
const { selectedSkillNames, normalizeProfiles } = require('./install-profiles');
|
|
7
7
|
const identity = require('./package-identity');
|
|
8
|
+
const frontmatter = require('./frontmatter');
|
|
8
9
|
|
|
9
10
|
const VERSION = identity.PACKAGE_VERSION;
|
|
10
11
|
|
|
@@ -46,52 +47,6 @@ function installSkillFile(srcFile, skillsDest, runtimeKey, targetName = null) {
|
|
|
46
47
|
fs.copyFileSync(srcFile, path.join(skillsDest, `${baseName}.md`));
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
function parseAgentFrontmatter(content) {
|
|
50
|
-
const fallback = { name: null, description: null };
|
|
51
|
-
if (!content.startsWith('---\n')) return fallback;
|
|
52
|
-
|
|
53
|
-
const end = content.indexOf('\n---', 4);
|
|
54
|
-
if (end === -1) return fallback;
|
|
55
|
-
|
|
56
|
-
const lines = content.slice(4, end).split('\n');
|
|
57
|
-
const parsed = { ...fallback };
|
|
58
|
-
|
|
59
|
-
for (let i = 0; i < lines.length; i++) {
|
|
60
|
-
const line = lines[i];
|
|
61
|
-
const nameMatch = line.match(/^name:\s*(.+)\s*$/);
|
|
62
|
-
if (nameMatch) {
|
|
63
|
-
parsed.name = nameMatch[1].replace(/^["']|["']$/g, '');
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (line === 'description: |') {
|
|
68
|
-
const desc = [];
|
|
69
|
-
i++;
|
|
70
|
-
while (i < lines.length && /^ {2}/.test(lines[i])) {
|
|
71
|
-
desc.push(lines[i].slice(2));
|
|
72
|
-
i++;
|
|
73
|
-
}
|
|
74
|
-
i--;
|
|
75
|
-
parsed.description = desc.join('\n').trim();
|
|
76
|
-
continue;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const descMatch = line.match(/^description:\s*(.+)\s*$/);
|
|
80
|
-
if (descMatch) {
|
|
81
|
-
parsed.description = descMatch[1].replace(/^["']|["']$/g, '');
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return parsed;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function stripFrontmatter(content) {
|
|
89
|
-
if (!content.startsWith('---\n')) return content.trim();
|
|
90
|
-
const end = content.indexOf('\n---', 4);
|
|
91
|
-
if (end === -1) return content.trim();
|
|
92
|
-
return content.slice(end + 4).trim();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
50
|
function tomlString(value) {
|
|
96
51
|
return JSON.stringify(value || '');
|
|
97
52
|
}
|
|
@@ -102,10 +57,10 @@ function tomlLiteral(value) {
|
|
|
102
57
|
|
|
103
58
|
function writeCodexAgentToml(srcFile, agentsDest) {
|
|
104
59
|
const content = fs.readFileSync(srcFile, 'utf8');
|
|
105
|
-
const
|
|
106
|
-
const name =
|
|
107
|
-
const description =
|
|
108
|
-
const instructions =
|
|
60
|
+
const metadata = frontmatter.parse(content, { strict: true, source: srcFile });
|
|
61
|
+
const name = metadata.name || path.basename(srcFile, '.md');
|
|
62
|
+
const description = metadata.description || `Godpowers specialist agent: ${name}.`;
|
|
63
|
+
const instructions = frontmatter.strip(content);
|
|
109
64
|
const toml = [
|
|
110
65
|
`name = ${tomlString(name)}`,
|
|
111
66
|
`description = ${tomlString(description)}`,
|
|
@@ -371,8 +326,8 @@ module.exports = {
|
|
|
371
326
|
uninstallForRuntime,
|
|
372
327
|
countInstalledSurface: countProfileSurface,
|
|
373
328
|
installSkillFile,
|
|
374
|
-
parseAgentFrontmatter,
|
|
375
|
-
stripFrontmatter,
|
|
329
|
+
parseAgentFrontmatter: frontmatter.parse,
|
|
330
|
+
stripFrontmatter: frontmatter.strip,
|
|
376
331
|
writeCodexAgentToml,
|
|
377
332
|
removeSkillEntry,
|
|
378
333
|
pruneGodpowersSkills,
|
package/lib/intent.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Note: this is a minimal YAML reader, intentionally avoiding a YAML
|
|
7
7
|
* dependency. Handles the subset of YAML our intent files use.
|
|
8
|
+
* Strict callers can collect diagnostics for skipped lines and unsafe keys.
|
|
8
9
|
* For complex YAML, agents read the file directly.
|
|
9
10
|
*/
|
|
10
11
|
|
|
@@ -66,15 +67,59 @@ function isUnsafeKey(key) {
|
|
|
66
67
|
* Parse a simple YAML subset. Just enough for intent.yaml structure.
|
|
67
68
|
* Real-world: replace with `yaml` npm package when we add deps.
|
|
68
69
|
*/
|
|
69
|
-
function
|
|
70
|
+
function createDiagnostic(severity, line, message, source) {
|
|
71
|
+
return {
|
|
72
|
+
severity,
|
|
73
|
+
line,
|
|
74
|
+
source: source || null,
|
|
75
|
+
message
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function formatDiagnostic(diagnostic) {
|
|
80
|
+
const source = diagnostic.source ? `${diagnostic.source}:` : '';
|
|
81
|
+
return `${source}${diagnostic.line}: ${diagnostic.message}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function diagnosticsError(diagnostics, label = 'YAML diagnostics') {
|
|
85
|
+
const error = new Error(`${label}:\n - ${diagnostics.map(formatDiagnostic).join('\n - ')}`);
|
|
86
|
+
error.diagnostics = diagnostics;
|
|
87
|
+
return error;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function parseSimpleYaml(content, opts = {}) {
|
|
91
|
+
const result = parseSimpleYamlWithDiagnostics(content, opts);
|
|
92
|
+
if (opts.throwOnDiagnostics && result.diagnostics.length > 0) {
|
|
93
|
+
throw diagnosticsError(result.diagnostics, opts.errorLabel);
|
|
94
|
+
}
|
|
95
|
+
return result.data;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function parseSimpleYamlWithDiagnostics(content, opts = {}) {
|
|
70
99
|
const lines = content.split('\n');
|
|
71
100
|
const result = {};
|
|
72
101
|
const stack = [{ obj: result, indent: -1, key: null, isArray: false, parent: null }];
|
|
102
|
+
const diagnostics = [];
|
|
103
|
+
const strict = opts.strict === true;
|
|
104
|
+
const source = opts.source || null;
|
|
105
|
+
const unsafeKeySeverity = opts.unsafeKeySeverity || 'warning';
|
|
106
|
+
|
|
107
|
+
function record(severity, lineNumber, message) {
|
|
108
|
+
const diagnostic = createDiagnostic(severity, lineNumber, message, source);
|
|
109
|
+
diagnostics.push(diagnostic);
|
|
110
|
+
if (typeof opts.onDiagnostic === 'function') opts.onDiagnostic(diagnostic);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function recordSkipped(lineNumber, rawLine, reason) {
|
|
114
|
+
if (!strict) return;
|
|
115
|
+
record('warning', lineNumber, `${reason}: ${rawLine.trim()}`);
|
|
116
|
+
}
|
|
73
117
|
|
|
74
118
|
for (let i = 0; i < lines.length; i++) {
|
|
75
119
|
let line = lines[i];
|
|
76
120
|
if (!line.trim() || line.trim().startsWith('#')) continue;
|
|
77
121
|
line = stripInlineComment(line);
|
|
122
|
+
if (!line.trim()) continue;
|
|
78
123
|
|
|
79
124
|
const indent = line.length - line.trimStart().length;
|
|
80
125
|
const trimmed = line.trim();
|
|
@@ -84,6 +129,16 @@ function parseSimpleYaml(content) {
|
|
|
84
129
|
stack.pop();
|
|
85
130
|
}
|
|
86
131
|
const parent = stack[stack.length - 1].obj;
|
|
132
|
+
|
|
133
|
+
if (trimmed === '[]') {
|
|
134
|
+
const current = stack[stack.length - 1];
|
|
135
|
+
if (current.parent && current.key && Object.keys(current.obj).length === 0) {
|
|
136
|
+
current.parent[current.key] = [];
|
|
137
|
+
stack.pop();
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
87
142
|
// List item: "- key: value" or "- value"
|
|
88
143
|
if (trimmed.startsWith('- ')) {
|
|
89
144
|
const rest = trimmed.slice(2);
|
|
@@ -102,7 +157,14 @@ function parseSimpleYaml(content) {
|
|
|
102
157
|
} else {
|
|
103
158
|
// List of objects: "- key: value"
|
|
104
159
|
const itemKey = rest.slice(0, restColonIdx).trim();
|
|
105
|
-
if (
|
|
160
|
+
if (!itemKey) {
|
|
161
|
+
recordSkipped(i + 1, lines[i], 'Missing list item key');
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (isUnsafeKey(itemKey)) {
|
|
165
|
+
record(unsafeKeySeverity, i + 1, `Unsafe YAML key rejected: ${itemKey}`);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
106
168
|
const itemVal = rest.slice(restColonIdx + 1).trim();
|
|
107
169
|
const newObj = {};
|
|
108
170
|
if (itemVal) {
|
|
@@ -122,9 +184,19 @@ function parseSimpleYaml(content) {
|
|
|
122
184
|
}
|
|
123
185
|
|
|
124
186
|
const colonIdx = findUnquotedColon(trimmed);
|
|
125
|
-
if (colonIdx === -1)
|
|
187
|
+
if (colonIdx === -1) {
|
|
188
|
+
recordSkipped(i + 1, lines[i], 'Unparseable YAML line skipped');
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
126
191
|
const key = trimmed.slice(0, colonIdx).trim();
|
|
127
|
-
if (
|
|
192
|
+
if (!key) {
|
|
193
|
+
recordSkipped(i + 1, lines[i], 'Missing YAML key');
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (isUnsafeKey(key)) {
|
|
197
|
+
record(unsafeKeySeverity, i + 1, `Unsafe YAML key rejected: ${key}`);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
128
200
|
const valueStr = trimmed.slice(colonIdx + 1).trim();
|
|
129
201
|
|
|
130
202
|
if (!valueStr) {
|
|
@@ -140,7 +212,7 @@ function parseSimpleYaml(content) {
|
|
|
140
212
|
}
|
|
141
213
|
}
|
|
142
214
|
|
|
143
|
-
return cleanArrays(result);
|
|
215
|
+
return { data: cleanArrays(result), diagnostics };
|
|
144
216
|
}
|
|
145
217
|
|
|
146
218
|
function readBlockScalar(lines, startIndex, parentIndent, folded) {
|
|
@@ -301,4 +373,14 @@ function validate(intent) {
|
|
|
301
373
|
return errors;
|
|
302
374
|
}
|
|
303
375
|
|
|
304
|
-
module.exports = {
|
|
376
|
+
module.exports = {
|
|
377
|
+
read,
|
|
378
|
+
readAsync,
|
|
379
|
+
get,
|
|
380
|
+
validate,
|
|
381
|
+
intentPath,
|
|
382
|
+
parseSimpleYaml,
|
|
383
|
+
parseSimpleYamlWithDiagnostics,
|
|
384
|
+
diagnosticsError,
|
|
385
|
+
formatDiagnostic
|
|
386
|
+
};
|
package/lib/pillars.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
const fs = require('fs');
|
|
10
10
|
const path = require('path');
|
|
11
|
+
const frontmatterLib = require('./frontmatter');
|
|
11
12
|
|
|
12
13
|
const PILLARS_FENCE_BEGIN = '<!-- pillars:begin -->';
|
|
13
14
|
const PILLARS_FENCE_END = '<!-- pillars:end -->';
|
|
@@ -119,47 +120,7 @@ function stripQuotes(value) {
|
|
|
119
120
|
return String(value).trim().replace(/^['"]|['"]$/g, '');
|
|
120
121
|
}
|
|
121
122
|
|
|
122
|
-
|
|
123
|
-
const trimmed = value.trim();
|
|
124
|
-
if (trimmed === '[]') return [];
|
|
125
|
-
if (!trimmed.startsWith('[') || !trimmed.endsWith(']')) return null;
|
|
126
|
-
const body = trimmed.slice(1, -1).trim();
|
|
127
|
-
if (!body) return [];
|
|
128
|
-
return body.split(',').map(part => stripQuotes(part)).filter(Boolean);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function parseScalar(value) {
|
|
132
|
-
const trimmed = value.trim();
|
|
133
|
-
if (trimmed === 'true') return true;
|
|
134
|
-
if (trimmed === 'false') return false;
|
|
135
|
-
const list = parseInlineList(trimmed);
|
|
136
|
-
if (list) return list;
|
|
137
|
-
return stripQuotes(trimmed);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function parseFrontmatter(raw) {
|
|
141
|
-
if (!raw.startsWith('---\n')) return null;
|
|
142
|
-
const end = raw.indexOf('\n---', 4);
|
|
143
|
-
if (end === -1) return null;
|
|
144
|
-
|
|
145
|
-
const frontmatter = {};
|
|
146
|
-
const lines = raw.slice(4, end).split('\n');
|
|
147
|
-
let currentKey = null;
|
|
148
|
-
for (const line of lines) {
|
|
149
|
-
const match = line.match(/^([\w-]+):\s*(.*)$/);
|
|
150
|
-
if (match) {
|
|
151
|
-
currentKey = match[1];
|
|
152
|
-
frontmatter[currentKey] = parseScalar(match[2]);
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
const itemMatch = line.match(/^\s*-\s*(.+)$/);
|
|
156
|
-
if (currentKey && itemMatch) {
|
|
157
|
-
if (!Array.isArray(frontmatter[currentKey])) frontmatter[currentKey] = [];
|
|
158
|
-
frontmatter[currentKey].push(stripQuotes(itemMatch[1]));
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
return frontmatter;
|
|
162
|
-
}
|
|
123
|
+
const parseFrontmatter = (raw) => frontmatterLib.parse(raw, { strict: true });
|
|
163
124
|
|
|
164
125
|
function walkMarkdown(dir) {
|
|
165
126
|
if (!fs.existsSync(dir)) return [];
|