@use-the-fork/spindle 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +3 -2
- package/build/Assets/Blueprints/Catalog.js +110 -0
- package/build/Assets/Blueprints/agents/general.agent.md +67 -0
- package/build/Assets/Blueprints/agents/sdd-code-reviewer.agent.md +62 -0
- package/build/Assets/Blueprints/agents/sdd-code-slop-reviewer.agent.md +59 -0
- package/build/Assets/Blueprints/agents/sdd-implementer.agent.md +77 -0
- package/build/Assets/Blueprints/agents/sdd-orchestrator.agent.md +42 -0
- package/build/Assets/Blueprints/agents/sdd-re-review.agent.md +57 -0
- package/build/Assets/Blueprints/agents/sdd-reviewer.agent.md +89 -0
- package/build/Assets/Blueprints/agents/sdd-task-reviewer.agent.md +54 -0
- package/build/Assets/Blueprints/agents/shared-codebase-analyzer.agent.md +151 -0
- package/build/Assets/Blueprints/agents/shared-codebase-locator.agent.md +130 -0
- package/build/Assets/Blueprints/agents/shared-codebase-pattern-finder.agent.md +247 -0
- package/build/Assets/Blueprints/agents/shared-explore.agent.md +53 -0
- package/build/Assets/Blueprints/agents/shared-r.agent.md +17 -0
- package/build/Assets/Blueprints/agents/shared-rw.agent.md +17 -0
- package/build/Assets/Blueprints/agents/shared-rwx.agent.md +17 -0
- package/build/Assets/Blueprints/agents/shared-web-search-researcher.agent.md +122 -0
- package/build/Assets/Blueprints/instructions/code-commenting.instructions.md +39 -0
- package/build/Assets/Blueprints/instructions/source-architecture.instructions.md +15 -0
- package/build/Assets/Blueprints/skills/asd-ste100/CREDITS.MD +1 -0
- package/build/Assets/Blueprints/skills/asd-ste100/SKILL.md +83 -0
- package/build/Assets/Blueprints/skills/asd-ste100/examples/before-after.md +107 -0
- package/build/Assets/Blueprints/skills/asd-ste100/references/writing-rules.md +65 -0
- package/build/Assets/Blueprints/skills/author-agent/SKILL.md +26 -0
- package/build/Assets/Blueprints/skills/author-design/SKILL.md +38 -0
- package/build/Assets/Blueprints/skills/author-instruction/SKILL.md +28 -0
- package/build/Assets/Blueprints/skills/author-skill/SKILL.md +26 -0
- package/build/Assets/Blueprints/skills/grill/SKILL.md +33 -0
- package/build/Assets/Blueprints/skills/grill-design/SKILL.md +41 -0
- package/build/Assets/Blueprints/skills/grill-with-docs/SKILL.md +14 -0
- package/build/Assets/Blueprints/skills/implement/SKILL.md +79 -0
- package/build/Assets/Blueprints/skills/implement-spec/SKILL.md +34 -0
- package/build/Assets/Blueprints/skills/improve-codebase-architecture/HTML-REPORT.md +129 -0
- package/build/Assets/Blueprints/skills/improve-codebase-architecture/SKILL.md +74 -0
- package/build/Assets/Blueprints/skills/int-code-review/SKILL.md +76 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/SKILL.md +238 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/_sections.md +36 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/_template.md +42 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-closing-brace-labels.md +112 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-empty-docblocks.md +132 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-narration.md +118 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-placeholder.md +130 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/defensive-generic-catch.md +153 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/defensive-impossible-null.md +157 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/defensive-missing-real.md +157 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-generic-placeholders.md +120 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-over-descriptive.md +108 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-suffix-abuse.md +167 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-type-in-name.md +100 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-dependency-creep.md +114 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-premature-interface.md +136 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-single-method-class.md +161 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-useless-wrapper.md +140 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-as-any-escape.md +127 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-debug-artifacts.md +144 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-hyper-consistent.md +84 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-no-hack-scars.md +113 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-trivial-boilerplate.md +191 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-doesnt-throw.md +132 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-mirror-implementation.md +125 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-mock-everything.md +148 -0
- package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-snapshot-abuse.md +131 -0
- package/build/Assets/Blueprints/skills/int-codebase-design/SKILL.md +116 -0
- package/build/Assets/Blueprints/skills/int-codebase-design/references/DEEPENING.md +37 -0
- package/build/Assets/Blueprints/skills/int-codebase-design/references/DESIGN-IT-TWICE.md +44 -0
- package/build/Assets/Blueprints/skills/int-domain-modeling/SKILL.md +78 -0
- package/build/Assets/Blueprints/skills/int-domain-modeling/references/ADR-FORMAT.md +47 -0
- package/build/Assets/Blueprints/skills/int-domain-modeling/references/GLOSSARY-FORMAT.md +60 -0
- package/build/Assets/Blueprints/skills/int-prototype/LOGIC.md +67 -0
- package/build/Assets/Blueprints/skills/int-prototype/SKILL.md +30 -0
- package/build/Assets/Blueprints/skills/int-prototype/UI.md +112 -0
- package/build/Assets/Blueprints/skills/int-retrospective/SKILL.md +88 -0
- package/build/Assets/Blueprints/skills/int-retrospective/assets/retrospective-prompt.md +38 -0
- package/build/Assets/Blueprints/skills/int-retrospective/references/assessment.md +56 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/SKILL.md +64 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/assets/implementer-prompt.md +40 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/assets/reviewer-prompt.md +43 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/aggregate-review.md +7 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/corrective-rounds.md +72 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/example-workflow.md +25 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/full-spec.md +72 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/implementer-corrections.md +9 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/review-inputs.md +26 -0
- package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/verification-evidence.md +7 -0
- package/build/Assets/Blueprints/skills/int-test-driven-development/SKILL.md +51 -0
- package/build/Assets/Blueprints/skills/int-test-driven-development/references/mocking.md +31 -0
- package/build/Assets/Blueprints/skills/int-test-driven-development/references/tests.md +68 -0
- package/build/Assets/Blueprints/skills/int-testing-agent-guidance/SKILL.md +53 -0
- package/build/Assets/Blueprints/skills/int-verification-before-completion/SKILL.md +25 -0
- package/build/Assets/Blueprints/skills/int-writing-agents/SKILL.md +66 -0
- package/build/Assets/Blueprints/skills/int-writing-for-agents/SKILL.md +86 -0
- package/build/Assets/Blueprints/skills/int-writing-instruction/SKILL.md +57 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/SKILL.md +129 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/assets/interactive.md +83 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/assets/reference.md +32 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/arguments.md +50 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/interactive-skills.md +53 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/persuasion-principles.md +220 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/reference-skills.md +20 -0
- package/build/Assets/Blueprints/skills/int-writing-skills/references/testing-skills.md +48 -0
- package/build/Assets/Blueprints/skills/retrospective/SKILL.md +16 -0
- package/build/Assets/Blueprints/skills/setup-project/SKILL.md +66 -0
- package/build/Assets/Blueprints/skills/to-spec/SKILL.md +53 -0
- package/build/Assets/Blueprints/skills/to-tickets/SKILL.md +49 -0
- package/build/Assets/Blueprints/skills/update-tooling/SKILL.md +86 -0
- package/build/Assets/Templates/Catalog.js +12 -0
- package/build/Assets/Templates/Catalog.ts +17 -0
- package/build/Assets/Templates/project/copilot-instructions.md +17 -0
- package/build/Assets/Templates/project/domain.md +51 -0
- package/build/Assets/Templates/project/ticket-tracker.md +33 -0
- package/build/Assets/Templates/sdd/design.md +25 -0
- package/build/Assets/Templates/sdd/spec.md +29 -0
- package/build/Assets/Templates/sdd/ticket.md +15 -0
- package/build/BlueprintManagement/BlueprintManagementServiceProvider.js +15 -0
- package/build/BlueprintManagement/Catalog/BlueprintCatalog.js +238 -0
- package/build/BlueprintManagement/Commands/BlueprintCommandOptions.js +9 -0
- package/build/BlueprintManagement/Commands/ConflictPolicy.js +6 -0
- package/build/BlueprintManagement/Commands/InitCommand.js +117 -0
- package/build/BlueprintManagement/Commands/Prompts.js +66 -0
- package/build/BlueprintManagement/Commands/UpdateCommand.js +86 -0
- package/build/BlueprintManagement/Handlers/Agents/AgentHandler.js +21 -0
- package/build/BlueprintManagement/Handlers/BlueprintHandlers.js +4 -0
- package/build/BlueprintManagement/Handlers/Instructions/InstructionHandler.js +21 -0
- package/build/BlueprintManagement/Handlers/Skills/SkillHandler.js +10 -0
- package/build/BlueprintManagement/Reconciliation/BlueprintCatalogPlanner.js +87 -0
- package/build/BlueprintManagement/Reconciliation/BlueprintRemovalCoordinator.js +78 -0
- package/build/BlueprintManagement/Services/BlueprintReconciliationService.js +391 -0
- package/build/BlueprintManagement/Services/InstallationService.js +54 -0
- package/build/BlueprintManagement/Support/BlueprintFiles.js +113 -0
- package/build/BlueprintManagement/Types/Blueprint.js +1 -0
- package/build/BlueprintManagement/Types/Installation.js +1 -0
- package/build/BlueprintManagement/Types/Reconciliation.js +1 -0
- package/build/BlueprintManagement/Versioning/BlueprintVersion.js +70 -0
- package/build/BlueprintManagement/Versioning/BlueprintVersioning.js +55 -0
- package/build/Commands/Command.js +3 -0
- package/build/Commands/CommandOptions.js +12 -0
- package/build/Config/Config.js +3 -0
- package/build/Foundation/Application.js +112 -0
- package/build/Foundation/ApplicationBuilder.js +23 -0
- package/build/Foundation/Bootstrap/Bootstrapper.js +2 -0
- package/build/Foundation/Bootstrap/DiscoverGitRoot.js +8 -0
- package/build/Foundation/Bootstrap/LoadConsoleArgs.js +47 -0
- package/build/Foundation/Bootstrap/LoadProjectConfiguration.js +144 -0
- package/build/Foundation/Bootstrap/RegisterProviders.js +12 -0
- package/build/Foundation/Bootstrap/RequireProjectConfiguration.js +14 -0
- package/build/Foundation/ConsoleInvocation.js +1 -0
- package/build/Foundation/Container.js +185 -0
- package/build/Foundation/FoundationServiceProvider.js +5 -0
- package/build/Foundation/Kernel.js +30 -0
- package/build/Foundation/Service.js +23 -0
- package/build/Foundation/ServiceProvider.js +12 -0
- package/build/Git/GitServiceProvider.js +8 -0
- package/build/Git/Services/GitService.js +26 -0
- package/build/Project/ProjectServiceProvider.js +8 -0
- package/build/Project/Services/ProjectService.js +124 -0
- package/build/SubagentDrivenDevelopment/Commands/ChecksRecordCommand.js +35 -0
- package/build/SubagentDrivenDevelopment/Commands/ChecksRequireCommand.js +33 -0
- package/build/SubagentDrivenDevelopment/Commands/ClaimCommand.js +23 -0
- package/build/SubagentDrivenDevelopment/Commands/CorrectCommand.js +37 -0
- package/build/SubagentDrivenDevelopment/Commands/CreateCommand.js +26 -0
- package/build/SubagentDrivenDevelopment/Commands/FindingCreateCommand.js +39 -0
- package/build/SubagentDrivenDevelopment/Commands/FindingUpdateCommand.js +36 -0
- package/build/SubagentDrivenDevelopment/Commands/FinishCommand.js +23 -0
- package/build/SubagentDrivenDevelopment/Commands/ImplementCommand.js +23 -0
- package/build/SubagentDrivenDevelopment/Commands/NextCommand.js +21 -0
- package/build/SubagentDrivenDevelopment/Commands/ReadyCommand.js +21 -0
- package/build/SubagentDrivenDevelopment/Commands/RequirementsDecideCommand.js +33 -0
- package/build/SubagentDrivenDevelopment/Commands/ResumeCommand.js +31 -0
- package/build/SubagentDrivenDevelopment/Commands/ReviewCommand.js +55 -0
- package/build/SubagentDrivenDevelopment/Commands/ReviewRequireCommand.js +12 -0
- package/build/SubagentDrivenDevelopment/Commands/StatusCommand.js +21 -0
- package/build/SubagentDrivenDevelopment/Commands/SubmitCommand.js +59 -0
- package/build/SubagentDrivenDevelopment/Commands/TicketCommentCommand.js +24 -0
- package/build/SubagentDrivenDevelopment/Commands/TicketCreateCommand.js +25 -0
- package/build/SubagentDrivenDevelopment/Commands/TicketUpdateCommand.js +31 -0
- package/build/SubagentDrivenDevelopment/Commands/groups.js +36 -0
- package/build/SubagentDrivenDevelopment/Evidence/GitEvidence.js +449 -0
- package/build/SubagentDrivenDevelopment/Evidence/ReportEvidence.js +80 -0
- package/build/SubagentDrivenDevelopment/Models/Assignments.js +1 -0
- package/build/SubagentDrivenDevelopment/Models/Evidence.js +1 -0
- package/build/SubagentDrivenDevelopment/Models/Findings.js +1 -0
- package/build/SubagentDrivenDevelopment/Models/Options.js +1 -0
- package/build/SubagentDrivenDevelopment/Models/Workflow.js +16 -0
- package/build/SubagentDrivenDevelopment/Persistence/AssignmentSchema.js +155 -0
- package/build/SubagentDrivenDevelopment/Persistence/CheckSchema.js +71 -0
- package/build/SubagentDrivenDevelopment/Persistence/FeatureWorkspace.js +76 -0
- package/build/SubagentDrivenDevelopment/Persistence/FileSystem.js +28 -0
- package/build/SubagentDrivenDevelopment/Persistence/FindingSchema.js +81 -0
- package/build/SubagentDrivenDevelopment/Persistence/LedgerRepository.js +322 -0
- package/build/SubagentDrivenDevelopment/Persistence/PathSafety.js +62 -0
- package/build/SubagentDrivenDevelopment/Persistence/ProgressSchema.js +95 -0
- package/build/SubagentDrivenDevelopment/Persistence/ReadinessReader.js +40 -0
- package/build/SubagentDrivenDevelopment/Persistence/ReviewSchema.js +68 -0
- package/build/SubagentDrivenDevelopment/Persistence/TicketRepository.js +147 -0
- package/build/SubagentDrivenDevelopment/Persistence/WorkspaceTransaction.js +282 -0
- package/build/SubagentDrivenDevelopment/Policies/AcceptancePolicy.js +100 -0
- package/build/SubagentDrivenDevelopment/Policies/AssignmentPolicy.js +36 -0
- package/build/SubagentDrivenDevelopment/Policies/CheckPolicy.js +93 -0
- package/build/SubagentDrivenDevelopment/Policies/FindingPolicy.js +53 -0
- package/build/SubagentDrivenDevelopment/Policies/InputValidation.js +36 -0
- package/build/SubagentDrivenDevelopment/Policies/ReadinessPolicy.js +26 -0
- package/build/SubagentDrivenDevelopment/Policies/RequiredReviewers.js +12 -0
- package/build/SubagentDrivenDevelopment/Policies/ReviewPolicy.js +87 -0
- package/build/SubagentDrivenDevelopment/Policies/WorkflowPolicy.js +85 -0
- package/build/SubagentDrivenDevelopment/Services/AssignmentService.js +269 -0
- package/build/SubagentDrivenDevelopment/Services/ChecksService.js +319 -0
- package/build/SubagentDrivenDevelopment/Services/CorrectionService.js +273 -0
- package/build/SubagentDrivenDevelopment/Services/DeliveryCorrectionService.js +253 -0
- package/build/SubagentDrivenDevelopment/Services/DeliveryFindingService.js +302 -0
- package/build/SubagentDrivenDevelopment/Services/DeliveryService.js +160 -0
- package/build/SubagentDrivenDevelopment/Services/DeliverySubmissionService.js +273 -0
- package/build/SubagentDrivenDevelopment/Services/FeatureCreationService.js +138 -0
- package/build/SubagentDrivenDevelopment/Services/FeatureInspectionService.js +399 -0
- package/build/SubagentDrivenDevelopment/Services/FindingService.js +392 -0
- package/build/SubagentDrivenDevelopment/Services/IntegrationService.js +258 -0
- package/build/SubagentDrivenDevelopment/Services/ReadinessService.js +21 -0
- package/build/SubagentDrivenDevelopment/Services/RequirementsService.js +216 -0
- package/build/SubagentDrivenDevelopment/Services/ReviewService.js +471 -0
- package/build/SubagentDrivenDevelopment/Services/SubmissionService.js +299 -0
- package/build/SubagentDrivenDevelopment/Services/TicketService.js +186 -0
- package/build/SubagentDrivenDevelopment/SubagentDrivenDevelopmentServiceProvider.js +113 -0
- package/build/Support/Records.js +5 -0
- package/build/Support/Repository.js +139 -0
- package/build/Support/Stacks.js +10 -0
- package/build/TemplateRendering/Computed/ComputedContextService.js +13 -0
- package/build/TemplateRendering/Computed/ComputedVariableProvider.js +6 -0
- package/build/TemplateRendering/Computed/ComputedVariableProviders.js +6 -0
- package/build/TemplateRendering/Computed/Providers/InternalSkillDescriptionProvider.js +6 -0
- package/build/TemplateRendering/Computed/Providers/SpindlePackageManagerProvider.js +11 -0
- package/build/TemplateRendering/Computed/ResolveRenderContext.js +18 -0
- package/build/TemplateRendering/Rendering/RenderText.js +43 -0
- package/build/TemplateRendering/TemplateRenderingServiceProvider.js +8 -0
- package/build/Tooling/Commands/ToolingAddCommand.js +29 -0
- package/build/Tooling/Commands/ToolingListCommand.js +19 -0
- package/build/Tooling/Commands/ToolingRemoveCommand.js +19 -0
- package/build/Tooling/Commands/ToolingUpdateCommand.js +30 -0
- package/build/Tooling/Commands/groups.js +4 -0
- package/build/Tooling/Models/ToolingEntry.js +1 -0
- package/build/Tooling/Services/ToolingService.js +278 -0
- package/build/Tooling/ToolingServiceProvider.js +15 -0
- package/build/cli.js +36 -0
- package/build/index.js +31 -0
- package/package.json +54 -3
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import matter from "gray-matter";
|
|
4
|
+
import { blueprintCatalog } from "../../BlueprintManagement/Catalog/BlueprintCatalog.js";
|
|
5
|
+
import { Service } from "../../Foundation/Service.js";
|
|
6
|
+
import { lstatIfPresent } from "../Persistence/FileSystem.js";
|
|
7
|
+
import { validateAuthor, validateTicketId, } from "../Policies/InputValidation.js";
|
|
8
|
+
import { requiredTicketReviewers, requiredDeliveryReviewers, } from "../Policies/RequiredReviewers.js";
|
|
9
|
+
export class ReviewService extends Service {
|
|
10
|
+
gitEvidence;
|
|
11
|
+
reportEvidence;
|
|
12
|
+
ticketRepository;
|
|
13
|
+
ledgerRepository;
|
|
14
|
+
workspaceTransaction;
|
|
15
|
+
assignmentPolicy;
|
|
16
|
+
checkPolicy;
|
|
17
|
+
reviewPolicy;
|
|
18
|
+
workflowPolicy;
|
|
19
|
+
featureInspectionService;
|
|
20
|
+
constructor(app, gitEvidence, reportEvidence, ticketRepository, ledgerRepository, workspaceTransaction, assignmentPolicy, checkPolicy, reviewPolicy, workflowPolicy, featureInspectionService) {
|
|
21
|
+
super(app);
|
|
22
|
+
this.gitEvidence = gitEvidence;
|
|
23
|
+
this.reportEvidence = reportEvidence;
|
|
24
|
+
this.ticketRepository = ticketRepository;
|
|
25
|
+
this.ledgerRepository = ledgerRepository;
|
|
26
|
+
this.workspaceTransaction = workspaceTransaction;
|
|
27
|
+
this.assignmentPolicy = assignmentPolicy;
|
|
28
|
+
this.checkPolicy = checkPolicy;
|
|
29
|
+
this.reviewPolicy = reviewPolicy;
|
|
30
|
+
this.workflowPolicy = workflowPolicy;
|
|
31
|
+
this.featureInspectionService = featureInspectionService;
|
|
32
|
+
}
|
|
33
|
+
reviewRequire(options) {
|
|
34
|
+
validateAuthor(options.author);
|
|
35
|
+
const agent = (options.type ?? options.agent ?? "").trim();
|
|
36
|
+
const details = options.details.trim();
|
|
37
|
+
if (!agent)
|
|
38
|
+
throw new Error("Review agent must not be empty.");
|
|
39
|
+
if (!details)
|
|
40
|
+
throw new Error("Specialist review details must not be empty.");
|
|
41
|
+
const catalogEntry = blueprintCatalog.agents[agent];
|
|
42
|
+
if (!catalogEntry) {
|
|
43
|
+
throw new Error(`Unknown review agent '${agent}' in the Blueprint Catalog.`);
|
|
44
|
+
}
|
|
45
|
+
if (catalogEntry.canReview !== true) {
|
|
46
|
+
throw new Error(`Review agent '${agent}' is not explicitly marked review-eligible.`);
|
|
47
|
+
}
|
|
48
|
+
if (options.scope === "delivery") {
|
|
49
|
+
if (options.ticket)
|
|
50
|
+
throw new Error("--ticket and --scope delivery are mutually exclusive.");
|
|
51
|
+
const feature = this.featureInspectionService.resolveFeature(options.spec);
|
|
52
|
+
return this.workspaceTransaction.run(feature.workspace, () => {
|
|
53
|
+
const ledger = this.ledgerRepository.readLedger(feature);
|
|
54
|
+
const delivery = ledger.delivery;
|
|
55
|
+
if (delivery.execution === "reviewing" || (delivery.reviewHistory?.length ?? 0) > 0) {
|
|
56
|
+
throw new Error("Delivery review assignments are already frozen.");
|
|
57
|
+
}
|
|
58
|
+
const existing = (delivery.reviewRequirements ?? []).find((requirement) => requirement.agent === agent);
|
|
59
|
+
if (existing) {
|
|
60
|
+
if (existing.details !== details || existing.author !== options.author.trim()) {
|
|
61
|
+
throw new Error(`Delivery specialist review for '${agent}' is already pinned with different details or author.`);
|
|
62
|
+
}
|
|
63
|
+
return { scope: "delivery", requirement: existing, idempotent: true };
|
|
64
|
+
}
|
|
65
|
+
if (ledger.reviewPolicy.delivery.includes(agent)) {
|
|
66
|
+
throw new Error(`Review agent '${agent}' is already required by the pinned delivery policy.`);
|
|
67
|
+
}
|
|
68
|
+
if (delivery.owner && delivery.owner !== options.author.trim()) {
|
|
69
|
+
throw new Error("Only the recorded delivery controller may require specialist reviews.");
|
|
70
|
+
}
|
|
71
|
+
delivery.owner = options.author.trim();
|
|
72
|
+
const requirement = {
|
|
73
|
+
agent,
|
|
74
|
+
details,
|
|
75
|
+
author: options.author.trim(),
|
|
76
|
+
requestedAt: new Date().toISOString(),
|
|
77
|
+
};
|
|
78
|
+
(delivery.reviewRequirements ??= []).push(requirement);
|
|
79
|
+
delivery.history.push({
|
|
80
|
+
at: new Date().toISOString(),
|
|
81
|
+
author: options.author.trim(),
|
|
82
|
+
action: `specialist-review-required:${agent}`,
|
|
83
|
+
});
|
|
84
|
+
this.workspaceTransaction.writeLedger(feature, ledger);
|
|
85
|
+
return { scope: "delivery", requirement };
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (options.scope !== undefined)
|
|
89
|
+
throw new Error("--scope must be 'delivery'.");
|
|
90
|
+
if (!options.ticket)
|
|
91
|
+
throw new Error("Review require needs --ticket or --scope delivery.");
|
|
92
|
+
validateTicketId(options.ticket);
|
|
93
|
+
const ticketId = options.ticket;
|
|
94
|
+
const feature = this.featureInspectionService.resolveFeature(options.spec);
|
|
95
|
+
return this.workspaceTransaction.run(feature.workspace, () => {
|
|
96
|
+
const ledger = this.ledgerRepository.readLedger(feature);
|
|
97
|
+
const ticket = this.ticketRepository.requireTicket(this.ticketRepository.readTickets(feature.workspace), ticketId);
|
|
98
|
+
const progress = this.workflowPolicy.requireProgress(ledger, ticketId);
|
|
99
|
+
if (!progress.owner || progress.owner !== options.author.trim()) {
|
|
100
|
+
throw new Error("Only the recorded ticket controller may require specialist reviews.");
|
|
101
|
+
}
|
|
102
|
+
const existing = (progress.reviewRequirements ?? []).find((requirement) => requirement.agent === agent);
|
|
103
|
+
if (existing) {
|
|
104
|
+
if (existing.details !== details || existing.author !== options.author.trim()) {
|
|
105
|
+
throw new Error(`Specialist review for '${agent}' is already pinned with different details or author.`);
|
|
106
|
+
}
|
|
107
|
+
return { ticket: ticketId, requirement: existing, idempotent: true };
|
|
108
|
+
}
|
|
109
|
+
if ((progress.reviewAssignments?.length ?? 0) > 0 ||
|
|
110
|
+
(progress.reviewHistory?.length ?? 0) > 0) {
|
|
111
|
+
throw new Error("Review assignments are already frozen; specialist requirements cannot be added.");
|
|
112
|
+
}
|
|
113
|
+
if (ledger.reviewPolicy.ticket.includes(agent)) {
|
|
114
|
+
throw new Error(`Review agent '${agent}' is already required by the pinned ticket policy.`);
|
|
115
|
+
}
|
|
116
|
+
const requirement = {
|
|
117
|
+
agent,
|
|
118
|
+
details,
|
|
119
|
+
author: options.author.trim(),
|
|
120
|
+
requestedAt: new Date().toISOString(),
|
|
121
|
+
};
|
|
122
|
+
(progress.reviewRequirements ??= []).push(requirement);
|
|
123
|
+
const afterTicket = matter.stringify(this.ticketRepository.appendAudit(ticket.body, options.author, `Specialist review required for '${agent}': ${details}.`), this.ticketRepository.metadata(ticket));
|
|
124
|
+
this.workspaceTransaction.writeWorkflowState(feature, ticket, afterTicket, ledger);
|
|
125
|
+
return { ticket: ticketId, requirement };
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
review(options) {
|
|
129
|
+
validateAuthor(options.author);
|
|
130
|
+
if (options.scope === "delivery")
|
|
131
|
+
return this.deliveryReview(options);
|
|
132
|
+
if (options.scope !== undefined)
|
|
133
|
+
throw new Error("--scope must be 'delivery'.");
|
|
134
|
+
if (!options.ticket)
|
|
135
|
+
throw new Error("Review requires --ticket or --scope delivery.");
|
|
136
|
+
validateTicketId(options.ticket);
|
|
137
|
+
const ticketId = options.ticket;
|
|
138
|
+
let confirmedInventory;
|
|
139
|
+
try {
|
|
140
|
+
confirmedInventory = JSON.parse(options.inventory);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
throw new Error("--inventory must be a JSON string array of Git-derived paths.");
|
|
144
|
+
}
|
|
145
|
+
if (!Array.isArray(confirmedInventory) ||
|
|
146
|
+
!confirmedInventory.every((path) => typeof path === "string" && path.length > 0) ||
|
|
147
|
+
new Set(confirmedInventory).size !== confirmedInventory.length) {
|
|
148
|
+
throw new Error("--inventory must be a JSON string array without duplicate paths.");
|
|
149
|
+
}
|
|
150
|
+
const feature = this.featureInspectionService.resolveFeature(options.spec);
|
|
151
|
+
return this.workspaceTransaction.run(feature.workspace, () => {
|
|
152
|
+
const ledger = this.ledgerRepository.readLedger(feature);
|
|
153
|
+
const ticket = this.ticketRepository.requireTicket(this.ticketRepository.readTickets(feature.workspace), ticketId);
|
|
154
|
+
const progress = this.workflowPolicy.requireProgress(ledger, ticketId);
|
|
155
|
+
if (progress.execution !== "reviewing" || !progress.assignment) {
|
|
156
|
+
throw new Error(`Ticket ${ticketId} must have a submitted implementation before review.`);
|
|
157
|
+
}
|
|
158
|
+
if (progress.owner !== options.author.trim()) {
|
|
159
|
+
throw new Error("Only the recorded controller may dispatch review.");
|
|
160
|
+
}
|
|
161
|
+
const implementationBlocker = this.assignmentPolicy.implementationReportBlocker(feature, progress.assignment);
|
|
162
|
+
if (implementationBlocker)
|
|
163
|
+
throw new Error(implementationBlocker);
|
|
164
|
+
if (!progress.baseSha)
|
|
165
|
+
throw new Error("Review requires the ticket's recorded Git baseline.");
|
|
166
|
+
const identity = this.gitEvidence.contentIdentity(feature, ticket, progress.baseSha, progress.checks);
|
|
167
|
+
const reviewCheckBlockers = this.checkPolicy.reviewCheckBlockers(feature, ticket, progress, identity);
|
|
168
|
+
if (reviewCheckBlockers.length > 0)
|
|
169
|
+
throw new Error(reviewCheckBlockers.join("\n"));
|
|
170
|
+
const inventory = this.gitEvidence.changedPaths(progress.baseSha);
|
|
171
|
+
const suppliedPaths = new Set(confirmedInventory);
|
|
172
|
+
if (suppliedPaths.size !== inventory.length ||
|
|
173
|
+
inventory.some((path) => !suppliedPaths.has(path))) {
|
|
174
|
+
throw new Error(`Controller-confirmed inventory does not match the current Git-derived inventory: ${JSON.stringify(inventory)}.`);
|
|
175
|
+
}
|
|
176
|
+
const requiredReviewers = requiredTicketReviewers(ledger, progress);
|
|
177
|
+
let existing = progress.reviewAssignments ?? [];
|
|
178
|
+
if (progress.assignment.action === "correct") {
|
|
179
|
+
const completeInitialCoverage = requiredReviewers.every(({ agent, details }) => {
|
|
180
|
+
const matching = existing.filter((assignment) => assignment.agent === agent && assignment.scope === "initial");
|
|
181
|
+
return (matching.length === 1 &&
|
|
182
|
+
matching[0].objective === details &&
|
|
183
|
+
matching[0].status === "submitted");
|
|
184
|
+
});
|
|
185
|
+
if (completeInitialCoverage) {
|
|
186
|
+
const attempt = progress.correctionAttempts?.find((candidate) => candidate.number === progress.assignment?.attempt);
|
|
187
|
+
if (!attempt)
|
|
188
|
+
throw new Error("Corrective assignment has no persisted attempt record.");
|
|
189
|
+
const correctionAssignments = attempt.reviewAssignments ?? [];
|
|
190
|
+
if (correctionAssignments.length > 0) {
|
|
191
|
+
if (correctionAssignments.length !== requiredReviewers.length ||
|
|
192
|
+
correctionAssignments.some((assignment, index) => assignment.scope !== "corrective" ||
|
|
193
|
+
assignment.attempt !== attempt.number ||
|
|
194
|
+
assignment.agent !== requiredReviewers[index]?.agent ||
|
|
195
|
+
assignment.objective !== requiredReviewers[index]?.details ||
|
|
196
|
+
assignment.snapshot !== identity.snapshot ||
|
|
197
|
+
assignment.codeSnapshot !== identity.codeSnapshot ||
|
|
198
|
+
assignment.requirementsSnapshot !== identity.requirementsSnapshot ||
|
|
199
|
+
JSON.stringify(assignment.inventory) !== JSON.stringify(inventory))) {
|
|
200
|
+
throw new Error("The frozen corrective review assignment no longer matches the current ticket snapshot.");
|
|
201
|
+
}
|
|
202
|
+
for (const assignment of correctionAssignments) {
|
|
203
|
+
const blocker = this.reviewPolicy.reviewSnapshotBlocker(feature, progress, assignment, identity);
|
|
204
|
+
if (blocker)
|
|
205
|
+
throw new Error(blocker);
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
ticket: ticketId,
|
|
209
|
+
execution: "reviewing",
|
|
210
|
+
assignments: correctionAssignments,
|
|
211
|
+
idempotent: true,
|
|
212
|
+
...identity,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
const assignments = requiredReviewers.map(({ agent, details }) => {
|
|
216
|
+
const catalogEntry = blueprintCatalog.agents[agent];
|
|
217
|
+
if (!catalogEntry || catalogEntry.canReview !== true) {
|
|
218
|
+
throw new Error(`Review agent '${agent}' is not explicitly marked review-eligible.`);
|
|
219
|
+
}
|
|
220
|
+
const id = randomUUID();
|
|
221
|
+
const report = join(feature.workspace, "reports", `${ticketId}-round-${attempt.number}-review-${id}.md`);
|
|
222
|
+
this.reportEvidence.assertReportPath(feature, report, false);
|
|
223
|
+
if (lstatIfPresent(report)) {
|
|
224
|
+
throw new Error(`Assigned report already exists and was preserved: ${report}.`);
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
id,
|
|
228
|
+
agent,
|
|
229
|
+
role: "review",
|
|
230
|
+
scope: "corrective",
|
|
231
|
+
attempt: attempt.number,
|
|
232
|
+
...(details ? { objective: details } : {}),
|
|
233
|
+
snapshot: identity.snapshot,
|
|
234
|
+
codeSnapshot: identity.codeSnapshot,
|
|
235
|
+
requirementsSnapshot: identity.requirementsSnapshot,
|
|
236
|
+
headSha: identity.headSha,
|
|
237
|
+
inventory,
|
|
238
|
+
report,
|
|
239
|
+
requestedAt: new Date().toISOString(),
|
|
240
|
+
status: "pending",
|
|
241
|
+
};
|
|
242
|
+
});
|
|
243
|
+
attempt.reviewAssignments = assignments;
|
|
244
|
+
const confirmedIdentity = this.gitEvidence.contentIdentity(feature, this.ticketRepository.requireTicket(this.ticketRepository.readTickets(feature.workspace), ticketId), progress.baseSha, progress.checks);
|
|
245
|
+
if (confirmedIdentity.snapshot !== identity.snapshot ||
|
|
246
|
+
JSON.stringify(this.gitEvidence.changedPaths(progress.baseSha)) !==
|
|
247
|
+
JSON.stringify(inventory)) {
|
|
248
|
+
delete attempt.reviewAssignments;
|
|
249
|
+
throw new Error("Ticket content changed while freezing corrective review; retry with a fresh inventory.");
|
|
250
|
+
}
|
|
251
|
+
const assignmentSummary = assignments
|
|
252
|
+
.map((assignment) => `${assignment.agent} (${assignment.id}) -> ${assignment.report}`)
|
|
253
|
+
.join("; ");
|
|
254
|
+
const afterTicket = matter.stringify(this.ticketRepository.appendAudit(ticket.body, options.author, `Corrective review assignments for attempt ${attempt.number} recorded: ${assignmentSummary}. Inventory and snapshot are frozen.`), this.ticketRepository.metadata(ticket));
|
|
255
|
+
this.workspaceTransaction.writeWorkflowState(feature, ticket, afterTicket, ledger);
|
|
256
|
+
return {
|
|
257
|
+
ticket: ticketId,
|
|
258
|
+
execution: "reviewing",
|
|
259
|
+
assignments,
|
|
260
|
+
...identity,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if (existing.length > 0) {
|
|
264
|
+
progress.reviewHistory = [...(progress.reviewHistory ?? []), ...existing];
|
|
265
|
+
delete progress.reviewAssignments;
|
|
266
|
+
existing = [];
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
if (existing.length > 0) {
|
|
270
|
+
if (existing.length !== requiredReviewers.length ||
|
|
271
|
+
existing.some((assignment, index) => assignment.agent !== requiredReviewers[index]?.agent ||
|
|
272
|
+
assignment.objective !== requiredReviewers[index]?.details ||
|
|
273
|
+
assignment.snapshot !== identity.snapshot ||
|
|
274
|
+
assignment.codeSnapshot !== identity.codeSnapshot ||
|
|
275
|
+
assignment.requirementsSnapshot !== identity.requirementsSnapshot ||
|
|
276
|
+
JSON.stringify(assignment.inventory) !== JSON.stringify(inventory))) {
|
|
277
|
+
throw new Error("The frozen review assignment no longer matches the current ticket snapshot.");
|
|
278
|
+
}
|
|
279
|
+
for (const assignment of existing) {
|
|
280
|
+
const blocker = this.reviewPolicy.reviewSnapshotBlocker(feature, progress, assignment, identity);
|
|
281
|
+
if (blocker)
|
|
282
|
+
throw new Error(blocker);
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
ticket: ticketId,
|
|
286
|
+
execution: "reviewing",
|
|
287
|
+
assignments: existing,
|
|
288
|
+
idempotent: true,
|
|
289
|
+
...identity,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const assignments = requiredReviewers.map(({ agent, details }) => {
|
|
293
|
+
const catalogEntry = blueprintCatalog.agents[agent];
|
|
294
|
+
if (!catalogEntry || catalogEntry.canReview !== true) {
|
|
295
|
+
throw new Error(`Review agent '${agent}' is not explicitly marked review-eligible.`);
|
|
296
|
+
}
|
|
297
|
+
const id = randomUUID();
|
|
298
|
+
const report = join(feature.workspace, "reports", `${ticketId}-review-${id}.md`);
|
|
299
|
+
this.reportEvidence.assertReportPath(feature, report, false);
|
|
300
|
+
if (lstatIfPresent(report)) {
|
|
301
|
+
throw new Error(`Assigned review report already exists and was preserved: ${report}.`);
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
id,
|
|
305
|
+
agent,
|
|
306
|
+
role: "review",
|
|
307
|
+
scope: "initial",
|
|
308
|
+
...(details ? { objective: details } : {}),
|
|
309
|
+
snapshot: identity.snapshot,
|
|
310
|
+
codeSnapshot: identity.codeSnapshot,
|
|
311
|
+
requirementsSnapshot: identity.requirementsSnapshot,
|
|
312
|
+
headSha: identity.headSha,
|
|
313
|
+
inventory,
|
|
314
|
+
report,
|
|
315
|
+
requestedAt: new Date().toISOString(),
|
|
316
|
+
status: "pending",
|
|
317
|
+
};
|
|
318
|
+
});
|
|
319
|
+
const confirmedTicket = this.ticketRepository.requireTicket(this.ticketRepository.readTickets(feature.workspace), ticketId);
|
|
320
|
+
const confirmedIdentity = this.gitEvidence.contentIdentity(feature, confirmedTicket, progress.baseSha, progress.checks);
|
|
321
|
+
const confirmedCurrentInventory = this.gitEvidence.changedPaths(progress.baseSha);
|
|
322
|
+
if (confirmedIdentity.snapshot !== identity.snapshot ||
|
|
323
|
+
JSON.stringify(confirmedCurrentInventory) !== JSON.stringify(inventory)) {
|
|
324
|
+
throw new Error("Ticket content changed while freezing review; retry with a fresh inventory.");
|
|
325
|
+
}
|
|
326
|
+
progress.reviewAssignments = assignments;
|
|
327
|
+
const assignmentSummary = assignments
|
|
328
|
+
.map((assignment) => `${assignment.agent} (${assignment.id}) -> ${assignment.report}`)
|
|
329
|
+
.join("; ");
|
|
330
|
+
const afterTicket = matter.stringify(this.ticketRepository.appendAudit(ticket.body, options.author, `Initial review assignment${assignments.length === 1 ? "" : "s"} recorded: ${assignmentSummary}. Inventory and snapshot are frozen.`), this.ticketRepository.metadata(ticket));
|
|
331
|
+
this.workspaceTransaction.writeWorkflowState(feature, ticket, afterTicket, ledger);
|
|
332
|
+
return {
|
|
333
|
+
ticket: ticketId,
|
|
334
|
+
execution: "reviewing",
|
|
335
|
+
assignments,
|
|
336
|
+
...identity,
|
|
337
|
+
};
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
deliveryReview(options) {
|
|
341
|
+
if (options.ticket)
|
|
342
|
+
throw new Error("--ticket and --scope delivery are mutually exclusive.");
|
|
343
|
+
let confirmedInventory;
|
|
344
|
+
try {
|
|
345
|
+
confirmedInventory = JSON.parse(options.inventory);
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
throw new Error("--inventory must be a JSON string array of Git-derived paths.");
|
|
349
|
+
}
|
|
350
|
+
if (!Array.isArray(confirmedInventory) ||
|
|
351
|
+
!confirmedInventory.every((path) => typeof path === "string" && path.length > 0) ||
|
|
352
|
+
new Set(confirmedInventory).size !== confirmedInventory.length) {
|
|
353
|
+
throw new Error("--inventory must be a JSON string array without duplicate paths.");
|
|
354
|
+
}
|
|
355
|
+
const feature = this.featureInspectionService.resolveFeature(options.spec);
|
|
356
|
+
return this.workspaceTransaction.run(feature.workspace, () => {
|
|
357
|
+
const ledger = this.ledgerRepository.readLedger(feature);
|
|
358
|
+
const tickets = this.ticketRepository.readTickets(feature.workspace);
|
|
359
|
+
const incomplete = ledger.tickets
|
|
360
|
+
.filter((progress) => progress.status !== "closed")
|
|
361
|
+
.map((progress) => progress.id);
|
|
362
|
+
if (incomplete.length > 0) {
|
|
363
|
+
throw new Error(`Feature review requires every ticket closed: ${incomplete.join(", ")}.`);
|
|
364
|
+
}
|
|
365
|
+
if (tickets.length !== ledger.tickets.length) {
|
|
366
|
+
throw new Error("Feature review requires every ticket document to be present.");
|
|
367
|
+
}
|
|
368
|
+
const delivery = ledger.delivery;
|
|
369
|
+
if (delivery.adjudication?.status === "pending") {
|
|
370
|
+
throw new Error("Delivery correction is paused for adjudication.");
|
|
371
|
+
}
|
|
372
|
+
delivery.owner = options.author.trim();
|
|
373
|
+
const identity = this.gitEvidence.deliveryIdentity(feature, ledger, tickets);
|
|
374
|
+
const blockers = this.checkPolicy.deliveryCheckBlockers(delivery, identity);
|
|
375
|
+
if (blockers.length > 0)
|
|
376
|
+
throw new Error(blockers.join("\n"));
|
|
377
|
+
const baseline = delivery.baseSha;
|
|
378
|
+
if (!baseline) {
|
|
379
|
+
throw new Error("Feature review requires a recorded feature baseline; create the feature again.");
|
|
380
|
+
}
|
|
381
|
+
const inventory = this.gitEvidence.changedPaths(baseline);
|
|
382
|
+
const suppliedPaths = new Set(confirmedInventory);
|
|
383
|
+
if (suppliedPaths.size !== inventory.length ||
|
|
384
|
+
inventory.some((path) => !suppliedPaths.has(path))) {
|
|
385
|
+
throw new Error(`Controller-confirmed delivery inventory does not match the current Git-derived inventory: ${JSON.stringify(inventory)}.`);
|
|
386
|
+
}
|
|
387
|
+
const required = requiredDeliveryReviewers(ledger, delivery);
|
|
388
|
+
const correctionNumber = delivery.assignment?.action === "correct" ? delivery.assignment.attempt : undefined;
|
|
389
|
+
let existing = delivery.reviewAssignments ?? [];
|
|
390
|
+
if (existing.length > 0) {
|
|
391
|
+
if (existing.length === required.length &&
|
|
392
|
+
existing.every((assignment, index) => assignment.scope === "delivery" &&
|
|
393
|
+
assignment.attempt === correctionNumber &&
|
|
394
|
+
assignment.agent === required[index]?.agent &&
|
|
395
|
+
assignment.objective === required[index]?.details &&
|
|
396
|
+
assignment.snapshot === identity.snapshot &&
|
|
397
|
+
assignment.codeSnapshot === identity.codeSnapshot &&
|
|
398
|
+
assignment.requirementsSnapshot === identity.requirementsSnapshot &&
|
|
399
|
+
JSON.stringify(assignment.inventory) === JSON.stringify(inventory))) {
|
|
400
|
+
for (const assignment of existing) {
|
|
401
|
+
const blocker = this.reviewPolicy.reviewSnapshotBlocker(feature, delivery, assignment, identity);
|
|
402
|
+
if (blocker)
|
|
403
|
+
throw new Error(blocker);
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
scope: "delivery",
|
|
407
|
+
execution: "reviewing",
|
|
408
|
+
assignments: existing,
|
|
409
|
+
idempotent: true,
|
|
410
|
+
...identity,
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
delivery.reviewHistory = [...(delivery.reviewHistory ?? []), ...existing];
|
|
414
|
+
delivery.reviewAssignments = [];
|
|
415
|
+
existing = [];
|
|
416
|
+
}
|
|
417
|
+
const assignments = required.map(({ agent, details }) => {
|
|
418
|
+
if (blueprintCatalog.agents[agent]?.canReview !== true) {
|
|
419
|
+
throw new Error(`Review agent '${agent}' is not explicitly marked review-eligible.`);
|
|
420
|
+
}
|
|
421
|
+
const id = randomUUID();
|
|
422
|
+
const report = join(feature.workspace, "reports", correctionNumber
|
|
423
|
+
? `final-round-${correctionNumber}-review-${id}.md`
|
|
424
|
+
: `final-review-${id}.md`);
|
|
425
|
+
this.reportEvidence.assertReportPath(feature, report, false);
|
|
426
|
+
if (lstatIfPresent(report)) {
|
|
427
|
+
throw new Error(`Assigned report already exists and was preserved: ${report}.`);
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
id,
|
|
431
|
+
agent,
|
|
432
|
+
role: "review",
|
|
433
|
+
scope: "delivery",
|
|
434
|
+
...(correctionNumber ? { attempt: correctionNumber } : {}),
|
|
435
|
+
...(details ? { objective: details } : {}),
|
|
436
|
+
snapshot: identity.snapshot,
|
|
437
|
+
codeSnapshot: identity.codeSnapshot,
|
|
438
|
+
requirementsSnapshot: identity.requirementsSnapshot,
|
|
439
|
+
headSha: identity.headSha,
|
|
440
|
+
inventory,
|
|
441
|
+
report,
|
|
442
|
+
requestedAt: new Date().toISOString(),
|
|
443
|
+
status: "pending",
|
|
444
|
+
};
|
|
445
|
+
});
|
|
446
|
+
delivery.reviewAssignments = assignments;
|
|
447
|
+
if (correctionNumber) {
|
|
448
|
+
const attempt = delivery.correctionAttempts?.find((candidate) => candidate.number === correctionNumber);
|
|
449
|
+
if (!attempt)
|
|
450
|
+
throw new Error("Delivery corrective review has no matching attempt record.");
|
|
451
|
+
attempt.reviewAssignments = assignments;
|
|
452
|
+
}
|
|
453
|
+
delivery.execution = "reviewing";
|
|
454
|
+
delivery.history.push({
|
|
455
|
+
at: new Date().toISOString(),
|
|
456
|
+
author: options.author.trim(),
|
|
457
|
+
action: "aggregate-review-dispatched",
|
|
458
|
+
});
|
|
459
|
+
const confirmed = this.gitEvidence.deliveryIdentity(feature, ledger, this.ticketRepository.readTickets(feature.workspace));
|
|
460
|
+
if (confirmed.snapshot !== identity.snapshot ||
|
|
461
|
+
JSON.stringify(this.gitEvidence.changedPaths(baseline)) !== JSON.stringify(inventory)) {
|
|
462
|
+
delivery.reviewAssignments = [];
|
|
463
|
+
delivery.execution = "unstarted";
|
|
464
|
+
delivery.history.pop();
|
|
465
|
+
throw new Error("Feature content changed while freezing aggregate review; retry with a fresh inventory.");
|
|
466
|
+
}
|
|
467
|
+
this.workspaceTransaction.writeLedger(feature, ledger);
|
|
468
|
+
return { scope: "delivery", execution: "reviewing", assignments, ...identity };
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|