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