@use-the-fork/spindle 0.0.0-stage → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,322 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { blueprintCatalog } from "../../BlueprintManagement/Catalog/BlueprintCatalog.js";
4
+ import { Service } from "../../Foundation/Service.js";
5
+ import { FEATURE_DIRECTORY, EXECUTION_STATES, TICKET_CATEGORIES, TRACKER_STATUSES, } from "../Models/Workflow.js";
6
+ import { isImplementationAssignment, isCorrectionAttempt, isCorrectionAdjudication, hasMatchingCorrectionAttempt, } from "./AssignmentSchema.js";
7
+ import { isCheckRequirement, isCheckRecord, isCheckGateInvalidation, } from "./CheckSchema.js";
8
+ import { isReviewFinding, isRequirementsDecision, } from "./FindingSchema.js";
9
+ import { isDeliveryProgress, isAcceptedEvidence, isIntegrationRecord, } from "./ProgressSchema.js";
10
+ import { isReviewAssignment, isSpecialistReviewRequirement, } from "./ReviewSchema.js";
11
+ import { Records } from "../../Support/Records.js";
12
+ const isRecord = Records.isRecord;
13
+ export class LedgerRepository extends Service {
14
+ constructor(app) {
15
+ super(app);
16
+ }
17
+ readLedger(feature) {
18
+ return this.validateLedger(JSON.parse(readFileSync(feature.ledger, "utf8")), feature.feature);
19
+ }
20
+ validateLedger(value, feature) {
21
+ this.assertLedger(value, feature);
22
+ return value;
23
+ }
24
+ assertLedger(value, feature) {
25
+ if (!isRecord(value))
26
+ throw new Error("Feature ledger must contain a JSON object.");
27
+ const id = FEATURE_DIRECTORY.exec(feature)?.[1];
28
+ if (value.schemaVersion !== 3) {
29
+ throw new Error(`Unsupported feature ledger schema version: ${String(value.schemaVersion)}.`);
30
+ }
31
+ if (typeof value.featureId !== "string" || value.featureId !== id) {
32
+ throw new Error("Feature ledger ID does not match its workspace directory.");
33
+ }
34
+ if (typeof value.title !== "string" || !value.title.trim()) {
35
+ throw new Error("Feature ledger title must be a non-empty string.");
36
+ }
37
+ if (typeof value.createdBy !== "string" || !value.createdBy.trim()) {
38
+ throw new Error("Feature ledger author must be a non-empty string.");
39
+ }
40
+ if (typeof value.createdAt !== "string" || !Number.isFinite(Date.parse(value.createdAt))) {
41
+ throw new Error("Feature ledger creation time must be a valid timestamp.");
42
+ }
43
+ if (typeof value.retrospectiveRequired !== "boolean") {
44
+ throw new Error("Feature ledger retrospective policy must be a boolean.");
45
+ }
46
+ if (!isRecord(value.reviewPolicy))
47
+ throw new Error("Feature review policy must be an object.");
48
+ if (typeof value.reviewPolicy.version !== "number" ||
49
+ !Number.isSafeInteger(value.reviewPolicy.version) ||
50
+ value.reviewPolicy.version < 1) {
51
+ throw new Error("Feature review policy version must be a positive integer.");
52
+ }
53
+ for (const role of ["ticket", "delivery"]) {
54
+ const reviewers = value.reviewPolicy[role];
55
+ if (!Array.isArray(reviewers) ||
56
+ reviewers.length === 0 ||
57
+ !reviewers.every((reviewer) => typeof reviewer === "string" && reviewer.trim())) {
58
+ throw new Error(`Feature ${role} review requirements must be a non-empty string list.`);
59
+ }
60
+ }
61
+ if (!Array.isArray(value.tickets) ||
62
+ !value.tickets.every((ticket) => isRecord(ticket) &&
63
+ typeof ticket.id === "string" &&
64
+ /^T-\d{4,}$/.test(ticket.id) &&
65
+ typeof ticket.execution === "string" &&
66
+ EXECUTION_STATES.includes(ticket.execution) &&
67
+ typeof ticket.title === "string" &&
68
+ !!ticket.title.trim() &&
69
+ typeof ticket.reporter === "string" &&
70
+ !!ticket.reporter.trim() &&
71
+ typeof ticket.category === "string" &&
72
+ TICKET_CATEGORIES.includes(ticket.category) &&
73
+ typeof ticket.status === "string" &&
74
+ TRACKER_STATUSES.includes(ticket.status) &&
75
+ Array.isArray(ticket.blockedBy) &&
76
+ ticket.blockedBy.every((dependency) => typeof dependency === "string") &&
77
+ (!Object.hasOwn(ticket, "blocker") ||
78
+ (typeof ticket.blocker === "string" && !!ticket.blocker.trim())) &&
79
+ ((!Object.hasOwn(ticket, "owner") && !Object.hasOwn(ticket, "baseSha")) ||
80
+ (typeof ticket.owner === "string" &&
81
+ !!ticket.owner.trim() &&
82
+ typeof ticket.baseSha === "string" &&
83
+ /^[0-9a-f]{40,64}$/i.test(ticket.baseSha))) &&
84
+ (!Object.hasOwn(ticket, "acceptedEvidence") ||
85
+ isAcceptedEvidence(ticket.acceptedEvidence)) &&
86
+ (!Object.hasOwn(ticket, "integration") ||
87
+ (ticket.execution === "integrated" &&
88
+ isIntegrationRecord(ticket.integration) &&
89
+ isAcceptedEvidence(ticket.acceptedEvidence) &&
90
+ JSON.stringify(ticket.integration.files) ===
91
+ JSON.stringify(ticket.acceptedEvidence.files) &&
92
+ ticket.integration.contentDigest === ticket.acceptedEvidence.contentDigest)) &&
93
+ (!Object.hasOwn(ticket, "reviewRequirements") ||
94
+ (Array.isArray(ticket.reviewRequirements) &&
95
+ ticket.reviewRequirements.every(isSpecialistReviewRequirement) &&
96
+ new Set(ticket.reviewRequirements.map((requirement) => requirement.agent)).size ===
97
+ ticket.reviewRequirements.length)) &&
98
+ (!Object.hasOwn(ticket, "checks") ||
99
+ (isRecord(ticket.checks) &&
100
+ Array.isArray(ticket.checks.required) &&
101
+ ticket.checks.required.every(isCheckRequirement) &&
102
+ Array.isArray(ticket.checks.records) &&
103
+ ticket.checks.records.every(isCheckRecord) &&
104
+ (!Object.hasOwn(ticket.checks, "invalidations") ||
105
+ (Array.isArray(ticket.checks.invalidations) &&
106
+ ticket.checks.invalidations.every(isCheckGateInvalidation))))) &&
107
+ (!Object.hasOwn(ticket, "assignment") ||
108
+ (isImplementationAssignment(ticket.assignment) &&
109
+ ticket.assignment.report ===
110
+ join(this.app.workspaceRoot, ".scratch", feature, "reports", ticket.assignment.action === "implement"
111
+ ? `${ticket.id}-implementation.md`
112
+ : `${ticket.id}-round-${ticket.assignment.attempt}-fix.md`))) &&
113
+ (!Object.hasOwn(ticket, "correctionAttempts") ||
114
+ (Array.isArray(ticket.correctionAttempts) &&
115
+ ticket.correctionAttempts.every(isCorrectionAttempt) &&
116
+ ticket.correctionAttempts.every((attempt, index) => attempt.number === index + 1) &&
117
+ ticket.correctionAttempts.every((attempt) => attempt.assignment.report ===
118
+ join(this.app.workspaceRoot, ".scratch", feature, "reports", `${ticket.id}-round-${attempt.number}-fix.md`) &&
119
+ (!attempt.priorAssignment ||
120
+ attempt.priorAssignment.report ===
121
+ join(this.app.workspaceRoot, ".scratch", feature, "reports", attempt.priorAssignment.action === "implement"
122
+ ? `${ticket.id}-implementation.md`
123
+ : `${ticket.id}-round-${attempt.priorAssignment.attempt}-fix.md`)) &&
124
+ (attempt.reviewAssignments ?? []).every((assignment) => assignment.report ===
125
+ join(this.app.workspaceRoot, ".scratch", feature, "reports", assignment.scope === "initial"
126
+ ? `${ticket.id}-review-${assignment.id}.md`
127
+ : `${ticket.id}-round-${assignment.attempt}-review-${assignment.id}.md`))) &&
128
+ hasMatchingCorrectionAttempt(ticket))) &&
129
+ (!Object.hasOwn(ticket, "adjudication") ||
130
+ isCorrectionAdjudication(ticket.adjudication)) &&
131
+ (!Object.hasOwn(ticket, "reviewAssignments") ||
132
+ (Array.isArray(ticket.reviewAssignments) &&
133
+ ticket.reviewAssignments.every((assignment) => isReviewAssignment(assignment) &&
134
+ blueprintCatalog.agents[assignment.agent]?.canReview === true &&
135
+ assignment.report ===
136
+ join(this.app.workspaceRoot, ".scratch", feature, "reports", assignment.scope === "initial"
137
+ ? `${ticket.id}-review-${assignment.id}.md`
138
+ : `${ticket.id}-round-${assignment.attempt}-review-${assignment.id}.md`)))) &&
139
+ (!Object.hasOwn(ticket, "reviewHistory") ||
140
+ (Array.isArray(ticket.reviewHistory) &&
141
+ ticket.reviewHistory.every((assignment) => isReviewAssignment(assignment) &&
142
+ blueprintCatalog.agents[assignment.agent]?.canReview === true &&
143
+ assignment.report ===
144
+ join(this.app.workspaceRoot, ".scratch", feature, "reports", assignment.scope === "initial"
145
+ ? `${ticket.id}-review-${assignment.id}.md`
146
+ : `${ticket.id}-round-${assignment.attempt}-review-${assignment.id}.md`)))) &&
147
+ (!Object.hasOwn(ticket, "requirementsDecisions") ||
148
+ (Array.isArray(ticket.requirementsDecisions) &&
149
+ ticket.requirementsDecisions.every(isRequirementsDecision))) &&
150
+ (!Object.hasOwn(ticket, "findings") ||
151
+ (Array.isArray(ticket.findings) && ticket.findings.every(isReviewFinding))))) {
152
+ throw new Error("Feature ticket records must contain a ticket ID and a valid execution state.");
153
+ }
154
+ if (new Set(value.tickets.map((ticket) => ticket.id)).size !==
155
+ value.tickets.length) {
156
+ throw new Error("Feature ledger ticket IDs must be unique.");
157
+ }
158
+ for (const ticket of value.tickets) {
159
+ for (const requirement of ticket.reviewRequirements ?? []) {
160
+ if (blueprintCatalog.agents[requirement.agent]?.canReview !== true) {
161
+ throw new Error(`Required specialist agent '${requirement.agent}' is not explicitly marked review-eligible.`);
162
+ }
163
+ if (value.reviewPolicy.ticket.includes(requirement.agent)) {
164
+ throw new Error(`Specialist reviewer '${requirement.agent}' duplicates the pinned ticket review policy.`);
165
+ }
166
+ }
167
+ const findingIds = ticket.findings?.map((finding) => finding.id) ?? [];
168
+ if (new Set(findingIds).size !== findingIds.length) {
169
+ throw new Error(`Finding IDs must be unique within ticket ${ticket.id}.`);
170
+ }
171
+ const decisionIds = ticket.requirementsDecisions?.map((decision) => decision.id) ?? [];
172
+ if (new Set(decisionIds).size !== decisionIds.length) {
173
+ throw new Error(`Requirements decision IDs must be unique within ticket ${ticket.id}.`);
174
+ }
175
+ for (const finding of ticket.findings ?? []) {
176
+ if (finding.requirementsDecision &&
177
+ !ticket.requirementsDecisions?.some((decision) => decision.id === finding.requirementsDecision &&
178
+ decision.finding === finding.id &&
179
+ decision.status === "approved")) {
180
+ throw new Error(`Finding ${finding.id} references an invalid requirements decision.`);
181
+ }
182
+ }
183
+ for (const assignment of [
184
+ ...(ticket.reviewHistory ?? []),
185
+ ...(ticket.reviewAssignments ?? []),
186
+ ]) {
187
+ const reported = (ticket.findings ?? []).filter((finding) => finding.provenance.some((source) => source.report === assignment.report && source.snapshot === assignment.snapshot));
188
+ if (assignment.noFindings === true && reported.length > 0) {
189
+ throw new Error(`Review assignment ${assignment.id} declares no findings despite registered provenance.`);
190
+ }
191
+ for (const id of assignment.findingIds ?? []) {
192
+ const finding = (ticket.findings ?? []).find((candidate) => candidate.id === id);
193
+ if (!finding ||
194
+ !finding.provenance.some((source) => source.report === assignment.report && source.snapshot === assignment.snapshot)) {
195
+ throw new Error(`Review assignment ${assignment.id} references finding ${id} without matching provenance.`);
196
+ }
197
+ }
198
+ if (assignment.status === "submitted" &&
199
+ assignment.findingIds &&
200
+ (reported.length !== assignment.findingIds.length ||
201
+ reported.some((finding) => !assignment.findingIds.includes(finding.id)))) {
202
+ throw new Error(`Review assignment ${assignment.id} does not list the complete canonical findings set.`);
203
+ }
204
+ }
205
+ }
206
+ const reviewAssignmentIds = value.tickets.flatMap((ticket) => isRecord(ticket)
207
+ ? [
208
+ ...(Array.isArray(ticket.reviewHistory) ? ticket.reviewHistory : []),
209
+ ...(Array.isArray(ticket.reviewAssignments) ? ticket.reviewAssignments : []),
210
+ ].map((assignment) => (isRecord(assignment) ? assignment.id : undefined))
211
+ : []);
212
+ if (reviewAssignmentIds.some((assignmentId) => typeof assignmentId !== "string") ||
213
+ new Set(reviewAssignmentIds).size !== reviewAssignmentIds.length) {
214
+ throw new Error("Feature review assignment IDs must be unique.");
215
+ }
216
+ if (!isRecord(value.delivery))
217
+ throw new Error("Feature delivery progress must be an object.");
218
+ if (typeof value.delivery.execution !== "string" ||
219
+ !EXECUTION_STATES.includes(value.delivery.execution)) {
220
+ throw new Error("Feature delivery execution state is invalid.");
221
+ }
222
+ if (!["active", "complete"].includes(String(value.delivery.status))) {
223
+ throw new Error("Feature tracking status is invalid.");
224
+ }
225
+ if (!Array.isArray(value.delivery.history) ||
226
+ !value.delivery.history.every((entry) => isRecord(entry) &&
227
+ typeof entry.at === "string" &&
228
+ Number.isFinite(Date.parse(entry.at)) &&
229
+ typeof entry.author === "string" &&
230
+ entry.author.trim() &&
231
+ typeof entry.action === "string" &&
232
+ entry.action.trim())) {
233
+ throw new Error("Feature delivery history records must contain a valid timestamp, author, and action.");
234
+ }
235
+ const delivery = value.delivery;
236
+ if ((Object.hasOwn(delivery, "baseSha") &&
237
+ (typeof delivery.baseSha !== "string" || !/^[0-9a-f]{40,64}$/i.test(delivery.baseSha))) ||
238
+ (Object.hasOwn(delivery, "owner") &&
239
+ (typeof delivery.owner !== "string" || !delivery.owner.trim())) ||
240
+ (Object.hasOwn(delivery, "reviewRequirements") &&
241
+ (!Array.isArray(delivery.reviewRequirements) ||
242
+ !delivery.reviewRequirements.every(isSpecialistReviewRequirement) ||
243
+ new Set(delivery.reviewRequirements.map((item) => item.agent)).size !==
244
+ delivery.reviewRequirements.length)) ||
245
+ (Object.hasOwn(delivery, "reviewAssignments") &&
246
+ (!Array.isArray(delivery.reviewAssignments) ||
247
+ !delivery.reviewAssignments.every((assignment) => isReviewAssignment(assignment) &&
248
+ assignment.scope === "delivery" &&
249
+ blueprintCatalog.agents[assignment.agent]?.canReview === true &&
250
+ assignment.report ===
251
+ join(this.app.workspaceRoot, ".scratch", feature, "reports", assignment.attempt
252
+ ? `final-round-${assignment.attempt}-review-${assignment.id}.md`
253
+ : `final-review-${assignment.id}.md`)))) ||
254
+ (Object.hasOwn(delivery, "reviewHistory") &&
255
+ (!Array.isArray(delivery.reviewHistory) ||
256
+ !delivery.reviewHistory.every((assignment) => isReviewAssignment(assignment) &&
257
+ assignment.scope === "delivery" &&
258
+ blueprintCatalog.agents[assignment.agent]?.canReview === true))) ||
259
+ (Object.hasOwn(delivery, "requirementsDecisions") &&
260
+ (!Array.isArray(delivery.requirementsDecisions) ||
261
+ !delivery.requirementsDecisions.every(isRequirementsDecision) ||
262
+ new Set(delivery.requirementsDecisions.map((decision) => decision.id)).size !==
263
+ delivery.requirementsDecisions.length)) ||
264
+ (Object.hasOwn(delivery, "findings") &&
265
+ (!Array.isArray(delivery.findings) || !delivery.findings.every(isReviewFinding))) ||
266
+ (Object.hasOwn(delivery, "assignment") &&
267
+ (!isImplementationAssignment(delivery.assignment) ||
268
+ delivery.assignment.action !== "correct" ||
269
+ !Number.isSafeInteger(delivery.assignment.attempt) ||
270
+ delivery.assignment.report !==
271
+ join(this.app.workspaceRoot, ".scratch", feature, "reports", `final-round-${delivery.assignment.attempt}-fix.md`))) ||
272
+ (Object.hasOwn(delivery, "checks") &&
273
+ (!isRecord(delivery.checks) ||
274
+ !Array.isArray(delivery.checks.required) ||
275
+ !delivery.checks.required.every(isCheckRequirement) ||
276
+ !Array.isArray(delivery.checks.records) ||
277
+ !delivery.checks.records.every(isCheckRecord))) ||
278
+ (Object.hasOwn(delivery, "correctionAttempts") &&
279
+ (!Array.isArray(delivery.correctionAttempts) ||
280
+ !delivery.correctionAttempts.every(isCorrectionAttempt) ||
281
+ delivery.correctionAttempts.some((attempt, index) => attempt.number !== index + 1 ||
282
+ attempt.assignment.action !== "correct" ||
283
+ attempt.assignment.report !==
284
+ join(this.app.workspaceRoot, ".scratch", feature, "reports", `final-round-${attempt.number}-fix.md`) ||
285
+ (attempt.reviewAssignments ?? []).some((assignment) => assignment.scope !== "delivery" ||
286
+ assignment.attempt !== attempt.number ||
287
+ assignment.report !==
288
+ join(this.app.workspaceRoot, ".scratch", feature, "reports", `final-round-${attempt.number}-review-${assignment.id}.md`))) ||
289
+ !hasMatchingCorrectionAttempt(delivery))) ||
290
+ (Object.hasOwn(delivery, "adjudication") && !isCorrectionAdjudication(delivery.adjudication))) {
291
+ throw new Error("Feature delivery evidence or assignment records are invalid.");
292
+ }
293
+ if (!isDeliveryProgress(delivery)) {
294
+ throw new Error("Feature delivery progress does not match the delivery ledger schema.");
295
+ }
296
+ const deliveryFindingIds = delivery.findings?.map((finding) => finding.id) ?? [];
297
+ if (new Set(deliveryFindingIds).size !== deliveryFindingIds.length) {
298
+ throw new Error("Delivery finding IDs must be unique.");
299
+ }
300
+ const ticketIds = new Set(value.tickets.map((ticket) => ticket.id));
301
+ for (const finding of delivery.findings ?? []) {
302
+ if (finding.relatedTicket && !ticketIds.has(finding.relatedTicket)) {
303
+ throw new Error(`Delivery finding ${finding.id} references missing ticket ${finding.relatedTicket}.`);
304
+ }
305
+ if (finding.requirementsDecision &&
306
+ !delivery.requirementsDecisions?.some((decision) => decision.id === finding.requirementsDecision &&
307
+ decision.finding === finding.id &&
308
+ decision.status === "approved")) {
309
+ throw new Error(`Delivery finding ${finding.id} references an invalid requirements decision.`);
310
+ }
311
+ }
312
+ const allReviewAssignmentIds = [
313
+ ...reviewAssignmentIds,
314
+ ...(Array.isArray(delivery.reviewHistory) ? delivery.reviewHistory : []).map((assignment) => isRecord(assignment) ? assignment.id : undefined),
315
+ ...(Array.isArray(delivery.reviewAssignments) ? delivery.reviewAssignments : []).map((assignment) => (isRecord(assignment) ? assignment.id : undefined)),
316
+ ];
317
+ if (allReviewAssignmentIds.some((assignmentId) => typeof assignmentId !== "string") ||
318
+ new Set(allReviewAssignmentIds).size !== allReviewAssignmentIds.length) {
319
+ throw new Error("Feature review assignment IDs must be unique.");
320
+ }
321
+ }
322
+ }
@@ -0,0 +1,62 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { join, relative } from "node:path";
3
+ import { Service } from "../../Foundation/Service.js";
4
+ import { lstatIfPresent, isPathInside } from "./FileSystem.js";
5
+ export class PathSafety extends Service {
6
+ git;
7
+ constructor(app, git) {
8
+ super(app);
9
+ this.git = git;
10
+ }
11
+ assertTicketPath(workspace, path) {
12
+ const ticketsDirectory = join(workspace, "tickets");
13
+ const directoryEntry = lstatIfPresent(ticketsDirectory);
14
+ if (!directoryEntry?.isDirectory() || directoryEntry.isSymbolicLink()) {
15
+ throw new Error("Feature tickets path must be a real directory.");
16
+ }
17
+ if (!isPathInside(ticketsDirectory, path) ||
18
+ !/^T-\d{4,}\.md$/.test(path.slice(ticketsDirectory.length + 1))) {
19
+ throw new Error("Ticket path must be a numbered Markdown file inside tickets/.");
20
+ }
21
+ const entry = lstatIfPresent(path);
22
+ if (entry && !entry.isFile())
23
+ throw new Error(`Ticket path must be a regular file: ${path}.`);
24
+ if (entry && !isPathInside(ticketsDirectory, realpathSync(path))) {
25
+ throw new Error("Ticket file must remain inside the feature tickets directory.");
26
+ }
27
+ const root = realpathSync(this.app.workspaceRoot);
28
+ const relativePath = relative(root, path);
29
+ if (this.isIgnored(root, relativePath))
30
+ throw new Error(`Ticket path is ignored: ${relativePath}`);
31
+ }
32
+ isIgnored(root, relativePath) {
33
+ const result = this.git.run([
34
+ "--no-pager",
35
+ "-c",
36
+ "color.ui=false",
37
+ "check-ignore",
38
+ "--quiet",
39
+ "--no-index",
40
+ "--",
41
+ relativePath,
42
+ ], {
43
+ cwd: root,
44
+ encoding: "utf8",
45
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
46
+ });
47
+ if (result.error)
48
+ throw result.error;
49
+ if (result.status !== 0 && result.status !== 1) {
50
+ throw new Error(`git check-ignore failed: ${result.stderr.trim()}`);
51
+ }
52
+ return result.status === 0;
53
+ }
54
+ assertReadyReadable(workspace) {
55
+ if (lstatIfPresent(join(workspace, ".sdd-write-recovery.json"))) {
56
+ throw new Error("Pending ticket recovery journal; run a mutating ticket command to recover.");
57
+ }
58
+ if (lstatIfPresent(join(workspace, ".sdd-tickets.lock"))) {
59
+ throw new Error("Ticket updates are in progress; retry ready after the lock is released.");
60
+ }
61
+ }
62
+ }
@@ -0,0 +1,95 @@
1
+ import { isAbsolute } from "node:path";
2
+ import { EXECUTION_STATES } from "../Models/Workflow.js";
3
+ import { isImplementationAssignment, isCorrectionAttempt, isCorrectionAdjudication, } from "./AssignmentSchema.js";
4
+ import { isCheckRequirement, isCheckRecord, isCheckGateInvalidation, isSafeCoveragePath, } from "./CheckSchema.js";
5
+ import { isReviewFinding, isRequirementsDecision, } from "./FindingSchema.js";
6
+ import { isReviewAssignment, isSpecialistReviewRequirement, } from "./ReviewSchema.js";
7
+ import { Records } from "../../Support/Records.js";
8
+ const isRecord = Records.isRecord;
9
+ export function isDeliveryProgress(value) {
10
+ return (isRecord(value) &&
11
+ (!Object.hasOwn(value, "status") || ["active", "complete"].includes(String(value.status))) &&
12
+ typeof value.execution === "string" &&
13
+ EXECUTION_STATES.some((state) => state === value.execution) &&
14
+ Array.isArray(value.history) &&
15
+ value.history.every((entry) => isRecord(entry) &&
16
+ typeof entry.at === "string" &&
17
+ Number.isFinite(Date.parse(entry.at)) &&
18
+ typeof entry.author === "string" &&
19
+ !!entry.author.trim() &&
20
+ typeof entry.action === "string" &&
21
+ !!entry.action.trim()) &&
22
+ (!Object.hasOwn(value, "baseSha") ||
23
+ (typeof value.baseSha === "string" && /^[0-9a-f]{40,64}$/i.test(value.baseSha))) &&
24
+ (!Object.hasOwn(value, "owner") || (typeof value.owner === "string" && !!value.owner.trim())) &&
25
+ (!Object.hasOwn(value, "reviewRequirements") ||
26
+ (Array.isArray(value.reviewRequirements) &&
27
+ value.reviewRequirements.every(isSpecialistReviewRequirement))) &&
28
+ (!Object.hasOwn(value, "reviewAssignments") ||
29
+ (Array.isArray(value.reviewAssignments) &&
30
+ value.reviewAssignments.every(isReviewAssignment))) &&
31
+ (!Object.hasOwn(value, "reviewHistory") ||
32
+ (Array.isArray(value.reviewHistory) && value.reviewHistory.every(isReviewAssignment))) &&
33
+ (!Object.hasOwn(value, "assignment") ||
34
+ (isImplementationAssignment(value.assignment) && value.assignment.action === "correct")) &&
35
+ (!Object.hasOwn(value, "requirementsDecisions") ||
36
+ (Array.isArray(value.requirementsDecisions) &&
37
+ value.requirementsDecisions.every(isRequirementsDecision))) &&
38
+ (!Object.hasOwn(value, "checks") ||
39
+ (isRecord(value.checks) &&
40
+ Array.isArray(value.checks.required) &&
41
+ value.checks.required.every(isCheckRequirement) &&
42
+ Array.isArray(value.checks.records) &&
43
+ value.checks.records.every(isCheckRecord) &&
44
+ (!Object.hasOwn(value.checks, "invalidations") ||
45
+ (Array.isArray(value.checks.invalidations) &&
46
+ value.checks.invalidations.every(isCheckGateInvalidation))))) &&
47
+ (!Object.hasOwn(value, "findings") ||
48
+ (Array.isArray(value.findings) && value.findings.every(isReviewFinding))) &&
49
+ (!Object.hasOwn(value, "correctionAttempts") ||
50
+ (Array.isArray(value.correctionAttempts) &&
51
+ value.correctionAttempts.every(isCorrectionAttempt))) &&
52
+ (!Object.hasOwn(value, "adjudication") || isCorrectionAdjudication(value.adjudication)) &&
53
+ (!Object.hasOwn(value, "acceptedEvidence") || isAcceptedEvidence(value.acceptedEvidence)) &&
54
+ (!Object.hasOwn(value, "finished") ||
55
+ (isRecord(value.finished) &&
56
+ typeof value.finished.at === "string" &&
57
+ Number.isFinite(Date.parse(value.finished.at)) &&
58
+ typeof value.finished.author === "string" &&
59
+ !!value.finished.author.trim() &&
60
+ (!Object.hasOwn(value.finished, "report") ||
61
+ (typeof value.finished.report === "string" && isAbsolute(value.finished.report))) &&
62
+ (!Object.hasOwn(value.finished, "reportSha256") ||
63
+ (typeof value.finished.reportSha256 === "string" &&
64
+ /^[0-9a-f]{64}$/i.test(value.finished.reportSha256))))));
65
+ }
66
+ export function isAcceptedEvidence(value) {
67
+ return (isRecord(value) &&
68
+ typeof value.snapshot === "string" &&
69
+ /^[0-9a-f]{64}$/i.test(value.snapshot) &&
70
+ typeof value.codeSnapshot === "string" &&
71
+ /^[0-9a-f]{64}$/i.test(value.codeSnapshot) &&
72
+ typeof value.requirementsSnapshot === "string" &&
73
+ /^[0-9a-f]{64}$/i.test(value.requirementsSnapshot) &&
74
+ Array.isArray(value.files) &&
75
+ value.files.length > 0 &&
76
+ value.files.every((path) => typeof path === "string" && isSafeCoveragePath(path)) &&
77
+ new Set(value.files).size === value.files.length &&
78
+ typeof value.contentDigest === "string" &&
79
+ /^[0-9a-f]{64}$/i.test(value.contentDigest));
80
+ }
81
+ export function isIntegrationRecord(value) {
82
+ return (isRecord(value) &&
83
+ typeof value.commitSha === "string" &&
84
+ /^[0-9a-f]{40,64}$/i.test(value.commitSha) &&
85
+ typeof value.integratedAt === "string" &&
86
+ Number.isFinite(Date.parse(value.integratedAt)) &&
87
+ typeof value.integratedBy === "string" &&
88
+ !!value.integratedBy.trim() &&
89
+ Array.isArray(value.files) &&
90
+ value.files.length > 0 &&
91
+ value.files.every((path) => typeof path === "string" && isSafeCoveragePath(path)) &&
92
+ new Set(value.files).size === value.files.length &&
93
+ typeof value.contentDigest === "string" &&
94
+ /^[0-9a-f]{64}$/i.test(value.contentDigest));
95
+ }
@@ -0,0 +1,40 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { Service } from "../../Foundation/Service.js";
3
+ export class ReadinessReader extends Service {
4
+ ticketRepository;
5
+ ledgerRepository;
6
+ pathSafety;
7
+ constructor(app, ticketRepository, ledgerRepository, pathSafety) {
8
+ super(app);
9
+ this.ticketRepository = ticketRepository;
10
+ this.ledgerRepository = ledgerRepository;
11
+ this.pathSafety = pathSafety;
12
+ }
13
+ read(feature) {
14
+ let ledger;
15
+ let tickets;
16
+ for (let attempt = 0; attempt < 3; attempt++) {
17
+ this.pathSafety.assertReadyReadable(feature.workspace);
18
+ const ledgerRaw = readFileSync(feature.ledger, "utf8");
19
+ this.ledgerRepository.validateLedger(JSON.parse(ledgerRaw), feature.feature);
20
+ const observedTickets = this.ticketRepository.readTickets(feature.workspace);
21
+ this.pathSafety.assertReadyReadable(feature.workspace);
22
+ const confirmedLedgerRaw = readFileSync(feature.ledger, "utf8");
23
+ const confirmedLedger = this.ledgerRepository.validateLedger(JSON.parse(confirmedLedgerRaw), feature.feature);
24
+ const confirmedTickets = this.ticketRepository.readTickets(feature.workspace);
25
+ this.pathSafety.assertReadyReadable(feature.workspace);
26
+ const observedTicketContents = new Map(observedTickets.map((ticket) => [ticket.id, ticket.raw]));
27
+ const ticketsMatch = observedTicketContents.size === confirmedTickets.length &&
28
+ confirmedTickets.every((ticket) => observedTicketContents.get(ticket.id) === ticket.raw);
29
+ if (ledgerRaw === confirmedLedgerRaw && ticketsMatch) {
30
+ ledger = confirmedLedger;
31
+ tickets = confirmedTickets;
32
+ break;
33
+ }
34
+ }
35
+ if (!ledger || !tickets) {
36
+ throw new Error("Ticket data changed while reading the ready queue; retry ready.");
37
+ }
38
+ return { ledger, tickets };
39
+ }
40
+ }
@@ -0,0 +1,68 @@
1
+ import { isAbsolute } from "node:path";
2
+ import { Records } from "../../Support/Records.js";
3
+ const isRecord = Records.isRecord;
4
+ export function isReviewAssignment(value) {
5
+ if (!isRecord(value) ||
6
+ typeof value.id !== "string" ||
7
+ !value.id.trim() ||
8
+ typeof value.agent !== "string" ||
9
+ !value.agent.trim() ||
10
+ value.role !== "review" ||
11
+ !["initial", "corrective", "delivery"].includes(String(value.scope)) ||
12
+ (value.scope === "corrective" &&
13
+ (!Number.isSafeInteger(value.attempt) || Number(value.attempt) < 1)) ||
14
+ (value.scope === "initial" && Object.hasOwn(value, "attempt")) ||
15
+ (value.scope === "delivery" &&
16
+ Object.hasOwn(value, "attempt") &&
17
+ (!Number.isSafeInteger(value.attempt) || Number(value.attempt) < 1)) ||
18
+ typeof value.snapshot !== "string" ||
19
+ !/^[0-9a-f]{64}$/i.test(value.snapshot) ||
20
+ typeof value.codeSnapshot !== "string" ||
21
+ !/^[0-9a-f]{64}$/i.test(value.codeSnapshot) ||
22
+ typeof value.requirementsSnapshot !== "string" ||
23
+ !/^[0-9a-f]{64}$/i.test(value.requirementsSnapshot) ||
24
+ typeof value.headSha !== "string" ||
25
+ !/^[0-9a-f]{40,64}$/i.test(value.headSha) ||
26
+ !Array.isArray(value.inventory) ||
27
+ !value.inventory.every((path) => typeof path === "string" && path.length > 0) ||
28
+ new Set(value.inventory).size !== value.inventory.length ||
29
+ (Object.hasOwn(value, "objective") &&
30
+ (typeof value.objective !== "string" || !value.objective.trim())) ||
31
+ (Object.hasOwn(value, "details") &&
32
+ (typeof value.details !== "string" || !value.details.trim())) ||
33
+ typeof value.report !== "string" ||
34
+ !isAbsolute(value.report) ||
35
+ typeof value.requestedAt !== "string" ||
36
+ !Number.isFinite(Date.parse(value.requestedAt)) ||
37
+ !["pending", "submitted"].includes(String(value.status))) {
38
+ return false;
39
+ }
40
+ if (value.status === "pending")
41
+ return true;
42
+ return (["passed", "failed", "unavailable"].includes(String(value.result)) &&
43
+ (value.noFindings === true
44
+ ? value.findingIds === undefined
45
+ : value.noFindings === undefined &&
46
+ Array.isArray(value.findingIds) &&
47
+ value.findingIds.length > 0 &&
48
+ value.findingIds.every((id) => typeof id === "string" && /^F-\d{4,}$/.test(id)) &&
49
+ new Set(value.findingIds).size === value.findingIds.length) &&
50
+ typeof value.reportSha256 === "string" &&
51
+ /^[0-9a-f]{64}$/i.test(value.reportSha256) &&
52
+ typeof value.submittedAt === "string" &&
53
+ Number.isFinite(Date.parse(value.submittedAt)) &&
54
+ typeof value.submittedBy === "string" &&
55
+ !!value.submittedBy.trim() &&
56
+ (value.result === "passed" || (typeof value.details === "string" && !!value.details.trim())));
57
+ }
58
+ export function isSpecialistReviewRequirement(value) {
59
+ return (isRecord(value) &&
60
+ typeof value.agent === "string" &&
61
+ !!value.agent.trim() &&
62
+ typeof value.details === "string" &&
63
+ !!value.details.trim() &&
64
+ typeof value.author === "string" &&
65
+ !!value.author.trim() &&
66
+ typeof value.requestedAt === "string" &&
67
+ Number.isFinite(Date.parse(value.requestedAt)));
68
+ }