@enterpriseai/cli 3.19.0 → 3.19.2
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/package.json +3 -3
- package/resources/deploy/eai-app-template/producer-pin.json +5 -5
- package/resources/deploy/eai-app-template/tests/source-unknown-deployment-evidence.test.mjs +2141 -1
- package/resources/gofer/.gofer-version +4 -357
- package/resources/gofer/agents-skills/eai/SKILL.md +365 -0
- package/resources/gofer/agents-skills/eai-update/SKILL.md +56 -0
- package/resources/gofer/agents-skills/gofer-documentation/SKILL.md +93 -0
- package/resources/gofer/bash-scripts/create-new-feature.sh +3 -1
- package/resources/gofer/bash-scripts/sync-implementation-status.sh +4 -3
- package/resources/gofer/bash-scripts/update-agent-context.sh +7 -8
- package/resources/gofer/bash-scripts/validate-artifact.sh +10 -6
- package/resources/gofer/bash-scripts/verify-object-type-routing-workspace.sh +184 -0
- package/resources/gofer/claude-commands/eai-update.md +46 -0
- package/resources/gofer/claude-commands/eai.md +355 -0
- package/resources/gofer/claude-skills/eai/SKILL.md +365 -0
- package/resources/gofer/claude-skills/eai-update/SKILL.md +56 -0
- package/resources/gofer/claude-skills/gofer-documentation/SKILL.md +93 -0
- package/resources/gofer/commands/0_gofer_start.md +440 -99
- package/resources/gofer/commands/0a_problem_validation.md +187 -27
- package/resources/gofer/commands/10_gofer_cloud.md +183 -23
- package/resources/gofer/commands/1_gofer_research.md +239 -30
- package/resources/gofer/commands/2_gofer_specify.md +239 -35
- package/resources/gofer/commands/3_gofer_plan.md +259 -43
- package/resources/gofer/commands/4_gofer_tasks.md +401 -81
- package/resources/gofer/commands/5_gofer_implement.md +375 -84
- package/resources/gofer/commands/6_gofer_validate.md +284 -53
- package/resources/gofer/commands/7_gofer_save.md +187 -26
- package/resources/gofer/commands/7a_stakeholder_comms.md +183 -23
- package/resources/gofer/commands/8_gofer_branding.md +183 -23
- package/resources/gofer/commands/9_gofer_tests.md +212 -23
- package/resources/gofer/commands/gofer_bootstrap_workspace.md +121 -8
- package/resources/gofer/commands/gofer_check_workspace.md +123 -8
- package/resources/gofer/commands/gofer_constitution.md +183 -23
- package/resources/gofer/commands/gofer_diagnose.md +183 -23
- package/resources/gofer/commands/gofer_eai_first_run.md +254 -25
- package/resources/gofer/commands/gofer_hydrate.md +183 -23
- package/resources/gofer/commands/gofer_personality.md +117 -6
- package/resources/gofer/commands/gofer_plan.md +117 -6
- package/resources/gofer/commands/gofer_side.md +117 -6
- package/resources/gofer/commands/gofer_spec_summary.md +183 -23
- package/resources/gofer/commands/gofer_tdd.md +183 -23
- package/resources/gofer/commands/gofer_vocabulary.md +185 -25
- package/resources/gofer/commands/gofer_zoom_out.md +183 -23
- package/resources/gofer/config/typesafe-learning-review.json +16 -0
- package/resources/gofer/config/typesafe-semantic-review.json +9 -0
- package/resources/gofer/copilot-instructions/gofer-cost.instructions.md +15 -6
- package/resources/gofer/copilot-instructions/typescript.instructions.md +1 -1
- package/resources/gofer/copilot-prompts/eai-update.prompt.md +58 -0
- package/resources/gofer/copilot-prompts/eai.prompt.md +367 -0
- package/resources/gofer/gemini/commands/gofer/eai-update.md +22 -0
- package/resources/gofer/gemini/commands/gofer/eai-update.toml +2 -0
- package/resources/gofer/gemini/commands/gofer/eai.md +357 -0
- package/resources/gofer/gemini/commands/gofer/eai.toml +2 -0
- package/resources/gofer/gemini/commands/gofer/manifest.json +3 -27
- package/resources/gofer/gemini/extension.json +4 -4
- package/resources/gofer/github-agents/eai.agent.md +83 -0
- package/resources/gofer/github-agents/gofer-business.agent.md +73 -4
- package/resources/gofer/github-agents/gofer-implement.agent.md +71 -2
- package/resources/gofer/github-agents/gofer-plan.agent.md +72 -2
- package/resources/gofer/github-agents/gofer-research.agent.md +72 -2
- package/resources/gofer/github-agents/gofer-validate.agent.md +72 -2
- package/resources/gofer/github-skills/eai/SKILL.md +365 -0
- package/resources/gofer/github-skills/eai-update/SKILL.md +56 -0
- package/resources/gofer/github-skills/gofer-documentation/SKILL.md +93 -0
- package/resources/gofer/grok-skills/eai/SKILL.md +267 -14
- package/resources/gofer/grok-skills/eai-update/SKILL.md +56 -0
- package/resources/gofer/hook-scripts/user-prompt-submit.mjs +58 -13
- package/resources/gofer/instruction-templates/base/agents-base.md +8 -4
- package/resources/gofer/instruction-templates/gofer/gofer-claude.md +7 -5
- package/resources/gofer/instruction-templates/gofer/gofer-copilot.md +10 -8
- package/resources/gofer/memory/constitution.md +9 -7
- package/resources/gofer/memory/gofer-model-policy.yaml +23 -1
- package/resources/gofer/memory/lessons.md +0 -31
- package/resources/gofer/node-scripts/canonical-descriptions.mjs +1 -1
- package/resources/gofer/node-scripts/check-version-alignment.mjs +4 -7
- package/resources/gofer/node-scripts/delivery-lineage-corpus.mjs +156 -0
- package/resources/gofer/node-scripts/generate-commands.mjs +1073 -265
- package/resources/gofer/node-scripts/gofer-acceptance-check.mjs +143 -0
- package/resources/gofer/node-scripts/gofer-agent-catalog.mjs +271 -0
- package/resources/gofer/node-scripts/gofer-benchmark-executor.mjs +123 -0
- package/resources/gofer/node-scripts/gofer-benchmark-sign.mjs +62 -0
- package/resources/gofer/node-scripts/gofer-benchmark-signer.mjs +89 -0
- package/resources/gofer/node-scripts/gofer-benchmark.mjs +76 -0
- package/resources/gofer/node-scripts/gofer-blocker-control.mjs +274 -0
- package/resources/gofer/node-scripts/gofer-claude-adapter.mjs +154 -0
- package/resources/gofer/node-scripts/gofer-closed-loop-audit.mjs +19 -2
- package/resources/gofer/node-scripts/gofer-delivery-check.mjs +121 -0
- package/resources/gofer/node-scripts/gofer-encrypted-key.mjs +58 -0
- package/resources/gofer/node-scripts/gofer-execution-metrics.mjs +477 -0
- package/resources/gofer/node-scripts/gofer-execution-recovery.mjs +334 -0
- package/resources/gofer/node-scripts/gofer-grok-adapter.mjs +184 -0
- package/resources/gofer/node-scripts/gofer-heldout-corpus.mjs +73 -0
- package/resources/gofer/node-scripts/gofer-heldout-snapshot.mjs +286 -0
- package/resources/gofer/node-scripts/gofer-heldout-verifier.mjs +211 -0
- package/resources/gofer/node-scripts/gofer-host-capability.mjs +335 -0
- package/resources/gofer/node-scripts/gofer-independent-reviewer.mjs +130 -0
- package/resources/gofer/node-scripts/gofer-live-routing.mjs +109 -0
- package/resources/gofer/node-scripts/gofer-local-capability-issuer.mjs +71 -0
- package/resources/gofer/node-scripts/gofer-local-isolation.mjs +175 -0
- package/resources/gofer/node-scripts/gofer-local-settings-cleanup.mjs +388 -0
- package/resources/gofer/node-scripts/gofer-loop-audit.mjs +54 -3
- package/resources/gofer/node-scripts/gofer-native-adapter.mjs +567 -0
- package/resources/gofer/node-scripts/gofer-native-benchmark-dispatch.mjs +145 -0
- package/resources/gofer/node-scripts/gofer-native-runtime.mjs +169 -0
- package/resources/gofer/node-scripts/gofer-pre-edit-check.mjs +204 -0
- package/resources/gofer/node-scripts/gofer-priority-check.mjs +214 -0
- package/resources/gofer/node-scripts/gofer-response-check.mjs +56 -0
- package/resources/gofer/node-scripts/gofer-reviewed-learning.mjs +1232 -0
- package/resources/gofer/node-scripts/gofer-run-verified-task.mjs +330 -0
- package/resources/gofer/node-scripts/gofer-runtime-ledger.mjs +289 -0
- package/resources/gofer/node-scripts/gofer-semantic-drift.mjs +584 -0
- package/resources/gofer/node-scripts/gofer-surface-update.mjs +1178 -0
- package/resources/gofer/node-scripts/gofer-trust-bootstrap.mjs +77 -0
- package/resources/gofer/node-scripts/gofer-trusted-benchmark.mjs +102 -0
- package/resources/gofer/node-scripts/gofer-trusted-evaluator.mjs +274 -0
- package/resources/gofer/node-scripts/gofer-tty-prompt.mjs +34 -0
- package/resources/gofer/node-scripts/gofer-typesafe-credentials.mjs +435 -0
- package/resources/gofer/node-scripts/gofer-ui-preview.mjs +1212 -0
- package/resources/gofer/node-scripts/gofer-verified-execution.mjs +484 -0
- package/resources/gofer/node-scripts/gofer-verifier-key-ceremony.mjs +101 -0
- package/resources/gofer/node-scripts/gofer-worktree-layout.mjs +32 -0
- package/resources/gofer/node-scripts/object-type-routing-phase-bundle.mjs +360 -0
- package/resources/gofer/node-scripts/package-agent-plugin.mjs +574 -82
- package/resources/gofer/node-scripts/parse-stage-command.mjs +1 -1
- package/resources/gofer/node-scripts/render-delivery-lineage.mjs +178 -0
- package/resources/gofer/node-scripts/sync-extension-resources.mjs +78 -2
- package/resources/gofer/node-scripts/validate-object-type-identifiers.mjs +767 -0
- package/resources/gofer/node-scripts/validate-object-type-routing-workspace.mjs +1325 -0
- package/resources/gofer/node-scripts/validate-v4-resource-contract.d.mts +44 -0
- package/resources/gofer/node-scripts/validate-v4-resource-contract.mjs +957 -0
- package/resources/gofer/node-scripts/workspace-bootstrap-lib.mjs +685 -116
- package/resources/gofer/references/agent-catalog.json +126 -0
- package/resources/gofer/references/blocker-mediation.md +156 -0
- package/resources/gofer/references/business-updates-and-goal-checks.md +109 -0
- package/resources/gofer/references/delivery-lineage.md +48 -0
- package/resources/gofer/references/mvp-capability-validation.md +102 -0
- package/resources/gofer/references/platform/README.md +1 -1
- package/resources/gofer/references/platform/eai-app-template.md +15 -13
- package/resources/gofer/references/platform/eai-auth-access.md +104 -0
- package/resources/gofer/references/platform/eai-config-driven-ui.md +5 -5
- package/resources/gofer/references/platform/eai-error-catalog.yaml +77 -19
- package/resources/gofer/references/platform/eai-repo-contract.md +61 -23
- package/resources/gofer/references/platform/eai-service-patterns.md +102 -105
- package/resources/gofer/references/platform/eai.md +44 -8
- package/resources/gofer/references/platform/vertical-template.md +10 -9
- package/resources/gofer/references/priority-outcome-protection.md +152 -0
- package/resources/gofer/references/verified-agent-execution.md +179 -0
- package/resources/gofer/schemas/gofer-learning-trace-v1.json +70 -0
- package/resources/gofer/specify-commands/0_gofer_start.md +440 -99
- package/resources/gofer/specify-commands/0a_problem_validation.md +187 -27
- package/resources/gofer/specify-commands/10_gofer_cloud.md +183 -23
- package/resources/gofer/specify-commands/1_gofer_research.md +239 -30
- package/resources/gofer/specify-commands/2_gofer_specify.md +239 -35
- package/resources/gofer/specify-commands/3_gofer_plan.md +259 -43
- package/resources/gofer/specify-commands/4_gofer_tasks.md +401 -81
- package/resources/gofer/specify-commands/5_gofer_implement.md +375 -84
- package/resources/gofer/specify-commands/6_gofer_validate.md +284 -53
- package/resources/gofer/specify-commands/7_gofer_save.md +187 -26
- package/resources/gofer/specify-commands/7a_stakeholder_comms.md +183 -23
- package/resources/gofer/specify-commands/8_gofer_branding.md +183 -23
- package/resources/gofer/specify-commands/9_gofer_tests.md +212 -23
- package/resources/gofer/specify-commands/gofer_bootstrap_workspace.md +121 -8
- package/resources/gofer/specify-commands/gofer_check_workspace.md +123 -8
- package/resources/gofer/specify-commands/gofer_constitution.md +183 -23
- package/resources/gofer/specify-commands/gofer_diagnose.md +183 -23
- package/resources/gofer/specify-commands/gofer_eai_first_run.md +254 -25
- package/resources/gofer/specify-commands/gofer_hydrate.md +183 -23
- package/resources/gofer/specify-commands/gofer_personality.md +117 -6
- package/resources/gofer/specify-commands/gofer_plan.md +117 -6
- package/resources/gofer/specify-commands/gofer_side.md +117 -6
- package/resources/gofer/specify-commands/gofer_spec_summary.md +183 -23
- package/resources/gofer/specify-commands/gofer_tdd.md +183 -23
- package/resources/gofer/specify-commands/gofer_vocabulary.md +185 -25
- package/resources/gofer/specify-commands/gofer_zoom_out.md +183 -23
- package/resources/gofer/specify-config/object-type-routing.json +24 -0
- package/resources/gofer/specify-config/typesafe-learning-review.json +16 -0
- package/resources/gofer/specify-config/typesafe-semantic-review.json +9 -0
- package/resources/gofer/system-skills/eai/SKILL.md +365 -0
- package/resources/gofer/system-skills/eai-update/SKILL.md +56 -0
- package/resources/gofer/system-skills/gofer-documentation/SKILL.md +93 -0
- package/resources/gofer/templates/build-map-template.md +64 -0
- package/resources/gofer/templates/business-scenarios-template.json +14 -0
- package/resources/gofer/templates/ciso-security-summary-template.md +3 -3
- package/resources/gofer/templates/context-bundle-template.md +4 -3
- package/resources/gofer/templates/contract-pack-template.md +8 -8
- package/resources/gofer/templates/cto-architecture-summary-template.md +3 -3
- package/resources/gofer/templates/delivery-lineage-template.json +8 -0
- package/resources/gofer/templates/eai-preflight-template.md +109 -68
- package/resources/gofer/templates/goal-ledger-template.json +1 -1
- package/resources/gofer/templates/gofer-model-policy.yaml +28 -6
- package/resources/gofer/templates/loop-contract-template.json +3 -1
- package/resources/gofer/templates/plan-template.md +8 -7
- package/resources/gofer/templates/priority-plan-template.json +29 -0
- package/resources/gofer/templates/proposal-review-template.md +6 -3
- package/resources/gofer/templates/release-capability-ledger-template.md +24 -0
- package/resources/gofer/templates/research-template.md +7 -6
- package/resources/gofer/templates/spec-summary-template.md +1 -1
- package/resources/gofer/templates/spec-template.md +45 -6
- package/resources/gofer/templates/tasks-template.md +12 -3
- package/resources/gofer/templates/ui-preview-brief-template.md +55 -5
- package/resources/gofer/templates/ui-review-log-template.md +21 -3
- package/resources/gofer/templates/ui-show-and-tell-template.md +46 -0
- package/resources/gofer/templates/visuals/capability-heatmap-template.md +2 -2
- package/resources/gofer/templates/working-backwards-prfaq-template.md +9 -8
- package/resources/linked-sources.json +4 -4
- package/resources/gofer/agents-skills/0_gofer_start/SKILL.md +0 -1287
- package/resources/gofer/agents-skills/0a_problem_validation/SKILL.md +0 -429
- package/resources/gofer/agents-skills/10_gofer_cloud/SKILL.md +0 -635
- package/resources/gofer/agents-skills/1_gofer_research/SKILL.md +0 -1159
- package/resources/gofer/agents-skills/2_gofer_specify/SKILL.md +0 -860
- package/resources/gofer/agents-skills/3_gofer_plan/SKILL.md +0 -899
- package/resources/gofer/agents-skills/4_gofer_tasks/SKILL.md +0 -760
- package/resources/gofer/agents-skills/5_gofer_implement/SKILL.md +0 -944
- package/resources/gofer/agents-skills/6_gofer_validate/SKILL.md +0 -2302
- package/resources/gofer/agents-skills/7_gofer_save/SKILL.md +0 -423
- package/resources/gofer/agents-skills/7a_stakeholder_comms/SKILL.md +0 -508
- package/resources/gofer/agents-skills/8_gofer_branding/SKILL.md +0 -315
- package/resources/gofer/agents-skills/9_gofer_tests/SKILL.md +0 -558
- package/resources/gofer/agents-skills/gofer_bootstrap_workspace/SKILL.md +0 -148
- package/resources/gofer/agents-skills/gofer_check_workspace/SKILL.md +0 -119
- package/resources/gofer/agents-skills/gofer_constitution/SKILL.md +0 -612
- package/resources/gofer/agents-skills/gofer_diagnose/SKILL.md +0 -114
- package/resources/gofer/agents-skills/gofer_eai_first_run/SKILL.md +0 -397
- package/resources/gofer/agents-skills/gofer_hydrate/SKILL.md +0 -561
- package/resources/gofer/agents-skills/gofer_personality/SKILL.md +0 -44
- package/resources/gofer/agents-skills/gofer_plan/SKILL.md +0 -40
- package/resources/gofer/agents-skills/gofer_side/SKILL.md +0 -45
- package/resources/gofer/agents-skills/gofer_spec_summary/SKILL.md +0 -112
- package/resources/gofer/agents-skills/gofer_tdd/SKILL.md +0 -112
- package/resources/gofer/agents-skills/gofer_vocabulary/SKILL.md +0 -112
- package/resources/gofer/agents-skills/gofer_zoom_out/SKILL.md +0 -112
- package/resources/gofer/claude-commands/0_gofer_start.md +0 -1282
- package/resources/gofer/claude-commands/0a_problem_validation.md +0 -424
- package/resources/gofer/claude-commands/10_gofer_cloud.md +0 -630
- package/resources/gofer/claude-commands/1_gofer_research.md +0 -1154
- package/resources/gofer/claude-commands/2_gofer_specify.md +0 -855
- package/resources/gofer/claude-commands/3_gofer_plan.md +0 -894
- package/resources/gofer/claude-commands/4_gofer_tasks.md +0 -755
- package/resources/gofer/claude-commands/5_gofer_implement.md +0 -939
- package/resources/gofer/claude-commands/6_gofer_validate.md +0 -2297
- package/resources/gofer/claude-commands/7_gofer_save.md +0 -418
- package/resources/gofer/claude-commands/7a_stakeholder_comms.md +0 -503
- package/resources/gofer/claude-commands/8_gofer_branding.md +0 -310
- package/resources/gofer/claude-commands/9_gofer_tests.md +0 -553
- package/resources/gofer/claude-commands/gofer_bootstrap_workspace.md +0 -143
- package/resources/gofer/claude-commands/gofer_check_workspace.md +0 -114
- package/resources/gofer/claude-commands/gofer_constitution.md +0 -607
- package/resources/gofer/claude-commands/gofer_diagnose.md +0 -109
- package/resources/gofer/claude-commands/gofer_eai_first_run.md +0 -392
- package/resources/gofer/claude-commands/gofer_hydrate.md +0 -556
- package/resources/gofer/claude-commands/gofer_personality.md +0 -39
- package/resources/gofer/claude-commands/gofer_plan.md +0 -35
- package/resources/gofer/claude-commands/gofer_side.md +0 -40
- package/resources/gofer/claude-commands/gofer_spec_summary.md +0 -107
- package/resources/gofer/claude-commands/gofer_tdd.md +0 -107
- package/resources/gofer/claude-commands/gofer_vocabulary.md +0 -107
- package/resources/gofer/claude-commands/gofer_zoom_out.md +0 -107
- package/resources/gofer/claude-skills/eai-gofer/SKILL.md +0 -56
- package/resources/gofer/copilot-prompts/0_gofer_start.prompt.md +0 -1308
- package/resources/gofer/copilot-prompts/0a_problem_validation.prompt.md +0 -429
- package/resources/gofer/copilot-prompts/10_gofer_cloud.prompt.md +0 -644
- package/resources/gofer/copilot-prompts/1_gofer_research.prompt.md +0 -1177
- package/resources/gofer/copilot-prompts/2_gofer_specify.prompt.md +0 -878
- package/resources/gofer/copilot-prompts/3_gofer_plan.prompt.md +0 -917
- package/resources/gofer/copilot-prompts/4_gofer_tasks.prompt.md +0 -778
- package/resources/gofer/copilot-prompts/5_gofer_implement.prompt.md +0 -962
- package/resources/gofer/copilot-prompts/6_gofer_validate.prompt.md +0 -2310
- package/resources/gofer/copilot-prompts/7_gofer_save.prompt.md +0 -433
- package/resources/gofer/copilot-prompts/7a_stakeholder_comms.prompt.md +0 -516
- package/resources/gofer/copilot-prompts/8_gofer_branding.prompt.md +0 -323
- package/resources/gofer/copilot-prompts/9_gofer_tests.prompt.md +0 -566
- package/resources/gofer/copilot-prompts/gofer_bootstrap_workspace.prompt.md +0 -161
- package/resources/gofer/copilot-prompts/gofer_check_workspace.prompt.md +0 -132
- package/resources/gofer/copilot-prompts/gofer_constitution.prompt.md +0 -621
- package/resources/gofer/copilot-prompts/gofer_diagnose.prompt.md +0 -127
- package/resources/gofer/copilot-prompts/gofer_eai_first_run.prompt.md +0 -410
- package/resources/gofer/copilot-prompts/gofer_hydrate.prompt.md +0 -571
- package/resources/gofer/copilot-prompts/gofer_personality.prompt.md +0 -57
- package/resources/gofer/copilot-prompts/gofer_plan.prompt.md +0 -53
- package/resources/gofer/copilot-prompts/gofer_side.prompt.md +0 -58
- package/resources/gofer/copilot-prompts/gofer_spec_summary.prompt.md +0 -125
- package/resources/gofer/copilot-prompts/gofer_tdd.prompt.md +0 -125
- package/resources/gofer/copilot-prompts/gofer_vocabulary.prompt.md +0 -125
- package/resources/gofer/copilot-prompts/gofer_zoom_out.prompt.md +0 -125
- package/resources/gofer/gemini/commands/gofer/0_gofer_start.md +0 -1282
- package/resources/gofer/gemini/commands/gofer/0_gofer_start.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/0a_problem_validation.md +0 -424
- package/resources/gofer/gemini/commands/gofer/0a_problem_validation.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/10_gofer_cloud.md +0 -630
- package/resources/gofer/gemini/commands/gofer/10_gofer_cloud.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/1_gofer_research.md +0 -1154
- package/resources/gofer/gemini/commands/gofer/1_gofer_research.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/2_gofer_specify.md +0 -855
- package/resources/gofer/gemini/commands/gofer/2_gofer_specify.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/3_gofer_plan.md +0 -894
- package/resources/gofer/gemini/commands/gofer/3_gofer_plan.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/4_gofer_tasks.md +0 -755
- package/resources/gofer/gemini/commands/gofer/4_gofer_tasks.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/5_gofer_implement.md +0 -939
- package/resources/gofer/gemini/commands/gofer/5_gofer_implement.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/6_gofer_validate.md +0 -2297
- package/resources/gofer/gemini/commands/gofer/6_gofer_validate.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/7_gofer_save.md +0 -418
- package/resources/gofer/gemini/commands/gofer/7_gofer_save.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/7a_stakeholder_comms.md +0 -503
- package/resources/gofer/gemini/commands/gofer/7a_stakeholder_comms.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/8_gofer_branding.md +0 -310
- package/resources/gofer/gemini/commands/gofer/8_gofer_branding.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/9_gofer_tests.md +0 -553
- package/resources/gofer/gemini/commands/gofer/9_gofer_tests.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_bootstrap_workspace.md +0 -143
- package/resources/gofer/gemini/commands/gofer/gofer_bootstrap_workspace.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_check_workspace.md +0 -114
- package/resources/gofer/gemini/commands/gofer/gofer_check_workspace.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_constitution.md +0 -607
- package/resources/gofer/gemini/commands/gofer/gofer_constitution.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_diagnose.md +0 -109
- package/resources/gofer/gemini/commands/gofer/gofer_diagnose.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_eai_first_run.md +0 -392
- package/resources/gofer/gemini/commands/gofer/gofer_eai_first_run.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_hydrate.md +0 -556
- package/resources/gofer/gemini/commands/gofer/gofer_hydrate.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_personality.md +0 -39
- package/resources/gofer/gemini/commands/gofer/gofer_personality.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_plan.md +0 -35
- package/resources/gofer/gemini/commands/gofer/gofer_plan.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_side.md +0 -40
- package/resources/gofer/gemini/commands/gofer/gofer_side.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_spec_summary.md +0 -107
- package/resources/gofer/gemini/commands/gofer/gofer_spec_summary.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_tdd.md +0 -107
- package/resources/gofer/gemini/commands/gofer/gofer_tdd.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_vocabulary.md +0 -107
- package/resources/gofer/gemini/commands/gofer/gofer_vocabulary.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_zoom_out.md +0 -107
- package/resources/gofer/gemini/commands/gofer/gofer_zoom_out.toml +0 -2
- package/resources/gofer/github-skills/eai-gofer/SKILL.md +0 -56
- package/resources/gofer/system-skills/0_gofer_start/SKILL.md +0 -1287
- package/resources/gofer/system-skills/0a_problem_validation/SKILL.md +0 -429
- package/resources/gofer/system-skills/10_gofer_cloud/SKILL.md +0 -635
- package/resources/gofer/system-skills/1_gofer_research/SKILL.md +0 -1159
- package/resources/gofer/system-skills/2_gofer_specify/SKILL.md +0 -860
- package/resources/gofer/system-skills/3_gofer_plan/SKILL.md +0 -899
- package/resources/gofer/system-skills/4_gofer_tasks/SKILL.md +0 -760
- package/resources/gofer/system-skills/5_gofer_implement/SKILL.md +0 -944
- package/resources/gofer/system-skills/6_gofer_validate/SKILL.md +0 -2302
- package/resources/gofer/system-skills/7_gofer_save/SKILL.md +0 -423
- package/resources/gofer/system-skills/7a_stakeholder_comms/SKILL.md +0 -508
- package/resources/gofer/system-skills/8_gofer_branding/SKILL.md +0 -315
- package/resources/gofer/system-skills/9_gofer_tests/SKILL.md +0 -558
- package/resources/gofer/system-skills/gofer_bootstrap_workspace/SKILL.md +0 -148
- package/resources/gofer/system-skills/gofer_check_workspace/SKILL.md +0 -119
- package/resources/gofer/system-skills/gofer_constitution/SKILL.md +0 -612
- package/resources/gofer/system-skills/gofer_diagnose/SKILL.md +0 -114
- package/resources/gofer/system-skills/gofer_eai_first_run/SKILL.md +0 -397
- package/resources/gofer/system-skills/gofer_hydrate/SKILL.md +0 -561
- package/resources/gofer/system-skills/gofer_personality/SKILL.md +0 -44
- package/resources/gofer/system-skills/gofer_plan/SKILL.md +0 -40
- package/resources/gofer/system-skills/gofer_side/SKILL.md +0 -45
- package/resources/gofer/system-skills/gofer_spec_summary/SKILL.md +0 -112
- package/resources/gofer/system-skills/gofer_tdd/SKILL.md +0 -112
- package/resources/gofer/system-skills/gofer_vocabulary/SKILL.md +0 -112
- package/resources/gofer/system-skills/gofer_zoom_out/SKILL.md +0 -112
- package/resources/gofer/templates/ui-approval-template.md +0 -32
|
@@ -0,0 +1,957 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { promises as fs } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const DOCS_URL = 'https://docs.eai.software/services/publicapi/v4/resource-mutation-contract';
|
|
8
|
+
const OBJECT_TYPE_ROUTING_CONFIG_PATH = path.resolve(
|
|
9
|
+
path.dirname(fileURLToPath(import.meta.url)),
|
|
10
|
+
'..',
|
|
11
|
+
'..',
|
|
12
|
+
'config',
|
|
13
|
+
'object-type-routing.json'
|
|
14
|
+
);
|
|
15
|
+
const SOURCE_EXTENSIONS = new Set(['.cjs', '.js', '.jsx', '.mjs', '.mts', '.ts', '.tsx', '.vue']);
|
|
16
|
+
const RESOURCE_URL_PATTERN =
|
|
17
|
+
/(?:\/api\/eai)?\/v4\/data\/resources|(?<![\w$])(?:this\s*\.\s*)?(?:resources?BaseUrl|resourceUrl)\s*\(/;
|
|
18
|
+
const NON_RECORD_MUTATION_PATTERN =
|
|
19
|
+
/\/(?:object-types|query|search|aggregate|batch|files|links|shares|parents|storage)(?:\/|['"`}]|$)/;
|
|
20
|
+
const RESOURCE_ROUTING_METHODS = Object.freeze([
|
|
21
|
+
'collection',
|
|
22
|
+
'member',
|
|
23
|
+
'subresource',
|
|
24
|
+
'collectionOperation',
|
|
25
|
+
'query',
|
|
26
|
+
'search',
|
|
27
|
+
'schema',
|
|
28
|
+
'objectTypes',
|
|
29
|
+
'objectType',
|
|
30
|
+
'parent',
|
|
31
|
+
]);
|
|
32
|
+
const RESOURCE_ROUTING_CALLEE_PATTERN =
|
|
33
|
+
`this\\s*\\.\\s*routing\\s*\\.\\s*(?:${RESOURCE_ROUTING_METHODS.join('|')})`;
|
|
34
|
+
const RESOURCE_ROUTING_ROUTE_PREFIX = 'eai-resource-routing:';
|
|
35
|
+
const RESOURCE_ROUTING_NON_RECORD_FAMILIES = new Set([
|
|
36
|
+
'subresource',
|
|
37
|
+
'collectionOperation',
|
|
38
|
+
'query',
|
|
39
|
+
'search',
|
|
40
|
+
'schema',
|
|
41
|
+
'parent',
|
|
42
|
+
]);
|
|
43
|
+
const NON_NETWORK_ROUTE_CONSUMERS = new Set(['URL']);
|
|
44
|
+
const IS_DIRECT_RUN =
|
|
45
|
+
process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
46
|
+
const FIXED_AUDIT_ROOTS = ['src', 'app', 'pages', 'lib', 'packages'];
|
|
47
|
+
const GOVERNED_REPOSITORY_ROOTS = ['front/eai-app-template'];
|
|
48
|
+
const SOLE_ROUTE_OWNER = 'front/eai-app-template/packages/platform-sdk/src/resource-routing.ts';
|
|
49
|
+
|
|
50
|
+
function assertObjectTypeRoutingConfig(config) {
|
|
51
|
+
if (
|
|
52
|
+
config?.schemaVersion !== 'eai.object-type-routing.source-audit-config/v1' ||
|
|
53
|
+
config?.contractVersion !== 'eai.object-type-routing/v1' ||
|
|
54
|
+
!Array.isArray(config.fixedRoots) ||
|
|
55
|
+
!Array.isArray(config.governedRepositoryRoots) ||
|
|
56
|
+
!Array.isArray(config.excludedDirectories) ||
|
|
57
|
+
typeof config.soleOwner !== 'string' ||
|
|
58
|
+
typeof config.canonicalGuidance !== 'string' ||
|
|
59
|
+
JSON.stringify(config.fixedRoots) !== JSON.stringify(FIXED_AUDIT_ROOTS) ||
|
|
60
|
+
JSON.stringify(config.governedRepositoryRoots) !==
|
|
61
|
+
JSON.stringify(GOVERNED_REPOSITORY_ROOTS) ||
|
|
62
|
+
config.soleOwner !== SOLE_ROUTE_OWNER
|
|
63
|
+
) {
|
|
64
|
+
throw new Error(`Invalid immutable Object Type routing config: ${OBJECT_TYPE_ROUTING_CONFIG_PATH}`);
|
|
65
|
+
}
|
|
66
|
+
return Object.freeze({
|
|
67
|
+
fixedRoots: Object.freeze([...config.fixedRoots]),
|
|
68
|
+
governedRepositoryRoots: Object.freeze([...config.governedRepositoryRoots]),
|
|
69
|
+
excludedDirectories: new Set(config.excludedDirectories),
|
|
70
|
+
soleOwner: config.soleOwner.replace(/\\/g, '/'),
|
|
71
|
+
canonicalGuidance: config.canonicalGuidance,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const OBJECT_TYPE_ROUTING_CONFIG = assertObjectTypeRoutingConfig(
|
|
76
|
+
JSON.parse(await fs.readFile(OBJECT_TYPE_ROUTING_CONFIG_PATH, 'utf8'))
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
export const OBJECT_TYPE_ROUTING_AUDIT_CONFIG = Object.freeze({
|
|
80
|
+
fixedRoots: [...OBJECT_TYPE_ROUTING_CONFIG.fixedRoots],
|
|
81
|
+
governedRepositoryRoots: [...OBJECT_TYPE_ROUTING_CONFIG.governedRepositoryRoots],
|
|
82
|
+
excludedDirectories: [...OBJECT_TYPE_ROUTING_CONFIG.excludedDirectories].sort(),
|
|
83
|
+
soleOwner: OBJECT_TYPE_ROUTING_CONFIG.soleOwner,
|
|
84
|
+
canonicalGuidance: OBJECT_TYPE_ROUTING_CONFIG.canonicalGuidance,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
function lineNumber(content, index) {
|
|
88
|
+
return content.slice(0, index).split(/\r?\n/).length;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function maskNonCode(content) {
|
|
92
|
+
const masked = [...content];
|
|
93
|
+
let quote = '';
|
|
94
|
+
let escaped = false;
|
|
95
|
+
let lineComment = false;
|
|
96
|
+
let blockComment = false;
|
|
97
|
+
|
|
98
|
+
for (let index = 0; index < content.length; index += 1) {
|
|
99
|
+
const character = content[index];
|
|
100
|
+
const next = content[index + 1];
|
|
101
|
+
|
|
102
|
+
if (lineComment) {
|
|
103
|
+
if (character === '\n') {
|
|
104
|
+
lineComment = false;
|
|
105
|
+
} else {
|
|
106
|
+
masked[index] = ' ';
|
|
107
|
+
}
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (blockComment) {
|
|
111
|
+
if (character === '*' && next === '/') {
|
|
112
|
+
masked[index] = ' ';
|
|
113
|
+
masked[index + 1] = ' ';
|
|
114
|
+
index += 1;
|
|
115
|
+
blockComment = false;
|
|
116
|
+
} else if (character !== '\n') {
|
|
117
|
+
masked[index] = ' ';
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (quote) {
|
|
122
|
+
if (character !== '\n') masked[index] = ' ';
|
|
123
|
+
if (escaped) {
|
|
124
|
+
escaped = false;
|
|
125
|
+
} else if (character === '\\') {
|
|
126
|
+
escaped = true;
|
|
127
|
+
} else if (character === quote) {
|
|
128
|
+
quote = '';
|
|
129
|
+
}
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (character === '/' && next === '/') {
|
|
133
|
+
masked[index] = ' ';
|
|
134
|
+
masked[index + 1] = ' ';
|
|
135
|
+
index += 1;
|
|
136
|
+
lineComment = true;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (character === '/' && next === '*') {
|
|
140
|
+
masked[index] = ' ';
|
|
141
|
+
masked[index + 1] = ' ';
|
|
142
|
+
index += 1;
|
|
143
|
+
blockComment = true;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (character === "'" || character === '"' || character === '`') {
|
|
147
|
+
masked[index] = ' ';
|
|
148
|
+
quote = character;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return masked.join('');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function callSnippets(content, calleePattern) {
|
|
156
|
+
const snippets = [];
|
|
157
|
+
const masked = maskNonCode(content);
|
|
158
|
+
const regex = new RegExp(`(?<![\\w$])(?:${calleePattern})(?![\\w$])`, 'g');
|
|
159
|
+
let match;
|
|
160
|
+
|
|
161
|
+
while ((match = regex.exec(masked)) !== null) {
|
|
162
|
+
let openIndex = match.index + match[0].length;
|
|
163
|
+
while (/\s/.test(masked[openIndex] || '')) openIndex += 1;
|
|
164
|
+
|
|
165
|
+
if (masked.slice(openIndex, openIndex + 2) === '?.') {
|
|
166
|
+
openIndex += 2;
|
|
167
|
+
while (/\s/.test(masked[openIndex] || '')) openIndex += 1;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (masked[openIndex] === '<') {
|
|
171
|
+
let genericDepth = 0;
|
|
172
|
+
for (let index = openIndex; index < masked.length; index += 1) {
|
|
173
|
+
if (masked[index] === '<') genericDepth += 1;
|
|
174
|
+
if (masked[index] === '>' && masked[index - 1] !== '=') {
|
|
175
|
+
genericDepth -= 1;
|
|
176
|
+
if (genericDepth === 0) {
|
|
177
|
+
openIndex = index + 1;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
while (/\s/.test(masked[openIndex] || '')) openIndex += 1;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (masked.slice(openIndex, openIndex + 2) === '?.') {
|
|
186
|
+
openIndex += 2;
|
|
187
|
+
while (/\s/.test(masked[openIndex] || '')) openIndex += 1;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (masked[openIndex] !== '(') continue;
|
|
191
|
+
let depth = 0;
|
|
192
|
+
for (let index = openIndex; index < content.length; index += 1) {
|
|
193
|
+
const character = masked[index];
|
|
194
|
+
if (character === '(') depth += 1;
|
|
195
|
+
if (character === ')') {
|
|
196
|
+
depth -= 1;
|
|
197
|
+
if (depth === 0) {
|
|
198
|
+
snippets.push({
|
|
199
|
+
start: match.index,
|
|
200
|
+
end: index + 1,
|
|
201
|
+
text: content.slice(match.index, index + 1),
|
|
202
|
+
callee: content.slice(match.index, match.index + match[0].length).replace(/\s+/g, ''),
|
|
203
|
+
openOffset: openIndex - match.index,
|
|
204
|
+
});
|
|
205
|
+
regex.lastIndex = index + 1;
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return snippets;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function splitTopLevel(content) {
|
|
216
|
+
const masked = maskNonCode(content);
|
|
217
|
+
const segments = [];
|
|
218
|
+
let segmentStart = 0;
|
|
219
|
+
let depth = 0;
|
|
220
|
+
|
|
221
|
+
for (let index = 0; index < masked.length; index += 1) {
|
|
222
|
+
const character = masked[index];
|
|
223
|
+
if (character === '{' || character === '[' || character === '(') depth += 1;
|
|
224
|
+
if (character === '}' || character === ']' || character === ')') depth -= 1;
|
|
225
|
+
if (character === ',' && depth === 0) {
|
|
226
|
+
segments.push(content.slice(segmentStart, index).trim());
|
|
227
|
+
segmentStart = index + 1;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
segments.push(content.slice(segmentStart).trim());
|
|
231
|
+
return segments;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function callArguments(snippet, openOffset) {
|
|
235
|
+
const openIndex = openOffset ?? maskNonCode(snippet).indexOf('(');
|
|
236
|
+
if (openIndex === -1 || !snippet.endsWith(')')) return [];
|
|
237
|
+
return splitTopLevel(snippet.slice(openIndex + 1, -1));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function literalRouteSegment(expression) {
|
|
241
|
+
const match = expression.trim().match(/^(['"`])([^'"`]*)\1$/);
|
|
242
|
+
if (!match || match[2].includes('${')) return undefined;
|
|
243
|
+
return match[2].replace(/^\/+|\/+$/g, '') || undefined;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function canonicalResourceRoutingRoute(expression) {
|
|
247
|
+
const call = callSnippets(expression, RESOURCE_ROUTING_CALLEE_PATTERN)[0];
|
|
248
|
+
if (!call) return undefined;
|
|
249
|
+
|
|
250
|
+
const prefix = expression.slice(0, call.start);
|
|
251
|
+
const suffix = expression.slice(call.end);
|
|
252
|
+
if (!/^\s*\(*\s*$/.test(prefix) || !/^\s*\)*\s*$/.test(suffix)) return undefined;
|
|
253
|
+
|
|
254
|
+
const method = call.callee.split('.').at(-1);
|
|
255
|
+
const args = callArguments(call.text, call.openOffset);
|
|
256
|
+
const family =
|
|
257
|
+
method === 'subresource' && literalRouteSegment(args[2] || '') === 'actions'
|
|
258
|
+
? 'action'
|
|
259
|
+
: method;
|
|
260
|
+
return `${RESOURCE_ROUTING_ROUTE_PREFIX}${family}`;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function canonicalResourceRoutingFamily(route) {
|
|
264
|
+
return route.startsWith(RESOURCE_ROUTING_ROUTE_PREFIX)
|
|
265
|
+
? route.slice(RESOURCE_ROUTING_ROUTE_PREFIX.length)
|
|
266
|
+
: undefined;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function objectLiteralBody(expression) {
|
|
270
|
+
let candidate = expression.trim();
|
|
271
|
+
const stringifyMatch = /^JSON\s*\.\s*stringify\s*\(/.exec(maskNonCode(candidate));
|
|
272
|
+
if (stringifyMatch) {
|
|
273
|
+
const args = callArguments(candidate);
|
|
274
|
+
candidate = args[0]?.trim() || '';
|
|
275
|
+
}
|
|
276
|
+
if (!candidate.startsWith('{')) return null;
|
|
277
|
+
|
|
278
|
+
const masked = maskNonCode(candidate);
|
|
279
|
+
let depth = 0;
|
|
280
|
+
for (let index = 0; index < masked.length; index += 1) {
|
|
281
|
+
if (masked[index] === '{') depth += 1;
|
|
282
|
+
if (masked[index] === '}') {
|
|
283
|
+
depth -= 1;
|
|
284
|
+
if (depth === 0) {
|
|
285
|
+
return candidate.slice(1, index);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function topLevelPropertyExpression(objectExpression, propertyName) {
|
|
293
|
+
const objectBody = objectLiteralBody(objectExpression);
|
|
294
|
+
if (objectBody === null) return undefined;
|
|
295
|
+
for (const segment of splitTopLevel(objectBody)) {
|
|
296
|
+
if (segment.trim() === propertyName) return propertyName;
|
|
297
|
+
const match = segment.match(
|
|
298
|
+
/^(?:['"`]([A-Za-z_$][\w$]*)['"`]|([A-Za-z_$][\w$]*))\s*:\s*([\s\S]+)$/
|
|
299
|
+
);
|
|
300
|
+
if ((match?.[1] || match?.[2]) === propertyName) return match[3].trim();
|
|
301
|
+
}
|
|
302
|
+
return undefined;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function methodOf(optionsExpression) {
|
|
306
|
+
const normalizedOptions = optionsExpression.trim();
|
|
307
|
+
if (!normalizedOptions || normalizedOptions === 'undefined') {
|
|
308
|
+
return { method: 'GET', resolved: true };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const objectBody = objectLiteralBody(normalizedOptions);
|
|
312
|
+
if (objectBody === null) return { method: undefined, resolved: false };
|
|
313
|
+
|
|
314
|
+
let methodExpression;
|
|
315
|
+
let unresolvedAssignment = false;
|
|
316
|
+
for (const segment of splitTopLevel(objectBody)) {
|
|
317
|
+
const candidate = segment.trim();
|
|
318
|
+
if (candidate.startsWith('...') || candidate.startsWith('[')) {
|
|
319
|
+
methodExpression = undefined;
|
|
320
|
+
unresolvedAssignment = true;
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (candidate === 'method') {
|
|
325
|
+
methodExpression = candidate;
|
|
326
|
+
unresolvedAssignment = false;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const match = candidate.match(/^(?:['"`]method['"`]|method)\s*:\s*([\s\S]+)$/);
|
|
331
|
+
if (match) {
|
|
332
|
+
methodExpression = match[1].trim();
|
|
333
|
+
unresolvedAssignment = false;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (unresolvedAssignment) return { method: undefined, resolved: false };
|
|
338
|
+
if (methodExpression === undefined) return { method: 'GET', resolved: true };
|
|
339
|
+
const method = methodExpression.match(/^['"`](GET|POST|PUT|PATCH|DELETE)['"`]$/i)?.[1];
|
|
340
|
+
return method
|
|
341
|
+
? { method: method.toUpperCase(), resolved: true }
|
|
342
|
+
: { method: undefined, resolved: false };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function objectLiteralArgument(expression) {
|
|
346
|
+
const stringifyIndex = expression.search(/JSON\s*\.\s*stringify\s*\(/);
|
|
347
|
+
if (stringifyIndex === -1) return null;
|
|
348
|
+
const openParenthesis = expression.indexOf('(', stringifyIndex);
|
|
349
|
+
let index = openParenthesis + 1;
|
|
350
|
+
while (/\s/.test(expression[index] || '')) index += 1;
|
|
351
|
+
return objectLiteralBody(expression.slice(index));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function topLevelObjectProperties(objectBody) {
|
|
355
|
+
const properties = new Set();
|
|
356
|
+
let segmentStart = 0;
|
|
357
|
+
let depth = 0;
|
|
358
|
+
let quote = '';
|
|
359
|
+
let escaped = false;
|
|
360
|
+
|
|
361
|
+
function recordSegment(end) {
|
|
362
|
+
const segment = objectBody.slice(segmentStart, end).trim();
|
|
363
|
+
const property = segment.match(
|
|
364
|
+
/^(?:['"`]([A-Za-z_$][\w$]*)['"`]|([A-Za-z_$][\w$]*))\s*(?::|$)/
|
|
365
|
+
);
|
|
366
|
+
const name = property?.[1] || property?.[2];
|
|
367
|
+
if (name) properties.add(name);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
for (let index = 0; index < objectBody.length; index += 1) {
|
|
371
|
+
const character = objectBody[index];
|
|
372
|
+
if (quote) {
|
|
373
|
+
if (escaped) {
|
|
374
|
+
escaped = false;
|
|
375
|
+
} else if (character === '\\') {
|
|
376
|
+
escaped = true;
|
|
377
|
+
} else if (character === quote) {
|
|
378
|
+
quote = '';
|
|
379
|
+
}
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (character === "'" || character === '"' || character === '`') {
|
|
383
|
+
quote = character;
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (character === '{' || character === '[' || character === '(') depth += 1;
|
|
387
|
+
if (character === '}' || character === ']' || character === ')') depth -= 1;
|
|
388
|
+
if (character === ',' && depth === 0) {
|
|
389
|
+
recordSegment(index);
|
|
390
|
+
segmentStart = index + 1;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
recordSegment(objectBody.length);
|
|
394
|
+
return properties;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function hasObjectEnvelope(bodyExpression, requiredProperties) {
|
|
398
|
+
const objectBody = objectLiteralArgument(bodyExpression);
|
|
399
|
+
if (objectBody === null) return false;
|
|
400
|
+
const properties = topLevelObjectProperties(objectBody);
|
|
401
|
+
return requiredProperties.every((property) => properties.has(property));
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function simpleResourceBindings(content) {
|
|
405
|
+
const bindings = new Map();
|
|
406
|
+
const masked = maskNonCode(content);
|
|
407
|
+
const bindingPattern = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=/g;
|
|
408
|
+
let match;
|
|
409
|
+
|
|
410
|
+
while ((match = bindingPattern.exec(masked)) !== null) {
|
|
411
|
+
const expressionStart = match.index + match[0].length;
|
|
412
|
+
let expressionEnd = masked.indexOf(';', expressionStart);
|
|
413
|
+
if (expressionEnd === -1) expressionEnd = content.length;
|
|
414
|
+
const expression = content.slice(expressionStart, expressionEnd).trim();
|
|
415
|
+
const canonicalRoute = canonicalResourceRoutingRoute(expression);
|
|
416
|
+
if (canonicalRoute || RESOURCE_URL_PATTERN.test(expression)) {
|
|
417
|
+
bindings.set(match[1], canonicalRoute || expression);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return bindings;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function resolvedResourceRoute(expression, bindings) {
|
|
424
|
+
const canonicalRoute = canonicalResourceRoutingRoute(expression);
|
|
425
|
+
if (canonicalRoute) return canonicalRoute;
|
|
426
|
+
if (RESOURCE_URL_PATTERN.test(expression)) return expression;
|
|
427
|
+
const identifier = expression.trim().match(/^([A-Za-z_$][\w$]*)$/)?.[1];
|
|
428
|
+
return identifier ? bindings.get(identifier) : undefined;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function isResourceMemberRoute(route) {
|
|
432
|
+
const routingFamily = canonicalResourceRoutingFamily(route);
|
|
433
|
+
if (routingFamily) return routingFamily === 'member';
|
|
434
|
+
|
|
435
|
+
const resourceUrlCall = route.match(/(?:this\s*\.\s*)?resourceUrl\s*\(([\s\S]*?)\)/);
|
|
436
|
+
if (resourceUrlCall) {
|
|
437
|
+
return splitTopLevel(resourceUrlCall[1]).length >= 2;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const marker = '/v4/data/resources';
|
|
441
|
+
const markerIndex = route.indexOf(marker);
|
|
442
|
+
if (markerIndex === -1) return false;
|
|
443
|
+
const suffix = route
|
|
444
|
+
.slice(markerIndex + marker.length)
|
|
445
|
+
.split(/[?#]/, 1)[0]
|
|
446
|
+
.replace(/['"`)}\];,\s]+$/g, '');
|
|
447
|
+
const segments = suffix.split('/').filter(Boolean);
|
|
448
|
+
if (segments.length >= 3) return true;
|
|
449
|
+
if (segments.length < 2) return false;
|
|
450
|
+
|
|
451
|
+
const firstSegment = segments[0];
|
|
452
|
+
const isExplicitTenantSegment =
|
|
453
|
+
/tenant/i.test(firstSegment) ||
|
|
454
|
+
/^\$\{[^}]*tenant[^}]*\}$/i.test(firstSegment) ||
|
|
455
|
+
/^[0-9a-f]{8}-[0-9a-f-]{27,}$/i.test(firstSegment);
|
|
456
|
+
return !isExplicitTenantSegment;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function enclosingBlockEnd(content, start) {
|
|
460
|
+
const masked = maskNonCode(content);
|
|
461
|
+
let depth = 0;
|
|
462
|
+
for (let index = 0; index < start; index += 1) {
|
|
463
|
+
if (masked[index] === '{') depth += 1;
|
|
464
|
+
if (masked[index] === '}') depth -= 1;
|
|
465
|
+
}
|
|
466
|
+
if (depth === 0) return content.length;
|
|
467
|
+
|
|
468
|
+
const targetDepth = depth - 1;
|
|
469
|
+
for (let index = start; index < masked.length; index += 1) {
|
|
470
|
+
if (masked[index] === '{') depth += 1;
|
|
471
|
+
if (masked[index] === '}') {
|
|
472
|
+
depth -= 1;
|
|
473
|
+
if (depth === targetDepth) return index;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return content.length;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function normalizedExpression(expression) {
|
|
480
|
+
let normalized = expression.replace(/\s+/g, '');
|
|
481
|
+
while (normalized.startsWith('(') && normalized.endsWith(')')) {
|
|
482
|
+
normalized = normalized.slice(1, -1);
|
|
483
|
+
}
|
|
484
|
+
return normalized;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function actionResultBinding(content, actionStart) {
|
|
488
|
+
const prefix = maskNonCode(content.slice(0, actionStart));
|
|
489
|
+
const destructured = prefix.match(
|
|
490
|
+
/(?:const|let|var)\s*\{([^{}]+)\}(?:\s*:\s*[^=]+)?\s*=\s*await\s*\(?\s*$/
|
|
491
|
+
);
|
|
492
|
+
if (destructured) {
|
|
493
|
+
for (const property of splitTopLevel(destructured[1])) {
|
|
494
|
+
const versionBinding = property
|
|
495
|
+
.trim()
|
|
496
|
+
.match(/^version(?:\s*:\s*([A-Za-z_$][\w$]*))?$/);
|
|
497
|
+
if (versionBinding) {
|
|
498
|
+
return {
|
|
499
|
+
resultName: undefined,
|
|
500
|
+
versionNames: new Set([versionBinding[1] || 'version']),
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
const declarations = [
|
|
507
|
+
...prefix.matchAll(/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)/g),
|
|
508
|
+
].reverse();
|
|
509
|
+
|
|
510
|
+
for (const declaration of declarations) {
|
|
511
|
+
const candidate = prefix.slice(declaration.index);
|
|
512
|
+
const binding = candidate.match(
|
|
513
|
+
/^(?:const|let|var)\s+([A-Za-z_$][\w$]*)(?:\s*:\s*[^=]*?)?\s*=\s*await\s*\(?\s*$/
|
|
514
|
+
);
|
|
515
|
+
if (binding) {
|
|
516
|
+
return { resultName: binding[1], versionNames: new Set() };
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const assignment = prefix.match(
|
|
521
|
+
/(?:^|[;{}])\s*([A-Za-z_$][\w$]*)\s*=\s*await\s*\(?\s*$/
|
|
522
|
+
);
|
|
523
|
+
return {
|
|
524
|
+
resultName: assignment?.[1],
|
|
525
|
+
versionNames: new Set(),
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function versionExpressionsBeforeUpdate(binding, flowPrefix) {
|
|
530
|
+
const accepted = new Set(binding.versionNames);
|
|
531
|
+
if (binding.resultName) accepted.add(`${binding.resultName}.version`);
|
|
532
|
+
|
|
533
|
+
const masked = maskNonCode(flowPrefix);
|
|
534
|
+
if (binding.resultName) {
|
|
535
|
+
const escapedResult = binding.resultName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
536
|
+
const propertyAlias = new RegExp(
|
|
537
|
+
`\\b(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)(?:\\s*:\\s*[^=]+)?\\s*=\\s*${escapedResult}\\s*\\.\\s*version\\b`,
|
|
538
|
+
'g'
|
|
539
|
+
);
|
|
540
|
+
for (const match of masked.matchAll(propertyAlias)) accepted.add(match[1]);
|
|
541
|
+
|
|
542
|
+
const destructuredAlias = new RegExp(
|
|
543
|
+
`\\b(?:const|let|var)\\s*\\{\\s*version(?:\\s*:\\s*([A-Za-z_$][\\w$]*))?\\s*\\}\\s*=\\s*${escapedResult}\\b`,
|
|
544
|
+
'g'
|
|
545
|
+
);
|
|
546
|
+
for (const match of masked.matchAll(destructuredAlias)) {
|
|
547
|
+
accepted.add(match[1] || 'version');
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
for (const sourceName of [...accepted]) {
|
|
552
|
+
if (sourceName.includes('.')) continue;
|
|
553
|
+
const escapedSource = sourceName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
554
|
+
const localAlias = new RegExp(
|
|
555
|
+
`\\b(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)(?:\\s*:\\s*[^=]+)?\\s*=\\s*${escapedSource}\\b`,
|
|
556
|
+
'g'
|
|
557
|
+
);
|
|
558
|
+
for (const match of masked.matchAll(localAlias)) accepted.add(match[1]);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
return accepted;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function violation(ruleId, file, content, index, message, remediation) {
|
|
565
|
+
return {
|
|
566
|
+
ruleId,
|
|
567
|
+
file,
|
|
568
|
+
line: lineNumber(content, index),
|
|
569
|
+
message,
|
|
570
|
+
remediation,
|
|
571
|
+
documentation: DOCS_URL,
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
export function validateSourceContent(content, file = '<memory>') {
|
|
576
|
+
const violations = [];
|
|
577
|
+
const bindings = simpleResourceBindings(content);
|
|
578
|
+
const requests = callSnippets(
|
|
579
|
+
content,
|
|
580
|
+
'[A-Za-z_$][\\w$]*(?:\\s*\\.\\s*[A-Za-z_$][\\w$]*)*'
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
for (const request of requests) {
|
|
584
|
+
const callee = request.callee;
|
|
585
|
+
const terminalCallee = callee.split('.').at(-1);
|
|
586
|
+
const args = callArguments(request.text, request.openOffset);
|
|
587
|
+
const isNetworkLike =
|
|
588
|
+
terminalCallee === 'fetch' ||
|
|
589
|
+
terminalCallee === 'platformFetch' ||
|
|
590
|
+
/(?:fetch|request|send|call)$/i.test(terminalCallee || '');
|
|
591
|
+
if (!isNetworkLike) continue;
|
|
592
|
+
|
|
593
|
+
const route = resolvedResourceRoute(args[0] || '', bindings);
|
|
594
|
+
if (!route) {
|
|
595
|
+
const routeExpression = (args[0] || '').trim();
|
|
596
|
+
const optionsExpression = args[1] || '{}';
|
|
597
|
+
const methodResult = methodOf(optionsExpression);
|
|
598
|
+
const isLiteralNonV4Route =
|
|
599
|
+
/^['"`]/.test(routeExpression) && !/\/v4\/data\/resources/.test(routeExpression);
|
|
600
|
+
const isUnresolvedResourceCandidate =
|
|
601
|
+
!isLiteralNonV4Route && /resource/i.test(routeExpression);
|
|
602
|
+
const isMutationCandidate =
|
|
603
|
+
!methodResult.resolved || ['POST', 'PUT', 'PATCH'].includes(methodResult.method);
|
|
604
|
+
|
|
605
|
+
if (isUnresolvedResourceCandidate && isMutationCandidate) {
|
|
606
|
+
violations.push(
|
|
607
|
+
violation(
|
|
608
|
+
'EAI_V4_RESOURCE_PATTERN_UNRESOLVED',
|
|
609
|
+
file,
|
|
610
|
+
content,
|
|
611
|
+
request.start,
|
|
612
|
+
'A mutation-capable PublicAPI resource request uses a route that cannot be proven.',
|
|
613
|
+
'Use the canonical resource SDK or keep the literal /v4/data/resources route in the network call.'
|
|
614
|
+
)
|
|
615
|
+
);
|
|
616
|
+
}
|
|
617
|
+
continue;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (NON_NETWORK_ROUTE_CONSUMERS.has(terminalCallee)) continue;
|
|
621
|
+
|
|
622
|
+
if (terminalCallee !== 'fetch' && terminalCallee !== 'platformFetch') {
|
|
623
|
+
violations.push(
|
|
624
|
+
violation(
|
|
625
|
+
'EAI_V4_RESOURCE_PATTERN_UNRESOLVED',
|
|
626
|
+
file,
|
|
627
|
+
content,
|
|
628
|
+
request.start,
|
|
629
|
+
`A PublicAPI v4 resource request is hidden behind unsupported helper ${callee}().`,
|
|
630
|
+
'Use the canonical resource SDK or a direct fetch/platformFetch call with a literal method and body envelope.'
|
|
631
|
+
)
|
|
632
|
+
);
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const optionsExpression = args[1] || '{}';
|
|
637
|
+
const methodResult = methodOf(optionsExpression);
|
|
638
|
+
if (!methodResult.resolved) {
|
|
639
|
+
violations.push(
|
|
640
|
+
violation(
|
|
641
|
+
'EAI_V4_RESOURCE_PATTERN_UNRESOLVED',
|
|
642
|
+
file,
|
|
643
|
+
content,
|
|
644
|
+
request.start,
|
|
645
|
+
'A PublicAPI v4 resource request uses a dynamic or unresolved HTTP method.',
|
|
646
|
+
'Use a literal GET, POST, PUT, PATCH, or DELETE method so Gofer can prove the mutation contract.'
|
|
647
|
+
)
|
|
648
|
+
);
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const method = methodResult.method;
|
|
653
|
+
const bodyExpression = topLevelPropertyExpression(optionsExpression, 'body') || '';
|
|
654
|
+
const routingFamily = canonicalResourceRoutingFamily(route);
|
|
655
|
+
const isObjectTypeManagement =
|
|
656
|
+
/\/object-types(?:\/|['"`}]|$)/.test(route) ||
|
|
657
|
+
routingFamily === 'objectTypes' ||
|
|
658
|
+
routingFamily === 'objectType';
|
|
659
|
+
const isAction = /\/actions\//.test(route) || routingFamily === 'action';
|
|
660
|
+
const isOperation = /\/operations\//.test(route);
|
|
661
|
+
const routeShapeResolved =
|
|
662
|
+
/\/v4\/data\/resources/.test(route) ||
|
|
663
|
+
/\b(?:this\.)?resources?BaseUrl\s*\(/.test(route) ||
|
|
664
|
+
/\b(?:this\.)?resourceUrl\s*\(/.test(route) ||
|
|
665
|
+
routingFamily !== undefined;
|
|
666
|
+
|
|
667
|
+
if (method === 'PATCH' && !isObjectTypeManagement) {
|
|
668
|
+
violations.push(
|
|
669
|
+
violation(
|
|
670
|
+
'EAI_V4_RESOURCE_PATCH_FORBIDDEN',
|
|
671
|
+
file,
|
|
672
|
+
content,
|
|
673
|
+
request.start,
|
|
674
|
+
'PATCH is not a valid PublicAPI v4 resource record update.',
|
|
675
|
+
'Use PUT with {"data": {...}, "version": n}.'
|
|
676
|
+
)
|
|
677
|
+
);
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (!routeShapeResolved && ['POST', 'PUT'].includes(method)) {
|
|
682
|
+
violations.push(
|
|
683
|
+
violation(
|
|
684
|
+
'EAI_V4_RESOURCE_PATTERN_UNRESOLVED',
|
|
685
|
+
file,
|
|
686
|
+
content,
|
|
687
|
+
request.start,
|
|
688
|
+
'A PublicAPI v4 resource mutation route is constructed dynamically and its route family cannot be proven.',
|
|
689
|
+
'Use the canonical resource SDK or keep the literal /v4/data/resources route in the fetch call.'
|
|
690
|
+
)
|
|
691
|
+
);
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
if (method === 'PUT' && !isObjectTypeManagement) {
|
|
696
|
+
if (!hasObjectEnvelope(bodyExpression, ['data', 'version'])) {
|
|
697
|
+
violations.push(
|
|
698
|
+
violation(
|
|
699
|
+
'EAI_V4_RESOURCE_ENVELOPE_REQUIRED',
|
|
700
|
+
file,
|
|
701
|
+
content,
|
|
702
|
+
request.start,
|
|
703
|
+
'A PublicAPI v4 resource update is not provably wrapped in data and version.',
|
|
704
|
+
'Send PUT with JSON.stringify({ data, version }).'
|
|
705
|
+
)
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
continue;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
if (method !== 'POST') continue;
|
|
712
|
+
if (isAction || isOperation) {
|
|
713
|
+
if (!hasObjectEnvelope(bodyExpression, ['params'])) {
|
|
714
|
+
violations.push(
|
|
715
|
+
violation(
|
|
716
|
+
'EAI_V4_RESOURCE_ENVELOPE_REQUIRED',
|
|
717
|
+
file,
|
|
718
|
+
content,
|
|
719
|
+
request.start,
|
|
720
|
+
`A PublicAPI v4 resource ${isOperation ? 'operation' : 'action'} is missing the params envelope.`,
|
|
721
|
+
'Send POST with JSON.stringify({ params }).'
|
|
722
|
+
)
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
if (
|
|
728
|
+
isObjectTypeManagement ||
|
|
729
|
+
NON_RECORD_MUTATION_PATTERN.test(route) ||
|
|
730
|
+
RESOURCE_ROUTING_NON_RECORD_FAMILIES.has(routingFamily)
|
|
731
|
+
) {
|
|
732
|
+
continue;
|
|
733
|
+
}
|
|
734
|
+
if (isResourceMemberRoute(route)) {
|
|
735
|
+
violations.push(
|
|
736
|
+
violation(
|
|
737
|
+
'EAI_V4_RESOURCE_METHOD_REQUIRED',
|
|
738
|
+
file,
|
|
739
|
+
content,
|
|
740
|
+
request.start,
|
|
741
|
+
'POST is not a valid PublicAPI v4 resource record update.',
|
|
742
|
+
'Use PUT with JSON.stringify({ data, version }) for a member resource route.'
|
|
743
|
+
)
|
|
744
|
+
);
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
if (!hasObjectEnvelope(bodyExpression, ['data'])) {
|
|
748
|
+
violations.push(
|
|
749
|
+
violation(
|
|
750
|
+
'EAI_V4_RESOURCE_ENVELOPE_REQUIRED',
|
|
751
|
+
file,
|
|
752
|
+
content,
|
|
753
|
+
request.start,
|
|
754
|
+
'A PublicAPI v4 resource create is not provably wrapped in data.',
|
|
755
|
+
'Send POST with JSON.stringify({ data }).'
|
|
756
|
+
)
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
const actionCalls = callSnippets(
|
|
762
|
+
content,
|
|
763
|
+
'[A-Za-z_$][\\w$]*(?:\\s*\\))?\\s*\\.\\s*executeAction'
|
|
764
|
+
);
|
|
765
|
+
for (const [actionIndex, actionCall] of actionCalls.entries()) {
|
|
766
|
+
const receiver = actionCall.text.match(
|
|
767
|
+
/^([A-Za-z_$][\w$]*)\s*(?:\)\s*)?\.\s*executeAction/
|
|
768
|
+
)?.[1];
|
|
769
|
+
if (!receiver) continue;
|
|
770
|
+
let flowEnd = enclosingBlockEnd(content, actionCall.end);
|
|
771
|
+
for (const nextAction of actionCalls.slice(actionIndex + 1)) {
|
|
772
|
+
if (nextAction.start >= flowEnd) break;
|
|
773
|
+
const nextReceiver = nextAction.text.match(
|
|
774
|
+
/^([A-Za-z_$][\w$]*)\s*(?:\)\s*)?\.\s*executeAction/
|
|
775
|
+
)?.[1];
|
|
776
|
+
if (nextReceiver === receiver) {
|
|
777
|
+
flowEnd = nextAction.start;
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
const afterAction = content.slice(actionCall.end, flowEnd);
|
|
782
|
+
const escapedReceiver = receiver.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
783
|
+
const actionBinding = actionResultBinding(content, actionCall.start);
|
|
784
|
+
const updateCalls = callSnippets(afterAction, `${escapedReceiver}\\s*\\.\\s*update`);
|
|
785
|
+
|
|
786
|
+
for (const updateCall of updateCalls) {
|
|
787
|
+
const versionExpression = callArguments(updateCall.text, updateCall.openOffset)[3];
|
|
788
|
+
const acceptedVersions = versionExpressionsBeforeUpdate(
|
|
789
|
+
actionBinding,
|
|
790
|
+
afterAction.slice(0, updateCall.start)
|
|
791
|
+
);
|
|
792
|
+
const usesActionVersion =
|
|
793
|
+
versionExpression &&
|
|
794
|
+
acceptedVersions.has(normalizedExpression(versionExpression));
|
|
795
|
+
|
|
796
|
+
if (!usesActionVersion) {
|
|
797
|
+
violations.push(
|
|
798
|
+
violation(
|
|
799
|
+
'EAI_V4_RESOURCE_STALE_VERSION_FLOW',
|
|
800
|
+
file,
|
|
801
|
+
content,
|
|
802
|
+
actionCall.end + updateCall.start,
|
|
803
|
+
'An action is followed by update without passing the action result version as the update version argument.',
|
|
804
|
+
'Capture executeAction(), then call updateFrom(objectType, actionResult, data) or pass actionResult.version as update() argument four.'
|
|
805
|
+
)
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return violations;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function rawObjectTypeRouteFindings(content, file) {
|
|
815
|
+
if (file.replace(/\\/g, '/') === OBJECT_TYPE_ROUTING_CONFIG.soleOwner) return [];
|
|
816
|
+
|
|
817
|
+
const findings = [];
|
|
818
|
+
const pattern = /(?:\/api\/eai)?\/v4\/data\/resources\b/g;
|
|
819
|
+
let match;
|
|
820
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
821
|
+
const prefix = content.slice(0, match.index);
|
|
822
|
+
const lineStart = Math.max(prefix.lastIndexOf('\n'), prefix.lastIndexOf('\r')) + 1;
|
|
823
|
+
findings.push({
|
|
824
|
+
contractVersion: 'eai.object-type-routing/v1',
|
|
825
|
+
rule: 'OBJECT_TYPE_DIRECT_ROUTE_CONSTRUCTION',
|
|
826
|
+
classification: 'blocking_source_drift',
|
|
827
|
+
severity: 'error',
|
|
828
|
+
location: {
|
|
829
|
+
kind: 'source',
|
|
830
|
+
file,
|
|
831
|
+
line: lineNumber(content, match.index),
|
|
832
|
+
column: match.index - lineStart + 1,
|
|
833
|
+
},
|
|
834
|
+
field: null,
|
|
835
|
+
offendingValue: match[0],
|
|
836
|
+
expectedValue: 'shared platform SDK or approved BFF route owner',
|
|
837
|
+
remediation: OBJECT_TYPE_ROUTING_CONFIG.canonicalGuidance,
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
return findings;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
async function sourceFiles(root) {
|
|
844
|
+
const files = [];
|
|
845
|
+
|
|
846
|
+
async function visit(directory) {
|
|
847
|
+
let entries;
|
|
848
|
+
try {
|
|
849
|
+
entries = await fs.readdir(directory, { withFileTypes: true });
|
|
850
|
+
} catch (error) {
|
|
851
|
+
if (error?.code === 'ENOENT') return;
|
|
852
|
+
throw error;
|
|
853
|
+
}
|
|
854
|
+
for (const entry of entries) {
|
|
855
|
+
if (entry.isDirectory() && OBJECT_TYPE_ROUTING_CONFIG.excludedDirectories.has(entry.name)) continue;
|
|
856
|
+
const absolute = path.join(directory, entry.name);
|
|
857
|
+
if (entry.isDirectory()) {
|
|
858
|
+
await visit(absolute);
|
|
859
|
+
} else if (SOURCE_EXTENSIONS.has(path.extname(entry.name))) {
|
|
860
|
+
files.push(absolute);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
for (const repositoryRoot of ['', ...OBJECT_TYPE_ROUTING_CONFIG.governedRepositoryRoots]) {
|
|
866
|
+
for (const fixedRoot of OBJECT_TYPE_ROUTING_CONFIG.fixedRoots) {
|
|
867
|
+
await visit(path.join(root, repositoryRoot, fixedRoot));
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
const soleOwner = path.join(root, ...OBJECT_TYPE_ROUTING_CONFIG.soleOwner.split('/'));
|
|
871
|
+
try {
|
|
872
|
+
if ((await fs.stat(soleOwner)).isFile()) files.push(soleOwner);
|
|
873
|
+
} catch (error) {
|
|
874
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
875
|
+
}
|
|
876
|
+
return [...new Set(files)].sort((left, right) => left.localeCompare(right));
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
export async function validateWorkspace(workspace) {
|
|
880
|
+
const root = path.resolve(workspace);
|
|
881
|
+
const files = await sourceFiles(root);
|
|
882
|
+
const violations = [];
|
|
883
|
+
for (const file of files) {
|
|
884
|
+
const content = await fs.readFile(file, 'utf8');
|
|
885
|
+
const relativeFile = path.relative(root, file).split(path.sep).join('/');
|
|
886
|
+
violations.push(...validateSourceContent(content, relativeFile));
|
|
887
|
+
violations.push(...rawObjectTypeRouteFindings(content, relativeFile));
|
|
888
|
+
}
|
|
889
|
+
violations.sort((left, right) => {
|
|
890
|
+
const leftFile = left.location?.file ?? left.file ?? '';
|
|
891
|
+
const rightFile = right.location?.file ?? right.file ?? '';
|
|
892
|
+
return (
|
|
893
|
+
leftFile.localeCompare(rightFile) ||
|
|
894
|
+
(left.location?.line ?? left.line ?? 0) - (right.location?.line ?? right.line ?? 0) ||
|
|
895
|
+
String(left.rule ?? left.ruleId).localeCompare(String(right.rule ?? right.ruleId))
|
|
896
|
+
);
|
|
897
|
+
});
|
|
898
|
+
return {
|
|
899
|
+
valid: violations.length === 0,
|
|
900
|
+
filesScanned: files.length,
|
|
901
|
+
violations,
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
function parseArgs(argv) {
|
|
906
|
+
const args = { workspace: process.cwd(), json: false };
|
|
907
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
908
|
+
if (argv[index] === '--workspace') {
|
|
909
|
+
const workspace = argv[index + 1];
|
|
910
|
+
if (!workspace || workspace.startsWith('-')) {
|
|
911
|
+
throw new Error('--workspace requires a path.');
|
|
912
|
+
}
|
|
913
|
+
args.workspace = workspace;
|
|
914
|
+
index += 1;
|
|
915
|
+
} else if (argv[index] === '--json') {
|
|
916
|
+
args.json = true;
|
|
917
|
+
} else if (argv[index] === '--help' || argv[index] === '-h') {
|
|
918
|
+
process.stdout.write(
|
|
919
|
+
'Usage: validate-v4-resource-contract.mjs [--workspace <path>] [--json]\n'
|
|
920
|
+
);
|
|
921
|
+
process.exit(0);
|
|
922
|
+
} else {
|
|
923
|
+
throw new Error(`Unknown argument: ${argv[index]}`);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
return args;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
async function main() {
|
|
930
|
+
const args = parseArgs(process.argv.slice(2));
|
|
931
|
+
const result = await validateWorkspace(args.workspace);
|
|
932
|
+
if (args.json) {
|
|
933
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
934
|
+
} else if (result.valid) {
|
|
935
|
+
process.stdout.write(
|
|
936
|
+
`PASS PublicAPI v4 resource mutation contract (${result.filesScanned} files scanned)\n`
|
|
937
|
+
);
|
|
938
|
+
} else {
|
|
939
|
+
for (const item of result.violations) {
|
|
940
|
+
const rule = item.rule ?? item.ruleId;
|
|
941
|
+
const location = item.location ?? { file: item.file, line: item.line };
|
|
942
|
+
process.stderr.write(
|
|
943
|
+
`${location.file}:${location.line} ${rule}: ${item.message ?? item.remediation}\n` +
|
|
944
|
+
` Fix: ${item.remediation}\n` +
|
|
945
|
+
(item.documentation ? ` Docs: ${item.documentation}\n` : '')
|
|
946
|
+
);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
process.exitCode = result.valid ? 0 : 1;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
if (IS_DIRECT_RUN) {
|
|
953
|
+
main().catch((error) => {
|
|
954
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
955
|
+
process.exit(1);
|
|
956
|
+
});
|
|
957
|
+
}
|