@use-the-fork/spindle 0.0.0-stage → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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,160 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { isAbsolute, resolve } from "node:path";
|
|
4
|
+
import { Service } from "../../Foundation/Service.js";
|
|
5
|
+
import { validateAuthor } from "../Policies/InputValidation.js";
|
|
6
|
+
import { requiredDeliveryReviewers } from "../Policies/RequiredReviewers.js";
|
|
7
|
+
export class DeliveryService extends Service {
|
|
8
|
+
gitEvidence;
|
|
9
|
+
reportEvidence;
|
|
10
|
+
ticketRepository;
|
|
11
|
+
ledgerRepository;
|
|
12
|
+
workspaceTransaction;
|
|
13
|
+
checkPolicy;
|
|
14
|
+
findingPolicy;
|
|
15
|
+
reviewPolicy;
|
|
16
|
+
featureInspectionService;
|
|
17
|
+
constructor(app, gitEvidence, reportEvidence, ticketRepository, ledgerRepository, workspaceTransaction, checkPolicy, findingPolicy, reviewPolicy, featureInspectionService) {
|
|
18
|
+
super(app);
|
|
19
|
+
this.gitEvidence = gitEvidence;
|
|
20
|
+
this.reportEvidence = reportEvidence;
|
|
21
|
+
this.ticketRepository = ticketRepository;
|
|
22
|
+
this.ledgerRepository = ledgerRepository;
|
|
23
|
+
this.workspaceTransaction = workspaceTransaction;
|
|
24
|
+
this.checkPolicy = checkPolicy;
|
|
25
|
+
this.findingPolicy = findingPolicy;
|
|
26
|
+
this.reviewPolicy = reviewPolicy;
|
|
27
|
+
this.featureInspectionService = featureInspectionService;
|
|
28
|
+
}
|
|
29
|
+
finish(options) {
|
|
30
|
+
validateAuthor(options.author);
|
|
31
|
+
const feature = this.featureInspectionService.resolveFeature(options.spec);
|
|
32
|
+
return this.workspaceTransaction.run(feature.workspace, () => {
|
|
33
|
+
const ledger = this.ledgerRepository.readLedger(feature);
|
|
34
|
+
const delivery = ledger.delivery;
|
|
35
|
+
if (ledger.tickets.length === 0) {
|
|
36
|
+
throw new Error("Cannot finish; the feature must contain at least one ticket.");
|
|
37
|
+
}
|
|
38
|
+
const tickets = this.ticketRepository.readTickets(feature.workspace);
|
|
39
|
+
const missing = ledger.tickets
|
|
40
|
+
.filter((progress) => progress.status !== "closed")
|
|
41
|
+
.map((progress) => progress.id);
|
|
42
|
+
if (missing.length > 0) {
|
|
43
|
+
throw new Error(`Cannot finish; every feature ticket must be closed: ${missing.join(", ")}.`);
|
|
44
|
+
}
|
|
45
|
+
if (tickets.length !== ledger.tickets.length) {
|
|
46
|
+
throw new Error("Cannot finish; one or more feature ticket documents are missing.");
|
|
47
|
+
}
|
|
48
|
+
if (delivery.adjudication?.status === "pending") {
|
|
49
|
+
throw new Error(`Cannot finish; delivery correction is paused for adjudication (${delivery.adjudication.reason}).`);
|
|
50
|
+
}
|
|
51
|
+
const identity = this.gitEvidence.deliveryIdentity(feature, ledger, tickets);
|
|
52
|
+
const checkBlockers = this.checkPolicy.deliveryCheckBlockers(delivery, identity);
|
|
53
|
+
if (checkBlockers.length > 0) {
|
|
54
|
+
throw new Error(`Cannot finish; final delivery checks are not current and passing:\n${checkBlockers.join("\n")}`);
|
|
55
|
+
}
|
|
56
|
+
const reviewers = requiredDeliveryReviewers(ledger, delivery);
|
|
57
|
+
const assignments = delivery.reviewAssignments ?? [];
|
|
58
|
+
const reviewBlockers = [];
|
|
59
|
+
if (assignments.length !== reviewers.length) {
|
|
60
|
+
reviewBlockers.push("Aggregate review assignment batch is incomplete.");
|
|
61
|
+
}
|
|
62
|
+
for (const required of reviewers) {
|
|
63
|
+
const matching = assignments.filter((assignment) => assignment.agent === required.agent &&
|
|
64
|
+
assignment.objective === required.details &&
|
|
65
|
+
assignment.scope === "delivery");
|
|
66
|
+
if (matching.length !== 1) {
|
|
67
|
+
reviewBlockers.push(`Required aggregate reviewer '${required.agent}' has no unique current assignment.`);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
const assignment = matching[0];
|
|
71
|
+
if (assignment.status !== "submitted" || assignment.result !== "passed") {
|
|
72
|
+
reviewBlockers.push(`Required aggregate review '${required.agent}' is ${assignment.status === "pending" ? "pending" : (assignment.result ?? "incomplete")}.`);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const blocker = this.reviewPolicy.reviewAssignmentBlocker(feature, delivery, assignment, identity);
|
|
76
|
+
if (blocker)
|
|
77
|
+
reviewBlockers.push(blocker);
|
|
78
|
+
}
|
|
79
|
+
if (reviewBlockers.length > 0) {
|
|
80
|
+
throw new Error(`Cannot finish; aggregate required reviews are not current and passing:\n${reviewBlockers.join("\n")}`);
|
|
81
|
+
}
|
|
82
|
+
const findingBlockers = this.findingPolicy.deliveryFindingBlockers(feature, ledger, delivery, identity);
|
|
83
|
+
if (findingBlockers.length > 0) {
|
|
84
|
+
throw new Error(`Cannot finish; unresolved delivery blockers remain:\n${findingBlockers.join("\n")}`);
|
|
85
|
+
}
|
|
86
|
+
if (ledger.retrospectiveRequired && !options.report) {
|
|
87
|
+
throw new Error("A retrospective report is required by the feature's stored creation policy.");
|
|
88
|
+
}
|
|
89
|
+
let retrospective;
|
|
90
|
+
if (options.report) {
|
|
91
|
+
const report = isAbsolute(options.report)
|
|
92
|
+
? resolve(options.report)
|
|
93
|
+
: resolve(feature.workspace, options.report);
|
|
94
|
+
this.reportEvidence.assertReportPath(feature, report, true);
|
|
95
|
+
const content = readFileSync(report);
|
|
96
|
+
if (content.length === 0 || !content.toString("utf8").trim()) {
|
|
97
|
+
throw new Error("Retrospective report must not be empty.");
|
|
98
|
+
}
|
|
99
|
+
retrospective = {
|
|
100
|
+
report,
|
|
101
|
+
reportSha256: createHash("sha256").update(content).digest("hex"),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (delivery.finished) {
|
|
105
|
+
const same = delivery.finished.report === retrospective?.report &&
|
|
106
|
+
delivery.finished.reportSha256 === retrospective?.reportSha256;
|
|
107
|
+
if (!same) {
|
|
108
|
+
throw new Error("Full-spec delivery is already finished with different retrospective evidence.");
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
scope: "delivery",
|
|
112
|
+
finished: true,
|
|
113
|
+
idempotent: true,
|
|
114
|
+
retrospectiveRequired: ledger.retrospectiveRequired,
|
|
115
|
+
...(retrospective ? { retrospective } : {}),
|
|
116
|
+
...identity,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const confirmed = this.gitEvidence.deliveryIdentity(feature, ledger, this.ticketRepository.readTickets(feature.workspace));
|
|
120
|
+
if (confirmed.snapshot !== identity.snapshot ||
|
|
121
|
+
this.checkPolicy.deliveryCheckBlockers(delivery, confirmed).length > 0) {
|
|
122
|
+
throw new Error("Aggregate content or check evidence changed while finishing; resume and revalidate delivery.");
|
|
123
|
+
}
|
|
124
|
+
delivery.execution = "complete";
|
|
125
|
+
delivery.finished = {
|
|
126
|
+
at: new Date().toISOString(),
|
|
127
|
+
author: options.author.trim(),
|
|
128
|
+
...retrospective,
|
|
129
|
+
};
|
|
130
|
+
delivery.status = "complete";
|
|
131
|
+
delivery.history.push({
|
|
132
|
+
at: delivery.finished.at,
|
|
133
|
+
author: delivery.finished.author,
|
|
134
|
+
action: "delivery-finished",
|
|
135
|
+
});
|
|
136
|
+
this.workspaceTransaction.writeLedger(feature, ledger);
|
|
137
|
+
return {
|
|
138
|
+
scope: "delivery",
|
|
139
|
+
finished: true,
|
|
140
|
+
retrospectiveRequired: ledger.retrospectiveRequired,
|
|
141
|
+
...(retrospective ? { retrospective } : {}),
|
|
142
|
+
...identity,
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
next(options) {
|
|
147
|
+
const status = this.featureInspectionService.status(options);
|
|
148
|
+
return {
|
|
149
|
+
feature: status.feature,
|
|
150
|
+
spec: status.spec,
|
|
151
|
+
snapshots: status.snapshots,
|
|
152
|
+
findings: status.findings,
|
|
153
|
+
requirementsDecisions: status.requirementsDecisions,
|
|
154
|
+
actions: status.actions,
|
|
155
|
+
pendingAssignments: status.pendingAssignments,
|
|
156
|
+
blockers: status.blockers,
|
|
157
|
+
delivery: status.delivery,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { Service } from "../../Foundation/Service.js";
|
|
2
|
+
import { validateAuthor } from "../Policies/InputValidation.js";
|
|
3
|
+
export class DeliverySubmissionService extends Service {
|
|
4
|
+
gitEvidence;
|
|
5
|
+
reportEvidence;
|
|
6
|
+
ticketRepository;
|
|
7
|
+
ledgerRepository;
|
|
8
|
+
workspaceTransaction;
|
|
9
|
+
checkPolicy;
|
|
10
|
+
reviewPolicy;
|
|
11
|
+
featureInspectionService;
|
|
12
|
+
constructor(app, gitEvidence, reportEvidence, ticketRepository, ledgerRepository, workspaceTransaction, checkPolicy, reviewPolicy, featureInspectionService) {
|
|
13
|
+
super(app);
|
|
14
|
+
this.gitEvidence = gitEvidence;
|
|
15
|
+
this.reportEvidence = reportEvidence;
|
|
16
|
+
this.ticketRepository = ticketRepository;
|
|
17
|
+
this.ledgerRepository = ledgerRepository;
|
|
18
|
+
this.workspaceTransaction = workspaceTransaction;
|
|
19
|
+
this.checkPolicy = checkPolicy;
|
|
20
|
+
this.reviewPolicy = reviewPolicy;
|
|
21
|
+
this.featureInspectionService = featureInspectionService;
|
|
22
|
+
}
|
|
23
|
+
submitDeliveryImplementation(options) {
|
|
24
|
+
if (options.assignment !== undefined ||
|
|
25
|
+
options.result !== undefined ||
|
|
26
|
+
options.noFindings ||
|
|
27
|
+
options.findings !== undefined) {
|
|
28
|
+
throw new Error("Review assignment, result, and findings options require role review.");
|
|
29
|
+
}
|
|
30
|
+
if (options.unavailable && !options.details?.trim()) {
|
|
31
|
+
throw new Error("--details is required when the delivery implementation report is unavailable.");
|
|
32
|
+
}
|
|
33
|
+
if (options.unavailable && options.report !== undefined) {
|
|
34
|
+
throw new Error("--report cannot be combined with --unavailable.");
|
|
35
|
+
}
|
|
36
|
+
if (!options.unavailable && options.details !== undefined) {
|
|
37
|
+
throw new Error("--details is only valid with --unavailable.");
|
|
38
|
+
}
|
|
39
|
+
validateAuthor(options.author);
|
|
40
|
+
const feature = this.featureInspectionService.resolveFeature(options.spec);
|
|
41
|
+
return this.workspaceTransaction.run(feature.workspace, () => {
|
|
42
|
+
const ledger = this.ledgerRepository.readLedger(feature);
|
|
43
|
+
const delivery = ledger.delivery;
|
|
44
|
+
const assignment = delivery.assignment;
|
|
45
|
+
if (!assignment || assignment.action !== "correct" || assignment.role !== "implementation") {
|
|
46
|
+
throw new Error("No matching delivery correction implementation assignment is pending.");
|
|
47
|
+
}
|
|
48
|
+
if (delivery.owner !== options.author.trim() ||
|
|
49
|
+
assignment.controller !== options.author.trim()) {
|
|
50
|
+
throw new Error("Only the current delivery controller may submit this implementation report.");
|
|
51
|
+
}
|
|
52
|
+
const attempt = delivery.correctionAttempts?.find((candidate) => candidate.number === assignment.attempt);
|
|
53
|
+
if (!attempt || attempt.assignmentId !== assignment.id) {
|
|
54
|
+
throw new Error("Delivery correction assignment has no matching persisted attempt.");
|
|
55
|
+
}
|
|
56
|
+
const identity = this.gitEvidence.deliveryIdentity(feature, ledger, this.ticketRepository.readTickets(feature.workspace));
|
|
57
|
+
if (options.unavailable) {
|
|
58
|
+
const details = options.details.trim();
|
|
59
|
+
if (assignment.status === "unavailable" && assignment.unavailableDetails === details) {
|
|
60
|
+
return {
|
|
61
|
+
scope: "delivery",
|
|
62
|
+
execution: delivery.execution,
|
|
63
|
+
assignment,
|
|
64
|
+
unavailable: true,
|
|
65
|
+
idempotent: true,
|
|
66
|
+
...identity,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (!["pending", "unavailable"].includes(assignment.status)) {
|
|
70
|
+
throw new Error("The delivery implementation assignment is no longer awaiting a report.");
|
|
71
|
+
}
|
|
72
|
+
assignment.status = "unavailable";
|
|
73
|
+
assignment.unavailableDetails = details;
|
|
74
|
+
this.workspaceTransaction.writeLedger(feature, ledger);
|
|
75
|
+
return {
|
|
76
|
+
scope: "delivery",
|
|
77
|
+
execution: delivery.execution,
|
|
78
|
+
assignment,
|
|
79
|
+
unavailable: true,
|
|
80
|
+
...identity,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (!options.report)
|
|
84
|
+
throw new Error("Delivery implementation submission requires --report.");
|
|
85
|
+
if (assignment.status === "submitted") {
|
|
86
|
+
const digest = this.reportEvidence.readReportDigest(feature, this.reportEvidence.submissionReportPath(feature, assignment, options.report));
|
|
87
|
+
if (digest !== assignment.reportSha256) {
|
|
88
|
+
throw new Error("The registered delivery implementation report changed after submission.");
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
scope: "delivery",
|
|
92
|
+
execution: delivery.execution,
|
|
93
|
+
assignment,
|
|
94
|
+
report: assignment.report,
|
|
95
|
+
reportSha256: digest,
|
|
96
|
+
idempotent: true,
|
|
97
|
+
...identity,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (!["pending", "unavailable"].includes(assignment.status)) {
|
|
101
|
+
throw new Error("The delivery implementation assignment is not awaiting a report.");
|
|
102
|
+
}
|
|
103
|
+
const report = this.reportEvidence.submissionReportPath(feature, assignment, options.report);
|
|
104
|
+
const reportSha256 = this.reportEvidence.readReportDigest(feature, report);
|
|
105
|
+
assignment.status = "submitted";
|
|
106
|
+
assignment.reportSha256 = reportSha256;
|
|
107
|
+
assignment.submittedAt = new Date().toISOString();
|
|
108
|
+
assignment.submittedBy = options.author.trim();
|
|
109
|
+
delete assignment.unavailableDetails;
|
|
110
|
+
attempt.assignment = assignment;
|
|
111
|
+
delivery.execution = "reviewing";
|
|
112
|
+
delivery.history.push({
|
|
113
|
+
at: assignment.submittedAt,
|
|
114
|
+
author: options.author.trim(),
|
|
115
|
+
action: `correction-report-submitted:${assignment.id}`,
|
|
116
|
+
});
|
|
117
|
+
this.workspaceTransaction.writeLedger(feature, ledger);
|
|
118
|
+
return {
|
|
119
|
+
scope: "delivery",
|
|
120
|
+
execution: delivery.execution,
|
|
121
|
+
assignment,
|
|
122
|
+
report: assignment.report,
|
|
123
|
+
reportSha256,
|
|
124
|
+
...identity,
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
submitDeliveryReview(options) {
|
|
129
|
+
if (options.role !== "review") {
|
|
130
|
+
throw new Error("Delivery implementation submissions require an active corrective assignment.");
|
|
131
|
+
}
|
|
132
|
+
if (!options.assignment?.trim())
|
|
133
|
+
throw new Error("Review submission requires --assignment.");
|
|
134
|
+
if (!options.report)
|
|
135
|
+
throw new Error("Review submission requires --report.");
|
|
136
|
+
if (!options.result)
|
|
137
|
+
throw new Error("Review submission requires explicit --result.");
|
|
138
|
+
if (!["passed", "failed", "unavailable"].includes(options.result)) {
|
|
139
|
+
throw new Error(`Invalid review result: ${options.result}.`);
|
|
140
|
+
}
|
|
141
|
+
const findingIds = options.findingIds?.map((id) => id.trim());
|
|
142
|
+
if (options.noFindings === true && findingIds !== undefined) {
|
|
143
|
+
throw new Error("--findings and --no-findings cannot be combined.");
|
|
144
|
+
}
|
|
145
|
+
if (findingIds !== undefined &&
|
|
146
|
+
(findingIds.length === 0 ||
|
|
147
|
+
findingIds.some((id) => !/^F-\d{4,}$/.test(id)) ||
|
|
148
|
+
new Set(findingIds).size !== findingIds.length)) {
|
|
149
|
+
throw new Error("--findings must contain unique canonical finding IDs.");
|
|
150
|
+
}
|
|
151
|
+
if (options.noFindings !== true && findingIds === undefined) {
|
|
152
|
+
throw new Error("Review submission requires either --findings or exclusive --no-findings.");
|
|
153
|
+
}
|
|
154
|
+
if (options.result === "passed" && options.details !== undefined) {
|
|
155
|
+
throw new Error("--details is only valid for failed or unavailable review results.");
|
|
156
|
+
}
|
|
157
|
+
if (options.result !== "passed" && !options.details?.trim()) {
|
|
158
|
+
throw new Error("--details is required for failed or unavailable reviews.");
|
|
159
|
+
}
|
|
160
|
+
validateAuthor(options.author);
|
|
161
|
+
const feature = this.featureInspectionService.resolveFeature(options.spec);
|
|
162
|
+
return this.workspaceTransaction.run(feature.workspace, () => {
|
|
163
|
+
const ledger = this.ledgerRepository.readLedger(feature);
|
|
164
|
+
const delivery = ledger.delivery;
|
|
165
|
+
if (!delivery.owner || delivery.owner !== options.author.trim()) {
|
|
166
|
+
throw new Error("Only the current delivery controller may submit aggregate review.");
|
|
167
|
+
}
|
|
168
|
+
const assignment = delivery.reviewAssignments?.find((candidate) => candidate.id === options.assignment);
|
|
169
|
+
if (!assignment || assignment.scope !== "delivery") {
|
|
170
|
+
throw new Error(`No delivery review assignment matches ID ${options.assignment}.`);
|
|
171
|
+
}
|
|
172
|
+
const identity = this.gitEvidence.deliveryIdentity(feature, ledger, this.ticketRepository.readTickets(feature.workspace));
|
|
173
|
+
const blockers = this.checkPolicy.deliveryCheckBlockers(delivery, identity);
|
|
174
|
+
if (blockers.length > 0)
|
|
175
|
+
throw new Error(blockers.join("\n"));
|
|
176
|
+
const snapshotBlocker = this.reviewPolicy.reviewSnapshotBlocker(feature, delivery, assignment, identity);
|
|
177
|
+
if (snapshotBlocker)
|
|
178
|
+
throw new Error(snapshotBlocker);
|
|
179
|
+
const report = this.reportEvidence.submissionReportPath(feature, assignment, options.report);
|
|
180
|
+
const reportSha256 = this.reportEvidence.readReportDigest(feature, report);
|
|
181
|
+
const reported = (delivery.findings ?? [])
|
|
182
|
+
.filter((finding) => finding.provenance.some((source) => source.report === assignment.report && source.snapshot === assignment.snapshot))
|
|
183
|
+
.map((finding) => finding.id);
|
|
184
|
+
if (options.noFindings === true && reported.length > 0) {
|
|
185
|
+
throw new Error(`Review report has registered findings and cannot declare --no-findings: ${reported.join(", ")}.`);
|
|
186
|
+
}
|
|
187
|
+
if (findingIds !== undefined) {
|
|
188
|
+
const missing = reported.filter((id) => !findingIds.includes(id));
|
|
189
|
+
const unknown = findingIds.filter((id) => !reported.includes(id));
|
|
190
|
+
if (missing.length > 0 || unknown.length > 0) {
|
|
191
|
+
throw new Error(`--findings must exactly list canonical findings registered from this delivery report and snapshot; missing=${missing.join(",") || "none"}; invalid provenance=${unknown.join(",") || "none"}.`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (assignment.status === "submitted") {
|
|
195
|
+
if (assignment.result === options.result &&
|
|
196
|
+
assignment.noFindings === (options.noFindings === true ? true : undefined) &&
|
|
197
|
+
JSON.stringify(assignment.findingIds) === JSON.stringify(findingIds) &&
|
|
198
|
+
assignment.submittedBy === options.author.trim() &&
|
|
199
|
+
assignment.details === options.details?.trim() &&
|
|
200
|
+
assignment.reportSha256 === reportSha256) {
|
|
201
|
+
return {
|
|
202
|
+
scope: "delivery",
|
|
203
|
+
execution: delivery.execution,
|
|
204
|
+
assignment,
|
|
205
|
+
report: assignment.report,
|
|
206
|
+
reportSha256,
|
|
207
|
+
idempotent: true,
|
|
208
|
+
...identity,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
throw new Error("The delivery review assignment already has a different submitted result.");
|
|
212
|
+
}
|
|
213
|
+
if (assignment.status !== "pending") {
|
|
214
|
+
throw new Error("The delivery review assignment is not awaiting a report.");
|
|
215
|
+
}
|
|
216
|
+
if (JSON.stringify(this.gitEvidence.changedPaths(delivery.baseSha)) !==
|
|
217
|
+
JSON.stringify(assignment.inventory)) {
|
|
218
|
+
throw new Error("Aggregate review inventory changed while submitting the report.");
|
|
219
|
+
}
|
|
220
|
+
assignment.status = "submitted";
|
|
221
|
+
assignment.result = options.result;
|
|
222
|
+
if (options.noFindings === true)
|
|
223
|
+
assignment.noFindings = true;
|
|
224
|
+
else
|
|
225
|
+
delete assignment.noFindings;
|
|
226
|
+
if (findingIds !== undefined)
|
|
227
|
+
assignment.findingIds = findingIds;
|
|
228
|
+
else
|
|
229
|
+
delete assignment.findingIds;
|
|
230
|
+
assignment.reportSha256 = reportSha256;
|
|
231
|
+
assignment.submittedAt = new Date().toISOString();
|
|
232
|
+
assignment.submittedBy = options.author.trim();
|
|
233
|
+
if (options.details?.trim())
|
|
234
|
+
assignment.details = options.details.trim();
|
|
235
|
+
const unresolvedBlocking = (assignment.findingIds ?? []).some((id) => delivery.findings?.some((finding) => finding.id === id &&
|
|
236
|
+
finding.blocking &&
|
|
237
|
+
finding.status !== "fixed" &&
|
|
238
|
+
finding.status !== "dismissed"));
|
|
239
|
+
if (assignment.result !== "passed" || unresolvedBlocking) {
|
|
240
|
+
delivery.execution = "changes-requested";
|
|
241
|
+
}
|
|
242
|
+
delivery.history.push({
|
|
243
|
+
at: new Date().toISOString(),
|
|
244
|
+
author: options.author.trim(),
|
|
245
|
+
action: `aggregate-review-submitted:${assignment.id}:${assignment.result}`,
|
|
246
|
+
});
|
|
247
|
+
if (delivery.reviewAssignments?.every((item) => item.status === "submitted")) {
|
|
248
|
+
for (const finding of delivery.findings ?? []) {
|
|
249
|
+
if (!finding.blocking ||
|
|
250
|
+
!finding.relatedTicket ||
|
|
251
|
+
finding.status === "fixed" ||
|
|
252
|
+
finding.status === "dismissed") {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const ticket = ledger.tickets.find((item) => item.id === finding.relatedTicket);
|
|
256
|
+
if (!ticket)
|
|
257
|
+
throw new Error(`Related ticket not found: ${finding.relatedTicket}.`);
|
|
258
|
+
ticket.status = "open";
|
|
259
|
+
delete ticket.blocker;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
this.workspaceTransaction.writeLedger(feature, ledger);
|
|
263
|
+
return {
|
|
264
|
+
scope: "delivery",
|
|
265
|
+
execution: delivery.execution,
|
|
266
|
+
assignment,
|
|
267
|
+
report: assignment.report,
|
|
268
|
+
reportSha256,
|
|
269
|
+
...identity,
|
|
270
|
+
};
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, realpathSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { templateCatalog } from "../../Assets/Templates/Catalog.js";
|
|
5
|
+
import { Service } from "../../Foundation/Service.js";
|
|
6
|
+
import { assertRealDirectory, lstatIfPresent, isPathInside, } from "../Persistence/FileSystem.js";
|
|
7
|
+
import { validateCreateOptions, featureSlug, } from "../Policies/InputValidation.js";
|
|
8
|
+
import { resolveRenderContext } from "../../TemplateRendering/Computed/ResolveRenderContext.js";
|
|
9
|
+
import { renderText } from "../../TemplateRendering/Rendering/RenderText.js";
|
|
10
|
+
const templates = fileURLToPath(new URL("../../Assets/Templates/", import.meta.url));
|
|
11
|
+
export class FeatureCreationService extends Service {
|
|
12
|
+
featureWorkspace;
|
|
13
|
+
pathSafety;
|
|
14
|
+
workspaceTransaction;
|
|
15
|
+
gitEvidence;
|
|
16
|
+
constructor(app, featureWorkspace, pathSafety, workspaceTransaction, gitEvidence) {
|
|
17
|
+
super(app);
|
|
18
|
+
this.featureWorkspace = featureWorkspace;
|
|
19
|
+
this.pathSafety = pathSafety;
|
|
20
|
+
this.workspaceTransaction = workspaceTransaction;
|
|
21
|
+
this.gitEvidence = gitEvidence;
|
|
22
|
+
}
|
|
23
|
+
create(options) {
|
|
24
|
+
this.assertApplicationBooted();
|
|
25
|
+
validateCreateOptions(options);
|
|
26
|
+
const root = realpathSync(this.app.workspaceRoot);
|
|
27
|
+
if (!statSync(root).isDirectory())
|
|
28
|
+
throw new Error("Workspace root must be a directory.");
|
|
29
|
+
const scratch = join(root, ".scratch");
|
|
30
|
+
if (this.pathSafety.isIgnored(root, ".scratch/.sdd-create.lock")) {
|
|
31
|
+
throw new Error("Feature destination is ignored: .scratch");
|
|
32
|
+
}
|
|
33
|
+
const scratchEntry = lstatIfPresent(scratch);
|
|
34
|
+
if (scratchEntry) {
|
|
35
|
+
assertRealDirectory(scratch, ".scratch");
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
try {
|
|
39
|
+
mkdirSync(scratch);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (typeof error !== "object" ||
|
|
43
|
+
error === null ||
|
|
44
|
+
!("code" in error) ||
|
|
45
|
+
error.code !== "EEXIST") {
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
assertRealDirectory(scratch, ".scratch");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (!isPathInside(root, realpathSync(scratch))) {
|
|
52
|
+
throw new Error(".scratch must remain inside this checkout.");
|
|
53
|
+
}
|
|
54
|
+
const release = this.workspaceTransaction.acquireCreationLock(scratch);
|
|
55
|
+
try {
|
|
56
|
+
this.featureWorkspace.rejectAbandonedStaging(scratch);
|
|
57
|
+
const id = this.featureWorkspace.nextFeatureId(scratch);
|
|
58
|
+
const feature = `${id}-${featureSlug(options.title)}`;
|
|
59
|
+
const workspace = join(scratch, feature);
|
|
60
|
+
if (lstatIfPresent(workspace)) {
|
|
61
|
+
throw new Error(`Feature workspace already exists: ${feature}`);
|
|
62
|
+
}
|
|
63
|
+
const relativeBase = `.scratch/${feature}`;
|
|
64
|
+
const targets = [
|
|
65
|
+
`${relativeBase}/spec.md`,
|
|
66
|
+
`${relativeBase}/sdd.json`,
|
|
67
|
+
`${relativeBase}/tickets`,
|
|
68
|
+
`${relativeBase}/reports`,
|
|
69
|
+
];
|
|
70
|
+
if (options.withDesign)
|
|
71
|
+
targets.push(`${relativeBase}/design.md`);
|
|
72
|
+
for (const target of targets) {
|
|
73
|
+
if (this.pathSafety.isIgnored(root, target)) {
|
|
74
|
+
throw new Error(`Feature destination is ignored: ${target}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const staging = mkdtempSync(join(scratch, ".sdd-create-"));
|
|
78
|
+
try {
|
|
79
|
+
mkdirSync(join(staging, "tickets"));
|
|
80
|
+
mkdirSync(join(staging, "reports"));
|
|
81
|
+
const context = resolveRenderContext(this.app, {
|
|
82
|
+
variables: {
|
|
83
|
+
title: options.title.trim(),
|
|
84
|
+
author: options.author.trim(),
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
const spec = renderText(readFileSync(join(templates, templateCatalog.sdd.spec), "utf8"), context);
|
|
88
|
+
const design = options.withDesign
|
|
89
|
+
? renderText(readFileSync(join(templates, templateCatalog.sdd.design), "utf8"), context)
|
|
90
|
+
: undefined;
|
|
91
|
+
const ledger = {
|
|
92
|
+
schemaVersion: 3,
|
|
93
|
+
featureId: id,
|
|
94
|
+
title: options.title.trim(),
|
|
95
|
+
createdBy: options.author.trim(),
|
|
96
|
+
createdAt: new Date().toISOString(),
|
|
97
|
+
retrospectiveRequired: options.requireRetrospective ?? false,
|
|
98
|
+
reviewPolicy: {
|
|
99
|
+
version: 1,
|
|
100
|
+
ticket: ["sdd-reviewer"],
|
|
101
|
+
delivery: ["sdd-reviewer"],
|
|
102
|
+
},
|
|
103
|
+
tickets: [],
|
|
104
|
+
delivery: {
|
|
105
|
+
status: "active",
|
|
106
|
+
execution: "unstarted",
|
|
107
|
+
history: [],
|
|
108
|
+
baseSha: this.gitEvidence.headSha(),
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
writeFileSync(join(staging, "spec.md"), spec, { flag: "wx" });
|
|
112
|
+
if (design !== undefined) {
|
|
113
|
+
writeFileSync(join(staging, "design.md"), design, { flag: "wx" });
|
|
114
|
+
}
|
|
115
|
+
writeFileSync(join(staging, "sdd.json"), `${JSON.stringify(ledger, null, 2)}\n`, {
|
|
116
|
+
flag: "wx",
|
|
117
|
+
});
|
|
118
|
+
if (lstatIfPresent(workspace)) {
|
|
119
|
+
throw new Error(`Feature workspace already exists: ${feature}`);
|
|
120
|
+
}
|
|
121
|
+
renameSync(staging, workspace);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
rmSync(staging, { recursive: true, force: true });
|
|
125
|
+
throw new Error(`Feature creation failed before publishing ${feature}; no complete workspace was created: ${error instanceof Error ? error.message : String(error)}`);
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
feature,
|
|
129
|
+
workspace,
|
|
130
|
+
spec: join(workspace, "spec.md"),
|
|
131
|
+
ledger: join(workspace, "sdd.json"),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
release();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|