@specforge/cli 0.1.0
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/README.md +781 -0
- package/bin/specforge +21 -0
- package/dist/channel.d.ts +27 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +22 -0
- package/dist/channel.js.map +1 -0
- package/dist/cli/commands/configure.d.ts +16 -0
- package/dist/cli/commands/configure.d.ts.map +1 -0
- package/dist/cli/commands/configure.js +186 -0
- package/dist/cli/commands/configure.js.map +1 -0
- package/dist/cli/commands/configure.types.d.ts +110 -0
- package/dist/cli/commands/configure.types.d.ts.map +1 -0
- package/dist/cli/commands/configure.types.js +82 -0
- package/dist/cli/commands/configure.types.js.map +1 -0
- package/dist/cli/commands/debug/call.d.ts +14 -0
- package/dist/cli/commands/debug/call.d.ts.map +1 -0
- package/dist/cli/commands/debug/call.js +96 -0
- package/dist/cli/commands/debug/call.js.map +1 -0
- package/dist/cli/commands/debug/index.d.ts +12 -0
- package/dist/cli/commands/debug/index.d.ts.map +1 -0
- package/dist/cli/commands/debug/index.js +14 -0
- package/dist/cli/commands/debug/index.js.map +1 -0
- package/dist/cli/commands/debug/register.d.ts +11 -0
- package/dist/cli/commands/debug/register.d.ts.map +1 -0
- package/dist/cli/commands/debug/register.js +78 -0
- package/dist/cli/commands/debug/register.js.map +1 -0
- package/dist/cli/commands/debug/test.d.ts +13 -0
- package/dist/cli/commands/debug/test.d.ts.map +1 -0
- package/dist/cli/commands/debug/test.js +91 -0
- package/dist/cli/commands/debug/test.js.map +1 -0
- package/dist/cli/commands/debug/tools.d.ts +13 -0
- package/dist/cli/commands/debug/tools.d.ts.map +1 -0
- package/dist/cli/commands/debug/tools.js +86 -0
- package/dist/cli/commands/debug/tools.js.map +1 -0
- package/dist/cli/commands/debug/types.d.ts +119 -0
- package/dist/cli/commands/debug/types.d.ts.map +1 -0
- package/dist/cli/commands/debug/types.js +36 -0
- package/dist/cli/commands/debug/types.js.map +1 -0
- package/dist/cli/commands/debug/whoami.d.ts +13 -0
- package/dist/cli/commands/debug/whoami.d.ts.map +1 -0
- package/dist/cli/commands/debug/whoami.js +131 -0
- package/dist/cli/commands/debug/whoami.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +16 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +413 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/doctor.types.d.ts +180 -0
- package/dist/cli/commands/doctor.types.d.ts.map +1 -0
- package/dist/cli/commands/doctor.types.js +140 -0
- package/dist/cli/commands/doctor.types.js.map +1 -0
- package/dist/cli/commands/feedback.d.ts +27 -0
- package/dist/cli/commands/feedback.d.ts.map +1 -0
- package/dist/cli/commands/feedback.js +262 -0
- package/dist/cli/commands/feedback.js.map +1 -0
- package/dist/cli/commands/feedback.types.d.ts +116 -0
- package/dist/cli/commands/feedback.types.d.ts.map +1 -0
- package/dist/cli/commands/feedback.types.js +65 -0
- package/dist/cli/commands/feedback.types.js.map +1 -0
- package/dist/cli/commands/index.d.ts +15 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/index.js +21 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/init.d.ts +17 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +773 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/init.types.d.ts +193 -0
- package/dist/cli/commands/init.types.d.ts.map +1 -0
- package/dist/cli/commands/init.types.js +29 -0
- package/dist/cli/commands/init.types.js.map +1 -0
- package/dist/cli/commands/invitations.d.ts +31 -0
- package/dist/cli/commands/invitations.d.ts.map +1 -0
- package/dist/cli/commands/invitations.js +250 -0
- package/dist/cli/commands/invitations.js.map +1 -0
- package/dist/cli/commands/login.d.ts +40 -0
- package/dist/cli/commands/login.d.ts.map +1 -0
- package/dist/cli/commands/login.js +139 -0
- package/dist/cli/commands/login.js.map +1 -0
- package/dist/cli/commands/members.d.ts +55 -0
- package/dist/cli/commands/members.d.ts.map +1 -0
- package/dist/cli/commands/members.js +296 -0
- package/dist/cli/commands/members.js.map +1 -0
- package/dist/cli/commands/scaffold/agent-types.d.ts +54 -0
- package/dist/cli/commands/scaffold/agent-types.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/agent-types.js +22 -0
- package/dist/cli/commands/scaffold/agent-types.js.map +1 -0
- package/dist/cli/commands/scaffold/display.d.ts +56 -0
- package/dist/cli/commands/scaffold/display.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/display.js +217 -0
- package/dist/cli/commands/scaffold/display.js.map +1 -0
- package/dist/cli/commands/scaffold/files.d.ts +99 -0
- package/dist/cli/commands/scaffold/files.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/files.js +122 -0
- package/dist/cli/commands/scaffold/files.js.map +1 -0
- package/dist/cli/commands/scaffold/index.d.ts +13 -0
- package/dist/cli/commands/scaffold/index.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/index.js +12 -0
- package/dist/cli/commands/scaffold/index.js.map +1 -0
- package/dist/cli/commands/scaffold/register.d.ts +11 -0
- package/dist/cli/commands/scaffold/register.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/register.js +11 -0
- package/dist/cli/commands/scaffold/register.js.map +1 -0
- package/dist/cli/commands/scaffold/scaffold.d.ts +11 -0
- package/dist/cli/commands/scaffold/scaffold.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/scaffold.js +728 -0
- package/dist/cli/commands/scaffold/scaffold.js.map +1 -0
- package/dist/cli/commands/scaffold/targets.d.ts +60 -0
- package/dist/cli/commands/scaffold/targets.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/targets.js +251 -0
- package/dist/cli/commands/scaffold/targets.js.map +1 -0
- package/dist/cli/commands/scaffold/types.d.ts +170 -0
- package/dist/cli/commands/scaffold/types.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/types.js +12 -0
- package/dist/cli/commands/scaffold/types.js.map +1 -0
- package/dist/cli/commands/scaffold/utils.d.ts +89 -0
- package/dist/cli/commands/scaffold/utils.d.ts.map +1 -0
- package/dist/cli/commands/scaffold/utils.js +401 -0
- package/dist/cli/commands/scaffold/utils.js.map +1 -0
- package/dist/cli/commands/status.d.ts +19 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +124 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/status.types.d.ts +37 -0
- package/dist/cli/commands/status.types.d.ts.map +1 -0
- package/dist/cli/commands/status.types.js +7 -0
- package/dist/cli/commands/status.types.js.map +1 -0
- package/dist/cli/commands/switch.d.ts +18 -0
- package/dist/cli/commands/switch.d.ts.map +1 -0
- package/dist/cli/commands/switch.js +134 -0
- package/dist/cli/commands/switch.js.map +1 -0
- package/dist/cli/commands/switch.types.d.ts +123 -0
- package/dist/cli/commands/switch.types.d.ts.map +1 -0
- package/dist/cli/commands/switch.types.js +46 -0
- package/dist/cli/commands/switch.types.js.map +1 -0
- package/dist/cli/config/agent-teams.types.d.ts +120 -0
- package/dist/cli/config/agent-teams.types.d.ts.map +1 -0
- package/dist/cli/config/agent-teams.types.js +23 -0
- package/dist/cli/config/agent-teams.types.js.map +1 -0
- package/dist/cli/config/index.d.ts +13 -0
- package/dist/cli/config/index.d.ts.map +1 -0
- package/dist/cli/config/index.js +7 -0
- package/dist/cli/config/index.js.map +1 -0
- package/dist/cli/config/loader.d.ts +114 -0
- package/dist/cli/config/loader.d.ts.map +1 -0
- package/dist/cli/config/loader.js +198 -0
- package/dist/cli/config/loader.js.map +1 -0
- package/dist/cli/config/paths.d.ts +107 -0
- package/dist/cli/config/paths.d.ts.map +1 -0
- package/dist/cli/config/paths.js +79 -0
- package/dist/cli/config/paths.js.map +1 -0
- package/dist/cli/config/planning-sessions-registry.d.ts +52 -0
- package/dist/cli/config/planning-sessions-registry.d.ts.map +1 -0
- package/dist/cli/config/planning-sessions-registry.js +46 -0
- package/dist/cli/config/planning-sessions-registry.js.map +1 -0
- package/dist/cli/config/types.d.ts +186 -0
- package/dist/cli/config/types.d.ts.map +1 -0
- package/dist/cli/config/types.js +27 -0
- package/dist/cli/config/types.js.map +1 -0
- package/dist/cli/config/validation.d.ts +62 -0
- package/dist/cli/config/validation.d.ts.map +1 -0
- package/dist/cli/config/validation.js +133 -0
- package/dist/cli/config/validation.js.map +1 -0
- package/dist/cli/config/writer.d.ts +153 -0
- package/dist/cli/config/writer.d.ts.map +1 -0
- package/dist/cli/config/writer.js +168 -0
- package/dist/cli/config/writer.js.map +1 -0
- package/dist/cli/index.d.ts +30 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +142 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/middleware/auth-guard.d.ts +107 -0
- package/dist/cli/middleware/auth-guard.d.ts.map +1 -0
- package/dist/cli/middleware/auth-guard.js +97 -0
- package/dist/cli/middleware/auth-guard.js.map +1 -0
- package/dist/cli/middleware/error-handler.d.ts +133 -0
- package/dist/cli/middleware/error-handler.d.ts.map +1 -0
- package/dist/cli/middleware/error-handler.js +139 -0
- package/dist/cli/middleware/error-handler.js.map +1 -0
- package/dist/cli/middleware/index.d.ts +6 -0
- package/dist/cli/middleware/index.d.ts.map +1 -0
- package/dist/cli/middleware/index.js +3 -0
- package/dist/cli/middleware/index.js.map +1 -0
- package/dist/cli/templates/agents/content/core/sfag-orchestrator.d.ts +9 -0
- package/dist/cli/templates/agents/content/core/sfag-orchestrator.d.ts.map +1 -0
- package/dist/cli/templates/agents/content/core/sfag-orchestrator.js +160 -0
- package/dist/cli/templates/agents/content/core/sfag-orchestrator.js.map +1 -0
- package/dist/cli/templates/agents/content/core/sfag-spec-creator.d.ts +9 -0
- package/dist/cli/templates/agents/content/core/sfag-spec-creator.d.ts.map +1 -0
- package/dist/cli/templates/agents/content/core/sfag-spec-creator.js +343 -0
- package/dist/cli/templates/agents/content/core/sfag-spec-creator.js.map +1 -0
- package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.d.ts +9 -0
- package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.d.ts.map +1 -0
- package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.js +254 -0
- package/dist/cli/templates/agents/content/core/sfag-ticket-implementer.js.map +1 -0
- package/dist/cli/templates/agents/content/research/sfag-package-researcher.d.ts +8 -0
- package/dist/cli/templates/agents/content/research/sfag-package-researcher.d.ts.map +1 -0
- package/dist/cli/templates/agents/content/research/sfag-package-researcher.js +128 -0
- package/dist/cli/templates/agents/content/research/sfag-package-researcher.js.map +1 -0
- package/dist/cli/templates/agents/index.d.ts +23 -0
- package/dist/cli/templates/agents/index.d.ts.map +1 -0
- package/dist/cli/templates/agents/index.js +29 -0
- package/dist/cli/templates/agents/index.js.map +1 -0
- package/dist/cli/templates/commands.d.ts +17 -0
- package/dist/cli/templates/commands.d.ts.map +1 -0
- package/dist/cli/templates/commands.js +78 -0
- package/dist/cli/templates/commands.js.map +1 -0
- package/dist/cli/templates/content/sf-blockers.d.ts +7 -0
- package/dist/cli/templates/content/sf-blockers.d.ts.map +1 -0
- package/dist/cli/templates/content/sf-blockers.js +69 -0
- package/dist/cli/templates/content/sf-blockers.js.map +1 -0
- package/dist/cli/templates/content/sf-commit.d.ts +7 -0
- package/dist/cli/templates/content/sf-commit.d.ts.map +1 -0
- package/dist/cli/templates/content/sf-commit.js +79 -0
- package/dist/cli/templates/content/sf-commit.js.map +1 -0
- package/dist/cli/templates/content/sf-context.d.ts +7 -0
- package/dist/cli/templates/content/sf-context.d.ts.map +1 -0
- package/dist/cli/templates/content/sf-context.js +63 -0
- package/dist/cli/templates/content/sf-context.js.map +1 -0
- package/dist/cli/templates/content/sf-help.d.ts +7 -0
- package/dist/cli/templates/content/sf-help.d.ts.map +1 -0
- package/dist/cli/templates/content/sf-help.js +42 -0
- package/dist/cli/templates/content/sf-help.js.map +1 -0
- package/dist/cli/templates/content/sf-init.d.ts +7 -0
- package/dist/cli/templates/content/sf-init.d.ts.map +1 -0
- package/dist/cli/templates/content/sf-init.js +62 -0
- package/dist/cli/templates/content/sf-init.js.map +1 -0
- package/dist/cli/templates/content/sf-reset.d.ts +7 -0
- package/dist/cli/templates/content/sf-reset.d.ts.map +1 -0
- package/dist/cli/templates/content/sf-reset.js +67 -0
- package/dist/cli/templates/content/sf-reset.js.map +1 -0
- package/dist/cli/templates/content/sf-search.d.ts +7 -0
- package/dist/cli/templates/content/sf-search.d.ts.map +1 -0
- package/dist/cli/templates/content/sf-search.js +63 -0
- package/dist/cli/templates/content/sf-search.js.map +1 -0
- package/dist/cli/templates/content/sf-status.d.ts +7 -0
- package/dist/cli/templates/content/sf-status.d.ts.map +1 -0
- package/dist/cli/templates/content/sf-status.js +62 -0
- package/dist/cli/templates/content/sf-status.js.map +1 -0
- package/dist/cli/templates/index.d.ts +8 -0
- package/dist/cli/templates/index.d.ts.map +1 -0
- package/dist/cli/templates/index.js +17 -0
- package/dist/cli/templates/index.js.map +1 -0
- package/dist/cli/ui/banner.d.ts +38 -0
- package/dist/cli/ui/banner.d.ts.map +1 -0
- package/dist/cli/ui/banner.js +89 -0
- package/dist/cli/ui/banner.js.map +1 -0
- package/dist/cli/ui/colors.d.ts +76 -0
- package/dist/cli/ui/colors.d.ts.map +1 -0
- package/dist/cli/ui/colors.js +67 -0
- package/dist/cli/ui/colors.js.map +1 -0
- package/dist/cli/ui/index.d.ts +7 -0
- package/dist/cli/ui/index.d.ts.map +1 -0
- package/dist/cli/ui/index.js +4 -0
- package/dist/cli/ui/index.js.map +1 -0
- package/dist/cli/ui/output.d.ts +117 -0
- package/dist/cli/ui/output.d.ts.map +1 -0
- package/dist/cli/ui/output.js +83 -0
- package/dist/cli/ui/output.js.map +1 -0
- package/dist/cli/ui/spinner.d.ts +120 -0
- package/dist/cli/ui/spinner.d.ts.map +1 -0
- package/dist/cli/ui/spinner.js +158 -0
- package/dist/cli/ui/spinner.js.map +1 -0
- package/dist/cli/ui/time.d.ts +54 -0
- package/dist/cli/ui/time.d.ts.map +1 -0
- package/dist/cli/ui/time.js +119 -0
- package/dist/cli/ui/time.js.map +1 -0
- package/dist/client/api-client.d.ts +123 -0
- package/dist/client/api-client.d.ts.map +1 -0
- package/dist/client/api-client.js +105 -0
- package/dist/client/api-client.js.map +1 -0
- package/dist/client/cli-client.d.ts +26 -0
- package/dist/client/cli-client.d.ts.map +1 -0
- package/dist/client/cli-client.js +20 -0
- package/dist/client/cli-client.js.map +1 -0
- package/dist/config/index.d.ts +78 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +79 -0
- package/dist/config/index.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/format.d.ts +126 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/format.js +71 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/index.d.ts +11 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +45 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/monorepo-detector.d.ts +31 -0
- package/dist/lib/monorepo-detector.d.ts.map +1 -0
- package/dist/lib/monorepo-detector.js +188 -0
- package/dist/lib/monorepo-detector.js.map +1 -0
- package/dist/lib/prompt-generator.d.ts +65 -0
- package/dist/lib/prompt-generator.d.ts.map +1 -0
- package/dist/lib/prompt-generator.js +162 -0
- package/dist/lib/prompt-generator.js.map +1 -0
- package/dist/lib/strategy-analyzer.d.ts +59 -0
- package/dist/lib/strategy-analyzer.d.ts.map +1 -0
- package/dist/lib/strategy-analyzer.js +91 -0
- package/dist/lib/strategy-analyzer.js.map +1 -0
- package/dist/lib/workflow-definitions.d.ts +41 -0
- package/dist/lib/workflow-definitions.d.ts.map +1 -0
- package/dist/lib/workflow-definitions.js +497 -0
- package/dist/lib/workflow-definitions.js.map +1 -0
- package/dist/patterns/index.d.ts +9 -0
- package/dist/patterns/index.d.ts.map +1 -0
- package/dist/patterns/index.js +21 -0
- package/dist/patterns/index.js.map +1 -0
- package/dist/patterns/inheritance.d.ts +193 -0
- package/dist/patterns/inheritance.d.ts.map +1 -0
- package/dist/patterns/inheritance.js +125 -0
- package/dist/patterns/inheritance.js.map +1 -0
- package/dist/server.d.ts +44 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +91 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/core/context-helper.d.ts +50 -0
- package/dist/tools/core/context-helper.d.ts.map +1 -0
- package/dist/tools/core/context-helper.js +49 -0
- package/dist/tools/core/context-helper.js.map +1 -0
- package/dist/tools/core/index.d.ts +2 -0
- package/dist/tools/core/index.d.ts.map +1 -0
- package/dist/tools/core/index.js +2 -0
- package/dist/tools/core/index.js.map +1 -0
- package/dist/tools/index.d.ts +58 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +1499 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/transport/__tests__/resolve-endpoint.test.d.ts +6 -0
- package/dist/transport/__tests__/resolve-endpoint.test.d.ts.map +1 -0
- package/dist/transport/index.d.ts +6 -0
- package/dist/transport/index.d.ts.map +1 -0
- package/dist/transport/index.js +5 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/interface.d.ts +25 -0
- package/dist/transport/interface.d.ts.map +1 -0
- package/dist/transport/interface.js +1 -0
- package/dist/transport/interface.js.map +1 -0
- package/dist/transport/remote.d.ts +38 -0
- package/dist/transport/remote.d.ts.map +1 -0
- package/dist/transport/remote.js +107 -0
- package/dist/transport/remote.js.map +1 -0
- package/dist/transport/resolve-endpoint.d.ts +58 -0
- package/dist/transport/resolve-endpoint.d.ts.map +1 -0
- package/dist/transport/resolve-endpoint.js +146 -0
- package/dist/transport/resolve-endpoint.js.map +1 -0
- package/dist/types/index.d.ts +212 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +16 -0
- package/dist/types/index.js.map +1 -0
- package/dist/validation/index.d.ts +86 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +392 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/ticket-validation.d.ts +162 -0
- package/dist/validation/ticket-validation.d.ts.map +1 -0
- package/dist/validation/ticket-validation.js +188 -0
- package/dist/validation/ticket-validation.js.map +1 -0
- package/package.json +69 -0
- package/src/cli/templates/agents/content/core/sfag-orchestrator.ts +165 -0
- package/src/cli/templates/agents/content/core/sfag-spec-creator.ts +348 -0
- package/src/cli/templates/agents/content/core/sfag-ticket-implementer.ts +259 -0
- package/src/cli/templates/agents/content/research/sfag-package-researcher.ts +132 -0
- package/src/cli/templates/agents/index.ts +47 -0
- package/src/cli/templates/commands.ts +90 -0
- package/src/cli/templates/content/sf-blockers.ts +71 -0
- package/src/cli/templates/content/sf-commit.ts +81 -0
- package/src/cli/templates/content/sf-context.ts +65 -0
- package/src/cli/templates/content/sf-help.ts +44 -0
- package/src/cli/templates/content/sf-init.ts +64 -0
- package/src/cli/templates/content/sf-reset.ts +69 -0
- package/src/cli/templates/content/sf-search.ts +65 -0
- package/src/cli/templates/content/sf-status.ts +64 -0
- package/src/cli/templates/index.ts +16 -0
- package/src/cli/templates/skills/specforge-conventions.md +114 -0
- package/src/cli/templates/skills/specforge-orchestrator.md +409 -0
- package/src/cli/templates/skills/specforge-validator.md +128 -0
- package/src/cli/templates/skills/specforge-worker.md +412 -0
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import {
|
|
7
|
+
resolveConfig,
|
|
8
|
+
loadGlobalConfig,
|
|
9
|
+
tryLoadProjectConfig,
|
|
10
|
+
saveProjectConfig,
|
|
11
|
+
setAgentTeamsConfig,
|
|
12
|
+
addToGitignore,
|
|
13
|
+
getConfigPaths
|
|
14
|
+
} from "../config/index.js";
|
|
15
|
+
import { CONFIG_FILES } from "../config/types.js";
|
|
16
|
+
import { withErrorHandler, CliError, NetworkError, ValidationError } from "../middleware/error-handler.js";
|
|
17
|
+
import {
|
|
18
|
+
printSuccess,
|
|
19
|
+
printWarning,
|
|
20
|
+
printBlank,
|
|
21
|
+
printHeader,
|
|
22
|
+
printField,
|
|
23
|
+
printHint
|
|
24
|
+
} from "../ui/output.js";
|
|
25
|
+
import { colors } from "../ui/colors.js";
|
|
26
|
+
import { printBanner } from "../ui/banner.js";
|
|
27
|
+
import { forgeProgress } from "../ui/spinner.js";
|
|
28
|
+
import { createCliClient } from "../../client/cli-client.js";
|
|
29
|
+
import {
|
|
30
|
+
FORMAT_CHOICES,
|
|
31
|
+
DEFAULT_FORMAT,
|
|
32
|
+
STRATEGY_CHOICES,
|
|
33
|
+
SKILL_TEMPLATE_FILES
|
|
34
|
+
} from "./init.types.js";
|
|
35
|
+
import { TARGETS } from "./scaffold/targets.js";
|
|
36
|
+
import { scaffoldMultiCli } from "./scaffold/files.js";
|
|
37
|
+
import { detectInstalledTools } from "./scaffold/utils.js";
|
|
38
|
+
async function fetchProjects(client) {
|
|
39
|
+
const response = await client.call("list_projects", {});
|
|
40
|
+
return response.projects || [];
|
|
41
|
+
}
|
|
42
|
+
async function fetchSpecifications(client, projectId) {
|
|
43
|
+
const response = await client.call(
|
|
44
|
+
"list_specifications",
|
|
45
|
+
{ projectId }
|
|
46
|
+
);
|
|
47
|
+
return response.items || response.specifications || [];
|
|
48
|
+
}
|
|
49
|
+
function checkExistingFiles() {
|
|
50
|
+
const paths = getConfigPaths();
|
|
51
|
+
const mcpConfig = fs.existsSync(paths.mcpConfig);
|
|
52
|
+
const projectConfig = fs.existsSync(paths.projectConfig);
|
|
53
|
+
return {
|
|
54
|
+
mcpConfig,
|
|
55
|
+
projectConfig,
|
|
56
|
+
anyExists: mcpConfig || projectConfig
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function buildProjectChoices(projects) {
|
|
60
|
+
return projects.map((p) => ({
|
|
61
|
+
name: `${p.name}${p.specCount ? ` (${p.specCount} specs)` : ""}`,
|
|
62
|
+
value: p.id,
|
|
63
|
+
short: p.name
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
function buildSpecificationChoices(specs) {
|
|
67
|
+
const choices = specs.map((s) => {
|
|
68
|
+
const ticketInfo = s.ticketCount ? ` - ${s.completedTicketCount || 0}/${s.ticketCount} tickets` : "";
|
|
69
|
+
return {
|
|
70
|
+
name: `${s.title} [${s.status}]${ticketInfo}`,
|
|
71
|
+
value: s.id,
|
|
72
|
+
short: s.title
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
choices.push({
|
|
76
|
+
name: colors.muted("(skip - select later)"),
|
|
77
|
+
value: void 0
|
|
78
|
+
});
|
|
79
|
+
return choices;
|
|
80
|
+
}
|
|
81
|
+
async function promptForProject(projects, defaultId) {
|
|
82
|
+
const choices = buildProjectChoices(projects);
|
|
83
|
+
const { projectId } = await inquirer.prompt([
|
|
84
|
+
{
|
|
85
|
+
type: "list",
|
|
86
|
+
name: "projectId",
|
|
87
|
+
message: "Select project for this directory:",
|
|
88
|
+
choices,
|
|
89
|
+
pageSize: 10,
|
|
90
|
+
// Resume-aware (M9.8): pre-select the currently-configured project.
|
|
91
|
+
...defaultId ? { default: defaultId } : {}
|
|
92
|
+
}
|
|
93
|
+
]);
|
|
94
|
+
const selected = projects.find((p) => p.id === projectId);
|
|
95
|
+
if (!selected) {
|
|
96
|
+
throw new CliError("Project selection failed");
|
|
97
|
+
}
|
|
98
|
+
return selected;
|
|
99
|
+
}
|
|
100
|
+
async function promptForSpecification(specs, defaultId) {
|
|
101
|
+
if (specs.length === 0) {
|
|
102
|
+
return void 0;
|
|
103
|
+
}
|
|
104
|
+
const choices = buildSpecificationChoices(specs);
|
|
105
|
+
const { specificationId } = await inquirer.prompt([
|
|
106
|
+
{
|
|
107
|
+
type: "list",
|
|
108
|
+
name: "specificationId",
|
|
109
|
+
message: "Select default specification (optional):",
|
|
110
|
+
choices,
|
|
111
|
+
pageSize: 10,
|
|
112
|
+
// Resume-aware (M9.8): pre-select the currently-configured spec.
|
|
113
|
+
...defaultId ? { default: defaultId } : {}
|
|
114
|
+
}
|
|
115
|
+
]);
|
|
116
|
+
if (!specificationId) {
|
|
117
|
+
return void 0;
|
|
118
|
+
}
|
|
119
|
+
return specs.find((s) => s.id === specificationId);
|
|
120
|
+
}
|
|
121
|
+
async function promptForFormat() {
|
|
122
|
+
const { format } = await inquirer.prompt([
|
|
123
|
+
{
|
|
124
|
+
type: "list",
|
|
125
|
+
name: "format",
|
|
126
|
+
message: "Default MCP response format (TOON or JSON):",
|
|
127
|
+
choices: FORMAT_CHOICES,
|
|
128
|
+
default: DEFAULT_FORMAT
|
|
129
|
+
}
|
|
130
|
+
]);
|
|
131
|
+
return format;
|
|
132
|
+
}
|
|
133
|
+
function validateProjectId(projectId, projects) {
|
|
134
|
+
const project = projects.find((p) => p.id === projectId);
|
|
135
|
+
if (!project) {
|
|
136
|
+
const available = projects.map((p) => ` - ${p.id}: ${p.name}`).join("\n");
|
|
137
|
+
throw new ValidationError(
|
|
138
|
+
`Project not found: ${projectId}`,
|
|
139
|
+
`Available projects:
|
|
140
|
+
${available}`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
return project;
|
|
144
|
+
}
|
|
145
|
+
function validateSpecificationId(specId, specs) {
|
|
146
|
+
const spec = specs.find((s) => s.id === specId);
|
|
147
|
+
if (!spec) {
|
|
148
|
+
const available = specs.map((s) => ` - ${s.id}: ${s.title}`).join("\n");
|
|
149
|
+
throw new ValidationError(
|
|
150
|
+
`Specification not found: ${specId}`,
|
|
151
|
+
`Available specifications:
|
|
152
|
+
${available}`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return spec;
|
|
156
|
+
}
|
|
157
|
+
function showSettings() {
|
|
158
|
+
const config = resolveConfig();
|
|
159
|
+
const globalConfig = loadGlobalConfig();
|
|
160
|
+
const projectConfig = tryLoadProjectConfig();
|
|
161
|
+
const paths = getConfigPaths();
|
|
162
|
+
printBlank();
|
|
163
|
+
printHeader("Current Configuration");
|
|
164
|
+
printBlank();
|
|
165
|
+
console.log(colors.bold("Resolved Settings"));
|
|
166
|
+
console.log(colors.muted("Values merged from all sources (env > project > global > default)"));
|
|
167
|
+
printBlank();
|
|
168
|
+
const apiKeyDisplay = config.apiKey ? `${config.apiKey.slice(0, 12)}...${config.apiKey.slice(-4)}` : null;
|
|
169
|
+
const sourceLabel = (source) => source ? colors.muted(` (from ${source})`) : "";
|
|
170
|
+
printField("API Key", apiKeyDisplay ? `${apiKeyDisplay}${sourceLabel(config.sources.apiKey)}` : void 0);
|
|
171
|
+
printField("Project ID", config.projectId ? `${config.projectId}${sourceLabel(config.sources.projectId)}` : void 0);
|
|
172
|
+
printField("Specification ID", config.specificationId ? `${config.specificationId}${sourceLabel(config.sources.specificationId)}` : void 0);
|
|
173
|
+
printField("MCP output format", `${config.mcpOutputFormat}${sourceLabel(config.sources.mcpOutputFormat)}`);
|
|
174
|
+
printField("Auto-context", config.autoSetContext ? "enabled" : "disabled");
|
|
175
|
+
printField("Debug", config.debug ? "enabled" : "disabled");
|
|
176
|
+
printField("API URL", config.apiUrl);
|
|
177
|
+
printBlank();
|
|
178
|
+
printHeader("Config Files");
|
|
179
|
+
printBlank();
|
|
180
|
+
const globalExists = fs.existsSync(paths.globalConfig);
|
|
181
|
+
const projectExists = fs.existsSync(paths.projectConfig);
|
|
182
|
+
const mcpExists = fs.existsSync(paths.mcpConfig);
|
|
183
|
+
printField("Global config", `${paths.globalConfig} ${globalExists ? colors.success("(exists)") : colors.muted("(not found)")}`);
|
|
184
|
+
printField("Project config", `${paths.projectConfig} ${projectExists ? colors.success("(exists)") : colors.muted("(not found)")}`);
|
|
185
|
+
printField("MCP config", `${paths.mcpConfig} ${mcpExists ? colors.success("(exists)") : colors.muted("(not found)")}`);
|
|
186
|
+
if (globalExists && Object.keys(globalConfig).length > 0) {
|
|
187
|
+
printBlank();
|
|
188
|
+
printHeader("Global Config (~/.specforge/config.json)");
|
|
189
|
+
printBlank();
|
|
190
|
+
if (globalConfig.apiKey) {
|
|
191
|
+
printField("apiKey", `${globalConfig.apiKey.slice(0, 12)}...${globalConfig.apiKey.slice(-4)}`);
|
|
192
|
+
}
|
|
193
|
+
if (globalConfig.mcpOutputFormat) {
|
|
194
|
+
printField("mcpOutputFormat", globalConfig.mcpOutputFormat);
|
|
195
|
+
}
|
|
196
|
+
if (globalConfig.defaultProjectId) {
|
|
197
|
+
printField("defaultProjectId", globalConfig.defaultProjectId);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (projectExists && projectConfig) {
|
|
201
|
+
printBlank();
|
|
202
|
+
printHeader("Project Config (.specforge/config.json)");
|
|
203
|
+
printBlank();
|
|
204
|
+
if (projectConfig.projectId) {
|
|
205
|
+
printField("projectId", projectConfig.projectId);
|
|
206
|
+
}
|
|
207
|
+
if (projectConfig.specificationId) {
|
|
208
|
+
printField("specificationId", projectConfig.specificationId);
|
|
209
|
+
}
|
|
210
|
+
if (projectConfig.mcpOutputFormat) {
|
|
211
|
+
printField("mcpOutputFormat", projectConfig.mcpOutputFormat);
|
|
212
|
+
}
|
|
213
|
+
if (projectConfig.autoSetContext !== void 0) {
|
|
214
|
+
printField("autoSetContext", String(projectConfig.autoSetContext));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
printBlank();
|
|
218
|
+
}
|
|
219
|
+
async function promptForAgentTeams() {
|
|
220
|
+
const { configure } = await inquirer.prompt([
|
|
221
|
+
{
|
|
222
|
+
type: "confirm",
|
|
223
|
+
name: "configure",
|
|
224
|
+
message: "Configure Agent Teams (Claude Code)?",
|
|
225
|
+
default: false
|
|
226
|
+
}
|
|
227
|
+
]);
|
|
228
|
+
if (!configure) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
const answers = await inquirer.prompt([
|
|
232
|
+
{
|
|
233
|
+
type: "list",
|
|
234
|
+
name: "strategy",
|
|
235
|
+
message: "Team strategy:",
|
|
236
|
+
choices: STRATEGY_CHOICES,
|
|
237
|
+
default: "auto"
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
type: "number",
|
|
241
|
+
name: "maxTicketsPerTeam",
|
|
242
|
+
message: "Max tickets per team (1-20):",
|
|
243
|
+
default: 10,
|
|
244
|
+
validate: (val) => {
|
|
245
|
+
if (!Number.isInteger(val) || val < 1 || val > 20) {
|
|
246
|
+
return "Must be an integer between 1 and 20";
|
|
247
|
+
}
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
type: "number",
|
|
253
|
+
name: "maxParallelEpics",
|
|
254
|
+
message: "Max parallel epics (1-10):",
|
|
255
|
+
default: 3,
|
|
256
|
+
validate: (val) => {
|
|
257
|
+
if (!Number.isInteger(val) || val < 1 || val > 10) {
|
|
258
|
+
return "Must be an integer between 1 and 10";
|
|
259
|
+
}
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
type: "list",
|
|
265
|
+
name: "branchPrefix",
|
|
266
|
+
message: "Branch prefix:",
|
|
267
|
+
choices: [
|
|
268
|
+
{ name: "ticket/ (Recommended)", value: "ticket/" },
|
|
269
|
+
{ name: "epic/", value: "epic/" },
|
|
270
|
+
{ name: "spec/", value: "spec/" }
|
|
271
|
+
],
|
|
272
|
+
default: "ticket/"
|
|
273
|
+
}
|
|
274
|
+
]);
|
|
275
|
+
return {
|
|
276
|
+
enabled: true,
|
|
277
|
+
strategy: answers.strategy,
|
|
278
|
+
maxTicketsPerTeam: answers.maxTicketsPerTeam,
|
|
279
|
+
maxParallelEpics: answers.maxParallelEpics,
|
|
280
|
+
branchPrefix: answers.branchPrefix
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
function getTemplatesDir() {
|
|
284
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
285
|
+
const __dirname = path.dirname(__filename);
|
|
286
|
+
const packageRoot = path.resolve(__dirname, "..", "..", "..");
|
|
287
|
+
return path.join(packageRoot, "src", "cli", "templates", "skills");
|
|
288
|
+
}
|
|
289
|
+
async function scaffoldSkillFiles(projectName, skipPrompts) {
|
|
290
|
+
const templatesDir = getTemplatesDir();
|
|
291
|
+
const skillsRoot = path.resolve(process.cwd(), ".claude", "skills");
|
|
292
|
+
if (!fs.existsSync(templatesDir)) {
|
|
293
|
+
printWarning("Skill templates not found. Skipping scaffolding.");
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
fs.mkdirSync(skillsRoot, { recursive: true });
|
|
298
|
+
} catch {
|
|
299
|
+
printWarning("Could not create .claude/skills/ directory. Skipping skill scaffolding.");
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const targetFor = (skill) => path.join(skillsRoot, skill, "SKILL.md");
|
|
303
|
+
const existingSkills = SKILL_TEMPLATE_FILES.filter(
|
|
304
|
+
(skill) => fs.existsSync(targetFor(skill))
|
|
305
|
+
);
|
|
306
|
+
let overwritePolicy = "ask";
|
|
307
|
+
if (existingSkills.length > 0 && !skipPrompts) {
|
|
308
|
+
const { policy } = await inquirer.prompt([
|
|
309
|
+
{
|
|
310
|
+
type: "list",
|
|
311
|
+
name: "policy",
|
|
312
|
+
message: `${existingSkills.length} skill(s) already exist. How should we proceed?`,
|
|
313
|
+
choices: [
|
|
314
|
+
{ name: "Skip all existing", value: "skip" },
|
|
315
|
+
{ name: "Overwrite all", value: "overwrite" },
|
|
316
|
+
{ name: "Ask for each", value: "ask" }
|
|
317
|
+
],
|
|
318
|
+
default: "ask"
|
|
319
|
+
}
|
|
320
|
+
]);
|
|
321
|
+
overwritePolicy = policy;
|
|
322
|
+
} else if (skipPrompts) {
|
|
323
|
+
overwritePolicy = "skip";
|
|
324
|
+
}
|
|
325
|
+
const scaffolded = [];
|
|
326
|
+
for (const skill of SKILL_TEMPLATE_FILES) {
|
|
327
|
+
const sourcePath = path.join(templatesDir, `${skill}.md`);
|
|
328
|
+
const targetPath = targetFor(skill);
|
|
329
|
+
if (!fs.existsSync(sourcePath)) {
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (fs.existsSync(targetPath)) {
|
|
333
|
+
if (overwritePolicy === "skip") {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (overwritePolicy === "ask") {
|
|
337
|
+
const { overwrite } = await inquirer.prompt([
|
|
338
|
+
{
|
|
339
|
+
type: "confirm",
|
|
340
|
+
name: "overwrite",
|
|
341
|
+
message: `Skill ${skill}/SKILL.md already exists. Overwrite?`,
|
|
342
|
+
default: false
|
|
343
|
+
}
|
|
344
|
+
]);
|
|
345
|
+
if (!overwrite) continue;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
let content = fs.readFileSync(sourcePath, "utf-8");
|
|
350
|
+
if (projectName) {
|
|
351
|
+
content = content.replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
352
|
+
}
|
|
353
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
354
|
+
fs.writeFileSync(targetPath, content);
|
|
355
|
+
scaffolded.push(skill);
|
|
356
|
+
} catch {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (scaffolded.length > 0) {
|
|
361
|
+
printBlank();
|
|
362
|
+
printSuccess(`Scaffolded ${scaffolded.length} skill(s) to .claude/skills/`);
|
|
363
|
+
for (const skill of scaffolded) {
|
|
364
|
+
console.log(` ${colors.muted("\u2022")} ${skill}/SKILL.md`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function refuseIfLegacyConfig() {
|
|
369
|
+
const legacyPath = path.resolve(CONFIG_FILES.LEGACY_PROJECT_CONFIG);
|
|
370
|
+
if (!fs.existsSync(legacyPath)) {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
throw new CliError(
|
|
374
|
+
"Legacy .specforge.json detected",
|
|
375
|
+
1,
|
|
376
|
+
`Delete ${legacyPath} before running \`specforge init\`. The config layout changed to .specforge/config.json in the first production release; no automatic migration is performed.`
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
async function initAction(options) {
|
|
380
|
+
if (options.show) {
|
|
381
|
+
showSettings();
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
refuseIfLegacyConfig();
|
|
385
|
+
const config = resolveConfig();
|
|
386
|
+
if (!config.apiKey) {
|
|
387
|
+
throw new CliError(
|
|
388
|
+
"Not authenticated",
|
|
389
|
+
1,
|
|
390
|
+
"Run `specforge login` to authenticate first"
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
if (options.spec === true) {
|
|
394
|
+
const existing2 = tryLoadProjectConfig();
|
|
395
|
+
if (existing2?.projectId) {
|
|
396
|
+
const client2 = createCliClient({ apiKey: config.apiKey, apiUrl: config.apiUrl, debug: config.debug });
|
|
397
|
+
const specs = await fetchSpecifications(client2, existing2.projectId);
|
|
398
|
+
const selectedSpec2 = await promptForSpecification(specs);
|
|
399
|
+
if (selectedSpec2) {
|
|
400
|
+
saveProjectConfig({
|
|
401
|
+
...existing2,
|
|
402
|
+
specificationId: selectedSpec2.id,
|
|
403
|
+
// Switching spec abandons the previous spec's planning session (M9.8).
|
|
404
|
+
planningSessionId: selectedSpec2.id === existing2.specificationId ? existing2.planningSessionId : void 0
|
|
405
|
+
});
|
|
406
|
+
printSuccess(`Switched to: ${selectedSpec2.title}`);
|
|
407
|
+
}
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
printWarning("No project configured. Running full init...");
|
|
411
|
+
printBlank();
|
|
412
|
+
}
|
|
413
|
+
if (options.specOnly) {
|
|
414
|
+
const existing2 = tryLoadProjectConfig();
|
|
415
|
+
if (!existing2?.projectId) {
|
|
416
|
+
throw new CliError("No .specforge/config.json found \u2014 run `specforge init` first");
|
|
417
|
+
}
|
|
418
|
+
const client2 = createCliClient({ apiKey: config.apiKey, apiUrl: config.apiUrl, debug: config.debug });
|
|
419
|
+
const specs = await fetchSpecifications(client2, existing2.projectId);
|
|
420
|
+
let selectedSpec2;
|
|
421
|
+
if (typeof options.spec === "string") {
|
|
422
|
+
selectedSpec2 = validateSpecificationId(options.spec, specs);
|
|
423
|
+
} else {
|
|
424
|
+
const { specId } = await inquirer.prompt([{
|
|
425
|
+
type: "list",
|
|
426
|
+
name: "specId",
|
|
427
|
+
message: "Select specification:",
|
|
428
|
+
choices: specs.map((s) => ({ name: `${s.title} (${s.status})`, value: s.id }))
|
|
429
|
+
}]);
|
|
430
|
+
selectedSpec2 = specs.find((s) => s.id === specId);
|
|
431
|
+
}
|
|
432
|
+
saveProjectConfig({
|
|
433
|
+
...existing2,
|
|
434
|
+
specificationId: selectedSpec2.id,
|
|
435
|
+
// Switching spec abandons the previous spec's planning session (M9.8).
|
|
436
|
+
planningSessionId: selectedSpec2.id === existing2.specificationId ? existing2.planningSessionId : void 0
|
|
437
|
+
});
|
|
438
|
+
printSuccess(`Switched to: ${selectedSpec2.title}`);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
printBlank();
|
|
442
|
+
printBanner("figlet");
|
|
443
|
+
printBlank();
|
|
444
|
+
const existing = checkExistingFiles();
|
|
445
|
+
const priorConfig = tryLoadProjectConfig();
|
|
446
|
+
const canResume = !!priorConfig?.projectId && !options.force;
|
|
447
|
+
if (existing.anyExists && !options.force && !canResume) {
|
|
448
|
+
printWarning("Configuration files already exist:");
|
|
449
|
+
if (existing.mcpConfig) {
|
|
450
|
+
console.log(` ${colors.muted("\u2022")} .mcp.json`);
|
|
451
|
+
}
|
|
452
|
+
if (existing.projectConfig) {
|
|
453
|
+
console.log(` ${colors.muted("\u2022")} ${CONFIG_FILES.PROJECT_CONFIG}`);
|
|
454
|
+
}
|
|
455
|
+
printBlank();
|
|
456
|
+
console.log(`Use ${colors.primary("--force")} to overwrite existing files.`);
|
|
457
|
+
printBlank();
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const client = createCliClient({
|
|
461
|
+
apiKey: config.apiKey,
|
|
462
|
+
apiUrl: config.apiUrl,
|
|
463
|
+
debug: config.debug
|
|
464
|
+
});
|
|
465
|
+
const spinner = ora({
|
|
466
|
+
text: "Connecting to SpecForge...",
|
|
467
|
+
color: "cyan"
|
|
468
|
+
}).start();
|
|
469
|
+
let projects;
|
|
470
|
+
try {
|
|
471
|
+
projects = await fetchProjects(client);
|
|
472
|
+
spinner.succeed("Connected to SpecForge");
|
|
473
|
+
} catch (error) {
|
|
474
|
+
spinner.fail("Connection failed");
|
|
475
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
476
|
+
if (message.includes("401") || message.includes("Unauthorized")) {
|
|
477
|
+
throw new CliError(
|
|
478
|
+
"Authentication failed",
|
|
479
|
+
1,
|
|
480
|
+
"Your API key may be invalid. Run `specforge login` to re-authenticate"
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
throw new NetworkError(`Failed to fetch projects: ${message}`);
|
|
484
|
+
}
|
|
485
|
+
if (projects.length === 0) {
|
|
486
|
+
printBlank();
|
|
487
|
+
printWarning("No projects found in your account.");
|
|
488
|
+
console.log(
|
|
489
|
+
`Create a project at ${colors.primary.underline("https://specforge.com.br")} first.`
|
|
490
|
+
);
|
|
491
|
+
printBlank();
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
let resumeMode = false;
|
|
495
|
+
let defaultProjectId;
|
|
496
|
+
let defaultSpecId;
|
|
497
|
+
let resumeSelectedSpec;
|
|
498
|
+
if (canResume) {
|
|
499
|
+
const currentProject = projects.find((p) => p.id === priorConfig.projectId);
|
|
500
|
+
if (currentProject) {
|
|
501
|
+
let currentSpecs = [];
|
|
502
|
+
try {
|
|
503
|
+
currentSpecs = await fetchSpecifications(client, currentProject.id);
|
|
504
|
+
} catch {
|
|
505
|
+
}
|
|
506
|
+
const currentSpec = priorConfig.specificationId ? currentSpecs.find((s) => s.id === priorConfig.specificationId) : void 0;
|
|
507
|
+
printBlank();
|
|
508
|
+
printHeader("Configuration already exists");
|
|
509
|
+
printBlank();
|
|
510
|
+
printField("Project", currentProject.name);
|
|
511
|
+
printField(
|
|
512
|
+
"Specification",
|
|
513
|
+
currentSpec ? `${currentSpec.title} [${currentSpec.status}]` : colors.muted("(none)")
|
|
514
|
+
);
|
|
515
|
+
if (priorConfig.planningSessionId) {
|
|
516
|
+
printField("Planning session", `${priorConfig.planningSessionId} ${colors.muted("(active)")}`);
|
|
517
|
+
}
|
|
518
|
+
printBlank();
|
|
519
|
+
const { choice } = await inquirer.prompt([
|
|
520
|
+
{
|
|
521
|
+
type: "list",
|
|
522
|
+
name: "choice",
|
|
523
|
+
message: "How do you want to proceed?",
|
|
524
|
+
choices: [
|
|
525
|
+
{ name: "Resume \u2014 keep config, refresh scaffolding", value: "resume" },
|
|
526
|
+
{ name: "Reconfigure \u2014 pick project/spec again (current pre-selected)", value: "reconfigure" },
|
|
527
|
+
{ name: "Cancel", value: "cancel" }
|
|
528
|
+
],
|
|
529
|
+
default: "resume"
|
|
530
|
+
}
|
|
531
|
+
]);
|
|
532
|
+
if (choice === "cancel") {
|
|
533
|
+
printBlank();
|
|
534
|
+
console.log(colors.muted("Cancelled \u2014 no changes made."));
|
|
535
|
+
printBlank();
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (choice === "resume") {
|
|
539
|
+
resumeMode = true;
|
|
540
|
+
resumeSelectedSpec = currentSpec;
|
|
541
|
+
} else {
|
|
542
|
+
defaultProjectId = currentProject.id;
|
|
543
|
+
defaultSpecId = currentSpec?.id;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
let selectedProject;
|
|
548
|
+
let selectedSpec;
|
|
549
|
+
let selectedFormat;
|
|
550
|
+
if (resumeMode) {
|
|
551
|
+
selectedProject = projects.find((p) => p.id === priorConfig.projectId);
|
|
552
|
+
selectedSpec = resumeSelectedSpec;
|
|
553
|
+
selectedFormat = priorConfig.mcpOutputFormat ?? DEFAULT_FORMAT;
|
|
554
|
+
} else {
|
|
555
|
+
if (options.project) {
|
|
556
|
+
selectedProject = validateProjectId(options.project, projects);
|
|
557
|
+
console.log(colors.muted(`Using project: ${selectedProject.name}`));
|
|
558
|
+
} else {
|
|
559
|
+
selectedProject = await promptForProject(projects, defaultProjectId);
|
|
560
|
+
}
|
|
561
|
+
const specsSpinner = ora({
|
|
562
|
+
text: "Fetching specifications...",
|
|
563
|
+
color: "cyan"
|
|
564
|
+
}).start();
|
|
565
|
+
let specs;
|
|
566
|
+
try {
|
|
567
|
+
specs = await fetchSpecifications(client, selectedProject.id);
|
|
568
|
+
specsSpinner.succeed(`Found ${specs.length} specification(s)`);
|
|
569
|
+
} catch (error) {
|
|
570
|
+
specsSpinner.fail("Failed to fetch specifications");
|
|
571
|
+
throw new NetworkError(
|
|
572
|
+
`Failed to fetch specifications: ${error instanceof Error ? error.message : String(error)}`
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
if (typeof options.spec === "string") {
|
|
576
|
+
if (specs.length === 0) {
|
|
577
|
+
printWarning(`No specifications found. Ignoring --spec flag.`);
|
|
578
|
+
} else {
|
|
579
|
+
selectedSpec = validateSpecificationId(options.spec, specs);
|
|
580
|
+
console.log(colors.muted(`Using specification: ${selectedSpec.title}`));
|
|
581
|
+
}
|
|
582
|
+
} else if (specs.length > 0) {
|
|
583
|
+
selectedSpec = await promptForSpecification(specs, defaultSpecId);
|
|
584
|
+
}
|
|
585
|
+
if (options.format) {
|
|
586
|
+
if (options.format !== "json" && options.format !== "toon") {
|
|
587
|
+
throw new ValidationError(
|
|
588
|
+
`Invalid format: ${options.format}`,
|
|
589
|
+
"Valid formats are: json, toon"
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
selectedFormat = options.format;
|
|
593
|
+
console.log(colors.muted(`Using format: ${selectedFormat}`));
|
|
594
|
+
} else {
|
|
595
|
+
selectedFormat = await promptForFormat();
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const isNonInteractive = !!(options.yes || options.project && typeof options.spec === "string" && options.format);
|
|
599
|
+
const detectedCLIs = detectInstalledTools();
|
|
600
|
+
let chosenTargets;
|
|
601
|
+
if (detectedCLIs.length > 1 && !isNonInteractive) {
|
|
602
|
+
const ordered = [
|
|
603
|
+
...detectedCLIs.includes("claude") ? ["claude"] : [],
|
|
604
|
+
...detectedCLIs.filter((t) => t !== "claude")
|
|
605
|
+
];
|
|
606
|
+
const choices = ordered.map((t) => ({
|
|
607
|
+
name: t === "claude" ? `${TARGETS[t].name} ${colors.dim("* Recommended")}` : TARGETS[t].name,
|
|
608
|
+
value: t
|
|
609
|
+
}));
|
|
610
|
+
printBlank();
|
|
611
|
+
const { chosen } = await inquirer.prompt([{
|
|
612
|
+
type: "list",
|
|
613
|
+
name: "chosen",
|
|
614
|
+
message: "Multiple AI CLIs detected \u2014 which one should SpecForge configure?",
|
|
615
|
+
choices,
|
|
616
|
+
default: detectedCLIs.includes("claude") ? "claude" : ordered[0]
|
|
617
|
+
}]);
|
|
618
|
+
chosenTargets = [chosen];
|
|
619
|
+
}
|
|
620
|
+
printBlank();
|
|
621
|
+
let addedSpecforge = false;
|
|
622
|
+
let addedMcp = false;
|
|
623
|
+
let scaffoldResult = { writes: [], detected: [] };
|
|
624
|
+
const keepPlanningSession = !!selectedSpec?.id && selectedSpec.id === priorConfig?.specificationId;
|
|
625
|
+
await forgeProgress("Forging spec", [
|
|
626
|
+
{
|
|
627
|
+
label: "save config",
|
|
628
|
+
fn: async () => {
|
|
629
|
+
saveProjectConfig({
|
|
630
|
+
projectId: selectedProject.id,
|
|
631
|
+
specificationId: selectedSpec?.id,
|
|
632
|
+
mcpOutputFormat: selectedFormat,
|
|
633
|
+
autoSetContext: true,
|
|
634
|
+
planningSessionId: keepPlanningSession ? priorConfig?.planningSessionId : void 0
|
|
635
|
+
});
|
|
636
|
+
const addedStable = addToGitignore(".specforge/");
|
|
637
|
+
const addedCanary = addToGitignore(".specforge-canary/");
|
|
638
|
+
addedSpecforge = addedStable || addedCanary;
|
|
639
|
+
addedMcp = addToGitignore(".mcp.json");
|
|
640
|
+
}
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
label: "scaffold files",
|
|
644
|
+
fn: async () => {
|
|
645
|
+
scaffoldResult = scaffoldMultiCli({
|
|
646
|
+
projectName: selectedProject.name,
|
|
647
|
+
apiKey: config.apiKey ?? void 0,
|
|
648
|
+
force: options.force,
|
|
649
|
+
targets: chosenTargets
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
]);
|
|
654
|
+
printBlank();
|
|
655
|
+
printSuccess("Created .specforge/config.json");
|
|
656
|
+
if (addedSpecforge) {
|
|
657
|
+
printSuccess("Added .specforge/ + .specforge-canary/ to .gitignore");
|
|
658
|
+
}
|
|
659
|
+
if (addedMcp) {
|
|
660
|
+
printSuccess("Added .mcp.json to .gitignore");
|
|
661
|
+
}
|
|
662
|
+
const { writes, detected } = scaffoldResult;
|
|
663
|
+
const changedWrites = writes.filter((w) => w.changed);
|
|
664
|
+
if (changedWrites.length > 0) {
|
|
665
|
+
printBlank();
|
|
666
|
+
printSuccess(`Multi-CLI scaffolding (${detected.length} CLI(s) detected):`);
|
|
667
|
+
for (const w of changedWrites) {
|
|
668
|
+
console.log(` ${colors.muted("\u2022")} ${w.relPath}`);
|
|
669
|
+
}
|
|
670
|
+
if (detected.length > 0) {
|
|
671
|
+
console.log(colors.dim(` Detected: ${detected.map((t) => TARGETS[t].name).join(", ")}`));
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
let agentTeamsConfig = null;
|
|
675
|
+
if (isNonInteractive) {
|
|
676
|
+
agentTeamsConfig = {
|
|
677
|
+
enabled: false,
|
|
678
|
+
strategy: "auto",
|
|
679
|
+
maxTicketsPerTeam: 10,
|
|
680
|
+
maxParallelEpics: 3,
|
|
681
|
+
branchPrefix: "ticket/"
|
|
682
|
+
};
|
|
683
|
+
} else {
|
|
684
|
+
printBlank();
|
|
685
|
+
agentTeamsConfig = await promptForAgentTeams();
|
|
686
|
+
}
|
|
687
|
+
if (agentTeamsConfig?.enabled) {
|
|
688
|
+
setAgentTeamsConfig({
|
|
689
|
+
enabled: agentTeamsConfig.enabled,
|
|
690
|
+
strategy: agentTeamsConfig.strategy,
|
|
691
|
+
maxTicketsPerTeam: agentTeamsConfig.maxTicketsPerTeam,
|
|
692
|
+
maxParallelEpics: agentTeamsConfig.maxParallelEpics,
|
|
693
|
+
branchPrefix: agentTeamsConfig.branchPrefix
|
|
694
|
+
});
|
|
695
|
+
printSuccess("Saved Agent Teams configuration to .specforge/config.json");
|
|
696
|
+
}
|
|
697
|
+
printBlank();
|
|
698
|
+
printHeader("Configuration Complete");
|
|
699
|
+
printBlank();
|
|
700
|
+
printField("Project", selectedProject.name);
|
|
701
|
+
if (selectedSpec) {
|
|
702
|
+
printField("Specification", selectedSpec.title);
|
|
703
|
+
}
|
|
704
|
+
printField("Format", selectedFormat);
|
|
705
|
+
printField("Auto-context", "enabled");
|
|
706
|
+
if (agentTeamsConfig?.enabled) {
|
|
707
|
+
printField("Agent Teams", "enabled");
|
|
708
|
+
printField("Strategy", agentTeamsConfig.strategy);
|
|
709
|
+
}
|
|
710
|
+
if (!isNonInteractive) {
|
|
711
|
+
printBlank();
|
|
712
|
+
const { scaffoldNow } = await inquirer.prompt([{
|
|
713
|
+
type: "list",
|
|
714
|
+
name: "scaffoldNow",
|
|
715
|
+
message: "Scaffold SpecForge commands, agents, and skills for Claude Code?\n Generates 8 slash commands (/sf-status, /sf-context, ...), 4 agent\n definitions, and 4 skills for deeper AI integration.",
|
|
716
|
+
choices: [
|
|
717
|
+
{ name: "Yes, scaffold now", value: true },
|
|
718
|
+
{ name: "No, I'll run `specforge scaffold` later", value: false }
|
|
719
|
+
],
|
|
720
|
+
default: true
|
|
721
|
+
}]);
|
|
722
|
+
if (scaffoldNow) {
|
|
723
|
+
const { scaffoldCommand } = await import("./scaffold/scaffold.js");
|
|
724
|
+
await scaffoldSkillFiles(selectedProject.name, isNonInteractive);
|
|
725
|
+
await scaffoldCommand({ target: "claude", update: true, project: true });
|
|
726
|
+
await scaffoldCommand({ agents: true, target: "claude", update: true, project: true });
|
|
727
|
+
} else {
|
|
728
|
+
printBlank();
|
|
729
|
+
console.log(
|
|
730
|
+
`Run ${colors.primary("specforge scaffold")} when ready.`
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
printBlank();
|
|
735
|
+
if (detected.length > 0) {
|
|
736
|
+
console.log(colors.warning(`Restart ${detected.map((t) => TARGETS[t].name).join(" / ")} to activate SpecForge MCP.`));
|
|
737
|
+
} else {
|
|
738
|
+
console.log(colors.warning("Restart your AI CLI tool to activate SpecForge MCP."));
|
|
739
|
+
}
|
|
740
|
+
printBlank();
|
|
741
|
+
console.log(colors.bold("Next steps:"));
|
|
742
|
+
printHint("specforge status", "verify your configuration");
|
|
743
|
+
printHint("specforge doctor", "check MCP server connectivity");
|
|
744
|
+
printBlank();
|
|
745
|
+
}
|
|
746
|
+
function registerInitCommand(program) {
|
|
747
|
+
program.command("init").description("Initialize SpecForge for this project").option("--project <id>", "Project ID (non-interactive)").option("--spec [id]", "Specification ID, or omit ID to pick from current project").option("--format <format>", "MCP response format: json or toon (default: toon)").option("-f, --force", "Overwrite existing configuration files").option("-s, --show", "Display current configuration settings").option("-y, --yes", "Skip interactive prompts, use defaults").option("--spec-only", "Switch active specification (quick switch)").addHelpText("after", `
|
|
748
|
+
Examples:
|
|
749
|
+
$ specforge init # Interactive setup (resume-aware if already configured)
|
|
750
|
+
$ specforge init --show # Display current settings
|
|
751
|
+
$ specforge init --project proj_abc123 # Set project non-interactively
|
|
752
|
+
$ specforge init --project proj_abc --spec spec_xyz --format toon
|
|
753
|
+
$ specforge init --force # Overwrite existing config
|
|
754
|
+
$ specforge init --yes # Use defaults, skip prompts
|
|
755
|
+
$ specforge init --spec # Pick specification from current project
|
|
756
|
+
$ specforge init --spec-only # Switch specification (alias)
|
|
757
|
+
|
|
758
|
+
This command creates:
|
|
759
|
+
.specforge/config.json - Project-specific SpecForge settings
|
|
760
|
+
Per-CLI MCP config - .mcp.json (Claude Code / Copilot / Cursor),
|
|
761
|
+
.gemini/settings.json, .codex/config.toml, opencode.json
|
|
762
|
+
Per-CLI instruction file - CLAUDE.md, GEMINI.md, .github/copilot-instructions.md,
|
|
763
|
+
or AGENTS.md (Codex / OpenCode / Cursor fallback)
|
|
764
|
+
Per-CLI skill files - specforge-conventions
|
|
765
|
+
|
|
766
|
+
After running init, restart your AI CLI tool to activate the MCP server.
|
|
767
|
+
`).action(withErrorHandler(initAction));
|
|
768
|
+
}
|
|
769
|
+
export {
|
|
770
|
+
initAction,
|
|
771
|
+
registerInitCommand
|
|
772
|
+
};
|
|
773
|
+
//# sourceMappingURL=init.js.map
|