@enterpriseai/cli 3.19.0 → 3.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -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 +2 -2
- 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,143 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { mkdir, realpath, open } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
const positive = value => Number.isSafeInteger(value) && value > 0;
|
|
7
|
+
const text = value => typeof value === 'string' && value.trim().length > 0;
|
|
8
|
+
|
|
9
|
+
/** Commands must come from reviewed host configuration, never worker output. */
|
|
10
|
+
export function createAcceptanceChecker({ workspaceRoot, evidenceDir, commands,
|
|
11
|
+
getInputRevision, verifyCleanup, timeoutMs = 60000, maxOutputBytes = 65536 }) {
|
|
12
|
+
if (!path.isAbsolute(workspaceRoot) || !path.isAbsolute(evidenceDir) ||
|
|
13
|
+
!positive(timeoutMs) || timeoutMs > 3600000 || !positive(maxOutputBytes) || maxOutputBytes > 1048576 ||
|
|
14
|
+
typeof getInputRevision !== 'function' || !commands || Array.isArray(commands)) throw new Error('INVALID_CHECK_CONFIGURATION');
|
|
15
|
+
const approved = structuredClone(commands);
|
|
16
|
+
for (const [id, command] of Object.entries(approved)) {
|
|
17
|
+
if (!text(id) || !command || !path.isAbsolute(command.program) || /\.(?:cmd|bat)$/i.test(command.program) ||
|
|
18
|
+
!Array.isArray(command.args) || command.args.length > 1000 ||
|
|
19
|
+
command.args.some(arg => typeof arg !== 'string' || arg.includes('\0') || arg.length > 65536)) throw new Error('INVALID_APPROVED_COMMAND');
|
|
20
|
+
}
|
|
21
|
+
return async function check(request) {
|
|
22
|
+
const { taskId, revision, inputRevision, signal, check: checkId } = request;
|
|
23
|
+
request = Object.freeze({ taskId, revision, inputRevision, signal, check: checkId });
|
|
24
|
+
if (!/^T\d+$/.test(taskId) || !text(revision) || !text(inputRevision) || !Object.hasOwn(approved, checkId)) throw new Error('UNAPPROVED_CHECK');
|
|
25
|
+
const command = approved[checkId];
|
|
26
|
+
const readRevision = async () => {
|
|
27
|
+
let timer;
|
|
28
|
+
try { return await Promise.race([
|
|
29
|
+
Promise.resolve().then(() => getInputRevision(request)),
|
|
30
|
+
new Promise((_, reject) => { timer = setTimeout(() => reject(new Error('INPUT_CHECK_TIMEOUT')), timeoutMs); }),
|
|
31
|
+
]); } finally { clearTimeout(timer); }
|
|
32
|
+
};
|
|
33
|
+
const cwd = await realpath(workspaceRoot);
|
|
34
|
+
if (await readRevision() !== inputRevision) throw new Error('STALE_INPUT');
|
|
35
|
+
await mkdir(evidenceDir, { recursive: true, mode: 0o700 });
|
|
36
|
+
const evidenceRoot = await realpath(evidenceDir);
|
|
37
|
+
if (signal?.aborted) throw new Error('CANCELLED');
|
|
38
|
+
const startedAt = new Date().toISOString();
|
|
39
|
+
const result = await new Promise(resolve => {
|
|
40
|
+
let child;
|
|
41
|
+
let timer;
|
|
42
|
+
let killTimer;
|
|
43
|
+
let settlementTimer;
|
|
44
|
+
let reason = null;
|
|
45
|
+
let bytes = 0;
|
|
46
|
+
const captured = Buffer.alloc(maxOutputBytes);
|
|
47
|
+
let settled = false;
|
|
48
|
+
const stop = cause => {
|
|
49
|
+
if (reason) return;
|
|
50
|
+
reason = cause;
|
|
51
|
+
if (!child?.pid) return;
|
|
52
|
+
if (process.platform === 'win32') {
|
|
53
|
+
// Kill only the tree started by this check, never a port or process name.
|
|
54
|
+
const killer = spawn(path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'taskkill.exe'),
|
|
55
|
+
['/PID', String(child.pid), '/T', '/F'], { shell: false, stdio: 'ignore', windowsHide: true });
|
|
56
|
+
killer.on('error', () => { child.kill(); });
|
|
57
|
+
killer.unref();
|
|
58
|
+
} else {
|
|
59
|
+
try { process.kill(-child.pid, 'SIGTERM'); } catch { /* Already exited. */ }
|
|
60
|
+
killTimer ??= setTimeout(() => { try { process.kill(-child.pid, 'SIGKILL'); } catch { /* Already exited. */ } }, 250);
|
|
61
|
+
}
|
|
62
|
+
settlementTimer = setTimeout(() => {
|
|
63
|
+
child.stdout.destroy(); child.stderr.destroy(); child.unref();
|
|
64
|
+
finish(null, new Error('CLEANUP_UNVERIFIED'));
|
|
65
|
+
}, 1000);
|
|
66
|
+
};
|
|
67
|
+
const cancel = () => stop('cancelled');
|
|
68
|
+
const finish = (code, error) => {
|
|
69
|
+
if (settled) return;
|
|
70
|
+
settled = true;
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
// After the parent exits, still terminate any surviving local descendants.
|
|
73
|
+
if (process.platform !== 'win32' && child?.pid) {
|
|
74
|
+
try { process.kill(-child.pid, 'SIGKILL'); } catch { /* Tree has exited. */ }
|
|
75
|
+
}
|
|
76
|
+
clearTimeout(killTimer);
|
|
77
|
+
clearTimeout(settlementTimer);
|
|
78
|
+
signal?.removeEventListener('abort', cancel);
|
|
79
|
+
resolve({ executed: !!child?.pid, pid: child?.pid ?? null, exitCode: reason || error ? 1 : code ?? 1,
|
|
80
|
+
reason: reason || (error ? 'spawn-failed' : code === 0 ? 'passed' : 'failed'),
|
|
81
|
+
output: captured.toString('utf8', 0, bytes), capturedBytes: bytes });
|
|
82
|
+
};
|
|
83
|
+
if (signal?.aborted) { reason = 'cancelled'; finish(null); return; }
|
|
84
|
+
// No shell, credential flags or inherited stdin. The reviewed process owns its side effects.
|
|
85
|
+
child = spawn(command.program, command.args, { cwd, shell: false, detached: process.platform !== 'win32',
|
|
86
|
+
windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
87
|
+
const collect = data => {
|
|
88
|
+
const remaining = Math.max(0, maxOutputBytes - bytes);
|
|
89
|
+
const accepted = Math.min(data.length, remaining);
|
|
90
|
+
if (accepted) { data.copy(captured, bytes, 0, accepted); bytes += accepted; }
|
|
91
|
+
if (data.length > remaining) stop('output-limit');
|
|
92
|
+
};
|
|
93
|
+
child.stdout.on('data', collect); child.stderr.on('data', collect);
|
|
94
|
+
child.on('error', error => finish(null, error));
|
|
95
|
+
child.on('close', code => finish(code));
|
|
96
|
+
signal?.addEventListener('abort', cancel, { once: true });
|
|
97
|
+
timer = setTimeout(() => stop('timeout'), timeoutMs);
|
|
98
|
+
if (signal?.aborted) cancel();
|
|
99
|
+
});
|
|
100
|
+
if (signal?.aborted) { result.exitCode = 1; result.reason = 'cancelled'; }
|
|
101
|
+
try {
|
|
102
|
+
if (await readRevision() !== inputRevision) { result.exitCode = 1; result.reason = 'stale-input'; }
|
|
103
|
+
} catch { result.exitCode = 1; result.reason = 'input-unverifiable'; }
|
|
104
|
+
// A closed parent does not prove detached descendants or remote work stopped.
|
|
105
|
+
let cleanup;
|
|
106
|
+
let cleanupTimer;
|
|
107
|
+
try {
|
|
108
|
+
cleanup = await Promise.race([
|
|
109
|
+
Promise.resolve().then(() => verifyCleanup?.({ ...request, pid: result.pid, startedAt })),
|
|
110
|
+
new Promise(resolve => { cleanupTimer = setTimeout(() => resolve(null), 1000); }),
|
|
111
|
+
]);
|
|
112
|
+
} catch { cleanup = null; } finally { clearTimeout(cleanupTimer); }
|
|
113
|
+
const cleanupVerified = cleanup?.allStopped === true && cleanup.pid === result.pid &&
|
|
114
|
+
cleanup.startedAt === startedAt && text(cleanup.receipt);
|
|
115
|
+
if (!cleanupVerified && result.exitCode === 0) { result.exitCode = 1; result.reason = 'cleanup-unverified'; }
|
|
116
|
+
if (signal?.aborted) { result.exitCode = 1; result.reason = 'cancelled'; }
|
|
117
|
+
const record = { schemaVersion: 1, taskId, revision, inputRevision, check: checkId,
|
|
118
|
+
...result, startedAt, finishedAt: new Date().toISOString(),
|
|
119
|
+
cleanupVerified, cleanupReceipt: cleanupVerified ? cleanup.receipt : null,
|
|
120
|
+
outputSha256: createHash('sha256').update(result.output).digest('hex'),
|
|
121
|
+
command: { program: command.program, args: command.args }, evidenceKind: 'local-command', nativeModelProof: false };
|
|
122
|
+
const receipt = path.join(evidenceRoot, `${taskId}-${randomUUID()}.json`);
|
|
123
|
+
const file = await open(receipt, 'wx', 0o600);
|
|
124
|
+
try {
|
|
125
|
+
await file.writeFile(JSON.stringify(record, null, 2));
|
|
126
|
+
await file.sync();
|
|
127
|
+
// Cancellation can arrive while evidence is being persisted. Rewrite the
|
|
128
|
+
// receipt so a successful result can never survive that cancellation.
|
|
129
|
+
if (signal?.aborted) {
|
|
130
|
+
record.exitCode = 1;
|
|
131
|
+
record.reason = 'cancelled';
|
|
132
|
+
const body = JSON.stringify(record, null, 2);
|
|
133
|
+
await file.truncate(0);
|
|
134
|
+
await file.write(body, 0, 'utf8');
|
|
135
|
+
await file.truncate(Buffer.byteLength(body));
|
|
136
|
+
await file.sync();
|
|
137
|
+
}
|
|
138
|
+
} finally { await file.close(); }
|
|
139
|
+
// Keep raw command output in the private receipt, not in the model context.
|
|
140
|
+
return { taskId, revision, inputRevision, check: checkId, executed: result.executed,
|
|
141
|
+
exitCode: result.exitCode, reason: result.reason, receipt, cleanupVerified, evidenceKind: 'local-command', nativeModelProof: false };
|
|
142
|
+
};
|
|
143
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/** Shared, read-only specialist assignments. No provider SDKs or model execution. */
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { readFile, realpath, stat } from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const MODULE_ROOT = fileURLToPath(new URL('../../../', import.meta.url));
|
|
9
|
+
const REFERENCE = '.specify/references/agent-catalog.json';
|
|
10
|
+
const SURFACES = ['claude', 'codex', 'copilot', 'vscode', 'grok', 'antigravity', 'gemini'];
|
|
11
|
+
const CAPABILITIES = ['independentReadIsolation', 'independentExecution'];
|
|
12
|
+
const BODY_NOTICE = `## Assignment Authority
|
|
13
|
+
|
|
14
|
+
Provider-specific examples within the role body, including Task(...) calls,
|
|
15
|
+
model names, tool names and launch syntax, are advisory and non-authoritative.
|
|
16
|
+
They do not select a model, grant tool access, authorize execution, or establish
|
|
17
|
+
independence. Follow the caller-bound assignment fields, dispatchAllowed gate,
|
|
18
|
+
and host-enforced permissions while preserving the role's responsibilities.
|
|
19
|
+
Baseline work must never be described as independent. This assignment is not
|
|
20
|
+
evidence that execution occurred.
|
|
21
|
+
|
|
22
|
+
## Canonical Role Body
|
|
23
|
+
|
|
24
|
+
`;
|
|
25
|
+
const HELP = `Usage: node gofer-agent-catalog.mjs --list [--json]
|
|
26
|
+
node gofer-agent-catalog.mjs --help
|
|
27
|
+
|
|
28
|
+
Read-only specialist catalogue. --list prints role IDs; --json includes neutral
|
|
29
|
+
role bodies and surface declarations. Paths are relative to the installed helper,
|
|
30
|
+
not the current directory. No models are selected or launched.
|
|
31
|
+
API: readCatalogue({ root? }); resolveAssignment(request, { root?, verifyNativeProof? }).
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
function requireValue(condition, message) {
|
|
35
|
+
if (!condition) throw new Error(`Agent catalogue: ${message}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isRecord(value) {
|
|
39
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function text(value, field, max = 4000) {
|
|
43
|
+
requireValue(typeof value === 'string' && value.trim().length > 0 &&
|
|
44
|
+
value === value.trim() && value.length <= max && !/[\u0000-\u001f\u007f]/.test(value),
|
|
45
|
+
`${field} must be non-empty text without surrounding whitespace or control characters`);
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function stringList(value, field, allowEmpty = false) {
|
|
50
|
+
requireValue(Array.isArray(value) && (allowEmpty || value.length > 0), `${field} must be an array${allowEmpty ? '' : ' with at least one entry'}`);
|
|
51
|
+
for (const item of value) text(item, field);
|
|
52
|
+
requireValue(new Set(value).size === value.length, `${field} must not contain duplicates`);
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function validateCatalogue(catalogue) {
|
|
57
|
+
requireValue(isRecord(catalogue) && catalogue.schemaVersion === 1, 'unsupported schemaVersion');
|
|
58
|
+
stringList(catalogue.stages, 'stages');
|
|
59
|
+
requireValue(catalogue.stages.every(stage => /^(?:\d+[a-z]?_[a-z0-9_]+|gofer_[a-z0-9_]+|gofer:[a-z0-9-]+)$/.test(stage)), 'invalid stage ID');
|
|
60
|
+
requireValue(Array.isArray(catalogue.roles) && catalogue.roles.length > 0, 'roles must be a non-empty array');
|
|
61
|
+
const ids = [];
|
|
62
|
+
for (const role of catalogue.roles) {
|
|
63
|
+
requireValue(isRecord(role) && typeof role.id === 'string' && /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(role.id), 'invalid role ID');
|
|
64
|
+
requireValue(role.body === `agents/${role.id}.md`, `invalid body reference for ${role.id}`);
|
|
65
|
+
requireValue(Object.keys(role).every(key => ['id', 'body'].includes(key)), `role ${role.id} must reference its body, not duplicate it`);
|
|
66
|
+
ids.push(role.id);
|
|
67
|
+
}
|
|
68
|
+
stringList(ids, 'role IDs');
|
|
69
|
+
requireValue(Array.isArray(catalogue.surfaces) && catalogue.surfaces.length === SURFACES.length, 'missing or duplicate surface records');
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
for (const surface of catalogue.surfaces) {
|
|
72
|
+
requireValue(isRecord(surface) && SURFACES.includes(surface.id) && !seen.has(surface.id), 'invalid or duplicate surface');
|
|
73
|
+
seen.add(surface.id);
|
|
74
|
+
requireValue(surface.legacy === (surface.id === 'gemini') && surface.evidenceKind === 'self-declaration' &&
|
|
75
|
+
CAPABILITIES.every(key => surface[key] === 'unqualified'), `surface ${surface.id} must not claim native proof`);
|
|
76
|
+
requireValue(Object.keys(surface).every(key => ['id', 'legacy', 'evidenceKind', ...CAPABILITIES].includes(key)), `unexpected surface metadata for ${surface.id}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function contained(base, target) {
|
|
81
|
+
const relative = path.relative(base, target);
|
|
82
|
+
return relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function safePath(base, target) {
|
|
86
|
+
const resolved = await realpath(target);
|
|
87
|
+
requireValue(contained(base, resolved), `path escapes outside canonical directory: ${target}`);
|
|
88
|
+
return resolved;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function roleDirectory(root) {
|
|
92
|
+
// Choose one layout for the whole catalogue. Never hide a broken bundle by
|
|
93
|
+
// mixing packaged roles with repository copies or searching a parent checkout.
|
|
94
|
+
for (const directory of ['agents', '.claude/agents']) {
|
|
95
|
+
const candidate = path.join(root, directory);
|
|
96
|
+
let info;
|
|
97
|
+
try { info = await stat(candidate); }
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (error.code === 'ENOENT') continue;
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
requireValue(info.isDirectory(), `role source is not a directory: ${candidate}`);
|
|
103
|
+
return { directory, resolved: await safePath(root, candidate) };
|
|
104
|
+
}
|
|
105
|
+
throw new Error(`Agent catalogue: no canonical agents/ or .claude/agents/ directory in ${root}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function neutralBody(raw, source) {
|
|
109
|
+
const content = raw.replace(/^\uFEFF/, '');
|
|
110
|
+
if (!/^---[ \t]*(?:\r?\n|$)/.test(content)) {
|
|
111
|
+
requireValue(content.trim().length > 0, `empty role body: ${source}`);
|
|
112
|
+
return content;
|
|
113
|
+
}
|
|
114
|
+
// Remove the entire provider header, without interpreting YAML or carrying
|
|
115
|
+
// model/tool settings into another host. Preserve everything after the fence.
|
|
116
|
+
const match = content.match(/^---[ \t]*\r?\n(?:[\s\S]*?\r?\n)?---[ \t]*(?:\r?\n|$)/);
|
|
117
|
+
requireValue(match, `unclosed frontmatter: ${source}`);
|
|
118
|
+
const body = content.slice(match[0].length);
|
|
119
|
+
requireValue(body.trim().length > 0, `empty role body: ${source}`);
|
|
120
|
+
return body;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Load and validate the catalogue and every canonical role body.
|
|
125
|
+
* @param {{root?: string}} [options] Installation/repository root; defaults to
|
|
126
|
+
* the helper's own root, including in relocated bundles. Never defaults to cwd.
|
|
127
|
+
* @returns {Promise<{schemaVersion: number, stages: string[], surfaces: object[],
|
|
128
|
+
* roles: Array<{id: string, body: string, source: string, content: string, contentSha256: string}>}>}
|
|
129
|
+
*/
|
|
130
|
+
export async function readCatalogue(options = {}) {
|
|
131
|
+
requireValue(isRecord(options), 'options must be an object');
|
|
132
|
+
const root = await realpath(path.resolve(text(options.root ?? MODULE_ROOT, 'root')));
|
|
133
|
+
const catalogueFile = await safePath(root, path.join(root, REFERENCE));
|
|
134
|
+
const catalogue = JSON.parse(await readFile(catalogueFile, 'utf8'));
|
|
135
|
+
validateCatalogue(catalogue);
|
|
136
|
+
const { directory, resolved } = await roleDirectory(root);
|
|
137
|
+
const roles = [];
|
|
138
|
+
for (const role of catalogue.roles) {
|
|
139
|
+
const source = `${directory}/${role.id}.md`;
|
|
140
|
+
const file = await safePath(resolved, path.join(resolved, `${role.id}.md`));
|
|
141
|
+
const content = neutralBody(await readFile(file, 'utf8'), source);
|
|
142
|
+
roles.push({ ...role, source, content, contentSha256: createHash('sha256').update(content).digest('hex') });
|
|
143
|
+
}
|
|
144
|
+
return { schemaVersion: catalogue.schemaVersion, stages: catalogue.stages, surfaces: catalogue.surfaces, roles };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function validateScope(scope) {
|
|
148
|
+
stringList(scope, 'scope');
|
|
149
|
+
for (const entry of scope) {
|
|
150
|
+
requireValue(entry === '.' || (!/^[\/]/.test(entry) && !/[\\:*?"<>|]/.test(entry) &&
|
|
151
|
+
entry.split('/').every(part => part !== '' && part !== '.' && part !== '..')),
|
|
152
|
+
`scope must contain normalized repository-relative literal paths: ${entry}`);
|
|
153
|
+
}
|
|
154
|
+
return [...scope];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function proofMatches(proof, binding) {
|
|
158
|
+
return isRecord(proof) && proof.kind === 'native-proof' && isRecord(proof.binding) &&
|
|
159
|
+
Object.keys(proof.binding).length === Object.keys(binding).length &&
|
|
160
|
+
Object.keys(binding).every(key => Object.hasOwn(proof.binding, key) &&
|
|
161
|
+
JSON.stringify(proof.binding[key]) === JSON.stringify(binding[key])) &&
|
|
162
|
+
CAPABILITIES.every(key => typeof proof[key] === 'boolean') &&
|
|
163
|
+
Array.isArray(proof.evidence) && proof.evidence.length > 0 &&
|
|
164
|
+
proof.evidence.every(ref => typeof ref === 'string' && ref.trim().length > 0 && !/[\u0000-\u001f\u007f]/.test(ref));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Prepare an assignment, never execute it or enforce its filesystem scope.
|
|
169
|
+
* Stage is a catalogue command ID, task is a non-empty ID/description, revision
|
|
170
|
+
* is a caller-owned revision token (not resolved through Git), and scope is a
|
|
171
|
+
* non-empty array of normalized repo-relative literal paths ('.' means all).
|
|
172
|
+
* No role/stage routing policy is imposed here; the controller owns that policy.
|
|
173
|
+
*
|
|
174
|
+
* Native proof is untrusted input until verifyNativeProof returns exactly true.
|
|
175
|
+
* The controller must supply that trusted verifier, check original host evidence,
|
|
176
|
+
* freshness and actual isolation/execution, and reject self-reported assertions.
|
|
177
|
+
* This helper checks assignment binding only; it cannot authenticate host logs.
|
|
178
|
+
*
|
|
179
|
+
* @param {{role: string, stage: string, task: string, revision: string,
|
|
180
|
+
* scope: string[], requiredChecks: string[], surface: string, availableModels: string[], model?: string,
|
|
181
|
+
* mode?: 'baseline'|'independent', capabilities?: {independentReadIsolation?: boolean,
|
|
182
|
+
* independentExecution?: boolean}, nativeProof?: object}} request
|
|
183
|
+
* @param {{root?: string, verifyNativeProof?: (proof: object, binding: object) => boolean|Promise<boolean>}} [options]
|
|
184
|
+
* @returns {Promise<object>} Neutral roleContent with an authority notice, exact
|
|
185
|
+
* binding, requested/effective mode, status, dispatchAllowed, qualification,
|
|
186
|
+
* structured limitations, and executed:false. An unqualified independent request
|
|
187
|
+
* is blocked, never silently downgraded to baseline. Missing models block dispatch.
|
|
188
|
+
*/
|
|
189
|
+
export async function resolveAssignment(request, options = {}) {
|
|
190
|
+
requireValue(isRecord(request), 'request must be an object');
|
|
191
|
+
requireValue(isRecord(options), 'options must be an object');
|
|
192
|
+
requireValue(options.verifyNativeProof === undefined || typeof options.verifyNativeProof === 'function', 'verifyNativeProof must be a trusted host function');
|
|
193
|
+
const input = structuredClone(request);
|
|
194
|
+
const roleId = text(input.role, 'role', 100);
|
|
195
|
+
const stage = text(input.stage, 'stage', 100);
|
|
196
|
+
const task = text(input.task, 'task');
|
|
197
|
+
const revision = text(input.revision, 'revision', 200);
|
|
198
|
+
requireValue(/^[a-zA-Z0-9][a-zA-Z0-9._:/-]*$/.test(revision) && !revision.includes('..'), 'invalid revision token');
|
|
199
|
+
const scope = validateScope(input.scope);
|
|
200
|
+
requireValue(Array.isArray(input.requiredChecks) && input.requiredChecks.length <= 256,
|
|
201
|
+
'requiredChecks must contain at most 256 entries');
|
|
202
|
+
const requiredChecks = [...stringList(input.requiredChecks, 'requiredChecks')];
|
|
203
|
+
for (const check of requiredChecks) text(check, 'requiredChecks', 1024);
|
|
204
|
+
const surfaceId = text(input.surface, 'surface', 100);
|
|
205
|
+
stringList(input.availableModels, 'availableModels', true);
|
|
206
|
+
const model = input.model === undefined ? null : text(input.model, 'model', 200);
|
|
207
|
+
requireValue(model === null || input.availableModels.includes(model), 'model is not in caller-provided availableModels');
|
|
208
|
+
const requestedMode = input.mode === undefined ? 'baseline' : input.mode;
|
|
209
|
+
requireValue(['baseline', 'independent'].includes(requestedMode), 'mode must be baseline or independent');
|
|
210
|
+
const declarations = input.capabilities === undefined ? {} : input.capabilities;
|
|
211
|
+
requireValue(isRecord(declarations) && Object.entries(declarations).every(([key, value]) => CAPABILITIES.includes(key) && typeof value === 'boolean'), 'capabilities must contain only boolean self-declarations');
|
|
212
|
+
|
|
213
|
+
const catalogue = await readCatalogue({ root: options.root });
|
|
214
|
+
const role = catalogue.roles.find(entry => entry.id === roleId);
|
|
215
|
+
requireValue(role, `unknown role: ${roleId}`);
|
|
216
|
+
requireValue(catalogue.stages.includes(stage), `unknown stage: ${stage}`);
|
|
217
|
+
const surface = catalogue.surfaces.find(entry => entry.id === surfaceId);
|
|
218
|
+
requireValue(surface, `unknown surface: ${surfaceId}`);
|
|
219
|
+
const binding = { role: roleId, stage, task, revision, scope, requiredChecks, surface: surfaceId, model, contentSha256: role.contentSha256 };
|
|
220
|
+
const limitations = [];
|
|
221
|
+
let verified = false;
|
|
222
|
+
if (input.nativeProof !== undefined) {
|
|
223
|
+
if (proofMatches(input.nativeProof, binding) && options.verifyNativeProof) {
|
|
224
|
+
try {
|
|
225
|
+
verified = (await options.verifyNativeProof(structuredClone(input.nativeProof), structuredClone(binding))) === true;
|
|
226
|
+
} catch {
|
|
227
|
+
verified = false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (!verified) limitations.push({ code: 'NATIVE_PROOF_UNVERIFIED', message: 'Native proof is missing, stale, mismatched or not approved by a trusted host verifier; self-declarations are not native proof.' });
|
|
231
|
+
}
|
|
232
|
+
const qualification = {
|
|
233
|
+
evidenceKind: verified ? 'native-proof' : 'unqualified',
|
|
234
|
+
declarations,
|
|
235
|
+
independentReadIsolation: verified && input.nativeProof.independentReadIsolation === true,
|
|
236
|
+
independentExecution: verified && input.nativeProof.independentExecution === true,
|
|
237
|
+
evidence: verified ? [...input.nativeProof.evidence] : [],
|
|
238
|
+
};
|
|
239
|
+
if (!qualification.independentReadIsolation) limitations.push({ code: 'INDEPENDENT_READ_ISOLATION_UNQUALIFIED', message: 'Independent read isolation is unqualified. Baseline role work must not be described as an independent read.' });
|
|
240
|
+
if (!qualification.independentExecution) limitations.push({ code: 'INDEPENDENT_EXECUTION_UNQUALIFIED', message: 'Independent execution is unqualified. A role assignment or capability declaration does not prove a separate execution.' });
|
|
241
|
+
if (model === null) limitations.push({ code: 'MODEL_NOT_SELECTED', message: 'No model was selected by the caller. Role frontmatter and model-list order are not model selection authority.' });
|
|
242
|
+
const independent = requestedMode === 'independent' && model !== null &&
|
|
243
|
+
qualification.independentReadIsolation && qualification.independentExecution;
|
|
244
|
+
const dispatchAllowed = model !== null && (requestedMode === 'baseline' || independent);
|
|
245
|
+
const mode = requestedMode === 'independent' ? (independent ? 'independent' : 'blocked') : 'baseline';
|
|
246
|
+
return {
|
|
247
|
+
...binding, binding: structuredClone(binding), schemaVersion: 1,
|
|
248
|
+
roleContent: `${BODY_NOTICE}${role.content}`, source: role.source, surfaceRecord: surface,
|
|
249
|
+
contentPolicy: { providerExamples: 'non-authoritative', modelSelection: 'caller-provided-only' },
|
|
250
|
+
requestedMode, mode, independent, status: dispatchAllowed ? 'ready' : 'blocked', dispatchAllowed,
|
|
251
|
+
qualification, limitations, executed: false, scopeEnforced: false,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function main(args) {
|
|
256
|
+
if (args.length === 1 && args[0] === '--help') {
|
|
257
|
+
process.stdout.write(HELP);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
requireValue(args.includes('--list') && new Set(args).size === args.length &&
|
|
261
|
+
args.every(arg => ['--list', '--json'].includes(arg)), `unsupported arguments.\n${HELP}`);
|
|
262
|
+
const catalogue = await readCatalogue();
|
|
263
|
+
process.stdout.write(args.includes('--json') ? `${JSON.stringify(catalogue, null, 2)}\n` : `${catalogue.roles.map(role => role.id).join('\n')}\n`);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (process.argv[1] && await realpath(process.argv[1]).catch(() => null) === fileURLToPath(import.meta.url)) {
|
|
267
|
+
main(process.argv.slice(2)).catch(error => {
|
|
268
|
+
process.stderr.write(`${error.message}\n`);
|
|
269
|
+
process.exitCode = 1;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run the pinned held-out corpus and write receipts in the layout the
|
|
3
|
+
* verifier, snapshot, and signer read. The executor holds no signing key and
|
|
4
|
+
* grants no authority. Dispatch and independent review are injected: a real
|
|
5
|
+
* native dispatch and a real reviewer must be supplied by the trusted caller,
|
|
6
|
+
* so this module cannot fabricate either.
|
|
7
|
+
*/
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
9
|
+
import { constants } from 'node:fs';
|
|
10
|
+
import { mkdir, mkdtemp, open, realpath, rm } from 'node:fs/promises';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { capabilityReceiptHash } from './gofer-host-capability.mjs';
|
|
14
|
+
import { runBenchmark } from './gofer-benchmark.mjs';
|
|
15
|
+
import { loadTrustedHeldOutCorpus } from './gofer-trusted-evaluator.mjs';
|
|
16
|
+
import { runHeldOutSandboxCheck, snapshotHeldOutWorktree } from './gofer-heldout-verifier.mjs';
|
|
17
|
+
|
|
18
|
+
const text = value => typeof value === 'string' && value.trim().length > 0;
|
|
19
|
+
const sha = value => createHash('sha256').update(value).digest('hex');
|
|
20
|
+
const jsonHash = value => sha(JSON.stringify(value));
|
|
21
|
+
const denied = () => new Error('BENCHMARK_EXECUTOR_REQUIRED');
|
|
22
|
+
const ISOLATION = 'git-worktree+local-os-sandbox';
|
|
23
|
+
const safeRelative = value => typeof value === 'string' && value.length > 0 &&
|
|
24
|
+
!path.posix.isAbsolute(value) && !value.includes('\\') &&
|
|
25
|
+
!value.split('/').some(part => !part || part === '.' || part === '..');
|
|
26
|
+
|
|
27
|
+
async function writePrivate(filename, content) {
|
|
28
|
+
const file = await open(filename, constants.O_WRONLY | constants.O_CREAT |
|
|
29
|
+
constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
30
|
+
try { await file.writeFile(content); await file.sync(); } finally { await file.close(); }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function materialize(worktree, files) {
|
|
34
|
+
for (const [name, content] of Object.entries(files)) {
|
|
35
|
+
if (!safeRelative(name) || typeof content !== 'string') throw denied();
|
|
36
|
+
const target = path.join(worktree, name);
|
|
37
|
+
await mkdir(path.dirname(target), { recursive: true, mode: 0o700 });
|
|
38
|
+
await writePrivate(target, content);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* `dispatchCase({ caseId, run, prompt, allowedWriteScope, worktree })` runs the
|
|
44
|
+
* model against one prepared worktree and returns
|
|
45
|
+
* `{ modelId, costUsd, durationMs, isolation }`. `review({ caseId, run,
|
|
46
|
+
* inputHash, executionReceipt, passed, prompt, worktree, baselineFiles })`
|
|
47
|
+
* returns `{ receipt, approved }` from a party other than the executor. A
|
|
48
|
+
* rejected review fails the case even when the protected check passed. `trustRoot` exists for isolated tests only.
|
|
49
|
+
*/
|
|
50
|
+
export async function runHeldOutBenchmark({ workspaceRoot, trustRoot, capabilityReceipt,
|
|
51
|
+
modelId, harnessId, dispatchCase, review } = {}) {
|
|
52
|
+
let worktreesRoot;
|
|
53
|
+
try {
|
|
54
|
+
if (!text(workspaceRoot) || !path.isAbsolute(workspaceRoot) || !text(modelId) ||
|
|
55
|
+
!text(harnessId) || capabilityReceipt?.host !== 'codex' ||
|
|
56
|
+
typeof dispatchCase !== 'function' || typeof review !== 'function') throw denied();
|
|
57
|
+
const corpus = await loadTrustedHeldOutCorpus({ workspaceRoot, trustRoot });
|
|
58
|
+
const receipts = path.join(corpus.corpusRoot, 'receipts');
|
|
59
|
+
await mkdir(receipts, { mode: 0o700 });
|
|
60
|
+
worktreesRoot = await realpath(await mkdtemp(path.join(os.tmpdir(), 'gofer-heldout-run-')));
|
|
61
|
+
const receiptHash = capabilityReceiptHash(capabilityReceipt);
|
|
62
|
+
const cases = new Map(corpus.cases.map(item => [item.id, item]));
|
|
63
|
+
|
|
64
|
+
const report = await runBenchmark({
|
|
65
|
+
cases: corpus.cases,
|
|
66
|
+
repetitions: 3,
|
|
67
|
+
provenance: { harnessId, modelId, corpusHash: corpus.corpusHash,
|
|
68
|
+
capabilityReceiptHash: receiptHash },
|
|
69
|
+
async execute({ id, run }) {
|
|
70
|
+
const input = cases.get(id).input;
|
|
71
|
+
const worktree = path.join(worktreesRoot, `${id}-${run}`);
|
|
72
|
+
await mkdir(worktree, { mode: 0o700 });
|
|
73
|
+
await materialize(worktree, input.files);
|
|
74
|
+
const result = await dispatchCase({ caseId: id, run, prompt: input.prompt,
|
|
75
|
+
allowedWriteScope: [...input.allowedWriteScope], worktree });
|
|
76
|
+
if (result?.modelId !== modelId || result.isolation !== ISOLATION ||
|
|
77
|
+
!Number.isFinite(result.costUsd) || result.costUsd < 0 ||
|
|
78
|
+
!Number.isFinite(result.durationMs) || result.durationMs < 0) throw denied();
|
|
79
|
+
const payload = { id, run, worktree, native: { isolation: ISOLATION } };
|
|
80
|
+
const receipt = jsonHash(payload);
|
|
81
|
+
await writePrivate(path.join(receipts, `${id}-${run}.execution.json`),
|
|
82
|
+
JSON.stringify({ ...payload, executionReceipt: receipt }));
|
|
83
|
+
return { modelId, costUsd: result.costUsd, durationMs: result.durationMs, receipt };
|
|
84
|
+
},
|
|
85
|
+
async verify({ caseId, run, inputHash, execution }) {
|
|
86
|
+
const input = cases.get(caseId).input;
|
|
87
|
+
const worktree = path.join(worktreesRoot, `${caseId}-${run}`);
|
|
88
|
+
// A worker that touched anything outside its scope fails the case.
|
|
89
|
+
let passed = false;
|
|
90
|
+
let failureClassification = 'none';
|
|
91
|
+
try {
|
|
92
|
+
const before = await snapshotHeldOutWorktree(worktree, input);
|
|
93
|
+
passed = runHeldOutSandboxCheck(worktree);
|
|
94
|
+
const after = await snapshotHeldOutWorktree(worktree, input);
|
|
95
|
+
if (before !== after) { passed = false; failureClassification = 'check-mutated-worktree'; }
|
|
96
|
+
else if (!passed) failureClassification = 'functional-check-failed';
|
|
97
|
+
} catch { passed = false; failureClassification = 'scope-violation'; }
|
|
98
|
+
const reviewed = await review({ caseId, run, inputHash,
|
|
99
|
+
executionReceipt: execution.receipt, passed, prompt: input.prompt, worktree,
|
|
100
|
+
baselineFiles: { ...input.files } });
|
|
101
|
+
if (!/^[a-f0-9]{64}$/.test(reviewed?.receipt ?? '') ||
|
|
102
|
+
typeof reviewed.approved !== 'boolean') throw denied();
|
|
103
|
+
if (passed && !reviewed.approved) { passed = false; failureClassification = 'review-rejected'; }
|
|
104
|
+
// The verification receipt binds the final verdict, review included.
|
|
105
|
+
const receipt = jsonHash({ caseId, run, inputHash, executionReceipt: execution.receipt,
|
|
106
|
+
passed, kind: 'verification' });
|
|
107
|
+
await writePrivate(path.join(receipts, `${caseId}-${run}.verification.json`),
|
|
108
|
+
JSON.stringify({ caseId, run, inputHash, executionReceipt: execution.receipt,
|
|
109
|
+
receipt, passed }));
|
|
110
|
+
return { caseId, run, inputHash, executionReceipt: execution.receipt, passed, receipt,
|
|
111
|
+
verifierId: 'gofer-heldout-sandbox-check', failureClassification,
|
|
112
|
+
reviewReceipt: reviewed.receipt };
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
await writePrivate(path.join(receipts, 'benchmark-report.json'),
|
|
116
|
+
JSON.stringify({ corpusHash: corpus.corpusHash, report }));
|
|
117
|
+
const leftover = worktreesRoot;
|
|
118
|
+
worktreesRoot = undefined;
|
|
119
|
+
return Object.freeze({ report, corpusHash: corpus.corpusHash, corpusRoot: corpus.corpusRoot,
|
|
120
|
+
worktreesRoot: leftover });
|
|
121
|
+
} catch { throw denied(); }
|
|
122
|
+
finally { if (worktreesRoot) await rm(worktreesRoot, { recursive: true, force: true }); }
|
|
123
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Human-gated signing. It re-runs the protected checks itself, then asks for
|
|
4
|
+
* the verifier passphrase at a terminal. It refuses to run without one, so an
|
|
5
|
+
* agent, a script, or a worker cannot sign. Output is written once, private.
|
|
6
|
+
*/
|
|
7
|
+
import { constants } from 'node:fs';
|
|
8
|
+
import { open } from 'node:fs/promises';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { signHeldOutBenchmarkAttestation } from './gofer-benchmark-signer.mjs';
|
|
12
|
+
import { resolveTrustedEvaluatorPublicKey } from './gofer-trusted-evaluator.mjs';
|
|
13
|
+
import { promptHidden } from './gofer-tty-prompt.mjs';
|
|
14
|
+
|
|
15
|
+
const denied = () => new Error('BENCHMARK_SIGN_COMMAND_REQUIRED');
|
|
16
|
+
|
|
17
|
+
export function parseSignArguments(argv) {
|
|
18
|
+
const flags = new Map();
|
|
19
|
+
for (let index = 0; index < argv.length; index += 2) {
|
|
20
|
+
if (!['--workspace-root', '--snapshot-id', '--capability-receipt', '--out', '--ttl-minutes'].includes(argv[index]) ||
|
|
21
|
+
typeof argv[index + 1] !== 'string' || flags.has(argv[index])) throw denied();
|
|
22
|
+
flags.set(argv[index], argv[index + 1]);
|
|
23
|
+
}
|
|
24
|
+
const args = { workspaceRoot: flags.get('--workspace-root'), snapshotId: flags.get('--snapshot-id'),
|
|
25
|
+
receiptPath: flags.get('--capability-receipt'), out: flags.get('--out') };
|
|
26
|
+
// Optional: how long the attestation stays valid. The signer allows up to 24
|
|
27
|
+
// hours; this command allows up to 4, never more than a receipt's own life.
|
|
28
|
+
const lifetime = flags.get('--ttl-minutes');
|
|
29
|
+
if (lifetime !== undefined && (!/^\d{1,3}$/.test(lifetime) || Number(lifetime) < 1 || Number(lifetime) > 240)) throw denied();
|
|
30
|
+
args.ttlMs = lifetime === undefined ? undefined : Number(lifetime) * 60 * 1000;
|
|
31
|
+
if (flags.size !== (lifetime === undefined ? 4 : 5) || !path.isAbsolute(args.workspaceRoot) || !path.isAbsolute(args.receiptPath) ||
|
|
32
|
+
!path.isAbsolute(args.out) || !/^[a-f0-9]{64}$/.test(args.snapshotId)) throw denied();
|
|
33
|
+
return args;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function main() {
|
|
37
|
+
const args = parseSignArguments(process.argv.slice(2));
|
|
38
|
+
// Checked first, before any work: a person must be at this terminal.
|
|
39
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) throw new Error('INTERACTIVE_TERMINAL_REQUIRED');
|
|
40
|
+
const file = await open(args.receiptPath, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
41
|
+
let capabilityReceipt;
|
|
42
|
+
try {
|
|
43
|
+
const info = await file.stat();
|
|
44
|
+
if (!info.isFile() || info.size > 65536) throw denied();
|
|
45
|
+
capabilityReceipt = JSON.parse(await file.readFile('utf8'));
|
|
46
|
+
} finally { await file.close(); }
|
|
47
|
+
const capabilityPublicKey = await resolveTrustedEvaluatorPublicKey(capabilityReceipt,
|
|
48
|
+
{ workspaceRoot: args.workspaceRoot });
|
|
49
|
+
const signed = await signHeldOutBenchmarkAttestation({ workspaceRoot: args.workspaceRoot,
|
|
50
|
+
capabilityReceipt, capabilityPublicKey, snapshotId: args.snapshotId,
|
|
51
|
+
...(args.ttlMs === undefined ? {} : { ttlMs: args.ttlMs }),
|
|
52
|
+
getPassphrase: () => promptHidden('Verifier passphrase: ') });
|
|
53
|
+
const out = await open(args.out, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL |
|
|
54
|
+
constants.O_NOFOLLOW, 0o600);
|
|
55
|
+
try { await out.writeFile(`${JSON.stringify({ attestation: signed.attestation, evidence: signed.evidence })}\n`); }
|
|
56
|
+
finally { await out.close(); }
|
|
57
|
+
process.stdout.write(`Signed ${signed.checks} re-run checks. Attestation written to ${args.out}\n`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
|
|
61
|
+
main().catch(error => { process.stderr.write(`${error.message}\n`); process.exitCode = 1; });
|
|
62
|
+
}
|