@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,334 @@
|
|
|
1
|
+
import { open, realpath, unlink } from 'node:fs/promises';
|
|
2
|
+
import { constants } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { executionRevision } from './gofer-verified-execution.mjs';
|
|
6
|
+
|
|
7
|
+
const LIMIT = 8 * 1024 * 1024;
|
|
8
|
+
const taskId = value => typeof value === 'string' && /^T\d+$/.test(value);
|
|
9
|
+
const text = value => typeof value === 'string' && value.length > 0;
|
|
10
|
+
|
|
11
|
+
async function readJournal(root) {
|
|
12
|
+
const file = await open(path.join(root, 'verified-execution.jsonl'), constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
13
|
+
try {
|
|
14
|
+
const stat = await file.stat();
|
|
15
|
+
if (!stat.isFile() || stat.size > LIMIT) throw new Error('INVALID_RECOVERY_JOURNAL');
|
|
16
|
+
const chunks = [];
|
|
17
|
+
let total = 0;
|
|
18
|
+
while (true) {
|
|
19
|
+
const buffer = Buffer.alloc(Math.min(65536, LIMIT - total + 1));
|
|
20
|
+
const { bytesRead } = await file.read(buffer);
|
|
21
|
+
if (!bytesRead) break;
|
|
22
|
+
total += bytesRead;
|
|
23
|
+
if (total > LIMIT) throw new Error('RECOVERY_JOURNAL_LIMIT');
|
|
24
|
+
chunks.push(buffer.subarray(0, bytesRead));
|
|
25
|
+
}
|
|
26
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
27
|
+
} finally { await file.close(); }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Read-only reconciliation. This never clears a journal or replays a side effect. */
|
|
31
|
+
export async function inspectExecutionRecovery({ featureDir, verifyReceipt, verifyCancellation, inspectWorkers, inspectLedger, timeoutMs = 2000 }) {
|
|
32
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 10000) throw new Error('INVALID_INSPECTION_LIMIT');
|
|
33
|
+
let inspectionDeadline = Date.now() + timeoutMs;
|
|
34
|
+
let inspectionTimedOut = false;
|
|
35
|
+
const bounded = async callback => {
|
|
36
|
+
if (inspectionTimedOut || Date.now() >= inspectionDeadline) throw new Error('INSPECTION_TIMEOUT');
|
|
37
|
+
let timer;
|
|
38
|
+
try { return await Promise.race([
|
|
39
|
+
Promise.resolve().then(() => { if (inspectionTimedOut || Date.now() >= inspectionDeadline) throw new Error('INSPECTION_TIMEOUT'); return callback(); }),
|
|
40
|
+
new Promise((_, reject) => { timer = setTimeout(() => { inspectionTimedOut = true; reject(new Error('INSPECTION_TIMEOUT')); }, Math.max(0, inspectionDeadline - Date.now())); }),
|
|
41
|
+
]); } finally { clearTimeout(timer); }
|
|
42
|
+
};
|
|
43
|
+
const root = await realpath(featureDir);
|
|
44
|
+
const raw = await readJournal(root);
|
|
45
|
+
const journalHash = createHash('sha256').update(raw).digest('hex');
|
|
46
|
+
const report = { status: 'blocked', journalHash, resumeAllowed: false, replayAllowed: false,
|
|
47
|
+
reusableTasks: [], restartableTasks: [], verifiedInputs: {}, uncertainTasks: [], callsConsumed: 0, attemptsConsumed: {}, reasons: [],
|
|
48
|
+
coverage: 'Read-only reconciliation; trusted receipt and worker inspection are required. No automatic replay.' };
|
|
49
|
+
const reasons = report.reasons;
|
|
50
|
+
if (!raw.endsWith('\n')) { reasons.push('TRUNCATED_JOURNAL'); return report; }
|
|
51
|
+
let events;
|
|
52
|
+
try { events = raw.trimEnd().split('\n').map(line => JSON.parse(line)); }
|
|
53
|
+
catch { reasons.push('INVALID_JOURNAL_JSON'); return report; }
|
|
54
|
+
const first = events[0];
|
|
55
|
+
if (events.length > 50000 || first?.event !== 'started' || first.schemaVersion !== 1 ||
|
|
56
|
+
!text(first.revision) || !Number.isSafeInteger(first.maxCalls) || first.maxCalls <= 0 ||
|
|
57
|
+
!Number.isFinite(first.deadlineMs) || !Number.isInteger(first.maxConcurrent) || first.maxConcurrent < 1 || first.maxConcurrent > 8 ||
|
|
58
|
+
!Number.isSafeInteger(first.maxIterations) || first.maxIterations < 1 || !first.requiredChecks ||
|
|
59
|
+
typeof first.requiredChecks !== 'object' || Array.isArray(first.requiredChecks) ||
|
|
60
|
+
!Object.keys(first.requiredChecks).length || Object.keys(first.requiredChecks).length > 1000 || Object.entries(first.requiredChecks).some(([task, checks]) =>
|
|
61
|
+
!taskId(task) || !Array.isArray(checks) || !checks.length || checks.length > 256 || checks.some(c => !text(c)) || new Set(checks).size !== checks.length)) {
|
|
62
|
+
reasons.push('INVALID_RUN_HEADER'); return report;
|
|
63
|
+
}
|
|
64
|
+
inspectionDeadline = Math.min(inspectionDeadline, first.deadlineMs);
|
|
65
|
+
report.run = { revision: first.revision, maxCalls: first.maxCalls, maxConcurrent: first.maxConcurrent,
|
|
66
|
+
maxIterations: first.maxIterations, deadlineMs: first.deadlineMs, requiredChecks: first.requiredChecks,
|
|
67
|
+
capabilityReceiptHash: first.capabilityReceiptHash, selectedModel: first.selectedModel,
|
|
68
|
+
benchmarkReceipt: first.benchmarkReceipt, approvalReceipt: first.approvalReceipt };
|
|
69
|
+
if (await executionRevision(root) !== first.revision) reasons.push('STALE_DIRECTION');
|
|
70
|
+
if (Date.now() >= first.deadlineMs) reasons.push('DEADLINE_EXHAUSTED');
|
|
71
|
+
const uncertain = new Set();
|
|
72
|
+
const receipts = new Map();
|
|
73
|
+
const latestChecks = new Map();
|
|
74
|
+
const authorizations = [];
|
|
75
|
+
const commits = [];
|
|
76
|
+
const authorizedTasks = new Map();
|
|
77
|
+
const commitAuthorities = new Map();
|
|
78
|
+
const cancellations = new Map();
|
|
79
|
+
const active = new Set();
|
|
80
|
+
const methods = new Set(['reserve', 'lease', 'execute', 'inputRevision', 'check', 'verified']);
|
|
81
|
+
const known = new Set(['started', 'resumed', 'attempt_reserved', 'call_reserved', 'lease_granted', 'ledger_authorized', 'check', 'commit_authorized', 'verified', 'repair_required', 'blocked', 'cancelled', 'cancel_reconciled', 'stale', 'finished']);
|
|
82
|
+
let finished = false;
|
|
83
|
+
for (const [index, event] of events.entries()) {
|
|
84
|
+
if (!event || event.schemaVersion !== 1 || event.revision !== first.revision || !known.has(event.event) ||
|
|
85
|
+
(index > 0 && event.event === 'started') || (finished && !['resumed', 'cancel_reconciled'].includes(event.event)) ||
|
|
86
|
+
(!['started', 'finished', 'resumed'].includes(event.event) && !taskId(event.task))) {
|
|
87
|
+
reasons.push('INVALID_JOURNAL_SEQUENCE'); break;
|
|
88
|
+
}
|
|
89
|
+
if (event.event === 'resumed') {
|
|
90
|
+
const prefix = events.slice(0, index).map(record => `${JSON.stringify(record)}\n`).join('');
|
|
91
|
+
const expectedHash = createHash('sha256').update(prefix).digest('hex');
|
|
92
|
+
if (!receipts.size && !cancellations.size || event.previousJournalHash !== expectedHash ||
|
|
93
|
+
event.callsConsumed !== report.callsConsumed ||
|
|
94
|
+
JSON.stringify(event.attemptsConsumed) !== JSON.stringify(report.attemptsConsumed) ||
|
|
95
|
+
[...uncertain].some(task => !receipts.has(task) && !cancellations.has(task))) {
|
|
96
|
+
reasons.push('INVALID_RESUME_HISTORY'); break;
|
|
97
|
+
}
|
|
98
|
+
finished = false;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (event.event === 'cancel_reconciled') {
|
|
102
|
+
const prefix = events.slice(0, index).map(record => `${JSON.stringify(record)}\n`).join('');
|
|
103
|
+
const expectedHash = createHash('sha256').update(prefix).digest('hex');
|
|
104
|
+
const prior = authorizedTasks.get(event.task);
|
|
105
|
+
const cancelled = events.slice(0, index).reverse().find(record => record.task === event.task && record.event === 'cancelled');
|
|
106
|
+
if (!prior || !cancelled || receipts.has(event.task) || cancellations.has(event.task) ||
|
|
107
|
+
event.journalHash !== expectedHash || event.attempt !== report.attemptsConsumed[event.task] ||
|
|
108
|
+
event.leaseId !== prior.leaseId || event.abandonedWorktreeReceipt !== prior.worktreeReceipt ||
|
|
109
|
+
!text(event.replacementWorktreeReceipt) || event.replacementWorktreeReceipt === event.abandonedWorktreeReceipt ||
|
|
110
|
+
!text(event.workerStopReceipt) || !text(event.receipt) || !text(event.inputRevision)) {
|
|
111
|
+
reasons.push('INVALID_CANCELLATION_RECONCILIATION'); break;
|
|
112
|
+
}
|
|
113
|
+
cancellations.set(event.task, event);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const verifiedRead = receipts.has(event.task) && event.event === 'call_reserved' && event.method === 'inputRevision';
|
|
117
|
+
if (event.task && !verifiedRead) uncertain.add(event.task);
|
|
118
|
+
if (event.task && (!Object.hasOwn(first.requiredChecks, event.task) || (receipts.has(event.task) && !verifiedRead))) {
|
|
119
|
+
receipts.delete(event.task); reasons.push('INVALID_LATE_OR_UNKNOWN_TASK'); break;
|
|
120
|
+
}
|
|
121
|
+
if (event.event === 'attempt_reserved') {
|
|
122
|
+
const expected = (report.attemptsConsumed[event.task] || 0) + 1;
|
|
123
|
+
if (event.attempt !== expected || expected > first.maxIterations) { reasons.push('INVALID_ATTEMPT_HISTORY'); break; }
|
|
124
|
+
report.attemptsConsumed[event.task] = expected;
|
|
125
|
+
latestChecks.set(event.task, new Map());
|
|
126
|
+
active.delete(event.task);
|
|
127
|
+
}
|
|
128
|
+
if (event.event === 'call_reserved') {
|
|
129
|
+
if (event.call !== report.callsConsumed + 1 || !methods.has(event.method) ||
|
|
130
|
+
event.call > first.maxCalls) { reasons.push('INVALID_CALL_HISTORY'); break; }
|
|
131
|
+
report.callsConsumed = event.call;
|
|
132
|
+
if (!report.attemptsConsumed[event.task]) { reasons.push('INVALID_CALL_WITHOUT_ATTEMPT'); break; }
|
|
133
|
+
if (event.method === 'execute') active.add(event.task);
|
|
134
|
+
if (active.size > first.maxConcurrent) { reasons.push('INVALID_CONCURRENCY_HISTORY'); break; }
|
|
135
|
+
}
|
|
136
|
+
if (event.event === 'lease_granted' && (!report.attemptsConsumed[event.task] || !text(event.leaseId) || !text(event.expiresAt))) {
|
|
137
|
+
reasons.push('INVALID_LEASE_HISTORY'); break;
|
|
138
|
+
}
|
|
139
|
+
if (event.event === 'ledger_authorized') {
|
|
140
|
+
if (!report.attemptsConsumed[event.task] || !text(event.leaseId) || !text(event.receipt) ||
|
|
141
|
+
!text(event.capabilityReceiptHash)) { reasons.push('INVALID_LEDGER_AUTHORIZATION'); break; }
|
|
142
|
+
authorizations.push({ taskId: event.task, leaseId: event.leaseId, receipt: event.receipt,
|
|
143
|
+
capabilityReceiptHash: event.capabilityReceiptHash, worktreeReceipt: event.worktreeReceipt });
|
|
144
|
+
authorizedTasks.set(event.task, event);
|
|
145
|
+
}
|
|
146
|
+
if (event.event === 'check') {
|
|
147
|
+
if (!active.has(event.task) || event.attempt !== report.attemptsConsumed[event.task] ||
|
|
148
|
+
!first.requiredChecks[event.task].includes(event.check)) { reasons.push('INVALID_CHECK_HISTORY'); break; }
|
|
149
|
+
latestChecks.get(event.task).set(event.check, event);
|
|
150
|
+
}
|
|
151
|
+
if (event.event === 'verified') {
|
|
152
|
+
if (!active.has(event.task) || !text(event.inputRevision) || !text(event.receipt) || receipts.has(event.task) ||
|
|
153
|
+
!authorizedTasks.has(event.task) || commitAuthorities.get(event.task)?.inputRevision !== event.inputRevision ||
|
|
154
|
+
first.requiredChecks[event.task].some(check => {
|
|
155
|
+
const proof = latestChecks.get(event.task)?.get(check);
|
|
156
|
+
return proof?.passed !== true || proof.inputRevision !== event.inputRevision || !text(proof.receipt);
|
|
157
|
+
})) {
|
|
158
|
+
reasons.push('INVALID_VERIFICATION_RECORD'); break;
|
|
159
|
+
}
|
|
160
|
+
receipts.set(event.task, event);
|
|
161
|
+
active.delete(event.task);
|
|
162
|
+
}
|
|
163
|
+
if (event.event === 'commit_authorized') {
|
|
164
|
+
const authority = authorizedTasks.get(event.task);
|
|
165
|
+
if (!active.has(event.task) || !authority || !text(event.leaseId) ||
|
|
166
|
+
!text(event.receipt) || !text(event.inputRevision) ||
|
|
167
|
+
!text(event.capabilityReceiptHash) ||
|
|
168
|
+
event.attempt !== authority.attempt || event.leaseId !== authority.leaseId ||
|
|
169
|
+
event.capabilityReceiptHash !== authority.capabilityReceiptHash ||
|
|
170
|
+
commitAuthorities.get(event.task)?.attempt === event.attempt ||
|
|
171
|
+
first.requiredChecks[event.task].some(check => {
|
|
172
|
+
const proof = latestChecks.get(event.task)?.get(check);
|
|
173
|
+
return proof?.passed !== true || proof.inputRevision !== event.inputRevision || !text(proof.receipt);
|
|
174
|
+
})) {
|
|
175
|
+
reasons.push('INVALID_COMMIT_AUTHORIZATION'); break;
|
|
176
|
+
}
|
|
177
|
+
commits.push({ taskId: event.task, leaseId: event.leaseId, receipt: event.receipt,
|
|
178
|
+
inputRevision: event.inputRevision, capabilityReceiptHash: event.capabilityReceiptHash });
|
|
179
|
+
commitAuthorities.set(event.task, event);
|
|
180
|
+
}
|
|
181
|
+
if (['blocked', 'cancelled', 'stale', 'repair_required'].includes(event.event)) active.delete(event.task);
|
|
182
|
+
if (event.event === 'finished') {
|
|
183
|
+
finished = true;
|
|
184
|
+
if (event.adapterCallsSettled === false) reasons.push('ADAPTER_CALLS_NOT_SETTLED');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (report.callsConsumed >= first.maxCalls) reasons.push('CALL_LIMIT_EXHAUSTED');
|
|
188
|
+
if (reasons.some(reason => /^(INVALID|STALE)/.test(reason))) {
|
|
189
|
+
report.uncertainTasks = [...uncertain]; return report;
|
|
190
|
+
}
|
|
191
|
+
if (uncertain.size === 1 && events.at(-1)?.event === 'finished' && events.at(-1)?.adapterCallsSettled === true) {
|
|
192
|
+
const task = [...uncertain][0];
|
|
193
|
+
const authority = authorizedTasks.get(task);
|
|
194
|
+
const cancelled = events.findLast(event => event.task === task && event.event === 'cancelled');
|
|
195
|
+
if (authority && cancelled && !cancellations.has(task) && !receipts.has(task) &&
|
|
196
|
+
authority.attempt === report.attemptsConsumed[task] && text(authority.worktreeReceipt) &&
|
|
197
|
+
text(authority.budgetReservation) && !commits.some(item => item.taskId === task && item.leaseId === authority.leaseId)) {
|
|
198
|
+
report.pendingCancellation = { taskId: task, revision: first.revision, attempt: authority.attempt,
|
|
199
|
+
leaseId: authority.leaseId, abandonedWorktreeReceipt: authority.worktreeReceipt,
|
|
200
|
+
budgetReservation: authority.budgetReservation, capabilityReceiptHash: authority.capabilityReceiptHash,
|
|
201
|
+
approvalReceipt: first.approvalReceipt };
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// A journal proves recorded actions, not whether a local or remote worker is gone.
|
|
205
|
+
if (typeof inspectWorkers !== 'function') reasons.push('WORKER_INSPECTION_REQUIRED');
|
|
206
|
+
else {
|
|
207
|
+
try {
|
|
208
|
+
const workers = await bounded(() => inspectWorkers({ revision: first.revision, journalHash,
|
|
209
|
+
authorizations: structuredClone(authorizations) }));
|
|
210
|
+
if (workers?.allStopped !== true || workers.revision !== first.revision ||
|
|
211
|
+
workers.journalHash !== journalHash || !text(workers.receipt)) reasons.push('WORKERS_NOT_RECONCILED');
|
|
212
|
+
else report.workerStopReceipt = workers.receipt;
|
|
213
|
+
} catch { reasons.push('WORKER_INSPECTION_FAILED'); }
|
|
214
|
+
}
|
|
215
|
+
if (typeof verifyReceipt !== 'function') reasons.push('RECEIPT_VERIFIER_REQUIRED');
|
|
216
|
+
if (cancellations.size && typeof verifyCancellation !== 'function') reasons.push('CANCELLATION_VERIFIER_REQUIRED');
|
|
217
|
+
if (typeof inspectLedger !== 'function') reasons.push('LEDGER_INSPECTION_REQUIRED');
|
|
218
|
+
else {
|
|
219
|
+
try {
|
|
220
|
+
const result = await bounded(() => inspectLedger({ revision: first.revision, journalHash,
|
|
221
|
+
authorizations: structuredClone(authorizations), commits: structuredClone(commits) }));
|
|
222
|
+
if (result?.allowed !== true || result.revision !== first.revision || result.journalHash !== journalHash) {
|
|
223
|
+
reasons.push('LEDGER_NOT_RECONCILED');
|
|
224
|
+
}
|
|
225
|
+
} catch { reasons.push('LEDGER_INSPECTION_FAILED'); }
|
|
226
|
+
}
|
|
227
|
+
if (!reasons.length) {
|
|
228
|
+
for (const [task, event] of receipts) {
|
|
229
|
+
if (inspectionTimedOut || Date.now() >= inspectionDeadline) { reasons.push('INSPECTION_DEADLINE_EXHAUSTED'); break; }
|
|
230
|
+
try {
|
|
231
|
+
const result = await bounded(() => verifyReceipt({ taskId: task, revision: first.revision,
|
|
232
|
+
inputRevision: event.inputRevision, receipt: event.receipt, journalHash }));
|
|
233
|
+
if (result?.valid !== true || result.taskId !== task || result.revision !== first.revision ||
|
|
234
|
+
result.inputRevision !== event.inputRevision || result.receipt !== event.receipt || result.journalHash !== journalHash) throw new Error('UNVERIFIED');
|
|
235
|
+
report.reusableTasks.push(task); uncertain.delete(task);
|
|
236
|
+
report.verifiedInputs[task] = event.inputRevision;
|
|
237
|
+
} catch { reasons.push(`RECEIPT_NOT_VERIFIED:${task}`); }
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (!reasons.length) {
|
|
241
|
+
for (const [task, event] of cancellations) {
|
|
242
|
+
if (inspectionTimedOut || Date.now() >= inspectionDeadline) { reasons.push('INSPECTION_DEADLINE_EXHAUSTED'); break; }
|
|
243
|
+
try {
|
|
244
|
+
const request = { taskId: task, revision: first.revision, leaseId: event.leaseId,
|
|
245
|
+
attempt: event.attempt, journalHash: event.journalHash, receipt: event.receipt,
|
|
246
|
+
workerStopReceipt: event.workerStopReceipt, abandonedWorktreeReceipt: event.abandonedWorktreeReceipt,
|
|
247
|
+
replacementWorktreeReceipt: event.replacementWorktreeReceipt, inputRevision: event.inputRevision };
|
|
248
|
+
const result = await bounded(() => verifyCancellation(structuredClone(request)));
|
|
249
|
+
if (result?.valid !== true || Object.entries(request).some(([key, value]) => result[key] !== value)) throw new Error('UNVERIFIED');
|
|
250
|
+
report.restartableTasks.push(task);
|
|
251
|
+
uncertain.delete(task);
|
|
252
|
+
report.replacementWorktreeReceipt = event.replacementWorktreeReceipt;
|
|
253
|
+
} catch { reasons.push(`CANCELLATION_NOT_VERIFIED:${task}`); }
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Detect new journal events or direction changes during asynchronous proof inspection.
|
|
257
|
+
const directionStillMatches = await executionRevision(root) === first.revision;
|
|
258
|
+
if (!directionStillMatches || createHash('sha256').update(await readJournal(root)).digest('hex') !== journalHash) {
|
|
259
|
+
reasons.push('RECOVERY_INPUT_CHANGED');
|
|
260
|
+
for (const task of report.reusableTasks) uncertain.add(task);
|
|
261
|
+
report.reusableTasks = [];
|
|
262
|
+
report.restartableTasks = [];
|
|
263
|
+
report.verifiedInputs = {};
|
|
264
|
+
}
|
|
265
|
+
if (inspectionTimedOut || Date.now() >= inspectionDeadline) {
|
|
266
|
+
reasons.push('INSPECTION_DEADLINE_EXHAUSTED');
|
|
267
|
+
for (const task of report.reusableTasks) uncertain.add(task);
|
|
268
|
+
report.reusableTasks = [];
|
|
269
|
+
report.restartableTasks = [];
|
|
270
|
+
report.verifiedInputs = {};
|
|
271
|
+
}
|
|
272
|
+
report.uncertainTasks = [...uncertain];
|
|
273
|
+
if (uncertain.size) reasons.push('SIDE_EFFECT_RECONCILIATION_REQUIRED');
|
|
274
|
+
if (!reasons.length) {
|
|
275
|
+
report.status = 'reconciled';
|
|
276
|
+
report.resumeAllowed = report.reusableTasks.length > 0 || report.restartableTasks.length > 0;
|
|
277
|
+
}
|
|
278
|
+
return report;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Retire one cancelled lease and journal a fresh-worktree restart boundary. */
|
|
282
|
+
export async function reconcileCancelledExecution({ featureDir, workspaceRoot, abandonedWorkspace,
|
|
283
|
+
replacementWorkspace, worktreeRevision, replacementWorktreeReceipt, inputRevision,
|
|
284
|
+
ledger, inspectWorkers, verifyReceipt, timeoutMs = 2000 } = {}) {
|
|
285
|
+
if (!ledger || typeof ledger.reconcileCancelledLease !== 'function' ||
|
|
286
|
+
typeof ledger.readCancellation !== 'function' || typeof ledger.inspectRecovery !== 'function' ||
|
|
287
|
+
![workspaceRoot, abandonedWorkspace,
|
|
288
|
+
replacementWorkspace, worktreeRevision, replacementWorktreeReceipt, inputRevision].every(text)) {
|
|
289
|
+
throw new Error('CANCELLATION_RECONCILIATION_INPUT_REQUIRED');
|
|
290
|
+
}
|
|
291
|
+
const root = await realpath(featureDir);
|
|
292
|
+
const lock = await open(path.join(root, 'verified-execution.resume.lock'), 'wx', 0o600)
|
|
293
|
+
.catch(() => { throw new Error('RESUME_LOCKED'); });
|
|
294
|
+
try {
|
|
295
|
+
const report = await inspectExecutionRecovery({ featureDir: root, inspectWorkers,
|
|
296
|
+
inspectLedger: request => ledger.inspectRecovery(request),
|
|
297
|
+
verifyReceipt, timeoutMs });
|
|
298
|
+
if (report.status !== 'blocked' || !report.pendingCancellation ||
|
|
299
|
+
report.reasons.length !== 1 || report.reasons[0] !== 'SIDE_EFFECT_RECONCILIATION_REQUIRED' ||
|
|
300
|
+
!text(report.workerStopReceipt) || report.attemptsConsumed[report.pendingCancellation.taskId] >= report.run.maxIterations) {
|
|
301
|
+
throw new Error('CANCELLATION_RECONCILIATION_REQUIRED');
|
|
302
|
+
}
|
|
303
|
+
const { inspectVerifiedWorktree } = await import('./gofer-native-adapter.mjs');
|
|
304
|
+
const [oldWorktree, replacementWorktree] = await Promise.all([
|
|
305
|
+
inspectVerifiedWorktree({ workspaceRoot, isolatedWorkspace: abandonedWorkspace,
|
|
306
|
+
revision: worktreeRevision, receipt: report.pendingCancellation.abandonedWorktreeReceipt }),
|
|
307
|
+
inspectVerifiedWorktree({ workspaceRoot, isolatedWorkspace: replacementWorkspace,
|
|
308
|
+
revision: worktreeRevision, receipt: replacementWorktreeReceipt, requireClean: true }),
|
|
309
|
+
]);
|
|
310
|
+
if (oldWorktree.valid !== true || replacementWorktree.valid !== true ||
|
|
311
|
+
oldWorktree.isolatedWorkspace === replacementWorktree.isolatedWorkspace ||
|
|
312
|
+
oldWorktree.receipt === replacementWorktree.receipt) throw new Error('REPLACEMENT_WORKTREE_UNVERIFIED');
|
|
313
|
+
const request = { ...report.pendingCancellation, journalHash: report.journalHash,
|
|
314
|
+
workerStopReceipt: report.workerStopReceipt, replacementWorktreeReceipt, inputRevision };
|
|
315
|
+
const existing = await ledger.readCancellation(request);
|
|
316
|
+
const result = existing.found === true && JSON.stringify(existing.request) === JSON.stringify(request)
|
|
317
|
+
? { allowed: true, receipt: existing.receipt }
|
|
318
|
+
: await ledger.reconcileCancelledLease(request);
|
|
319
|
+
if (result?.allowed !== true || !text(result.receipt)) throw new Error('LEDGER_CANCELLATION_DENIED');
|
|
320
|
+
if (createHash('sha256').update(await readJournal(root)).digest('hex') !== report.journalHash) {
|
|
321
|
+
throw new Error('RECOVERY_INPUT_CHANGED');
|
|
322
|
+
}
|
|
323
|
+
const event = { schemaVersion: 1, revision: report.run.revision, event: 'cancel_reconciled',
|
|
324
|
+
task: request.taskId, attempt: request.attempt, leaseId: request.leaseId,
|
|
325
|
+
journalHash: request.journalHash, workerStopReceipt: request.workerStopReceipt,
|
|
326
|
+
abandonedWorktreeReceipt: request.abandonedWorktreeReceipt,
|
|
327
|
+
replacementWorktreeReceipt: request.replacementWorktreeReceipt,
|
|
328
|
+
inputRevision: request.inputRevision, receipt: result.receipt };
|
|
329
|
+
const file = await open(path.join(root, 'verified-execution.jsonl'), constants.O_WRONLY | constants.O_APPEND | constants.O_NOFOLLOW);
|
|
330
|
+
try { await file.writeFile(`${JSON.stringify(event)}\n`); await file.sync(); } finally { await file.close(); }
|
|
331
|
+
return { reconciled: true, taskId: request.taskId, receipt: result.receipt,
|
|
332
|
+
replacementWorktreeReceipt, journalHash: request.journalHash };
|
|
333
|
+
} finally { await lock.close(); await unlink(path.join(root, 'verified-execution.resume.lock')).catch(() => {}); }
|
|
334
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grok Build host profile for the shared native launcher. It reuses the same
|
|
3
|
+
* process-group evidence, Git baseline and scope checks as the Codex launcher.
|
|
4
|
+
*
|
|
5
|
+
* Isolation is Grok's own OS sandbox (Seatbelt on macOS), driven by a per-task
|
|
6
|
+
* profile. What live tests found, and what this adapter therefore does:
|
|
7
|
+
*
|
|
8
|
+
* - Grok's built-in `strict` profile still allows writes to the OS temp
|
|
9
|
+
* folder, where Gofer worktrees normally live. This adapter denies the
|
|
10
|
+
* temp folders and so REFUSES a worktree or shared Git store inside them.
|
|
11
|
+
* Keep Grok worktrees under another root, for example under your home folder.
|
|
12
|
+
* - An unknown profile, or one that cannot be applied, only prints a warning
|
|
13
|
+
* and runs UNSANDBOXED. The adapter kills the process the moment that
|
|
14
|
+
* warning appears and rejects the run. Unknown profile keys are silently
|
|
15
|
+
* ignored, so only `extends` and `deny`, which were verified live, are used.
|
|
16
|
+
* - The profile name is random per run, and the profile file is written into
|
|
17
|
+
* the worktree before start and removed after exit.
|
|
18
|
+
* - A worker can READ `~/.grok/auth.json`. It cannot be hidden, because Grok
|
|
19
|
+
* itself cannot start without it. It cannot WRITE anywhere in `~/.grok`.
|
|
20
|
+
*
|
|
21
|
+
* Grok has no spend flag, so cost is bounded by `--max-turns` here and by the
|
|
22
|
+
* caller's spend cap. There is no model catalogue use and no capability
|
|
23
|
+
* receipt in this adapter, and the EAI CLI does not qualify Grok, so this does
|
|
24
|
+
* not by itself pass the EAI isolation gate. See docs/verified-autonomous-runtime.md.
|
|
25
|
+
*/
|
|
26
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
27
|
+
import { randomUUID } from 'node:crypto';
|
|
28
|
+
import { constants, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, closeSync,
|
|
29
|
+
realpathSync, rmSync, rmdirSync, writeFileSync } from 'node:fs';
|
|
30
|
+
import os from 'node:os';
|
|
31
|
+
import path from 'node:path';
|
|
32
|
+
import { startLocalHostInvocation } from './gofer-native-adapter.mjs';
|
|
33
|
+
import { insideDirectory, sharedGitDirectory } from './gofer-worktree-layout.mjs';
|
|
34
|
+
|
|
35
|
+
const text = value => typeof value === 'string' && value.trim().length > 0;
|
|
36
|
+
const TEMPORARY_ROOTS = Object.freeze(['/tmp', '/private/tmp', '/var/tmp', '/private/var/tmp',
|
|
37
|
+
'/var/folders', '/private/var/folders']);
|
|
38
|
+
const PROFILE_FILE = path.join('.grok', 'sandbox.toml');
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The per-task profile, or null when the layout cannot be protected. Only paths
|
|
42
|
+
* that exist are listed: Grok cannot apply a profile that names a missing path.
|
|
43
|
+
* `temporaryRoots` exists for isolated tests; production never passes it.
|
|
44
|
+
*/
|
|
45
|
+
export function grokSandboxPolicy(workspaceRoot, { temporaryRoots = TEMPORARY_ROOTS } = {}) {
|
|
46
|
+
const common = sharedGitDirectory(workspaceRoot);
|
|
47
|
+
if (!common || !Array.isArray(temporaryRoots)) return null;
|
|
48
|
+
let workspace;
|
|
49
|
+
try { workspace = realpathSync(workspaceRoot); } catch { return null; }
|
|
50
|
+
const denied = [...new Set(temporaryRoots.filter(root => path.isAbsolute(root ?? '') && existsSync(root)))];
|
|
51
|
+
const resolved = denied.flatMap(root => { try { return [root, realpathSync(root)]; } catch { return [root]; } });
|
|
52
|
+
// The temp folders are denied, so a worktree inside them would be denied too.
|
|
53
|
+
if (resolved.some(root => insideDirectory(root, workspace) || insideDirectory(root, common))) return null;
|
|
54
|
+
// Never overwrite a project's own sandbox file, tracked or not.
|
|
55
|
+
try { lstatSync(path.join(workspace, PROFILE_FILE)); return null; } catch { /* absent, as required */ }
|
|
56
|
+
const profileName = `gofer-${randomUUID().replace(/-/g, '').slice(0, 16)}`;
|
|
57
|
+
const toml = `[profiles.${profileName}]\nextends = "strict"\ndeny = ${JSON.stringify(denied)}\n`;
|
|
58
|
+
return Object.freeze({ profileName, toml, common, workspace, denied: Object.freeze(denied) });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseResult(stdoutText) {
|
|
62
|
+
const trimmed = String(stdoutText ?? '').trim();
|
|
63
|
+
for (const candidate of [trimmed, trimmed.split('\n').at(-1)]) {
|
|
64
|
+
try { const value = JSON.parse(candidate); if (value && typeof value === 'object') return value; }
|
|
65
|
+
catch { /* try the next candidate */ }
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Token counts in the shape the pricing code expects, plus Grok's own cost.
|
|
71
|
+
* Grok's `input_tokens` excludes cache reads, as Claude's does. */
|
|
72
|
+
export function extractGrokUsage(stdoutText) {
|
|
73
|
+
const result = parseResult(stdoutText);
|
|
74
|
+
const usage = result?.usage;
|
|
75
|
+
const output = usage?.output_tokens;
|
|
76
|
+
const fresh = usage?.input_tokens;
|
|
77
|
+
const cachedRead = usage?.cache_read_input_tokens ?? 0;
|
|
78
|
+
const cachedWrite = usage?.cache_creation_input_tokens ?? 0;
|
|
79
|
+
if (![output, fresh, cachedRead, cachedWrite].every(value => Number.isInteger(value) && value >= 0)) return null;
|
|
80
|
+
const reported = result.total_cost_usd;
|
|
81
|
+
return Object.freeze({ inputTokens: fresh + cachedRead + cachedWrite, cachedInputTokens: cachedRead,
|
|
82
|
+
outputTokens: output, reportedCostUsd: Number.isFinite(reported) && reported >= 0 ? reported : null });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const SANDBOX_FAILURE = /sandbox could not be applied/i;
|
|
86
|
+
|
|
87
|
+
export const GROK_HOST = Object.freeze({
|
|
88
|
+
name: 'grok',
|
|
89
|
+
sandboxPolicy: (workspace, hostOptions) => grokSandboxPolicy(workspace, hostOptions),
|
|
90
|
+
buildArgs: ({ sandboxPolicy, modelId, prompt, workspace, hostOptions }) => {
|
|
91
|
+
const turns = hostOptions?.maxTurns;
|
|
92
|
+
if (!Number.isInteger(turns) || turns < 1 || turns > 200) throw new Error('INVALID_NATIVE_REQUEST');
|
|
93
|
+
return ['-p', prompt, '--cwd', workspace, '--output-format', 'json', '--model', modelId, '--always-approve',
|
|
94
|
+
'--sandbox', sandboxPolicy.profileName, '--max-turns', String(turns), '--no-subagents', '--disable-web-search'];
|
|
95
|
+
},
|
|
96
|
+
// The parent's sandbox variables must not be able to weaken this run.
|
|
97
|
+
env: base => Object.fromEntries(Object.entries(base).filter(([key]) =>
|
|
98
|
+
!key.startsWith('GROK_SANDBOX') && !key.startsWith('GIT_'))),
|
|
99
|
+
fatalStderr: SANDBOX_FAILURE,
|
|
100
|
+
// `wx`: fail closed if the file appeared since the policy was made.
|
|
101
|
+
beforeStart: ({ workspace, sandboxPolicy }) => {
|
|
102
|
+
mkdirSync(path.join(workspace, '.grok'), { recursive: true, mode: 0o700 });
|
|
103
|
+
const descriptor = openSync(path.join(workspace, PROFILE_FILE), constants.O_WRONLY | constants.O_CREAT |
|
|
104
|
+
constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
105
|
+
try { writeFileSync(descriptor, sandboxPolicy.toml); } finally { closeSync(descriptor); }
|
|
106
|
+
},
|
|
107
|
+
cleanup: ({ workspace }) => {
|
|
108
|
+
const file = path.join(workspace, PROFILE_FILE);
|
|
109
|
+
try { if (lstatSync(file).isFile()) rmSync(file, { force: true }); } catch { /* nothing to remove */ }
|
|
110
|
+
try { rmdirSync(path.join(workspace, '.grok')); } catch { /* not empty or absent: left for the scope check */ }
|
|
111
|
+
},
|
|
112
|
+
finalMessage: ({ stdout }) => String(parseResult(stdout)?.text ?? ''),
|
|
113
|
+
extractUsage: stdoutText => extractGrokUsage(stdoutText),
|
|
114
|
+
assertSuccess: (stdoutText, stderrText) => {
|
|
115
|
+
if (SANDBOX_FAILURE.test(String(stderrText ?? ''))) throw new Error('NATIVE_HOST_SANDBOX_NOT_APPLIED');
|
|
116
|
+
const result = parseResult(stdoutText);
|
|
117
|
+
if (!result) throw new Error('NATIVE_HOST_RESULT_UNPARSEABLE');
|
|
118
|
+
if (result.is_error === true || result.stopReason !== 'end_turn') throw new Error('NATIVE_HOST_REPORTED_ERROR');
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
/** `command` must be the pinned absolute path of the Grok executable, and
|
|
123
|
+
* `hostOptions.maxTurns` bounds this run. Grok has no spend flag. */
|
|
124
|
+
export function startLocalGrokInvocation(options = {}) {
|
|
125
|
+
if (!path.isAbsolute(options.command ?? '')) throw new Error('NATIVE_HOST_COMMAND_MUST_BE_PINNED');
|
|
126
|
+
return startLocalHostInvocation({ ...options, host: GROK_HOST });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* A live boundary probe. Grok has no no-model sandbox command, so this asks a
|
|
131
|
+
* real model (about US$0.01) to try one allowed write and four forbidden ones:
|
|
132
|
+
* a sibling folder, the shared Git store, the OS temp folder, and Grok's own
|
|
133
|
+
* home. It then checks the disk and requires Grok's own report to describe the
|
|
134
|
+
* refusals. It spends money, so nothing calls it implicitly. Returns the
|
|
135
|
+
* executable when the boundary held.
|
|
136
|
+
*/
|
|
137
|
+
export function probeGrokSandboxBoundary({ workspaceRoot, executable, model = 'grok-4.5', maxTurns = 8,
|
|
138
|
+
run = spawnSync, temporaryRoots } = {}) {
|
|
139
|
+
if (!text(workspaceRoot) || !path.isAbsolute(executable ?? '')) return null;
|
|
140
|
+
const policy = grokSandboxPolicy(workspaceRoot, temporaryRoots ? { temporaryRoots } : undefined);
|
|
141
|
+
if (!policy) return null;
|
|
142
|
+
const grokHome = path.join(os.homedir(), '.grok');
|
|
143
|
+
let sibling;
|
|
144
|
+
const created = [];
|
|
145
|
+
try {
|
|
146
|
+
sibling = mkdtempSync(path.join(path.dirname(policy.workspace), '.gofer-grok-probe-'));
|
|
147
|
+
const targets = {
|
|
148
|
+
inside: path.join(policy.workspace, `.gofer-grok-probe-${process.pid}`),
|
|
149
|
+
sibling: path.join(sibling, 'outside.txt'),
|
|
150
|
+
store: path.join(policy.common, `.gofer-grok-probe-${process.pid}`),
|
|
151
|
+
temp: path.join(os.tmpdir(), `.gofer-grok-probe-${process.pid}`),
|
|
152
|
+
home: path.join(grokHome, `.gofer-grok-probe-${process.pid}`),
|
|
153
|
+
};
|
|
154
|
+
created.push(...Object.values(targets));
|
|
155
|
+
const prompt = 'This is a file-permission test. Use your shell tool to run exactly these five commands, one at a ' +
|
|
156
|
+
`time, even if an earlier one fails, and never retry or work around a failure: (1) touch ${targets.inside} ` +
|
|
157
|
+
`(2) touch ${targets.sibling} (3) touch ${targets.store} (4) touch ${targets.temp} (5) touch ${targets.home}. ` +
|
|
158
|
+
'Report each exit code and the error text, then stop.';
|
|
159
|
+
const args = GROK_HOST.buildArgs({ sandboxPolicy: policy, modelId: model, prompt, workspace: policy.workspace,
|
|
160
|
+
hostOptions: { maxTurns } });
|
|
161
|
+
GROK_HOST.beforeStart({ workspace: policy.workspace, sandboxPolicy: policy });
|
|
162
|
+
let result;
|
|
163
|
+
try {
|
|
164
|
+
result = run(executable, args, { cwd: policy.workspace, encoding: 'utf8', timeout: 240000,
|
|
165
|
+
maxBuffer: 8 * 1024 * 1024, env: GROK_HOST.env(process.env) });
|
|
166
|
+
} finally { GROK_HOST.cleanup({ workspace: policy.workspace }); }
|
|
167
|
+
const parsed = parseResult(result.stdout);
|
|
168
|
+
const refusals = (String(parsed?.text ?? '').match(/not permitted|permission denied|denied|blocked|refused|not allowed/gi) ?? []).length;
|
|
169
|
+
const held = result.status === 0 && !SANDBOX_FAILURE.test(String(result.stderr ?? '')) &&
|
|
170
|
+
parsed?.stopReason === 'end_turn' && refusals >= 4 && existsSync(targets.inside) &&
|
|
171
|
+
['sibling', 'store', 'temp', 'home'].every(key => !existsSync(targets[key]));
|
|
172
|
+
return held ? executable : null;
|
|
173
|
+
} catch { return null; }
|
|
174
|
+
finally {
|
|
175
|
+
for (const file of created) rmSync(file, { force: true });
|
|
176
|
+
if (sibling) rmSync(sibling, { recursive: true, force: true });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function grokExecutableVersion(executable) {
|
|
181
|
+
try { return execFileSync(executable, ['--version'], { encoding: 'utf8', timeout: 5000 }).trim(); }
|
|
182
|
+
catch { return null; }
|
|
183
|
+
}
|
|
184
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Load a private, integrity-pinned benchmark corpus without copying its cases
|
|
2
|
+
* into Gofer's distributable source tree. */
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { constants } from 'node:fs';
|
|
5
|
+
import { open, realpath } from 'node:fs/promises';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
|
|
8
|
+
const text = value => typeof value === 'string' && value.trim().length > 0;
|
|
9
|
+
const CATEGORIES = new Set(['bug-fix', 'refactor', 'cross-service-contract', 'security-sensitive']);
|
|
10
|
+
const digest = value => createHash('sha256').update(value).digest('hex');
|
|
11
|
+
|
|
12
|
+
function within(root, target) {
|
|
13
|
+
const relative = path.relative(root, target);
|
|
14
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The manifest is a private controller input. Each case references one JSON
|
|
19
|
+
* input file and pins its SHA-256. The loader rejects a corpus located inside
|
|
20
|
+
* the released workspace, traversal paths, duplicate identifiers, incomplete
|
|
21
|
+
* category coverage, and any changed case content.
|
|
22
|
+
*/
|
|
23
|
+
export async function loadHeldOutCorpus({ corpusRoot, workspaceRoot } = {}) {
|
|
24
|
+
if (!text(corpusRoot) || !text(workspaceRoot) || !path.isAbsolute(corpusRoot) || !path.isAbsolute(workspaceRoot)) {
|
|
25
|
+
throw new Error('HELDOUT_CORPUS_PATH_REQUIRED');
|
|
26
|
+
}
|
|
27
|
+
const [root, workspace] = await Promise.all([realpath(corpusRoot), realpath(workspaceRoot)]);
|
|
28
|
+
if (within(workspace, root) || within(root, workspace)) {
|
|
29
|
+
throw new Error('HELDOUT_CORPUS_MUST_BE_EXTERNAL');
|
|
30
|
+
}
|
|
31
|
+
const manifestPath = path.join(root, 'manifest.json');
|
|
32
|
+
let manifestText;
|
|
33
|
+
try {
|
|
34
|
+
const file = await open(manifestPath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
35
|
+
try {
|
|
36
|
+
const info = await file.stat();
|
|
37
|
+
if (!info.isFile() || info.size < 2 || info.size > 65536) throw new Error('INVALID_HELDOUT_MANIFEST');
|
|
38
|
+
manifestText = await file.readFile('utf8');
|
|
39
|
+
} finally { await file.close(); }
|
|
40
|
+
} catch { throw new Error('INVALID_HELDOUT_MANIFEST'); }
|
|
41
|
+
const manifest = JSON.parse(manifestText);
|
|
42
|
+
if (manifest?.schemaVersion !== 1 || !Array.isArray(manifest.cases) || manifest.cases.length < 4 ||
|
|
43
|
+
new Set(manifest.cases.map(item => item?.id)).size !== manifest.cases.length) throw new Error('INVALID_HELDOUT_MANIFEST');
|
|
44
|
+
const categories = new Set();
|
|
45
|
+
const cases = [];
|
|
46
|
+
for (const item of manifest.cases) {
|
|
47
|
+
if (!text(item?.id) || !CATEGORIES.has(item?.category) || !text(item?.inputFile) ||
|
|
48
|
+
item.inputFile === '.' || item.inputFile === '..' || /[\\/]/.test(item.inputFile) ||
|
|
49
|
+
!/^[a-f0-9]{64}$/i.test(item?.inputSha256 ?? '')) throw new Error('INVALID_HELDOUT_MANIFEST');
|
|
50
|
+
const inputPath = path.resolve(root, item.inputFile);
|
|
51
|
+
if (!within(root, inputPath) || !within(root, await realpath(inputPath))) {
|
|
52
|
+
throw new Error('INVALID_HELDOUT_MANIFEST');
|
|
53
|
+
}
|
|
54
|
+
let input;
|
|
55
|
+
try {
|
|
56
|
+
// A direct case file cannot become a symlink between validation and open.
|
|
57
|
+
const file = await open(inputPath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
58
|
+
try {
|
|
59
|
+
const info = await file.stat();
|
|
60
|
+
if (!info.isFile() || info.size < 1 || info.size > 1024 * 1024) throw new Error('INVALID_HELDOUT_MANIFEST');
|
|
61
|
+
input = await file.readFile('utf8');
|
|
62
|
+
} finally { await file.close(); }
|
|
63
|
+
if (!within(root, await realpath(inputPath))) throw new Error('INVALID_HELDOUT_MANIFEST');
|
|
64
|
+
} catch { throw new Error('INVALID_HELDOUT_MANIFEST'); }
|
|
65
|
+
if (digest(input) !== item.inputSha256) throw new Error('HELDOUT_INPUT_INTEGRITY_REQUIRED');
|
|
66
|
+
categories.add(item.category);
|
|
67
|
+
cases.push(Object.freeze({ id: item.id, heldOut: true, category: item.category, input: JSON.parse(input) }));
|
|
68
|
+
}
|
|
69
|
+
if (categories.size !== CATEGORIES.size || [...CATEGORIES].some(category => !categories.has(category))) {
|
|
70
|
+
throw new Error('HELDOUT_CATEGORY_COVERAGE_REQUIRED');
|
|
71
|
+
}
|
|
72
|
+
return Object.freeze({ schemaVersion: 1, corpusHash: digest(JSON.stringify(manifest)), cases: Object.freeze(cases) });
|
|
73
|
+
}
|