@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
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { promises as fs } from 'fs';
|
|
4
|
+
import { randomUUID } from 'crypto';
|
|
4
5
|
import path from 'path';
|
|
5
6
|
import { fileURLToPath } from 'url';
|
|
7
|
+
import { SUPPORTED_HOSTS } from './gofer-surface-update.mjs';
|
|
6
8
|
import { parseStageCommand } from './parse-stage-command.mjs';
|
|
7
9
|
|
|
8
10
|
export const GOFER_VERSION_FILE = path.join('.specify', '.gofer-version');
|
|
9
11
|
export const CORE_SENTINELS = [
|
|
10
12
|
GOFER_VERSION_FILE,
|
|
11
13
|
path.join('.specify', 'commands', '0_gofer_start.md'),
|
|
14
|
+
path.join('.specify', 'config', 'object-type-routing.json'),
|
|
15
|
+
path.join('.specify', 'config', 'typesafe-semantic-review.json'),
|
|
12
16
|
path.join('.specify', 'templates', 'spec-template.md'),
|
|
17
|
+
path.join('.specify', 'templates', 'build-map-template.md'),
|
|
13
18
|
path.join('.specify', 'templates', 'loop-contract-template.json'),
|
|
19
|
+
path.join('.specify', 'templates', 'business-scenarios-template.json'),
|
|
14
20
|
path.join('.specify', 'templates', 'working-backwards-prfaq-template.md'),
|
|
15
21
|
path.join('.specify', 'templates', 'business-owner-summary-template.md'),
|
|
16
22
|
path.join('.specify', 'templates', 'cto-architecture-summary-template.md'),
|
|
@@ -21,7 +27,11 @@ export const CORE_SENTINELS = [
|
|
|
21
27
|
path.join('.specify', 'templates', 'brand', 'marp-theme-template.css'),
|
|
22
28
|
path.join('.specify', 'scripts', 'bash', 'create-new-feature.sh'),
|
|
23
29
|
path.join('.specify', 'scripts', 'node', 'parse-stage-command.mjs'),
|
|
30
|
+
path.join('.specify', 'scripts', 'node', 'gofer-local-settings-cleanup.mjs'),
|
|
24
31
|
path.join('.specify', 'scripts', 'node', 'gofer-loop-audit.mjs'),
|
|
32
|
+
path.join('.specify', 'scripts', 'node', 'gofer-typesafe-credentials.mjs'),
|
|
33
|
+
path.join('.specify', 'scripts', 'node', 'gofer-semantic-drift.mjs'),
|
|
34
|
+
path.join('.specify', 'scripts', 'node', 'gofer-ui-preview.mjs'),
|
|
25
35
|
path.join('.specify', 'scripts', 'hooks', 'post-tool-use.mjs'),
|
|
26
36
|
path.join('.specify', 'scripts', 'powershell', 'install-optional-tools.ps1'),
|
|
27
37
|
path.join('.specify', 'references', 'platform', 'README.md'),
|
|
@@ -42,8 +52,11 @@ const LEGACY_MANAGED_PATHS = [
|
|
|
42
52
|
path.join('.gemini', 'commands', 'gofer', '0_business_scenario.md'),
|
|
43
53
|
path.join('.gemini', 'commands', 'gofer', '0_business_scenario.toml'),
|
|
44
54
|
];
|
|
55
|
+
const RETIRED_PUBLIC_ENTRYPOINT_STEMS = ['gofer'];
|
|
45
56
|
const LEGACY_MANAGED_ARCHIVE_ROOT = path.join('.specify', 'logs', 'legacy-command-backups');
|
|
46
57
|
|
|
58
|
+
export const WORKSPACE_HOSTS = SUPPORTED_HOSTS;
|
|
59
|
+
|
|
47
60
|
export const HOST_POLICIES = {
|
|
48
61
|
auto: { required: [] },
|
|
49
62
|
claude: {
|
|
@@ -55,11 +68,21 @@ export const HOST_POLICIES = {
|
|
|
55
68
|
copilot: {
|
|
56
69
|
required: [path.join('.github', 'copilot-instructions.md')],
|
|
57
70
|
},
|
|
58
|
-
|
|
59
|
-
required: [],
|
|
71
|
+
antigravity: {
|
|
72
|
+
required: ['AGENTS.md', 'GEMINI.md'],
|
|
73
|
+
},
|
|
74
|
+
grok: {
|
|
75
|
+
required: ['AGENTS.md'],
|
|
76
|
+
},
|
|
77
|
+
vscode: {
|
|
78
|
+
required: [path.join('.github', 'copilot-instructions.md')],
|
|
60
79
|
},
|
|
61
80
|
};
|
|
62
81
|
|
|
82
|
+
const LEGACY_HOST_ALIASES = {
|
|
83
|
+
gemini: 'antigravity',
|
|
84
|
+
};
|
|
85
|
+
|
|
63
86
|
const WORKSPACE_MARKERS = [
|
|
64
87
|
'.git',
|
|
65
88
|
'package.json',
|
|
@@ -77,7 +100,9 @@ const EAI_RUNTIME_CONTRACT_MARKER = 'eai.runtime.json';
|
|
|
77
100
|
|
|
78
101
|
const EXTENSION_RESOURCE_PATHS = new Map([
|
|
79
102
|
[path.join('.specify', 'commands'), path.join('resources', 'specify-commands')],
|
|
103
|
+
[path.join('.specify', 'config'), path.join('resources', 'specify-config')],
|
|
80
104
|
[path.join('.specify', 'references'), path.join('resources', 'references')],
|
|
105
|
+
[path.join('.specify', 'schemas'), path.join('resources', 'schemas')],
|
|
81
106
|
[path.join('.specify', 'templates'), path.join('resources', 'templates')],
|
|
82
107
|
[path.join('.specify', 'scripts', 'bash'), path.join('resources', 'bash-scripts')],
|
|
83
108
|
[path.join('.specify', 'scripts', 'node'), path.join('resources', 'node-scripts')],
|
|
@@ -94,6 +119,7 @@ const EXTENSION_RESOURCE_PATHS = new Map([
|
|
|
94
119
|
[path.join('.github', 'instructions'), path.join('resources', 'copilot-instructions')],
|
|
95
120
|
[path.join('.github', 'skills'), path.join('resources', 'github-skills')],
|
|
96
121
|
[path.join('.gemini'), path.join('resources', 'gemini')],
|
|
122
|
+
[path.join('.grok', 'skills'), path.join('resources', 'grok-skills')],
|
|
97
123
|
[path.join('.agents', 'skills'), 'skills'],
|
|
98
124
|
[path.join('.system', 'skills'), 'skills'],
|
|
99
125
|
]);
|
|
@@ -108,6 +134,7 @@ const GOFER_GITIGNORE_ENTRIES = [
|
|
|
108
134
|
'.specify/memory/context-health-state.json',
|
|
109
135
|
'.specify/memory/observation-cache/',
|
|
110
136
|
'.specify/specs/*/research-index.json',
|
|
137
|
+
'.specify/secrets/',
|
|
111
138
|
];
|
|
112
139
|
|
|
113
140
|
const CLAUDE_HOOKS_CONFIG = {
|
|
@@ -160,8 +187,19 @@ function createEmptyProjectInfo(workspaceRoot) {
|
|
|
160
187
|
}
|
|
161
188
|
|
|
162
189
|
export function normalizeHost(host = 'auto') {
|
|
163
|
-
const normalized =
|
|
164
|
-
|
|
190
|
+
const normalized = host == null ? 'auto' : String(host).trim().toLowerCase();
|
|
191
|
+
if (normalized === '' || normalized === 'auto') {
|
|
192
|
+
return 'auto';
|
|
193
|
+
}
|
|
194
|
+
if (Object.prototype.hasOwnProperty.call(LEGACY_HOST_ALIASES, normalized)) {
|
|
195
|
+
return LEGACY_HOST_ALIASES[normalized];
|
|
196
|
+
}
|
|
197
|
+
if (Object.prototype.hasOwnProperty.call(HOST_POLICIES, normalized)) {
|
|
198
|
+
return normalized;
|
|
199
|
+
}
|
|
200
|
+
throw new Error(
|
|
201
|
+
`Unsupported Gofer host. Expected auto or one of: ${WORKSPACE_HOSTS.join(', ')}.`
|
|
202
|
+
);
|
|
165
203
|
}
|
|
166
204
|
|
|
167
205
|
export function scriptRootFromUrl(scriptUrl) {
|
|
@@ -185,46 +223,392 @@ export async function pathExists(targetPath) {
|
|
|
185
223
|
}
|
|
186
224
|
}
|
|
187
225
|
|
|
188
|
-
function
|
|
189
|
-
|
|
226
|
+
async function lstatIfExists(targetPath) {
|
|
227
|
+
try {
|
|
228
|
+
return await fs.lstat(targetPath);
|
|
229
|
+
} catch (error) {
|
|
230
|
+
if (error?.code === 'ENOENT') {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function resolveConfinedWorkspacePath(workspaceRoot, relativePath) {
|
|
238
|
+
const resolvedRoot = path.resolve(workspaceRoot);
|
|
239
|
+
if (path.isAbsolute(relativePath)) {
|
|
240
|
+
throw new Error('Unsafe Gofer workspace path: absolute managed paths are not allowed.');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const targetPath = path.resolve(resolvedRoot, relativePath);
|
|
244
|
+
const relativeTarget = path.relative(resolvedRoot, targetPath);
|
|
245
|
+
if (
|
|
246
|
+
relativeTarget === '..' ||
|
|
247
|
+
relativeTarget.startsWith(`..${path.sep}`) ||
|
|
248
|
+
path.isAbsolute(relativeTarget)
|
|
249
|
+
) {
|
|
250
|
+
throw new Error('Unsafe Gofer workspace path: managed paths must stay inside the workspace.');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return { resolvedRoot, targetPath, relativeTarget };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function assertSafeWorkspaceRoot(workspaceRoot) {
|
|
257
|
+
const resolvedRoot = path.resolve(workspaceRoot);
|
|
258
|
+
const rootStat = await lstatIfExists(resolvedRoot);
|
|
259
|
+
if (!rootStat) {
|
|
260
|
+
throw new Error('Unsafe Gofer workspace path: the workspace root does not exist.');
|
|
261
|
+
}
|
|
262
|
+
if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
'Unsafe Gofer workspace path: the workspace root must be a real directory, not a symbolic link.'
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
return resolvedRoot;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function inspectWorkspacePath(
|
|
271
|
+
workspaceRoot,
|
|
272
|
+
relativePath,
|
|
273
|
+
{ leafType = 'any', allowMissing = true } = {}
|
|
274
|
+
) {
|
|
275
|
+
const { resolvedRoot, targetPath, relativeTarget } = resolveConfinedWorkspacePath(
|
|
276
|
+
workspaceRoot,
|
|
277
|
+
relativePath
|
|
278
|
+
);
|
|
279
|
+
await assertSafeWorkspaceRoot(resolvedRoot);
|
|
280
|
+
|
|
281
|
+
if (relativeTarget === '') {
|
|
282
|
+
return {
|
|
283
|
+
exists: true,
|
|
284
|
+
path: resolvedRoot,
|
|
285
|
+
stat: await fs.lstat(resolvedRoot),
|
|
286
|
+
relativeTarget,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const components = relativeTarget.split(path.sep).filter(Boolean);
|
|
291
|
+
let currentPath = resolvedRoot;
|
|
292
|
+
let leafStat = null;
|
|
293
|
+
for (let index = 0; index < components.length; index += 1) {
|
|
294
|
+
currentPath = path.join(currentPath, components[index]);
|
|
295
|
+
const currentStat = await lstatIfExists(currentPath);
|
|
296
|
+
if (!currentStat) {
|
|
297
|
+
if (!allowMissing) {
|
|
298
|
+
throw new Error('Unsafe Gofer workspace path: a required managed path is missing.');
|
|
299
|
+
}
|
|
300
|
+
return { exists: false, path: targetPath, stat: null, relativeTarget };
|
|
301
|
+
}
|
|
302
|
+
if (currentStat.isSymbolicLink()) {
|
|
303
|
+
throw new Error(
|
|
304
|
+
'Unsafe Gofer workspace path: symbolic links are not allowed in managed paths.'
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const isLeaf = index === components.length - 1;
|
|
309
|
+
if (isLeaf) {
|
|
310
|
+
leafStat = currentStat;
|
|
311
|
+
}
|
|
312
|
+
if (!isLeaf && !currentStat.isDirectory()) {
|
|
313
|
+
throw new Error(
|
|
314
|
+
'Unsafe Gofer workspace path: every managed parent component must be a directory.'
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
if (isLeaf && leafType === 'directory' && !currentStat.isDirectory()) {
|
|
318
|
+
throw new Error('Unsafe Gofer workspace path: the managed path must be a directory.');
|
|
319
|
+
}
|
|
320
|
+
if (isLeaf && leafType === 'file' && !currentStat.isFile()) {
|
|
321
|
+
throw new Error('Unsafe Gofer workspace path: the managed path must be a regular file.');
|
|
322
|
+
}
|
|
323
|
+
if (
|
|
324
|
+
isLeaf &&
|
|
325
|
+
leafType === 'any' &&
|
|
326
|
+
!currentStat.isDirectory() &&
|
|
327
|
+
!currentStat.isFile()
|
|
328
|
+
) {
|
|
329
|
+
throw new Error(
|
|
330
|
+
'Unsafe Gofer workspace path: managed paths must be regular files or directories.'
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
exists: true,
|
|
337
|
+
path: targetPath,
|
|
338
|
+
stat: leafStat,
|
|
339
|
+
relativeTarget,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
async function ensureSafeWorkspaceDirectory(workspaceRoot, relativePath, dryRun) {
|
|
344
|
+
const { resolvedRoot, targetPath, relativeTarget } = resolveConfinedWorkspacePath(
|
|
345
|
+
workspaceRoot,
|
|
346
|
+
relativePath
|
|
347
|
+
);
|
|
348
|
+
await assertSafeWorkspaceRoot(resolvedRoot);
|
|
349
|
+
if (relativeTarget === '') {
|
|
350
|
+
return targetPath;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const components = relativeTarget.split(path.sep).filter(Boolean);
|
|
354
|
+
let currentPath = resolvedRoot;
|
|
355
|
+
for (const component of components) {
|
|
356
|
+
currentPath = path.join(currentPath, component);
|
|
357
|
+
let currentStat = await lstatIfExists(currentPath);
|
|
358
|
+
if (!currentStat) {
|
|
359
|
+
if (dryRun) {
|
|
360
|
+
return targetPath;
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
await fs.mkdir(currentPath);
|
|
364
|
+
} catch (error) {
|
|
365
|
+
if (error?.code !== 'EEXIST') {
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
currentStat = await lstatIfExists(currentPath);
|
|
370
|
+
}
|
|
371
|
+
if (!currentStat || currentStat.isSymbolicLink() || !currentStat.isDirectory()) {
|
|
372
|
+
throw new Error(
|
|
373
|
+
'Unsafe Gofer workspace path: every managed directory component must be a real directory; symbolic links are not allowed.'
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return targetPath;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function directoryIdentityMatches(left, right) {
|
|
382
|
+
return (
|
|
383
|
+
left.dev === right.dev &&
|
|
384
|
+
left.ino === right.ino &&
|
|
385
|
+
left.isDirectory() &&
|
|
386
|
+
right.isDirectory()
|
|
387
|
+
);
|
|
190
388
|
}
|
|
191
389
|
|
|
192
|
-
async function
|
|
193
|
-
await fs.
|
|
390
|
+
async function openValidatedDirectory(directoryPath) {
|
|
391
|
+
const initialStat = await fs.lstat(directoryPath);
|
|
392
|
+
let handle = null;
|
|
194
393
|
try {
|
|
195
|
-
await fs.
|
|
394
|
+
handle = await fs.open(directoryPath, 'r');
|
|
196
395
|
} catch (error) {
|
|
197
|
-
if (
|
|
396
|
+
if (
|
|
397
|
+
process.platform !== 'win32' ||
|
|
398
|
+
!['EACCES', 'EISDIR', 'EPERM'].includes(String(error?.code))
|
|
399
|
+
) {
|
|
198
400
|
throw error;
|
|
199
401
|
}
|
|
402
|
+
}
|
|
403
|
+
const validation = { handle, initialStat };
|
|
404
|
+
await assertDirectoryMatchesPath(validation, directoryPath);
|
|
405
|
+
return validation;
|
|
406
|
+
}
|
|
200
407
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
408
|
+
async function assertDirectoryMatchesPath(validation, directoryPath) {
|
|
409
|
+
const [openedStat, pathStat] = await Promise.all([
|
|
410
|
+
validation.handle ? validation.handle.stat() : validation.initialStat,
|
|
411
|
+
fs.lstat(directoryPath),
|
|
412
|
+
]);
|
|
413
|
+
if (pathStat.isSymbolicLink() || !directoryIdentityMatches(openedStat, pathStat)) {
|
|
414
|
+
throw new Error(
|
|
415
|
+
'Unsafe Gofer workspace path: a managed parent directory changed during validation.'
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async function writeWorkspaceFile(workspaceRoot, relativePath, content, dryRun, mode = null) {
|
|
421
|
+
const target = await inspectWorkspacePath(workspaceRoot, relativePath, {
|
|
422
|
+
leafType: 'file',
|
|
423
|
+
allowMissing: true,
|
|
424
|
+
});
|
|
425
|
+
if (dryRun) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const parentRelativePath = path.dirname(target.relativeTarget);
|
|
430
|
+
const normalizedParent = parentRelativePath === '.' ? '' : parentRelativePath;
|
|
431
|
+
const parentPath = await ensureSafeWorkspaceDirectory(
|
|
432
|
+
workspaceRoot,
|
|
433
|
+
normalizedParent,
|
|
434
|
+
false
|
|
435
|
+
);
|
|
436
|
+
const existingMode = target.exists ? target.stat.mode & 0o777 : 0o666;
|
|
437
|
+
const temporaryName = `.gofer-write-${process.pid}-${randomUUID()}.tmp`;
|
|
438
|
+
const temporaryRelativePath = path.join(normalizedParent, temporaryName);
|
|
439
|
+
const temporaryPath = path.join(parentPath, temporaryName);
|
|
440
|
+
let temporaryHandle;
|
|
441
|
+
let parentValidation;
|
|
442
|
+
|
|
443
|
+
try {
|
|
444
|
+
parentValidation = await openValidatedDirectory(parentPath);
|
|
445
|
+
await inspectWorkspacePath(workspaceRoot, temporaryRelativePath, {
|
|
446
|
+
leafType: 'file',
|
|
447
|
+
allowMissing: true,
|
|
205
448
|
});
|
|
206
|
-
await fs.
|
|
449
|
+
temporaryHandle = await fs.open(temporaryPath, 'wx', mode ?? existingMode);
|
|
450
|
+
await temporaryHandle.writeFile(content);
|
|
451
|
+
await temporaryHandle.sync();
|
|
452
|
+
await temporaryHandle.close();
|
|
453
|
+
temporaryHandle = null;
|
|
454
|
+
|
|
455
|
+
await inspectWorkspacePath(workspaceRoot, normalizedParent, {
|
|
456
|
+
leafType: 'directory',
|
|
457
|
+
allowMissing: false,
|
|
458
|
+
});
|
|
459
|
+
await assertDirectoryMatchesPath(parentValidation, parentPath);
|
|
460
|
+
await inspectWorkspacePath(workspaceRoot, relativePath, {
|
|
461
|
+
leafType: 'file',
|
|
462
|
+
allowMissing: true,
|
|
463
|
+
});
|
|
464
|
+
await fs.rename(temporaryPath, target.path);
|
|
465
|
+
await assertDirectoryMatchesPath(parentValidation, parentPath);
|
|
466
|
+
await inspectWorkspacePath(workspaceRoot, relativePath, {
|
|
467
|
+
leafType: 'file',
|
|
468
|
+
allowMissing: false,
|
|
469
|
+
});
|
|
470
|
+
} finally {
|
|
471
|
+
if (temporaryHandle) {
|
|
472
|
+
await temporaryHandle.close().catch(() => {});
|
|
473
|
+
}
|
|
474
|
+
if (parentValidation) {
|
|
475
|
+
try {
|
|
476
|
+
await assertDirectoryMatchesPath(parentValidation, parentPath);
|
|
477
|
+
await fs.rm(temporaryPath, { force: true });
|
|
478
|
+
} catch {
|
|
479
|
+
// If the parent identity changed, do not risk deleting through its replacement.
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
if (parentValidation?.handle) {
|
|
483
|
+
await parentValidation.handle.close().catch(() => {});
|
|
484
|
+
}
|
|
207
485
|
}
|
|
208
486
|
}
|
|
209
487
|
|
|
488
|
+
function buildArchiveStamp() {
|
|
489
|
+
return `${new Date().toISOString().replace(/[:.]/g, '-')}-${process.pid}`;
|
|
490
|
+
}
|
|
491
|
+
|
|
210
492
|
async function archiveLegacyManagedPath(workspaceRoot, relativePath, archiveStamp, dryRun) {
|
|
211
493
|
const archiveRelativePath = path.join(LEGACY_MANAGED_ARCHIVE_ROOT, archiveStamp, relativePath);
|
|
494
|
+
const source = await inspectWorkspacePath(workspaceRoot, relativePath, {
|
|
495
|
+
leafType: 'any',
|
|
496
|
+
allowMissing: false,
|
|
497
|
+
});
|
|
498
|
+
const archiveTarget = await inspectWorkspacePath(workspaceRoot, archiveRelativePath, {
|
|
499
|
+
leafType: 'any',
|
|
500
|
+
allowMissing: true,
|
|
501
|
+
});
|
|
502
|
+
if (archiveTarget.exists) {
|
|
503
|
+
throw new Error('Unsafe Gofer archive path: the managed archive target already exists.');
|
|
504
|
+
}
|
|
505
|
+
|
|
212
506
|
if (!dryRun) {
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
507
|
+
const archiveParent = path.dirname(archiveRelativePath);
|
|
508
|
+
const archiveParentPath = await ensureSafeWorkspaceDirectory(
|
|
509
|
+
workspaceRoot,
|
|
510
|
+
archiveParent,
|
|
511
|
+
false
|
|
216
512
|
);
|
|
513
|
+
const sourceParentPath = resolveConfinedWorkspacePath(
|
|
514
|
+
workspaceRoot,
|
|
515
|
+
path.dirname(relativePath)
|
|
516
|
+
).targetPath;
|
|
517
|
+
const sourceParentValidation = await openValidatedDirectory(sourceParentPath);
|
|
518
|
+
const archiveParentValidation = await openValidatedDirectory(archiveParentPath);
|
|
519
|
+
try {
|
|
520
|
+
await assertDirectoryMatchesPath(sourceParentValidation, sourceParentPath);
|
|
521
|
+
await assertDirectoryMatchesPath(archiveParentValidation, archiveParentPath);
|
|
522
|
+
const currentSource = await inspectWorkspacePath(workspaceRoot, relativePath, {
|
|
523
|
+
leafType: 'any',
|
|
524
|
+
allowMissing: false,
|
|
525
|
+
});
|
|
526
|
+
if (
|
|
527
|
+
currentSource.stat.dev !== source.stat.dev ||
|
|
528
|
+
currentSource.stat.ino !== source.stat.ino ||
|
|
529
|
+
currentSource.stat.isDirectory() !== source.stat.isDirectory() ||
|
|
530
|
+
currentSource.stat.isFile() !== source.stat.isFile()
|
|
531
|
+
) {
|
|
532
|
+
throw new Error(
|
|
533
|
+
'Unsafe Gofer archive path: the legacy path changed during validation.'
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
const currentArchiveTarget = await inspectWorkspacePath(
|
|
537
|
+
workspaceRoot,
|
|
538
|
+
archiveRelativePath,
|
|
539
|
+
{ leafType: 'any', allowMissing: true }
|
|
540
|
+
);
|
|
541
|
+
if (currentArchiveTarget.exists) {
|
|
542
|
+
throw new Error('Unsafe Gofer archive path: the managed archive target already exists.');
|
|
543
|
+
}
|
|
544
|
+
await fs.rename(source.path, archiveTarget.path);
|
|
545
|
+
await assertDirectoryMatchesPath(sourceParentValidation, sourceParentPath);
|
|
546
|
+
await assertDirectoryMatchesPath(archiveParentValidation, archiveParentPath);
|
|
547
|
+
} catch (error) {
|
|
548
|
+
if (error?.code === 'EXDEV') {
|
|
549
|
+
throw new Error(
|
|
550
|
+
'Unsafe Gofer archive path: cross-device legacy archives are not supported.'
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
throw error;
|
|
554
|
+
} finally {
|
|
555
|
+
if (sourceParentValidation.handle) {
|
|
556
|
+
await sourceParentValidation.handle.close().catch(() => {});
|
|
557
|
+
}
|
|
558
|
+
if (archiveParentValidation.handle) {
|
|
559
|
+
await archiveParentValidation.handle.close().catch(() => {});
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
const archived = await inspectWorkspacePath(workspaceRoot, archiveRelativePath, {
|
|
563
|
+
leafType: 'any',
|
|
564
|
+
allowMissing: false,
|
|
565
|
+
});
|
|
566
|
+
if (
|
|
567
|
+
archived.stat.dev !== source.stat.dev ||
|
|
568
|
+
archived.stat.ino !== source.stat.ino ||
|
|
569
|
+
archived.stat.isDirectory() !== source.stat.isDirectory() ||
|
|
570
|
+
archived.stat.isFile() !== source.stat.isFile()
|
|
571
|
+
) {
|
|
572
|
+
throw new Error('Unsafe Gofer archive path: the archived path failed identity validation.');
|
|
573
|
+
}
|
|
217
574
|
}
|
|
218
575
|
|
|
219
576
|
return archiveRelativePath;
|
|
220
577
|
}
|
|
221
578
|
|
|
222
|
-
|
|
579
|
+
function buildVisibleSurfacePathsForStem(stem) {
|
|
580
|
+
return [
|
|
581
|
+
path.join('.claude', 'commands', `${stem}.md`),
|
|
582
|
+
path.join('.github', 'prompts', `${stem}.prompt.md`),
|
|
583
|
+
path.join('.agents', 'skills', stem),
|
|
584
|
+
path.join('.system', 'skills', stem),
|
|
585
|
+
path.join('.gemini', 'commands', 'gofer', `${stem}.md`),
|
|
586
|
+
path.join('.gemini', 'commands', 'gofer', `${stem}.toml`),
|
|
587
|
+
];
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function buildStaleVisibleManagedPaths(stages) {
|
|
591
|
+
const staleStems = new Set(RETIRED_PUBLIC_ENTRYPOINT_STEMS);
|
|
592
|
+
for (const stage of stages) {
|
|
593
|
+
staleStems.add(String(stage.stem));
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
return Array.from(staleStems).flatMap((stem) => buildVisibleSurfacePathsForStem(stem));
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
async function removeLegacyManagedPaths(workspaceRoot, dryRun, stages = []) {
|
|
223
600
|
const archived = [];
|
|
224
601
|
const archiveStamp = buildArchiveStamp();
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
602
|
+
const legacyPaths = Array.from(
|
|
603
|
+
new Set([...LEGACY_MANAGED_PATHS, ...buildStaleVisibleManagedPaths(stages)])
|
|
604
|
+
);
|
|
605
|
+
|
|
606
|
+
for (const relativePath of legacyPaths) {
|
|
607
|
+
const target = await inspectWorkspacePath(workspaceRoot, relativePath, {
|
|
608
|
+
leafType: 'any',
|
|
609
|
+
allowMissing: true,
|
|
610
|
+
});
|
|
611
|
+
if (!target.exists) {
|
|
228
612
|
continue;
|
|
229
613
|
}
|
|
230
614
|
|
|
@@ -244,9 +628,13 @@ async function resolveSourcePath(sourceRoot, relativePath) {
|
|
|
244
628
|
|
|
245
629
|
const resourceRelativePath = EXTENSION_RESOURCE_PATHS.get(path.normalize(relativePath));
|
|
246
630
|
if (resourceRelativePath) {
|
|
247
|
-
const resourcePath
|
|
248
|
-
|
|
249
|
-
|
|
631
|
+
for (const resourcePath of [
|
|
632
|
+
path.join(sourceRoot, resourceRelativePath),
|
|
633
|
+
path.join(sourceRoot, 'extension', resourceRelativePath),
|
|
634
|
+
]) {
|
|
635
|
+
if (await pathExists(resourcePath)) {
|
|
636
|
+
return resourcePath;
|
|
637
|
+
}
|
|
250
638
|
}
|
|
251
639
|
}
|
|
252
640
|
|
|
@@ -546,25 +934,56 @@ function buildEaiRepoContractSection(projectInfo) {
|
|
|
546
934
|
if (!projectInfo.eaiInitialized) {
|
|
547
935
|
return `## EAI Platform Readiness
|
|
548
936
|
|
|
549
|
-
-
|
|
550
|
-
-
|
|
551
|
-
-
|
|
937
|
+
- Classify the request before EAI readiness. If it is EAI app delivery or ambiguous, continue directly to EAI readiness. If it is clearly non-app work, confirm once before skipping EAI workspace/app setup.
|
|
938
|
+
- This repo is not confirmed as EAI-initialized yet. Run \`eai whoami\` only for EAI app delivery work or explicit EAI CLI recovery.
|
|
939
|
+
- If \`eai\` is missing, login fails, the token is expired, or no active workspace is visible during app delivery, use the public \`eai\` entrypoint and the internal \`.specify/commands/gofer_eai_first_run.md\` setup contract before building.
|
|
940
|
+
- Do not invent, guess, or complete EAI CLI commands from memory. Verify exact \`eai ...\` syntax and flags with \`eai --describe\` and command-specific \`--help\` before suggesting or running them.
|
|
941
|
+
- Build on EAI Platform first and Azure second for app delivery. Treat non-EAI runtimes as explicit exceptions only.
|
|
552
942
|
- Do not write tokens, secrets, private workspace IDs, Entra/CIAM authority tenant IDs, or local \`.env\` values into Gofer artifacts.`;
|
|
553
943
|
}
|
|
554
944
|
|
|
555
945
|
return `## EAI Repo Contract
|
|
556
946
|
|
|
557
947
|
- This repo appears to be initialized from the EAI app template. Before app-delivery work, read \`.specify/references/platform/eai-repo-contract.md\` and \`.specify/references/platform/eai-error-catalog.yaml\`.
|
|
558
|
-
-
|
|
559
|
-
- If
|
|
948
|
+
- Classify the request before EAI readiness. If it is EAI app delivery or ambiguous, continue directly to EAI readiness. If it is clearly non-app work, confirm once before skipping EAI workspace/app setup.
|
|
949
|
+
- Run \`eai whoami\` only for EAI app delivery work or explicit EAI CLI recovery. If \`eai\` is missing, login fails, the token is expired, or no active workspace is visible during app delivery, use the public \`eai\` entrypoint and the internal \`.specify/commands/gofer_eai_first_run.md\` setup contract before building.
|
|
950
|
+
- If CLI, login, workspace, template, or Gofer readiness is missing or stale during app delivery, use the public \`eai\` entrypoint and the internal first-run setup contract before building.
|
|
560
951
|
- Use \`eai update --check\`, \`eai --describe\`, \`eai agent guide --format json\`, \`eai template check --format json\`, \`eai gofer refresh --check --format json\`, and \`eai workflow readiness --format json\` when the CLI advertises them before assuming the repo is current.
|
|
952
|
+
- Do not invent, guess, or complete EAI CLI commands from memory. Verify exact \`eai ...\` syntax and flags with \`eai --describe\` and command-specific \`--help\` before suggesting or running them. If the installed CLI does not list a command, do not run it.
|
|
561
953
|
- After any \`eai\` command error, use \`eai errors explain <code-or-reason> --format json\` before guessing remediation.
|
|
562
954
|
- If \`eai errors explain\` is unavailable, match \`.specify/references/platform/eai-error-catalog.yaml\`, run read-only diagnostics before mutating fixes, and stop at the retry or escalation condition.
|
|
563
|
-
- For \`eai user invite\` 5xx or \`EXTERNAL_SERVICE_ERROR\`, check existing members with \`eai user list --workspace <workspace-id> --search <email> --format json\`; use \`eai user role set --workspace <workspace-id> --member-id <member-id> --role tenant-admin --format json\` only after verification and user approval
|
|
955
|
+
- For \`eai user invite\` 5xx or \`EXTERNAL_SERVICE_ERROR\`, check existing members with \`eai user list --workspace <workspace-id> --search <email> --format json\`; use \`eai user role set --workspace <workspace-id> --member-id <member-id> --role tenant-admin --format json\` only after verification and user approval. The platform role ID \`tenant-admin\` means workspace admin access. Then tell the app user to sign out and sign back in.
|
|
956
|
+
- For \`MISSING_TENANT\`, \`app_token_tenant_context_required\`, or "Tenant context required for app tokens" on platform user lookup or membership prerequisites, run \`eai errors explain app_token_tenant_context_required --format json\`, confirm workspace context, and retry \`/v4/platform/tenants/<tenant-id>/...\` routes before changing workspace members, Entra, role definitions, databases, or cloud portals.
|
|
564
957
|
- Build on EAI Platform first and Azure second. Treat non-EAI runtimes as explicit exceptions only.
|
|
565
958
|
- Keep provisioning, types seed, schema/storage health, workflow readiness, and preview as separate gates.`;
|
|
566
959
|
}
|
|
567
960
|
|
|
961
|
+
function buildUserFacingResponseGateSection() {
|
|
962
|
+
return `## User-Facing Response Gate
|
|
963
|
+
|
|
964
|
+
Before each user-facing reply, check the draft against these rules:
|
|
965
|
+
|
|
966
|
+
1. Lead with the business outcome, effect, risk, or decision.
|
|
967
|
+
2. Use concise, simple language.
|
|
968
|
+
3. Include technical detail only when it supports a decision or the user asks for it.
|
|
969
|
+
4. If any check fails, rewrite the reply before sending it.`;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
function buildAlwaysOnEaiSection() {
|
|
973
|
+
return `## Always-On EAI Contract
|
|
974
|
+
<!-- gofer:always-on-eai:start -->
|
|
975
|
+
|
|
976
|
+
Apply this contract to every request after Gofer is installed for this repo or AI coding app. The user does not need to type \`/eai\` or \`$eai\`.
|
|
977
|
+
|
|
978
|
+
1. Preserve the user's request. Do not rewrite it or add a visible command prefix.
|
|
979
|
+
2. Treat an explicit \`/eai\` in Claude, Copilot, Antigravity, Grok, or VS Code, and \`$eai\` in Codex, as an idempotent request for the same contract.
|
|
980
|
+
3. Apply Gofer's Controlled English and business-first response rules.
|
|
981
|
+
4. Select the internal pipeline stage. Do not make the user select a stage.
|
|
982
|
+
5. Check workspace health before meaningful repo work, tool use, or a pipeline stage. Do not repeat setup on every message.
|
|
983
|
+
6. When the user explicitly asks to update Gofer, use its maintenance contract only.
|
|
984
|
+
<!-- gofer:always-on-eai:end -->`;
|
|
985
|
+
}
|
|
986
|
+
|
|
568
987
|
export function buildAgentsMd(projectInfo, stages) {
|
|
569
988
|
const corePipelineOrder = [
|
|
570
989
|
'0_gofer_start',
|
|
@@ -612,6 +1031,10 @@ export function buildAgentsMd(projectInfo, stages) {
|
|
|
612
1031
|
|
|
613
1032
|
**Project**: ${projectInfo.name} | **Language**: ${formatLanguage(projectInfo.language)}${frameworkLine} | **Package Manager**: ${projectInfo.packageManager || 'Not detected'}
|
|
614
1033
|
|
|
1034
|
+
${buildUserFacingResponseGateSection()}
|
|
1035
|
+
|
|
1036
|
+
${buildAlwaysOnEaiSection()}
|
|
1037
|
+
|
|
615
1038
|
## Core Pipeline Stages
|
|
616
1039
|
|
|
617
1040
|
${pipelineSections}
|
|
@@ -642,7 +1065,7 @@ ${buildCodeStyleSection(projectInfo)}
|
|
|
642
1065
|
|
|
643
1066
|
## Gofer Pipeline
|
|
644
1067
|
|
|
645
|
-
This project uses Gofer for spec-driven development. Run \`/
|
|
1068
|
+
This project uses Gofer for spec-driven development. Run \`/eai\` in Claude, Copilot, Antigravity, Grok, or VS Code, and \`$eai\` in Codex, to start or continue the core pipeline (Gofer Start -> research -> specify -> plan -> tasks -> implement -> validate). Gofer routes internally through \`.specify/commands/*.md\` contracts; validation is the terminal quality gate and includes the final engineering review loop. Before EAI readiness, classify the request: app delivery continues directly, while clear non-app work asks once before skipping EAI workspace/app setup. Artifacts in \`.specify/specs/{feature}/\`.
|
|
646
1069
|
|
|
647
1070
|
Each feature should carry a bounded loop contract:
|
|
648
1071
|
|
|
@@ -673,6 +1096,8 @@ export function buildClaudeMd(projectInfo) {
|
|
|
673
1096
|
|
|
674
1097
|
See @AGENTS.md for project conventions, commands, and code style.
|
|
675
1098
|
|
|
1099
|
+
${buildAlwaysOnEaiSection()}
|
|
1100
|
+
|
|
676
1101
|
## Workflow Orchestration
|
|
677
1102
|
|
|
678
1103
|
### 1. Plan Node Default
|
|
@@ -704,7 +1129,7 @@ See @AGENTS.md for project conventions, commands, and code style.
|
|
|
704
1129
|
|
|
705
1130
|
## Gofer Pipeline
|
|
706
1131
|
|
|
707
|
-
Run \`/
|
|
1132
|
+
Run \`/eai\` in Claude, Copilot, Antigravity, Grok, or VS Code, and \`$eai\` in Codex, to start or continue the core pipeline: Gofer Start -> research -> specify -> plan -> tasks -> implement -> validate. Gofer routes internally through \`.specify/commands/*.md\` contracts; validation is the terminal quality gate and includes the final engineering review loop. Before EAI readiness, classify the request: app delivery continues directly, while clear non-app work asks once before skipping EAI workspace/app setup. Artifacts go to \`.specify/specs/{feature}/\`.
|
|
708
1133
|
|
|
709
1134
|
For each active feature, keep \`loop-contract.json\`, \`loop-ledger.jsonl\`, and
|
|
710
1135
|
\`loop-audit-report.md\` in the feature directory. The loop contract bounds
|
|
@@ -725,11 +1150,13 @@ export function buildCopilotInstructions(projectInfo) {
|
|
|
725
1150
|
|
|
726
1151
|
**${projectInfo.name}** is a ${formatLanguage(projectInfo.language)} project${frameworkBit}.
|
|
727
1152
|
|
|
1153
|
+
${buildAlwaysOnEaiSection()}
|
|
1154
|
+
|
|
728
1155
|
## Gofer Pipeline
|
|
729
1156
|
|
|
730
|
-
This project uses Gofer for spec-driven development. Run \`/
|
|
1157
|
+
This project uses Gofer for spec-driven development. Run \`/eai\` to start or continue the core pipeline: Gofer Start -> research -> specify -> plan -> tasks -> implement -> validate.
|
|
731
1158
|
|
|
732
|
-
|
|
1159
|
+
Gofer routes internally through \`.specify/commands/*.md\` contracts; validation is the terminal quality gate and includes the final engineering review loop. Before EAI readiness, classify the request: app delivery continues directly, while clear non-app work asks once before skipping EAI workspace/app setup. Artifacts in \`.specify/specs/{feature}/\`.
|
|
733
1160
|
|
|
734
1161
|
${eaiSection}
|
|
735
1162
|
|
|
@@ -753,21 +1180,25 @@ ${buildCodeStyleSection(projectInfo)}
|
|
|
753
1180
|
}
|
|
754
1181
|
|
|
755
1182
|
export async function readManagedVersion(workspaceRoot) {
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
}
|
|
763
|
-
throw error;
|
|
1183
|
+
const target = await inspectWorkspacePath(workspaceRoot, GOFER_VERSION_FILE, {
|
|
1184
|
+
leafType: 'file',
|
|
1185
|
+
allowMissing: true,
|
|
1186
|
+
});
|
|
1187
|
+
if (!target.exists) {
|
|
1188
|
+
return null;
|
|
764
1189
|
}
|
|
1190
|
+
const content = await fs.readFile(target.path, 'utf8');
|
|
1191
|
+
return content.split('\n')[0]?.trim() || null;
|
|
765
1192
|
}
|
|
766
1193
|
|
|
767
1194
|
async function collectMissingPaths(workspaceRoot, relativePaths) {
|
|
768
1195
|
const missing = [];
|
|
769
1196
|
for (const relativePath of relativePaths) {
|
|
770
|
-
|
|
1197
|
+
const target = await inspectWorkspacePath(workspaceRoot, relativePath, {
|
|
1198
|
+
leafType: 'any',
|
|
1199
|
+
allowMissing: true,
|
|
1200
|
+
});
|
|
1201
|
+
if (!target.exists) {
|
|
771
1202
|
missing.push(relativePath);
|
|
772
1203
|
}
|
|
773
1204
|
}
|
|
@@ -780,6 +1211,7 @@ export async function checkWorkspaceState({
|
|
|
780
1211
|
sourceRoot,
|
|
781
1212
|
}) {
|
|
782
1213
|
const normalizedHost = normalizeHost(host);
|
|
1214
|
+
await assertSafeWorkspaceRoot(workspaceRoot);
|
|
783
1215
|
const expectedVersion = await detectGoferVersion(sourceRoot);
|
|
784
1216
|
const actualVersion = await readManagedVersion(workspaceRoot);
|
|
785
1217
|
const missingCore = await collectMissingPaths(workspaceRoot, CORE_SENTINELS);
|
|
@@ -822,44 +1254,139 @@ export async function checkWorkspaceState({
|
|
|
822
1254
|
};
|
|
823
1255
|
}
|
|
824
1256
|
|
|
825
|
-
async function ensureDir(
|
|
826
|
-
|
|
827
|
-
|
|
1257
|
+
async function ensureDir(workspaceRoot, relativePath, dryRun) {
|
|
1258
|
+
await ensureSafeWorkspaceDirectory(workspaceRoot, relativePath, dryRun);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
function sourceIdentityMatches(left, right) {
|
|
1262
|
+
return (
|
|
1263
|
+
left.dev === right.dev &&
|
|
1264
|
+
left.ino === right.ino &&
|
|
1265
|
+
left.size === right.size &&
|
|
1266
|
+
left.mtimeMs === right.mtimeMs &&
|
|
1267
|
+
left.isFile() === right.isFile()
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
async function readStableSourceFile(sourcePath) {
|
|
1272
|
+
const before = await fs.lstat(sourcePath);
|
|
1273
|
+
if (before.isSymbolicLink() || !before.isFile()) {
|
|
1274
|
+
throw new Error('Unsafe Gofer source path: copied entries must be regular files.');
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
const handle = await fs.open(sourcePath, 'r');
|
|
1278
|
+
try {
|
|
1279
|
+
const opened = await handle.stat();
|
|
1280
|
+
if (!sourceIdentityMatches(before, opened)) {
|
|
1281
|
+
throw new Error('Unsafe Gofer source path: a copied file changed during validation.');
|
|
1282
|
+
}
|
|
1283
|
+
const content = await handle.readFile();
|
|
1284
|
+
const after = await fs.lstat(sourcePath);
|
|
1285
|
+
if (!sourceIdentityMatches(opened, after)) {
|
|
1286
|
+
throw new Error('Unsafe Gofer source path: a copied file changed during validation.');
|
|
1287
|
+
}
|
|
1288
|
+
return { content, mode: opened.mode & 0o777 };
|
|
1289
|
+
} finally {
|
|
1290
|
+
await handle.close();
|
|
828
1291
|
}
|
|
829
1292
|
}
|
|
830
1293
|
|
|
831
|
-
async function copyDirectory(sourcePath,
|
|
832
|
-
|
|
1294
|
+
async function copyDirectory(sourcePath, workspaceRoot, targetRelativePath, dryRun) {
|
|
1295
|
+
const sourceStat = await lstatIfExists(sourcePath);
|
|
1296
|
+
if (!sourceStat) {
|
|
833
1297
|
return false;
|
|
834
1298
|
}
|
|
835
|
-
if (
|
|
836
|
-
|
|
1299
|
+
if (sourceStat.isSymbolicLink() || !sourceStat.isDirectory()) {
|
|
1300
|
+
throw new Error('Unsafe Gofer source path: copied roots must be real directories.');
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
await ensureSafeWorkspaceDirectory(workspaceRoot, targetRelativePath, dryRun);
|
|
1304
|
+
const entries = await fs.readdir(sourcePath, { withFileTypes: true });
|
|
1305
|
+
for (const entry of entries) {
|
|
1306
|
+
const sourceEntryPath = path.join(sourcePath, entry.name);
|
|
1307
|
+
const targetEntryRelativePath = path.join(targetRelativePath, entry.name);
|
|
1308
|
+
const entryStat = await fs.lstat(sourceEntryPath);
|
|
1309
|
+
if (entryStat.isSymbolicLink()) {
|
|
1310
|
+
throw new Error('Unsafe Gofer source path: symbolic links are not copied.');
|
|
1311
|
+
}
|
|
1312
|
+
if (entryStat.isDirectory()) {
|
|
1313
|
+
await copyDirectory(sourceEntryPath, workspaceRoot, targetEntryRelativePath, dryRun);
|
|
1314
|
+
} else if (entryStat.isFile()) {
|
|
1315
|
+
const sourceFile = await readStableSourceFile(sourceEntryPath);
|
|
1316
|
+
await writeWorkspaceFile(
|
|
1317
|
+
workspaceRoot,
|
|
1318
|
+
targetEntryRelativePath,
|
|
1319
|
+
sourceFile.content,
|
|
1320
|
+
dryRun,
|
|
1321
|
+
sourceFile.mode
|
|
1322
|
+
);
|
|
1323
|
+
} else {
|
|
1324
|
+
throw new Error(
|
|
1325
|
+
'Unsafe Gofer source path: only regular files and directories may be copied.'
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
837
1328
|
}
|
|
838
|
-
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
839
|
-
await fs.cp(sourcePath, targetPath, { recursive: true, force: true, dereference: false });
|
|
840
1329
|
return true;
|
|
841
1330
|
}
|
|
842
1331
|
|
|
843
|
-
async function writeFileIfMissing(
|
|
844
|
-
|
|
1332
|
+
async function writeFileIfMissing(workspaceRoot, relativePath, content, dryRun) {
|
|
1333
|
+
const target = await inspectWorkspacePath(workspaceRoot, relativePath, {
|
|
1334
|
+
leafType: 'file',
|
|
1335
|
+
allowMissing: true,
|
|
1336
|
+
});
|
|
1337
|
+
if (target.exists) {
|
|
845
1338
|
return false;
|
|
846
1339
|
}
|
|
847
|
-
|
|
848
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
849
|
-
await fs.writeFile(filePath, content, 'utf8');
|
|
850
|
-
}
|
|
1340
|
+
await writeWorkspaceFile(workspaceRoot, relativePath, content, dryRun);
|
|
851
1341
|
return true;
|
|
852
1342
|
}
|
|
853
1343
|
|
|
854
|
-
async function
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1344
|
+
async function ensureAlwaysOnEaiSection(workspaceRoot, relativePath, fallbackContent, dryRun) {
|
|
1345
|
+
let existing = '';
|
|
1346
|
+
const target = await inspectWorkspacePath(workspaceRoot, relativePath, {
|
|
1347
|
+
leafType: 'file',
|
|
1348
|
+
allowMissing: true,
|
|
1349
|
+
});
|
|
1350
|
+
if (target.exists) {
|
|
1351
|
+
existing = await fs.readFile(target.path, 'utf8');
|
|
858
1352
|
}
|
|
1353
|
+
|
|
1354
|
+
const section = buildAlwaysOnEaiSection();
|
|
1355
|
+
const markerPattern = /## Always-On EAI Contract\r?\n<!-- gofer:always-on-eai:start -->[\s\S]*?<!-- gofer:always-on-eai:end -->/;
|
|
1356
|
+
const legacySectionPattern = /## Always-On EAI Contract\r?\n[\s\S]*?(?=^##\s|(?![\s\S]))/m;
|
|
1357
|
+
const updated = existing
|
|
1358
|
+
? markerPattern.test(existing)
|
|
1359
|
+
? existing.replace(markerPattern, section)
|
|
1360
|
+
: legacySectionPattern.test(existing)
|
|
1361
|
+
? existing.replace(legacySectionPattern, `${section}\n`)
|
|
1362
|
+
: `${existing.trimEnd()}\n\n${section}\n`
|
|
1363
|
+
: fallbackContent;
|
|
1364
|
+
|
|
1365
|
+
if (updated === existing) {
|
|
1366
|
+
return false;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
await writeTextFile(workspaceRoot, relativePath, updated, dryRun);
|
|
1370
|
+
return true;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
function buildGeminiMd(projectInfo) {
|
|
1374
|
+
return `# GEMINI.md
|
|
1375
|
+
|
|
1376
|
+
See @AGENTS.md for project conventions, commands, and code style.
|
|
1377
|
+
|
|
1378
|
+
${buildAlwaysOnEaiSection()}
|
|
1379
|
+
|
|
1380
|
+
Use the internal Gofer pipeline to select the next stage. Keep the user-facing
|
|
1381
|
+
conversation in business language.\n`;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
async function writeTextFile(workspaceRoot, relativePath, content, dryRun) {
|
|
1385
|
+
await writeWorkspaceFile(workspaceRoot, relativePath, content, dryRun);
|
|
859
1386
|
}
|
|
860
1387
|
|
|
861
1388
|
async function writeModelPolicyIfMissing(workspaceRoot, sourceRoot, dryRun) {
|
|
862
|
-
const
|
|
1389
|
+
const targetRelativePath = path.join('.specify', 'memory', 'gofer-model-policy.yaml');
|
|
863
1390
|
|
|
864
1391
|
let template = '';
|
|
865
1392
|
try {
|
|
@@ -874,18 +1401,18 @@ async function writeModelPolicyIfMissing(workspaceRoot, sourceRoot, dryRun) {
|
|
|
874
1401
|
throw error;
|
|
875
1402
|
}
|
|
876
1403
|
|
|
877
|
-
return writeFileIfMissing(
|
|
1404
|
+
return writeFileIfMissing(workspaceRoot, targetRelativePath, template, dryRun);
|
|
878
1405
|
}
|
|
879
1406
|
|
|
880
1407
|
async function mergeGitignore(workspaceRoot, dryRun) {
|
|
881
|
-
const
|
|
1408
|
+
const gitignoreRelativePath = '.gitignore';
|
|
882
1409
|
let existing = '';
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
1410
|
+
const gitignoreTarget = await inspectWorkspacePath(workspaceRoot, gitignoreRelativePath, {
|
|
1411
|
+
leafType: 'file',
|
|
1412
|
+
allowMissing: true,
|
|
1413
|
+
});
|
|
1414
|
+
if (gitignoreTarget.exists) {
|
|
1415
|
+
existing = await fs.readFile(gitignoreTarget.path, 'utf8');
|
|
889
1416
|
}
|
|
890
1417
|
|
|
891
1418
|
const missingEntries = GOFER_GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
|
|
@@ -904,7 +1431,7 @@ async function mergeGitignore(workspaceRoot, dryRun) {
|
|
|
904
1431
|
updated += `${entry}\n`;
|
|
905
1432
|
}
|
|
906
1433
|
|
|
907
|
-
await writeTextFile(
|
|
1434
|
+
await writeTextFile(workspaceRoot, gitignoreRelativePath, updated, dryRun);
|
|
908
1435
|
return true;
|
|
909
1436
|
}
|
|
910
1437
|
|
|
@@ -924,12 +1451,14 @@ This folder contains all project specifications for AI-driven feature developmen
|
|
|
924
1451
|
|
|
925
1452
|
## Quick Start
|
|
926
1453
|
|
|
927
|
-
Run the unified Gofer pipeline with:
|
|
1454
|
+
Run the unified Gofer pipeline with one public command:
|
|
928
1455
|
|
|
929
1456
|
\`\`\`
|
|
930
|
-
/
|
|
1457
|
+
/eai Add user authentication with OAuth2 and JWT
|
|
931
1458
|
\`\`\`
|
|
932
1459
|
|
|
1460
|
+
Use \`/eai\` in Claude, Copilot, Antigravity, Grok, or VS Code, and \`$eai\` in Codex. Gofer routes internally through \`.specify/commands/*.md\` contracts.
|
|
1461
|
+
|
|
933
1462
|
Artifacts are stored in \`.specify/specs/{feature}/\`.
|
|
934
1463
|
|
|
935
1464
|
Each feature should include a bounded loop contract:
|
|
@@ -957,8 +1486,9 @@ Each feature should also keep a running stakeholder review pack:
|
|
|
957
1486
|
|
|
958
1487
|
## Model Policy
|
|
959
1488
|
|
|
960
|
-
Edit \`.specify/memory/gofer-model-policy.yaml\` to
|
|
961
|
-
and arbiter
|
|
1489
|
+
Edit \`.specify/memory/gofer-model-policy.yaml\` to set advisory simple, medium, hard,
|
|
1490
|
+
and arbiter capability constraints for Claude, Codex/OpenAI, Google Antigravity,
|
|
1491
|
+
Copilot, Grok, and VS Code. Fresh signed capability receipts select models. The
|
|
962
1492
|
file is copied from \`.specify/templates/gofer-model-policy.yaml\` when missing
|
|
963
1493
|
and is not overwritten by bootstrap.
|
|
964
1494
|
`;
|
|
@@ -989,19 +1519,26 @@ function getMirrorCopyCandidates() {
|
|
|
989
1519
|
target: path.join('.github', 'skills'),
|
|
990
1520
|
},
|
|
991
1521
|
{ sourceRelativePath: '.gemini', target: '.gemini' },
|
|
1522
|
+
{ sourceRelativePath: path.join('.grok', 'skills'), target: path.join('.grok', 'skills') },
|
|
992
1523
|
{ sourceRelativePath: path.join('.agents', 'skills'), target: path.join('.agents', 'skills') },
|
|
993
1524
|
{ sourceRelativePath: path.join('.system', 'skills'), target: path.join('.system', 'skills') },
|
|
994
1525
|
];
|
|
995
1526
|
}
|
|
996
1527
|
|
|
997
1528
|
async function installClaudeHooksSettings(workspaceRoot, dryRun) {
|
|
998
|
-
const
|
|
1529
|
+
const settingsRelativePath = path.join('.claude', 'settings.json');
|
|
999
1530
|
let settings = {};
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1531
|
+
const settingsTarget = await inspectWorkspacePath(workspaceRoot, settingsRelativePath, {
|
|
1532
|
+
leafType: 'file',
|
|
1533
|
+
allowMissing: true,
|
|
1534
|
+
});
|
|
1535
|
+
if (settingsTarget.exists) {
|
|
1536
|
+
try {
|
|
1537
|
+
settings = JSON.parse(await fs.readFile(settingsTarget.path, 'utf8'));
|
|
1538
|
+
} catch (error) {
|
|
1539
|
+
if (!(error instanceof SyntaxError)) {
|
|
1540
|
+
throw error;
|
|
1541
|
+
}
|
|
1005
1542
|
}
|
|
1006
1543
|
}
|
|
1007
1544
|
|
|
@@ -1010,7 +1547,12 @@ async function installClaudeHooksSettings(workspaceRoot, dryRun) {
|
|
|
1010
1547
|
...CLAUDE_HOOKS_CONFIG,
|
|
1011
1548
|
};
|
|
1012
1549
|
|
|
1013
|
-
await writeTextFile(
|
|
1550
|
+
await writeTextFile(
|
|
1551
|
+
workspaceRoot,
|
|
1552
|
+
settingsRelativePath,
|
|
1553
|
+
`${JSON.stringify(settings, null, 2)}\n`,
|
|
1554
|
+
dryRun
|
|
1555
|
+
);
|
|
1014
1556
|
}
|
|
1015
1557
|
|
|
1016
1558
|
export async function bootstrapWorkspace({
|
|
@@ -1021,6 +1563,7 @@ export async function bootstrapWorkspace({
|
|
|
1021
1563
|
includeMirrors = false,
|
|
1022
1564
|
}) {
|
|
1023
1565
|
const normalizedHost = normalizeHost(host);
|
|
1566
|
+
await assertSafeWorkspaceRoot(workspaceRoot);
|
|
1024
1567
|
const projectInfo = await detectProjectInfo(workspaceRoot);
|
|
1025
1568
|
const stages = await loadStageMetadata(sourceRoot);
|
|
1026
1569
|
const version = await detectGoferVersion(sourceRoot);
|
|
@@ -1032,13 +1575,15 @@ export async function bootstrapWorkspace({
|
|
|
1032
1575
|
path.join('.specify', 'logs'),
|
|
1033
1576
|
];
|
|
1034
1577
|
for (const relativeDir of coreDirs) {
|
|
1035
|
-
await ensureDir(
|
|
1578
|
+
await ensureDir(workspaceRoot, relativeDir, dryRun);
|
|
1036
1579
|
changed.push(relativeDir);
|
|
1037
1580
|
}
|
|
1038
1581
|
|
|
1039
1582
|
const coreCopies = [
|
|
1040
1583
|
path.join('.specify', 'commands'),
|
|
1584
|
+
path.join('.specify', 'config'),
|
|
1041
1585
|
path.join('.specify', 'references'),
|
|
1586
|
+
path.join('.specify', 'schemas'),
|
|
1042
1587
|
path.join('.specify', 'templates'),
|
|
1043
1588
|
path.join('.specify', 'scripts', 'bash'),
|
|
1044
1589
|
path.join('.specify', 'scripts', 'node'),
|
|
@@ -1048,7 +1593,8 @@ export async function bootstrapWorkspace({
|
|
|
1048
1593
|
for (const relativePath of coreCopies) {
|
|
1049
1594
|
const copied = await copyDirectory(
|
|
1050
1595
|
await resolveSourcePath(sourceRoot, relativePath),
|
|
1051
|
-
|
|
1596
|
+
workspaceRoot,
|
|
1597
|
+
relativePath,
|
|
1052
1598
|
dryRun
|
|
1053
1599
|
);
|
|
1054
1600
|
if (copied) {
|
|
@@ -1060,52 +1606,75 @@ export async function bootstrapWorkspace({
|
|
|
1060
1606
|
changed.push(path.join('.specify', 'memory', 'gofer-model-policy.yaml'));
|
|
1061
1607
|
}
|
|
1062
1608
|
|
|
1063
|
-
await writeTextFile(
|
|
1064
|
-
path.join(workspaceRoot, GOFER_VERSION_FILE),
|
|
1065
|
-
`${version}\n`,
|
|
1066
|
-
dryRun
|
|
1067
|
-
);
|
|
1609
|
+
await writeTextFile(workspaceRoot, GOFER_VERSION_FILE, `${version}\n`, dryRun);
|
|
1068
1610
|
changed.push(GOFER_VERSION_FILE);
|
|
1069
1611
|
|
|
1070
|
-
if (
|
|
1612
|
+
if (
|
|
1613
|
+
await writeFileIfMissing(
|
|
1614
|
+
workspaceRoot,
|
|
1615
|
+
path.join('.specify', 'README.md'),
|
|
1616
|
+
buildSpecifyReadme(),
|
|
1617
|
+
dryRun
|
|
1618
|
+
)
|
|
1619
|
+
) {
|
|
1071
1620
|
changed.push(path.join('.specify', 'README.md'));
|
|
1072
1621
|
}
|
|
1073
1622
|
|
|
1074
|
-
if (
|
|
1623
|
+
if (
|
|
1624
|
+
await ensureAlwaysOnEaiSection(
|
|
1625
|
+
workspaceRoot,
|
|
1626
|
+
'AGENTS.md',
|
|
1627
|
+
buildAgentsMd(projectInfo, stages),
|
|
1628
|
+
dryRun
|
|
1629
|
+
)
|
|
1630
|
+
) {
|
|
1075
1631
|
changed.push('AGENTS.md');
|
|
1076
1632
|
}
|
|
1077
1633
|
|
|
1634
|
+
if (
|
|
1635
|
+
await ensureAlwaysOnEaiSection(
|
|
1636
|
+
workspaceRoot,
|
|
1637
|
+
'CLAUDE.md',
|
|
1638
|
+
buildClaudeMd(projectInfo),
|
|
1639
|
+
dryRun
|
|
1640
|
+
)
|
|
1641
|
+
) {
|
|
1642
|
+
changed.push('CLAUDE.md');
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
if (
|
|
1646
|
+
await ensureAlwaysOnEaiSection(
|
|
1647
|
+
workspaceRoot,
|
|
1648
|
+
path.join('.github', 'copilot-instructions.md'),
|
|
1649
|
+
buildCopilotInstructions(projectInfo),
|
|
1650
|
+
dryRun
|
|
1651
|
+
)
|
|
1652
|
+
) {
|
|
1653
|
+
changed.push(path.join('.github', 'copilot-instructions.md'));
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
if (
|
|
1657
|
+
await ensureAlwaysOnEaiSection(
|
|
1658
|
+
workspaceRoot,
|
|
1659
|
+
'GEMINI.md',
|
|
1660
|
+
buildGeminiMd(projectInfo),
|
|
1661
|
+
dryRun
|
|
1662
|
+
)
|
|
1663
|
+
) {
|
|
1664
|
+
changed.push('GEMINI.md');
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1078
1667
|
if (normalizedHost === 'claude') {
|
|
1079
|
-
if (
|
|
1080
|
-
await writeFileIfMissing(
|
|
1081
|
-
path.join(workspaceRoot, 'CLAUDE.md'),
|
|
1082
|
-
buildClaudeMd(projectInfo),
|
|
1083
|
-
dryRun
|
|
1084
|
-
)
|
|
1085
|
-
) {
|
|
1086
|
-
changed.push('CLAUDE.md');
|
|
1087
|
-
}
|
|
1088
1668
|
await installClaudeHooksSettings(workspaceRoot, dryRun);
|
|
1089
1669
|
changed.push(path.join('.claude', 'settings.json'));
|
|
1090
1670
|
}
|
|
1091
1671
|
|
|
1092
|
-
if (normalizedHost === 'copilot') {
|
|
1093
|
-
if (
|
|
1094
|
-
await writeFileIfMissing(
|
|
1095
|
-
path.join(workspaceRoot, '.github', 'copilot-instructions.md'),
|
|
1096
|
-
buildCopilotInstructions(projectInfo),
|
|
1097
|
-
dryRun
|
|
1098
|
-
)
|
|
1099
|
-
) {
|
|
1100
|
-
changed.push(path.join('.github', 'copilot-instructions.md'));
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
1672
|
if (includeMirrors) {
|
|
1105
1673
|
for (const candidate of getMirrorCopyCandidates(sourceRoot)) {
|
|
1106
1674
|
const copied = await copyDirectory(
|
|
1107
1675
|
await resolveSourcePath(sourceRoot, candidate.sourceRelativePath),
|
|
1108
|
-
|
|
1676
|
+
workspaceRoot,
|
|
1677
|
+
candidate.target,
|
|
1109
1678
|
dryRun
|
|
1110
1679
|
);
|
|
1111
1680
|
if (copied) {
|
|
@@ -1114,7 +1683,7 @@ export async function bootstrapWorkspace({
|
|
|
1114
1683
|
}
|
|
1115
1684
|
}
|
|
1116
1685
|
|
|
1117
|
-
const archivedLegacyPaths = await removeLegacyManagedPaths(workspaceRoot, dryRun);
|
|
1686
|
+
const archivedLegacyPaths = await removeLegacyManagedPaths(workspaceRoot, dryRun, stages);
|
|
1118
1687
|
changed.push(
|
|
1119
1688
|
...archivedLegacyPaths.map(
|
|
1120
1689
|
({ relativePath, archivePath }) => `${relativePath} (archived legacy to ${archivePath})`
|