pgserve 2.1.3 → 2.2.1
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 +96 -0
- package/README.md +105 -1
- package/bin/autopg-wrapper.cjs +16 -0
- package/bin/pgserve-wrapper.cjs +32 -6
- package/bin/postgres-server.js +56 -0
- package/console/README.md +131 -0
- package/console/api.js +173 -0
- package/console/app.jsx +483 -0
- package/console/colors_and_type.css +227 -0
- package/console/components.jsx +167 -0
- package/console/console.css +1666 -0
- package/console/data.jsx +350 -0
- package/console/index.html +31 -0
- package/console/screens/databases.jsx +5 -0
- package/console/screens/health.jsx +5 -0
- package/console/screens/ingress.jsx +5 -0
- package/console/screens/optimizer.jsx +5 -0
- package/console/screens/rlm-sim.jsx +5 -0
- package/console/screens/rlm-trace.jsx +5 -0
- package/console/screens/security.jsx +5 -0
- package/console/screens/settings.jsx +611 -0
- package/console/screens/sql.jsx +5 -0
- package/console/screens/sync.jsx +5 -0
- package/console/screens/tables.jsx +5 -0
- package/console/tweaks-panel.jsx +425 -0
- package/package.json +14 -2
- package/scripts/postinstall.cjs +60 -0
- package/src/cli-config.cjs +310 -0
- package/src/cli-install.cjs +112 -11
- package/src/cli-restart.cjs +228 -0
- package/src/cli-ui.cjs +580 -0
- package/src/cluster.js +43 -38
- package/src/postgres.js +141 -19
- package/src/settings-loader.cjs +235 -0
- package/src/settings-migrate.cjs +212 -0
- package/src/settings-pg-args.cjs +146 -0
- package/src/settings-schema.cjs +422 -0
- package/src/settings-validator.cjs +416 -0
- package/src/settings-writer.cjs +288 -0
- package/src/upgrade/index.js +65 -0
- package/src/upgrade/runner.js +23 -0
- package/src/upgrade/steps/binary-cache-flush.js +67 -0
- package/src/upgrade/steps/consumer-signal.js +40 -0
- package/src/upgrade/steps/env-refresh.js +89 -0
- package/src/upgrade/steps/health-validate.js +53 -0
- package/src/upgrade/steps/plpgsql-resolve.js +66 -0
- package/src/upgrade/steps/port-reconcile.js +52 -0
- package/.claude/context/windows-debug.md +0 -119
- package/.genie/AGENTS.md +0 -15
- package/.genie/agents/README.md +0 -110
- package/.genie/agents/analyze.md +0 -176
- package/.genie/agents/forge.md +0 -290
- package/.genie/agents/garbage-cleaner.md +0 -324
- package/.genie/agents/garbage-collector.md +0 -596
- package/.genie/agents/github-issue-gc.md +0 -618
- package/.genie/agents/review.md +0 -380
- package/.genie/agents/semantic-analyzer/find-duplicates.md +0 -90
- package/.genie/agents/semantic-analyzer/find-orphans.md +0 -99
- package/.genie/agents/semantic-analyzer.md +0 -101
- package/.genie/agents/update.md +0 -182
- package/.genie/agents/wish.md +0 -357
- package/.genie/brainstorms/pgserve-v2/DESIGN.md +0 -174
- package/.genie/code/AGENTS.md +0 -694
- package/.genie/code/agents/audit/risk.md +0 -173
- package/.genie/code/agents/audit/security.md +0 -189
- package/.genie/code/agents/audit.md +0 -145
- package/.genie/code/agents/challenge.md +0 -230
- package/.genie/code/agents/change-reviewer.md +0 -295
- package/.genie/code/agents/code-garbage-collector.md +0 -425
- package/.genie/code/agents/code-quality.md +0 -410
- package/.genie/code/agents/commit-suggester.md +0 -255
- package/.genie/code/agents/commit.md +0 -124
- package/.genie/code/agents/consensus.md +0 -204
- package/.genie/code/agents/daily-standup.md +0 -722
- package/.genie/code/agents/docgen.md +0 -48
- package/.genie/code/agents/explore.md +0 -79
- package/.genie/code/agents/fix.md +0 -100
- package/.genie/code/agents/git/commit-advisory.md +0 -219
- package/.genie/code/agents/git/workflows/issue.md +0 -244
- package/.genie/code/agents/git/workflows/pr.md +0 -179
- package/.genie/code/agents/git/workflows/release.md +0 -460
- package/.genie/code/agents/git/workflows/report.md +0 -342
- package/.genie/code/agents/git.md +0 -432
- package/.genie/code/agents/implementor.md +0 -161
- package/.genie/code/agents/install.md +0 -515
- package/.genie/code/agents/issue-creator.md +0 -344
- package/.genie/code/agents/polish.md +0 -116
- package/.genie/code/agents/qa.md +0 -653
- package/.genie/code/agents/refactor.md +0 -294
- package/.genie/code/agents/release.md +0 -1129
- package/.genie/code/agents/roadmap.md +0 -885
- package/.genie/code/agents/tests.md +0 -557
- package/.genie/code/agents/tracer.md +0 -50
- package/.genie/code/agents/update/upstream-update.md +0 -85
- package/.genie/code/agents/update/versions/generic-update.md +0 -305
- package/.genie/code/agents/vibe.md +0 -1317
- package/.genie/code/spells/agent-configuration.md +0 -58
- package/.genie/code/spells/automated-rc-publishing.md +0 -106
- package/.genie/code/spells/branch-tracker-guidance.md +0 -28
- package/.genie/code/spells/debug.md +0 -320
- package/.genie/code/spells/emoji-naming-convention.md +0 -303
- package/.genie/code/spells/evidence-storage.md +0 -26
- package/.genie/code/spells/file-naming-rules.md +0 -35
- package/.genie/code/spells/forge-code-blueprints.md +0 -195
- package/.genie/code/spells/genie-integration.md +0 -153
- package/.genie/code/spells/publishing-protocol.md +0 -61
- package/.genie/code/spells/team-consultation-protocol.md +0 -284
- package/.genie/code/spells/tool-requirements.md +0 -20
- package/.genie/code/spells/triad-maintenance-protocol.md +0 -154
- package/.genie/code/teams/tech-council/council.md +0 -328
- package/.genie/code/teams/tech-council/jt.md +0 -352
- package/.genie/code/teams/tech-council/nayr.md +0 -305
- package/.genie/code/teams/tech-council/oettam.md +0 -375
- package/.genie/neurons/README.md +0 -193
- package/.genie/neurons/forge.md +0 -106
- package/.genie/neurons/genie.md +0 -63
- package/.genie/neurons/review.md +0 -106
- package/.genie/neurons/wish.md +0 -104
- package/.genie/product/README.md +0 -20
- package/.genie/product/cli-automation.md +0 -359
- package/.genie/product/environment.md +0 -60
- package/.genie/product/mission.md +0 -60
- package/.genie/product/roadmap.md +0 -44
- package/.genie/product/tech-stack.md +0 -34
- package/.genie/product/templates/context-template.md +0 -218
- package/.genie/product/templates/qa-done-report-template.md +0 -68
- package/.genie/product/templates/review-report-template.md +0 -89
- package/.genie/product/templates/wish-template.md +0 -120
- package/.genie/scripts/helpers/analyze-commit.js +0 -195
- package/.genie/scripts/helpers/bullet-counter.js +0 -194
- package/.genie/scripts/helpers/bullet-find.js +0 -289
- package/.genie/scripts/helpers/bullet-id.js +0 -244
- package/.genie/scripts/helpers/check-secrets.js +0 -237
- package/.genie/scripts/helpers/count-tokens.js +0 -200
- package/.genie/scripts/helpers/create-frontmatter.js +0 -456
- package/.genie/scripts/helpers/detect-markers.js +0 -293
- package/.genie/scripts/helpers/detect-todos.js +0 -267
- package/.genie/scripts/helpers/detect-unlabeled-blocks.js +0 -135
- package/.genie/scripts/helpers/embeddings.js +0 -344
- package/.genie/scripts/helpers/find-empty-sections.js +0 -158
- package/.genie/scripts/helpers/index.js +0 -319
- package/.genie/scripts/helpers/validate-frontmatter.js +0 -578
- package/.genie/scripts/helpers/validate-links.js +0 -207
- package/.genie/scripts/helpers/validate-paths.js +0 -373
- package/.genie/spells/README.md +0 -9
- package/.genie/spells/ace-protocol.md +0 -118
- package/.genie/spells/ask-one-at-a-time.md +0 -175
- package/.genie/spells/backup-analyzer.md +0 -542
- package/.genie/spells/blocker.md +0 -12
- package/.genie/spells/break-things-move-fast.md +0 -56
- package/.genie/spells/context-candidates.md +0 -72
- package/.genie/spells/context-critic.md +0 -51
- package/.genie/spells/defer-to-expertise.md +0 -278
- package/.genie/spells/delegate-dont-do.md +0 -292
- package/.genie/spells/error-investigation-protocol.md +0 -328
- package/.genie/spells/evidence-based-completion.md +0 -273
- package/.genie/spells/experiment.md +0 -65
- package/.genie/spells/file-creation-protocol.md +0 -229
- package/.genie/spells/forge-integration.md +0 -281
- package/.genie/spells/forge-orchestration.md +0 -514
- package/.genie/spells/gather-context.md +0 -18
- package/.genie/spells/global-health-check.md +0 -34
- package/.genie/spells/global-noop-roundtrip.md +0 -25
- package/.genie/spells/install-genie.md +0 -1232
- package/.genie/spells/install.md +0 -82
- package/.genie/spells/investigate-before-commit.md +0 -112
- package/.genie/spells/know-yourself.md +0 -288
- package/.genie/spells/learn.md +0 -828
- package/.genie/spells/mcp-diagnostic-protocol.md +0 -246
- package/.genie/spells/mcp-first.md +0 -124
- package/.genie/spells/multi-step-execution.md +0 -67
- package/.genie/spells/orchestration-boundary-protocol.md +0 -256
- package/.genie/spells/orchestrator-not-implementor.md +0 -189
- package/.genie/spells/prompt.md +0 -746
- package/.genie/spells/reflect.md +0 -404
- package/.genie/spells/routing-decision-matrix.md +0 -368
- package/.genie/spells/run-in-parallel.md +0 -12
- package/.genie/spells/session-state-updater-example.md +0 -196
- package/.genie/spells/session-state-updater.md +0 -220
- package/.genie/spells/track-long-running-tasks.md +0 -133
- package/.genie/spells/troubleshoot-infrastructure.md +0 -176
- package/.genie/spells/upgrade-genie.md +0 -415
- package/.genie/spells/url-presentation-protocol.md +0 -301
- package/.genie/spells/wish-initiation.md +0 -158
- package/.genie/spells/wish-issue-linkage.md +0 -410
- package/.genie/spells/wish-lifecycle.md +0 -100
- package/.genie/state/provider-status.json +0 -3
- package/.genie/state/version.json +0 -16
- package/.genie/wishes/canonical-pgserve-pm2-supervision/WISH.md +0 -290
- package/.genie/wishes/pgserve-v2/BRIEF-from-genie-pgserve.md +0 -99
- package/.genie/wishes/pgserve-v2/WISH.md +0 -442
- package/.genie/wishes/release-system-genie-pattern/WISH.md +0 -268
- package/.genie/wishes/release-system-genie-pattern/validation.md +0 -205
- package/.gitguardian.yaml +0 -29
- package/.gitguardianignore +0 -16
- package/.github/workflows/ci.yml +0 -122
- package/.github/workflows/release.yml +0 -289
- package/.github/workflows/version.yml +0 -228
- package/.husky/pre-commit +0 -2
- package/AGENTS.md +0 -433
- package/CLAUDE.md +0 -1
- package/Makefile +0 -285
- package/assets/icon.ico +0 -0
- package/bun.lock +0 -435
- package/bunfig.toml +0 -28
- package/ecosystem.config.cjs +0 -23
- package/eslint.config.js +0 -63
- package/examples/multi-tenant-demo.js +0 -104
- package/install.sh +0 -123
- package/knip.json +0 -9
- package/tests/audit.test.js +0 -189
- package/tests/backpressure.test.js +0 -167
- package/tests/benchmarks/runner.js +0 -1197
- package/tests/benchmarks/vector-generator.js +0 -368
- package/tests/cli-install.test.js +0 -322
- package/tests/control-db.test.js +0 -285
- package/tests/daemon-args.test.js +0 -86
- package/tests/daemon-control.test.js +0 -171
- package/tests/daemon-fingerprint-integration.test.js +0 -111
- package/tests/daemon-pr24-regression.test.js +0 -198
- package/tests/fingerprint.test.js +0 -263
- package/tests/fixtures/240-orphan-seed.sql +0 -30
- package/tests/multi-tenant.test.js +0 -374
- package/tests/orphan-cleanup.test.js +0 -390
- package/tests/pg-version-regex.test.js +0 -129
- package/tests/quick-bench.js +0 -135
- package/tests/router-handshake-retry.test.js +0 -119
- package/tests/router-handshake-watchdog.test.js +0 -110
- package/tests/sdk.test.js +0 -71
- package/tests/stale-postmaster-pid.test.js +0 -85
- package/tests/stress-test.js +0 -439
- package/tests/sync-perf-test.js +0 -150
- package/tests/tcp-listen.test.js +0 -368
- package/tests/tenancy.test.js +0 -403
- package/tests/wrapper-supervision.test.js +0 -107
|
@@ -1,578 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Frontmatter Validator for Genie Framework
|
|
5
|
-
*
|
|
6
|
-
* Validates .md file frontmatter across Genie framework:
|
|
7
|
-
* - YAML syntax validation
|
|
8
|
-
* - Required fields presence
|
|
9
|
-
* - Amendment 7 violations (version, timestamps)
|
|
10
|
-
* - Proper delimiter structure
|
|
11
|
-
*
|
|
12
|
-
* Usage:
|
|
13
|
-
* node validate-frontmatter.js [path]
|
|
14
|
-
* (defaults to .genie/ if no path provided)
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
const fs = require('fs');
|
|
18
|
-
const path = require('path');
|
|
19
|
-
const yaml = require('yaml');
|
|
20
|
-
const { execSync } = require('child_process');
|
|
21
|
-
|
|
22
|
-
// Configuration
|
|
23
|
-
const REQUIRED_FIELDS = {
|
|
24
|
-
agent: ['name', 'description', 'genie'],
|
|
25
|
-
spell: ['name', 'description'],
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const FORBIDDEN_FIELDS = ['version', 'last_updated', 'author'];
|
|
29
|
-
|
|
30
|
-
// Valid Claude models
|
|
31
|
-
const VALID_CLAUDE_MODELS = ['haiku', 'sonnet', 'opus-4'];
|
|
32
|
-
|
|
33
|
-
// NOTE: Executor names are NOT validated against a hardcoded list
|
|
34
|
-
// They are fetched dynamically from Forge via AgentRegistry.getSupportedExecutors()
|
|
35
|
-
// Validation here only checks format (uppercase). Runtime validation happens in Forge.
|
|
36
|
-
|
|
37
|
-
// Cache for opencode models (fetched once)
|
|
38
|
-
let OPENCODE_MODELS_CACHE = null;
|
|
39
|
-
|
|
40
|
-
// Results tracking
|
|
41
|
-
const results = {
|
|
42
|
-
totalFiles: 0,
|
|
43
|
-
scannedFiles: 0,
|
|
44
|
-
validFiles: 0,
|
|
45
|
-
issues: [],
|
|
46
|
-
skipped: [],
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Fetch valid OpenCode models from `opencode models` command
|
|
51
|
-
*/
|
|
52
|
-
async function getOpenCodeModels() {
|
|
53
|
-
if (OPENCODE_MODELS_CACHE !== null) {
|
|
54
|
-
return OPENCODE_MODELS_CACHE;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
try {
|
|
58
|
-
const output = execSync('opencode models', {
|
|
59
|
-
encoding: 'utf-8',
|
|
60
|
-
stdio: ['pipe', 'pipe', 'ignore'] // Suppress stderr
|
|
61
|
-
});
|
|
62
|
-
OPENCODE_MODELS_CACHE = output
|
|
63
|
-
.split('\n')
|
|
64
|
-
.map(line => line.trim())
|
|
65
|
-
.filter(line => line.length > 0);
|
|
66
|
-
return OPENCODE_MODELS_CACHE;
|
|
67
|
-
} catch (err) {
|
|
68
|
-
console.warn('⚠️ Could not fetch opencode models (is opencode installed?). Skipping OpenCode model validation.');
|
|
69
|
-
OPENCODE_MODELS_CACHE = []; // Empty cache = skip validation
|
|
70
|
-
return OPENCODE_MODELS_CACHE;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Check if file should be scanned
|
|
76
|
-
*/
|
|
77
|
-
function shouldScan(filePath) {
|
|
78
|
-
const excludePatterns = [
|
|
79
|
-
'node_modules',
|
|
80
|
-
'.git',
|
|
81
|
-
'dist',
|
|
82
|
-
'build',
|
|
83
|
-
'coverage',
|
|
84
|
-
'/backups/', // Exclude backup directories
|
|
85
|
-
'README.md', // README files don't need frontmatter
|
|
86
|
-
'SETUP-', // Setup guide files
|
|
87
|
-
];
|
|
88
|
-
|
|
89
|
-
return !excludePatterns.some(pattern => filePath.includes(pattern));
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Extract frontmatter from markdown content
|
|
94
|
-
*/
|
|
95
|
-
function extractFrontmatter(content, filePath) {
|
|
96
|
-
const lines = content.split('\n');
|
|
97
|
-
|
|
98
|
-
// Check if file starts with frontmatter delimiter
|
|
99
|
-
if (lines[0] !== '---') {
|
|
100
|
-
return { hasFrontmatter: false };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Find closing delimiter
|
|
104
|
-
let closingIndex = -1;
|
|
105
|
-
for (let i = 1; i < lines.length; i++) {
|
|
106
|
-
if (lines[i] === '---') {
|
|
107
|
-
closingIndex = i;
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (closingIndex === -1) {
|
|
113
|
-
return {
|
|
114
|
-
hasFrontmatter: true,
|
|
115
|
-
error: 'Missing closing frontmatter delimiter (---)',
|
|
116
|
-
line: 1,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Extract YAML content
|
|
121
|
-
const yamlContent = lines.slice(1, closingIndex).join('\n');
|
|
122
|
-
|
|
123
|
-
try {
|
|
124
|
-
const parsed = yaml.parse(yamlContent);
|
|
125
|
-
return {
|
|
126
|
-
hasFrontmatter: true,
|
|
127
|
-
frontmatter: parsed,
|
|
128
|
-
yamlContent,
|
|
129
|
-
closingLine: closingIndex + 1,
|
|
130
|
-
};
|
|
131
|
-
} catch (err) {
|
|
132
|
-
return {
|
|
133
|
-
hasFrontmatter: true,
|
|
134
|
-
error: `Invalid YAML syntax: ${err.message}`,
|
|
135
|
-
line: 1,
|
|
136
|
-
yamlContent,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Detect file type (agent, spell, etc.)
|
|
143
|
-
*/
|
|
144
|
-
function detectFileType(filePath) {
|
|
145
|
-
if (filePath.includes('/agents/')) return 'agent';
|
|
146
|
-
if (filePath.includes('/spells/')) return 'spell';
|
|
147
|
-
return 'other';
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Validate frontmatter fields
|
|
152
|
-
*/
|
|
153
|
-
async function validateFields(frontmatter, fileType, filePath) {
|
|
154
|
-
const issues = [];
|
|
155
|
-
|
|
156
|
-
// Check required fields
|
|
157
|
-
const required = REQUIRED_FIELDS[fileType] || [];
|
|
158
|
-
for (const field of required) {
|
|
159
|
-
if (!frontmatter[field]) {
|
|
160
|
-
issues.push({
|
|
161
|
-
type: 'missing_required',
|
|
162
|
-
field,
|
|
163
|
-
message: `Missing required field: ${field}`,
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Check forbidden fields (Amendment 7)
|
|
169
|
-
for (const field of FORBIDDEN_FIELDS) {
|
|
170
|
-
if (frontmatter[field]) {
|
|
171
|
-
issues.push({
|
|
172
|
-
type: 'amendment_7_violation',
|
|
173
|
-
field,
|
|
174
|
-
message: `Forbidden field (Amendment 7): ${field}`,
|
|
175
|
-
suggestion: 'Remove (git tracks this)',
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Check for nested genie structure (agents only)
|
|
181
|
-
if (fileType === 'agent' && frontmatter.genie) {
|
|
182
|
-
if (typeof frontmatter.genie !== 'object') {
|
|
183
|
-
issues.push({
|
|
184
|
-
type: 'invalid_structure',
|
|
185
|
-
field: 'genie',
|
|
186
|
-
message: 'genie field must be an object',
|
|
187
|
-
});
|
|
188
|
-
} else {
|
|
189
|
-
// Validate genie.executor and genie.model
|
|
190
|
-
const genieIssues = await validateGenieFields(frontmatter.genie);
|
|
191
|
-
issues.push(...genieIssues);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Check for empty required fields (present but empty/null)
|
|
196
|
-
for (const field of required) {
|
|
197
|
-
if (frontmatter[field] !== undefined && !frontmatter[field]) {
|
|
198
|
-
issues.push({
|
|
199
|
-
type: 'empty_required',
|
|
200
|
-
field,
|
|
201
|
-
message: `Required field is empty: ${field}`,
|
|
202
|
-
suggestion: 'Provide a value or remove the field',
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return issues;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Validate genie.executor and genie.model fields
|
|
212
|
-
*/
|
|
213
|
-
async function validateGenieFields(genie) {
|
|
214
|
-
const issues = [];
|
|
215
|
-
const executor = genie.executor;
|
|
216
|
-
const model = genie.model;
|
|
217
|
-
const variant = genie.executorVariant || genie.variant || genie.executor_variant || genie.executorProfile;
|
|
218
|
-
|
|
219
|
-
// Validate executor format (should be uppercase)
|
|
220
|
-
// NOTE: We don't validate against a hardcoded list - executors are dynamic in Forge
|
|
221
|
-
// AgentRegistry.getSupportedExecutors() fetches the current list from Forge
|
|
222
|
-
if (executor) {
|
|
223
|
-
if (executor !== executor.toUpperCase()) {
|
|
224
|
-
issues.push({
|
|
225
|
-
type: 'executor_case',
|
|
226
|
-
field: 'genie.executor',
|
|
227
|
-
message: `Executor should be uppercase (Forge format): ${executor}`,
|
|
228
|
-
suggestion: `Change to: ${executor.toUpperCase()}`,
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Warn about executorVariant (deprecated field)
|
|
234
|
-
if (variant) {
|
|
235
|
-
issues.push({
|
|
236
|
-
type: 'deprecated_field',
|
|
237
|
-
field: 'genie.executorVariant',
|
|
238
|
-
message: `executorVariant is deprecated (conflicts with Forge profile-per-agent pattern)`,
|
|
239
|
-
suggestion: `Remove this field - Forge creates one profile per agent automatically`,
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Validate model based on executor
|
|
244
|
-
if (executor && model) {
|
|
245
|
-
if (executor === 'CLAUDE_CODE') {
|
|
246
|
-
// Claude models: haiku, sonnet, opus-4
|
|
247
|
-
if (!VALID_CLAUDE_MODELS.includes(model)) {
|
|
248
|
-
issues.push({
|
|
249
|
-
type: 'invalid_claude_model',
|
|
250
|
-
field: 'genie.model',
|
|
251
|
-
message: `Invalid Claude model: ${model}`,
|
|
252
|
-
suggestion: `Valid Claude models: ${VALID_CLAUDE_MODELS.join(', ')}`,
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
} else if (executor === 'OPENCODE') {
|
|
256
|
-
// OpenCode models: must be in `opencode models` output
|
|
257
|
-
const validModels = await getOpenCodeModels();
|
|
258
|
-
if (validModels.length > 0 && !validModels.includes(model)) {
|
|
259
|
-
issues.push({
|
|
260
|
-
type: 'invalid_opencode_model',
|
|
261
|
-
field: 'genie.model',
|
|
262
|
-
message: `OpenCode model not found: ${model}`,
|
|
263
|
-
suggestion: `Run 'opencode models' to see valid models`,
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Validate executor-specific permission flags (from Forge API 2025-10-26)
|
|
270
|
-
// See: .genie/product/docs/executor-configuration.md
|
|
271
|
-
const permissionIssues = validatePermissionFlags(genie, executor);
|
|
272
|
-
issues.push(...permissionIssues);
|
|
273
|
-
|
|
274
|
-
return issues;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Validate executor-specific permission flags
|
|
279
|
-
* Based on live Forge API query 2025-10-26
|
|
280
|
-
*/
|
|
281
|
-
function validatePermissionFlags(genie, executor) {
|
|
282
|
-
const issues = [];
|
|
283
|
-
|
|
284
|
-
// Permission flags by executor (from Forge DEFAULT profiles)
|
|
285
|
-
const EXECUTOR_PERMISSION_FLAGS = {
|
|
286
|
-
CLAUDE_CODE: ['dangerously_skip_permissions'],
|
|
287
|
-
CODEX: ['sandbox'],
|
|
288
|
-
AMP: ['dangerously_allow_all'],
|
|
289
|
-
OPENCODE: [], // No permission flags
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
const VALID_PERMISSION_FLAGS = {
|
|
293
|
-
dangerously_skip_permissions: { executor: 'CLAUDE_CODE', type: 'boolean' },
|
|
294
|
-
sandbox: { executor: 'CODEX', type: 'string', values: ['danger-full-access', 'read-only', 'safe'] },
|
|
295
|
-
dangerously_allow_all: { executor: 'AMP', type: 'boolean' },
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
const VALID_ADDITIONAL_FIELDS = {
|
|
299
|
-
model_reasoning_effort: { executors: ['CODEX'], type: 'string', values: ['low', 'medium', 'high'] },
|
|
300
|
-
};
|
|
301
|
-
|
|
302
|
-
// Check for permission flags in frontmatter
|
|
303
|
-
for (const [flag, config] of Object.entries(VALID_PERMISSION_FLAGS)) {
|
|
304
|
-
if (genie[flag] !== undefined) {
|
|
305
|
-
// Flag is present - check if it matches the executor
|
|
306
|
-
if (executor !== config.executor) {
|
|
307
|
-
issues.push({
|
|
308
|
-
type: 'wrong_executor_permission_flag',
|
|
309
|
-
field: `genie.${flag}`,
|
|
310
|
-
message: `Permission flag '${flag}' is for ${config.executor}, but executor is ${executor}`,
|
|
311
|
-
suggestion: executor ?
|
|
312
|
-
`Remove this flag or use ${executor}-specific flag: ${EXECUTOR_PERMISSION_FLAGS[executor]?.join(', ') || 'none'}` :
|
|
313
|
-
`Remove this flag or set executor to ${config.executor}`,
|
|
314
|
-
});
|
|
315
|
-
} else {
|
|
316
|
-
// Correct executor - validate value type
|
|
317
|
-
if (config.type === 'boolean' && typeof genie[flag] !== 'boolean') {
|
|
318
|
-
issues.push({
|
|
319
|
-
type: 'invalid_permission_flag_type',
|
|
320
|
-
field: `genie.${flag}`,
|
|
321
|
-
message: `${flag} must be a boolean (true or false)`,
|
|
322
|
-
suggestion: `Change to: true or false (no quotes)`,
|
|
323
|
-
});
|
|
324
|
-
} else if (config.type === 'string' && typeof genie[flag] !== 'string') {
|
|
325
|
-
issues.push({
|
|
326
|
-
type: 'invalid_permission_flag_type',
|
|
327
|
-
field: `genie.${flag}`,
|
|
328
|
-
message: `${flag} must be a string`,
|
|
329
|
-
suggestion: `Valid values: ${config.values.join(', ')}`,
|
|
330
|
-
});
|
|
331
|
-
} else if (config.values && !config.values.includes(genie[flag])) {
|
|
332
|
-
issues.push({
|
|
333
|
-
type: 'invalid_permission_flag_value',
|
|
334
|
-
field: `genie.${flag}`,
|
|
335
|
-
message: `Invalid value for ${flag}: '${genie[flag]}'`,
|
|
336
|
-
suggestion: `Valid values: ${config.values.join(', ')}`,
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Validate additional executor-specific fields
|
|
344
|
-
for (const [field, config] of Object.entries(VALID_ADDITIONAL_FIELDS)) {
|
|
345
|
-
if (genie[field] !== undefined) {
|
|
346
|
-
// Check if field is valid for this executor
|
|
347
|
-
if (executor && !config.executors.includes(executor)) {
|
|
348
|
-
issues.push({
|
|
349
|
-
type: 'wrong_executor_field',
|
|
350
|
-
field: `genie.${field}`,
|
|
351
|
-
message: `Field '${field}' is only valid for ${config.executors.join(', ')}, but executor is ${executor}`,
|
|
352
|
-
suggestion: `Remove this field or use one of: ${config.executors.join(', ')}`,
|
|
353
|
-
});
|
|
354
|
-
} else {
|
|
355
|
-
// Validate value type and values
|
|
356
|
-
if (config.type === 'string' && typeof genie[field] !== 'string') {
|
|
357
|
-
issues.push({
|
|
358
|
-
type: 'invalid_field_type',
|
|
359
|
-
field: `genie.${field}`,
|
|
360
|
-
message: `${field} must be a string`,
|
|
361
|
-
suggestion: `Valid values: ${config.values.join(', ')}`,
|
|
362
|
-
});
|
|
363
|
-
} else if (config.values && !config.values.includes(genie[field])) {
|
|
364
|
-
issues.push({
|
|
365
|
-
type: 'invalid_field_value',
|
|
366
|
-
field: `genie.${field}`,
|
|
367
|
-
message: `Invalid value for ${field}: '${genie[field]}'`,
|
|
368
|
-
suggestion: `Valid values: ${config.values.join(', ')}`,
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Warn about deprecated 'additional_params' usage
|
|
376
|
-
if (genie.additional_params !== undefined) {
|
|
377
|
-
issues.push({
|
|
378
|
-
type: 'deprecated_field',
|
|
379
|
-
field: 'genie.additional_params',
|
|
380
|
-
message: `additional_params is not used by Forge (defaults to empty array)`,
|
|
381
|
-
suggestion: `Remove this field and use executor-specific permission flags instead`,
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
return issues;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Scan single markdown file
|
|
390
|
-
*/
|
|
391
|
-
async function scanFile(filePath) {
|
|
392
|
-
results.totalFiles++;
|
|
393
|
-
|
|
394
|
-
if (!shouldScan(filePath)) {
|
|
395
|
-
results.skipped.push({ file: filePath, reason: 'Excluded path' });
|
|
396
|
-
return;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
try {
|
|
400
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
401
|
-
const extracted = extractFrontmatter(content, filePath);
|
|
402
|
-
|
|
403
|
-
results.scannedFiles++;
|
|
404
|
-
|
|
405
|
-
// No frontmatter (may be valid for some files)
|
|
406
|
-
if (!extracted.hasFrontmatter) {
|
|
407
|
-
const fileType = detectFileType(filePath);
|
|
408
|
-
if (fileType === 'agent' || fileType === 'spell') {
|
|
409
|
-
results.issues.push({
|
|
410
|
-
file: filePath,
|
|
411
|
-
line: 1,
|
|
412
|
-
type: 'missing_frontmatter',
|
|
413
|
-
message: `${fileType} file missing frontmatter`,
|
|
414
|
-
severity: 'error',
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Frontmatter extraction error
|
|
421
|
-
if (extracted.error) {
|
|
422
|
-
results.issues.push({
|
|
423
|
-
file: filePath,
|
|
424
|
-
line: extracted.line,
|
|
425
|
-
type: 'invalid_frontmatter',
|
|
426
|
-
message: extracted.error,
|
|
427
|
-
severity: 'error',
|
|
428
|
-
yaml: extracted.yamlContent,
|
|
429
|
-
});
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
// Validate fields
|
|
434
|
-
const fileType = detectFileType(filePath);
|
|
435
|
-
const fieldIssues = await validateFields(extracted.frontmatter, fileType, filePath);
|
|
436
|
-
|
|
437
|
-
if (fieldIssues.length > 0) {
|
|
438
|
-
fieldIssues.forEach(issue => {
|
|
439
|
-
results.issues.push({
|
|
440
|
-
file: filePath,
|
|
441
|
-
line: 2, // Approximate (inside frontmatter)
|
|
442
|
-
type: issue.type,
|
|
443
|
-
field: issue.field,
|
|
444
|
-
message: issue.message,
|
|
445
|
-
suggestion: issue.suggestion,
|
|
446
|
-
severity: [
|
|
447
|
-
'amendment_7_violation',
|
|
448
|
-
'deprecated_field',
|
|
449
|
-
'executor_case',
|
|
450
|
-
'wrong_executor_permission_flag',
|
|
451
|
-
'invalid_permission_flag_type',
|
|
452
|
-
'invalid_permission_flag_value',
|
|
453
|
-
'wrong_executor_field',
|
|
454
|
-
'invalid_field_type',
|
|
455
|
-
'invalid_field_value'
|
|
456
|
-
].includes(issue.type) ? 'warning' : 'error',
|
|
457
|
-
});
|
|
458
|
-
});
|
|
459
|
-
} else {
|
|
460
|
-
results.validFiles++;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
} catch (err) {
|
|
464
|
-
results.issues.push({
|
|
465
|
-
file: filePath,
|
|
466
|
-
type: 'read_error',
|
|
467
|
-
message: `Failed to read file: ${err.message}`,
|
|
468
|
-
severity: 'error',
|
|
469
|
-
});
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
* Recursively scan directory
|
|
475
|
-
*/
|
|
476
|
-
async function scanDirectory(dir) {
|
|
477
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
478
|
-
|
|
479
|
-
for (const entry of entries) {
|
|
480
|
-
const fullPath = path.join(dir, entry.name);
|
|
481
|
-
|
|
482
|
-
if (entry.isDirectory()) {
|
|
483
|
-
await scanDirectory(fullPath);
|
|
484
|
-
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
485
|
-
await scanFile(fullPath);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Generate report
|
|
492
|
-
*/
|
|
493
|
-
function generateReport() {
|
|
494
|
-
console.log('\n=== Frontmatter Validation Report ===\n');
|
|
495
|
-
|
|
496
|
-
console.log(`Total files: ${results.totalFiles}`);
|
|
497
|
-
console.log(`Scanned: ${results.scannedFiles}`);
|
|
498
|
-
console.log(`Valid: ${results.validFiles}`);
|
|
499
|
-
console.log(`Issues found: ${results.issues.length}`);
|
|
500
|
-
console.log(`Skipped: ${results.skipped.length}\n`);
|
|
501
|
-
|
|
502
|
-
if (results.issues.length === 0) {
|
|
503
|
-
console.log('✅ No frontmatter issues found!\n');
|
|
504
|
-
return 0;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// Group issues by severity
|
|
508
|
-
const errors = results.issues.filter(i => i.severity === 'error');
|
|
509
|
-
const warnings = results.issues.filter(i => i.severity === 'warning');
|
|
510
|
-
|
|
511
|
-
if (errors.length > 0) {
|
|
512
|
-
console.log(`🔴 ERRORS (${errors.length}):\n`);
|
|
513
|
-
errors.forEach(issue => {
|
|
514
|
-
console.log(` ${issue.file}:${issue.line || '?'}`);
|
|
515
|
-
console.log(` Type: ${issue.type}`);
|
|
516
|
-
console.log(` Message: ${issue.message}`);
|
|
517
|
-
if (issue.field) console.log(` Field: ${issue.field}`);
|
|
518
|
-
if (issue.suggestion) console.log(` Suggestion: ${issue.suggestion}`);
|
|
519
|
-
if (issue.yaml) console.log(` YAML:\n${issue.yaml.split('\n').map(l => ' ' + l).join('\n')}`);
|
|
520
|
-
console.log('');
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
if (warnings.length > 0) {
|
|
525
|
-
console.log(`⚠️ WARNINGS (${warnings.length}):\n`);
|
|
526
|
-
warnings.forEach(issue => {
|
|
527
|
-
console.log(` ${issue.file}:${issue.line || '?'}`);
|
|
528
|
-
console.log(` Type: ${issue.type}`);
|
|
529
|
-
console.log(` Message: ${issue.message}`);
|
|
530
|
-
if (issue.field) console.log(` Field: ${issue.field}`);
|
|
531
|
-
if (issue.suggestion) console.log(` Suggestion: ${issue.suggestion}`);
|
|
532
|
-
console.log('');
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
// Summary
|
|
537
|
-
console.log('\n=== Summary by Issue Type ===\n');
|
|
538
|
-
const byType = {};
|
|
539
|
-
results.issues.forEach(issue => {
|
|
540
|
-
byType[issue.type] = (byType[issue.type] || 0) + 1;
|
|
541
|
-
});
|
|
542
|
-
|
|
543
|
-
Object.entries(byType).forEach(([type, count]) => {
|
|
544
|
-
console.log(` ${type}: ${count}`);
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
console.log('');
|
|
548
|
-
return errors.length > 0 ? 1 : 0;
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
/**
|
|
552
|
-
* Main
|
|
553
|
-
*/
|
|
554
|
-
async function main() {
|
|
555
|
-
const targetPath = process.argv[2] || '.genie';
|
|
556
|
-
|
|
557
|
-
if (!fs.existsSync(targetPath)) {
|
|
558
|
-
console.error(`Error: Path not found: ${targetPath}`);
|
|
559
|
-
process.exit(1);
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
console.log(`Scanning: ${targetPath}\n`);
|
|
563
|
-
|
|
564
|
-
const stat = fs.statSync(targetPath);
|
|
565
|
-
if (stat.isDirectory()) {
|
|
566
|
-
await scanDirectory(targetPath);
|
|
567
|
-
} else if (targetPath.endsWith('.md')) {
|
|
568
|
-
await scanFile(targetPath);
|
|
569
|
-
} else {
|
|
570
|
-
console.error('Error: Target must be a directory or .md file');
|
|
571
|
-
process.exit(1);
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
const exitCode = generateReport();
|
|
575
|
-
process.exit(exitCode);
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
main();
|