pgserve 2.1.2 → 2.2.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.
Files changed (227) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +105 -1
  3. package/bin/autopg-wrapper.cjs +16 -0
  4. package/bin/pgserve-wrapper.cjs +31 -6
  5. package/bin/postgres-server.js +80 -7
  6. package/console/README.md +131 -0
  7. package/console/api.js +173 -0
  8. package/console/app.jsx +483 -0
  9. package/console/colors_and_type.css +227 -0
  10. package/console/components.jsx +167 -0
  11. package/console/console.css +1666 -0
  12. package/console/data.jsx +350 -0
  13. package/console/index.html +31 -0
  14. package/console/screens/databases.jsx +5 -0
  15. package/console/screens/health.jsx +5 -0
  16. package/console/screens/ingress.jsx +5 -0
  17. package/console/screens/optimizer.jsx +5 -0
  18. package/console/screens/rlm-sim.jsx +5 -0
  19. package/console/screens/rlm-trace.jsx +5 -0
  20. package/console/screens/security.jsx +5 -0
  21. package/console/screens/settings.jsx +611 -0
  22. package/console/screens/sql.jsx +5 -0
  23. package/console/screens/sync.jsx +5 -0
  24. package/console/screens/tables.jsx +5 -0
  25. package/console/tweaks-panel.jsx +425 -0
  26. package/package.json +11 -1
  27. package/src/cli-config.cjs +310 -0
  28. package/src/cli-install.cjs +98 -11
  29. package/src/cli-restart.cjs +228 -0
  30. package/src/cli-ui.cjs +580 -0
  31. package/src/cluster.js +43 -38
  32. package/src/postgres.js +141 -19
  33. package/src/settings-loader.cjs +235 -0
  34. package/src/settings-migrate.cjs +212 -0
  35. package/src/settings-pg-args.cjs +146 -0
  36. package/src/settings-schema.cjs +422 -0
  37. package/src/settings-validator.cjs +416 -0
  38. package/src/settings-writer.cjs +288 -0
  39. package/.claude/context/windows-debug.md +0 -119
  40. package/.genie/AGENTS.md +0 -15
  41. package/.genie/agents/README.md +0 -110
  42. package/.genie/agents/analyze.md +0 -176
  43. package/.genie/agents/forge.md +0 -290
  44. package/.genie/agents/garbage-cleaner.md +0 -324
  45. package/.genie/agents/garbage-collector.md +0 -596
  46. package/.genie/agents/github-issue-gc.md +0 -618
  47. package/.genie/agents/review.md +0 -380
  48. package/.genie/agents/semantic-analyzer/find-duplicates.md +0 -90
  49. package/.genie/agents/semantic-analyzer/find-orphans.md +0 -99
  50. package/.genie/agents/semantic-analyzer.md +0 -101
  51. package/.genie/agents/update.md +0 -182
  52. package/.genie/agents/wish.md +0 -357
  53. package/.genie/brainstorms/pgserve-v2/DESIGN.md +0 -174
  54. package/.genie/code/AGENTS.md +0 -694
  55. package/.genie/code/agents/audit/risk.md +0 -173
  56. package/.genie/code/agents/audit/security.md +0 -189
  57. package/.genie/code/agents/audit.md +0 -145
  58. package/.genie/code/agents/challenge.md +0 -230
  59. package/.genie/code/agents/change-reviewer.md +0 -295
  60. package/.genie/code/agents/code-garbage-collector.md +0 -425
  61. package/.genie/code/agents/code-quality.md +0 -410
  62. package/.genie/code/agents/commit-suggester.md +0 -255
  63. package/.genie/code/agents/commit.md +0 -124
  64. package/.genie/code/agents/consensus.md +0 -204
  65. package/.genie/code/agents/daily-standup.md +0 -722
  66. package/.genie/code/agents/docgen.md +0 -48
  67. package/.genie/code/agents/explore.md +0 -79
  68. package/.genie/code/agents/fix.md +0 -100
  69. package/.genie/code/agents/git/commit-advisory.md +0 -219
  70. package/.genie/code/agents/git/workflows/issue.md +0 -244
  71. package/.genie/code/agents/git/workflows/pr.md +0 -179
  72. package/.genie/code/agents/git/workflows/release.md +0 -460
  73. package/.genie/code/agents/git/workflows/report.md +0 -342
  74. package/.genie/code/agents/git.md +0 -432
  75. package/.genie/code/agents/implementor.md +0 -161
  76. package/.genie/code/agents/install.md +0 -515
  77. package/.genie/code/agents/issue-creator.md +0 -344
  78. package/.genie/code/agents/polish.md +0 -116
  79. package/.genie/code/agents/qa.md +0 -653
  80. package/.genie/code/agents/refactor.md +0 -294
  81. package/.genie/code/agents/release.md +0 -1129
  82. package/.genie/code/agents/roadmap.md +0 -885
  83. package/.genie/code/agents/tests.md +0 -557
  84. package/.genie/code/agents/tracer.md +0 -50
  85. package/.genie/code/agents/update/upstream-update.md +0 -85
  86. package/.genie/code/agents/update/versions/generic-update.md +0 -305
  87. package/.genie/code/agents/vibe.md +0 -1317
  88. package/.genie/code/spells/agent-configuration.md +0 -58
  89. package/.genie/code/spells/automated-rc-publishing.md +0 -106
  90. package/.genie/code/spells/branch-tracker-guidance.md +0 -28
  91. package/.genie/code/spells/debug.md +0 -320
  92. package/.genie/code/spells/emoji-naming-convention.md +0 -303
  93. package/.genie/code/spells/evidence-storage.md +0 -26
  94. package/.genie/code/spells/file-naming-rules.md +0 -35
  95. package/.genie/code/spells/forge-code-blueprints.md +0 -195
  96. package/.genie/code/spells/genie-integration.md +0 -153
  97. package/.genie/code/spells/publishing-protocol.md +0 -61
  98. package/.genie/code/spells/team-consultation-protocol.md +0 -284
  99. package/.genie/code/spells/tool-requirements.md +0 -20
  100. package/.genie/code/spells/triad-maintenance-protocol.md +0 -154
  101. package/.genie/code/teams/tech-council/council.md +0 -328
  102. package/.genie/code/teams/tech-council/jt.md +0 -352
  103. package/.genie/code/teams/tech-council/nayr.md +0 -305
  104. package/.genie/code/teams/tech-council/oettam.md +0 -375
  105. package/.genie/neurons/README.md +0 -193
  106. package/.genie/neurons/forge.md +0 -106
  107. package/.genie/neurons/genie.md +0 -63
  108. package/.genie/neurons/review.md +0 -106
  109. package/.genie/neurons/wish.md +0 -104
  110. package/.genie/product/README.md +0 -20
  111. package/.genie/product/cli-automation.md +0 -359
  112. package/.genie/product/environment.md +0 -60
  113. package/.genie/product/mission.md +0 -60
  114. package/.genie/product/roadmap.md +0 -44
  115. package/.genie/product/tech-stack.md +0 -34
  116. package/.genie/product/templates/context-template.md +0 -218
  117. package/.genie/product/templates/qa-done-report-template.md +0 -68
  118. package/.genie/product/templates/review-report-template.md +0 -89
  119. package/.genie/product/templates/wish-template.md +0 -120
  120. package/.genie/scripts/helpers/analyze-commit.js +0 -195
  121. package/.genie/scripts/helpers/bullet-counter.js +0 -194
  122. package/.genie/scripts/helpers/bullet-find.js +0 -289
  123. package/.genie/scripts/helpers/bullet-id.js +0 -244
  124. package/.genie/scripts/helpers/check-secrets.js +0 -237
  125. package/.genie/scripts/helpers/count-tokens.js +0 -200
  126. package/.genie/scripts/helpers/create-frontmatter.js +0 -456
  127. package/.genie/scripts/helpers/detect-markers.js +0 -293
  128. package/.genie/scripts/helpers/detect-todos.js +0 -267
  129. package/.genie/scripts/helpers/detect-unlabeled-blocks.js +0 -135
  130. package/.genie/scripts/helpers/embeddings.js +0 -344
  131. package/.genie/scripts/helpers/find-empty-sections.js +0 -158
  132. package/.genie/scripts/helpers/index.js +0 -319
  133. package/.genie/scripts/helpers/validate-frontmatter.js +0 -578
  134. package/.genie/scripts/helpers/validate-links.js +0 -207
  135. package/.genie/scripts/helpers/validate-paths.js +0 -373
  136. package/.genie/spells/README.md +0 -9
  137. package/.genie/spells/ace-protocol.md +0 -118
  138. package/.genie/spells/ask-one-at-a-time.md +0 -175
  139. package/.genie/spells/backup-analyzer.md +0 -542
  140. package/.genie/spells/blocker.md +0 -12
  141. package/.genie/spells/break-things-move-fast.md +0 -56
  142. package/.genie/spells/context-candidates.md +0 -72
  143. package/.genie/spells/context-critic.md +0 -51
  144. package/.genie/spells/defer-to-expertise.md +0 -278
  145. package/.genie/spells/delegate-dont-do.md +0 -292
  146. package/.genie/spells/error-investigation-protocol.md +0 -328
  147. package/.genie/spells/evidence-based-completion.md +0 -273
  148. package/.genie/spells/experiment.md +0 -65
  149. package/.genie/spells/file-creation-protocol.md +0 -229
  150. package/.genie/spells/forge-integration.md +0 -281
  151. package/.genie/spells/forge-orchestration.md +0 -514
  152. package/.genie/spells/gather-context.md +0 -18
  153. package/.genie/spells/global-health-check.md +0 -34
  154. package/.genie/spells/global-noop-roundtrip.md +0 -25
  155. package/.genie/spells/install-genie.md +0 -1232
  156. package/.genie/spells/install.md +0 -82
  157. package/.genie/spells/investigate-before-commit.md +0 -112
  158. package/.genie/spells/know-yourself.md +0 -288
  159. package/.genie/spells/learn.md +0 -828
  160. package/.genie/spells/mcp-diagnostic-protocol.md +0 -246
  161. package/.genie/spells/mcp-first.md +0 -124
  162. package/.genie/spells/multi-step-execution.md +0 -67
  163. package/.genie/spells/orchestration-boundary-protocol.md +0 -256
  164. package/.genie/spells/orchestrator-not-implementor.md +0 -189
  165. package/.genie/spells/prompt.md +0 -746
  166. package/.genie/spells/reflect.md +0 -404
  167. package/.genie/spells/routing-decision-matrix.md +0 -368
  168. package/.genie/spells/run-in-parallel.md +0 -12
  169. package/.genie/spells/session-state-updater-example.md +0 -196
  170. package/.genie/spells/session-state-updater.md +0 -220
  171. package/.genie/spells/track-long-running-tasks.md +0 -133
  172. package/.genie/spells/troubleshoot-infrastructure.md +0 -176
  173. package/.genie/spells/upgrade-genie.md +0 -415
  174. package/.genie/spells/url-presentation-protocol.md +0 -301
  175. package/.genie/spells/wish-initiation.md +0 -158
  176. package/.genie/spells/wish-issue-linkage.md +0 -410
  177. package/.genie/spells/wish-lifecycle.md +0 -100
  178. package/.genie/state/provider-status.json +0 -3
  179. package/.genie/state/version.json +0 -16
  180. package/.genie/wishes/canonical-pgserve-pm2-supervision/WISH.md +0 -290
  181. package/.genie/wishes/pgserve-v2/BRIEF-from-genie-pgserve.md +0 -99
  182. package/.genie/wishes/pgserve-v2/WISH.md +0 -442
  183. package/.genie/wishes/release-system-genie-pattern/WISH.md +0 -268
  184. package/.genie/wishes/release-system-genie-pattern/validation.md +0 -205
  185. package/.gitguardian.yaml +0 -29
  186. package/.gitguardianignore +0 -16
  187. package/.github/workflows/ci.yml +0 -122
  188. package/.github/workflows/release.yml +0 -289
  189. package/.github/workflows/version.yml +0 -228
  190. package/.husky/pre-commit +0 -2
  191. package/AGENTS.md +0 -433
  192. package/CLAUDE.md +0 -1
  193. package/Makefile +0 -285
  194. package/assets/icon.ico +0 -0
  195. package/bun.lock +0 -435
  196. package/bunfig.toml +0 -28
  197. package/ecosystem.config.cjs +0 -23
  198. package/eslint.config.js +0 -63
  199. package/examples/multi-tenant-demo.js +0 -104
  200. package/install.sh +0 -123
  201. package/knip.json +0 -9
  202. package/scripts/test-bun-self-heal.sh +0 -163
  203. package/scripts/test-npx.sh +0 -60
  204. package/tests/audit.test.js +0 -189
  205. package/tests/backpressure.test.js +0 -167
  206. package/tests/benchmarks/runner.js +0 -1197
  207. package/tests/benchmarks/vector-generator.js +0 -368
  208. package/tests/cli-install.test.js +0 -322
  209. package/tests/control-db.test.js +0 -285
  210. package/tests/daemon-control.test.js +0 -171
  211. package/tests/daemon-fingerprint-integration.test.js +0 -111
  212. package/tests/daemon-pr24-regression.test.js +0 -198
  213. package/tests/fingerprint.test.js +0 -263
  214. package/tests/fixtures/240-orphan-seed.sql +0 -30
  215. package/tests/multi-tenant.test.js +0 -374
  216. package/tests/orphan-cleanup.test.js +0 -390
  217. package/tests/pg-version-regex.test.js +0 -129
  218. package/tests/quick-bench.js +0 -135
  219. package/tests/router-handshake-retry.test.js +0 -119
  220. package/tests/router-handshake-watchdog.test.js +0 -110
  221. package/tests/sdk.test.js +0 -71
  222. package/tests/stale-postmaster-pid.test.js +0 -85
  223. package/tests/stress-test.js +0 -439
  224. package/tests/sync-perf-test.js +0 -150
  225. package/tests/tcp-listen.test.js +0 -368
  226. package/tests/tenancy.test.js +0 -403
  227. 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();