@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,584 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createHash } from 'crypto';
|
|
4
|
+
import { constants } from 'fs';
|
|
5
|
+
import { promises as fs } from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import process from 'process';
|
|
8
|
+
import { credentialStatus, resolveApiKey } from './gofer-typesafe-credentials.mjs';
|
|
9
|
+
|
|
10
|
+
const POLICY_RELATIVE_PATH = path.join('.specify', 'config', 'typesafe-semantic-review.json');
|
|
11
|
+
const TYPE_SAFE_TOTAL_INPUT_LIMIT = 64 * 1024;
|
|
12
|
+
const TYPE_SAFE_STATE_QUESTION_LIMIT = 32 * 1024;
|
|
13
|
+
const TYPE_SAFE_SAFETY_RESERVE_BYTES = 4 * 1024;
|
|
14
|
+
const MAX_PROVIDER_ERROR_BYTES = 4 * 1024;
|
|
15
|
+
const MAX_PROVIDER_RESPONSE_BYTES = 64 * 1024;
|
|
16
|
+
const REQUESTED_MODEL = 'jev-latest';
|
|
17
|
+
const CHAT_SECRET_PATTERNS = [
|
|
18
|
+
/\bBearer\s+[A-Za-z0-9._~+\/-]+=*/gi,
|
|
19
|
+
/\b(?:sk|ghp|github_pat|xox[baprs])[-_][A-Za-z0-9_-]{12,}\b/g,
|
|
20
|
+
/\bTYPESAFE_API_KEY\s*=\s*[^\s]+/gi,
|
|
21
|
+
/\b(?:api[-_]?key|access[-_]?token|password|secret|token)\s*=\s*[^\s&]+/gi,
|
|
22
|
+
];
|
|
23
|
+
// A local artifact far larger than any real spec/plan/tasks file is treated
|
|
24
|
+
// as a hard error rather than hashed in full: this bounds worst-case memory
|
|
25
|
+
// use independent of the provider payload cap.
|
|
26
|
+
const MAX_ARTIFACT_READ_BYTES = 8 * 1024 * 1024;
|
|
27
|
+
const DELIVERY_ARTIFACTS = [
|
|
28
|
+
'goal-ledger.json',
|
|
29
|
+
'spec.md',
|
|
30
|
+
'plan.md',
|
|
31
|
+
'tasks.md',
|
|
32
|
+
'decisions.md',
|
|
33
|
+
'traceability.md',
|
|
34
|
+
'test-spec.md',
|
|
35
|
+
'change-manifest.json',
|
|
36
|
+
'blast-radius-report.md',
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
function digest(value) { return createHash('sha256').update(value).digest('hex'); }
|
|
40
|
+
// O_NOFOLLOW is unavailable on Windows; the bitwise OR silently contributes
|
|
41
|
+
// nothing there rather than erroring, which would otherwise look like
|
|
42
|
+
// protection that isn't actually applied. lstat detects a symlink or
|
|
43
|
+
// junction cross-platform (including Windows) and is the actual protection;
|
|
44
|
+
// O_NOFOLLOW only closes the small remaining gap between that check and the
|
|
45
|
+
// open, on platforms that support it.
|
|
46
|
+
const noFollowFlag = process.platform === 'win32' ? 0 : constants.O_NOFOLLOW;
|
|
47
|
+
async function assertNotSymlink(target) {
|
|
48
|
+
const info = await fs.lstat(target).catch((error) => {
|
|
49
|
+
if (error?.code === 'ENOENT') return null;
|
|
50
|
+
throw error;
|
|
51
|
+
});
|
|
52
|
+
if (info?.isSymbolicLink()) throw new Error('Gofer path must not be a symbolic link.');
|
|
53
|
+
return info;
|
|
54
|
+
}
|
|
55
|
+
async function openExistingNoFollow(target, flags) {
|
|
56
|
+
await assertNotSymlink(target);
|
|
57
|
+
return fs.open(target, flags | noFollowFlag);
|
|
58
|
+
}
|
|
59
|
+
// assertNoSymlinkComponents only walks descendants of root; it never checks
|
|
60
|
+
// root itself. A caller-supplied workspace that is a symlink (or missing, or
|
|
61
|
+
// not a directory) would otherwise sail through every confinement check
|
|
62
|
+
// below it. Matches workspace-bootstrap-lib.mjs's assertSafeWorkspaceRoot.
|
|
63
|
+
async function assertSafeWorkspaceRoot(workspaceRoot) {
|
|
64
|
+
const resolvedRoot = path.resolve(workspaceRoot);
|
|
65
|
+
const rootStat = await fs.lstat(resolvedRoot).catch((error) => {
|
|
66
|
+
if (error?.code === 'ENOENT') return null;
|
|
67
|
+
throw error;
|
|
68
|
+
});
|
|
69
|
+
if (!rootStat) throw new Error('Gofer workspace root does not exist.');
|
|
70
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
71
|
+
throw new Error('Gofer workspace root must be a real directory, not a symbolic link.');
|
|
72
|
+
}
|
|
73
|
+
return resolvedRoot;
|
|
74
|
+
}
|
|
75
|
+
// The event name is used verbatim to build the receipt file path. It is
|
|
76
|
+
// checked against policy.events, but that list is itself external config
|
|
77
|
+
// (or attacker-influenced if the config is compromised) and is never
|
|
78
|
+
// restricted to a safe charset, so a value like "../../evil" would satisfy
|
|
79
|
+
// the membership check while still escaping receiptDir on join. Restrict to
|
|
80
|
+
// a safe charset before it ever reaches a path.
|
|
81
|
+
function assertSafeEventName(event) {
|
|
82
|
+
if (!/^[A-Za-z0-9_-]+$/.test(event)) {
|
|
83
|
+
throw new Error('Gofer TypeSafe event name contains unsupported characters.');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// A lexical check alone does not stop a symlinked intermediate directory (or
|
|
87
|
+
// the feature directory itself) from redirecting reads/writes outside the
|
|
88
|
+
// workspace. Walk every component from the workspace root and reject any
|
|
89
|
+
// that is a symlink, matching this repository's other protected readers.
|
|
90
|
+
async function assertNoSymlinkComponents(root, relativeTarget) {
|
|
91
|
+
const components = relativeTarget.split(path.sep).filter(Boolean);
|
|
92
|
+
let currentPath = root;
|
|
93
|
+
for (const component of components) {
|
|
94
|
+
currentPath = path.join(currentPath, component);
|
|
95
|
+
try {
|
|
96
|
+
const status = await fs.lstat(currentPath);
|
|
97
|
+
if (status.isSymbolicLink()) throw new Error('Gofer feature directory must not pass through a symbolic link.');
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (error?.code === 'ENOENT') return;
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Reject any feature directory outside the workspace instead of trusting the
|
|
105
|
+
// caller: an escaping path would read arbitrary files, send their contents to
|
|
106
|
+
// the external provider, and write the receipt outside the workspace.
|
|
107
|
+
async function confined(workspace, value) {
|
|
108
|
+
const target = path.resolve(workspace, value);
|
|
109
|
+
const relative = path.relative(workspace, target);
|
|
110
|
+
if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
111
|
+
throw new Error('Gofer feature directory must remain inside the workspace.');
|
|
112
|
+
}
|
|
113
|
+
await assertNoSymlinkComponents(workspace, relative);
|
|
114
|
+
// A typo'd or nonexistent feature directory must fail closed here, not
|
|
115
|
+
// silently produce an all-empty state that could earn a false "aligned"
|
|
116
|
+
// verdict and then have the directory created out from under it by mkdir
|
|
117
|
+
// when the receipt is written.
|
|
118
|
+
const info = await fs.lstat(target).catch(() => null);
|
|
119
|
+
if (!info || !info.isDirectory()) throw new Error('Gofer feature directory does not exist.');
|
|
120
|
+
return target;
|
|
121
|
+
}
|
|
122
|
+
async function parseArgs(argv) {
|
|
123
|
+
const args = { workspace: process.cwd(), featureDir: '', event: '', json: false };
|
|
124
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
125
|
+
const arg = argv[index];
|
|
126
|
+
if (arg === '--workspace') { args.workspace = argv[++index] || args.workspace; }
|
|
127
|
+
else if (arg === '--feature-dir') { args.featureDir = argv[++index] || ''; }
|
|
128
|
+
else if (arg === '--event') { args.event = argv[++index] || ''; }
|
|
129
|
+
else if (arg === '--json') { args.json = true; }
|
|
130
|
+
else throw new Error(`Unknown argument: ${arg}`);
|
|
131
|
+
}
|
|
132
|
+
if (!args.featureDir || !args.event) throw new Error('--feature-dir and --event are required');
|
|
133
|
+
assertSafeEventName(args.event);
|
|
134
|
+
args.workspace = await assertSafeWorkspaceRoot(args.workspace); args.featureDir = await confined(args.workspace, args.featureDir); return args;
|
|
135
|
+
}
|
|
136
|
+
// The policy path is fixed, but a symlinked .specify/config directory or the
|
|
137
|
+
// policy file itself could still redirect this read outside the workspace,
|
|
138
|
+
// bypassing the same confinement invariant enforced for the feature
|
|
139
|
+
// directory and its artifacts. Apply the same no-follow protection.
|
|
140
|
+
async function readConfinedJson(workspace, relativePath) {
|
|
141
|
+
await assertNoSymlinkComponents(workspace, relativePath);
|
|
142
|
+
const target = path.join(workspace, relativePath);
|
|
143
|
+
const handle = await openExistingNoFollow(target, constants.O_RDONLY);
|
|
144
|
+
try { return JSON.parse(await handle.readFile('utf8')); } finally { await handle.close(); }
|
|
145
|
+
}
|
|
146
|
+
// Hash and retain the complete bounded text so the provider never judges an
|
|
147
|
+
// artifact prefix. The local read cap bounds memory. Open with O_NOFOLLOW so a
|
|
148
|
+
// same-account symlink swap cannot redirect the read. Missing artifacts stay
|
|
149
|
+
// distinct from empty files and fail closed before any provider request.
|
|
150
|
+
async function readArtifact(target) {
|
|
151
|
+
let handle;
|
|
152
|
+
try { handle = await openExistingNoFollow(target, constants.O_RDONLY); }
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (error?.code === 'ENOENT') return { present: false, sha256: digest(''), sent: '' };
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
const info = await handle.stat();
|
|
159
|
+
if (!info.isFile()) throw new Error('Gofer feature artifact must be a regular file.');
|
|
160
|
+
if (info.size > MAX_ARTIFACT_READ_BYTES) throw new Error('Gofer feature artifact exceeds the maximum readable size.');
|
|
161
|
+
const hash = createHash('sha256');
|
|
162
|
+
const contentChunks = [];
|
|
163
|
+
let fullBytes = 0;
|
|
164
|
+
// autoClose defaults to true, which would close the handle at EOF and
|
|
165
|
+
// make the finally block's own close() below fail on an already-closed
|
|
166
|
+
// handle. The finally block owns the close; the stream must not race it.
|
|
167
|
+
for await (const chunk of handle.createReadStream({ autoClose: false })) {
|
|
168
|
+
fullBytes += chunk.length;
|
|
169
|
+
if (fullBytes > MAX_ARTIFACT_READ_BYTES) throw new Error('Gofer feature artifact exceeds the maximum readable size.');
|
|
170
|
+
hash.update(chunk);
|
|
171
|
+
contentChunks.push(chunk);
|
|
172
|
+
}
|
|
173
|
+
const content = new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(contentChunks));
|
|
174
|
+
return { present: true, sha256: hash.digest('hex'), bytes: fullBytes, content, complete: true };
|
|
175
|
+
} finally { await handle.close(); }
|
|
176
|
+
}
|
|
177
|
+
function normalizeAnswer(answer) { return String(answer || '').trim().toLowerCase(); }
|
|
178
|
+
function redactChatContext(value) {
|
|
179
|
+
let redacted = String(value || '');
|
|
180
|
+
for (const pattern of CHAT_SECRET_PATTERNS) redacted = redacted.replace(pattern, '[REDACTED]');
|
|
181
|
+
return redacted;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const QUESTION_CHOICES = {
|
|
185
|
+
goal_alignment: ['aligned', 'partial', 'conflict'],
|
|
186
|
+
specification_currency: ['aligned', 'partial', 'conflict'],
|
|
187
|
+
test_coverage: ['aligned', 'partial', 'conflict'],
|
|
188
|
+
blast_radius: ['aligned', 'partial', 'conflict'],
|
|
189
|
+
chat_readiness: ['ready', 'reconcile', 'ask_user'],
|
|
190
|
+
required_action: ['continue', 'reconcile', 'ask_user'],
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
function buildQuestions(event) {
|
|
194
|
+
return {
|
|
195
|
+
goal_alignment: { type: 'choice', instructions: 'Does the current work remain aligned to the approved goal and specification?', criteria: { aligned: 'The work remains aligned.', partial: 'The work needs document reconciliation.', conflict: 'The work conflicts with approved direction.' } },
|
|
196
|
+
specification_currency: { type: 'choice', instructions: 'Compare the supplied specification, plan, tasks, decisions, traceability, and change manifest. Are the stated implementation paths, completed-task status, commit evidence, and material changes internally consistent and current? Select partial only when you can identify a concrete missing or stale record.', criteria: { aligned: 'All supplied delivery records agree on the current implementation and no concrete stale or missing record is present.', partial: 'A concrete delivery record is missing, stale, or inconsistent and needs reconciliation.', conflict: 'The recorded implementation conflicts with the approved specification or decision.' } },
|
|
197
|
+
test_coverage: { type: 'choice', instructions: 'Does the test specification map the changed behavior to executable owning-repository tests and any required eai-testing-dev release evidence?', criteria: { aligned: 'The changed behavior has complete executable coverage.', partial: 'The test evidence is incomplete or stale.', conflict: 'The test evidence contradicts the claimed behavior.' } },
|
|
198
|
+
blast_radius: { type: 'choice', instructions: 'Does the change manifest and blast-radius report cover all affected interfaces, packages, release surfaces, dependencies, rollback paths, and external test contracts?', criteria: { aligned: 'The blast radius is complete and contained.', partial: 'The blast-radius evidence is incomplete or stale.', conflict: 'The reported blast radius conflicts with the changed surface.' } },
|
|
199
|
+
...(event === 'chat_readiness' ? { chat_readiness: { type: 'choice', instructions: 'Using the supplied chat context summary and the current goal, specification, plan, tasks, decisions, and approved edit scope, is this chat ready to deliver the requested outcome? Identify any concrete missing context or contradiction.', criteria: { ready: 'The request and constraints are clear, the approved feature records match the current request, and the next task and edit scope are defined.', reconcile: 'The chat context or feature records have a concrete gap or inconsistency that must be reconciled before code edits.', ask_user: 'A material business, security, cost, deployment, or destructive decision requires the user.' } } } : {}),
|
|
200
|
+
required_action: { type: 'choice', instructions: 'Choose the action required by the evidence judgments. Select continue only when all are aligned; select reconcile when any is partial or evidence is missing; select ask_user only for a genuine conflict requiring a new business decision.', criteria: { continue: 'All evidence judgments are aligned.', reconcile: 'At least one evidence judgment is partial or missing.', ask_user: 'A conflict requires a material user decision.' } },
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// TypeSafe publishes token limits, not a tokenizer endpoint. Count the exact
|
|
205
|
+
// UTF-8 JSON representation as a conservative local upper bound and reserve
|
|
206
|
+
// 4 KiB for provider framing. This is not an exact token estimate.
|
|
207
|
+
export function measureTypeSafeInputBudget(state, questions) {
|
|
208
|
+
const stateBytes = Buffer.byteLength(JSON.stringify(state), 'utf8');
|
|
209
|
+
const questionEntries = Object.entries(questions || {});
|
|
210
|
+
const questionBytes = questionEntries.map(([id, question]) => Buffer.byteLength(JSON.stringify({ [id]: question }), 'utf8'));
|
|
211
|
+
const longestQuestionBytes = questionBytes.length ? Math.max(...questionBytes) : 0;
|
|
212
|
+
const allQuestionsBytes = Buffer.byteLength(JSON.stringify(questions || {}), 'utf8');
|
|
213
|
+
const statePlusLongestQuestionBytes = stateBytes + longestQuestionBytes;
|
|
214
|
+
const statePlusAllQuestionsBytes = stateBytes + allQuestionsBytes;
|
|
215
|
+
const safePairLimitBytes = TYPE_SAFE_STATE_QUESTION_LIMIT - TYPE_SAFE_SAFETY_RESERVE_BYTES;
|
|
216
|
+
const safeTotalLimitBytes = TYPE_SAFE_TOTAL_INPUT_LIMIT - TYPE_SAFE_SAFETY_RESERVE_BYTES;
|
|
217
|
+
return {
|
|
218
|
+
basis: 'utf8_serialized_bytes_upper_bound_not_tokenizer',
|
|
219
|
+
stateBytes,
|
|
220
|
+
longestQuestionBytes,
|
|
221
|
+
allQuestionsBytes,
|
|
222
|
+
statePlusLongestQuestionBytes,
|
|
223
|
+
statePlusAllQuestionsBytes,
|
|
224
|
+
safetyReserveBytes: TYPE_SAFE_SAFETY_RESERVE_BYTES,
|
|
225
|
+
publishedStateQuestionLimit: TYPE_SAFE_STATE_QUESTION_LIMIT,
|
|
226
|
+
publishedTotalInputLimit: TYPE_SAFE_TOTAL_INPUT_LIMIT,
|
|
227
|
+
safeStateQuestionLimitBytes: safePairLimitBytes,
|
|
228
|
+
safeTotalInputLimitBytes: safeTotalLimitBytes,
|
|
229
|
+
withinLimits: statePlusLongestQuestionBytes <= safePairLimitBytes && statePlusAllQuestionsBytes <= safeTotalLimitBytes,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function readBoundedResponseText(response, limit = MAX_PROVIDER_ERROR_BYTES) {
|
|
234
|
+
if (!response.body) return { text: '', truncated: false };
|
|
235
|
+
const reader = response.body.getReader();
|
|
236
|
+
const chunks = [];
|
|
237
|
+
let bytes = 0;
|
|
238
|
+
let truncated = false;
|
|
239
|
+
try {
|
|
240
|
+
while (true) {
|
|
241
|
+
const { done, value } = await reader.read();
|
|
242
|
+
if (done) break;
|
|
243
|
+
const remaining = limit - bytes;
|
|
244
|
+
if (value.length > remaining) {
|
|
245
|
+
if (remaining > 0) chunks.push(value.subarray(0, remaining));
|
|
246
|
+
bytes += Math.max(remaining, 0);
|
|
247
|
+
truncated = true;
|
|
248
|
+
await reader.cancel().catch(() => {});
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
chunks.push(value);
|
|
252
|
+
bytes += value.length;
|
|
253
|
+
}
|
|
254
|
+
} finally {
|
|
255
|
+
try { reader.releaseLock(); } catch {}
|
|
256
|
+
}
|
|
257
|
+
return { text: Buffer.concat(chunks).toString('utf8'), truncated };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function safeToken(value, maxLength = 80) {
|
|
261
|
+
return typeof value === 'string' && value.length <= maxLength && /^[A-Za-z0-9_.:-]+$/.test(value) ? value : null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function safeLocation(value) {
|
|
265
|
+
if (!Array.isArray(value)) return null;
|
|
266
|
+
const segments = value.slice(0, 8).map((segment) => {
|
|
267
|
+
if (Number.isInteger(segment) && segment >= 0) return String(segment);
|
|
268
|
+
return safeToken(segment);
|
|
269
|
+
});
|
|
270
|
+
return segments.length && segments.every(Boolean) ? segments.join('.') : null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function providerDiagnostics(response, bodyText, bodyTruncated) {
|
|
274
|
+
const headers = response.headers;
|
|
275
|
+
const requestId = safeToken(headers?.get?.('x-request-id') || headers?.get?.('request-id') || '', 128);
|
|
276
|
+
const diagnostic = { httpStatus: response.status };
|
|
277
|
+
if (requestId) diagnostic.requestId = requestId;
|
|
278
|
+
if (bodyTruncated) {
|
|
279
|
+
diagnostic.bodyTruncated = true;
|
|
280
|
+
return diagnostic;
|
|
281
|
+
}
|
|
282
|
+
let payload;
|
|
283
|
+
try { payload = JSON.parse(bodyText); } catch { return diagnostic; }
|
|
284
|
+
const code = safeToken(payload?.code || payload?.type);
|
|
285
|
+
if (code) diagnostic.code = code;
|
|
286
|
+
const details = Array.isArray(payload?.detail) ? payload.detail : Array.isArray(payload?.errors) ? payload.errors : [];
|
|
287
|
+
const validation = details.slice(0, 8).map((item) => ({
|
|
288
|
+
...(safeLocation(item?.loc) ? { location: safeLocation(item.loc) } : {}),
|
|
289
|
+
...(safeToken(item?.type) ? { type: safeToken(item.type) } : {}),
|
|
290
|
+
})).filter((item) => item.location || item.type);
|
|
291
|
+
if (validation.length) diagnostic.validation = validation;
|
|
292
|
+
return diagnostic;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function normalizeProbabilities(answer, questionId) {
|
|
296
|
+
if (!answer?.probabilities || typeof answer.probabilities !== 'object' || Array.isArray(answer.probabilities)) return null;
|
|
297
|
+
const allowed = new Set(QUESTION_CHOICES[questionId] || []);
|
|
298
|
+
const entries = Object.entries(answer.probabilities).filter(([choice, probability]) =>
|
|
299
|
+
allowed.has(choice) && typeof probability === 'number' && Number.isFinite(probability) && probability >= 0 && probability <= 1
|
|
300
|
+
);
|
|
301
|
+
const total = entries.reduce((sum, [, probability]) => sum + probability, 0);
|
|
302
|
+
return entries.length && Math.abs(total - 1) <= 0.02 ? Object.fromEntries(entries) : null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function answerRecord(answer, questionId) {
|
|
306
|
+
const probabilities = normalizeProbabilities(answer, questionId);
|
|
307
|
+
const choice = typeof answer?.choice === 'string' && QUESTION_CHOICES[questionId]?.includes(answer.choice) ? answer.choice : null;
|
|
308
|
+
const closestAlternative = probabilities
|
|
309
|
+
? Object.entries(probabilities).filter(([option]) => option !== choice).sort((left, right) => right[1] - left[1])[0]
|
|
310
|
+
: null;
|
|
311
|
+
return {
|
|
312
|
+
choice,
|
|
313
|
+
confidence: answer?.confidence ?? null,
|
|
314
|
+
probabilities,
|
|
315
|
+
closestAlternative: closestAlternative ? { choice: closestAlternative[0], probability: closestAlternative[1] } : null,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function saveReceipt(featureDir, event, receipt) {
|
|
320
|
+
const receiptRelativeDir = path.join('evidence', 'semantic-review');
|
|
321
|
+
await assertNoSymlinkComponents(featureDir, receiptRelativeDir);
|
|
322
|
+
const receiptDir = path.join(featureDir, receiptRelativeDir);
|
|
323
|
+
await fs.mkdir(receiptDir, { recursive: true });
|
|
324
|
+
const receiptPath = path.join(receiptDir, `${event}.json`);
|
|
325
|
+
await assertNotSymlink(receiptPath);
|
|
326
|
+
let previous;
|
|
327
|
+
try {
|
|
328
|
+
const previousHandle = await openExistingNoFollow(receiptPath, constants.O_RDONLY);
|
|
329
|
+
try { previous = await previousHandle.readFile('utf8'); } finally { await previousHandle.close(); }
|
|
330
|
+
} catch (error) {
|
|
331
|
+
if (error?.code !== 'ENOENT') throw error;
|
|
332
|
+
}
|
|
333
|
+
if (previous) {
|
|
334
|
+
let previousVersion;
|
|
335
|
+
try { previousVersion = JSON.parse(previous).schemaVersion; } catch {}
|
|
336
|
+
if (previousVersion === 2) {
|
|
337
|
+
const archivePath = path.join(receiptDir, `${event}.v2.json`);
|
|
338
|
+
await assertNotSymlink(archivePath);
|
|
339
|
+
try {
|
|
340
|
+
const archiveHandle = await fs.open(archivePath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollowFlag, 0o600);
|
|
341
|
+
try { await archiveHandle.writeFile(previous); } finally { await archiveHandle.close(); }
|
|
342
|
+
} catch (error) {
|
|
343
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
344
|
+
const archiveHandle = await openExistingNoFollow(archivePath, constants.O_RDONLY);
|
|
345
|
+
try {
|
|
346
|
+
if ((await archiveHandle.readFile('utf8')) !== previous) {
|
|
347
|
+
throw new Error('A different version-2 receipt archive already exists. Preserve it before writing a new review.');
|
|
348
|
+
}
|
|
349
|
+
} finally { await archiveHandle.close(); }
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const receiptHandle = await fs.open(receiptPath, constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | noFollowFlag, 0o600);
|
|
354
|
+
try { await receiptHandle.writeFile(`${JSON.stringify(receipt, null, 2)}\n`); } finally { await receiptHandle.close(); }
|
|
355
|
+
return receipt;
|
|
356
|
+
}
|
|
357
|
+
// A response outside this set is unexpected (a provider bug, a new API
|
|
358
|
+
// version, a malformed payload) and must not be read as a silent pass.
|
|
359
|
+
const KNOWN_ALIGNMENTS = new Set(['aligned', 'partial', 'conflict']);
|
|
360
|
+
const KNOWN_ACTIONS = new Set(['continue', 'reconcile', 'ask_user']);
|
|
361
|
+
const KNOWN_CHAT_READINESS = new Set(['ready', 'reconcile', 'ask_user']);
|
|
362
|
+
|
|
363
|
+
export async function runSemanticReview({ workspace = process.cwd(), featureDir, event, fetchImpl = globalThis.fetch, env = process.env, keychain, chatContext, onDemand = false } = {}) {
|
|
364
|
+
assertSafeEventName(event);
|
|
365
|
+
const resolvedWorkspace = await assertSafeWorkspaceRoot(workspace);
|
|
366
|
+
featureDir = await confined(resolvedWorkspace, featureDir);
|
|
367
|
+
const policy = await readConfinedJson(resolvedWorkspace, POLICY_RELATIVE_PATH);
|
|
368
|
+
const credentials = await credentialStatus({ workspace, env, keychain });
|
|
369
|
+
const globallyConnected = credentials.source === 'macos_keychain';
|
|
370
|
+
if (!policy.enabled && !globallyConnected && !onDemand) return { status: 'disabled', event };
|
|
371
|
+
// A syntactically valid but malformed enabled policy must not silently
|
|
372
|
+
// bypass its own gate: `confidence < undefined` is always false in JS, so
|
|
373
|
+
// a missing/non-numeric minimumConfidence would otherwise let any
|
|
374
|
+
// confidence "pass" the threshold check regardless of its actual value.
|
|
375
|
+
if (!Array.isArray(policy.events) || !policy.events.every((value) => typeof value === 'string') ||
|
|
376
|
+
typeof policy.minimumConfidence !== 'number' || !Number.isFinite(policy.minimumConfidence) ||
|
|
377
|
+
policy.minimumConfidence < 0 || policy.minimumConfidence > 1) {
|
|
378
|
+
throw new Error('Gofer TypeSafe policy is enabled but malformed (events or minimumConfidence).');
|
|
379
|
+
}
|
|
380
|
+
if (!policy.events.includes(event)) return { status: onDemand ? 'not_enabled_for_event' : 'disabled', event };
|
|
381
|
+
if (!credentials.configured) return { status: 'not_configured', event };
|
|
382
|
+
if (event === 'chat_readiness' && !String(chatContext || '').trim()) {
|
|
383
|
+
return { status: 'unavailable', event, reason: 'chat_context_missing' };
|
|
384
|
+
}
|
|
385
|
+
if (event !== 'chat_readiness' && chatContext !== undefined) {
|
|
386
|
+
return { status: 'unavailable', event, reason: 'chat_context_not_allowed_for_event' };
|
|
387
|
+
}
|
|
388
|
+
const artifacts = await Promise.all(DELIVERY_ARTIFACTS.map(async (name) => [name, await readArtifact(path.join(featureDir, name))]));
|
|
389
|
+
const missingArtifacts = artifacts.filter(([, info]) => !info.present).map(([name]) => name);
|
|
390
|
+
const artifactHashes = Object.fromEntries(artifacts.map(([name, info]) => [name, info.sha256]));
|
|
391
|
+
const artifactCoverage = Object.fromEntries(artifacts.map(([name, info]) => [name, {
|
|
392
|
+
present: info.present,
|
|
393
|
+
complete: Boolean(info.complete),
|
|
394
|
+
sourceBytes: info.bytes ?? 0,
|
|
395
|
+
sentBytes: 0,
|
|
396
|
+
}]));
|
|
397
|
+
const state = Object.fromEntries(artifacts.map(([name, info]) => [name, {
|
|
398
|
+
present: info.present,
|
|
399
|
+
complete: Boolean(info.complete),
|
|
400
|
+
sha256: info.sha256,
|
|
401
|
+
byteLength: info.bytes ?? 0,
|
|
402
|
+
content: info.content,
|
|
403
|
+
}]));
|
|
404
|
+
const redactedChatContext = event === 'chat_readiness' ? redactChatContext(chatContext) : '';
|
|
405
|
+
const chatContextSha256 = event === 'chat_readiness' ? digest(redactedChatContext) : null;
|
|
406
|
+
const chatContextBytes = event === 'chat_readiness' ? Buffer.byteLength(redactedChatContext, 'utf8') : null;
|
|
407
|
+
if (event === 'chat_readiness') {
|
|
408
|
+
state.chat_context = {
|
|
409
|
+
present: Boolean(redactedChatContext),
|
|
410
|
+
complete: true,
|
|
411
|
+
summaryOnly: true,
|
|
412
|
+
sha256: chatContextSha256,
|
|
413
|
+
byteLength: chatContextBytes,
|
|
414
|
+
content: redactedChatContext,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
const questions = buildQuestions(event);
|
|
418
|
+
const inputBudget = measureTypeSafeInputBudget(state, questions);
|
|
419
|
+
const missingRequiredEvidence = missingArtifacts.length > 0;
|
|
420
|
+
const baseReceipt = {
|
|
421
|
+
schemaVersion: 3,
|
|
422
|
+
provider: 'typesafe',
|
|
423
|
+
event,
|
|
424
|
+
status: 'unavailable',
|
|
425
|
+
reason: null,
|
|
426
|
+
requestAttempted: false,
|
|
427
|
+
requestSent: false,
|
|
428
|
+
requestedModel: REQUESTED_MODEL,
|
|
429
|
+
model: null,
|
|
430
|
+
usage: null,
|
|
431
|
+
inputBudget,
|
|
432
|
+
missingArtifacts,
|
|
433
|
+
missingRequiredEvidence,
|
|
434
|
+
policySha256: digest(JSON.stringify(policy)),
|
|
435
|
+
artifacts: artifactHashes,
|
|
436
|
+
artifactCoverage,
|
|
437
|
+
...(event === 'chat_readiness' ? { chatContextSha256, chatContextBytes } : {}),
|
|
438
|
+
confidence: null,
|
|
439
|
+
confidenceGate: {
|
|
440
|
+
minimum: policy.minimumConfidence,
|
|
441
|
+
observed: null,
|
|
442
|
+
belowMinimum: null,
|
|
443
|
+
weakestQuestion: null,
|
|
444
|
+
},
|
|
445
|
+
answers: {},
|
|
446
|
+
providerError: null,
|
|
447
|
+
};
|
|
448
|
+
if (missingRequiredEvidence) {
|
|
449
|
+
return saveReceipt(featureDir, event, {
|
|
450
|
+
...baseReceipt,
|
|
451
|
+
status: 'reconcile',
|
|
452
|
+
reason: 'required_artifacts_missing',
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
if (!inputBudget.withinLimits) {
|
|
456
|
+
return saveReceipt(featureDir, event, {
|
|
457
|
+
...baseReceipt,
|
|
458
|
+
status: 'unavailable',
|
|
459
|
+
reason: 'input_budget_exceeded',
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const { apiKey } = await resolveApiKey({ workspace, env, keychain });
|
|
464
|
+
let response;
|
|
465
|
+
try {
|
|
466
|
+
baseReceipt.requestAttempted = true;
|
|
467
|
+
response = await fetchImpl('https://api.typesafe.ai/v1/systemone', {
|
|
468
|
+
method: 'POST', headers: { authorization: `Bearer ${apiKey}`, 'content-type': 'application/json' },
|
|
469
|
+
signal: AbortSignal.timeout(10_000),
|
|
470
|
+
body: JSON.stringify({ model: REQUESTED_MODEL, state, questions }),
|
|
471
|
+
});
|
|
472
|
+
} catch (error) {
|
|
473
|
+
return saveReceipt(featureDir, event, {
|
|
474
|
+
...baseReceipt,
|
|
475
|
+
requestSent: null,
|
|
476
|
+
reason: error?.name === 'TimeoutError' || error?.name === 'AbortError' ? 'timeout' : 'network_error',
|
|
477
|
+
artifactCoverage: Object.fromEntries(artifacts.map(([name]) => [name, { ...artifactCoverage[name], sentBytes: null }])),
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
baseReceipt.requestSent = true;
|
|
481
|
+
baseReceipt.artifactCoverage = Object.fromEntries(artifacts.map(([name, info]) => [name, {
|
|
482
|
+
...artifactCoverage[name],
|
|
483
|
+
sentBytes: info.bytes ?? 0,
|
|
484
|
+
}]));
|
|
485
|
+
if (!response.ok) {
|
|
486
|
+
const errorBody = await readBoundedResponseText(response);
|
|
487
|
+
const providerError = providerDiagnostics(response, errorBody.text, errorBody.truncated);
|
|
488
|
+
return saveReceipt(featureDir, event, {
|
|
489
|
+
...baseReceipt,
|
|
490
|
+
requestSent: true,
|
|
491
|
+
reason: 'provider_http_error',
|
|
492
|
+
providerError,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
const responseBody = await readBoundedResponseText(response, MAX_PROVIDER_RESPONSE_BYTES);
|
|
496
|
+
if (responseBody.truncated) {
|
|
497
|
+
return saveReceipt(featureDir, event, { ...baseReceipt, reason: 'response_too_large' });
|
|
498
|
+
}
|
|
499
|
+
let payload;
|
|
500
|
+
try { payload = JSON.parse(responseBody.text); } catch {
|
|
501
|
+
return saveReceipt(featureDir, event, { ...baseReceipt, reason: 'invalid_response_body' });
|
|
502
|
+
}
|
|
503
|
+
const answers = payload.answers && typeof payload.answers === 'object' ? payload.answers : {};
|
|
504
|
+
const alignmentAnswer = answers.goal_alignment || {};
|
|
505
|
+
const specificationAnswer = answers.specification_currency || {};
|
|
506
|
+
const testAnswer = answers.test_coverage || {};
|
|
507
|
+
const blastRadiusAnswer = answers.blast_radius || {};
|
|
508
|
+
const actionAnswer = answers.required_action || {};
|
|
509
|
+
const chatReadinessAnswer = answers.chat_readiness || {};
|
|
510
|
+
const alignment = normalizeAnswer(alignmentAnswer.choice);
|
|
511
|
+
const specification = normalizeAnswer(specificationAnswer.choice);
|
|
512
|
+
const testCoverage = normalizeAnswer(testAnswer.choice);
|
|
513
|
+
const blastRadius = normalizeAnswer(blastRadiusAnswer.choice);
|
|
514
|
+
const action = normalizeAnswer(actionAnswer.choice);
|
|
515
|
+
const chatReadiness = event === 'chat_readiness' ? normalizeAnswer(chatReadinessAnswer.choice) : '';
|
|
516
|
+
// An invalid or out-of-range confidence must count as zero, not be
|
|
517
|
+
// dropped: dropping it would let one bad value be outweighed by the
|
|
518
|
+
// other, and an out-of-range value (e.g. 2) would otherwise satisfy the
|
|
519
|
+
// minimum-confidence check on a malformed response.
|
|
520
|
+
const responseAnswers = [alignmentAnswer, specificationAnswer, testAnswer, blastRadiusAnswer, actionAnswer,
|
|
521
|
+
...(event === 'chat_readiness' ? [chatReadinessAnswer] : [])];
|
|
522
|
+
const confidences = responseAnswers.map((answer) => {
|
|
523
|
+
const value = Number(answer.confidence);
|
|
524
|
+
return Number.isFinite(value) && value >= 0 && value <= 1 ? value : 0;
|
|
525
|
+
});
|
|
526
|
+
const confidence = Math.min(...confidences);
|
|
527
|
+
// An answer outside the known choice set fails toward reconcile, not
|
|
528
|
+
// toward a silent aligned pass: an unexpected label must not be read as
|
|
529
|
+
// "everything is fine".
|
|
530
|
+
const evidenceAlignments = [alignment, specification, testCoverage, blastRadius];
|
|
531
|
+
const recognized = evidenceAlignments.every((value) => KNOWN_ALIGNMENTS.has(value)) && KNOWN_ACTIONS.has(action) &&
|
|
532
|
+
(event !== 'chat_readiness' || KNOWN_CHAT_READINESS.has(chatReadiness));
|
|
533
|
+
const status = evidenceAlignments.includes('conflict') || action === 'ask_user' || chatReadiness === 'ask_user' ? 'conflict'
|
|
534
|
+
: !recognized || confidence < policy.minimumConfidence || evidenceAlignments.includes('partial') || action === 'reconcile' || chatReadiness === 'reconcile' ? 'reconcile'
|
|
535
|
+
: 'aligned';
|
|
536
|
+
const answerIds = ['goal_alignment', 'specification_currency', 'test_coverage', 'blast_radius', 'required_action',
|
|
537
|
+
...(event === 'chat_readiness' ? ['chat_readiness'] : [])];
|
|
538
|
+
const answerRecords = Object.fromEntries(answerIds.map((id) => [id, answerRecord(answers[id] || {}, id)]));
|
|
539
|
+
const weakestIndex = confidences.indexOf(confidence);
|
|
540
|
+
const weakestQuestionId = answerIds[weakestIndex] || null;
|
|
541
|
+
const weakestQuestion = weakestQuestionId ? answerRecords[weakestQuestionId] : null;
|
|
542
|
+
const providerModel = safeToken(payload.model, 80);
|
|
543
|
+
const usage = payload.usage && Number.isSafeInteger(payload.usage.input_tokens) && payload.usage.input_tokens >= 0 &&
|
|
544
|
+
Number.isSafeInteger(payload.usage.output_tokens) && payload.usage.output_tokens >= 0
|
|
545
|
+
? { inputTokens: payload.usage.input_tokens, outputTokens: payload.usage.output_tokens }
|
|
546
|
+
: null;
|
|
547
|
+
return saveReceipt(featureDir, event, {
|
|
548
|
+
...baseReceipt,
|
|
549
|
+
status,
|
|
550
|
+
reason: status === 'reconcile' ? (confidence < policy.minimumConfidence ? 'confidence_below_minimum_or_evidence_needs_reconciliation' : 'evidence_needs_reconciliation') : null,
|
|
551
|
+
requestSent: true,
|
|
552
|
+
model: providerModel,
|
|
553
|
+
usage,
|
|
554
|
+
confidence,
|
|
555
|
+
confidenceGate: {
|
|
556
|
+
minimum: policy.minimumConfidence,
|
|
557
|
+
observed: confidence,
|
|
558
|
+
belowMinimum: confidence < policy.minimumConfidence,
|
|
559
|
+
weakestQuestion: weakestQuestionId ? {
|
|
560
|
+
id: weakestQuestionId,
|
|
561
|
+
choice: weakestQuestion.choice,
|
|
562
|
+
confidence: weakestQuestion.confidence,
|
|
563
|
+
probabilities: weakestQuestion.probabilities,
|
|
564
|
+
closestAlternative: weakestQuestion.closestAlternative,
|
|
565
|
+
} : null,
|
|
566
|
+
},
|
|
567
|
+
answers: answerRecords,
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
// A CI/automation caller must not read "exit 0" as a pass for anything other
|
|
571
|
+
// than a genuine alignment or an intentionally inactive review: reconcile,
|
|
572
|
+
// unavailable, and not_configured all mean the checkpoint was not verified.
|
|
573
|
+
export function exitCodeForStatus(status) {
|
|
574
|
+
if (status === 'conflict') return 2;
|
|
575
|
+
if (!['disabled', 'aligned'].includes(status)) return 3;
|
|
576
|
+
return 0;
|
|
577
|
+
}
|
|
578
|
+
async function main() {
|
|
579
|
+
const args = await parseArgs(process.argv.slice(2));
|
|
580
|
+
const result = await runSemanticReview(args);
|
|
581
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
582
|
+
process.exitCode = exitCodeForStatus(result.status);
|
|
583
|
+
}
|
|
584
|
+
if (import.meta.url === `file://${process.argv[1]}`) main().catch((error) => { process.stderr.write(`${error.message}\n`); process.exitCode = 1; });
|