@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.
Files changed (245) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +3 -2
  3. package/build/Assets/Blueprints/Catalog.js +110 -0
  4. package/build/Assets/Blueprints/agents/general.agent.md +67 -0
  5. package/build/Assets/Blueprints/agents/sdd-code-reviewer.agent.md +62 -0
  6. package/build/Assets/Blueprints/agents/sdd-code-slop-reviewer.agent.md +59 -0
  7. package/build/Assets/Blueprints/agents/sdd-implementer.agent.md +77 -0
  8. package/build/Assets/Blueprints/agents/sdd-orchestrator.agent.md +42 -0
  9. package/build/Assets/Blueprints/agents/sdd-re-review.agent.md +57 -0
  10. package/build/Assets/Blueprints/agents/sdd-reviewer.agent.md +89 -0
  11. package/build/Assets/Blueprints/agents/sdd-task-reviewer.agent.md +54 -0
  12. package/build/Assets/Blueprints/agents/shared-codebase-analyzer.agent.md +151 -0
  13. package/build/Assets/Blueprints/agents/shared-codebase-locator.agent.md +130 -0
  14. package/build/Assets/Blueprints/agents/shared-codebase-pattern-finder.agent.md +247 -0
  15. package/build/Assets/Blueprints/agents/shared-explore.agent.md +53 -0
  16. package/build/Assets/Blueprints/agents/shared-r.agent.md +17 -0
  17. package/build/Assets/Blueprints/agents/shared-rw.agent.md +17 -0
  18. package/build/Assets/Blueprints/agents/shared-rwx.agent.md +17 -0
  19. package/build/Assets/Blueprints/agents/shared-web-search-researcher.agent.md +122 -0
  20. package/build/Assets/Blueprints/instructions/code-commenting.instructions.md +39 -0
  21. package/build/Assets/Blueprints/instructions/source-architecture.instructions.md +15 -0
  22. package/build/Assets/Blueprints/skills/asd-ste100/CREDITS.MD +1 -0
  23. package/build/Assets/Blueprints/skills/asd-ste100/SKILL.md +83 -0
  24. package/build/Assets/Blueprints/skills/asd-ste100/examples/before-after.md +107 -0
  25. package/build/Assets/Blueprints/skills/asd-ste100/references/writing-rules.md +65 -0
  26. package/build/Assets/Blueprints/skills/author-agent/SKILL.md +26 -0
  27. package/build/Assets/Blueprints/skills/author-design/SKILL.md +38 -0
  28. package/build/Assets/Blueprints/skills/author-instruction/SKILL.md +28 -0
  29. package/build/Assets/Blueprints/skills/author-skill/SKILL.md +26 -0
  30. package/build/Assets/Blueprints/skills/grill/SKILL.md +33 -0
  31. package/build/Assets/Blueprints/skills/grill-design/SKILL.md +41 -0
  32. package/build/Assets/Blueprints/skills/grill-with-docs/SKILL.md +14 -0
  33. package/build/Assets/Blueprints/skills/implement/SKILL.md +79 -0
  34. package/build/Assets/Blueprints/skills/implement-spec/SKILL.md +34 -0
  35. package/build/Assets/Blueprints/skills/improve-codebase-architecture/HTML-REPORT.md +129 -0
  36. package/build/Assets/Blueprints/skills/improve-codebase-architecture/SKILL.md +74 -0
  37. package/build/Assets/Blueprints/skills/int-code-review/SKILL.md +76 -0
  38. package/build/Assets/Blueprints/skills/int-code-slop-review/SKILL.md +238 -0
  39. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/_sections.md +36 -0
  40. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/_template.md +42 -0
  41. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-closing-brace-labels.md +112 -0
  42. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-empty-docblocks.md +132 -0
  43. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-narration.md +118 -0
  44. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/comments-placeholder.md +130 -0
  45. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/defensive-generic-catch.md +153 -0
  46. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/defensive-impossible-null.md +157 -0
  47. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/defensive-missing-real.md +157 -0
  48. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-generic-placeholders.md +120 -0
  49. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-over-descriptive.md +108 -0
  50. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-suffix-abuse.md +167 -0
  51. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/naming-type-in-name.md +100 -0
  52. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-dependency-creep.md +114 -0
  53. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-premature-interface.md +136 -0
  54. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-single-method-class.md +161 -0
  55. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/over-eng-useless-wrapper.md +140 -0
  56. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-as-any-escape.md +127 -0
  57. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-debug-artifacts.md +144 -0
  58. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-hyper-consistent.md +84 -0
  59. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-no-hack-scars.md +113 -0
  60. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/style-trivial-boilerplate.md +191 -0
  61. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-doesnt-throw.md +132 -0
  62. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-mirror-implementation.md +125 -0
  63. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-mock-everything.md +148 -0
  64. package/build/Assets/Blueprints/skills/int-code-slop-review/rules/test-snapshot-abuse.md +131 -0
  65. package/build/Assets/Blueprints/skills/int-codebase-design/SKILL.md +116 -0
  66. package/build/Assets/Blueprints/skills/int-codebase-design/references/DEEPENING.md +37 -0
  67. package/build/Assets/Blueprints/skills/int-codebase-design/references/DESIGN-IT-TWICE.md +44 -0
  68. package/build/Assets/Blueprints/skills/int-domain-modeling/SKILL.md +78 -0
  69. package/build/Assets/Blueprints/skills/int-domain-modeling/references/ADR-FORMAT.md +47 -0
  70. package/build/Assets/Blueprints/skills/int-domain-modeling/references/GLOSSARY-FORMAT.md +60 -0
  71. package/build/Assets/Blueprints/skills/int-prototype/LOGIC.md +67 -0
  72. package/build/Assets/Blueprints/skills/int-prototype/SKILL.md +30 -0
  73. package/build/Assets/Blueprints/skills/int-prototype/UI.md +112 -0
  74. package/build/Assets/Blueprints/skills/int-retrospective/SKILL.md +88 -0
  75. package/build/Assets/Blueprints/skills/int-retrospective/assets/retrospective-prompt.md +38 -0
  76. package/build/Assets/Blueprints/skills/int-retrospective/references/assessment.md +56 -0
  77. package/build/Assets/Blueprints/skills/int-subagent-driven-dev/SKILL.md +64 -0
  78. package/build/Assets/Blueprints/skills/int-subagent-driven-dev/assets/implementer-prompt.md +40 -0
  79. package/build/Assets/Blueprints/skills/int-subagent-driven-dev/assets/reviewer-prompt.md +43 -0
  80. package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/aggregate-review.md +7 -0
  81. package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/corrective-rounds.md +72 -0
  82. package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/example-workflow.md +25 -0
  83. package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/full-spec.md +72 -0
  84. package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/implementer-corrections.md +9 -0
  85. package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/review-inputs.md +26 -0
  86. package/build/Assets/Blueprints/skills/int-subagent-driven-dev/references/verification-evidence.md +7 -0
  87. package/build/Assets/Blueprints/skills/int-test-driven-development/SKILL.md +51 -0
  88. package/build/Assets/Blueprints/skills/int-test-driven-development/references/mocking.md +31 -0
  89. package/build/Assets/Blueprints/skills/int-test-driven-development/references/tests.md +68 -0
  90. package/build/Assets/Blueprints/skills/int-testing-agent-guidance/SKILL.md +53 -0
  91. package/build/Assets/Blueprints/skills/int-verification-before-completion/SKILL.md +25 -0
  92. package/build/Assets/Blueprints/skills/int-writing-agents/SKILL.md +66 -0
  93. package/build/Assets/Blueprints/skills/int-writing-for-agents/SKILL.md +86 -0
  94. package/build/Assets/Blueprints/skills/int-writing-instruction/SKILL.md +57 -0
  95. package/build/Assets/Blueprints/skills/int-writing-skills/SKILL.md +129 -0
  96. package/build/Assets/Blueprints/skills/int-writing-skills/assets/interactive.md +83 -0
  97. package/build/Assets/Blueprints/skills/int-writing-skills/assets/reference.md +32 -0
  98. package/build/Assets/Blueprints/skills/int-writing-skills/references/arguments.md +50 -0
  99. package/build/Assets/Blueprints/skills/int-writing-skills/references/interactive-skills.md +53 -0
  100. package/build/Assets/Blueprints/skills/int-writing-skills/references/persuasion-principles.md +220 -0
  101. package/build/Assets/Blueprints/skills/int-writing-skills/references/reference-skills.md +20 -0
  102. package/build/Assets/Blueprints/skills/int-writing-skills/references/testing-skills.md +48 -0
  103. package/build/Assets/Blueprints/skills/retrospective/SKILL.md +16 -0
  104. package/build/Assets/Blueprints/skills/setup-project/SKILL.md +66 -0
  105. package/build/Assets/Blueprints/skills/to-spec/SKILL.md +53 -0
  106. package/build/Assets/Blueprints/skills/to-tickets/SKILL.md +49 -0
  107. package/build/Assets/Blueprints/skills/update-tooling/SKILL.md +86 -0
  108. package/build/Assets/Templates/Catalog.js +12 -0
  109. package/build/Assets/Templates/Catalog.ts +17 -0
  110. package/build/Assets/Templates/project/copilot-instructions.md +17 -0
  111. package/build/Assets/Templates/project/domain.md +51 -0
  112. package/build/Assets/Templates/project/ticket-tracker.md +33 -0
  113. package/build/Assets/Templates/sdd/design.md +25 -0
  114. package/build/Assets/Templates/sdd/spec.md +29 -0
  115. package/build/Assets/Templates/sdd/ticket.md +15 -0
  116. package/build/BlueprintManagement/BlueprintManagementServiceProvider.js +15 -0
  117. package/build/BlueprintManagement/Catalog/BlueprintCatalog.js +238 -0
  118. package/build/BlueprintManagement/Commands/BlueprintCommandOptions.js +9 -0
  119. package/build/BlueprintManagement/Commands/ConflictPolicy.js +6 -0
  120. package/build/BlueprintManagement/Commands/InitCommand.js +117 -0
  121. package/build/BlueprintManagement/Commands/Prompts.js +66 -0
  122. package/build/BlueprintManagement/Commands/UpdateCommand.js +86 -0
  123. package/build/BlueprintManagement/Handlers/Agents/AgentHandler.js +21 -0
  124. package/build/BlueprintManagement/Handlers/BlueprintHandlers.js +4 -0
  125. package/build/BlueprintManagement/Handlers/Instructions/InstructionHandler.js +21 -0
  126. package/build/BlueprintManagement/Handlers/Skills/SkillHandler.js +10 -0
  127. package/build/BlueprintManagement/Reconciliation/BlueprintCatalogPlanner.js +87 -0
  128. package/build/BlueprintManagement/Reconciliation/BlueprintRemovalCoordinator.js +78 -0
  129. package/build/BlueprintManagement/Services/BlueprintReconciliationService.js +391 -0
  130. package/build/BlueprintManagement/Services/InstallationService.js +54 -0
  131. package/build/BlueprintManagement/Support/BlueprintFiles.js +113 -0
  132. package/build/BlueprintManagement/Types/Blueprint.js +1 -0
  133. package/build/BlueprintManagement/Types/Installation.js +1 -0
  134. package/build/BlueprintManagement/Types/Reconciliation.js +1 -0
  135. package/build/BlueprintManagement/Versioning/BlueprintVersion.js +70 -0
  136. package/build/BlueprintManagement/Versioning/BlueprintVersioning.js +55 -0
  137. package/build/Commands/Command.js +3 -0
  138. package/build/Commands/CommandOptions.js +12 -0
  139. package/build/Config/Config.js +3 -0
  140. package/build/Foundation/Application.js +112 -0
  141. package/build/Foundation/ApplicationBuilder.js +23 -0
  142. package/build/Foundation/Bootstrap/Bootstrapper.js +2 -0
  143. package/build/Foundation/Bootstrap/DiscoverGitRoot.js +8 -0
  144. package/build/Foundation/Bootstrap/LoadConsoleArgs.js +47 -0
  145. package/build/Foundation/Bootstrap/LoadProjectConfiguration.js +144 -0
  146. package/build/Foundation/Bootstrap/RegisterProviders.js +12 -0
  147. package/build/Foundation/Bootstrap/RequireProjectConfiguration.js +14 -0
  148. package/build/Foundation/ConsoleInvocation.js +1 -0
  149. package/build/Foundation/Container.js +185 -0
  150. package/build/Foundation/FoundationServiceProvider.js +5 -0
  151. package/build/Foundation/Kernel.js +30 -0
  152. package/build/Foundation/Service.js +23 -0
  153. package/build/Foundation/ServiceProvider.js +12 -0
  154. package/build/Git/GitServiceProvider.js +8 -0
  155. package/build/Git/Services/GitService.js +26 -0
  156. package/build/Project/ProjectServiceProvider.js +8 -0
  157. package/build/Project/Services/ProjectService.js +124 -0
  158. package/build/SubagentDrivenDevelopment/Commands/ChecksRecordCommand.js +35 -0
  159. package/build/SubagentDrivenDevelopment/Commands/ChecksRequireCommand.js +33 -0
  160. package/build/SubagentDrivenDevelopment/Commands/ClaimCommand.js +23 -0
  161. package/build/SubagentDrivenDevelopment/Commands/CorrectCommand.js +37 -0
  162. package/build/SubagentDrivenDevelopment/Commands/CreateCommand.js +26 -0
  163. package/build/SubagentDrivenDevelopment/Commands/FindingCreateCommand.js +39 -0
  164. package/build/SubagentDrivenDevelopment/Commands/FindingUpdateCommand.js +36 -0
  165. package/build/SubagentDrivenDevelopment/Commands/FinishCommand.js +23 -0
  166. package/build/SubagentDrivenDevelopment/Commands/ImplementCommand.js +23 -0
  167. package/build/SubagentDrivenDevelopment/Commands/NextCommand.js +21 -0
  168. package/build/SubagentDrivenDevelopment/Commands/ReadyCommand.js +21 -0
  169. package/build/SubagentDrivenDevelopment/Commands/RequirementsDecideCommand.js +33 -0
  170. package/build/SubagentDrivenDevelopment/Commands/ResumeCommand.js +31 -0
  171. package/build/SubagentDrivenDevelopment/Commands/ReviewCommand.js +55 -0
  172. package/build/SubagentDrivenDevelopment/Commands/ReviewRequireCommand.js +12 -0
  173. package/build/SubagentDrivenDevelopment/Commands/StatusCommand.js +21 -0
  174. package/build/SubagentDrivenDevelopment/Commands/SubmitCommand.js +59 -0
  175. package/build/SubagentDrivenDevelopment/Commands/TicketCommentCommand.js +24 -0
  176. package/build/SubagentDrivenDevelopment/Commands/TicketCreateCommand.js +25 -0
  177. package/build/SubagentDrivenDevelopment/Commands/TicketUpdateCommand.js +31 -0
  178. package/build/SubagentDrivenDevelopment/Commands/groups.js +36 -0
  179. package/build/SubagentDrivenDevelopment/Evidence/GitEvidence.js +449 -0
  180. package/build/SubagentDrivenDevelopment/Evidence/ReportEvidence.js +80 -0
  181. package/build/SubagentDrivenDevelopment/Models/Assignments.js +1 -0
  182. package/build/SubagentDrivenDevelopment/Models/Evidence.js +1 -0
  183. package/build/SubagentDrivenDevelopment/Models/Findings.js +1 -0
  184. package/build/SubagentDrivenDevelopment/Models/Options.js +1 -0
  185. package/build/SubagentDrivenDevelopment/Models/Workflow.js +16 -0
  186. package/build/SubagentDrivenDevelopment/Persistence/AssignmentSchema.js +155 -0
  187. package/build/SubagentDrivenDevelopment/Persistence/CheckSchema.js +71 -0
  188. package/build/SubagentDrivenDevelopment/Persistence/FeatureWorkspace.js +76 -0
  189. package/build/SubagentDrivenDevelopment/Persistence/FileSystem.js +28 -0
  190. package/build/SubagentDrivenDevelopment/Persistence/FindingSchema.js +81 -0
  191. package/build/SubagentDrivenDevelopment/Persistence/LedgerRepository.js +322 -0
  192. package/build/SubagentDrivenDevelopment/Persistence/PathSafety.js +62 -0
  193. package/build/SubagentDrivenDevelopment/Persistence/ProgressSchema.js +95 -0
  194. package/build/SubagentDrivenDevelopment/Persistence/ReadinessReader.js +40 -0
  195. package/build/SubagentDrivenDevelopment/Persistence/ReviewSchema.js +68 -0
  196. package/build/SubagentDrivenDevelopment/Persistence/TicketRepository.js +147 -0
  197. package/build/SubagentDrivenDevelopment/Persistence/WorkspaceTransaction.js +282 -0
  198. package/build/SubagentDrivenDevelopment/Policies/AcceptancePolicy.js +100 -0
  199. package/build/SubagentDrivenDevelopment/Policies/AssignmentPolicy.js +36 -0
  200. package/build/SubagentDrivenDevelopment/Policies/CheckPolicy.js +93 -0
  201. package/build/SubagentDrivenDevelopment/Policies/FindingPolicy.js +53 -0
  202. package/build/SubagentDrivenDevelopment/Policies/InputValidation.js +36 -0
  203. package/build/SubagentDrivenDevelopment/Policies/ReadinessPolicy.js +26 -0
  204. package/build/SubagentDrivenDevelopment/Policies/RequiredReviewers.js +12 -0
  205. package/build/SubagentDrivenDevelopment/Policies/ReviewPolicy.js +87 -0
  206. package/build/SubagentDrivenDevelopment/Policies/WorkflowPolicy.js +85 -0
  207. package/build/SubagentDrivenDevelopment/Services/AssignmentService.js +269 -0
  208. package/build/SubagentDrivenDevelopment/Services/ChecksService.js +319 -0
  209. package/build/SubagentDrivenDevelopment/Services/CorrectionService.js +273 -0
  210. package/build/SubagentDrivenDevelopment/Services/DeliveryCorrectionService.js +253 -0
  211. package/build/SubagentDrivenDevelopment/Services/DeliveryFindingService.js +302 -0
  212. package/build/SubagentDrivenDevelopment/Services/DeliveryService.js +160 -0
  213. package/build/SubagentDrivenDevelopment/Services/DeliverySubmissionService.js +273 -0
  214. package/build/SubagentDrivenDevelopment/Services/FeatureCreationService.js +138 -0
  215. package/build/SubagentDrivenDevelopment/Services/FeatureInspectionService.js +399 -0
  216. package/build/SubagentDrivenDevelopment/Services/FindingService.js +392 -0
  217. package/build/SubagentDrivenDevelopment/Services/IntegrationService.js +258 -0
  218. package/build/SubagentDrivenDevelopment/Services/ReadinessService.js +21 -0
  219. package/build/SubagentDrivenDevelopment/Services/RequirementsService.js +216 -0
  220. package/build/SubagentDrivenDevelopment/Services/ReviewService.js +471 -0
  221. package/build/SubagentDrivenDevelopment/Services/SubmissionService.js +299 -0
  222. package/build/SubagentDrivenDevelopment/Services/TicketService.js +186 -0
  223. package/build/SubagentDrivenDevelopment/SubagentDrivenDevelopmentServiceProvider.js +113 -0
  224. package/build/Support/Records.js +5 -0
  225. package/build/Support/Repository.js +139 -0
  226. package/build/Support/Stacks.js +10 -0
  227. package/build/TemplateRendering/Computed/ComputedContextService.js +13 -0
  228. package/build/TemplateRendering/Computed/ComputedVariableProvider.js +6 -0
  229. package/build/TemplateRendering/Computed/ComputedVariableProviders.js +6 -0
  230. package/build/TemplateRendering/Computed/Providers/InternalSkillDescriptionProvider.js +6 -0
  231. package/build/TemplateRendering/Computed/Providers/SpindlePackageManagerProvider.js +11 -0
  232. package/build/TemplateRendering/Computed/ResolveRenderContext.js +18 -0
  233. package/build/TemplateRendering/Rendering/RenderText.js +43 -0
  234. package/build/TemplateRendering/TemplateRenderingServiceProvider.js +8 -0
  235. package/build/Tooling/Commands/ToolingAddCommand.js +29 -0
  236. package/build/Tooling/Commands/ToolingListCommand.js +19 -0
  237. package/build/Tooling/Commands/ToolingRemoveCommand.js +19 -0
  238. package/build/Tooling/Commands/ToolingUpdateCommand.js +30 -0
  239. package/build/Tooling/Commands/groups.js +4 -0
  240. package/build/Tooling/Models/ToolingEntry.js +1 -0
  241. package/build/Tooling/Services/ToolingService.js +278 -0
  242. package/build/Tooling/ToolingServiceProvider.js +15 -0
  243. package/build/cli.js +36 -0
  244. package/build/index.js +31 -0
  245. package/package.json +54 -3
@@ -0,0 +1,392 @@
1
+ import { isAbsolute, resolve } from "node:path";
2
+ import matter from "gray-matter";
3
+ import { Service } from "../../Foundation/Service.js";
4
+ import { validateAuthor, validateTicketId, } from "../Policies/InputValidation.js";
5
+ import { requiredTicketReviewers } from "../Policies/RequiredReviewers.js";
6
+ export class FindingService extends Service {
7
+ gitEvidence;
8
+ reportEvidence;
9
+ ticketRepository;
10
+ ledgerRepository;
11
+ workspaceTransaction;
12
+ checkPolicy;
13
+ findingPolicy;
14
+ reviewPolicy;
15
+ workflowPolicy;
16
+ deliveryFindingService;
17
+ featureInspectionService;
18
+ constructor(app, gitEvidence, reportEvidence, ticketRepository, ledgerRepository, workspaceTransaction, checkPolicy, findingPolicy, reviewPolicy, workflowPolicy, deliveryFindingService, 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.checkPolicy = checkPolicy;
26
+ this.findingPolicy = findingPolicy;
27
+ this.reviewPolicy = reviewPolicy;
28
+ this.workflowPolicy = workflowPolicy;
29
+ this.deliveryFindingService = deliveryFindingService;
30
+ this.featureInspectionService = featureInspectionService;
31
+ }
32
+ findingCreate(options) {
33
+ if (options.scope === "delivery") {
34
+ if (options.ticket)
35
+ throw new Error("--ticket and --scope delivery are mutually exclusive.");
36
+ return this.deliveryFindingService.deliveryFindingCreate(options);
37
+ }
38
+ if (options.scope !== undefined)
39
+ throw new Error("--scope must be 'delivery'.");
40
+ if (options.relatedTicket !== undefined) {
41
+ throw new Error("--related-ticket is only valid for a delivery finding.");
42
+ }
43
+ if (!options.ticket)
44
+ throw new Error("Finding creation requires --ticket or --scope delivery.");
45
+ validateTicketId(options.ticket);
46
+ validateAuthor(options.author);
47
+ if (!["high", "medium", "low"].includes(options.severity)) {
48
+ throw new Error(`Invalid finding severity: ${options.severity}.`);
49
+ }
50
+ if (options.blocking === options.nonBlocking) {
51
+ throw new Error("Choose exactly one of --blocking or --non-blocking.");
52
+ }
53
+ if (!options.summary.trim())
54
+ throw new Error("Finding summary must not be empty.");
55
+ if (!options.details.trim())
56
+ throw new Error("Finding details must not be empty.");
57
+ if (!/^[0-9a-f]{64}$/i.test(options.snapshot)) {
58
+ throw new Error("Finding snapshot must be a CLI-returned content snapshot.");
59
+ }
60
+ const feature = this.featureInspectionService.resolveFeature(options.spec);
61
+ return this.workspaceTransaction.run(feature.workspace, () => {
62
+ const ledger = this.ledgerRepository.readLedger(feature);
63
+ const ticket = this.ticketRepository.requireTicket(this.ticketRepository.readTickets(feature.workspace), options.ticket);
64
+ const progress = this.workflowPolicy.requireProgress(ledger, options.ticket);
65
+ if (progress.owner !== options.author.trim()) {
66
+ throw new Error("Only the current ticket controller may register findings.");
67
+ }
68
+ const report = options.source
69
+ ? isAbsolute(options.source)
70
+ ? resolve(options.source)
71
+ : resolve(feature.workspace, options.source)
72
+ : "";
73
+ const correctionAttempt = progress.correctionAttempts?.find((attempt) => attempt.reviewAssignments?.some((candidate) => candidate.report === report && candidate.snapshot === options.snapshot));
74
+ const assignment = correctionAttempt?.reviewAssignments?.find((candidate) => candidate.report === report && candidate.snapshot === options.snapshot) ??
75
+ progress.reviewAssignments?.find((candidate) => candidate.report === report && candidate.snapshot === options.snapshot);
76
+ if (!assignment) {
77
+ throw new Error("Finding source report and snapshot must match a frozen review assignment for this ticket.");
78
+ }
79
+ if (assignment.status === "submitted") {
80
+ throw new Error("Findings must be registered before submitting the review assignment.");
81
+ }
82
+ if (!progress.baseSha)
83
+ throw new Error("Finding registration requires a recorded Git baseline.");
84
+ const identity = this.gitEvidence.contentIdentity(feature, ticket, progress.baseSha, progress.checks);
85
+ const snapshotBlocker = this.reviewPolicy.reviewSnapshotBlocker(feature, progress, assignment, identity);
86
+ if (snapshotBlocker)
87
+ throw new Error(snapshotBlocker);
88
+ this.reportEvidence.readReportDigest(feature, report);
89
+ const source = {
90
+ report,
91
+ snapshot: options.snapshot,
92
+ author: options.author.trim(),
93
+ summary: options.summary.trim(),
94
+ details: options.details.trim(),
95
+ };
96
+ const findings = progress.findings ?? [];
97
+ if (options.link !== undefined) {
98
+ if (!/^F-\d{4,}$/.test(options.link)) {
99
+ throw new Error(`Invalid canonical finding ID to link: ${options.link}.`);
100
+ }
101
+ const canonical = findings.find((finding) => finding.id === options.link);
102
+ if (!canonical)
103
+ throw new Error(`Canonical finding not found: ${options.link}.`);
104
+ const priorSource = canonical.provenance.find((provenance) => provenance.report === source.report && provenance.snapshot === source.snapshot);
105
+ if (priorSource) {
106
+ if (priorSource.author !== source.author ||
107
+ priorSource.summary !== source.summary ||
108
+ priorSource.details !== source.details) {
109
+ throw new Error("This report and snapshot are already linked with different details.");
110
+ }
111
+ return {
112
+ ticket: options.ticket,
113
+ finding: canonical,
114
+ idempotent: true,
115
+ snapshot: identity.snapshot,
116
+ headSha: identity.headSha,
117
+ };
118
+ }
119
+ canonical.provenance.push(source);
120
+ canonical.history.push({
121
+ at: new Date().toISOString(),
122
+ author: options.author.trim(),
123
+ action: "observation-linked",
124
+ details: `Linked observation from ${report} at snapshot ${options.snapshot}: ${source.summary} — ${source.details}`,
125
+ });
126
+ if (canonical.status === "fixed" || canonical.status === "dismissed") {
127
+ canonical.status = "open";
128
+ delete canonical.rulingDetails;
129
+ delete canonical.followUp;
130
+ delete canonical.requirementsDecision;
131
+ delete canonical.verification;
132
+ canonical.history.push({
133
+ at: new Date().toISOString(),
134
+ author: options.author.trim(),
135
+ action: "observation-reopened",
136
+ details: `New observation from ${report} at snapshot ${options.snapshot} requires renewed adjudication.`,
137
+ });
138
+ }
139
+ const afterTicket = matter.stringify(this.ticketRepository.appendAudit(ticket.body, options.author, `Observation linked to finding ${canonical.id}: source=${report}; snapshot=${options.snapshot}; summary=${source.summary}.`), this.ticketRepository.metadata(ticket));
140
+ this.workspaceTransaction.writeWorkflowState(feature, ticket, afterTicket, ledger);
141
+ return {
142
+ ticket: options.ticket,
143
+ finding: canonical,
144
+ snapshot: identity.snapshot,
145
+ headSha: identity.headSha,
146
+ };
147
+ }
148
+ const existing = findings.find((finding) => finding.author === options.author.trim() &&
149
+ finding.severity === options.severity &&
150
+ finding.blocking === (options.blocking === true) &&
151
+ finding.summary === options.summary.trim() &&
152
+ finding.details === options.details.trim() &&
153
+ finding.provenance.some((provenance) => provenance.report === source.report &&
154
+ provenance.snapshot === source.snapshot &&
155
+ provenance.author === source.author &&
156
+ provenance.summary === source.summary &&
157
+ provenance.details === source.details));
158
+ if (existing) {
159
+ return {
160
+ ticket: options.ticket,
161
+ finding: existing,
162
+ idempotent: true,
163
+ snapshot: identity.snapshot,
164
+ headSha: identity.headSha,
165
+ };
166
+ }
167
+ let highest = 0n;
168
+ for (const candidate of findings) {
169
+ const match = /^F-(\d{4,})$/.exec(candidate.id);
170
+ if (match && BigInt(match[1]) > highest)
171
+ highest = BigInt(match[1]);
172
+ }
173
+ const at = new Date().toISOString();
174
+ progress.findings = findings;
175
+ const finding = {
176
+ id: `F-${String(highest + 1n).padStart(4, "0")}`,
177
+ severity: options.severity,
178
+ blocking: options.blocking === true,
179
+ status: "open",
180
+ summary: options.summary.trim(),
181
+ details: options.details.trim(),
182
+ author: options.author.trim(),
183
+ createdAt: at,
184
+ provenance: [source],
185
+ history: [
186
+ {
187
+ at,
188
+ author: options.author.trim(),
189
+ action: "registered",
190
+ details: `Registered from ${report} at snapshot ${options.snapshot}.`,
191
+ },
192
+ ],
193
+ };
194
+ findings.push(finding);
195
+ const afterTicket = matter.stringify(this.ticketRepository.appendAudit(ticket.body, options.author, `Finding ${finding.id} registered by ${options.author.trim()}: ${finding.summary}; source=${report}; snapshot=${options.snapshot}.`), this.ticketRepository.metadata(ticket));
196
+ this.workspaceTransaction.writeWorkflowState(feature, ticket, afterTicket, ledger);
197
+ return {
198
+ ticket: options.ticket,
199
+ finding,
200
+ snapshot: identity.snapshot,
201
+ headSha: identity.headSha,
202
+ };
203
+ });
204
+ }
205
+ findingUpdate(options) {
206
+ if (options.scope === "delivery") {
207
+ if (options.ticket)
208
+ throw new Error("--ticket and --scope delivery are mutually exclusive.");
209
+ return this.deliveryFindingService.deliveryFindingUpdate(options);
210
+ }
211
+ if (options.scope !== undefined)
212
+ throw new Error("--scope must be 'delivery'.");
213
+ if (!options.ticket)
214
+ throw new Error("Finding update requires --ticket or --scope delivery.");
215
+ validateTicketId(options.ticket);
216
+ validateAuthor(options.author);
217
+ if (!/^F-\d{4,}$/.test(options.finding)) {
218
+ throw new Error(`Invalid finding ID: ${options.finding}.`);
219
+ }
220
+ if (!["open", "confirmed", "fixed", "dismissed", "deferred"].includes(options.status)) {
221
+ throw new Error(`Invalid finding status: ${options.status}.`);
222
+ }
223
+ if (["dismissed", "deferred"].includes(options.status) && !options.details?.trim()) {
224
+ throw new Error(`--details is required for a ${options.status} finding disposition.`);
225
+ }
226
+ if (options.status === "deferred" && !options.followUp?.trim()) {
227
+ throw new Error("--follow-up is required for a deferred finding.");
228
+ }
229
+ if (options.status === "fixed" && !options.evidence?.trim()) {
230
+ throw new Error("--evidence is required to mark a finding fixed.");
231
+ }
232
+ if (options.status !== "deferred" && options.followUp !== undefined) {
233
+ throw new Error("--follow-up is only valid for a deferred finding.");
234
+ }
235
+ if (options.status !== "deferred" && options.requirementsDecision !== undefined) {
236
+ throw new Error("--requirements-decision is only valid for a deferred finding.");
237
+ }
238
+ if (options.status !== "fixed" && options.evidence !== undefined) {
239
+ throw new Error("--evidence is only valid for a fixed finding.");
240
+ }
241
+ const feature = this.featureInspectionService.resolveFeature(options.spec);
242
+ return this.workspaceTransaction.run(feature.workspace, () => {
243
+ const ledger = this.ledgerRepository.readLedger(feature);
244
+ const ticket = this.ticketRepository.requireTicket(this.ticketRepository.readTickets(feature.workspace), options.ticket);
245
+ const progress = this.workflowPolicy.requireProgress(ledger, options.ticket);
246
+ const finding = progress.findings?.find((candidate) => candidate.id === options.finding);
247
+ if (!finding)
248
+ throw new Error(`Finding not found: ${options.finding}.`);
249
+ const wasBlocking = finding.blocking && !["fixed", "dismissed"].includes(finding.status);
250
+ if (progress.owner !== options.author.trim()) {
251
+ throw new Error("Only the current ticket controller may record a finding ruling.");
252
+ }
253
+ if (!progress.baseSha)
254
+ throw new Error("Finding rulings require a recorded Git baseline.");
255
+ const identity = this.gitEvidence.contentIdentity(feature, ticket, progress.baseSha, progress.checks);
256
+ let verification;
257
+ let requirementsDecision;
258
+ if (options.status === "deferred" && finding.blocking) {
259
+ if (!options.requirementsDecision) {
260
+ throw new Error(`Blocking finding ${finding.id} requires an approved requirements decision and fresh review before deferral.`);
261
+ }
262
+ requirementsDecision = progress.requirementsDecisions?.find((decision) => decision.id === options.requirementsDecision &&
263
+ decision.finding === finding.id &&
264
+ decision.status === "approved" &&
265
+ decision.requirementsSnapshot === identity.requirementsSnapshot);
266
+ if (!requirementsDecision) {
267
+ throw new Error("Blocking finding deferral requires a matching approved requirements decision for the current requirements snapshot.");
268
+ }
269
+ const assignments = progress.reviewAssignments ?? [];
270
+ for (const { agent, details } of requiredTicketReviewers(ledger, progress)) {
271
+ const coverage = assignments.filter((assignment) => assignment.agent === agent && assignment.scope === "initial");
272
+ if (coverage.length !== 1 ||
273
+ coverage[0].objective !== details ||
274
+ coverage[0].status !== "submitted" ||
275
+ coverage[0].result !== "passed" ||
276
+ this.reviewPolicy.reviewAssignmentBlocker(feature, progress, coverage[0], identity)) {
277
+ throw new Error(`Blocking finding deferral requires fresh passing review coverage for '${agent}'.`);
278
+ }
279
+ }
280
+ }
281
+ if (options.status === "fixed") {
282
+ if (!/^[0-9a-f]{64}$/i.test(options.evidence)) {
283
+ throw new Error("--evidence must be a CLI-returned content snapshot.");
284
+ }
285
+ if (options.evidence !== identity.snapshot) {
286
+ throw new Error("Verification evidence must match the current ticket snapshot.");
287
+ }
288
+ if (finding.provenance.some((observation) => observation.snapshot === identity.snapshot)) {
289
+ throw new Error("Verification evidence must reflect changed content since every finding observation; dismiss an invalid finding with --details.");
290
+ }
291
+ const required = progress.checks?.required.filter((check) => !check.removedAt) ?? [];
292
+ if (required.length === 0) {
293
+ throw new Error("Verification requires at least one current passing required check.");
294
+ }
295
+ const checkBlockers = this.checkPolicy.checkBlockers(feature, ticket, progress, identity);
296
+ if (checkBlockers.length > 0) {
297
+ throw new Error(`Verification evidence is insufficient:\n${checkBlockers.join("\n")}`);
298
+ }
299
+ verification = {
300
+ snapshot: identity.snapshot,
301
+ checkCommands: required.map((check) => check.command),
302
+ verifiedAt: new Date().toISOString(),
303
+ verifiedBy: options.author.trim(),
304
+ };
305
+ }
306
+ const rulingDetails = options.details?.trim();
307
+ const followUp = options.followUp?.trim();
308
+ const last = finding.history.at(-1);
309
+ if (finding.status === options.status &&
310
+ finding.rulingDetails === rulingDetails &&
311
+ finding.followUp === followUp &&
312
+ finding.requirementsDecision === requirementsDecision?.id &&
313
+ last?.action === `status:${options.status}` &&
314
+ last.author === options.author.trim() &&
315
+ last.details ===
316
+ (rulingDetails ??
317
+ (verification ? `Verified at ${verification.snapshot}.` : options.status)) &&
318
+ (options.status !== "fixed" || finding.verification?.snapshot === verification?.snapshot)) {
319
+ return { ticket: options.ticket, finding, idempotent: true, snapshot: identity.snapshot };
320
+ }
321
+ const before = identity.snapshot;
322
+ const currentIdentity = this.gitEvidence.contentIdentity(feature, this.ticketRepository.requireTicket(this.ticketRepository.readTickets(feature.workspace), options.ticket), progress.baseSha, progress.checks);
323
+ if (currentIdentity.snapshot !== before) {
324
+ throw new Error("Ticket content changed while recording the finding ruling.");
325
+ }
326
+ finding.status = options.status;
327
+ if (["dismissed", "deferred"].includes(options.status)) {
328
+ finding.rulingDetails = rulingDetails;
329
+ }
330
+ else {
331
+ delete finding.rulingDetails;
332
+ }
333
+ if (options.status === "deferred")
334
+ finding.followUp = followUp;
335
+ else
336
+ delete finding.followUp;
337
+ if (options.status === "deferred" && requirementsDecision) {
338
+ finding.requirementsDecision = requirementsDecision.id;
339
+ }
340
+ else {
341
+ delete finding.requirementsDecision;
342
+ }
343
+ if (verification)
344
+ finding.verification = verification;
345
+ else
346
+ delete finding.verification;
347
+ if (progress.execution === "changes-requested" &&
348
+ wasBlocking &&
349
+ ["fixed", "dismissed"].includes(options.status) &&
350
+ this.findingPolicy.findingBlockers(progress, identity).length === 0) {
351
+ const assignments = progress.reviewAssignments ?? [];
352
+ const requiredReviewers = requiredTicketReviewers(ledger, progress);
353
+ const freshCoverage = assignments.length === requiredReviewers.length &&
354
+ requiredReviewers.every(({ agent, details }) => {
355
+ const matching = assignments.filter((assignment) => assignment.agent === agent);
356
+ return (matching.length === 1 &&
357
+ matching[0].objective === details &&
358
+ matching[0].status === "submitted" &&
359
+ matching[0].result === "passed" &&
360
+ this.reviewPolicy.reviewAssignmentBlocker(feature, progress, matching[0], identity) === undefined);
361
+ });
362
+ if (!freshCoverage) {
363
+ progress.reviewHistory = [...(progress.reviewHistory ?? []), ...assignments];
364
+ delete progress.reviewAssignments;
365
+ }
366
+ progress.execution = "reviewing";
367
+ }
368
+ const historyDetails = rulingDetails ?? (verification ? `Verified at ${verification.snapshot}.` : options.status);
369
+ finding.history.push({
370
+ at: new Date().toISOString(),
371
+ author: options.author.trim(),
372
+ action: `status:${options.status}`,
373
+ details: historyDetails,
374
+ });
375
+ const audit = [
376
+ `Finding ${finding.id} disposition recorded: ${options.status}`,
377
+ rulingDetails ? `details=${rulingDetails}` : undefined,
378
+ finding.followUp ? `follow-up=${finding.followUp}` : undefined,
379
+ finding.requirementsDecision
380
+ ? `requirements-decision=${finding.requirementsDecision}`
381
+ : undefined,
382
+ followUp ? `follow-up=${followUp}` : undefined,
383
+ verification ? `verification=${verification.snapshot}` : undefined,
384
+ ]
385
+ .filter((entry) => entry !== undefined)
386
+ .join("; ");
387
+ const afterTicket = matter.stringify(this.ticketRepository.appendAudit(ticket.body, options.author, `${audit}.`), this.ticketRepository.metadata(ticket));
388
+ this.workspaceTransaction.writeWorkflowState(feature, ticket, afterTicket, ledger);
389
+ return { ticket: options.ticket, finding, snapshot: identity.snapshot };
390
+ });
391
+ }
392
+ }
@@ -0,0 +1,258 @@
1
+ import matter from "gray-matter";
2
+ import { Service } from "../../Foundation/Service.js";
3
+ import { validateAuthor, validateTicketId, } from "../Policies/InputValidation.js";
4
+ import { requiredTicketReviewers } from "../Policies/RequiredReviewers.js";
5
+ export class IntegrationService extends Service {
6
+ gitEvidence;
7
+ reportEvidence;
8
+ ticketRepository;
9
+ ledgerRepository;
10
+ workspaceTransaction;
11
+ acceptancePolicy;
12
+ workflowPolicy;
13
+ featureInspectionService;
14
+ constructor(app, gitEvidence, reportEvidence, ticketRepository, ledgerRepository, workspaceTransaction, acceptancePolicy, workflowPolicy, featureInspectionService) {
15
+ super(app);
16
+ this.gitEvidence = gitEvidence;
17
+ this.reportEvidence = reportEvidence;
18
+ this.ticketRepository = ticketRepository;
19
+ this.ledgerRepository = ledgerRepository;
20
+ this.workspaceTransaction = workspaceTransaction;
21
+ this.acceptancePolicy = acceptancePolicy;
22
+ this.workflowPolicy = workflowPolicy;
23
+ this.featureInspectionService = featureInspectionService;
24
+ }
25
+ accept(options) {
26
+ validateTicketId(options.ticket);
27
+ validateAuthor(options.author);
28
+ const feature = this.featureInspectionService.resolveFeature(options.spec);
29
+ return this.workspaceTransaction.run(feature.workspace, () => {
30
+ const ledger = this.ledgerRepository.readLedger(feature);
31
+ const ticket = this.ticketRepository.requireTicket(this.ticketRepository.readTickets(feature.workspace), options.ticket);
32
+ const progress = this.workflowPolicy.requireProgress(ledger, options.ticket);
33
+ if (progress.execution !== "reviewing" &&
34
+ progress.execution !== "changes-requested" &&
35
+ progress.execution !== "accepted") {
36
+ throw new Error(`Ticket ${options.ticket} must be reviewing before it can be accepted.`);
37
+ }
38
+ if (!progress.owner || progress.owner !== options.author.trim()) {
39
+ throw new Error("Only the current controller may accept this ticket.");
40
+ }
41
+ const identity = this.gitEvidence.contentIdentity(feature, ticket, progress.baseSha, progress.checks);
42
+ const blockers = this.acceptancePolicy.acceptBlockers(feature, ledger, ticket, progress, identity);
43
+ if (blockers.length > 0) {
44
+ throw new Error(`Ticket ${options.ticket} cannot be accepted:\n${blockers.join("\n")}`);
45
+ }
46
+ const reviews = requiredTicketReviewers(ledger, progress).map(({ agent, details }) => {
47
+ const assignment = progress.reviewAssignments.find((candidate) => candidate.agent === agent &&
48
+ candidate.objective === details &&
49
+ candidate.scope === "initial");
50
+ return {
51
+ agent,
52
+ assignment: assignment.id,
53
+ result: "passed",
54
+ ...(assignment.noFindings === true
55
+ ? { noFindings: true }
56
+ : { findingIds: assignment.findingIds }),
57
+ };
58
+ });
59
+ if (progress.execution === "accepted") {
60
+ if (!progress.acceptedEvidence) {
61
+ throw new Error(`Accepted ticket ${options.ticket} has no frozen integration evidence.`);
62
+ }
63
+ return {
64
+ ticket: options.ticket,
65
+ execution: "accepted",
66
+ reviews,
67
+ idempotent: true,
68
+ ...identity,
69
+ };
70
+ }
71
+ const confirmedTicket = this.ticketRepository.requireTicket(this.ticketRepository.readTickets(feature.workspace), options.ticket);
72
+ const confirmedIdentity = this.gitEvidence.contentIdentity(feature, confirmedTicket, progress.baseSha, progress.checks);
73
+ const confirmedBlockers = this.acceptancePolicy.acceptBlockers(feature, ledger, confirmedTicket, progress, confirmedIdentity);
74
+ if (confirmedIdentity.snapshot !== identity.snapshot || confirmedBlockers.length > 0) {
75
+ throw new Error(`Ticket content changed while accepting; revalidate current review and check coverage:\n${confirmedBlockers.join("\n")}`);
76
+ }
77
+ const acceptedFiles = this.workflowPolicy.acceptedInventory(progress, options.ticket);
78
+ if (JSON.stringify(this.gitEvidence.changedPaths(progress.baseSha)) !==
79
+ JSON.stringify(acceptedFiles)) {
80
+ throw new Error("Accepted Git inventory changed while accepting the ticket.");
81
+ }
82
+ const contentDigest = this.gitEvidence.worktreeContentDigest(acceptedFiles);
83
+ progress.execution = "accepted";
84
+ progress.acceptedEvidence = {
85
+ snapshot: identity.snapshot,
86
+ codeSnapshot: identity.codeSnapshot,
87
+ requirementsSnapshot: identity.requirementsSnapshot,
88
+ files: acceptedFiles,
89
+ contentDigest,
90
+ };
91
+ const afterTicket = matter.stringify(this.ticketRepository.appendAudit(ticket.body, options.author, `Ticket accepted with current passing checks and complete initial review coverage: ${reviews.map((review) => `${review.agent} (${review.assignment})`).join(", ")}.`), this.ticketRepository.metadata(ticket));
92
+ this.workspaceTransaction.writeWorkflowState(feature, ticket, afterTicket, ledger);
93
+ return {
94
+ ticket: options.ticket,
95
+ execution: "accepted",
96
+ reviews,
97
+ ...identity,
98
+ };
99
+ });
100
+ }
101
+ integrate(options) {
102
+ validateTicketId(options.ticket);
103
+ validateAuthor(options.author);
104
+ if (options.commit && !/^[0-9a-f]{40,64}$/i.test(options.commit)) {
105
+ throw new Error("--commit must be an existing full Git commit SHA.");
106
+ }
107
+ const feature = this.featureInspectionService.resolveFeature(options.spec);
108
+ return this.workspaceTransaction.run(feature.workspace, () => {
109
+ const ledger = this.ledgerRepository.readLedger(feature);
110
+ const ticket = this.ticketRepository.requireTicket(this.ticketRepository.readTickets(feature.workspace), options.ticket);
111
+ const progress = this.workflowPolicy.requireProgress(ledger, options.ticket);
112
+ if (progress.execution !== "accepted" && progress.execution !== "integrated") {
113
+ throw new Error(`Ticket ${options.ticket} must be accepted before integration.`);
114
+ }
115
+ if (!progress.owner || progress.owner !== options.author.trim()) {
116
+ throw new Error("Only the current controller may integrate this ticket.");
117
+ }
118
+ const accepted = progress.acceptedEvidence;
119
+ if (!accepted) {
120
+ throw new Error(`Ticket ${options.ticket} has no frozen accepted inventory and evidence.`);
121
+ }
122
+ if (!progress.baseSha) {
123
+ throw new Error(`Ticket ${options.ticket} has no recorded Git baseline for integration.`);
124
+ }
125
+ const identity = this.gitEvidence.contentIdentity(feature, ticket, progress.baseSha, progress.checks);
126
+ if (identity.requirementsSnapshot !== accepted.requirementsSnapshot) {
127
+ throw new Error("Accepted requirements evidence is stale; re-review is required.");
128
+ }
129
+ const inventory = this.gitEvidence.changedPaths(progress.baseSha);
130
+ if (JSON.stringify(inventory) !== JSON.stringify(accepted.files)) {
131
+ throw new Error(`Git-derived inventory changed after acceptance; expected ${JSON.stringify(accepted.files)}, found ${JSON.stringify(inventory)}.`);
132
+ }
133
+ const currentDigest = this.gitEvidence.worktreeContentDigest(accepted.files);
134
+ if (currentDigest !== accepted.contentDigest) {
135
+ throw new Error("Accepted file content changed after acceptance.");
136
+ }
137
+ this.gitEvidence.assertSafeIntegrationChanges(feature, accepted.files, ledger);
138
+ this.reportEvidence.assertAcceptedReportsCurrent(feature, progress);
139
+ if (!options.commit) {
140
+ if (progress.execution === "integrated") {
141
+ return {
142
+ ticket: options.ticket,
143
+ execution: "integrated",
144
+ acceptedFiles: accepted.files,
145
+ deliveryRecords: this.gitEvidence.deliveryRecordPaths(feature),
146
+ commitSha: progress.integration?.commitSha,
147
+ idempotent: true,
148
+ ...identity,
149
+ };
150
+ }
151
+ const blockers = this.acceptancePolicy.acceptBlockers(feature, ledger, ticket, progress, identity);
152
+ if (blockers.length > 0) {
153
+ throw new Error(`Ticket ${options.ticket} cannot be integrated:\n${blockers.join("\n")}`);
154
+ }
155
+ return {
156
+ ticket: options.ticket,
157
+ execution: "accepted",
158
+ acceptedFiles: accepted.files,
159
+ deliveryRecords: this.gitEvidence.deliveryRecordPaths(feature),
160
+ ...identity,
161
+ };
162
+ }
163
+ const requestedCommit = this.gitEvidence
164
+ .gitBuffer(["rev-parse", "--verify", `${options.commit}^{commit}`])
165
+ .toString("utf8")
166
+ .trim()
167
+ .toLowerCase();
168
+ if (requestedCommit !== options.commit.toLowerCase()) {
169
+ throw new Error(`Commit ${options.commit} did not resolve to the requested commit.`);
170
+ }
171
+ if (progress.execution === "integrated") {
172
+ if (progress.integration?.commitSha !== requestedCommit) {
173
+ throw new Error(`Ticket ${options.ticket} is already integrated through ${progress.integration?.commitSha ?? "an unknown commit"}.`);
174
+ }
175
+ if (this.gitEvidence.gitIndexContentDigest(accepted.files) !== accepted.contentDigest) {
176
+ throw new Error("Staged index content does not match the accepted file content.");
177
+ }
178
+ return {
179
+ ticket: options.ticket,
180
+ execution: "integrated",
181
+ acceptedFiles: accepted.files,
182
+ deliveryRecords: this.gitEvidence.deliveryRecordPaths(feature),
183
+ commitSha: requestedCommit,
184
+ idempotent: true,
185
+ ...identity,
186
+ };
187
+ }
188
+ const acceptedIdentity = {
189
+ snapshot: accepted.snapshot,
190
+ codeSnapshot: accepted.codeSnapshot,
191
+ requirementsSnapshot: accepted.requirementsSnapshot,
192
+ headSha: identity.headSha,
193
+ };
194
+ const acceptanceBlockers = this.acceptancePolicy.acceptBlockers(feature, ledger, ticket, progress, acceptedIdentity);
195
+ if (acceptanceBlockers.length > 0) {
196
+ throw new Error(`Accepted evidence is no longer valid for integration:\n${acceptanceBlockers.join("\n")}`);
197
+ }
198
+ const headSha = this.gitEvidence.headSha();
199
+ if (headSha !== requestedCommit) {
200
+ throw new Error(`Commit ${requestedCommit} must be the current HEAD (${headSha}) before integration can be confirmed.`);
201
+ }
202
+ const ancestry = this.gitEvidence
203
+ .gitBuffer(["rev-list", "--parents", "-n", "1", requestedCommit])
204
+ .toString("utf8")
205
+ .trim()
206
+ .split(/\s+/);
207
+ if (ancestry.length !== 2 || ancestry[1] !== progress.baseSha) {
208
+ throw new Error(`Commit ${requestedCommit} must be a caller-created child of the recorded baseline ${progress.baseSha}.`);
209
+ }
210
+ const committedFiles = this.gitEvidence
211
+ .gitBuffer([
212
+ "diff",
213
+ "--name-only",
214
+ "-z",
215
+ "--no-renames",
216
+ progress.baseSha,
217
+ requestedCommit,
218
+ "--",
219
+ ".",
220
+ ])
221
+ .toString("utf8")
222
+ .split("\0")
223
+ .filter(Boolean)
224
+ .sort();
225
+ if (JSON.stringify(committedFiles) !== JSON.stringify(accepted.files)) {
226
+ throw new Error(`Commit contents do not match the accepted inventory; expected ${JSON.stringify(accepted.files)}, found ${JSON.stringify(committedFiles)}.`);
227
+ }
228
+ if (this.gitEvidence.gitTreeContentDigest(requestedCommit, accepted.files) !==
229
+ accepted.contentDigest) {
230
+ throw new Error("Caller commit content does not match the accepted file content.");
231
+ }
232
+ if (this.gitEvidence.worktreeContentDigest(accepted.files) !== accepted.contentDigest) {
233
+ throw new Error("Accepted file content changed before integration confirmation.");
234
+ }
235
+ if (this.gitEvidence.gitIndexContentDigest(accepted.files) !== accepted.contentDigest) {
236
+ throw new Error("Staged index content does not match the accepted file content.");
237
+ }
238
+ progress.execution = "integrated";
239
+ progress.integration = {
240
+ commitSha: requestedCommit,
241
+ integratedAt: new Date().toISOString(),
242
+ integratedBy: options.author.trim(),
243
+ files: accepted.files,
244
+ contentDigest: accepted.contentDigest,
245
+ };
246
+ const afterTicket = matter.stringify(this.ticketRepository.appendAudit(ticket.body, options.author, `Ticket integration confirmed against caller-owned commit ${requestedCommit}; delivery records remain caller-owned.`), this.ticketRepository.metadata(ticket));
247
+ this.workspaceTransaction.writeWorkflowState(feature, ticket, afterTicket, ledger);
248
+ return {
249
+ ticket: options.ticket,
250
+ execution: "integrated",
251
+ acceptedFiles: accepted.files,
252
+ deliveryRecords: this.gitEvidence.deliveryRecordPaths(feature),
253
+ commitSha: requestedCommit,
254
+ ...this.gitEvidence.contentIdentity(feature, this.ticketRepository.requireTicket(this.ticketRepository.readTickets(feature.workspace), options.ticket), progress.baseSha, progress.checks),
255
+ };
256
+ });
257
+ }
258
+ }