@use-the-fork/spindle 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +3 -2
- package/build/Assets/Blueprints/Catalog.js +110 -0
- package/build/Assets/Blueprints/agents/general.agent.md +67 -0
- package/build/Assets/Blueprints/agents/sdd-code-reviewer.agent.md +62 -0
- package/build/Assets/Blueprints/agents/sdd-code-slop-reviewer.agent.md +59 -0
- package/build/Assets/Blueprints/agents/sdd-implementer.agent.md +77 -0
- package/build/Assets/Blueprints/agents/sdd-orchestrator.agent.md +42 -0
- package/build/Assets/Blueprints/agents/sdd-re-review.agent.md +57 -0
- package/build/Assets/Blueprints/agents/sdd-reviewer.agent.md +89 -0
- package/build/Assets/Blueprints/agents/sdd-task-reviewer.agent.md +54 -0
- package/build/Assets/Blueprints/agents/shared-codebase-analyzer.agent.md +151 -0
- package/build/Assets/Blueprints/agents/shared-codebase-locator.agent.md +130 -0
- package/build/Assets/Blueprints/agents/shared-codebase-pattern-finder.agent.md +247 -0
- package/build/Assets/Blueprints/agents/shared-explore.agent.md +53 -0
- package/build/Assets/Blueprints/agents/shared-r.agent.md +17 -0
- package/build/Assets/Blueprints/agents/shared-rw.agent.md +17 -0
- package/build/Assets/Blueprints/agents/shared-rwx.agent.md +17 -0
- package/build/Assets/Blueprints/agents/shared-web-search-researcher.agent.md +122 -0
- package/build/Assets/Blueprints/instructions/code-commenting.instructions.md +39 -0
- package/build/Assets/Blueprints/instructions/source-architecture.instructions.md +15 -0
- package/build/Assets/Blueprints/skills/asd-ste100/CREDITS.MD +1 -0
- package/build/Assets/Blueprints/skills/asd-ste100/SKILL.md +83 -0
- package/build/Assets/Blueprints/skills/asd-ste100/examples/before-after.md +107 -0
- package/build/Assets/Blueprints/skills/asd-ste100/references/writing-rules.md +65 -0
- package/build/Assets/Blueprints/skills/author-agent/SKILL.md +26 -0
- package/build/Assets/Blueprints/skills/author-design/SKILL.md +38 -0
- package/build/Assets/Blueprints/skills/author-instruction/SKILL.md +28 -0
- package/build/Assets/Blueprints/skills/author-skill/SKILL.md +26 -0
- package/build/Assets/Blueprints/skills/grill/SKILL.md +33 -0
- package/build/Assets/Blueprints/skills/grill-design/SKILL.md +41 -0
- package/build/Assets/Blueprints/skills/grill-with-docs/SKILL.md +14 -0
- package/build/Assets/Blueprints/skills/implement/SKILL.md +79 -0
- package/build/Assets/Blueprints/skills/implement-spec/SKILL.md +34 -0
- package/build/Assets/Blueprints/skills/improve-codebase-architecture/HTML-REPORT.md +129 -0
- package/build/Assets/Blueprints/skills/improve-codebase-architecture/SKILL.md +74 -0
- package/build/Assets/Blueprints/skills/int-code-review/SKILL.md +76 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/SKILL.md +238 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/_sections.md +36 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/_template.md +42 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-closing-brace-labels.md +112 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-empty-docblocks.md +132 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-narration.md +118 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-placeholder.md +130 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/defensive-generic-catch.md +153 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/defensive-impossible-null.md +157 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/defensive-missing-real.md +157 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-generic-placeholders.md +120 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-over-descriptive.md +108 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-suffix-abuse.md +167 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-type-in-name.md +100 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-dependency-creep.md +114 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-premature-interface.md +136 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-single-method-class.md +161 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-useless-wrapper.md +140 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-as-any-escape.md +127 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-debug-artifacts.md +144 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-hyper-consistent.md +84 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-no-hack-scars.md +113 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-trivial-boilerplate.md +191 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-doesnt-throw.md +132 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-mirror-implementation.md +125 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-mock-everything.md +148 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-snapshot-abuse.md +131 -0
- package/build/Assets/Blueprints/skills/int-codebase-design/SKILL.md +116 -0
- package/build/Assets/Blueprints/skills/int-codebase-design/references/DEEPENING.md +37 -0
- package/build/Assets/Blueprints/skills/int-codebase-design/references/DESIGN-IT-TWICE.md +44 -0
- package/build/Assets/Blueprints/skills/int-domain-modeling/SKILL.md +78 -0
- package/build/Assets/Blueprints/skills/int-domain-modeling/references/ADR-FORMAT.md +47 -0
- package/build/Assets/Blueprints/skills/int-domain-modeling/references/GLOSSARY-FORMAT.md +60 -0
- package/build/Assets/Blueprints/skills/int-prototype/LOGIC.md +67 -0
- package/build/Assets/Blueprints/skills/int-prototype/SKILL.md +30 -0
- package/build/Assets/Blueprints/skills/int-prototype/UI.md +112 -0
- package/build/Assets/Blueprints/skills/int-retrospective/SKILL.md +88 -0
- package/build/Assets/Blueprints/skills/int-retrospective/assets/retrospective-prompt.md +38 -0
- package/build/Assets/Blueprints/skills/int-retrospective/references/assessment.md +56 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/SKILL.md +64 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/assets/implementer-prompt.md +40 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/assets/reviewer-prompt.md +43 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/aggregate-review.md +7 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/corrective-rounds.md +72 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/example-workflow.md +25 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/full-spec.md +72 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/implementer-corrections.md +9 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/review-inputs.md +26 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/verification-evidence.md +7 -0
- package/build/Assets/Blueprints/skills/int-test-driven-development/SKILL.md +51 -0
- package/build/Assets/Blueprints/skills/int-test-driven-development/references/mocking.md +31 -0
- package/build/Assets/Blueprints/skills/int-test-driven-development/references/tests.md +68 -0
- package/build/Assets/Blueprints/skills/int-testing-agent-guidance/SKILL.md +53 -0
- package/build/Assets/Blueprints/skills/int-verification-before-completion/SKILL.md +25 -0
- package/build/Assets/Blueprints/skills/int-writing-agents/SKILL.md +66 -0
- package/build/Assets/Blueprints/skills/int-writing-for-agents/SKILL.md +86 -0
- package/build/Assets/Blueprints/skills/int-writing-instruction/SKILL.md +57 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/SKILL.md +129 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/assets/interactive.md +83 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/assets/reference.md +32 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/arguments.md +50 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/interactive-skills.md +53 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/persuasion-principles.md +220 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/reference-skills.md +20 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/testing-skills.md +48 -0
- package/build/Assets/Blueprints/skills/retrospective/SKILL.md +16 -0
- package/build/Assets/Blueprints/skills/setup-project/SKILL.md +66 -0
- package/build/Assets/Blueprints/skills/to-spec/SKILL.md +53 -0
- package/build/Assets/Blueprints/skills/to-tickets/SKILL.md +49 -0
- package/build/Assets/Blueprints/skills/update-tooling/SKILL.md +86 -0
- package/build/Assets/Templates/Catalog.js +12 -0
- package/build/Assets/Templates/Catalog.ts +17 -0
- package/build/Assets/Templates/project/copilot-instructions.md +17 -0
- package/build/Assets/Templates/project/domain.md +51 -0
- package/build/Assets/Templates/project/ticket-tracker.md +33 -0
- package/build/Assets/Templates/sdd/design.md +25 -0
- package/build/Assets/Templates/sdd/spec.md +29 -0
- package/build/Assets/Templates/sdd/ticket.md +15 -0
- package/build/BlueprintManagement/BlueprintManagementServiceProvider.js +15 -0
- package/build/BlueprintManagement/Catalog/BlueprintCatalog.js +238 -0
- package/build/BlueprintManagement/Commands/BlueprintCommandOptions.js +9 -0
- package/build/BlueprintManagement/Commands/ConflictPolicy.js +6 -0
- package/build/BlueprintManagement/Commands/InitCommand.js +117 -0
- package/build/BlueprintManagement/Commands/Prompts.js +66 -0
- package/build/BlueprintManagement/Commands/UpdateCommand.js +86 -0
- package/build/BlueprintManagement/Handlers/Agents/AgentHandler.js +21 -0
- package/build/BlueprintManagement/Handlers/BlueprintHandlers.js +4 -0
- package/build/BlueprintManagement/Handlers/Instructions/InstructionHandler.js +21 -0
- package/build/BlueprintManagement/Handlers/Skills/SkillHandler.js +10 -0
- package/build/BlueprintManagement/Reconciliation/BlueprintCatalogPlanner.js +87 -0
- package/build/BlueprintManagement/Reconciliation/BlueprintRemovalCoordinator.js +78 -0
- package/build/BlueprintManagement/Services/BlueprintReconciliationService.js +391 -0
- package/build/BlueprintManagement/Services/InstallationService.js +54 -0
- package/build/BlueprintManagement/Support/BlueprintFiles.js +113 -0
- package/build/BlueprintManagement/Types/Blueprint.js +1 -0
- package/build/BlueprintManagement/Types/Installation.js +1 -0
- package/build/BlueprintManagement/Types/Reconciliation.js +1 -0
- package/build/BlueprintManagement/Versioning/BlueprintVersion.js +70 -0
- package/build/BlueprintManagement/Versioning/BlueprintVersioning.js +55 -0
- package/build/Commands/Command.js +3 -0
- package/build/Commands/CommandOptions.js +12 -0
- package/build/Config/Config.js +3 -0
- package/build/Foundation/Application.js +112 -0
- package/build/Foundation/ApplicationBuilder.js +23 -0
- package/build/Foundation/Bootstrap/Bootstrapper.js +2 -0
- package/build/Foundation/Bootstrap/DiscoverGitRoot.js +8 -0
- package/build/Foundation/Bootstrap/LoadConsoleArgs.js +47 -0
- package/build/Foundation/Bootstrap/LoadProjectConfiguration.js +144 -0
- package/build/Foundation/Bootstrap/RegisterProviders.js +12 -0
- package/build/Foundation/Bootstrap/RequireProjectConfiguration.js +14 -0
- package/build/Foundation/ConsoleInvocation.js +1 -0
- package/build/Foundation/Container.js +185 -0
- package/build/Foundation/FoundationServiceProvider.js +5 -0
- package/build/Foundation/Kernel.js +30 -0
- package/build/Foundation/Service.js +23 -0
- package/build/Foundation/ServiceProvider.js +12 -0
- package/build/Git/GitServiceProvider.js +8 -0
- package/build/Git/Services/GitService.js +26 -0
- package/build/Project/ProjectServiceProvider.js +8 -0
- package/build/Project/Services/ProjectService.js +124 -0
- package/build/SubagentDrivenDevelopment/Commands/ChecksRecordCommand.js +35 -0
- package/build/SubagentDrivenDevelopment/Commands/ChecksRequireCommand.js +33 -0
- package/build/SubagentDrivenDevelopment/Commands/ClaimCommand.js +23 -0
- package/build/SubagentDrivenDevelopment/Commands/CorrectCommand.js +37 -0
- package/build/SubagentDrivenDevelopment/Commands/CreateCommand.js +26 -0
- package/build/SubagentDrivenDevelopment/Commands/FindingCreateCommand.js +39 -0
- package/build/SubagentDrivenDevelopment/Commands/FindingUpdateCommand.js +36 -0
- package/build/SubagentDrivenDevelopment/Commands/FinishCommand.js +23 -0
- package/build/SubagentDrivenDevelopment/Commands/ImplementCommand.js +23 -0
- package/build/SubagentDrivenDevelopment/Commands/NextCommand.js +21 -0
- package/build/SubagentDrivenDevelopment/Commands/ReadyCommand.js +21 -0
- package/build/SubagentDrivenDevelopment/Commands/RequirementsDecideCommand.js +33 -0
- package/build/SubagentDrivenDevelopment/Commands/ResumeCommand.js +31 -0
- package/build/SubagentDrivenDevelopment/Commands/ReviewCommand.js +55 -0
- package/build/SubagentDrivenDevelopment/Commands/ReviewRequireCommand.js +12 -0
- package/build/SubagentDrivenDevelopment/Commands/StatusCommand.js +21 -0
- package/build/SubagentDrivenDevelopment/Commands/SubmitCommand.js +59 -0
- package/build/SubagentDrivenDevelopment/Commands/TicketCommentCommand.js +24 -0
- package/build/SubagentDrivenDevelopment/Commands/TicketCreateCommand.js +25 -0
- package/build/SubagentDrivenDevelopment/Commands/TicketUpdateCommand.js +31 -0
- package/build/SubagentDrivenDevelopment/Commands/groups.js +36 -0
- package/build/SubagentDrivenDevelopment/Evidence/GitEvidence.js +449 -0
- package/build/SubagentDrivenDevelopment/Evidence/ReportEvidence.js +80 -0
- package/build/SubagentDrivenDevelopment/Models/Assignments.js +1 -0
- package/build/SubagentDrivenDevelopment/Models/Evidence.js +1 -0
- package/build/SubagentDrivenDevelopment/Models/Findings.js +1 -0
- package/build/SubagentDrivenDevelopment/Models/Options.js +1 -0
- package/build/SubagentDrivenDevelopment/Models/Workflow.js +16 -0
- package/build/SubagentDrivenDevelopment/Persistence/AssignmentSchema.js +155 -0
- package/build/SubagentDrivenDevelopment/Persistence/CheckSchema.js +71 -0
- package/build/SubagentDrivenDevelopment/Persistence/FeatureWorkspace.js +76 -0
- package/build/SubagentDrivenDevelopment/Persistence/FileSystem.js +28 -0
- package/build/SubagentDrivenDevelopment/Persistence/FindingSchema.js +81 -0
- package/build/SubagentDrivenDevelopment/Persistence/LedgerRepository.js +322 -0
- package/build/SubagentDrivenDevelopment/Persistence/PathSafety.js +62 -0
- package/build/SubagentDrivenDevelopment/Persistence/ProgressSchema.js +95 -0
- package/build/SubagentDrivenDevelopment/Persistence/ReadinessReader.js +40 -0
- package/build/SubagentDrivenDevelopment/Persistence/ReviewSchema.js +68 -0
- package/build/SubagentDrivenDevelopment/Persistence/TicketRepository.js +147 -0
- package/build/SubagentDrivenDevelopment/Persistence/WorkspaceTransaction.js +282 -0
- package/build/SubagentDrivenDevelopment/Policies/AcceptancePolicy.js +100 -0
- package/build/SubagentDrivenDevelopment/Policies/AssignmentPolicy.js +36 -0
- package/build/SubagentDrivenDevelopment/Policies/CheckPolicy.js +93 -0
- package/build/SubagentDrivenDevelopment/Policies/FindingPolicy.js +53 -0
- package/build/SubagentDrivenDevelopment/Policies/InputValidation.js +36 -0
- package/build/SubagentDrivenDevelopment/Policies/ReadinessPolicy.js +26 -0
- package/build/SubagentDrivenDevelopment/Policies/RequiredReviewers.js +12 -0
- package/build/SubagentDrivenDevelopment/Policies/ReviewPolicy.js +87 -0
- package/build/SubagentDrivenDevelopment/Policies/WorkflowPolicy.js +85 -0
- package/build/SubagentDrivenDevelopment/Services/AssignmentService.js +269 -0
- package/build/SubagentDrivenDevelopment/Services/ChecksService.js +319 -0
- package/build/SubagentDrivenDevelopment/Services/CorrectionService.js +273 -0
- package/build/SubagentDrivenDevelopment/Services/DeliveryCorrectionService.js +253 -0
- package/build/SubagentDrivenDevelopment/Services/DeliveryFindingService.js +302 -0
- package/build/SubagentDrivenDevelopment/Services/DeliveryService.js +160 -0
- package/build/SubagentDrivenDevelopment/Services/DeliverySubmissionService.js +273 -0
- package/build/SubagentDrivenDevelopment/Services/FeatureCreationService.js +138 -0
- package/build/SubagentDrivenDevelopment/Services/FeatureInspectionService.js +399 -0
- package/build/SubagentDrivenDevelopment/Services/FindingService.js +392 -0
- package/build/SubagentDrivenDevelopment/Services/IntegrationService.js +258 -0
- package/build/SubagentDrivenDevelopment/Services/ReadinessService.js +21 -0
- package/build/SubagentDrivenDevelopment/Services/RequirementsService.js +216 -0
- package/build/SubagentDrivenDevelopment/Services/ReviewService.js +471 -0
- package/build/SubagentDrivenDevelopment/Services/SubmissionService.js +299 -0
- package/build/SubagentDrivenDevelopment/Services/TicketService.js +186 -0
- package/build/SubagentDrivenDevelopment/SubagentDrivenDevelopmentServiceProvider.js +113 -0
- package/build/Support/Records.js +5 -0
- package/build/Support/Repository.js +139 -0
- package/build/Support/Stacks.js +10 -0
- package/build/TemplateRendering/Computed/ComputedContextService.js +13 -0
- package/build/TemplateRendering/Computed/ComputedVariableProvider.js +6 -0
- package/build/TemplateRendering/Computed/ComputedVariableProviders.js +6 -0
- package/build/TemplateRendering/Computed/Providers/InternalSkillDescriptionProvider.js +6 -0
- package/build/TemplateRendering/Computed/Providers/SpindlePackageManagerProvider.js +11 -0
- package/build/TemplateRendering/Computed/ResolveRenderContext.js +18 -0
- package/build/TemplateRendering/Rendering/RenderText.js +43 -0
- package/build/TemplateRendering/TemplateRenderingServiceProvider.js +8 -0
- package/build/Tooling/Commands/ToolingAddCommand.js +29 -0
- package/build/Tooling/Commands/ToolingListCommand.js +19 -0
- package/build/Tooling/Commands/ToolingRemoveCommand.js +19 -0
- package/build/Tooling/Commands/ToolingUpdateCommand.js +30 -0
- package/build/Tooling/Commands/groups.js +4 -0
- package/build/Tooling/Models/ToolingEntry.js +1 -0
- package/build/Tooling/Services/ToolingService.js +278 -0
- package/build/Tooling/ToolingServiceProvider.js +15 -0
- package/build/cli.js +36 -0
- package/build/index.js +31 -0
- package/package.json +54 -3
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Domain Docs
|
|
2
|
+
|
|
3
|
+
How the engineering skills should consume this repo's domain documentation when exploring the codebase.
|
|
4
|
+
|
|
5
|
+
## Before exploring, read these
|
|
6
|
+
|
|
7
|
+
- **`GLOSSARY.md`** at the repo root, or
|
|
8
|
+
- **`GLOSSARY-MAP.md`** at the repo root if it exists: it points at one `GLOSSARY.md` per context. Read each one relevant to the topic.
|
|
9
|
+
- **`docs/adr/`**: read ADRs that touch the area you're about to work in. In multi-context repos, also check `src/<context>/docs/adr/` for context-scoped decisions.
|
|
10
|
+
|
|
11
|
+
If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `int-domain-modeling` skill (reached via the `/grill-with-docs` skill and `/improve-codebase-architecture` skill) creates them lazily when terms or decisions actually get resolved.
|
|
12
|
+
|
|
13
|
+
## File structure
|
|
14
|
+
|
|
15
|
+
Single-context repo (most repos):
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
/
|
|
19
|
+
├── GLOSSARY.md
|
|
20
|
+
├── docs/adr/
|
|
21
|
+
│ ├── 0001-event-sourced-orders.md
|
|
22
|
+
│ └── 0002-postgres-for-write-model.md
|
|
23
|
+
└── src/
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Multi-context repo (presence of `GLOSSARY-MAP.md` at the root):
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
/
|
|
30
|
+
├── GLOSSARY-MAP.md
|
|
31
|
+
├── docs/adr/ ← system-wide decisions
|
|
32
|
+
└── src/
|
|
33
|
+
├── ordering/
|
|
34
|
+
│ ├── GLOSSARY.md
|
|
35
|
+
│ └── docs/adr/ ← context-specific decisions
|
|
36
|
+
└── billing/
|
|
37
|
+
├── GLOSSARY.md
|
|
38
|
+
└── docs/adr/
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Use the glossary's vocabulary
|
|
42
|
+
|
|
43
|
+
When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `GLOSSARY.md`. Don't drift to synonyms the glossary explicitly avoids.
|
|
44
|
+
|
|
45
|
+
If the concept you need isn't in the glossary yet, that's a signal: either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for the `int-domain-modeling` skill).
|
|
46
|
+
|
|
47
|
+
## Flag ADR conflicts
|
|
48
|
+
|
|
49
|
+
If your output contradicts an existing ADR, surface it explicitly rather than silently overriding:
|
|
50
|
+
|
|
51
|
+
> _Contradicts ADR-0007 (event-sourced orders), but worth reopening because…_
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Ticket Tracker: Local Markdown
|
|
2
|
+
|
|
3
|
+
Spindle SDD stores feature work in `.scratch/` workspaces. A workspace contains an approved `spec.md`, its `sdd.json` execution ledger, `tickets/`, and `reports/`. A design is optional; when present, complete and approve it before the spec. Derive tickets from the approved spec.
|
|
4
|
+
|
|
5
|
+
## Ticket format
|
|
6
|
+
|
|
7
|
+
- Each ticket is `tickets/T-0001.md`, with IDs starting at `T-0001` independently in each feature. IDs use at least four digits.
|
|
8
|
+
- `sdd.json` is authoritative for ticket metadata, status, dependencies, and execution state. Ticket Markdown contains only its ID in YAML front matter; requirements, acceptance criteria, and comments are the editable body.
|
|
9
|
+
- Ticket creation copies the shared template, including `[REQUIREMENTS]` and `[ACCEPTANCE CRITERIA]` placeholders. Fill both before implementation.
|
|
10
|
+
- Supported status values are `open`, `in-progress`, and `closed`. Dependencies must refer to existing tickets; cycles are rejected.
|
|
11
|
+
- There is no migration or compatibility support for `issues/`, `I-####`, or previous SDD ledger schemas. Create a fresh feature workspace when using this ticket format.
|
|
12
|
+
|
|
13
|
+
## Create and maintain tickets
|
|
14
|
+
|
|
15
|
+
Create each approved ticket in dependency order:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
spindle sdd ticket create --spec <absolute-spec-path> --title "..." --reporter "..." --category enhancement --author maintainer
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The command returns the new ticket ID and path. Complete its template, then set dependencies with `ticket update`:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
spindle sdd ticket update --spec <absolute-spec-path> --ticket T-0002 --blocked-by T-0001 --author maintainer
|
|
25
|
+
spindle sdd ticket comment --spec <absolute-spec-path> --ticket T-0001 --details "..." --author maintainer
|
|
26
|
+
spindle sdd ready --spec <absolute-spec-path>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Use `--clear-blocked-by` to remove dependencies. Use `ticket update --status` to change status. Ticket Markdown remains editable; attributed comments are appended under `## Comments`. Do not add an execution field to the ticket file: execution transitions are recorded in `sdd.json`.
|
|
30
|
+
|
|
31
|
+
## Delivery
|
|
32
|
+
|
|
33
|
+
Use `spindle sdd status` and `spindle sdd ready` to inspect work. Claim and dispatch ready tickets with `spindle sdd claim` and `spindle sdd implement`, submit the assigned report with `spindle sdd submit`, and close completed tickets with `spindle sdd ticket update --status closed`. Follow the `int-subagent-driven-dev` skill for review, correction, and feature completion.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# {{ variables.title | default("[FEATURE TITLE]") }}
|
|
2
|
+
|
|
3
|
+
## Intent
|
|
4
|
+
|
|
5
|
+
{{ variables.intent | default("[INTENT]") }}
|
|
6
|
+
|
|
7
|
+
## Success Criteria
|
|
8
|
+
|
|
9
|
+
{{ variables.success_criteria | default("[SUCCESS CRITERIA]") }}
|
|
10
|
+
|
|
11
|
+
## Scope and Constraints
|
|
12
|
+
|
|
13
|
+
{{ variables.scope_and_constraints | default("[SCOPE AND CONSTRAINTS]") }}
|
|
14
|
+
|
|
15
|
+
## Approaches Considered
|
|
16
|
+
|
|
17
|
+
{{ variables.approaches_and_trade_offs | default("[APPROACHES AND TRADE-OFFS]") }}
|
|
18
|
+
|
|
19
|
+
## Selected Direction
|
|
20
|
+
|
|
21
|
+
{{ variables.selected_direction | default("[SELECTED DIRECTION]") }}
|
|
22
|
+
|
|
23
|
+
## Assumptions and Open Decisions
|
|
24
|
+
|
|
25
|
+
{{ variables.assumptions_and_open_decisions | default("[ASSUMPTIONS AND OPEN DECISIONS]") }}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# {{ variables.title | default("[FEATURE TITLE]") }}
|
|
2
|
+
|
|
3
|
+
## Problem Statement
|
|
4
|
+
|
|
5
|
+
{{ variables.problem_statement | default("[PROBLEM STATEMENT]") }}
|
|
6
|
+
|
|
7
|
+
## Solution
|
|
8
|
+
|
|
9
|
+
{{ variables.solution | default("[SOLUTION]") }}
|
|
10
|
+
|
|
11
|
+
## User Stories
|
|
12
|
+
|
|
13
|
+
{{ variables.user_stories | default("[USER STORIES]") }}
|
|
14
|
+
|
|
15
|
+
## Implementation Decisions
|
|
16
|
+
|
|
17
|
+
{{ variables.implementation_decisions | default("[IMPLEMENTATION DECISIONS]") }}
|
|
18
|
+
|
|
19
|
+
## Testing Decisions
|
|
20
|
+
|
|
21
|
+
{{ variables.testing_decisions | default("[TESTING DECISIONS]") }}
|
|
22
|
+
|
|
23
|
+
## Out of Scope
|
|
24
|
+
|
|
25
|
+
{{ variables.out_of_scope | default("[OUT OF SCOPE]") }}
|
|
26
|
+
|
|
27
|
+
## Further Notes
|
|
28
|
+
|
|
29
|
+
{{ variables.further_notes | default("[FURTHER NOTES]") }}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# {{ variables.title | default("[TICKET TITLE]") }}
|
|
2
|
+
|
|
3
|
+
- **Reporter:** {{ variables.reporter | default("[REPORTER]") }}
|
|
4
|
+
- **Category:** {{ variables.category | default("[CATEGORY]") }}
|
|
5
|
+
- **Author:** {{ variables.author | default("[AUTHOR]") }}
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
{{ variables.requirements | default("[REQUIREMENTS]") }}
|
|
10
|
+
|
|
11
|
+
## Acceptance Criteria
|
|
12
|
+
|
|
13
|
+
{{ variables.acceptance_criteria | default("[ACCEPTANCE CRITERIA]") }}
|
|
14
|
+
|
|
15
|
+
## Comments
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { InitCommand } from "./Commands/InitCommand.js";
|
|
2
|
+
import { UpdateCommand } from "./Commands/UpdateCommand.js";
|
|
3
|
+
import { BlueprintReconciliationService } from "./Services/BlueprintReconciliationService.js";
|
|
4
|
+
import { InstallationService } from "./Services/InstallationService.js";
|
|
5
|
+
import { ServiceProvider } from "../Foundation/ServiceProvider.js";
|
|
6
|
+
export class BlueprintManagementServiceProvider extends ServiceProvider {
|
|
7
|
+
static commands() {
|
|
8
|
+
return [InitCommand, UpdateCommand];
|
|
9
|
+
}
|
|
10
|
+
register() {
|
|
11
|
+
this.app.singleton(BlueprintReconciliationService);
|
|
12
|
+
this.app.singleton(InstallationService);
|
|
13
|
+
}
|
|
14
|
+
async boot() { }
|
|
15
|
+
}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { lstat, readdir } from "node:fs/promises";
|
|
2
|
+
import { isAbsolute, join, relative, sep, win32 } from "node:path";
|
|
3
|
+
import { blueprintCatalog as assetCatalog } from "../../Assets/Blueprints/Catalog.js";
|
|
4
|
+
import { Stacks } from "../../Support/Stacks.js";
|
|
5
|
+
const catalogKinds = ["agents", "instructions", "skills"];
|
|
6
|
+
const allowedEntryFields = new Set(["path", "status", "stacks", "canReview"]);
|
|
7
|
+
const requiredEntryFields = ["path", "status", "stacks"];
|
|
8
|
+
const globCharacters = /[*?[\]{}]/;
|
|
9
|
+
function isRecord(value) {
|
|
10
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
function validateCatalogPath(value, kind, key) {
|
|
13
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
14
|
+
throw new Error(`Blueprint Catalog ${kind}.${key}.path must be a non-empty relative path.`);
|
|
15
|
+
}
|
|
16
|
+
const directory = value.endsWith("/");
|
|
17
|
+
const path = directory ? value.slice(0, -1) : value;
|
|
18
|
+
const segments = path.split("/");
|
|
19
|
+
if (path.length === 0 ||
|
|
20
|
+
path.includes("\\") ||
|
|
21
|
+
isAbsolute(path) ||
|
|
22
|
+
win32.isAbsolute(path) ||
|
|
23
|
+
segments.some((segment) => segment === "" || segment === "." || segment === "..") ||
|
|
24
|
+
globCharacters.test(path)) {
|
|
25
|
+
throw new Error(`Blueprint Catalog ${kind}.${key}.path '${value}' is unsafe; use a relative path without traversal or glob syntax.`);
|
|
26
|
+
}
|
|
27
|
+
return { path: segments.join("/"), directory };
|
|
28
|
+
}
|
|
29
|
+
export function validateBlueprintCatalog(data) {
|
|
30
|
+
if (!isRecord(data))
|
|
31
|
+
throw new Error("Blueprint Catalog must be an object.");
|
|
32
|
+
for (const key of Object.keys(data)) {
|
|
33
|
+
if (!catalogKinds.includes(key)) {
|
|
34
|
+
throw new Error(`Unknown Blueprint Catalog kind '${key}'.`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (const kind of catalogKinds) {
|
|
38
|
+
if (!Object.hasOwn(data, kind)) {
|
|
39
|
+
throw new Error(`Blueprint Catalog is missing required '${kind}' mapping.`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const catalog = {};
|
|
43
|
+
for (const kind of catalogKinds) {
|
|
44
|
+
const section = data[kind];
|
|
45
|
+
if (!isRecord(section)) {
|
|
46
|
+
throw new Error(`Blueprint Catalog '${kind}' must be a mapping of logical keys to entries.`);
|
|
47
|
+
}
|
|
48
|
+
const entries = [];
|
|
49
|
+
for (const [key, rawEntry] of Object.entries(section)) {
|
|
50
|
+
if (key.length === 0 || !isRecord(rawEntry)) {
|
|
51
|
+
throw new Error(`Blueprint Catalog '${kind}.${key}' must be an entry mapping.`);
|
|
52
|
+
}
|
|
53
|
+
for (const field of Object.keys(rawEntry)) {
|
|
54
|
+
if (field === "canReview" && kind !== "agents") {
|
|
55
|
+
throw new Error(`Field 'canReview' is only valid for Blueprint Catalog agents, not '${kind}'.`);
|
|
56
|
+
}
|
|
57
|
+
if (!allowedEntryFields.has(field)) {
|
|
58
|
+
throw new Error(`Unknown field '${field}' in Blueprint Catalog entry '${kind}.${key}'.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
for (const field of requiredEntryFields) {
|
|
62
|
+
if (!Object.hasOwn(rawEntry, field)) {
|
|
63
|
+
throw new Error(`Blueprint Catalog '${kind}.${key}' is missing required '${field}'.`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const { path, directory } = validateCatalogPath(rawEntry.path, kind, key);
|
|
67
|
+
if (rawEntry.status !== "active" && rawEntry.status !== "removed") {
|
|
68
|
+
throw new Error(`Invalid status in Blueprint Catalog '${kind}.${key}': expected 'active' or 'removed'.`);
|
|
69
|
+
}
|
|
70
|
+
if (!Array.isArray(rawEntry.stacks) ||
|
|
71
|
+
!rawEntry.stacks.every((stack) => typeof stack === "string")) {
|
|
72
|
+
throw new Error(`Blueprint Catalog '${kind}.${key}.stacks' must be a list of Stack names.`);
|
|
73
|
+
}
|
|
74
|
+
if (kind === "agents" &&
|
|
75
|
+
Object.hasOwn(rawEntry, "canReview") &&
|
|
76
|
+
typeof rawEntry.canReview !== "boolean") {
|
|
77
|
+
throw new Error(`Blueprint Catalog '${kind}.${key}.canReview' must be a boolean when supplied.`);
|
|
78
|
+
}
|
|
79
|
+
for (const stack of rawEntry.stacks) {
|
|
80
|
+
if (!Stacks.supported.includes(stack)) {
|
|
81
|
+
throw new Error(`Invalid Stack '${stack}' in Blueprint Catalog '${kind}.${key}'. Supported Stacks: ${Stacks.supported.join(", ")}.`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (kind === "skills" && !directory) {
|
|
85
|
+
throw new Error(`Blueprint Catalog Skill path '${kind}.${key}' must select a directory.`);
|
|
86
|
+
}
|
|
87
|
+
entries.push({
|
|
88
|
+
key,
|
|
89
|
+
path,
|
|
90
|
+
directory,
|
|
91
|
+
status: rawEntry.status,
|
|
92
|
+
stacks: rawEntry.stacks,
|
|
93
|
+
...(kind === "agents" && Object.hasOwn(rawEntry, "canReview")
|
|
94
|
+
? { canReview: rawEntry.canReview }
|
|
95
|
+
: {}),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catalog[kind] = entries;
|
|
99
|
+
}
|
|
100
|
+
for (const kind of catalogKinds) {
|
|
101
|
+
const entries = catalog[kind];
|
|
102
|
+
for (let first = 0; first < entries.length; first += 1) {
|
|
103
|
+
for (let second = first + 1; second < entries.length; second += 1) {
|
|
104
|
+
const left = entries[first].path;
|
|
105
|
+
const right = entries[second].path;
|
|
106
|
+
if (left === right || left.startsWith(`${right}/`) || right.startsWith(`${left}/`)) {
|
|
107
|
+
throw new Error(`Overlapping Blueprint Catalog paths in '${kind}': '${left}' and '${right}'.`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return catalog;
|
|
113
|
+
}
|
|
114
|
+
async function assertNoSymlinkPath(root, path) {
|
|
115
|
+
let current = root;
|
|
116
|
+
for (const segment of path.split("/")) {
|
|
117
|
+
current = join(current, segment);
|
|
118
|
+
try {
|
|
119
|
+
if ((await lstat(current)).isSymbolicLink()) {
|
|
120
|
+
throw new Error(`Blueprint Catalog path '${path}' traverses symbolic link '${current}'.`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
if (error.code === "ENOENT")
|
|
125
|
+
return;
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async function collectDirectoryFiles(source, root, result) {
|
|
131
|
+
for (const entry of await readdir(source, { withFileTypes: true })) {
|
|
132
|
+
const path = join(source, entry.name);
|
|
133
|
+
const stat = await lstat(path);
|
|
134
|
+
if (stat.isSymbolicLink()) {
|
|
135
|
+
throw new Error(`Blueprint directory '${relative(root, path)}' contains a symbolic link.`);
|
|
136
|
+
}
|
|
137
|
+
if (stat.isDirectory()) {
|
|
138
|
+
await collectDirectoryFiles(path, root, result);
|
|
139
|
+
}
|
|
140
|
+
else if (stat.isFile()) {
|
|
141
|
+
result.push(path);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
throw new Error(`Blueprint path '${relative(root, path)}' is not a regular file or directory.`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
export async function planBlueprintCatalog(catalog, kind, sourceRoot, selectedStacks) {
|
|
149
|
+
const selections = [];
|
|
150
|
+
const deletions = [];
|
|
151
|
+
const directories = [];
|
|
152
|
+
for (const entry of catalog[kind]) {
|
|
153
|
+
if (entry.status === "removed") {
|
|
154
|
+
deletions.push({ entry, path: entry.path, reason: "removed" });
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
await assertNoSymlinkPath(sourceRoot, entry.path);
|
|
158
|
+
const sourcePath = join(sourceRoot, ...entry.path.split("/"));
|
|
159
|
+
let stat;
|
|
160
|
+
try {
|
|
161
|
+
stat = await lstat(sourcePath);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (error.code === "ENOENT") {
|
|
165
|
+
throw new Error(`Active Blueprint Catalog entry '${kind}.${entry.key}' is missing source '${entry.path}'.`, { cause: error });
|
|
166
|
+
}
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
if (entry.directory !== stat.isDirectory() || (!entry.directory && !stat.isFile())) {
|
|
170
|
+
throw new Error(`Blueprint Catalog source '${kind}.${entry.key}' has an incompatible type for '${entry.path}'.`);
|
|
171
|
+
}
|
|
172
|
+
let directoryFiles;
|
|
173
|
+
if (entry.directory) {
|
|
174
|
+
directoryFiles = [];
|
|
175
|
+
await collectDirectoryFiles(sourcePath, sourceRoot, directoryFiles);
|
|
176
|
+
}
|
|
177
|
+
if (kind === "skills") {
|
|
178
|
+
const entryFile = join(sourcePath, "SKILL.md");
|
|
179
|
+
try {
|
|
180
|
+
const entryStat = await lstat(entryFile);
|
|
181
|
+
if (!entryStat.isFile() || entryStat.isSymbolicLink()) {
|
|
182
|
+
throw new Error(`Skill Blueprint '${entry.path}' must contain a regular SKILL.md file.`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
if (error.code === "ENOENT") {
|
|
187
|
+
throw new Error(`Skill Blueprint '${entry.path}' must contain a SKILL.md file.`, {
|
|
188
|
+
cause: error,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (entry.stacks.length > 0 && !entry.stacks.some((stack) => selectedStacks.includes(stack))) {
|
|
195
|
+
deletions.push({ entry, path: entry.path, reason: "stack-inapplicable" });
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const files = [];
|
|
199
|
+
if (entry.directory) {
|
|
200
|
+
directories.push({
|
|
201
|
+
entry,
|
|
202
|
+
path: entry.path,
|
|
203
|
+
sourceFiles: directoryFiles.map((file) => relative(sourcePath, file).split(sep).join("/")),
|
|
204
|
+
});
|
|
205
|
+
for (const file of directoryFiles) {
|
|
206
|
+
files.push({
|
|
207
|
+
sourcePath: file,
|
|
208
|
+
outputPath: relative(sourcePath, file).split(sep).join("/"),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
files.push({ sourcePath, outputPath: entry.path.split("/").at(-1) });
|
|
214
|
+
}
|
|
215
|
+
if (kind === "agents" || kind === "instructions") {
|
|
216
|
+
for (const file of files) {
|
|
217
|
+
const outputPath = entry.directory
|
|
218
|
+
? relative(sourceRoot, file.sourcePath).split(sep).join("/")
|
|
219
|
+
: entry.path;
|
|
220
|
+
selections.push({
|
|
221
|
+
entry,
|
|
222
|
+
name: outputPath,
|
|
223
|
+
files: [{ ...file, outputPath }],
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
selections.push({
|
|
229
|
+
entry,
|
|
230
|
+
name: entry.path,
|
|
231
|
+
files,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return { selections, deletions, directories };
|
|
236
|
+
}
|
|
237
|
+
validateBlueprintCatalog(assetCatalog);
|
|
238
|
+
export const blueprintCatalog = assetCatalog;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Option } from "commander";
|
|
2
|
+
export class BlueprintCommandOptions {
|
|
3
|
+
static conflictPolicyOption(description) {
|
|
4
|
+
return new Option("--on-conflict <overwrite|skip>", description).choices(["overwrite", "skip"]);
|
|
5
|
+
}
|
|
6
|
+
static noInteractiveOption(description) {
|
|
7
|
+
return new Option("--no-interactive", description);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { BlueprintCommandOptions } from "./BlueprintCommandOptions.js";
|
|
3
|
+
import { parseConflictPolicy } from "./ConflictPolicy.js";
|
|
4
|
+
import { promptConflict, promptSpindlePackageManager, promptStacks, } from "./Prompts.js";
|
|
5
|
+
import { blueprintHandlers } from "../Handlers/BlueprintHandlers.js";
|
|
6
|
+
import { BlueprintReconciliationService } from "../Services/BlueprintReconciliationService.js";
|
|
7
|
+
import { Command } from "../../Commands/Command.js";
|
|
8
|
+
import { LoadProjectConfiguration, } from "../../Foundation/Bootstrap/LoadProjectConfiguration.js";
|
|
9
|
+
import { ProjectService } from "../../Project/Services/ProjectService.js";
|
|
10
|
+
import { Stacks } from "../../Support/Stacks.js";
|
|
11
|
+
export class InitCommand extends Command {
|
|
12
|
+
handlers = [];
|
|
13
|
+
reconciliationService;
|
|
14
|
+
static normalizeSpindlePackageManager(command) {
|
|
15
|
+
const normalized = command.trim();
|
|
16
|
+
if (normalized.length === 0 || /[\r\n]/.test(normalized)) {
|
|
17
|
+
throw new Error("--spindle-package-manager must be a non-empty, single-line command.");
|
|
18
|
+
}
|
|
19
|
+
return normalized;
|
|
20
|
+
}
|
|
21
|
+
static register(program, createApp) {
|
|
22
|
+
program
|
|
23
|
+
.command("init")
|
|
24
|
+
.description("Install spindle Blueprints and initialize project guidance at the Git root.")
|
|
25
|
+
.option("--stack <id>", `Stack to install; repeat for several (${Stacks.supported.join(", ")})`, (value, previous = []) => [...previous, value], [])
|
|
26
|
+
.option("--spindle-package-manager <command>", "Package-manager command prefix used in generated tooling guidance")
|
|
27
|
+
.addOption(BlueprintCommandOptions.conflictPolicyOption("Policy for Blueprints that already exist; required when non-interactive and any exist (interactive runs prompt per Blueprint)"))
|
|
28
|
+
.addOption(BlueprintCommandOptions.noInteractiveOption("Never prompt; missing required input is an error"))
|
|
29
|
+
.addHelpText("after", `
|
|
30
|
+
If Blueprint stacks are already configured, project guidance is initialized and
|
|
31
|
+
\`spindle update\` is required to change the installed Blueprints.
|
|
32
|
+
Prompts are also disabled when stdin or stdout is not a terminal.
|
|
33
|
+
Other commands: update (see \`spindle update --help\`), tool (see \`spindle tool --help\`), sdd (see \`spindle sdd --help\`).
|
|
34
|
+
Run \`spindle --version\` to print the installed version.
|
|
35
|
+
`)
|
|
36
|
+
.allowExcessArguments(false)
|
|
37
|
+
.action(async (options) => {
|
|
38
|
+
const interactive = options.interactive && Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
|
|
39
|
+
const policy = parseConflictPolicy(options.onConflict);
|
|
40
|
+
Stacks.validate(options.stack);
|
|
41
|
+
const spindlePackageManager = options.spindlePackageManager === undefined
|
|
42
|
+
? undefined
|
|
43
|
+
: InitCommand.normalizeSpindlePackageManager(options.spindlePackageManager);
|
|
44
|
+
if (!interactive && options.stack.length === 0) {
|
|
45
|
+
throw new Error(`Provide at least one --stack flag <${Stacks.supported.join("|")}> (or run interactively).`);
|
|
46
|
+
}
|
|
47
|
+
await (await (await createApp()).make(InitCommand)).run(options.stack, spindlePackageManager, interactive, policy);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
constructor(app) {
|
|
51
|
+
super(app);
|
|
52
|
+
}
|
|
53
|
+
async boot() {
|
|
54
|
+
this.handlers = blueprintHandlers;
|
|
55
|
+
this.reconciliationService = await this.app.make(BlueprintReconciliationService);
|
|
56
|
+
}
|
|
57
|
+
async run(stackArguments, packageManagerArgument, interactive, policy) {
|
|
58
|
+
this.assertApplicationBooted();
|
|
59
|
+
const existing = LoadProjectConfiguration.current(this.app);
|
|
60
|
+
if (existing.status === "invalid") {
|
|
61
|
+
throw new Error(existing.reason, { cause: existing.error });
|
|
62
|
+
}
|
|
63
|
+
if (existing.status === "valid" && existing.data.stacks.length > 0) {
|
|
64
|
+
await (await this.app.make(ProjectService)).initialize();
|
|
65
|
+
console.log(`${chalk.green("✔")} Initialized project guidance and tooling documentation.`);
|
|
66
|
+
throw new Error("This repository already has configured Blueprint stacks; run `spindle update` instead of `init`.");
|
|
67
|
+
}
|
|
68
|
+
let stacks = [...new Set(stackArguments)];
|
|
69
|
+
Stacks.validate(stacks);
|
|
70
|
+
if (stacks.length === 0) {
|
|
71
|
+
if (!interactive) {
|
|
72
|
+
throw new Error(`Provide at least one --stack flag <${Stacks.supported.join("|")}> (or run interactively).`);
|
|
73
|
+
}
|
|
74
|
+
stacks = await promptStacks(Stacks.supported);
|
|
75
|
+
}
|
|
76
|
+
let spindlePackageManager = packageManagerArgument ??
|
|
77
|
+
(existing.status === "valid" ? existing.data.spindle_package_manager : undefined);
|
|
78
|
+
if (spindlePackageManager === undefined && interactive) {
|
|
79
|
+
spindlePackageManager = await promptSpindlePackageManager();
|
|
80
|
+
}
|
|
81
|
+
if (spindlePackageManager === undefined) {
|
|
82
|
+
throw new Error("A package-manager command is required; provide --spindle-package-manager <command> or run interactively.");
|
|
83
|
+
}
|
|
84
|
+
spindlePackageManager = InitCommand.normalizeSpindlePackageManager(spindlePackageManager);
|
|
85
|
+
const config = {
|
|
86
|
+
...(existing.status === "valid" ? existing.data : {}),
|
|
87
|
+
stacks,
|
|
88
|
+
spindle_package_manager: spindlePackageManager,
|
|
89
|
+
};
|
|
90
|
+
delete config.variables;
|
|
91
|
+
const outcomes = await this.reconciliationService.reconcile(this.handlers, {
|
|
92
|
+
mode: "init",
|
|
93
|
+
context: { stacks, variables: {} },
|
|
94
|
+
config,
|
|
95
|
+
conflictPolicy: policy,
|
|
96
|
+
chooseConflict: interactive ? promptConflict : undefined,
|
|
97
|
+
});
|
|
98
|
+
await LoadProjectConfiguration.refresh(this.app);
|
|
99
|
+
for (const outcome of outcomes.filter((item) => item.status === "removed")) {
|
|
100
|
+
console.log(`${chalk.green("✔")} Removed ${outcome.kind.toLowerCase()} '${outcome.name}'`);
|
|
101
|
+
}
|
|
102
|
+
const kinds = [...new Set(outcomes.map((outcome) => outcome.kind))];
|
|
103
|
+
const counts = (status) => kinds
|
|
104
|
+
.map((kind) => ({
|
|
105
|
+
kind,
|
|
106
|
+
count: outcomes.filter((outcome) => outcome.kind === kind && outcome.status === status)
|
|
107
|
+
.length,
|
|
108
|
+
}))
|
|
109
|
+
.filter(({ count }) => status === "installed" || count > 0)
|
|
110
|
+
.map(({ kind, count }) => `${count} ${status === "skipped" ? "existing " : ""}${kind.toLowerCase()}(s)`)
|
|
111
|
+
.join(", ");
|
|
112
|
+
const skipped = counts("skipped");
|
|
113
|
+
console.log(`${chalk.green("✔")} Installed ${counts("installed") || "0 Blueprints"} for ${chalk.cyan(stacks.join(", "))}${skipped ? `. Skipped ${skipped}` : ""}`);
|
|
114
|
+
await (await this.app.make(ProjectService)).initialize();
|
|
115
|
+
console.log(`${chalk.green("✔")} Initialized project guidance and tooling documentation.`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { cancel, isCancel, multiselect, select, text } from "@clack/prompts";
|
|
2
|
+
function ensureAnswer(answer) {
|
|
3
|
+
// clack signals cancellation with a unique symbol
|
|
4
|
+
if (typeof answer === "symbol" && isCancel(answer)) {
|
|
5
|
+
cancel("Cancelled.");
|
|
6
|
+
throw new Error("Cancelled by user.");
|
|
7
|
+
}
|
|
8
|
+
return answer;
|
|
9
|
+
}
|
|
10
|
+
export async function promptStacks(supported) {
|
|
11
|
+
const answer = await multiselect({
|
|
12
|
+
message: "Which stacks does this project use?",
|
|
13
|
+
options: supported.map((value) => ({ value, label: value })),
|
|
14
|
+
required: true,
|
|
15
|
+
});
|
|
16
|
+
return ensureAnswer(answer);
|
|
17
|
+
}
|
|
18
|
+
export async function promptSpindlePackageManager() {
|
|
19
|
+
const answer = await select({
|
|
20
|
+
message: "Which package-manager command should Spindle use in generated guidance?",
|
|
21
|
+
options: [
|
|
22
|
+
{ value: "npm run", label: "npm run" },
|
|
23
|
+
{ value: "pnpm", label: "pnpm" },
|
|
24
|
+
{ value: "yarn", label: "yarn" },
|
|
25
|
+
{ value: "bun", label: "bun" },
|
|
26
|
+
{ value: "other", label: "Other" },
|
|
27
|
+
],
|
|
28
|
+
});
|
|
29
|
+
const selection = ensureAnswer(answer);
|
|
30
|
+
if (selection !== "other")
|
|
31
|
+
return selection;
|
|
32
|
+
const custom = await text({
|
|
33
|
+
message: "Enter the package-manager command prefix",
|
|
34
|
+
validate: (value) => (value ?? "").trim().length === 0 ? "A command prefix is required." : undefined,
|
|
35
|
+
});
|
|
36
|
+
return ensureAnswer(custom).trim();
|
|
37
|
+
}
|
|
38
|
+
export async function promptConflict(kind, name) {
|
|
39
|
+
const answer = await select({
|
|
40
|
+
message: `${kind} '${name}' already exists. What should happen?`,
|
|
41
|
+
options: [
|
|
42
|
+
{ value: "overwrite", label: "Overwrite" },
|
|
43
|
+
{ value: "skip", label: "Skip" },
|
|
44
|
+
],
|
|
45
|
+
});
|
|
46
|
+
return ensureAnswer(answer);
|
|
47
|
+
}
|
|
48
|
+
export async function promptOutdated(kind, name, installed, available) {
|
|
49
|
+
const answer = await select({
|
|
50
|
+
message: `${kind} '${name}' is outdated (installed ${installed}, available ${available}). What should happen?`,
|
|
51
|
+
options: [
|
|
52
|
+
{ value: "overwrite", label: "Overwrite" },
|
|
53
|
+
{ value: "skip", label: "Skip" },
|
|
54
|
+
],
|
|
55
|
+
});
|
|
56
|
+
return ensureAnswer(answer);
|
|
57
|
+
}
|
|
58
|
+
export async function promptNewBlueprints(kind, names) {
|
|
59
|
+
const answer = await multiselect({
|
|
60
|
+
message: `Which new ${kind}s should be installed?`,
|
|
61
|
+
options: names.map((value) => ({ value, label: value })),
|
|
62
|
+
initialValues: names,
|
|
63
|
+
required: false,
|
|
64
|
+
});
|
|
65
|
+
return ensureAnswer(answer);
|
|
66
|
+
}
|