@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,53 @@
|
|
|
1
|
+
import { Service } from "../../Foundation/Service.js";
|
|
2
|
+
import { requiredDeliveryReviewers } from "./RequiredReviewers.js";
|
|
3
|
+
export class FindingPolicy extends Service {
|
|
4
|
+
reviewPolicy;
|
|
5
|
+
constructor(app, reviewPolicy) {
|
|
6
|
+
super(app);
|
|
7
|
+
this.reviewPolicy = reviewPolicy;
|
|
8
|
+
}
|
|
9
|
+
findingBlockers(progress, identity) {
|
|
10
|
+
return (progress.findings ?? [])
|
|
11
|
+
.filter((finding) => finding.blocking &&
|
|
12
|
+
finding.status !== "fixed" &&
|
|
13
|
+
finding.status !== "dismissed" &&
|
|
14
|
+
!(finding.status === "deferred" &&
|
|
15
|
+
finding.requirementsDecision &&
|
|
16
|
+
progress.requirementsDecisions?.some((decision) => decision.id === finding.requirementsDecision &&
|
|
17
|
+
decision.finding === finding.id &&
|
|
18
|
+
decision.status === "approved" &&
|
|
19
|
+
decision.requirementsSnapshot === identity.requirementsSnapshot) &&
|
|
20
|
+
(progress.reviewAssignments?.length ?? 0) > 0 &&
|
|
21
|
+
progress.reviewAssignments.every((assignment) => assignment.status === "submitted" &&
|
|
22
|
+
assignment.result === "passed" &&
|
|
23
|
+
assignment.snapshot === identity.snapshot &&
|
|
24
|
+
assignment.requirementsSnapshot === identity.requirementsSnapshot)))
|
|
25
|
+
.map((finding) => `Finding ${finding.id} is unresolved and blocking acceptance (severity=${finding.severity}, status=${finding.status}).`);
|
|
26
|
+
}
|
|
27
|
+
deliveryFindingBlockers(feature, ledger, delivery, identity) {
|
|
28
|
+
return (delivery.findings ?? [])
|
|
29
|
+
.filter((finding) => {
|
|
30
|
+
if (!finding.blocking || ["fixed", "dismissed"].includes(finding.status))
|
|
31
|
+
return false;
|
|
32
|
+
if (finding.status !== "deferred" || !finding.requirementsDecision)
|
|
33
|
+
return true;
|
|
34
|
+
const decision = delivery.requirementsDecisions?.find((candidate) => candidate.id === finding.requirementsDecision &&
|
|
35
|
+
candidate.finding === finding.id &&
|
|
36
|
+
candidate.status === "approved" &&
|
|
37
|
+
candidate.requirementsSnapshot === identity.requirementsSnapshot);
|
|
38
|
+
if (!decision)
|
|
39
|
+
return true;
|
|
40
|
+
const required = requiredDeliveryReviewers(ledger, delivery);
|
|
41
|
+
return required.some(({ agent }) => {
|
|
42
|
+
const matching = (delivery.reviewAssignments ?? []).filter((assignment) => assignment.agent === agent &&
|
|
43
|
+
assignment.status === "submitted" &&
|
|
44
|
+
assignment.result === "passed" &&
|
|
45
|
+
assignment.snapshot === identity.snapshot &&
|
|
46
|
+
assignment.requirementsSnapshot === identity.requirementsSnapshot);
|
|
47
|
+
return (matching.length !== 1 ||
|
|
48
|
+
!!this.reviewPolicy.reviewAssignmentBlocker(feature, delivery, matching[0], identity));
|
|
49
|
+
});
|
|
50
|
+
})
|
|
51
|
+
.map((finding) => `Delivery finding ${finding.id} remains ${finding.status}.`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { TICKET_ID, TICKET_CATEGORIES, TRACKER_STATUSES, } from "../Models/Workflow.js";
|
|
2
|
+
export function validateAuthor(author) {
|
|
3
|
+
if (!author.trim())
|
|
4
|
+
throw new Error("Author must not be empty.");
|
|
5
|
+
}
|
|
6
|
+
export function validateTicketId(ticket) {
|
|
7
|
+
if (!TICKET_ID.test(ticket))
|
|
8
|
+
throw new Error(`Invalid ticket ID: ${ticket}.`);
|
|
9
|
+
}
|
|
10
|
+
export function validateCategory(category) {
|
|
11
|
+
if (!TICKET_CATEGORIES.includes(category)) {
|
|
12
|
+
throw new Error(`Invalid ticket category: ${category}.`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export function validateTriageStatus(status) {
|
|
16
|
+
if (!TRACKER_STATUSES.includes(status)) {
|
|
17
|
+
throw new Error(`Invalid ticket status: ${status}.`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function validateCreateOptions({ title, author }) {
|
|
21
|
+
if (!title.trim())
|
|
22
|
+
throw new Error("Title must not be empty.");
|
|
23
|
+
if (!author.trim())
|
|
24
|
+
throw new Error("Author must not be empty.");
|
|
25
|
+
}
|
|
26
|
+
export function featureSlug(title) {
|
|
27
|
+
const slug = title
|
|
28
|
+
.normalize("NFKD")
|
|
29
|
+
.replace(/\p{M}/gu, "")
|
|
30
|
+
.toLowerCase()
|
|
31
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
32
|
+
.replace(/^-|-$/g, "");
|
|
33
|
+
if (!slug)
|
|
34
|
+
throw new Error("Title must contain at least one letter or number.");
|
|
35
|
+
return slug;
|
|
36
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Service } from "../../Foundation/Service.js";
|
|
2
|
+
export class ReadinessPolicy extends Service {
|
|
3
|
+
ticketRepository;
|
|
4
|
+
constructor(app, ticketRepository) {
|
|
5
|
+
super(app);
|
|
6
|
+
this.ticketRepository = ticketRepository;
|
|
7
|
+
}
|
|
8
|
+
evaluate(tickets) {
|
|
9
|
+
this.ticketRepository.validateDependencyGraph(tickets);
|
|
10
|
+
const byId = new Map(tickets.map((item) => [item.id, item]));
|
|
11
|
+
const eligible = tickets.filter((item) => item.status === "open" && !item.blocker);
|
|
12
|
+
const ready = [];
|
|
13
|
+
const unready = [];
|
|
14
|
+
for (const item of eligible) {
|
|
15
|
+
const pending = item.blocked_by.filter((id) => {
|
|
16
|
+
const dependencyTicket = byId.get(id);
|
|
17
|
+
return (!dependencyTicket || dependencyTicket.status !== "closed" || !!dependencyTicket.blocker);
|
|
18
|
+
});
|
|
19
|
+
if (pending.length === 0)
|
|
20
|
+
ready.push(item.id);
|
|
21
|
+
else
|
|
22
|
+
unready.push({ ticket: item.id, blockedBy: pending });
|
|
23
|
+
}
|
|
24
|
+
return { ready, unready };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function requiredReviewers(policy, requirements = []) {
|
|
2
|
+
return [
|
|
3
|
+
...policy.map((agent) => ({ agent })),
|
|
4
|
+
...requirements.map(({ agent, details }) => ({ agent, details })),
|
|
5
|
+
];
|
|
6
|
+
}
|
|
7
|
+
export function requiredTicketReviewers(ledger, progress) {
|
|
8
|
+
return requiredReviewers(ledger.reviewPolicy.ticket, progress.reviewRequirements ?? []);
|
|
9
|
+
}
|
|
10
|
+
export function requiredDeliveryReviewers(ledger, progress) {
|
|
11
|
+
return requiredReviewers(ledger.reviewPolicy.delivery, progress.reviewRequirements ?? []);
|
|
12
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Service } from "../../Foundation/Service.js";
|
|
2
|
+
export class ReviewPolicy extends Service {
|
|
3
|
+
gitEvidence;
|
|
4
|
+
reportEvidence;
|
|
5
|
+
constructor(app, gitEvidence, reportEvidence) {
|
|
6
|
+
super(app);
|
|
7
|
+
this.gitEvidence = gitEvidence;
|
|
8
|
+
this.reportEvidence = reportEvidence;
|
|
9
|
+
}
|
|
10
|
+
reviewAssignmentBlocker(feature, progress, assignment, identity) {
|
|
11
|
+
if (assignment.status !== "pending") {
|
|
12
|
+
try {
|
|
13
|
+
const currentDigest = this.reportEvidence.readReportDigest(feature, assignment.report);
|
|
14
|
+
if (currentDigest !== assignment.reportSha256) {
|
|
15
|
+
return `The registered review report for assignment ${assignment.id} changed after submission.`;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return `The registered review report for assignment ${assignment.id} is missing or unavailable.`;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const snapshotBlocker = this.reviewSnapshotBlocker(feature, progress, assignment, identity);
|
|
23
|
+
if (snapshotBlocker)
|
|
24
|
+
return snapshotBlocker;
|
|
25
|
+
if (assignment.status === "pending")
|
|
26
|
+
return undefined;
|
|
27
|
+
if (assignment.result !== "passed") {
|
|
28
|
+
return `Review assignment ${assignment.id} is ${assignment.result}.${assignment.details ? ` Details: ${assignment.details}` : ""}`;
|
|
29
|
+
}
|
|
30
|
+
if (assignment.noFindings !== true && (assignment.findingIds?.length ?? 0) === 0) {
|
|
31
|
+
return `Review assignment ${assignment.id} has no explicit findings declaration.`;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
reviewSnapshotBlocker(feature, progress, assignment, identity) {
|
|
36
|
+
const integratedEvidence = assignment.scope !== "delivery" &&
|
|
37
|
+
this.gitEvidence.integrationPreservesEvidence(progress, identity) &&
|
|
38
|
+
assignment.snapshot === progress.acceptedEvidence?.snapshot &&
|
|
39
|
+
assignment.codeSnapshot === progress.acceptedEvidence?.codeSnapshot &&
|
|
40
|
+
assignment.requirementsSnapshot === progress.acceptedEvidence?.requirementsSnapshot &&
|
|
41
|
+
JSON.stringify(assignment.inventory) === JSON.stringify(progress.acceptedEvidence?.files);
|
|
42
|
+
if (!integratedEvidence &&
|
|
43
|
+
(assignment.snapshot !== identity.snapshot ||
|
|
44
|
+
assignment.codeSnapshot !== identity.codeSnapshot ||
|
|
45
|
+
assignment.requirementsSnapshot !== identity.requirementsSnapshot)) {
|
|
46
|
+
return `Review assignment ${assignment.id} is stale for the current ticket snapshot.`;
|
|
47
|
+
}
|
|
48
|
+
if (!progress.baseSha)
|
|
49
|
+
return "Review assignment has no recorded Git baseline.";
|
|
50
|
+
let inventory;
|
|
51
|
+
try {
|
|
52
|
+
inventory = this.gitEvidence.changedPaths(progress.baseSha);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
return `Review inventory cannot be revalidated: ${error instanceof Error ? error.message : String(error)}`;
|
|
56
|
+
}
|
|
57
|
+
if (JSON.stringify(inventory) !== JSON.stringify(assignment.inventory)) {
|
|
58
|
+
return `Review assignment ${assignment.id} no longer matches the frozen Git inventory.`;
|
|
59
|
+
}
|
|
60
|
+
if (assignment.status === "pending")
|
|
61
|
+
return undefined;
|
|
62
|
+
try {
|
|
63
|
+
const currentDigest = this.reportEvidence.readReportDigest(feature, assignment.report);
|
|
64
|
+
if (currentDigest !== assignment.reportSha256) {
|
|
65
|
+
return `The registered review report for assignment ${assignment.id} changed after submission.`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return `The registered review report for assignment ${assignment.id} is missing or unavailable.`;
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
reviewReportIntegrityBlocker(feature, assignment) {
|
|
74
|
+
if (assignment.status !== "submitted" || !assignment.reportSha256) {
|
|
75
|
+
return "Review report was not submitted.";
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
if (this.reportEvidence.readReportDigest(feature, assignment.report) !== assignment.reportSha256) {
|
|
79
|
+
return "Review report content changed after submission.";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return "Review report is missing or unavailable.";
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { Service } from "../../Foundation/Service.js";
|
|
2
|
+
export class WorkflowPolicy extends Service {
|
|
3
|
+
constructor(app) {
|
|
4
|
+
super(app);
|
|
5
|
+
}
|
|
6
|
+
nextAction(progress, blocker) {
|
|
7
|
+
if (progress.adjudication?.status === "pending")
|
|
8
|
+
return "adjudicate";
|
|
9
|
+
if (progress.execution === "unstarted")
|
|
10
|
+
return "claim";
|
|
11
|
+
if (progress.execution === "claimed") {
|
|
12
|
+
return progress.owner && progress.baseSha ? "implement" : "resolve-claim-blocker";
|
|
13
|
+
}
|
|
14
|
+
if (progress.execution === "implementing") {
|
|
15
|
+
if (!progress.assignment)
|
|
16
|
+
return "resolve-assignment-blocker";
|
|
17
|
+
if (progress.assignment.status === "submitted") {
|
|
18
|
+
if (blocker?.startsWith("Required check "))
|
|
19
|
+
return "record-current-checks";
|
|
20
|
+
return blocker ? "restore-implementation-report" : "review";
|
|
21
|
+
}
|
|
22
|
+
return "await-implementation-report";
|
|
23
|
+
}
|
|
24
|
+
if (progress.execution === "reviewing") {
|
|
25
|
+
if (blocker?.startsWith("Finding "))
|
|
26
|
+
return "resolve-findings";
|
|
27
|
+
if (blocker?.startsWith("Required check "))
|
|
28
|
+
return "record-current-checks";
|
|
29
|
+
const reviews = progress.reviewAssignments ?? [];
|
|
30
|
+
if (reviews.length > 0) {
|
|
31
|
+
if (blocker?.startsWith("The registered review report"))
|
|
32
|
+
return "restore-review-report";
|
|
33
|
+
if (blocker)
|
|
34
|
+
return "resolve-review-blocker";
|
|
35
|
+
if (reviews.some((assignment) => assignment.status === "pending")) {
|
|
36
|
+
return "await-review-report";
|
|
37
|
+
}
|
|
38
|
+
if (reviews.some((assignment) => assignment.result !== "passed")) {
|
|
39
|
+
return "resolve-review-result";
|
|
40
|
+
}
|
|
41
|
+
return "accept";
|
|
42
|
+
}
|
|
43
|
+
return blocker ? "restore-implementation-report" : "review";
|
|
44
|
+
}
|
|
45
|
+
if (progress.execution === "changes-requested") {
|
|
46
|
+
if ((progress.findings ?? []).some((finding) => finding.status === "confirmed")) {
|
|
47
|
+
return "correct";
|
|
48
|
+
}
|
|
49
|
+
if (blocker?.startsWith("Finding "))
|
|
50
|
+
return "adjudicate-findings";
|
|
51
|
+
return (progress.correctionAttempts?.length ?? 0) > 0
|
|
52
|
+
? "correct"
|
|
53
|
+
: "controller-correction-decision";
|
|
54
|
+
}
|
|
55
|
+
if (progress.execution === "accepted")
|
|
56
|
+
return blocker ? "resolve-check-blocker" : "integrate";
|
|
57
|
+
if (progress.execution === "integrated")
|
|
58
|
+
return blocker ? "resolve-check-blocker" : "complete";
|
|
59
|
+
return "resolve-blocker";
|
|
60
|
+
}
|
|
61
|
+
requireProgress(ledger, ticket) {
|
|
62
|
+
const progress = ledger.tickets.find((item) => item.id === ticket);
|
|
63
|
+
if (!progress)
|
|
64
|
+
throw new Error(`Ticket ${ticket} has no ledger execution record.`);
|
|
65
|
+
return progress;
|
|
66
|
+
}
|
|
67
|
+
acceptedInventory(progress, ticketId) {
|
|
68
|
+
const corrective = progress.correctionAttempts?.at(-1)?.reviewAssignments ?? [];
|
|
69
|
+
const assignments = corrective.length > 0 ? corrective : (progress.reviewAssignments ?? []);
|
|
70
|
+
if (assignments.length === 0 ||
|
|
71
|
+
assignments.some((assignment) => assignment.status !== "submitted")) {
|
|
72
|
+
throw new Error(`Ticket ${ticketId} has no complete frozen review inventory.`);
|
|
73
|
+
}
|
|
74
|
+
const inventory = assignments[0].inventory;
|
|
75
|
+
if (assignments.some((assignment) => JSON.stringify(assignment.inventory) !== JSON.stringify(inventory))) {
|
|
76
|
+
throw new Error(`Ticket ${ticketId} review assignments disagree on the accepted inventory.`);
|
|
77
|
+
}
|
|
78
|
+
return [...inventory];
|
|
79
|
+
}
|
|
80
|
+
assertDeliveryOpen(delivery) {
|
|
81
|
+
if (delivery.finished) {
|
|
82
|
+
throw new Error("Delivery is finished; final checks cannot be changed.");
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import matter from "gray-matter";
|
|
4
|
+
import { Service } from "../../Foundation/Service.js";
|
|
5
|
+
import { lstatIfPresent } from "../Persistence/FileSystem.js";
|
|
6
|
+
import { validateAuthor, validateTicketId, } from "../Policies/InputValidation.js";
|
|
7
|
+
export class AssignmentService extends Service {
|
|
8
|
+
gitEvidence;
|
|
9
|
+
reportEvidence;
|
|
10
|
+
ticketRepository;
|
|
11
|
+
ledgerRepository;
|
|
12
|
+
workspaceTransaction;
|
|
13
|
+
assignmentPolicy;
|
|
14
|
+
checkPolicy;
|
|
15
|
+
reviewPolicy;
|
|
16
|
+
workflowPolicy;
|
|
17
|
+
featureInspectionService;
|
|
18
|
+
constructor(app, gitEvidence, reportEvidence, ticketRepository, ledgerRepository, workspaceTransaction, assignmentPolicy, checkPolicy, reviewPolicy, workflowPolicy, featureInspectionService) {
|
|
19
|
+
super(app);
|
|
20
|
+
this.gitEvidence = gitEvidence;
|
|
21
|
+
this.reportEvidence = reportEvidence;
|
|
22
|
+
this.ticketRepository = ticketRepository;
|
|
23
|
+
this.ledgerRepository = ledgerRepository;
|
|
24
|
+
this.workspaceTransaction = workspaceTransaction;
|
|
25
|
+
this.assignmentPolicy = assignmentPolicy;
|
|
26
|
+
this.checkPolicy = checkPolicy;
|
|
27
|
+
this.reviewPolicy = reviewPolicy;
|
|
28
|
+
this.workflowPolicy = workflowPolicy;
|
|
29
|
+
this.featureInspectionService = featureInspectionService;
|
|
30
|
+
}
|
|
31
|
+
claim(options) {
|
|
32
|
+
validateTicketId(options.ticket);
|
|
33
|
+
validateAuthor(options.author);
|
|
34
|
+
const feature = this.featureInspectionService.resolveFeature(options.spec);
|
|
35
|
+
return this.workspaceTransaction.run(feature.workspace, () => {
|
|
36
|
+
const ledger = this.ledgerRepository.readLedger(feature);
|
|
37
|
+
const tickets = this.ticketRepository.readTickets(feature.workspace);
|
|
38
|
+
this.ticketRepository.validateDependencyGraph(tickets);
|
|
39
|
+
const ticket = this.ticketRepository.requireTicket(tickets, options.ticket);
|
|
40
|
+
const progress = this.workflowPolicy.requireProgress(ledger, options.ticket);
|
|
41
|
+
if (progress.execution !== "unstarted") {
|
|
42
|
+
throw new Error(`Ticket ${options.ticket} is already owned or has progressed to ${progress.execution}.`);
|
|
43
|
+
}
|
|
44
|
+
if (ticket.status !== "open" || ticket.blocker) {
|
|
45
|
+
throw new Error(`Ticket ${options.ticket} is not open and unblocked.`);
|
|
46
|
+
}
|
|
47
|
+
const ticketsById = new Map(tickets.map((item) => [item.id, item]));
|
|
48
|
+
const unmet = ticket.blocked_by.filter((dependency) => {
|
|
49
|
+
const dependencyTicket = ticketsById.get(dependency);
|
|
50
|
+
return (!dependencyTicket || dependencyTicket.status !== "closed" || !!dependencyTicket.blocker);
|
|
51
|
+
});
|
|
52
|
+
if (unmet.length > 0) {
|
|
53
|
+
throw new Error(`Ticket ${options.ticket} has open dependencies: ${unmet.join(", ")}.`);
|
|
54
|
+
}
|
|
55
|
+
const baseSha = this.gitEvidence.cleanGitBaseline(feature);
|
|
56
|
+
progress.execution = "claimed";
|
|
57
|
+
progress.status = "in-progress";
|
|
58
|
+
progress.owner = options.author.trim();
|
|
59
|
+
progress.baseSha = baseSha;
|
|
60
|
+
ledger.delivery.baseSha ??= baseSha;
|
|
61
|
+
const identity = this.gitEvidence.contentIdentity(feature, ticket, baseSha, progress.checks);
|
|
62
|
+
const afterTicket = matter.stringify(this.ticketRepository.appendAudit(ticket.body, options.author, `Claimed from clean Git baseline ${baseSha}.`), this.ticketRepository.metadata(ticket));
|
|
63
|
+
this.workspaceTransaction.writeWorkflowState(feature, ticket, afterTicket, ledger);
|
|
64
|
+
return {
|
|
65
|
+
ticket: options.ticket,
|
|
66
|
+
execution: "claimed",
|
|
67
|
+
owner: options.author.trim(),
|
|
68
|
+
baseSha,
|
|
69
|
+
...identity,
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
implement(options) {
|
|
74
|
+
validateTicketId(options.ticket);
|
|
75
|
+
validateAuthor(options.author);
|
|
76
|
+
const feature = this.featureInspectionService.resolveFeature(options.spec);
|
|
77
|
+
return this.workspaceTransaction.run(feature.workspace, () => {
|
|
78
|
+
const ledger = this.ledgerRepository.readLedger(feature);
|
|
79
|
+
const ticket = this.ticketRepository.requireTicket(this.ticketRepository.readTickets(feature.workspace), options.ticket);
|
|
80
|
+
const progress = this.workflowPolicy.requireProgress(ledger, options.ticket);
|
|
81
|
+
const identity = this.gitEvidence.contentIdentity(feature, ticket, progress.baseSha, progress.checks);
|
|
82
|
+
if (progress.execution === "implementing" && progress.assignment) {
|
|
83
|
+
if (progress.owner !== options.author.trim()) {
|
|
84
|
+
throw new Error("Controller ownership changed; resume and confirm the handoff first.");
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
ticket: options.ticket,
|
|
88
|
+
execution: "implementing",
|
|
89
|
+
assignment: progress.assignment,
|
|
90
|
+
...identity,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (progress.execution !== "claimed" || !progress.baseSha || !progress.owner) {
|
|
94
|
+
throw new Error(`Ticket ${options.ticket} must be claimed before implementation dispatch.`);
|
|
95
|
+
}
|
|
96
|
+
if (progress.owner !== options.author.trim()) {
|
|
97
|
+
throw new Error("Only the recorded controller may dispatch implementation.");
|
|
98
|
+
}
|
|
99
|
+
const report = join(feature.workspace, "reports", `${options.ticket}-implementation.md`);
|
|
100
|
+
this.reportEvidence.assertReportPath(feature, report, false);
|
|
101
|
+
if (lstatIfPresent(report)) {
|
|
102
|
+
throw new Error(`Assigned report already exists and was preserved: ${report}.`);
|
|
103
|
+
}
|
|
104
|
+
const assignment = {
|
|
105
|
+
id: randomUUID(),
|
|
106
|
+
role: "implementation",
|
|
107
|
+
action: "implement",
|
|
108
|
+
controller: options.author.trim(),
|
|
109
|
+
report,
|
|
110
|
+
requestedAt: new Date().toISOString(),
|
|
111
|
+
status: "pending",
|
|
112
|
+
handoffs: [],
|
|
113
|
+
};
|
|
114
|
+
progress.execution = "implementing";
|
|
115
|
+
progress.assignment = assignment;
|
|
116
|
+
const afterTicket = matter.stringify(this.ticketRepository.appendAudit(ticket.body, options.author, `Implementation assignment ${assignment.id} recorded; role=implementation; report=${report}. No agent was launched.`), this.ticketRepository.metadata(ticket));
|
|
117
|
+
this.workspaceTransaction.writeWorkflowState(feature, ticket, afterTicket, ledger);
|
|
118
|
+
return { ticket: options.ticket, execution: "implementing", assignment, ...identity };
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
resume(options) {
|
|
122
|
+
if (options.scope === "delivery") {
|
|
123
|
+
if (options.ticket)
|
|
124
|
+
throw new Error("--ticket and --scope delivery are mutually exclusive.");
|
|
125
|
+
return this.deliveryResume(options);
|
|
126
|
+
}
|
|
127
|
+
if (options.scope !== undefined)
|
|
128
|
+
throw new Error("--scope must be 'delivery'.");
|
|
129
|
+
if (!options.ticket)
|
|
130
|
+
throw new Error("resume requires --ticket or --scope delivery.");
|
|
131
|
+
validateTicketId(options.ticket);
|
|
132
|
+
validateAuthor(options.author);
|
|
133
|
+
const feature = this.featureInspectionService.resolveFeature(options.spec);
|
|
134
|
+
return this.workspaceTransaction.run(feature.workspace, () => {
|
|
135
|
+
const ledger = this.ledgerRepository.readLedger(feature);
|
|
136
|
+
const ticket = this.ticketRepository.requireTicket(this.ticketRepository.readTickets(feature.workspace), options.ticket);
|
|
137
|
+
const progress = this.workflowPolicy.requireProgress(ledger, options.ticket);
|
|
138
|
+
let handoffRecorded = false;
|
|
139
|
+
if (progress.owner && progress.owner !== options.author.trim()) {
|
|
140
|
+
if (!options.confirmHandoff) {
|
|
141
|
+
throw new Error(`Ownership handoff from ${progress.owner} to ${options.author.trim()} requires --confirm-handoff after the previous controller has stopped editing.`);
|
|
142
|
+
}
|
|
143
|
+
const previousOwner = progress.owner;
|
|
144
|
+
progress.owner = options.author.trim();
|
|
145
|
+
if (progress.assignment) {
|
|
146
|
+
progress.assignment.controller = options.author.trim();
|
|
147
|
+
progress.assignment.handoffs.push({
|
|
148
|
+
at: new Date().toISOString(),
|
|
149
|
+
from: previousOwner,
|
|
150
|
+
to: options.author.trim(),
|
|
151
|
+
confirmedStopped: true,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
const afterTicket = matter.stringify(this.ticketRepository.appendAudit(ticket.body, options.author, `Controller handoff from ${previousOwner} to ${options.author.trim()} confirmed after the previous controller stopped editing.`), this.ticketRepository.metadata(ticket));
|
|
155
|
+
this.workspaceTransaction.writeWorkflowState(feature, ticket, afterTicket, ledger);
|
|
156
|
+
handoffRecorded = true;
|
|
157
|
+
}
|
|
158
|
+
const currentTicket = this.ticketRepository.requireTicket(this.ticketRepository.readTickets(feature.workspace), options.ticket);
|
|
159
|
+
const identity = this.gitEvidence.contentIdentity(feature, currentTicket, progress.baseSha, progress.checks);
|
|
160
|
+
const reviewAssignments = progress.assignment?.action === "correct"
|
|
161
|
+
? ((progress.correctionAttempts ?? []).find((attempt) => attempt.assignmentId === progress.assignment?.id)?.reviewAssignments ?? [])
|
|
162
|
+
: progress.reviewAssignments;
|
|
163
|
+
const resumeProgress = reviewAssignments === undefined ? progress : { ...progress, reviewAssignments };
|
|
164
|
+
const reviewBlocker = (reviewAssignments ?? [])
|
|
165
|
+
.map((assignment) => this.reviewPolicy.reviewAssignmentBlocker(feature, progress, assignment, identity))
|
|
166
|
+
.find((reason) => reason !== undefined);
|
|
167
|
+
const blocker = this.assignmentPolicy.resumeBlocker(feature, progress) ??
|
|
168
|
+
reviewBlocker ??
|
|
169
|
+
this.checkPolicy.checkBlockers(feature, currentTicket, progress, identity)[0];
|
|
170
|
+
return {
|
|
171
|
+
ticket: options.ticket,
|
|
172
|
+
gate: progress.execution,
|
|
173
|
+
nextAction: this.workflowPolicy.nextAction(resumeProgress, blocker),
|
|
174
|
+
...(progress.owner ? { owner: progress.owner } : {}),
|
|
175
|
+
...(progress.assignment ? { assignment: progress.assignment } : {}),
|
|
176
|
+
correctionAttempts: progress.correctionAttempts ?? [],
|
|
177
|
+
...(progress.adjudication ? { adjudication: progress.adjudication } : {}),
|
|
178
|
+
...(reviewAssignments ? { reviewAssignments } : {}),
|
|
179
|
+
...(handoffRecorded ? { handoffRecorded: true } : {}),
|
|
180
|
+
...(blocker ? { blocker } : {}),
|
|
181
|
+
...identity,
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
deliveryResume(options) {
|
|
186
|
+
validateAuthor(options.author);
|
|
187
|
+
const feature = this.featureInspectionService.resolveFeature(options.spec);
|
|
188
|
+
return this.workspaceTransaction.run(feature.workspace, () => {
|
|
189
|
+
const ledger = this.ledgerRepository.readLedger(feature);
|
|
190
|
+
const delivery = ledger.delivery;
|
|
191
|
+
let handoffRecorded = false;
|
|
192
|
+
if (delivery.owner && delivery.owner !== options.author.trim()) {
|
|
193
|
+
if (!options.confirmHandoff) {
|
|
194
|
+
throw new Error(`Ownership handoff from ${delivery.owner} to ${options.author.trim()} requires --confirm-handoff after the previous controller has stopped editing.`);
|
|
195
|
+
}
|
|
196
|
+
const previousOwner = delivery.owner;
|
|
197
|
+
delivery.owner = options.author.trim();
|
|
198
|
+
if (delivery.assignment) {
|
|
199
|
+
delivery.assignment.controller = options.author.trim();
|
|
200
|
+
delivery.assignment.handoffs.push({
|
|
201
|
+
at: new Date().toISOString(),
|
|
202
|
+
from: previousOwner,
|
|
203
|
+
to: options.author.trim(),
|
|
204
|
+
confirmedStopped: true,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
delivery.history.push({
|
|
208
|
+
at: new Date().toISOString(),
|
|
209
|
+
author: options.author.trim(),
|
|
210
|
+
action: `controller-handoff:${previousOwner}`,
|
|
211
|
+
});
|
|
212
|
+
this.workspaceTransaction.writeLedger(feature, ledger);
|
|
213
|
+
handoffRecorded = true;
|
|
214
|
+
}
|
|
215
|
+
const tickets = this.ticketRepository.readTickets(feature.workspace);
|
|
216
|
+
const identity = this.gitEvidence.deliveryIdentity(feature, ledger, tickets);
|
|
217
|
+
const completion = delivery.finished
|
|
218
|
+
? this.featureInspectionService.deliveryStatus(feature, ledger, tickets)
|
|
219
|
+
: undefined;
|
|
220
|
+
const checkBlocker = this.checkPolicy.deliveryCheckBlockers(delivery, identity)[0];
|
|
221
|
+
let blocker = completion?.action === "completion-invalidated"
|
|
222
|
+
? `Finished delivery completion is invalidated: ${completion.blockers.join(" ")}`
|
|
223
|
+
: delivery.adjudication?.status === "pending"
|
|
224
|
+
? `Delivery correction is paused for adjudication (${delivery.adjudication.reason}).`
|
|
225
|
+
: checkBlocker;
|
|
226
|
+
let nextAction;
|
|
227
|
+
if (completion) {
|
|
228
|
+
nextAction = completion.action;
|
|
229
|
+
}
|
|
230
|
+
else if (delivery.adjudication?.status === "pending") {
|
|
231
|
+
nextAction = "adjudicate";
|
|
232
|
+
}
|
|
233
|
+
else if (delivery.assignment &&
|
|
234
|
+
["pending", "unavailable"].includes(delivery.assignment.status)) {
|
|
235
|
+
nextAction = "await-implementation-report";
|
|
236
|
+
}
|
|
237
|
+
else if (delivery.reviewAssignments?.some((assignment) => assignment.status === "pending")) {
|
|
238
|
+
nextAction = "await-review-report";
|
|
239
|
+
}
|
|
240
|
+
else if (checkBlocker) {
|
|
241
|
+
nextAction = "record-final-checks";
|
|
242
|
+
}
|
|
243
|
+
else if (delivery.execution === "changes-requested") {
|
|
244
|
+
nextAction = "correct";
|
|
245
|
+
}
|
|
246
|
+
else if (delivery.execution === "reviewing") {
|
|
247
|
+
nextAction = "review-or-finish";
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
nextAction = "review";
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
scope: "delivery",
|
|
254
|
+
gate: delivery.execution,
|
|
255
|
+
nextAction,
|
|
256
|
+
...(delivery.owner ? { owner: delivery.owner } : {}),
|
|
257
|
+
...(delivery.assignment ? { assignment: delivery.assignment } : {}),
|
|
258
|
+
correctionAttempts: delivery.correctionAttempts ?? [],
|
|
259
|
+
...(delivery.adjudication ? { adjudication: delivery.adjudication } : {}),
|
|
260
|
+
...(delivery.reviewAssignments ? { reviewAssignments: delivery.reviewAssignments } : {}),
|
|
261
|
+
...(handoffRecorded ? { handoffRecorded: true } : {}),
|
|
262
|
+
...(blocker ? { blocker } : {}),
|
|
263
|
+
retrospectiveRequired: ledger.retrospectiveRequired,
|
|
264
|
+
finished: !!delivery.finished,
|
|
265
|
+
...identity,
|
|
266
|
+
};
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|