gsd-remix 1.0.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/LICENSE +21 -0
- package/README.md +939 -0
- package/README.zh-CN.md +876 -0
- package/agents/gsd-advisor-researcher.md +127 -0
- package/agents/gsd-ai-researcher.md +133 -0
- package/agents/gsd-assumptions-analyzer.md +105 -0
- package/agents/gsd-code-fixer.md +517 -0
- package/agents/gsd-code-reviewer.md +371 -0
- package/agents/gsd-codebase-mapper.md +781 -0
- package/agents/gsd-debug-session-manager.md +314 -0
- package/agents/gsd-debugger.md +1452 -0
- package/agents/gsd-doc-classifier.md +168 -0
- package/agents/gsd-doc-synthesizer.md +204 -0
- package/agents/gsd-doc-verifier.md +217 -0
- package/agents/gsd-doc-writer.md +615 -0
- package/agents/gsd-domain-researcher.md +153 -0
- package/agents/gsd-eval-auditor.md +191 -0
- package/agents/gsd-eval-planner.md +154 -0
- package/agents/gsd-executor.md +603 -0
- package/agents/gsd-framework-selector.md +160 -0
- package/agents/gsd-integration-checker.md +470 -0
- package/agents/gsd-intel-updater.md +334 -0
- package/agents/gsd-nyquist-auditor.md +203 -0
- package/agents/gsd-pattern-mapper.md +335 -0
- package/agents/gsd-phase-researcher.md +841 -0
- package/agents/gsd-plan-checker.md +978 -0
- package/agents/gsd-planner.md +1251 -0
- package/agents/gsd-project-researcher.md +677 -0
- package/agents/gsd-research-synthesizer.md +247 -0
- package/agents/gsd-roadmapper.md +688 -0
- package/agents/gsd-security-auditor.md +155 -0
- package/agents/gsd-ui-auditor.md +495 -0
- package/agents/gsd-ui-checker.md +309 -0
- package/agents/gsd-ui-researcher.md +380 -0
- package/agents/gsd-user-profiler.md +171 -0
- package/agents/gsd-verifier.md +830 -0
- package/bin/install.js +7062 -0
- package/commands/gsd/add-backlog.md +79 -0
- package/commands/gsd/add-phase.md +43 -0
- package/commands/gsd/add-tests.md +41 -0
- package/commands/gsd/add-todo.md +47 -0
- package/commands/gsd/ai-integration-phase.md +36 -0
- package/commands/gsd/analyze-dependencies.md +34 -0
- package/commands/gsd/audit-fix.md +33 -0
- package/commands/gsd/audit-milestone.md +36 -0
- package/commands/gsd/audit-uat.md +24 -0
- package/commands/gsd/autonomous.md +46 -0
- package/commands/gsd/check-todos.md +45 -0
- package/commands/gsd/cleanup.md +23 -0
- package/commands/gsd/code-review-fix.md +52 -0
- package/commands/gsd/code-review.md +55 -0
- package/commands/gsd/complete-milestone.md +136 -0
- package/commands/gsd/debug.md +263 -0
- package/commands/gsd/discuss-phase.md +69 -0
- package/commands/gsd/do.md +30 -0
- package/commands/gsd/docs-update.md +48 -0
- package/commands/gsd/eval-review.md +32 -0
- package/commands/gsd/execute-phase.md +63 -0
- package/commands/gsd/explore.md +27 -0
- package/commands/gsd/extract_learnings.md +22 -0
- package/commands/gsd/fast.md +30 -0
- package/commands/gsd/forensics.md +56 -0
- package/commands/gsd/from-gsd2.md +47 -0
- package/commands/gsd/graphify.md +201 -0
- package/commands/gsd/health.md +22 -0
- package/commands/gsd/help.md +24 -0
- package/commands/gsd/import.md +37 -0
- package/commands/gsd/inbox.md +38 -0
- package/commands/gsd/ingest-docs.md +42 -0
- package/commands/gsd/insert-phase.md +32 -0
- package/commands/gsd/intel.md +179 -0
- package/commands/gsd/join-discord.md +19 -0
- package/commands/gsd/list-phase-assumptions.md +46 -0
- package/commands/gsd/list-workspaces.md +19 -0
- package/commands/gsd/manager.md +40 -0
- package/commands/gsd/map-codebase.md +71 -0
- package/commands/gsd/milestone-summary.md +51 -0
- package/commands/gsd/new-milestone.md +44 -0
- package/commands/gsd/new-project.md +46 -0
- package/commands/gsd/new-workspace.md +44 -0
- package/commands/gsd/next.md +28 -0
- package/commands/gsd/note.md +34 -0
- package/commands/gsd/pause-work.md +38 -0
- package/commands/gsd/plan-milestone-gaps.md +34 -0
- package/commands/gsd/plan-phase.md +52 -0
- package/commands/gsd/plan-review-convergence.md +52 -0
- package/commands/gsd/plant-seed.md +28 -0
- package/commands/gsd/pr-branch.md +25 -0
- package/commands/gsd/profile-user.md +46 -0
- package/commands/gsd/progress.md +25 -0
- package/commands/gsd/quick.md +173 -0
- package/commands/gsd/reapply-patches.md +331 -0
- package/commands/gsd/remove-phase.md +31 -0
- package/commands/gsd/remove-workspace.md +26 -0
- package/commands/gsd/research-phase.md +195 -0
- package/commands/gsd/resume-work.md +40 -0
- package/commands/gsd/review-backlog.md +62 -0
- package/commands/gsd/review.md +40 -0
- package/commands/gsd/scan.md +26 -0
- package/commands/gsd/secure-phase.md +35 -0
- package/commands/gsd/session-report.md +19 -0
- package/commands/gsd/set-profile.md +12 -0
- package/commands/gsd/settings.md +36 -0
- package/commands/gsd/ship.md +23 -0
- package/commands/gsd/sketch-wrap-up.md +31 -0
- package/commands/gsd/sketch.md +49 -0
- package/commands/gsd/spec-phase.md +62 -0
- package/commands/gsd/spike-wrap-up.md +31 -0
- package/commands/gsd/spike.md +46 -0
- package/commands/gsd/stats.md +18 -0
- package/commands/gsd/sync-skills.md +19 -0
- package/commands/gsd/thread.md +227 -0
- package/commands/gsd/ui-phase.md +34 -0
- package/commands/gsd/ui-review.md +32 -0
- package/commands/gsd/ultraplan-phase.md +33 -0
- package/commands/gsd/undo.md +34 -0
- package/commands/gsd/update.md +37 -0
- package/commands/gsd/validate-phase.md +35 -0
- package/commands/gsd/verify-work.md +38 -0
- package/commands/gsd/workstreams.md +69 -0
- package/get-shit-done/bin/gsd-tools.cjs +1263 -0
- package/get-shit-done/bin/lib/artifacts.cjs +52 -0
- package/get-shit-done/bin/lib/audit.cjs +757 -0
- package/get-shit-done/bin/lib/commands.cjs +1023 -0
- package/get-shit-done/bin/lib/config-schema.cjs +79 -0
- package/get-shit-done/bin/lib/config.cjs +463 -0
- package/get-shit-done/bin/lib/core.cjs +1794 -0
- package/get-shit-done/bin/lib/docs.cjs +267 -0
- package/get-shit-done/bin/lib/frontmatter.cjs +379 -0
- package/get-shit-done/bin/lib/graphify.cjs +494 -0
- package/get-shit-done/bin/lib/gsd2-import.cjs +511 -0
- package/get-shit-done/bin/lib/init.cjs +1878 -0
- package/get-shit-done/bin/lib/intel.cjs +639 -0
- package/get-shit-done/bin/lib/learnings.cjs +378 -0
- package/get-shit-done/bin/lib/milestone.cjs +283 -0
- package/get-shit-done/bin/lib/model-profiles.cjs +71 -0
- package/get-shit-done/bin/lib/phase.cjs +1058 -0
- package/get-shit-done/bin/lib/profile-output.cjs +1080 -0
- package/get-shit-done/bin/lib/profile-pipeline.cjs +539 -0
- package/get-shit-done/bin/lib/roadmap.cjs +523 -0
- package/get-shit-done/bin/lib/schema-detect.cjs +238 -0
- package/get-shit-done/bin/lib/security.cjs +504 -0
- package/get-shit-done/bin/lib/state.cjs +1649 -0
- package/get-shit-done/bin/lib/template.cjs +226 -0
- package/get-shit-done/bin/lib/uat.cjs +288 -0
- package/get-shit-done/bin/lib/verify.cjs +1184 -0
- package/get-shit-done/bin/lib/workstream.cjs +495 -0
- package/get-shit-done/bin/repair-sdk.cjs +177 -0
- package/get-shit-done/contexts/dev.md +21 -0
- package/get-shit-done/contexts/research.md +22 -0
- package/get-shit-done/contexts/review.md +22 -0
- package/get-shit-done/references/agent-contracts.md +79 -0
- package/get-shit-done/references/ai-evals.md +156 -0
- package/get-shit-done/references/ai-frameworks.md +186 -0
- package/get-shit-done/references/artifact-types.md +131 -0
- package/get-shit-done/references/autonomous-smart-discuss.md +277 -0
- package/get-shit-done/references/checkpoints.md +808 -0
- package/get-shit-done/references/common-bug-patterns.md +114 -0
- package/get-shit-done/references/context-budget.md +49 -0
- package/get-shit-done/references/continuation-format.md +253 -0
- package/get-shit-done/references/debugger-philosophy.md +76 -0
- package/get-shit-done/references/decimal-phase-calculation.md +64 -0
- package/get-shit-done/references/doc-conflict-engine.md +91 -0
- package/get-shit-done/references/domain-probes.md +125 -0
- package/get-shit-done/references/executor-examples.md +110 -0
- package/get-shit-done/references/few-shot-examples/plan-checker.md +73 -0
- package/get-shit-done/references/few-shot-examples/verifier.md +109 -0
- package/get-shit-done/references/gate-prompts.md +100 -0
- package/get-shit-done/references/gates.md +70 -0
- package/get-shit-done/references/git-integration.md +295 -0
- package/get-shit-done/references/git-planning-commit.md +40 -0
- package/get-shit-done/references/ios-scaffold.md +123 -0
- package/get-shit-done/references/mandatory-initial-read.md +2 -0
- package/get-shit-done/references/model-profile-resolution.md +38 -0
- package/get-shit-done/references/model-profiles.md +145 -0
- package/get-shit-done/references/phase-argument-parsing.md +61 -0
- package/get-shit-done/references/planner-antipatterns.md +89 -0
- package/get-shit-done/references/planner-gap-closure.md +62 -0
- package/get-shit-done/references/planner-reviews.md +39 -0
- package/get-shit-done/references/planner-revision.md +87 -0
- package/get-shit-done/references/planner-source-audit.md +73 -0
- package/get-shit-done/references/planning-config.md +460 -0
- package/get-shit-done/references/project-skills-discovery.md +19 -0
- package/get-shit-done/references/questioning.md +162 -0
- package/get-shit-done/references/revision-loop.md +97 -0
- package/get-shit-done/references/sketch-interactivity.md +41 -0
- package/get-shit-done/references/sketch-theme-system.md +94 -0
- package/get-shit-done/references/sketch-tooling.md +45 -0
- package/get-shit-done/references/sketch-variant-patterns.md +81 -0
- package/get-shit-done/references/tdd.md +330 -0
- package/get-shit-done/references/thinking-models-debug.md +44 -0
- package/get-shit-done/references/thinking-models-execution.md +50 -0
- package/get-shit-done/references/thinking-models-planning.md +62 -0
- package/get-shit-done/references/thinking-models-research.md +50 -0
- package/get-shit-done/references/thinking-models-verification.md +55 -0
- package/get-shit-done/references/thinking-partner.md +96 -0
- package/get-shit-done/references/ui-brand.md +160 -0
- package/get-shit-done/references/universal-anti-patterns.md +63 -0
- package/get-shit-done/references/user-profiling.md +681 -0
- package/get-shit-done/references/verification-overrides.md +227 -0
- package/get-shit-done/references/verification-patterns.md +612 -0
- package/get-shit-done/references/workstream-flag.md +111 -0
- package/get-shit-done/templates/AI-SPEC.md +246 -0
- package/get-shit-done/templates/DEBUG.md +169 -0
- package/get-shit-done/templates/README.md +76 -0
- package/get-shit-done/templates/SECURITY.md +61 -0
- package/get-shit-done/templates/UAT.md +265 -0
- package/get-shit-done/templates/UI-SPEC.md +100 -0
- package/get-shit-done/templates/VALIDATION.md +76 -0
- package/get-shit-done/templates/claude-md.md +145 -0
- package/get-shit-done/templates/codebase/architecture.md +255 -0
- package/get-shit-done/templates/codebase/concerns.md +310 -0
- package/get-shit-done/templates/codebase/conventions.md +307 -0
- package/get-shit-done/templates/codebase/integrations.md +280 -0
- package/get-shit-done/templates/codebase/stack.md +186 -0
- package/get-shit-done/templates/codebase/structure.md +285 -0
- package/get-shit-done/templates/codebase/testing.md +480 -0
- package/get-shit-done/templates/config.json +56 -0
- package/get-shit-done/templates/context.md +352 -0
- package/get-shit-done/templates/continue-here.md +78 -0
- package/get-shit-done/templates/copilot-instructions.md +7 -0
- package/get-shit-done/templates/debug-subagent-prompt.md +91 -0
- package/get-shit-done/templates/dev-preferences.md +21 -0
- package/get-shit-done/templates/discovery.md +146 -0
- package/get-shit-done/templates/discussion-log.md +63 -0
- package/get-shit-done/templates/milestone-archive.md +123 -0
- package/get-shit-done/templates/milestone.md +115 -0
- package/get-shit-done/templates/phase-prompt.md +610 -0
- package/get-shit-done/templates/planner-subagent-prompt.md +117 -0
- package/get-shit-done/templates/project.md +186 -0
- package/get-shit-done/templates/requirements.md +231 -0
- package/get-shit-done/templates/research-project/ARCHITECTURE.md +204 -0
- package/get-shit-done/templates/research-project/FEATURES.md +147 -0
- package/get-shit-done/templates/research-project/PITFALLS.md +200 -0
- package/get-shit-done/templates/research-project/STACK.md +120 -0
- package/get-shit-done/templates/research-project/SUMMARY.md +170 -0
- package/get-shit-done/templates/research.md +592 -0
- package/get-shit-done/templates/retrospective.md +54 -0
- package/get-shit-done/templates/roadmap.md +202 -0
- package/get-shit-done/templates/spec.md +307 -0
- package/get-shit-done/templates/state.md +184 -0
- package/get-shit-done/templates/summary-complex.md +59 -0
- package/get-shit-done/templates/summary-minimal.md +41 -0
- package/get-shit-done/templates/summary-standard.md +48 -0
- package/get-shit-done/templates/summary.md +248 -0
- package/get-shit-done/templates/user-profile.md +146 -0
- package/get-shit-done/templates/user-setup.md +311 -0
- package/get-shit-done/templates/verification-report.md +322 -0
- package/get-shit-done/workflows/add-phase.md +112 -0
- package/get-shit-done/workflows/add-tests.md +354 -0
- package/get-shit-done/workflows/add-todo.md +160 -0
- package/get-shit-done/workflows/ai-integration-phase.md +284 -0
- package/get-shit-done/workflows/analyze-dependencies.md +96 -0
- package/get-shit-done/workflows/audit-fix.md +175 -0
- package/get-shit-done/workflows/audit-milestone.md +340 -0
- package/get-shit-done/workflows/audit-uat.md +109 -0
- package/get-shit-done/workflows/autonomous.md +789 -0
- package/get-shit-done/workflows/check-todos.md +179 -0
- package/get-shit-done/workflows/cleanup.md +154 -0
- package/get-shit-done/workflows/code-review-fix.md +497 -0
- package/get-shit-done/workflows/code-review.md +515 -0
- package/get-shit-done/workflows/complete-milestone.md +847 -0
- package/get-shit-done/workflows/diagnose-issues.md +238 -0
- package/get-shit-done/workflows/discovery-phase.md +291 -0
- package/get-shit-done/workflows/discuss-phase-assumptions.md +670 -0
- package/get-shit-done/workflows/discuss-phase-power.md +308 -0
- package/get-shit-done/workflows/discuss-phase.md +1378 -0
- package/get-shit-done/workflows/do.md +110 -0
- package/get-shit-done/workflows/docs-update.md +1155 -0
- package/get-shit-done/workflows/eval-review.md +155 -0
- package/get-shit-done/workflows/execute-phase.md +1677 -0
- package/get-shit-done/workflows/execute-plan.md +533 -0
- package/get-shit-done/workflows/explore.md +141 -0
- package/get-shit-done/workflows/extract_learnings.md +242 -0
- package/get-shit-done/workflows/fast.md +105 -0
- package/get-shit-done/workflows/forensics.md +265 -0
- package/get-shit-done/workflows/graduation.md +195 -0
- package/get-shit-done/workflows/health.md +314 -0
- package/get-shit-done/workflows/help.md +667 -0
- package/get-shit-done/workflows/import.md +246 -0
- package/get-shit-done/workflows/inbox.md +387 -0
- package/get-shit-done/workflows/ingest-docs.md +328 -0
- package/get-shit-done/workflows/insert-phase.md +130 -0
- package/get-shit-done/workflows/list-phase-assumptions.md +178 -0
- package/get-shit-done/workflows/list-workspaces.md +56 -0
- package/get-shit-done/workflows/manager.md +365 -0
- package/get-shit-done/workflows/map-codebase.md +393 -0
- package/get-shit-done/workflows/milestone-summary.md +223 -0
- package/get-shit-done/workflows/new-milestone.md +611 -0
- package/get-shit-done/workflows/new-project.md +1391 -0
- package/get-shit-done/workflows/new-workspace.md +239 -0
- package/get-shit-done/workflows/next.md +220 -0
- package/get-shit-done/workflows/node-repair.md +92 -0
- package/get-shit-done/workflows/note.md +158 -0
- package/get-shit-done/workflows/pause-work.md +243 -0
- package/get-shit-done/workflows/plan-milestone-gaps.md +273 -0
- package/get-shit-done/workflows/plan-phase.md +1349 -0
- package/get-shit-done/workflows/plan-review-convergence.md +254 -0
- package/get-shit-done/workflows/plant-seed.md +172 -0
- package/get-shit-done/workflows/pr-branch.md +157 -0
- package/get-shit-done/workflows/profile-user.md +452 -0
- package/get-shit-done/workflows/progress.md +619 -0
- package/get-shit-done/workflows/quick.md +970 -0
- package/get-shit-done/workflows/remove-phase.md +155 -0
- package/get-shit-done/workflows/remove-workspace.md +92 -0
- package/get-shit-done/workflows/research-phase.md +89 -0
- package/get-shit-done/workflows/resume-project.md +326 -0
- package/get-shit-done/workflows/review.md +344 -0
- package/get-shit-done/workflows/scan.md +102 -0
- package/get-shit-done/workflows/secure-phase.md +166 -0
- package/get-shit-done/workflows/session-report.md +146 -0
- package/get-shit-done/workflows/settings.md +319 -0
- package/get-shit-done/workflows/ship.md +302 -0
- package/get-shit-done/workflows/sketch-wrap-up.md +283 -0
- package/get-shit-done/workflows/sketch.md +286 -0
- package/get-shit-done/workflows/spec-phase.md +262 -0
- package/get-shit-done/workflows/spike-wrap-up.md +281 -0
- package/get-shit-done/workflows/spike.md +362 -0
- package/get-shit-done/workflows/stats.md +60 -0
- package/get-shit-done/workflows/sync-skills.md +182 -0
- package/get-shit-done/workflows/transition.md +693 -0
- package/get-shit-done/workflows/ui-phase.md +323 -0
- package/get-shit-done/workflows/ui-review.md +190 -0
- package/get-shit-done/workflows/ultraplan-phase.md +189 -0
- package/get-shit-done/workflows/undo.md +314 -0
- package/get-shit-done/workflows/update.md +587 -0
- package/get-shit-done/workflows/validate-phase.md +176 -0
- package/get-shit-done/workflows/verify-phase.md +465 -0
- package/get-shit-done/workflows/verify-work.md +740 -0
- package/hooks/dist/gsd-check-update-worker.js +108 -0
- package/hooks/dist/gsd-check-update.js +64 -0
- package/hooks/dist/gsd-context-monitor.js +192 -0
- package/hooks/dist/gsd-phase-boundary.sh +28 -0
- package/hooks/dist/gsd-prompt-guard.js +97 -0
- package/hooks/dist/gsd-read-guard.js +82 -0
- package/hooks/dist/gsd-read-injection-scanner.js +152 -0
- package/hooks/dist/gsd-session-state.sh +34 -0
- package/hooks/dist/gsd-statusline.js +293 -0
- package/hooks/dist/gsd-validate-commit.sh +48 -0
- package/hooks/dist/gsd-workflow-guard.js +94 -0
- package/hooks/gsd-check-update-worker.js +108 -0
- package/hooks/gsd-check-update.js +64 -0
- package/hooks/gsd-context-monitor.js +192 -0
- package/hooks/gsd-phase-boundary.sh +28 -0
- package/hooks/gsd-prompt-guard.js +97 -0
- package/hooks/gsd-read-guard.js +82 -0
- package/hooks/gsd-read-injection-scanner.js +152 -0
- package/hooks/gsd-session-state.sh +34 -0
- package/hooks/gsd-statusline.js +293 -0
- package/hooks/gsd-validate-commit.sh +48 -0
- package/hooks/gsd-workflow-guard.js +94 -0
- package/package.json +59 -0
- package/scripts/base64-scan.sh +262 -0
- package/scripts/build-hooks.js +95 -0
- package/scripts/gen-inventory-manifest.cjs +109 -0
- package/scripts/prompt-injection-scan.sh +201 -0
- package/scripts/run-tests.cjs +33 -0
- package/scripts/secret-scan.sh +227 -0
- package/sdk/package-lock.json +1998 -0
- package/sdk/package.json +52 -0
- package/sdk/prompts/agents/gsd-executor.md +110 -0
- package/sdk/prompts/agents/gsd-phase-researcher.md +158 -0
- package/sdk/prompts/agents/gsd-plan-checker.md +160 -0
- package/sdk/prompts/agents/gsd-planner.md +214 -0
- package/sdk/prompts/agents/gsd-project-researcher.md +323 -0
- package/sdk/prompts/agents/gsd-research-synthesizer.md +237 -0
- package/sdk/prompts/agents/gsd-roadmapper.md +670 -0
- package/sdk/prompts/agents/gsd-verifier.md +159 -0
- package/sdk/prompts/templates/project.md +186 -0
- package/sdk/prompts/templates/requirements.md +231 -0
- package/sdk/prompts/templates/research-project/ARCHITECTURE.md +204 -0
- package/sdk/prompts/templates/research-project/FEATURES.md +147 -0
- package/sdk/prompts/templates/research-project/PITFALLS.md +200 -0
- package/sdk/prompts/templates/research-project/STACK.md +120 -0
- package/sdk/prompts/templates/research-project/SUMMARY.md +170 -0
- package/sdk/prompts/templates/roadmap.md +202 -0
- package/sdk/prompts/templates/state.md +175 -0
- package/sdk/prompts/workflows/discuss-phase.md +126 -0
- package/sdk/prompts/workflows/execute-plan.md +106 -0
- package/sdk/prompts/workflows/plan-phase.md +84 -0
- package/sdk/prompts/workflows/research-phase.md +45 -0
- package/sdk/prompts/workflows/verify-phase.md +142 -0
- package/sdk/src/assembled-prompts.test.ts +349 -0
- package/sdk/src/cli-transport.test.ts +388 -0
- package/sdk/src/cli-transport.ts +130 -0
- package/sdk/src/cli.test.ts +383 -0
- package/sdk/src/cli.ts +670 -0
- package/sdk/src/config.test.ts +168 -0
- package/sdk/src/config.ts +177 -0
- package/sdk/src/context-engine.test.ts +295 -0
- package/sdk/src/context-engine.ts +170 -0
- package/sdk/src/context-truncation.test.ts +163 -0
- package/sdk/src/context-truncation.ts +233 -0
- package/sdk/src/e2e.integration.test.ts +178 -0
- package/sdk/src/errors.ts +72 -0
- package/sdk/src/event-stream.test.ts +661 -0
- package/sdk/src/event-stream.ts +441 -0
- package/sdk/src/failure-memory.test.ts +457 -0
- package/sdk/src/failure-memory.ts +1324 -0
- package/sdk/src/golden/capture.ts +95 -0
- package/sdk/src/golden/fixtures/generate-slug.golden.json +1 -0
- package/sdk/src/golden/fixtures/profile-sample-sessions/demo-project/sample.jsonl +3 -0
- package/sdk/src/golden/fixtures/summary-extract-sample.md +26 -0
- package/sdk/src/golden/fixtures/uat-render-checkpoint-sample.md +15 -0
- package/sdk/src/golden/golden-integration-covered.ts +30 -0
- package/sdk/src/golden/golden-mutation-covered.ts +7 -0
- package/sdk/src/golden/golden-policy.test.ts +8 -0
- package/sdk/src/golden/golden-policy.ts +112 -0
- package/sdk/src/golden/golden.integration.test.ts +373 -0
- package/sdk/src/golden/init-golden-normalize.ts +15 -0
- package/sdk/src/golden/read-only-golden-rows.ts +77 -0
- package/sdk/src/golden/read-only-parity.integration.test.ts +125 -0
- package/sdk/src/golden/registry-canonical-commands.ts +31 -0
- package/sdk/src/gsd-tools.test.ts +409 -0
- package/sdk/src/gsd-tools.ts +595 -0
- package/sdk/src/headless-prompts.test.ts +159 -0
- package/sdk/src/index.ts +333 -0
- package/sdk/src/init-e2e.integration.test.ts +136 -0
- package/sdk/src/init-runner.test.ts +783 -0
- package/sdk/src/init-runner.ts +735 -0
- package/sdk/src/lifecycle-e2e.integration.test.ts +258 -0
- package/sdk/src/logger.test.ts +149 -0
- package/sdk/src/logger.ts +113 -0
- package/sdk/src/milestone-runner.test.ts +421 -0
- package/sdk/src/phase-prompt.test.ts +538 -0
- package/sdk/src/phase-prompt.ts +264 -0
- package/sdk/src/phase-runner-types.test.ts +421 -0
- package/sdk/src/phase-runner.integration.test.ts +377 -0
- package/sdk/src/phase-runner.test.ts +2333 -0
- package/sdk/src/phase-runner.ts +1203 -0
- package/sdk/src/plan-parser.test.ts +528 -0
- package/sdk/src/plan-parser.ts +427 -0
- package/sdk/src/prompt-builder.test.ts +306 -0
- package/sdk/src/prompt-builder.ts +193 -0
- package/sdk/src/prompt-sanitizer.test.ts +260 -0
- package/sdk/src/prompt-sanitizer.ts +71 -0
- package/sdk/src/query/QUERY-HANDLERS.md +317 -0
- package/sdk/src/query/audit-open.ts +722 -0
- package/sdk/src/query/check-auto-mode.test.ts +77 -0
- package/sdk/src/query/check-auto-mode.ts +50 -0
- package/sdk/src/query/check-completion.test.ts +113 -0
- package/sdk/src/query/check-completion.ts +182 -0
- package/sdk/src/query/check-gates.test.ts +103 -0
- package/sdk/src/query/check-gates.ts +112 -0
- package/sdk/src/query/check-ship-ready.test.ts +77 -0
- package/sdk/src/query/check-ship-ready.ts +103 -0
- package/sdk/src/query/check-verification-status.test.ts +143 -0
- package/sdk/src/query/check-verification-status.ts +160 -0
- package/sdk/src/query/commit.test.ts +202 -0
- package/sdk/src/query/commit.ts +301 -0
- package/sdk/src/query/config-gates.test.ts +89 -0
- package/sdk/src/query/config-gates.ts +69 -0
- package/sdk/src/query/config-mutation.test.ts +365 -0
- package/sdk/src/query/config-mutation.ts +497 -0
- package/sdk/src/query/config-query.test.ts +161 -0
- package/sdk/src/query/config-query.ts +190 -0
- package/sdk/src/query/context-history.test.ts +165 -0
- package/sdk/src/query/context-history.ts +467 -0
- package/sdk/src/query/decomposed-handlers.test.ts +365 -0
- package/sdk/src/query/detect-custom-files.ts +97 -0
- package/sdk/src/query/detect-phase-type.test.ts +105 -0
- package/sdk/src/query/detect-phase-type.ts +141 -0
- package/sdk/src/query/docs-init.ts +257 -0
- package/sdk/src/query/failure-capture.ts +58 -0
- package/sdk/src/query/frontmatter-array.test.ts +14 -0
- package/sdk/src/query/frontmatter-mutation.test.ts +259 -0
- package/sdk/src/query/frontmatter-mutation.ts +343 -0
- package/sdk/src/query/frontmatter.test.ts +281 -0
- package/sdk/src/query/frontmatter.ts +397 -0
- package/sdk/src/query/helpers.test.ts +426 -0
- package/sdk/src/query/helpers.ts +482 -0
- package/sdk/src/query/index.ts +586 -0
- package/sdk/src/query/init-complex.test.ts +232 -0
- package/sdk/src/query/init-complex.ts +578 -0
- package/sdk/src/query/init.test.ts +522 -0
- package/sdk/src/query/init.ts +1046 -0
- package/sdk/src/query/intel.test.ts +90 -0
- package/sdk/src/query/intel.ts +404 -0
- package/sdk/src/query/normalize-query-command.test.ts +50 -0
- package/sdk/src/query/normalize-query-command.ts +56 -0
- package/sdk/src/query/phase-lifecycle.test.ts +1126 -0
- package/sdk/src/query/phase-lifecycle.ts +1799 -0
- package/sdk/src/query/phase-list-queries.test.ts +88 -0
- package/sdk/src/query/phase-list-queries.ts +152 -0
- package/sdk/src/query/phase-ready.test.ts +65 -0
- package/sdk/src/query/phase-ready.ts +158 -0
- package/sdk/src/query/phase.test.ts +307 -0
- package/sdk/src/query/phase.ts +340 -0
- package/sdk/src/query/pipeline.test.ts +169 -0
- package/sdk/src/query/pipeline.ts +243 -0
- package/sdk/src/query/plan-execution-route.test.ts +166 -0
- package/sdk/src/query/plan-execution-route.ts +209 -0
- package/sdk/src/query/plan-task-structure.test.ts +65 -0
- package/sdk/src/query/plan-task-structure.ts +63 -0
- package/sdk/src/query/profile-extract-messages.ts +247 -0
- package/sdk/src/query/profile-output.ts +908 -0
- package/sdk/src/query/profile-questionnaire-data.ts +181 -0
- package/sdk/src/query/profile-sample.ts +184 -0
- package/sdk/src/query/profile-scan-sessions.ts +174 -0
- package/sdk/src/query/profile.test.ts +74 -0
- package/sdk/src/query/profile.ts +337 -0
- package/sdk/src/query/progress.test.ts +156 -0
- package/sdk/src/query/progress.ts +566 -0
- package/sdk/src/query/registry.test.ts +216 -0
- package/sdk/src/query/registry.ts +174 -0
- package/sdk/src/query/requirements-extract-from-plans.test.ts +58 -0
- package/sdk/src/query/requirements-extract-from-plans.ts +86 -0
- package/sdk/src/query/roadmap-update-plan-progress.ts +132 -0
- package/sdk/src/query/roadmap.test.ts +359 -0
- package/sdk/src/query/roadmap.ts +591 -0
- package/sdk/src/query/route-next-action.test.ts +61 -0
- package/sdk/src/query/route-next-action.ts +345 -0
- package/sdk/src/query/runtime-health.ts +7 -0
- package/sdk/src/query/schema-detect.ts +189 -0
- package/sdk/src/query/skill-manifest.ts +214 -0
- package/sdk/src/query/skills.test.ts +80 -0
- package/sdk/src/query/skills.ts +62 -0
- package/sdk/src/query/state-mutation.test.ts +450 -0
- package/sdk/src/query/state-mutation.ts +1444 -0
- package/sdk/src/query/state-project-load.ts +109 -0
- package/sdk/src/query/state.test.ts +347 -0
- package/sdk/src/query/state.ts +397 -0
- package/sdk/src/query/summary.test.ts +95 -0
- package/sdk/src/query/summary.ts +296 -0
- package/sdk/src/query/template.test.ts +180 -0
- package/sdk/src/query/template.ts +242 -0
- package/sdk/src/query/uat.test.ts +77 -0
- package/sdk/src/query/uat.ts +314 -0
- package/sdk/src/query/utils.test.ts +82 -0
- package/sdk/src/query/utils.ts +92 -0
- package/sdk/src/query/validate.test.ts +656 -0
- package/sdk/src/query/validate.ts +807 -0
- package/sdk/src/query/verify.test.ts +414 -0
- package/sdk/src/query/verify.ts +645 -0
- package/sdk/src/query/websearch.test.ts +31 -0
- package/sdk/src/query/websearch.ts +82 -0
- package/sdk/src/query/workspace.test.ts +119 -0
- package/sdk/src/query/workspace.ts +131 -0
- package/sdk/src/query/workstream.test.ts +51 -0
- package/sdk/src/query/workstream.ts +434 -0
- package/sdk/src/research-gate.test.ts +190 -0
- package/sdk/src/research-gate.ts +94 -0
- package/sdk/src/runtime-health.test.ts +176 -0
- package/sdk/src/runtime-health.ts +387 -0
- package/sdk/src/session-runner.test.ts +98 -0
- package/sdk/src/session-runner.ts +299 -0
- package/sdk/src/tool-scoping.test.ts +160 -0
- package/sdk/src/tool-scoping.ts +61 -0
- package/sdk/src/types.ts +917 -0
- package/sdk/src/workstream-utils.ts +33 -0
- package/sdk/src/ws-flag.test.ts +285 -0
- package/sdk/src/ws-transport.test.ts +161 -0
- package/sdk/src/ws-transport.ts +93 -0
- package/sdk/tsconfig.json +20 -0
|
@@ -0,0 +1,1046 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Init composition handlers — compound init commands for workflow bootstrapping.
|
|
3
|
+
*
|
|
4
|
+
* Composes existing atomic SDK queries into the same flat JSON bundles
|
|
5
|
+
* that CJS init.cjs produces, enabling workflow migration. Each handler
|
|
6
|
+
* follows the QueryHandler signature and returns { data: <flat JSON> }.
|
|
7
|
+
*
|
|
8
|
+
* Port of get-shit-done/bin/lib/init.cjs (13 of 16 handlers).
|
|
9
|
+
* The 3 complex handlers (new-project, progress, manager) are in init-complex.ts.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { initExecutePhase, withProjectRoot } from './init.js';
|
|
14
|
+
*
|
|
15
|
+
* const result = await initExecutePhase(['9'], '/project');
|
|
16
|
+
* // { data: { executor_model: 'opus', phase_found: true, ... } }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { existsSync, readdirSync, readFileSync, statSync, type Dirent } from 'node:fs';
|
|
21
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
22
|
+
import { join, relative, basename } from 'node:path';
|
|
23
|
+
import { execSync } from 'node:child_process';
|
|
24
|
+
import { homedir } from 'node:os';
|
|
25
|
+
|
|
26
|
+
import { loadConfig, type GSDConfig } from '../config.js';
|
|
27
|
+
import { resolveModel, MODEL_PROFILES } from './config-query.js';
|
|
28
|
+
import { findPhase } from './phase.js';
|
|
29
|
+
import { roadmapGetPhase, getMilestoneInfo } from './roadmap.js';
|
|
30
|
+
import { planningPaths, normalizePhaseName, toPosixPath, resolveAgentsDir, detectRuntime } from './helpers.js';
|
|
31
|
+
import type { QueryHandler } from './utils.js';
|
|
32
|
+
|
|
33
|
+
// ─── Internal helpers ──────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Extract model alias string from a resolveModel result.
|
|
37
|
+
*/
|
|
38
|
+
async function getModelAlias(agentType: string, projectDir: string): Promise<string> {
|
|
39
|
+
const result = await resolveModel([agentType], projectDir);
|
|
40
|
+
const data = result.data as Record<string, unknown>;
|
|
41
|
+
return (data.model as string) || 'sonnet';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Generate a slug from text (inline, matches CJS generateSlugInternal).
|
|
46
|
+
*/
|
|
47
|
+
function generateSlugInternal(text: string): string {
|
|
48
|
+
return text
|
|
49
|
+
.toLowerCase()
|
|
50
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
51
|
+
.replace(/^-+|-+$/g, '')
|
|
52
|
+
.substring(0, 60);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if a path exists on disk.
|
|
57
|
+
*/
|
|
58
|
+
function pathExists(base: string, relPath: string): boolean {
|
|
59
|
+
return existsSync(join(base, relPath));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get the latest completed milestone from MILESTONES.md.
|
|
64
|
+
* Port of getLatestCompletedMilestone from init.cjs lines 10-25.
|
|
65
|
+
*/
|
|
66
|
+
function getLatestCompletedMilestone(projectDir: string): { version: string; name: string } | null {
|
|
67
|
+
const milestonesPath = join(projectDir, '.planning', 'MILESTONES.md');
|
|
68
|
+
if (!existsSync(milestonesPath)) return null;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const content = readFileSync(milestonesPath, 'utf-8');
|
|
72
|
+
const match = content.match(/^##\s+(v[\d.]+)\s+(.+?)\s+\(Shipped:/m);
|
|
73
|
+
if (!match) return null;
|
|
74
|
+
return { version: match[1], name: match[2].trim() };
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check which GSD agents are installed on disk.
|
|
82
|
+
*
|
|
83
|
+
* Runtime-aware per issue #2402: detects the invoking runtime
|
|
84
|
+
* (`GSD_RUNTIME` → `config.runtime` → 'claude') and probes that runtime's
|
|
85
|
+
* canonical `agents/` directory. `GSD_AGENTS_DIR` still short-circuits.
|
|
86
|
+
*
|
|
87
|
+
* Port of checkAgentsInstalled from core.cjs lines 1274-1306.
|
|
88
|
+
*/
|
|
89
|
+
function checkAgentsInstalled(config?: { runtime?: unknown }): { agents_installed: boolean; missing_agents: string[] } {
|
|
90
|
+
const runtime = detectRuntime(config);
|
|
91
|
+
const agentsDir = resolveAgentsDir(runtime);
|
|
92
|
+
const expectedAgents = Object.keys(MODEL_PROFILES);
|
|
93
|
+
|
|
94
|
+
if (!existsSync(agentsDir)) {
|
|
95
|
+
return { agents_installed: false, missing_agents: expectedAgents };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const missing: string[] = [];
|
|
99
|
+
for (const agent of expectedAgents) {
|
|
100
|
+
const agentFile = join(agentsDir, `${agent}.md`);
|
|
101
|
+
const agentFileCopilot = join(agentsDir, `${agent}.agent.md`);
|
|
102
|
+
if (!existsSync(agentFile) && !existsSync(agentFileCopilot)) {
|
|
103
|
+
missing.push(agent);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
agents_installed: missing.length === 0,
|
|
109
|
+
missing_agents: missing,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Extract phase info from findPhase result, or build fallback from roadmap.
|
|
115
|
+
*/
|
|
116
|
+
async function getPhaseInfoWithFallback(
|
|
117
|
+
phase: string,
|
|
118
|
+
projectDir: string,
|
|
119
|
+
): Promise<{ phaseInfo: Record<string, unknown> | null; roadmapPhase: Record<string, unknown> | null }> {
|
|
120
|
+
const phaseResult = await findPhase([phase], projectDir);
|
|
121
|
+
let phaseInfo = phaseResult.data as Record<string, unknown> | null;
|
|
122
|
+
// findPhase returns { found: false } when missing; findPhaseInternal returns null — align for init parity.
|
|
123
|
+
if (phaseInfo && phaseInfo.found === false) {
|
|
124
|
+
phaseInfo = null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const roadmapResult = await roadmapGetPhase([phase], projectDir);
|
|
128
|
+
const roadmapPhase = roadmapResult.data as Record<string, unknown> | null;
|
|
129
|
+
|
|
130
|
+
// Match init.cjs: drop archived disk match when the phase is listed in the current ROADMAP
|
|
131
|
+
if (phaseInfo?.archived && roadmapPhase?.found) {
|
|
132
|
+
phaseInfo = null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Fallback to ROADMAP.md if no phase directory exists yet
|
|
136
|
+
if ((!phaseInfo || !phaseInfo.found) && roadmapPhase?.found) {
|
|
137
|
+
const phaseName = roadmapPhase.phase_name as string;
|
|
138
|
+
phaseInfo = {
|
|
139
|
+
found: true,
|
|
140
|
+
directory: null,
|
|
141
|
+
phase_number: roadmapPhase.phase_number,
|
|
142
|
+
phase_name: phaseName,
|
|
143
|
+
phase_slug: phaseName ? generateSlugInternal(phaseName) : null,
|
|
144
|
+
plans: [],
|
|
145
|
+
summaries: [],
|
|
146
|
+
incomplete_plans: [],
|
|
147
|
+
has_research: false,
|
|
148
|
+
has_context: false,
|
|
149
|
+
has_verification: false,
|
|
150
|
+
has_reviews: false,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return { phaseInfo, roadmapPhase };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Phase resolution for `init verify-work` — matches init.cjs cmdInitVerifyWork (archived + fallback).
|
|
159
|
+
*/
|
|
160
|
+
async function getPhaseInfoForVerifyWork(
|
|
161
|
+
phase: string,
|
|
162
|
+
projectDir: string,
|
|
163
|
+
): Promise<{ phaseInfo: Record<string, unknown> | null }> {
|
|
164
|
+
const phaseResult = await findPhase([phase], projectDir);
|
|
165
|
+
let phaseInfo = phaseResult.data as Record<string, unknown> | null;
|
|
166
|
+
if (phaseInfo && phaseInfo.found === false) {
|
|
167
|
+
phaseInfo = null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const roadmapResult = await roadmapGetPhase([phase], projectDir);
|
|
171
|
+
const roadmapPhase = roadmapResult.data as Record<string, unknown> | null;
|
|
172
|
+
|
|
173
|
+
if (phaseInfo?.archived && roadmapPhase?.found) {
|
|
174
|
+
phaseInfo = null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!phaseInfo && roadmapPhase?.found) {
|
|
178
|
+
const phaseName = roadmapPhase.phase_name as string;
|
|
179
|
+
phaseInfo = {
|
|
180
|
+
found: true,
|
|
181
|
+
directory: null,
|
|
182
|
+
phase_number: roadmapPhase.phase_number,
|
|
183
|
+
phase_name: phaseName,
|
|
184
|
+
phase_slug: phaseName
|
|
185
|
+
? phaseName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')
|
|
186
|
+
: null,
|
|
187
|
+
plans: [],
|
|
188
|
+
summaries: [],
|
|
189
|
+
incomplete_plans: [],
|
|
190
|
+
has_research: false,
|
|
191
|
+
has_context: false,
|
|
192
|
+
has_verification: false,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return { phaseInfo };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Extract requirement IDs from roadmap section text.
|
|
201
|
+
*/
|
|
202
|
+
function extractReqIds(roadmapPhase: Record<string, unknown> | null): string | null {
|
|
203
|
+
const section = roadmapPhase?.section as string | undefined;
|
|
204
|
+
const reqMatch = section?.match(/^\*\*Requirements\*\*:[^\S\n]*([^\n]*)$/m);
|
|
205
|
+
const reqExtracted = reqMatch
|
|
206
|
+
? reqMatch[1].replace(/[\[\]]/g, '').split(',').map((s: string) => s.trim()).filter(Boolean).join(', ')
|
|
207
|
+
: null;
|
|
208
|
+
return (reqExtracted && reqExtracted !== 'TBD') ? reqExtracted : null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ─── withProjectRoot ─────────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Inject project_root, agents_installed, missing_agents, and response_language
|
|
215
|
+
* into an init result object.
|
|
216
|
+
*
|
|
217
|
+
* Port of withProjectRoot from init.cjs lines 32-63.
|
|
218
|
+
*
|
|
219
|
+
* @param projectDir - Absolute project root path
|
|
220
|
+
* @param result - The result object to augment
|
|
221
|
+
* @param config - Optional loaded config (avoids re-reading config.json)
|
|
222
|
+
* @returns The augmented result object
|
|
223
|
+
*/
|
|
224
|
+
export function withProjectRoot(
|
|
225
|
+
projectDir: string,
|
|
226
|
+
result: Record<string, unknown>,
|
|
227
|
+
config?: Record<string, unknown>,
|
|
228
|
+
): Record<string, unknown> {
|
|
229
|
+
result.project_root = projectDir;
|
|
230
|
+
|
|
231
|
+
const agentStatus = checkAgentsInstalled(config);
|
|
232
|
+
result.agents_installed = agentStatus.agents_installed;
|
|
233
|
+
result.missing_agents = agentStatus.missing_agents;
|
|
234
|
+
|
|
235
|
+
const responseLang = config?.response_language;
|
|
236
|
+
if (responseLang) {
|
|
237
|
+
result.response_language = responseLang;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const projectCode = config?.project_code;
|
|
241
|
+
if (projectCode) {
|
|
242
|
+
result.project_code = projectCode;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const projectMdPath = join(projectDir, '.planning', 'PROJECT.md');
|
|
246
|
+
try {
|
|
247
|
+
if (existsSync(projectMdPath)) {
|
|
248
|
+
const content = readFileSync(projectMdPath, 'utf-8');
|
|
249
|
+
const h1Match = content.match(/^#\s+(.+)$/m);
|
|
250
|
+
if (h1Match) {
|
|
251
|
+
result.project_title = h1Match[1].trim();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
} catch {
|
|
255
|
+
/* intentionally empty */
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return result;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ─── initExecutePhase ─────────────────────────────────────────────────────
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Init handler for execute-phase workflow.
|
|
265
|
+
* Port of cmdInitExecutePhase from init.cjs lines 50-171.
|
|
266
|
+
*/
|
|
267
|
+
export const initExecutePhase: QueryHandler = async (args, projectDir) => {
|
|
268
|
+
const phase = args[0];
|
|
269
|
+
if (!phase) {
|
|
270
|
+
return { data: { error: 'phase required for init execute-phase' } };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const config = await loadConfig(projectDir);
|
|
274
|
+
const planningDir = join(projectDir, '.planning');
|
|
275
|
+
|
|
276
|
+
const { phaseInfo, roadmapPhase } = await getPhaseInfoWithFallback(phase, projectDir);
|
|
277
|
+
const phase_req_ids = extractReqIds(roadmapPhase);
|
|
278
|
+
|
|
279
|
+
const [executorModel, verifierModel] = await Promise.all([
|
|
280
|
+
getModelAlias('gsd-executor', projectDir),
|
|
281
|
+
getModelAlias('gsd-verifier', projectDir),
|
|
282
|
+
]);
|
|
283
|
+
|
|
284
|
+
const milestone = await getMilestoneInfo(projectDir);
|
|
285
|
+
|
|
286
|
+
const phaseNumber = (phaseInfo?.phase_number as string) || null;
|
|
287
|
+
const phaseSlug = (phaseInfo?.phase_slug as string) || null;
|
|
288
|
+
const plans = (phaseInfo?.plans || []) as string[];
|
|
289
|
+
const summaries = (phaseInfo?.summaries || []) as string[];
|
|
290
|
+
const incompletePlans = (phaseInfo?.incomplete_plans || []) as string[];
|
|
291
|
+
const projectCode = (config as Record<string, unknown>).project_code as string || '';
|
|
292
|
+
|
|
293
|
+
const result: Record<string, unknown> = {
|
|
294
|
+
executor_model: executorModel,
|
|
295
|
+
verifier_model: verifierModel,
|
|
296
|
+
tdd_mode: config.workflow.tdd_mode ?? false,
|
|
297
|
+
commit_docs: config.commit_docs,
|
|
298
|
+
sub_repos: (config as Record<string, unknown>).sub_repos ?? [],
|
|
299
|
+
parallelization: config.parallelization,
|
|
300
|
+
context_window: (config as Record<string, unknown>).context_window ?? 200000,
|
|
301
|
+
branching_strategy: config.git.branching_strategy,
|
|
302
|
+
phase_branch_template: config.git.phase_branch_template,
|
|
303
|
+
milestone_branch_template: config.git.milestone_branch_template,
|
|
304
|
+
verifier_enabled: config.workflow.verifier,
|
|
305
|
+
phase_found: !!phaseInfo,
|
|
306
|
+
phase_dir: (phaseInfo?.directory as string) ?? null,
|
|
307
|
+
phase_number: phaseNumber,
|
|
308
|
+
phase_name: (phaseInfo?.phase_name as string) ?? null,
|
|
309
|
+
phase_slug: phaseSlug,
|
|
310
|
+
phase_req_ids,
|
|
311
|
+
plans,
|
|
312
|
+
summaries,
|
|
313
|
+
incomplete_plans: incompletePlans,
|
|
314
|
+
plan_count: plans.length,
|
|
315
|
+
incomplete_count: incompletePlans.length,
|
|
316
|
+
branch_name: config.git.branching_strategy === 'phase' && phaseInfo
|
|
317
|
+
? config.git.phase_branch_template
|
|
318
|
+
.replace('{project}', projectCode)
|
|
319
|
+
.replace('{phase}', phaseNumber || '')
|
|
320
|
+
.replace('{slug}', phaseSlug || 'phase')
|
|
321
|
+
: config.git.branching_strategy === 'milestone'
|
|
322
|
+
? config.git.milestone_branch_template
|
|
323
|
+
.replace('{milestone}', milestone.version)
|
|
324
|
+
.replace('{slug}', generateSlugInternal(milestone.name) || 'milestone')
|
|
325
|
+
: null,
|
|
326
|
+
milestone_version: milestone.version,
|
|
327
|
+
milestone_name: milestone.name,
|
|
328
|
+
milestone_slug: generateSlugInternal(milestone.name),
|
|
329
|
+
state_exists: existsSync(join(planningDir, 'STATE.md')),
|
|
330
|
+
roadmap_exists: existsSync(join(planningDir, 'ROADMAP.md')),
|
|
331
|
+
config_exists: existsSync(join(planningDir, 'config.json')),
|
|
332
|
+
state_path: toPosixPath(relative(projectDir, join(planningDir, 'STATE.md'))),
|
|
333
|
+
roadmap_path: toPosixPath(relative(projectDir, join(planningDir, 'ROADMAP.md'))),
|
|
334
|
+
config_path: toPosixPath(relative(projectDir, join(planningDir, 'config.json'))),
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
return { data: withProjectRoot(projectDir, result, config as Record<string, unknown>) };
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
// ─── initPlanPhase ────────────────────────────────────────────────────────
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Init handler for plan-phase workflow.
|
|
344
|
+
* Port of cmdInitPlanPhase from init.cjs lines 173-293.
|
|
345
|
+
*/
|
|
346
|
+
export const initPlanPhase: QueryHandler = async (args, projectDir) => {
|
|
347
|
+
const phase = args[0];
|
|
348
|
+
if (!phase) {
|
|
349
|
+
return { data: { error: 'phase required for init plan-phase' } };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const config = await loadConfig(projectDir);
|
|
353
|
+
const planningDir = join(projectDir, '.planning');
|
|
354
|
+
|
|
355
|
+
const { phaseInfo, roadmapPhase } = await getPhaseInfoWithFallback(phase, projectDir);
|
|
356
|
+
const phase_req_ids = extractReqIds(roadmapPhase);
|
|
357
|
+
|
|
358
|
+
const [researcherModel, plannerModel, checkerModel] = await Promise.all([
|
|
359
|
+
getModelAlias('gsd-phase-researcher', projectDir),
|
|
360
|
+
getModelAlias('gsd-planner', projectDir),
|
|
361
|
+
getModelAlias('gsd-plan-checker', projectDir),
|
|
362
|
+
]);
|
|
363
|
+
|
|
364
|
+
const phaseNumber = (phaseInfo?.phase_number as string) || null;
|
|
365
|
+
const plans = (phaseInfo?.plans || []) as string[];
|
|
366
|
+
|
|
367
|
+
const cfg = config as GSDConfig;
|
|
368
|
+
const result: Record<string, unknown> = {
|
|
369
|
+
researcher_model: researcherModel,
|
|
370
|
+
planner_model: plannerModel,
|
|
371
|
+
checker_model: checkerModel,
|
|
372
|
+
tdd_mode: config.workflow.tdd_mode ?? false,
|
|
373
|
+
research_enabled: config.workflow.research,
|
|
374
|
+
plan_checker_enabled: config.workflow.plan_check,
|
|
375
|
+
nyquist_validation_enabled: config.workflow.nyquist_validation,
|
|
376
|
+
commit_docs: config.commit_docs,
|
|
377
|
+
text_mode: config.workflow.text_mode,
|
|
378
|
+
auto_advance: !!config.workflow.auto_advance,
|
|
379
|
+
auto_chain_active: !!cfg._auto_chain_active,
|
|
380
|
+
mode: cfg.mode ?? 'interactive',
|
|
381
|
+
phase_found: !!phaseInfo,
|
|
382
|
+
phase_dir: (phaseInfo?.directory as string) ?? null,
|
|
383
|
+
phase_number: phaseNumber,
|
|
384
|
+
phase_name: (phaseInfo?.phase_name as string) ?? null,
|
|
385
|
+
phase_slug: (phaseInfo?.phase_slug as string) ?? null,
|
|
386
|
+
padded_phase: phaseNumber ? normalizePhaseName(phaseNumber) : null,
|
|
387
|
+
phase_req_ids,
|
|
388
|
+
has_research: (phaseInfo?.has_research as boolean) || false,
|
|
389
|
+
has_context: (phaseInfo?.has_context as boolean) || false,
|
|
390
|
+
has_reviews: (phaseInfo?.has_reviews as boolean) || false,
|
|
391
|
+
has_plans: plans.length > 0,
|
|
392
|
+
plan_count: plans.length,
|
|
393
|
+
planning_exists: existsSync(planningDir),
|
|
394
|
+
roadmap_exists: existsSync(join(planningDir, 'ROADMAP.md')),
|
|
395
|
+
state_path: toPosixPath(relative(projectDir, join(planningDir, 'STATE.md'))),
|
|
396
|
+
roadmap_path: toPosixPath(relative(projectDir, join(planningDir, 'ROADMAP.md'))),
|
|
397
|
+
requirements_path: toPosixPath(relative(projectDir, join(planningDir, 'REQUIREMENTS.md'))),
|
|
398
|
+
patterns_path: null,
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
// Add artifact paths if phase directory exists
|
|
402
|
+
if (phaseInfo?.directory) {
|
|
403
|
+
const phaseDirFull = join(projectDir, phaseInfo.directory as string);
|
|
404
|
+
try {
|
|
405
|
+
const files = readdirSync(phaseDirFull);
|
|
406
|
+
const contextFile = files.find(f => f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md');
|
|
407
|
+
if (contextFile) result.context_path = toPosixPath(join(phaseInfo.directory as string, contextFile));
|
|
408
|
+
const researchFile = files.find(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
|
|
409
|
+
if (researchFile) result.research_path = toPosixPath(join(phaseInfo.directory as string, researchFile));
|
|
410
|
+
const verificationFile = files.find(f => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md');
|
|
411
|
+
if (verificationFile) result.verification_path = toPosixPath(join(phaseInfo.directory as string, verificationFile));
|
|
412
|
+
const uatFile = files.find(f => f.endsWith('-UAT.md') || f === 'UAT.md');
|
|
413
|
+
if (uatFile) result.uat_path = toPosixPath(join(phaseInfo.directory as string, uatFile));
|
|
414
|
+
const reviewsFile = files.find(f => f.endsWith('-REVIEWS.md') || f === 'REVIEWS.md');
|
|
415
|
+
if (reviewsFile) result.reviews_path = toPosixPath(join(phaseInfo.directory as string, reviewsFile));
|
|
416
|
+
const patternsFile = files.find(f => f.endsWith('-PATTERNS.md') || f === 'PATTERNS.md');
|
|
417
|
+
if (patternsFile) result.patterns_path = toPosixPath(join(phaseInfo.directory as string, patternsFile));
|
|
418
|
+
} catch { /* intentionally empty */ }
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return { data: withProjectRoot(projectDir, result, config as Record<string, unknown>) };
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
// ─── initNewMilestone ─────────────────────────────────────────────────────
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Init handler for new-milestone workflow.
|
|
428
|
+
* Port of cmdInitNewMilestone from init.cjs lines 401-446.
|
|
429
|
+
*/
|
|
430
|
+
export const initNewMilestone: QueryHandler = async (_args, projectDir) => {
|
|
431
|
+
const config = await loadConfig(projectDir);
|
|
432
|
+
const planningDir = join(projectDir, '.planning');
|
|
433
|
+
const milestone = await getMilestoneInfo(projectDir);
|
|
434
|
+
const latestCompleted = getLatestCompletedMilestone(projectDir);
|
|
435
|
+
|
|
436
|
+
const phasesDir = join(planningDir, 'phases');
|
|
437
|
+
let phaseDirCount = 0;
|
|
438
|
+
try {
|
|
439
|
+
if (existsSync(phasesDir)) {
|
|
440
|
+
phaseDirCount = readdirSync(phasesDir, { withFileTypes: true })
|
|
441
|
+
.filter(entry => entry.isDirectory())
|
|
442
|
+
.length;
|
|
443
|
+
}
|
|
444
|
+
} catch { /* intentionally empty */ }
|
|
445
|
+
|
|
446
|
+
const [researcherModel, synthesizerModel, roadmapperModel] = await Promise.all([
|
|
447
|
+
getModelAlias('gsd-project-researcher', projectDir),
|
|
448
|
+
getModelAlias('gsd-research-synthesizer', projectDir),
|
|
449
|
+
getModelAlias('gsd-roadmapper', projectDir),
|
|
450
|
+
]);
|
|
451
|
+
|
|
452
|
+
const result: Record<string, unknown> = {
|
|
453
|
+
researcher_model: researcherModel,
|
|
454
|
+
synthesizer_model: synthesizerModel,
|
|
455
|
+
roadmapper_model: roadmapperModel,
|
|
456
|
+
commit_docs: config.commit_docs,
|
|
457
|
+
research_enabled: config.workflow.research,
|
|
458
|
+
current_milestone: milestone.version,
|
|
459
|
+
current_milestone_name: milestone.name,
|
|
460
|
+
latest_completed_milestone: latestCompleted?.version || null,
|
|
461
|
+
latest_completed_milestone_name: latestCompleted?.name || null,
|
|
462
|
+
phase_dir_count: phaseDirCount,
|
|
463
|
+
phase_archive_path: latestCompleted
|
|
464
|
+
? toPosixPath(relative(projectDir, join(projectDir, '.planning', 'milestones', `${latestCompleted.version}-phases`)))
|
|
465
|
+
: null,
|
|
466
|
+
project_exists: pathExists(projectDir, '.planning/PROJECT.md'),
|
|
467
|
+
roadmap_exists: existsSync(join(planningDir, 'ROADMAP.md')),
|
|
468
|
+
state_exists: existsSync(join(planningDir, 'STATE.md')),
|
|
469
|
+
project_path: '.planning/PROJECT.md',
|
|
470
|
+
roadmap_path: toPosixPath(relative(projectDir, join(planningDir, 'ROADMAP.md'))),
|
|
471
|
+
state_path: toPosixPath(relative(projectDir, join(planningDir, 'STATE.md'))),
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
return { data: withProjectRoot(projectDir, result, config as Record<string, unknown>) };
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// ─── initQuick ────────────────────────────────────────────────────────────
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Init handler for quick workflow.
|
|
481
|
+
* Port of cmdInitQuick from init.cjs lines 448-504.
|
|
482
|
+
*/
|
|
483
|
+
export const initQuick: QueryHandler = async (args, projectDir) => {
|
|
484
|
+
const description = args[0] || null;
|
|
485
|
+
const config = await loadConfig(projectDir);
|
|
486
|
+
const planningDir = join(projectDir, '.planning');
|
|
487
|
+
const now = new Date();
|
|
488
|
+
const slug = description ? generateSlugInternal(description).substring(0, 40) : null;
|
|
489
|
+
|
|
490
|
+
// Generate collision-resistant quick task ID: YYMMDD-xxx
|
|
491
|
+
const yy = String(now.getFullYear()).slice(-2);
|
|
492
|
+
const mm = String(now.getMonth() + 1).padStart(2, '0');
|
|
493
|
+
const dd = String(now.getDate()).padStart(2, '0');
|
|
494
|
+
const dateStr = yy + mm + dd;
|
|
495
|
+
const secondsSinceMidnight = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds();
|
|
496
|
+
const timeBlocks = Math.floor(secondsSinceMidnight / 2);
|
|
497
|
+
const timeEncoded = timeBlocks.toString(36).padStart(3, '0');
|
|
498
|
+
const quickId = dateStr + '-' + timeEncoded;
|
|
499
|
+
const branchSlug = slug || 'quick';
|
|
500
|
+
const quickBranchName = config.git.quick_branch_template
|
|
501
|
+
? config.git.quick_branch_template
|
|
502
|
+
.replace('{num}', quickId)
|
|
503
|
+
.replace('{quick}', quickId)
|
|
504
|
+
.replace('{slug}', branchSlug)
|
|
505
|
+
: null;
|
|
506
|
+
|
|
507
|
+
const [plannerModel, executorModel, checkerModel, verifierModel] = await Promise.all([
|
|
508
|
+
getModelAlias('gsd-planner', projectDir),
|
|
509
|
+
getModelAlias('gsd-executor', projectDir),
|
|
510
|
+
getModelAlias('gsd-plan-checker', projectDir),
|
|
511
|
+
getModelAlias('gsd-verifier', projectDir),
|
|
512
|
+
]);
|
|
513
|
+
|
|
514
|
+
const result: Record<string, unknown> = {
|
|
515
|
+
planner_model: plannerModel,
|
|
516
|
+
executor_model: executorModel,
|
|
517
|
+
checker_model: checkerModel,
|
|
518
|
+
verifier_model: verifierModel,
|
|
519
|
+
commit_docs: config.commit_docs,
|
|
520
|
+
branch_name: quickBranchName,
|
|
521
|
+
quick_id: quickId,
|
|
522
|
+
slug,
|
|
523
|
+
description,
|
|
524
|
+
date: now.toISOString().split('T')[0],
|
|
525
|
+
timestamp: now.toISOString(),
|
|
526
|
+
quick_dir: '.planning/quick',
|
|
527
|
+
task_dir: slug ? `.planning/quick/${quickId}-${slug}` : null,
|
|
528
|
+
roadmap_exists: existsSync(join(planningDir, 'ROADMAP.md')),
|
|
529
|
+
planning_exists: existsSync(join(projectDir, '.planning')),
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
return { data: withProjectRoot(projectDir, result, config as Record<string, unknown>) };
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
// ─── initResume ───────────────────────────────────────────────────────────
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Init handler for resume-project workflow.
|
|
539
|
+
* Port of cmdInitResume from init.cjs lines 506-536.
|
|
540
|
+
*/
|
|
541
|
+
export const initResume: QueryHandler = async (_args, projectDir) => {
|
|
542
|
+
const config = await loadConfig(projectDir);
|
|
543
|
+
const planningDir = join(projectDir, '.planning');
|
|
544
|
+
|
|
545
|
+
let interruptedAgentId: string | null = null;
|
|
546
|
+
try {
|
|
547
|
+
interruptedAgentId = readFileSync(join(projectDir, '.planning', 'current-agent-id.txt'), 'utf-8').trim();
|
|
548
|
+
} catch { /* intentionally empty */ }
|
|
549
|
+
|
|
550
|
+
const result: Record<string, unknown> = {
|
|
551
|
+
state_exists: existsSync(join(planningDir, 'STATE.md')),
|
|
552
|
+
roadmap_exists: existsSync(join(planningDir, 'ROADMAP.md')),
|
|
553
|
+
project_exists: pathExists(projectDir, '.planning/PROJECT.md'),
|
|
554
|
+
planning_exists: existsSync(join(projectDir, '.planning')),
|
|
555
|
+
state_path: toPosixPath(relative(projectDir, join(planningDir, 'STATE.md'))),
|
|
556
|
+
roadmap_path: toPosixPath(relative(projectDir, join(planningDir, 'ROADMAP.md'))),
|
|
557
|
+
project_path: '.planning/PROJECT.md',
|
|
558
|
+
has_interrupted_agent: !!interruptedAgentId,
|
|
559
|
+
interrupted_agent_id: interruptedAgentId,
|
|
560
|
+
commit_docs: config.commit_docs,
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
return { data: withProjectRoot(projectDir, result, config as Record<string, unknown>) };
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
// ─── initVerifyWork ───────────────────────────────────────────────────────
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Init handler for verify-work workflow.
|
|
570
|
+
* Port of cmdInitVerifyWork from init.cjs lines 538-586.
|
|
571
|
+
*/
|
|
572
|
+
export const initVerifyWork: QueryHandler = async (args, projectDir) => {
|
|
573
|
+
const phase = args[0];
|
|
574
|
+
if (!phase) {
|
|
575
|
+
return { data: { error: 'phase required for init verify-work' } };
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const config = await loadConfig(projectDir);
|
|
579
|
+
const { phaseInfo } = await getPhaseInfoForVerifyWork(phase, projectDir);
|
|
580
|
+
|
|
581
|
+
const [plannerModel, checkerModel] = await Promise.all([
|
|
582
|
+
getModelAlias('gsd-planner', projectDir),
|
|
583
|
+
getModelAlias('gsd-plan-checker', projectDir),
|
|
584
|
+
]);
|
|
585
|
+
|
|
586
|
+
const result: Record<string, unknown> = {
|
|
587
|
+
planner_model: plannerModel,
|
|
588
|
+
checker_model: checkerModel,
|
|
589
|
+
commit_docs: config.commit_docs,
|
|
590
|
+
phase_found: !!phaseInfo,
|
|
591
|
+
phase_dir: (phaseInfo?.directory as string) ?? null,
|
|
592
|
+
phase_number: (phaseInfo?.phase_number as string) ?? null,
|
|
593
|
+
phase_name: (phaseInfo?.phase_name as string) ?? null,
|
|
594
|
+
has_verification: (phaseInfo?.has_verification as boolean) || false,
|
|
595
|
+
};
|
|
596
|
+
|
|
597
|
+
return { data: withProjectRoot(projectDir, result, config as Record<string, unknown>) };
|
|
598
|
+
};
|
|
599
|
+
|
|
600
|
+
// ─── initPhaseOp ──────────────────────────────────────────────────────────
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Init handler for discuss-phase and similar phase operations.
|
|
604
|
+
* Port of cmdInitPhaseOp from init.cjs lines 588-697.
|
|
605
|
+
*/
|
|
606
|
+
export const initPhaseOp: QueryHandler = async (args, projectDir) => {
|
|
607
|
+
const phase = args[0];
|
|
608
|
+
if (!phase) {
|
|
609
|
+
return { data: { error: 'phase required for init phase-op' } };
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const config = await loadConfig(projectDir);
|
|
613
|
+
const planningDir = join(projectDir, '.planning');
|
|
614
|
+
|
|
615
|
+
// findPhase with archived override: if only match is archived, prefer ROADMAP
|
|
616
|
+
const phaseResult = await findPhase([phase], projectDir);
|
|
617
|
+
let phaseInfo = phaseResult.data as Record<string, unknown> | null;
|
|
618
|
+
|
|
619
|
+
const roadmapResult = await roadmapGetPhase([phase], projectDir);
|
|
620
|
+
const roadmapPhase = roadmapResult.data as Record<string, unknown> | null;
|
|
621
|
+
|
|
622
|
+
// If the only match comes from an archived milestone, prefer current ROADMAP
|
|
623
|
+
if (phaseInfo?.archived && roadmapPhase?.found) {
|
|
624
|
+
const phaseName = roadmapPhase.phase_name as string;
|
|
625
|
+
phaseInfo = {
|
|
626
|
+
found: true,
|
|
627
|
+
directory: null,
|
|
628
|
+
phase_number: roadmapPhase.phase_number,
|
|
629
|
+
phase_name: phaseName,
|
|
630
|
+
phase_slug: phaseName ? generateSlugInternal(phaseName) : null,
|
|
631
|
+
plans: [],
|
|
632
|
+
summaries: [],
|
|
633
|
+
incomplete_plans: [],
|
|
634
|
+
has_research: false,
|
|
635
|
+
has_context: false,
|
|
636
|
+
has_verification: false,
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Fallback to ROADMAP.md if no directory exists
|
|
641
|
+
if (!phaseInfo || !phaseInfo.found) {
|
|
642
|
+
if (roadmapPhase?.found) {
|
|
643
|
+
const phaseName = roadmapPhase.phase_name as string;
|
|
644
|
+
phaseInfo = {
|
|
645
|
+
found: true,
|
|
646
|
+
directory: null,
|
|
647
|
+
phase_number: roadmapPhase.phase_number,
|
|
648
|
+
phase_name: phaseName,
|
|
649
|
+
phase_slug: phaseName ? generateSlugInternal(phaseName) : null,
|
|
650
|
+
plans: [],
|
|
651
|
+
summaries: [],
|
|
652
|
+
incomplete_plans: [],
|
|
653
|
+
has_research: false,
|
|
654
|
+
has_context: false,
|
|
655
|
+
has_verification: false,
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const phaseFound = !!(phaseInfo && phaseInfo.found);
|
|
661
|
+
const phaseNumber = (phaseInfo?.phase_number as string) || null;
|
|
662
|
+
const plans = (phaseInfo?.plans || []) as string[];
|
|
663
|
+
|
|
664
|
+
const result: Record<string, unknown> = {
|
|
665
|
+
commit_docs: config.commit_docs,
|
|
666
|
+
brave_search: config.brave_search,
|
|
667
|
+
firecrawl: config.firecrawl,
|
|
668
|
+
exa_search: config.exa_search,
|
|
669
|
+
phase_found: phaseFound,
|
|
670
|
+
phase_dir: (phaseInfo?.directory as string) ?? null,
|
|
671
|
+
phase_number: phaseNumber,
|
|
672
|
+
phase_name: (phaseInfo?.phase_name as string) ?? null,
|
|
673
|
+
phase_slug: (phaseInfo?.phase_slug as string) ?? null,
|
|
674
|
+
padded_phase: phaseNumber ? normalizePhaseName(phaseNumber) : null,
|
|
675
|
+
has_research: (phaseInfo?.has_research as boolean) || false,
|
|
676
|
+
has_context: (phaseInfo?.has_context as boolean) || false,
|
|
677
|
+
has_plans: plans.length > 0,
|
|
678
|
+
has_verification: (phaseInfo?.has_verification as boolean) || false,
|
|
679
|
+
has_reviews: (phaseInfo?.has_reviews as boolean) || false,
|
|
680
|
+
plan_count: plans.length,
|
|
681
|
+
roadmap_exists: existsSync(join(planningDir, 'ROADMAP.md')),
|
|
682
|
+
planning_exists: existsSync(planningDir),
|
|
683
|
+
state_path: toPosixPath(relative(projectDir, join(planningDir, 'STATE.md'))),
|
|
684
|
+
roadmap_path: toPosixPath(relative(projectDir, join(planningDir, 'ROADMAP.md'))),
|
|
685
|
+
requirements_path: toPosixPath(relative(projectDir, join(planningDir, 'REQUIREMENTS.md'))),
|
|
686
|
+
};
|
|
687
|
+
|
|
688
|
+
// Add artifact paths if phase directory exists
|
|
689
|
+
if (phaseInfo?.directory) {
|
|
690
|
+
const phaseDirFull = join(projectDir, phaseInfo.directory as string);
|
|
691
|
+
try {
|
|
692
|
+
const files = readdirSync(phaseDirFull);
|
|
693
|
+
const contextFile = files.find(f => f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md');
|
|
694
|
+
if (contextFile) result.context_path = toPosixPath(join(phaseInfo.directory as string, contextFile));
|
|
695
|
+
const researchFile = files.find(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
|
|
696
|
+
if (researchFile) result.research_path = toPosixPath(join(phaseInfo.directory as string, researchFile));
|
|
697
|
+
const verificationFile = files.find(f => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md');
|
|
698
|
+
if (verificationFile) result.verification_path = toPosixPath(join(phaseInfo.directory as string, verificationFile));
|
|
699
|
+
const uatFile = files.find(f => f.endsWith('-UAT.md') || f === 'UAT.md');
|
|
700
|
+
if (uatFile) result.uat_path = toPosixPath(join(phaseInfo.directory as string, uatFile));
|
|
701
|
+
const reviewsFile = files.find(f => f.endsWith('-REVIEWS.md') || f === 'REVIEWS.md');
|
|
702
|
+
if (reviewsFile) result.reviews_path = toPosixPath(join(phaseInfo.directory as string, reviewsFile));
|
|
703
|
+
} catch { /* intentionally empty */ }
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
return { data: withProjectRoot(projectDir, result, config as Record<string, unknown>) };
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
// ─── initTodos ────────────────────────────────────────────────────────────
|
|
710
|
+
|
|
711
|
+
/**
|
|
712
|
+
* Init handler for check-todos and add-todo workflows.
|
|
713
|
+
* Port of cmdInitTodos from init.cjs lines 699-756.
|
|
714
|
+
*/
|
|
715
|
+
export const initTodos: QueryHandler = async (args, projectDir) => {
|
|
716
|
+
const area = args[0] || null;
|
|
717
|
+
const config = await loadConfig(projectDir);
|
|
718
|
+
const planningDir = join(projectDir, '.planning');
|
|
719
|
+
const now = new Date();
|
|
720
|
+
|
|
721
|
+
const pendingDir = join(planningDir, 'todos', 'pending');
|
|
722
|
+
let count = 0;
|
|
723
|
+
const todos: Array<Record<string, unknown>> = [];
|
|
724
|
+
|
|
725
|
+
try {
|
|
726
|
+
const files = readdirSync(pendingDir).filter(f => f.endsWith('.md'));
|
|
727
|
+
for (const file of files) {
|
|
728
|
+
try {
|
|
729
|
+
const content = readFileSync(join(pendingDir, file), 'utf-8');
|
|
730
|
+
const createdMatch = content.match(/^created:\s*(.+)$/m);
|
|
731
|
+
const titleMatch = content.match(/^title:\s*(.+)$/m);
|
|
732
|
+
const areaMatch = content.match(/^area:\s*(.+)$/m);
|
|
733
|
+
const todoArea = areaMatch ? areaMatch[1].trim() : 'general';
|
|
734
|
+
|
|
735
|
+
if (area && todoArea !== area) continue;
|
|
736
|
+
|
|
737
|
+
count++;
|
|
738
|
+
todos.push({
|
|
739
|
+
file,
|
|
740
|
+
created: createdMatch ? createdMatch[1].trim() : 'unknown',
|
|
741
|
+
title: titleMatch ? titleMatch[1].trim() : 'Untitled',
|
|
742
|
+
area: todoArea,
|
|
743
|
+
path: toPosixPath(relative(projectDir, join(pendingDir, file))),
|
|
744
|
+
});
|
|
745
|
+
} catch { /* intentionally empty */ }
|
|
746
|
+
}
|
|
747
|
+
} catch { /* intentionally empty */ }
|
|
748
|
+
|
|
749
|
+
const result: Record<string, unknown> = {
|
|
750
|
+
commit_docs: config.commit_docs,
|
|
751
|
+
date: now.toISOString().split('T')[0],
|
|
752
|
+
timestamp: now.toISOString(),
|
|
753
|
+
todo_count: count,
|
|
754
|
+
todos,
|
|
755
|
+
area_filter: area,
|
|
756
|
+
pending_dir: toPosixPath(relative(projectDir, join(planningDir, 'todos', 'pending'))),
|
|
757
|
+
completed_dir: toPosixPath(relative(projectDir, join(planningDir, 'todos', 'completed'))),
|
|
758
|
+
planning_exists: existsSync(planningDir),
|
|
759
|
+
todos_dir_exists: existsSync(join(planningDir, 'todos')),
|
|
760
|
+
pending_dir_exists: existsSync(pendingDir),
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
return { data: withProjectRoot(projectDir, result, config as Record<string, unknown>) };
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
// ─── initMilestoneOp ─────────────────────────────────────────────────────
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Init handler for complete-milestone and audit-milestone workflows.
|
|
770
|
+
* Port of cmdInitMilestoneOp from init.cjs lines 758-817.
|
|
771
|
+
*/
|
|
772
|
+
export const initMilestoneOp: QueryHandler = async (_args, projectDir) => {
|
|
773
|
+
const config = await loadConfig(projectDir);
|
|
774
|
+
const planningDir = join(projectDir, '.planning');
|
|
775
|
+
const milestone = await getMilestoneInfo(projectDir);
|
|
776
|
+
|
|
777
|
+
const phasesDir = join(planningDir, 'phases');
|
|
778
|
+
let phaseCount = 0;
|
|
779
|
+
let completedPhases = 0;
|
|
780
|
+
|
|
781
|
+
try {
|
|
782
|
+
const entries = readdirSync(phasesDir, { withFileTypes: true });
|
|
783
|
+
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
784
|
+
phaseCount = dirs.length;
|
|
785
|
+
|
|
786
|
+
for (const dir of dirs) {
|
|
787
|
+
try {
|
|
788
|
+
const phaseFiles = readdirSync(join(phasesDir, dir));
|
|
789
|
+
const hasSummary = phaseFiles.some(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
|
|
790
|
+
if (hasSummary) completedPhases++;
|
|
791
|
+
} catch { /* intentionally empty */ }
|
|
792
|
+
}
|
|
793
|
+
} catch { /* intentionally empty */ }
|
|
794
|
+
|
|
795
|
+
const archiveDir = join(projectDir, '.planning', 'archive');
|
|
796
|
+
let archivedMilestones: string[] = [];
|
|
797
|
+
try {
|
|
798
|
+
archivedMilestones = readdirSync(archiveDir, { withFileTypes: true })
|
|
799
|
+
.filter(e => e.isDirectory())
|
|
800
|
+
.map(e => e.name);
|
|
801
|
+
} catch { /* intentionally empty */ }
|
|
802
|
+
|
|
803
|
+
const result: Record<string, unknown> = {
|
|
804
|
+
commit_docs: config.commit_docs,
|
|
805
|
+
milestone_version: milestone.version,
|
|
806
|
+
milestone_name: milestone.name,
|
|
807
|
+
milestone_slug: generateSlugInternal(milestone.name),
|
|
808
|
+
phase_count: phaseCount,
|
|
809
|
+
completed_phases: completedPhases,
|
|
810
|
+
all_phases_complete: phaseCount > 0 && phaseCount === completedPhases,
|
|
811
|
+
archived_milestones: archivedMilestones,
|
|
812
|
+
archive_count: archivedMilestones.length,
|
|
813
|
+
project_exists: pathExists(projectDir, '.planning/PROJECT.md'),
|
|
814
|
+
roadmap_exists: existsSync(join(planningDir, 'ROADMAP.md')),
|
|
815
|
+
state_exists: existsSync(join(planningDir, 'STATE.md')),
|
|
816
|
+
archive_exists: existsSync(archiveDir),
|
|
817
|
+
phases_dir_exists: existsSync(phasesDir),
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
return { data: withProjectRoot(projectDir, result, config as Record<string, unknown>) };
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
// ─── initMapCodebase ──────────────────────────────────────────────────────
|
|
824
|
+
|
|
825
|
+
/**
|
|
826
|
+
* Init handler for map-codebase workflow.
|
|
827
|
+
* Port of cmdInitMapCodebase from init.cjs lines 819-852.
|
|
828
|
+
*/
|
|
829
|
+
export const initMapCodebase: QueryHandler = async (_args, projectDir) => {
|
|
830
|
+
const config = await loadConfig(projectDir);
|
|
831
|
+
const now = new Date();
|
|
832
|
+
const codebaseDir = join(projectDir, '.planning', 'codebase');
|
|
833
|
+
let existingMaps: string[] = [];
|
|
834
|
+
try {
|
|
835
|
+
existingMaps = readdirSync(codebaseDir).filter(f => f.endsWith('.md'));
|
|
836
|
+
} catch { /* intentionally empty */ }
|
|
837
|
+
|
|
838
|
+
const mapperModel = await getModelAlias('gsd-codebase-mapper', projectDir);
|
|
839
|
+
|
|
840
|
+
const result: Record<string, unknown> = {
|
|
841
|
+
mapper_model: mapperModel,
|
|
842
|
+
commit_docs: config.commit_docs,
|
|
843
|
+
search_gitignored: config.search_gitignored,
|
|
844
|
+
parallelization: config.parallelization,
|
|
845
|
+
subagent_timeout: (config as Record<string, unknown>).subagent_timeout ?? undefined,
|
|
846
|
+
date: now.toISOString().split('T')[0],
|
|
847
|
+
timestamp: now.toISOString(),
|
|
848
|
+
codebase_dir: '.planning/codebase',
|
|
849
|
+
existing_maps: existingMaps,
|
|
850
|
+
has_maps: existingMaps.length > 0,
|
|
851
|
+
planning_exists: pathExists(projectDir, '.planning'),
|
|
852
|
+
codebase_dir_exists: pathExists(projectDir, '.planning/codebase'),
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
return { data: withProjectRoot(projectDir, result, config as Record<string, unknown>) };
|
|
856
|
+
};
|
|
857
|
+
|
|
858
|
+
// ─── initNewWorkspace ─────────────────────────────────────────────────────
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Init handler for new-workspace workflow.
|
|
862
|
+
* Port of cmdInitNewWorkspace from init.cjs lines 1311-1335.
|
|
863
|
+
* T-14-01: Validates workspace name rejects path separators.
|
|
864
|
+
*/
|
|
865
|
+
export const initNewWorkspace: QueryHandler = async (_args, projectDir) => {
|
|
866
|
+
const home = process.env.HOME || homedir();
|
|
867
|
+
const defaultBase = join(home, 'gsd-workspaces');
|
|
868
|
+
|
|
869
|
+
// Detect child git repos (one level deep)
|
|
870
|
+
const childRepos: Array<{ name: string; path: string; has_uncommitted: boolean }> = [];
|
|
871
|
+
try {
|
|
872
|
+
const entries = readdirSync(projectDir, { withFileTypes: true });
|
|
873
|
+
for (const entry of entries) {
|
|
874
|
+
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
|
|
875
|
+
const fullPath = join(projectDir, entry.name);
|
|
876
|
+
if (existsSync(join(fullPath, '.git'))) {
|
|
877
|
+
let hasUncommitted = false;
|
|
878
|
+
try {
|
|
879
|
+
const status = execSync('git status --porcelain', { cwd: fullPath, encoding: 'utf8', timeout: 5000, stdio: 'pipe' });
|
|
880
|
+
hasUncommitted = status.trim().length > 0;
|
|
881
|
+
} catch { /* best-effort */ }
|
|
882
|
+
childRepos.push({ name: entry.name, path: fullPath, has_uncommitted: hasUncommitted });
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
} catch { /* intentionally empty */ }
|
|
886
|
+
|
|
887
|
+
let worktreeAvailable = false;
|
|
888
|
+
try {
|
|
889
|
+
execSync('git --version', { encoding: 'utf8', timeout: 5000, stdio: 'pipe' });
|
|
890
|
+
worktreeAvailable = true;
|
|
891
|
+
} catch { /* no git */ }
|
|
892
|
+
|
|
893
|
+
const result: Record<string, unknown> = {
|
|
894
|
+
default_workspace_base: defaultBase,
|
|
895
|
+
child_repos: childRepos,
|
|
896
|
+
child_repo_count: childRepos.length,
|
|
897
|
+
worktree_available: worktreeAvailable,
|
|
898
|
+
is_git_repo: pathExists(projectDir, '.git'),
|
|
899
|
+
cwd_repo_name: basename(projectDir),
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
return { data: withProjectRoot(projectDir, result) };
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
// ─── initListWorkspaces ───────────────────────────────────────────────────
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Init handler for list-workspaces workflow.
|
|
909
|
+
* Port of cmdInitListWorkspaces from init.cjs lines 1337-1381.
|
|
910
|
+
*/
|
|
911
|
+
export const initListWorkspaces: QueryHandler = async (_args, _projectDir) => {
|
|
912
|
+
const home = process.env.HOME || homedir();
|
|
913
|
+
const defaultBase = join(home, 'gsd-workspaces');
|
|
914
|
+
|
|
915
|
+
const workspaces: Array<Record<string, unknown>> = [];
|
|
916
|
+
if (existsSync(defaultBase)) {
|
|
917
|
+
let entries: Dirent[] = [];
|
|
918
|
+
try {
|
|
919
|
+
entries = readdirSync(defaultBase, { withFileTypes: true });
|
|
920
|
+
} catch { entries = []; }
|
|
921
|
+
for (const entry of entries) {
|
|
922
|
+
if (!entry.isDirectory()) continue;
|
|
923
|
+
const wsPath = join(defaultBase, String(entry.name));
|
|
924
|
+
const manifestPath = join(wsPath, 'WORKSPACE.md');
|
|
925
|
+
if (!existsSync(manifestPath)) continue;
|
|
926
|
+
|
|
927
|
+
let repoCount = 0;
|
|
928
|
+
let strategy = 'unknown';
|
|
929
|
+
try {
|
|
930
|
+
const manifest = readFileSync(manifestPath, 'utf8');
|
|
931
|
+
const strategyMatch = manifest.match(/^Strategy:\s*(.+)$/m);
|
|
932
|
+
if (strategyMatch) strategy = strategyMatch[1].trim();
|
|
933
|
+
const tableRows = manifest.split('\n').filter(l => l.match(/^\|\s*\w/) && !l.includes('Repo') && !l.includes('---'));
|
|
934
|
+
repoCount = tableRows.length;
|
|
935
|
+
} catch { /* best-effort */ }
|
|
936
|
+
const hasProject = existsSync(join(wsPath, '.planning', 'PROJECT.md'));
|
|
937
|
+
|
|
938
|
+
workspaces.push({
|
|
939
|
+
name: entry.name,
|
|
940
|
+
path: wsPath,
|
|
941
|
+
repo_count: repoCount,
|
|
942
|
+
strategy,
|
|
943
|
+
has_project: hasProject,
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
const result: Record<string, unknown> = {
|
|
949
|
+
workspace_base: defaultBase,
|
|
950
|
+
workspaces,
|
|
951
|
+
workspace_count: workspaces.length,
|
|
952
|
+
};
|
|
953
|
+
|
|
954
|
+
return { data: result };
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
// ─── initRemoveWorkspace ──────────────────────────────────────────────────
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* Init handler for remove-workspace workflow.
|
|
961
|
+
* Port of cmdInitRemoveWorkspace from init.cjs lines 1383-1443.
|
|
962
|
+
* T-14-01: Validates workspace name rejects path separators and '..' sequences.
|
|
963
|
+
*/
|
|
964
|
+
export const initRemoveWorkspace: QueryHandler = async (args, _projectDir) => {
|
|
965
|
+
const name = args[0];
|
|
966
|
+
if (!name) {
|
|
967
|
+
return { data: { error: 'workspace name required for init remove-workspace' } };
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// T-14-01: Reject path traversal attempts
|
|
971
|
+
if (name.includes('/') || name.includes('\\') || name.includes('..')) {
|
|
972
|
+
return { data: { error: `Invalid workspace name: ${name} (path separators not allowed)` } };
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
const home = process.env.HOME || homedir();
|
|
976
|
+
const defaultBase = join(home, 'gsd-workspaces');
|
|
977
|
+
const wsPath = join(defaultBase, name);
|
|
978
|
+
const manifestPath = join(wsPath, 'WORKSPACE.md');
|
|
979
|
+
|
|
980
|
+
if (!existsSync(wsPath)) {
|
|
981
|
+
return { data: { error: `Workspace not found: ${wsPath}` } };
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
const repos: Array<Record<string, unknown>> = [];
|
|
985
|
+
let strategy = 'unknown';
|
|
986
|
+
if (existsSync(manifestPath)) {
|
|
987
|
+
try {
|
|
988
|
+
const manifest = readFileSync(manifestPath, 'utf8');
|
|
989
|
+
const strategyMatch = manifest.match(/^Strategy:\s*(.+)$/m);
|
|
990
|
+
if (strategyMatch) strategy = strategyMatch[1].trim();
|
|
991
|
+
|
|
992
|
+
const lines = manifest.split('\n');
|
|
993
|
+
for (const line of lines) {
|
|
994
|
+
const match = line.match(/^\|\s*(\S+)\s*\|\s*(\S+)\s*\|\s*(\S+)\s*\|\s*(\S+)\s*\|$/);
|
|
995
|
+
if (match && match[1] !== 'Repo' && !match[1].includes('---')) {
|
|
996
|
+
repos.push({ name: match[1], source: match[2], branch: match[3], strategy: match[4] });
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
} catch { /* best-effort */ }
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Check for uncommitted changes in workspace repos
|
|
1003
|
+
const dirtyRepos: string[] = [];
|
|
1004
|
+
for (const repo of repos) {
|
|
1005
|
+
const repoPath = join(wsPath, repo.name as string);
|
|
1006
|
+
if (!existsSync(repoPath)) continue;
|
|
1007
|
+
try {
|
|
1008
|
+
const status = execSync('git status --porcelain', { cwd: repoPath, encoding: 'utf8', timeout: 5000, stdio: 'pipe' });
|
|
1009
|
+
if (status.trim().length > 0) {
|
|
1010
|
+
dirtyRepos.push(repo.name as string);
|
|
1011
|
+
}
|
|
1012
|
+
} catch { /* best-effort */ }
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
const result: Record<string, unknown> = {
|
|
1016
|
+
workspace_name: name,
|
|
1017
|
+
workspace_path: wsPath,
|
|
1018
|
+
has_manifest: existsSync(manifestPath),
|
|
1019
|
+
strategy,
|
|
1020
|
+
repos,
|
|
1021
|
+
repo_count: repos.length,
|
|
1022
|
+
dirty_repos: dirtyRepos,
|
|
1023
|
+
has_dirty_repos: dirtyRepos.length > 0,
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
return { data: result };
|
|
1027
|
+
};
|
|
1028
|
+
// ─── initIngestDocs ───────────────────────────────────────────────────────
|
|
1029
|
+
|
|
1030
|
+
/**
|
|
1031
|
+
* Init handler for ingest-docs workflow.
|
|
1032
|
+
* Mirrors `initResume` shape but without current-agent-id lookup — the
|
|
1033
|
+
* ingest-docs workflow reads `project_exists`, `planning_exists`, `has_git`,
|
|
1034
|
+
* and `project_path` to branch between new-project vs merge-milestone modes.
|
|
1035
|
+
*/
|
|
1036
|
+
export const initIngestDocs: QueryHandler = async (_args, projectDir) => {
|
|
1037
|
+
const config = await loadConfig(projectDir);
|
|
1038
|
+
const result: Record<string, unknown> = {
|
|
1039
|
+
project_exists: pathExists(projectDir, '.planning/PROJECT.md'),
|
|
1040
|
+
planning_exists: pathExists(projectDir, '.planning'),
|
|
1041
|
+
has_git: pathExists(projectDir, '.git'),
|
|
1042
|
+
project_path: '.planning/PROJECT.md',
|
|
1043
|
+
commit_docs: config.commit_docs,
|
|
1044
|
+
};
|
|
1045
|
+
return { data: withProjectRoot(projectDir, result, config as Record<string, unknown>) };
|
|
1046
|
+
};
|