@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,449 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFileSync, realpathSync, readlinkSync } from "node:fs";
3
+ import { isAbsolute, join, relative, resolve } from "node:path";
4
+ import matter from "gray-matter";
5
+ import { Service } from "../../Foundation/Service.js";
6
+ import { isSafeCoveragePath } from "../Persistence/CheckSchema.js";
7
+ import { lstatIfPresent, isPathInside } from "../Persistence/FileSystem.js";
8
+ export class GitEvidence extends Service {
9
+ git;
10
+ ledgerRepository;
11
+ constructor(app, git, ledgerRepository) {
12
+ super(app);
13
+ this.git = git;
14
+ this.ledgerRepository = ledgerRepository;
15
+ }
16
+ allGitChangePaths() {
17
+ const paths = new Set();
18
+ for (const args of [
19
+ ["diff", "--name-only", "-z", "--no-renames", "HEAD", "--", "."],
20
+ ["diff", "--cached", "--name-only", "-z", "--no-renames", "HEAD", "--", "."],
21
+ ["ls-files", "--others", "--exclude-standard", "-z"],
22
+ ]) {
23
+ for (const path of this.gitBuffer(args).toString("utf8").split("\0")) {
24
+ if (path)
25
+ paths.add(path);
26
+ }
27
+ }
28
+ return [...paths].sort();
29
+ }
30
+ deliveryRecordPaths(feature, ledger = this.ledgerRepository.readLedger(feature)) {
31
+ const lockPath = `.scratch/${feature.feature}/.sdd-tickets.lock`;
32
+ const allowed = this.workflowDeliveryRecordPaths(feature, ledger);
33
+ return this.allGitChangePaths().filter((path) => path !== lockPath && allowed.has(path));
34
+ }
35
+ workflowDeliveryRecordPaths(feature, ledger) {
36
+ const root = realpathSync(this.app.workspaceRoot);
37
+ const featurePrefix = `.scratch/${feature.feature}/`;
38
+ const paths = new Set([
39
+ relative(root, feature.spec),
40
+ relative(root, feature.ledger),
41
+ ...ledger.tickets.map((ticket) => `${featurePrefix}tickets/${ticket.id}.md`),
42
+ ]);
43
+ const reports = join(feature.workspace, "reports");
44
+ const addReport = (report) => {
45
+ if (!report)
46
+ return;
47
+ const path = isAbsolute(report) ? resolve(report) : resolve(feature.workspace, report);
48
+ if (isPathInside(reports, path) && /^[^/]+\.md$/.test(relative(reports, path))) {
49
+ paths.add(relative(root, path));
50
+ }
51
+ };
52
+ for (const ticket of ledger.tickets) {
53
+ addReport(ticket.assignment?.report);
54
+ for (const assignment of [
55
+ ...(ticket.reviewAssignments ?? []),
56
+ ...(ticket.reviewHistory ?? []),
57
+ ]) {
58
+ addReport(assignment.report);
59
+ }
60
+ for (const attempt of ticket.correctionAttempts ?? []) {
61
+ addReport(attempt.assignment.report);
62
+ addReport(attempt.priorAssignment?.report);
63
+ for (const assignment of attempt.reviewAssignments ?? [])
64
+ addReport(assignment.report);
65
+ }
66
+ for (const finding of ticket.findings ?? []) {
67
+ for (const provenance of finding.provenance)
68
+ addReport(provenance.report);
69
+ }
70
+ }
71
+ return paths;
72
+ }
73
+ worktreeContentDigest(paths) {
74
+ const records = paths.map((path) => {
75
+ const location = join(this.app.workspaceRoot, path);
76
+ const entry = lstatIfPresent(location);
77
+ if (!entry)
78
+ return [path, "deleted", ""];
79
+ if (entry.isSymbolicLink()) {
80
+ return [path, "120000", readlinkSync(location)];
81
+ }
82
+ if (!entry.isFile()) {
83
+ throw new Error(`Accepted path '${path}' is not a regular file or symlink.`);
84
+ }
85
+ const mode = entry.mode & 0o111 ? "100755" : "100644";
86
+ return [path, mode, readFileSync(location).toString("base64")];
87
+ });
88
+ return createHash("sha256").update(JSON.stringify(records)).digest("hex");
89
+ }
90
+ gitTreeContentDigest(commitSha, paths) {
91
+ const records = paths.map((path) => {
92
+ const entries = this.gitBuffer(["ls-tree", "-z", commitSha, "--", `:(literal)${path}`])
93
+ .toString("utf8")
94
+ .split("\0")
95
+ .filter(Boolean);
96
+ const exact = entries
97
+ .map((entry) => {
98
+ const [metadata, treePath] = entry.split("\t", 2);
99
+ const [mode, type, object] = (metadata ?? "").split(" ");
100
+ return treePath === path && mode && type === "blob" && object
101
+ ? { mode, object }
102
+ : undefined;
103
+ })
104
+ .find((entry) => entry !== undefined);
105
+ if (!exact)
106
+ return [path, "deleted", ""];
107
+ const content = this.gitBuffer(["cat-file", "blob", exact.object]).toString("base64");
108
+ return [path, exact.mode, content];
109
+ });
110
+ return createHash("sha256").update(JSON.stringify(records)).digest("hex");
111
+ }
112
+ gitIndexContentDigest(paths) {
113
+ const records = paths.map((path) => {
114
+ const entries = this.gitBuffer(["ls-files", "--stage", "-z", "--", `:(literal)${path}`])
115
+ .toString("utf8")
116
+ .split("\0")
117
+ .filter(Boolean);
118
+ if (entries.length !== 1)
119
+ return [path, "invalid-index-entry", ""];
120
+ const separator = entries[0].indexOf("\t");
121
+ if (separator < 0 || entries[0].slice(separator + 1) !== path) {
122
+ return [path, "invalid-index-entry", ""];
123
+ }
124
+ const [mode, object, stage] = entries[0].slice(0, separator).split(" ");
125
+ if (stage !== "0" ||
126
+ !object ||
127
+ (mode !== "100644" && mode !== "100755" && mode !== "120000")) {
128
+ return [path, "invalid-index-entry", ""];
129
+ }
130
+ const content = this.gitBuffer(["cat-file", "blob", object]).toString("base64");
131
+ return [path, mode, content];
132
+ });
133
+ return createHash("sha256").update(JSON.stringify(records)).digest("hex");
134
+ }
135
+ cleanGitBaseline(feature) {
136
+ const root = realpathSync(this.app.workspaceRoot);
137
+ const changes = this.gitChangeSummary(feature);
138
+ if (changes) {
139
+ const deliveryRecords = this.deliveryRecordPaths(feature);
140
+ if (deliveryRecords.length > 0) {
141
+ throw new Error(`Cannot claim while uncommitted delivery records remain; the caller must commit them first:\n${deliveryRecords.join("\n")}`);
142
+ }
143
+ throw new Error(`Cannot claim from a dirty checkout; commit or otherwise resolve staged, unstaged, and untracked changes first:\n${changes}`);
144
+ }
145
+ const revision = this.git.run(["rev-parse", "--verify", "HEAD"], {
146
+ cwd: root,
147
+ encoding: "utf8",
148
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
149
+ });
150
+ if (revision.error)
151
+ throw revision.error;
152
+ if (revision.status !== 0 || !/^[0-9a-f]{40,64}\n?$/i.test(revision.stdout)) {
153
+ throw new Error(`Unable to record the clean checkout's starting commit: ${revision.stderr.trim()}`);
154
+ }
155
+ return revision.stdout.trim();
156
+ }
157
+ contentIdentity(feature, ticket, baseSha = this.headSha(), checks, coveredPaths) {
158
+ const root = realpathSync(this.app.workspaceRoot);
159
+ const headSha = this.headSha();
160
+ const changedPaths = this.changedPaths(baseSha);
161
+ const paths = new Set(coveredPaths ?? changedPaths);
162
+ if (coveredPaths) {
163
+ for (const path of paths) {
164
+ if (!isSafeCoveragePath(path) || !isPathInside(root, resolve(root, path))) {
165
+ throw new Error(`Unsafe path in content identity scope: ${path}.`);
166
+ }
167
+ }
168
+ }
169
+ const code = createHash("sha256");
170
+ code.update("spindle-code-snapshot-v2\0");
171
+ const field = (tag, value) => {
172
+ const tagBytes = Buffer.from(tag);
173
+ const valueBytes = Buffer.isBuffer(value) ? value : Buffer.from(value);
174
+ const tagLength = Buffer.alloc(4);
175
+ tagLength.writeUInt32BE(tagBytes.length);
176
+ const valueLength = Buffer.alloc(8);
177
+ valueLength.writeBigUInt64BE(BigInt(valueBytes.length));
178
+ code.update(tagLength);
179
+ code.update(tagBytes);
180
+ code.update(valueLength);
181
+ code.update(valueBytes);
182
+ };
183
+ for (const path of [...paths].sort()) {
184
+ field("record", "path");
185
+ field("path", path);
186
+ field("index-stage", this.gitBuffer(["ls-files", "--stage", "-z", "--", `:(literal)${path}`]));
187
+ const indexed = this.git.run(["show", `:${path}`], {
188
+ cwd: root,
189
+ encoding: "buffer",
190
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
191
+ });
192
+ if (indexed.error)
193
+ throw indexed.error;
194
+ field("index-state", indexed.status === 0 ? "present" : "absent");
195
+ if (indexed.status === 0)
196
+ field("index-content", indexed.stdout);
197
+ const pathOnDisk = join(root, path);
198
+ const entry = lstatIfPresent(pathOnDisk);
199
+ if (!entry) {
200
+ field("worktree-state", "deleted");
201
+ }
202
+ else {
203
+ field("worktree-mode", String(entry.mode & 0o777));
204
+ if (entry.isSymbolicLink()) {
205
+ field("worktree-type", "symlink");
206
+ field("worktree-target", readlinkSync(pathOnDisk));
207
+ }
208
+ else if (entry.isFile()) {
209
+ field("worktree-type", "file");
210
+ field("worktree-content", readFileSync(pathOnDisk));
211
+ }
212
+ else if (entry.isDirectory()) {
213
+ throw new Error(`Cannot compute code identity for nested repository or worktree '${path}'; directory worktrees are unsupported.`);
214
+ }
215
+ else {
216
+ throw new Error(`Cannot compute code identity for unsupported non-file worktree path '${path}'.`);
217
+ }
218
+ }
219
+ }
220
+ const codeSnapshot = code.digest("hex");
221
+ const requirementParts = [
222
+ readFileSync(feature.spec, "utf8"),
223
+ this.ticketRequirements(ticket.raw),
224
+ checks?.required ?? [],
225
+ ];
226
+ const requirementsSnapshot = createHash("sha256")
227
+ .update(JSON.stringify(requirementParts))
228
+ .digest("hex");
229
+ const snapshot = createHash("sha256")
230
+ .update(JSON.stringify([codeSnapshot, requirementsSnapshot]))
231
+ .digest("hex");
232
+ return { snapshot, codeSnapshot, requirementsSnapshot, headSha };
233
+ }
234
+ deliveryIdentity(feature, ledger, tickets) {
235
+ if (tickets.length === 0)
236
+ throw new Error("Aggregate evidence requires at least one ticket.");
237
+ const delivery = ledger.delivery;
238
+ delivery.baseSha ??= ledger.tickets.find((progress) => progress.baseSha)?.baseSha;
239
+ if (!delivery.baseSha) {
240
+ throw new Error("Aggregate evidence requires the first ticket's recorded Git baseline.");
241
+ }
242
+ const codeIdentity = this.contentIdentity(feature, tickets[0], delivery.baseSha);
243
+ const requirementsSnapshot = createHash("sha256")
244
+ .update(JSON.stringify([
245
+ readFileSync(feature.spec, "utf8"),
246
+ tickets.map((ticket) => [
247
+ ticket.id,
248
+ ticket.title,
249
+ ticket.reporter,
250
+ ticket.category,
251
+ ticket.status,
252
+ ticket.blocked_by,
253
+ ticket.blocker,
254
+ this.ticketRequirements(ticket.raw),
255
+ ]),
256
+ (delivery.checks?.required ?? []).filter((requirement) => !requirement.removedAt),
257
+ ]))
258
+ .digest("hex");
259
+ const snapshot = createHash("sha256")
260
+ .update(JSON.stringify([codeIdentity.codeSnapshot, requirementsSnapshot]))
261
+ .digest("hex");
262
+ return {
263
+ snapshot,
264
+ codeSnapshot: codeIdentity.codeSnapshot,
265
+ requirementsSnapshot,
266
+ headSha: codeIdentity.headSha,
267
+ };
268
+ }
269
+ changedPaths(baseSha) {
270
+ const paths = new Set();
271
+ for (const args of [
272
+ ["diff", "--name-only", "-z", baseSha, "--", "."],
273
+ ["diff", "--cached", "--name-only", "-z", baseSha, "--", "."],
274
+ ["ls-files", "--others", "--exclude-standard", "-z"],
275
+ ]) {
276
+ const output = this.gitBuffer(args).toString("utf8");
277
+ for (const path of output.split("\0")) {
278
+ if (path && path !== ".scratch" && !path.startsWith(".scratch/"))
279
+ paths.add(path);
280
+ }
281
+ }
282
+ this.assertStableSubmodules(baseSha, paths);
283
+ return [...paths].sort();
284
+ }
285
+ assertStableSubmodules(baseSha, changedPaths) {
286
+ const gitlinks = new Map();
287
+ for (const record of this.gitBuffer(["ls-files", "--stage", "-z"])
288
+ .toString("utf8")
289
+ .split("\0")) {
290
+ const [metadata, path] = record.split("\t", 2);
291
+ if (!metadata || !path || path === ".scratch" || path.startsWith(".scratch/"))
292
+ continue;
293
+ const [mode, sha, stage] = metadata.split(" ");
294
+ if (mode === "160000" && sha && stage === "0")
295
+ gitlinks.set(path, sha);
296
+ }
297
+ const knownPaths = new Set(gitlinks.keys());
298
+ for (const record of this.gitBuffer(["ls-tree", "-r", "-z", baseSha])
299
+ .toString("utf8")
300
+ .split("\0")) {
301
+ const [metadata, path] = record.split("\t", 2);
302
+ if (metadata?.startsWith("160000 commit ") &&
303
+ path &&
304
+ path !== ".scratch" &&
305
+ !path.startsWith(".scratch/")) {
306
+ knownPaths.add(path);
307
+ }
308
+ }
309
+ for (const path of knownPaths) {
310
+ const entry = lstatIfPresent(join(this.app.workspaceRoot, path));
311
+ if (!entry) {
312
+ throw new Error(`Cannot compute code identity for submodule '${path}'; its worktree is missing or uninitialized.`);
313
+ }
314
+ if (!entry.isDirectory()) {
315
+ throw new Error(`Cannot compute code identity for submodule '${path}'; its worktree state is unsupported.`);
316
+ }
317
+ if (changedPaths.has(path)) {
318
+ throw new Error(`Cannot compute code identity for changed submodule '${path}'; submodule changes are unsupported.`);
319
+ }
320
+ if (!gitlinks.has(path))
321
+ continue;
322
+ let revision;
323
+ try {
324
+ revision = this.gitBuffer(["-C", path, "rev-parse", "--verify", "HEAD"])
325
+ .toString("utf8")
326
+ .trim();
327
+ }
328
+ catch {
329
+ throw new Error(`Cannot compute code identity for submodule '${path}'; its worktree is missing or uninitialized.`);
330
+ }
331
+ const status = this.gitBuffer([
332
+ "-C",
333
+ path,
334
+ "status",
335
+ "--porcelain",
336
+ "--untracked-files=all",
337
+ "--ignore-submodules=none",
338
+ ]).toString("utf8");
339
+ if (revision !== gitlinks.get(path) || status.length > 0) {
340
+ throw new Error(`Cannot compute code identity for changed submodule '${path}'; submodule changes are unsupported.`);
341
+ }
342
+ }
343
+ }
344
+ ticketRequirements(raw) {
345
+ const body = matter(raw).content;
346
+ const sections = new Map();
347
+ const headings = [...body.matchAll(/^##[ \t]+(.+?)[ \t]*\r?$/gm)];
348
+ for (let index = 0; index < headings.length; index++) {
349
+ const heading = headings[index];
350
+ const title = heading[1].trim().toLowerCase();
351
+ const start = heading.index + heading[0].length;
352
+ const end = headings[index + 1]?.index ?? body.length;
353
+ if (title === "requirements" || title === "acceptance criteria") {
354
+ sections.set(title, body.slice(start, end).trim());
355
+ }
356
+ if (title === "comments") {
357
+ const decisions = body
358
+ .slice(start, end)
359
+ .split(/\n[ \t]*\n/)
360
+ .filter((entry) => !entry.startsWith("[SDD-SCOPE-RULING ") && /\brequirements? decision\b/i.test(entry))
361
+ .map((entry) => entry.trim());
362
+ sections.set("requirements decisions", decisions.join("\n\n"));
363
+ }
364
+ }
365
+ return JSON.stringify([...sections.entries()].sort(([left], [right]) => left.localeCompare(right)));
366
+ }
367
+ headSha() {
368
+ const revision = this.git.run(["rev-parse", "--verify", "HEAD"], {
369
+ cwd: realpathSync(this.app.workspaceRoot),
370
+ encoding: "utf8",
371
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
372
+ });
373
+ if (revision.error)
374
+ throw revision.error;
375
+ if (revision.status !== 0 || !/^[0-9a-f]{40,64}\n?$/i.test(revision.stdout)) {
376
+ throw new Error(`Unable to identify current Git revision: ${revision.stderr.trim()}`);
377
+ }
378
+ return revision.stdout.trim();
379
+ }
380
+ gitBuffer(args) {
381
+ const result = this.git.run(args, {
382
+ cwd: realpathSync(this.app.workspaceRoot),
383
+ encoding: "buffer",
384
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
385
+ });
386
+ if (result.error)
387
+ throw result.error;
388
+ if (result.status !== 0) {
389
+ throw new Error(`git ${args[0]} failed: ${result.stderr.toString("utf8").trim()}`);
390
+ }
391
+ return result.stdout;
392
+ }
393
+ gitChangeSummary(feature) {
394
+ const root = realpathSync(this.app.workspaceRoot);
395
+ const relativeLock = relative(root, join(feature.workspace, ".sdd-tickets.lock"));
396
+ const status = this.git.run([
397
+ "--no-pager",
398
+ "-c",
399
+ "color.ui=false",
400
+ "status",
401
+ "--porcelain=v1",
402
+ "--untracked-files=all",
403
+ "--",
404
+ ".",
405
+ `:(exclude)${relativeLock}`,
406
+ ], {
407
+ cwd: root,
408
+ encoding: "utf8",
409
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
410
+ });
411
+ if (status.error)
412
+ throw status.error;
413
+ if (status.status !== 0) {
414
+ throw new Error(`git status failed while checking claim readiness: ${status.stderr.trim()}`);
415
+ }
416
+ return status.stdout.trim();
417
+ }
418
+ integrationPreservesEvidence(progress, identity) {
419
+ if (!("id" in progress))
420
+ return false;
421
+ const accepted = progress.acceptedEvidence;
422
+ if (progress.execution !== "integrated" ||
423
+ !progress.integration ||
424
+ !accepted ||
425
+ !progress.baseSha ||
426
+ identity.requirementsSnapshot !== accepted.requirementsSnapshot) {
427
+ return false;
428
+ }
429
+ try {
430
+ return (progress.integration.contentDigest === accepted.contentDigest &&
431
+ JSON.stringify(progress.integration.files) === JSON.stringify(accepted.files) &&
432
+ JSON.stringify(this.changedPaths(progress.baseSha)) === JSON.stringify(accepted.files) &&
433
+ this.worktreeContentDigest(accepted.files) === accepted.contentDigest);
434
+ }
435
+ catch {
436
+ return false;
437
+ }
438
+ }
439
+ assertSafeIntegrationChanges(feature, acceptedFiles, ledger) {
440
+ const allowed = new Set(acceptedFiles);
441
+ for (const path of this.workflowDeliveryRecordPaths(feature, ledger))
442
+ allowed.add(path);
443
+ const lockPath = `.scratch/${feature.feature}/.sdd-tickets.lock`;
444
+ const unrelated = this.allGitChangePaths().filter((path) => path !== lockPath && !allowed.has(path));
445
+ if (unrelated.length > 0) {
446
+ throw new Error(`Unsafe unrelated pending changes prevent integration: ${JSON.stringify(unrelated)}.`);
447
+ }
448
+ }
449
+ }
@@ -0,0 +1,80 @@
1
+ import { createHash } from "node:crypto";
2
+ import { readFileSync, realpathSync } from "node:fs";
3
+ import { isAbsolute, join, relative, resolve } from "node:path";
4
+ import { Service } from "../../Foundation/Service.js";
5
+ import { lstatIfPresent, isPathInside } from "../Persistence/FileSystem.js";
6
+ export class ReportEvidence extends Service {
7
+ pathSafety;
8
+ constructor(app, pathSafety) {
9
+ super(app);
10
+ this.pathSafety = pathSafety;
11
+ }
12
+ assertReportPath(feature, path, mustExist) {
13
+ const reports = join(feature.workspace, "reports");
14
+ const directory = lstatIfPresent(reports);
15
+ if (!directory?.isDirectory() || directory.isSymbolicLink()) {
16
+ throw new Error("Feature reports path must be a real directory.");
17
+ }
18
+ if (!isPathInside(reports, path) || !/^[^/]+\.md$/.test(path.slice(reports.length + 1))) {
19
+ throw new Error("Implementation report must be a Markdown file inside feature reports/.");
20
+ }
21
+ const root = realpathSync(this.app.workspaceRoot);
22
+ if (!isPathInside(root, path) || !isPathInside(root, realpathSync(reports))) {
23
+ throw new Error("Implementation report must remain inside this checkout.");
24
+ }
25
+ const relativePath = relative(root, path);
26
+ if (this.pathSafety.isIgnored(root, relativePath)) {
27
+ throw new Error(`Implementation report path is ignored: ${relativePath}`);
28
+ }
29
+ const entry = lstatIfPresent(path);
30
+ if (mustExist && !entry)
31
+ throw Object.assign(new Error("Report does not exist."), { code: "ENOENT" });
32
+ if (entry && (!entry.isFile() || entry.isSymbolicLink())) {
33
+ throw new Error(`Implementation report must be a regular file: ${path}.`);
34
+ }
35
+ if (entry && !isPathInside(reports, realpathSync(path))) {
36
+ throw new Error("Implementation report must remain inside feature reports/.");
37
+ }
38
+ }
39
+ submissionReportPath(feature, assignment, supplied) {
40
+ const path = supplied
41
+ ? isAbsolute(supplied)
42
+ ? resolve(supplied)
43
+ : resolve(feature.workspace, supplied)
44
+ : assignment.report;
45
+ if (path !== assignment.report) {
46
+ throw new Error(`Report target does not match the assigned destination ${assignment.report}.`);
47
+ }
48
+ this.assertReportPath(feature, path, false);
49
+ return path;
50
+ }
51
+ readReportDigest(feature, report) {
52
+ this.assertReportPath(feature, report, true);
53
+ return createHash("sha256").update(readFileSync(report)).digest("hex");
54
+ }
55
+ assertAcceptedReportsCurrent(feature, progress) {
56
+ const implementationReports = [
57
+ progress.assignment,
58
+ ...(progress.correctionAttempts ?? []).flatMap((attempt) => [attempt.assignment]),
59
+ ].filter((assignment) => !!assignment);
60
+ for (const assignment of implementationReports) {
61
+ if (assignment.status !== "submitted" || !assignment.reportSha256) {
62
+ throw new Error(`Implementation report for ${assignment.id} is not current and submitted.`);
63
+ }
64
+ if (this.readReportDigest(feature, assignment.report) !== assignment.reportSha256) {
65
+ throw new Error(`Implementation report for ${assignment.id} changed after submission.`);
66
+ }
67
+ }
68
+ const reviewReports = [
69
+ ...(progress.reviewAssignments ?? []),
70
+ ...(progress.correctionAttempts ?? []).flatMap((attempt) => attempt.reviewAssignments ?? []),
71
+ ];
72
+ for (const assignment of reviewReports) {
73
+ if (assignment.status !== "submitted" ||
74
+ !assignment.reportSha256 ||
75
+ this.readReportDigest(feature, assignment.report) !== assignment.reportSha256) {
76
+ throw new Error(`Review report for ${assignment.id} is not current and submitted.`);
77
+ }
78
+ }
79
+ }
80
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ export const FEATURE_DIRECTORY = /^(\d{4,})-([a-z0-9]+(?:-[a-z0-9]+)*)$/;
2
+ export const HISTORICAL_DATE_DIRECTORY = /^\d{4}-\d{2}-\d{2}-/;
3
+ export const TICKET_ID = /^T-(\d{4,})$/;
4
+ export const TICKET_CATEGORIES = ["bug", "enhancement"];
5
+ export const TRACKER_STATUSES = ["open", "in-progress", "closed"];
6
+ export const EXECUTION_STATES = [
7
+ "unstarted",
8
+ "claimed",
9
+ "implementing",
10
+ "reviewing",
11
+ "changes-requested",
12
+ "accepted",
13
+ "integrated",
14
+ "complete",
15
+ "blocked",
16
+ ];