perttool 0.7.1 → 0.8.0

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 (229) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/README.md +94 -18
  3. package/dist/actuals/lifecycle.d.ts +6 -20
  4. package/dist/actuals/lifecycle.d.ts.map +1 -1
  5. package/dist/actuals/lifecycle.js +5 -176
  6. package/dist/actuals/lifecycle.js.map +1 -1
  7. package/dist/actuals/reduction.d.ts +20 -0
  8. package/dist/actuals/reduction.d.ts.map +1 -0
  9. package/dist/actuals/reduction.js +175 -0
  10. package/dist/actuals/reduction.js.map +1 -0
  11. package/dist/analysis/service.d.ts +33 -0
  12. package/dist/analysis/service.d.ts.map +1 -0
  13. package/dist/analysis/service.js +153 -0
  14. package/dist/analysis/service.js.map +1 -0
  15. package/dist/analysis/target-temporal-input.d.ts +88 -0
  16. package/dist/analysis/target-temporal-input.d.ts.map +1 -0
  17. package/dist/analysis/target-temporal-input.js +249 -0
  18. package/dist/analysis/target-temporal-input.js.map +1 -0
  19. package/dist/analysis/temporal-deadline.d.ts +1 -1
  20. package/dist/analysis/temporal-deadline.d.ts.map +1 -1
  21. package/dist/analysis/temporal-precedence.d.ts +1 -1
  22. package/dist/analysis/temporal-precedence.d.ts.map +1 -1
  23. package/dist/analysis/temporal-precedence.js +1 -1
  24. package/dist/analysis/temporal-precedence.js.map +1 -1
  25. package/dist/analysis/temporal-resource.d.ts +1 -1
  26. package/dist/analysis/temporal-resource.d.ts.map +1 -1
  27. package/dist/analysis/temporal-resource.js +1 -1
  28. package/dist/analysis/temporal-resource.js.map +1 -1
  29. package/dist/application/advance-history.d.ts +2 -1
  30. package/dist/application/advance-history.d.ts.map +1 -1
  31. package/dist/application/advance-history.js +1 -1
  32. package/dist/application/advance-history.js.map +1 -1
  33. package/dist/application/analyze.d.ts +1 -32
  34. package/dist/application/analyze.d.ts.map +1 -1
  35. package/dist/application/analyze.js +1 -152
  36. package/dist/application/analyze.js.map +1 -1
  37. package/dist/application/check.d.ts +1 -42
  38. package/dist/application/check.d.ts.map +1 -1
  39. package/dist/application/check.js +1 -109
  40. package/dist/application/check.js.map +1 -1
  41. package/dist/application/cli-facade.d.ts +85 -0
  42. package/dist/application/cli-facade.d.ts.map +1 -0
  43. package/dist/application/cli-facade.js +108 -0
  44. package/dist/application/cli-facade.js.map +1 -0
  45. package/dist/application/contract6-mutation.js +2 -2
  46. package/dist/application/contract6-mutation.js.map +1 -1
  47. package/dist/application/contract7-mermaid.d.ts.map +1 -1
  48. package/dist/application/contract7-mermaid.js +3 -3
  49. package/dist/application/contract7-mermaid.js.map +1 -1
  50. package/dist/application/contract7-source.d.ts +1 -7
  51. package/dist/application/contract7-source.d.ts.map +1 -1
  52. package/dist/application/contract7-source.js +4 -12
  53. package/dist/application/contract7-source.js.map +1 -1
  54. package/dist/application/editor-dag-focus.d.ts +52 -0
  55. package/dist/application/editor-dag-focus.d.ts.map +1 -0
  56. package/dist/application/editor-dag-focus.js +132 -0
  57. package/dist/application/editor-dag-focus.js.map +1 -0
  58. package/dist/application/format.js +2 -2
  59. package/dist/application/format.js.map +1 -1
  60. package/dist/application/mutate.d.ts +1 -26
  61. package/dist/application/mutate.d.ts.map +1 -1
  62. package/dist/application/mutate.js +1 -205
  63. package/dist/application/mutate.js.map +1 -1
  64. package/dist/application/next.js +2 -2
  65. package/dist/application/next.js.map +1 -1
  66. package/dist/application/target-actuals-advance.js +2 -2
  67. package/dist/application/target-actuals-advance.js.map +1 -1
  68. package/dist/application/target-actuals-analysis.js +2 -2
  69. package/dist/application/target-actuals-analysis.js.map +1 -1
  70. package/dist/application/target-actuals-mutation.d.ts.map +1 -1
  71. package/dist/application/target-actuals-mutation.js +6 -5
  72. package/dist/application/target-actuals-mutation.js.map +1 -1
  73. package/dist/application/target-assurance-analysis.js +2 -2
  74. package/dist/application/target-assurance-analysis.js.map +1 -1
  75. package/dist/application/target-assurance-write.d.ts +2 -1
  76. package/dist/application/target-assurance-write.d.ts.map +1 -1
  77. package/dist/application/target-assurance-write.js +36 -12
  78. package/dist/application/target-assurance-write.js.map +1 -1
  79. package/dist/application/target-governance-mutation.js +2 -2
  80. package/dist/application/target-governance-mutation.js.map +1 -1
  81. package/dist/application/target-historical-graph.d.ts +124 -0
  82. package/dist/application/target-historical-graph.d.ts.map +1 -0
  83. package/dist/application/target-historical-graph.js +547 -0
  84. package/dist/application/target-historical-graph.js.map +1 -0
  85. package/dist/application/target-mutate.d.ts +1 -17
  86. package/dist/application/target-mutate.d.ts.map +1 -1
  87. package/dist/application/target-mutate.js +1 -159
  88. package/dist/application/target-mutate.js.map +1 -1
  89. package/dist/application/target-project-history.d.ts +2 -1
  90. package/dist/application/target-project-history.d.ts.map +1 -1
  91. package/dist/application/target-project-history.js +3 -3
  92. package/dist/application/target-project-history.js.map +1 -1
  93. package/dist/application/target-temporal-analysis.js +2 -2
  94. package/dist/application/target-temporal-analysis.js.map +1 -1
  95. package/dist/application/target-temporal-input.d.ts +1 -87
  96. package/dist/application/target-temporal-input.d.ts.map +1 -1
  97. package/dist/application/target-temporal-input.js +1 -248
  98. package/dist/application/target-temporal-input.js.map +1 -1
  99. package/dist/application/target-unit-migration-candidate.js +2 -2
  100. package/dist/application/target-unit-migration-candidate.js.map +1 -1
  101. package/dist/assurance/advance.js +2 -2
  102. package/dist/assurance/advance.js.map +1 -1
  103. package/dist/assurance/canonical.js +2 -2
  104. package/dist/assurance/canonical.js.map +1 -1
  105. package/dist/assurance/compatibility.js +2 -2
  106. package/dist/assurance/compatibility.js.map +1 -1
  107. package/dist/assurance/mermaid.d.ts +4 -2
  108. package/dist/assurance/mermaid.d.ts.map +1 -1
  109. package/dist/assurance/mermaid.js +11 -7
  110. package/dist/assurance/mermaid.js.map +1 -1
  111. package/dist/assurance/mutation.d.ts.map +1 -1
  112. package/dist/assurance/mutation.js +9 -23
  113. package/dist/assurance/mutation.js.map +1 -1
  114. package/dist/cli.js +66 -26
  115. package/dist/cli.js.map +1 -1
  116. package/dist/command/assurance-discovery.d.ts.map +1 -1
  117. package/dist/command/assurance-discovery.js +62 -2
  118. package/dist/command/assurance-discovery.js.map +1 -1
  119. package/dist/command/assurance-usage.d.ts.map +1 -1
  120. package/dist/command/assurance-usage.js +11 -0
  121. package/dist/command/assurance-usage.js.map +1 -1
  122. package/dist/conversion/mermaid-import.js +3 -3
  123. package/dist/conversion/mermaid-import.js.map +1 -1
  124. package/dist/conversion/mermaid.d.ts +1 -1
  125. package/dist/conversion/mermaid.d.ts.map +1 -1
  126. package/dist/conversion/mermaid.js +4 -4
  127. package/dist/conversion/mermaid.js.map +1 -1
  128. package/dist/core/index.d.ts +33 -0
  129. package/dist/core/index.d.ts.map +1 -0
  130. package/dist/core/index.js +13 -0
  131. package/dist/core/index.js.map +1 -0
  132. package/dist/core/source.d.ts +8 -0
  133. package/dist/core/source.d.ts.map +1 -0
  134. package/dist/core/source.js +11 -0
  135. package/dist/core/source.js.map +1 -0
  136. package/dist/formatter/source-formatter.d.ts +1 -1
  137. package/dist/formatter/source-formatter.d.ts.map +1 -1
  138. package/dist/formatter/source-formatter.js +1 -1
  139. package/dist/formatter/source-formatter.js.map +1 -1
  140. package/dist/guidance/validator.js +2 -2
  141. package/dist/guidance/validator.js.map +1 -1
  142. package/dist/help/assurance-guide.d.ts.map +1 -1
  143. package/dist/help/assurance-guide.js +78 -0
  144. package/dist/help/assurance-guide.js.map +1 -1
  145. package/dist/history/git-probe.d.ts +71 -0
  146. package/dist/history/git-probe.d.ts.map +1 -1
  147. package/dist/history/git-probe.js +517 -0
  148. package/dist/history/git-probe.js.map +1 -1
  149. package/dist/history/historical-graph.d.ts +174 -0
  150. package/dist/history/historical-graph.d.ts.map +1 -0
  151. package/dist/history/historical-graph.js +870 -0
  152. package/dist/history/historical-graph.js.map +1 -0
  153. package/dist/history/historical-transition.d.ts +311 -0
  154. package/dist/history/historical-transition.d.ts.map +1 -0
  155. package/dist/history/historical-transition.js +998 -0
  156. package/dist/history/historical-transition.js.map +1 -0
  157. package/dist/history/project-history.d.ts.map +1 -1
  158. package/dist/history/project-history.js +2 -2
  159. package/dist/history/project-history.js.map +1 -1
  160. package/dist/index.d.ts +2 -0
  161. package/dist/index.d.ts.map +1 -1
  162. package/dist/index.js +1 -0
  163. package/dist/index.js.map +1 -1
  164. package/dist/io/document-file.js +2 -2
  165. package/dist/io/document-file.js.map +1 -1
  166. package/dist/io/safe-write.js +1 -1
  167. package/dist/io/safe-write.js.map +1 -1
  168. package/dist/model/sha256.d.ts +5 -0
  169. package/dist/model/sha256.d.ts.map +1 -0
  170. package/dist/model/sha256.js +105 -0
  171. package/dist/model/sha256.js.map +1 -0
  172. package/dist/mutation/advance.js +3 -3
  173. package/dist/mutation/advance.js.map +1 -1
  174. package/dist/mutation/diagnostics.d.ts +2 -2
  175. package/dist/mutation/diagnostics.d.ts.map +1 -1
  176. package/dist/mutation/diagnostics.js.map +1 -1
  177. package/dist/mutation/planner.d.ts +27 -0
  178. package/dist/mutation/planner.d.ts.map +1 -0
  179. package/dist/mutation/planner.js +206 -0
  180. package/dist/mutation/planner.js.map +1 -0
  181. package/dist/mutation/source.d.ts +2 -1
  182. package/dist/mutation/source.d.ts.map +1 -1
  183. package/dist/mutation/source.js +14 -0
  184. package/dist/mutation/source.js.map +1 -1
  185. package/dist/mutation/target-planner.d.ts +18 -0
  186. package/dist/mutation/target-planner.d.ts.map +1 -0
  187. package/dist/mutation/target-planner.js +160 -0
  188. package/dist/mutation/target-planner.js.map +1 -0
  189. package/dist/mutation/task.d.ts.map +1 -1
  190. package/dist/mutation/task.js +19 -11
  191. package/dist/mutation/task.js.map +1 -1
  192. package/dist/mutation/types.d.ts +1 -1
  193. package/dist/mutation/types.d.ts.map +1 -1
  194. package/dist/node/historical-host.d.ts +6 -0
  195. package/dist/node/historical-host.d.ts.map +1 -0
  196. package/dist/node/historical-host.js +8 -0
  197. package/dist/node/historical-host.js.map +1 -0
  198. package/dist/node/host.d.ts +3 -0
  199. package/dist/node/host.d.ts.map +1 -0
  200. package/dist/node/host.js +48 -0
  201. package/dist/node/host.js.map +1 -0
  202. package/dist/node/index.d.ts +2 -0
  203. package/dist/node/index.d.ts.map +1 -0
  204. package/dist/node/index.js +2 -0
  205. package/dist/node/index.js.map +1 -0
  206. package/dist/perttool-vscode-private-0.0.0-d1578b9.vsix +0 -0
  207. package/dist/ports/node-host.d.ts +41 -0
  208. package/dist/ports/node-host.d.ts.map +1 -0
  209. package/dist/ports/node-host.js +2 -0
  210. package/dist/ports/node-host.js.map +1 -0
  211. package/dist/recommendation/override.d.ts +40 -2
  212. package/dist/recommendation/override.d.ts.map +1 -1
  213. package/dist/recommendation/override.js +5 -6
  214. package/dist/recommendation/override.js.map +1 -1
  215. package/dist/schema/registry.d.ts.map +1 -1
  216. package/dist/schema/registry.js +1 -0
  217. package/dist/schema/registry.js.map +1 -1
  218. package/dist/semantic/check.d.ts +43 -0
  219. package/dist/semantic/check.d.ts.map +1 -0
  220. package/dist/semantic/check.js +110 -0
  221. package/dist/semantic/check.js.map +1 -0
  222. package/dist/session/document-session.d.ts +109 -0
  223. package/dist/session/document-session.d.ts.map +1 -0
  224. package/dist/session/document-session.js +432 -0
  225. package/dist/session/document-session.js.map +1 -0
  226. package/dist/version.d.ts +1 -1
  227. package/dist/version.js +1 -1
  228. package/package.json +18 -4
  229. package/schemas/Perttool.HistoricalGraphResult.v1.schema.json +654 -0
@@ -5,6 +5,12 @@ import { dirname, isAbsolute, relative, resolve, sep, } from "node:path";
5
5
  import { digestDocumentBytes } from "../io/document-file.js";
6
6
  export const GIT_HISTORY_PROBE_MODEL_VERSION = 1;
7
7
  export const ADVANCE_HISTORY_BASELINE_MODEL_VERSION = 1;
8
+ export const HISTORICAL_GIT_EVIDENCE_MODEL_VERSION = 1;
9
+ export const HISTORICAL_GIT_EVIDENCE_LIMITS = Object.freeze({
10
+ inspectedCommits: 2_048,
11
+ rawBytesPerSnapshot: 8_388_608,
12
+ aggregateRawSnapshotBytes: 134_217_728,
13
+ });
8
14
  const gitTimeoutMilliseconds = 30_000;
9
15
  const gitMaxBufferBytes = 64 * 1024 * 1024;
10
16
  const availabilityOrder = new Map([
@@ -574,6 +580,517 @@ export async function probeGitHistory(request, dependencies = {}) {
574
580
  availability: allCauses,
575
581
  };
576
582
  }
583
+ function historicalLimits(overrides) {
584
+ function select(name) {
585
+ const value = overrides?.[name] ?? HISTORICAL_GIT_EVIDENCE_LIMITS[name];
586
+ return Number.isSafeInteger(value) && value > 0
587
+ ? value
588
+ : HISTORICAL_GIT_EVIDENCE_LIMITS[name];
589
+ }
590
+ return Object.freeze({
591
+ inspectedCommits: select("inspectedCommits"),
592
+ rawBytesPerSnapshot: select("rawBytesPerSnapshot"),
593
+ aggregateRawSnapshotBytes: select("aggregateRawSnapshotBytes"),
594
+ });
595
+ }
596
+ function historicalCause(cause, subject, options = {}) {
597
+ return Object.freeze({
598
+ cause,
599
+ subject,
600
+ commitId: options.commitId ?? null,
601
+ limit: options.limit ?? null,
602
+ actual: options.actual ?? null,
603
+ });
604
+ }
605
+ function historicalEvidenceResult(request, limits, status, causes, fields = {}) {
606
+ return Object.freeze({
607
+ ok: true,
608
+ modelVersion: HISTORICAL_GIT_EVIDENCE_MODEL_VERSION,
609
+ status,
610
+ ancestryProfile: "first_parent",
611
+ objectFormat: fields.objectFormat ?? null,
612
+ repositoryId: fields.repositoryId ?? null,
613
+ repositoryReadSnapshotId: fields.repositoryReadSnapshotId ?? null,
614
+ repositoryRelativePath: fields.repositoryRelativePath ?? null,
615
+ requestedEndpoint: request.requestedEndpoint ?? "HEAD",
616
+ resolvedEndpoint: fields.resolvedEndpoint ?? null,
617
+ requestedLowerBoundary: request.lowerBoundary ?? null,
618
+ resolvedLowerBoundary: fields.resolvedLowerBoundary ?? null,
619
+ oldestInspectedCommitId: fields.oldestInspectedCommitId ?? null,
620
+ currentSourceDigest: fields.currentSourceDigest ?? null,
621
+ aggregateRawSnapshotBytes: fields.aggregateRawSnapshotBytes ?? 0,
622
+ limits,
623
+ inspectedCommitIds: Object.freeze([
624
+ ...(fields.inspectedCommitIds ?? []),
625
+ ]),
626
+ snapshots: Object.freeze([...(fields.snapshots ?? [])]),
627
+ causes: Object.freeze([...causes]),
628
+ });
629
+ }
630
+ function parseHistoricalCommitList(result, operation, objectFormat) {
631
+ const text = stdoutText(result, operation);
632
+ if (typeof text !== "string")
633
+ return text;
634
+ const values = text === "" ? [] : text.split("\n");
635
+ if (values.some((value) => !commitId(value, objectFormat)) ||
636
+ new Set(values).size !== values.length) {
637
+ return malformed(operation);
638
+ }
639
+ return values;
640
+ }
641
+ function resolveHistoricalRevision(executable, repositoryRoot, spelling, objectFormat, operation) {
642
+ const commitCommand = runGit(executable, repositoryRoot, operation, [
643
+ "rev-parse",
644
+ "--verify",
645
+ "--end-of-options",
646
+ `${spelling}^{commit}`,
647
+ ]);
648
+ if (commitCommand.ok) {
649
+ const text = stdoutText(commitCommand, operation);
650
+ if (typeof text !== "string")
651
+ return { ok: false, failure: text };
652
+ if (!commitId(text, objectFormat)) {
653
+ return { ok: false, failure: malformed(operation) };
654
+ }
655
+ return { ok: true, commitId: text };
656
+ }
657
+ if (commitCommand.kind === "failure") {
658
+ return { ok: false, failure: commitCommand.failure };
659
+ }
660
+ if (/^[0-9a-fA-F]{4,64}$/.test(spelling)) {
661
+ const disambiguate = runGit(executable, repositoryRoot, `${operation}_disambiguate`, ["rev-parse", `--disambiguate=${spelling.toLowerCase()}`]);
662
+ if (!disambiguate.ok) {
663
+ if (disambiguate.kind === "failure") {
664
+ return { ok: false, failure: disambiguate.failure };
665
+ }
666
+ }
667
+ else {
668
+ const candidates = parseHistoricalCommitList(disambiguate, `${operation}_disambiguate`, objectFormat);
669
+ if (!Array.isArray(candidates)) {
670
+ return { ok: false, failure: candidates };
671
+ }
672
+ if (candidates.length > 1) {
673
+ return { ok: false, cause: "ambiguous_revision" };
674
+ }
675
+ }
676
+ }
677
+ const objectCommand = runGit(executable, repositoryRoot, `${operation}_object`, [
678
+ "rev-parse",
679
+ "--verify",
680
+ "--end-of-options",
681
+ `${spelling}^{object}`,
682
+ ]);
683
+ if (!objectCommand.ok) {
684
+ return objectCommand.kind === "failure"
685
+ ? { ok: false, failure: objectCommand.failure }
686
+ : { ok: false, cause: "unknown_revision" };
687
+ }
688
+ const objectText = stdoutText(objectCommand, `${operation}_object`);
689
+ if (typeof objectText !== "string") {
690
+ return { ok: false, failure: objectText };
691
+ }
692
+ if (!commitId(objectText, objectFormat)) {
693
+ return { ok: false, failure: malformed(`${operation}_object`) };
694
+ }
695
+ return { ok: false, cause: "non_commit_revision" };
696
+ }
697
+ function readHistoricalTreeEntry(executable, repositoryRoot, commit, relativePath, objectFormat, operation) {
698
+ const command = runGit(executable, repositoryRoot, operation, ["ls-tree", "-z", commit, "--", relativePath]);
699
+ if (!command.ok) {
700
+ return command.kind === "failure"
701
+ ? command.failure
702
+ : commandFailure(operation);
703
+ }
704
+ if (command.stdout.length === 0)
705
+ return { kind: "missing" };
706
+ const text = stdoutText(command, operation);
707
+ if (typeof text !== "string")
708
+ return text;
709
+ const match = /^([0-7]{6}) ([a-z]+) ([0-9a-f]+)\t([^\u0000]+)\u0000?$/.exec(text);
710
+ if (match === null ||
711
+ match[4] !== relativePath ||
712
+ !commitId(match[3], objectFormat)) {
713
+ return malformed(operation);
714
+ }
715
+ return Object.freeze({
716
+ kind: "object",
717
+ mode: match[1],
718
+ objectType: match[2],
719
+ objectId: match[3],
720
+ });
721
+ }
722
+ function regularHistoricalBlob(entry) {
723
+ return entry.kind === "object" &&
724
+ entry.objectType === "blob" &&
725
+ (entry.mode === "100644" || entry.mode === "100755");
726
+ }
727
+ function repositoryIdentity(commonDirectory) {
728
+ const digest = digestDocumentBytes(Buffer.from(commonDirectory, "utf8"));
729
+ return `git-repository:${digest}`;
730
+ }
731
+ export async function probeHistoricalGitEvidence(request, dependencies = {}) {
732
+ const limits = historicalLimits(dependencies.limits);
733
+ const requestedEndpoint = request.requestedEndpoint ?? "HEAD";
734
+ const executable = dependencies.gitExecutable ?? "git";
735
+ const targetPath = resolve(request.targetPath);
736
+ const initialTarget = await captureTarget(targetPath);
737
+ if (!initialTarget.ok) {
738
+ if ("ambiguous" in initialTarget) {
739
+ return historicalEvidenceResult(request, limits, "unavailable", [historicalCause("ambiguous_path", "target_path")]);
740
+ }
741
+ return initialTarget.failure;
742
+ }
743
+ const repositoryCommand = runGit(executable, dirname(initialTarget.capture.realPath), "historical_repository_root", ["rev-parse", "--path-format=absolute", "--show-toplevel"]);
744
+ if (!repositoryCommand.ok) {
745
+ if (repositoryCommand.kind === "failure") {
746
+ return repositoryCommand.failure;
747
+ }
748
+ return historicalEvidenceResult(request, limits, "unavailable", [historicalCause("no_repository", "repository")], { currentSourceDigest: initialTarget.capture.digest });
749
+ }
750
+ const repositoryText = stdoutText(repositoryCommand, "historical_repository_root");
751
+ if (typeof repositoryText !== "string")
752
+ return repositoryText;
753
+ let repositoryRoot;
754
+ try {
755
+ repositoryRoot = await realpath(repositoryText);
756
+ }
757
+ catch {
758
+ return commandFailure("historical_repository_root");
759
+ }
760
+ const relativePath = normalizeRepositoryPath(repositoryRoot, initialTarget.capture.realPath);
761
+ if (relativePath === null) {
762
+ return historicalEvidenceResult(request, limits, "unavailable", [historicalCause("ambiguous_path", "target_path")], { currentSourceDigest: initialTarget.capture.digest });
763
+ }
764
+ const headCommand = runGit(executable, repositoryRoot, "historical_resolve_head", ["rev-parse", "--verify", "--end-of-options", "HEAD^{commit}"]);
765
+ if (!headCommand.ok) {
766
+ if (headCommand.kind === "failure")
767
+ return headCommand.failure;
768
+ return historicalEvidenceResult(request, limits, "unavailable", [historicalCause("no_head", "repository")], {
769
+ repositoryRelativePath: relativePath,
770
+ currentSourceDigest: initialTarget.capture.digest,
771
+ });
772
+ }
773
+ const formatCommand = runGit(executable, repositoryRoot, "historical_object_format", ["rev-parse", "--show-object-format=storage"]);
774
+ if (!formatCommand.ok) {
775
+ return formatCommand.kind === "failure"
776
+ ? formatCommand.failure
777
+ : commandFailure("historical_object_format");
778
+ }
779
+ const formatText = stdoutText(formatCommand, "historical_object_format");
780
+ if (typeof formatText !== "string")
781
+ return formatText;
782
+ if (formatText !== "sha1" && formatText !== "sha256") {
783
+ return historicalEvidenceResult(request, limits, "unavailable", [historicalCause("unsupported_object_format", "repository")], {
784
+ repositoryRelativePath: relativePath,
785
+ currentSourceDigest: initialTarget.capture.digest,
786
+ });
787
+ }
788
+ const objectFormat = formatText;
789
+ const commonCommand = runGit(executable, repositoryRoot, "historical_common_directory", ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
790
+ if (!commonCommand.ok) {
791
+ return commonCommand.kind === "failure"
792
+ ? commonCommand.failure
793
+ : commandFailure("historical_common_directory");
794
+ }
795
+ const commonText = stdoutText(commonCommand, "historical_common_directory");
796
+ if (typeof commonText !== "string")
797
+ return commonText;
798
+ let commonDirectory;
799
+ try {
800
+ commonDirectory = await realpath(commonText);
801
+ }
802
+ catch {
803
+ return commandFailure("historical_common_directory");
804
+ }
805
+ const repositoryId = repositoryIdentity(commonDirectory);
806
+ const baseFields = {
807
+ objectFormat,
808
+ repositoryId,
809
+ repositoryRelativePath: relativePath,
810
+ currentSourceDigest: initialTarget.capture.digest,
811
+ };
812
+ if (request.expectedSourceDigest !== undefined &&
813
+ request.expectedSourceDigest !== initialTarget.capture.digest) {
814
+ return historicalEvidenceResult(request, limits, "unavailable", [historicalCause("target_changed", "target_path")], baseFields);
815
+ }
816
+ const endpoint = resolveHistoricalRevision(executable, repositoryRoot, requestedEndpoint, objectFormat, "historical_resolve_endpoint");
817
+ if (!endpoint.ok) {
818
+ if ("failure" in endpoint)
819
+ return endpoint.failure;
820
+ return historicalEvidenceResult(request, limits, "unavailable", [historicalCause(endpoint.cause, "endpoint")], baseFields);
821
+ }
822
+ const resolvedEndpoint = endpoint.commitId;
823
+ const endpointFields = {
824
+ ...baseFields,
825
+ resolvedEndpoint,
826
+ };
827
+ const entryCache = new Map();
828
+ const endpointEntry = readHistoricalTreeEntry(executable, repositoryRoot, resolvedEndpoint, relativePath, objectFormat, "historical_endpoint_tree");
829
+ if ("ok" in endpointEntry)
830
+ return endpointEntry;
831
+ entryCache.set(resolvedEndpoint, endpointEntry);
832
+ if (!regularHistoricalBlob(endpointEntry)) {
833
+ return historicalEvidenceResult(request, limits, "unavailable", [
834
+ historicalCause("endpoint_path_missing", "endpoint", {
835
+ commitId: resolvedEndpoint,
836
+ }),
837
+ ], endpointFields);
838
+ }
839
+ const shallowCommand = runGit(executable, repositoryRoot, "historical_shallow_state", ["rev-parse", "--is-shallow-repository"]);
840
+ if (!shallowCommand.ok) {
841
+ return shallowCommand.kind === "failure"
842
+ ? shallowCommand.failure
843
+ : commandFailure("historical_shallow_state");
844
+ }
845
+ const shallowText = stdoutText(shallowCommand, "historical_shallow_state");
846
+ if (typeof shallowText !== "string")
847
+ return shallowText;
848
+ if (shallowText !== "true" && shallowText !== "false") {
849
+ return malformed("historical_shallow_state");
850
+ }
851
+ const shallow = shallowText === "true";
852
+ const laneCommand = runGit(executable, repositoryRoot, "historical_first_parent_lane", ["rev-list", "--first-parent", "--reverse", resolvedEndpoint]);
853
+ if (!laneCommand.ok) {
854
+ return laneCommand.kind === "failure"
855
+ ? laneCommand.failure
856
+ : commandFailure("historical_first_parent_lane");
857
+ }
858
+ const lane = parseHistoricalCommitList(laneCommand, "historical_first_parent_lane", objectFormat);
859
+ if (!Array.isArray(lane))
860
+ return lane;
861
+ if (lane.length === 0 || lane.at(-1) !== resolvedEndpoint) {
862
+ return malformed("historical_first_parent_lane");
863
+ }
864
+ const laneOrder = new Map(lane.map((id, index) => [id, index]));
865
+ let resolvedLowerBoundary = null;
866
+ let lowerIndex = 0;
867
+ if (request.lowerBoundary !== undefined) {
868
+ const lower = resolveHistoricalRevision(executable, repositoryRoot, request.lowerBoundary, objectFormat, "historical_resolve_lower");
869
+ if (!lower.ok) {
870
+ if ("failure" in lower)
871
+ return lower.failure;
872
+ return historicalEvidenceResult(request, limits, "unavailable", [historicalCause(lower.cause, "lower_boundary")], endpointFields);
873
+ }
874
+ resolvedLowerBoundary = lower.commitId;
875
+ const index = laneOrder.get(resolvedLowerBoundary);
876
+ if (index === undefined) {
877
+ return historicalEvidenceResult(request, limits, "unavailable", [
878
+ historicalCause(shallow
879
+ ? "shallow_origin"
880
+ : "lower_not_first_parent_ancestor", "lower_boundary", { commitId: resolvedLowerBoundary }),
881
+ ], { ...endpointFields, resolvedLowerBoundary });
882
+ }
883
+ lowerIndex = index;
884
+ const lowerEntry = resolvedLowerBoundary === resolvedEndpoint
885
+ ? endpointEntry
886
+ : readHistoricalTreeEntry(executable, repositoryRoot, resolvedLowerBoundary, relativePath, objectFormat, "historical_lower_tree");
887
+ if ("ok" in lowerEntry)
888
+ return lowerEntry;
889
+ entryCache.set(resolvedLowerBoundary, lowerEntry);
890
+ if (!regularHistoricalBlob(lowerEntry)) {
891
+ return historicalEvidenceResult(request, limits, "unavailable", [
892
+ historicalCause("lower_path_missing", "lower_boundary", {
893
+ commitId: resolvedLowerBoundary,
894
+ }),
895
+ ], { ...endpointFields, resolvedLowerBoundary });
896
+ }
897
+ }
898
+ const changesCommand = runGit(executable, repositoryRoot, "historical_path_changes", [
899
+ "rev-list",
900
+ "--first-parent",
901
+ "--full-history",
902
+ "--reverse",
903
+ resolvedEndpoint,
904
+ "--",
905
+ relativePath,
906
+ ]);
907
+ if (!changesCommand.ok) {
908
+ return changesCommand.kind === "failure"
909
+ ? changesCommand.failure
910
+ : commandFailure("historical_path_changes");
911
+ }
912
+ const pathChanges = parseHistoricalCommitList(changesCommand, "historical_path_changes", objectFormat);
913
+ if (!Array.isArray(pathChanges))
914
+ return pathChanges;
915
+ if (pathChanges.some((id) => !laneOrder.has(id))) {
916
+ return malformed("historical_path_changes");
917
+ }
918
+ const selected = new Set();
919
+ if (resolvedLowerBoundary !== null)
920
+ selected.add(resolvedLowerBoundary);
921
+ for (const id of pathChanges) {
922
+ const index = laneOrder.get(id);
923
+ if (index >= lowerIndex)
924
+ selected.add(id);
925
+ }
926
+ selected.add(resolvedEndpoint);
927
+ const inspectedCommitIds = [...selected].sort((left, right) => laneOrder.get(left) - laneOrder.get(right));
928
+ if (inspectedCommitIds.length > limits.inspectedCommits) {
929
+ return historicalEvidenceResult(request, limits, "unavailable", [
930
+ historicalCause("hard_limit", "inspection", {
931
+ limit: "inspectedCommits",
932
+ actual: inspectedCommitIds.length,
933
+ }),
934
+ ], { ...endpointFields, resolvedLowerBoundary });
935
+ }
936
+ const pending = [];
937
+ let aggregateRawSnapshotBytes = 0;
938
+ for (const inspectedCommitId of inspectedCommitIds) {
939
+ const metadataCommand = runGit(executable, repositoryRoot, "historical_commit_metadata", ["show", "-s", "--format=%P%x00%cI", inspectedCommitId]);
940
+ if (!metadataCommand.ok) {
941
+ return metadataCommand.kind === "failure"
942
+ ? metadataCommand.failure
943
+ : historicalEvidenceResult(request, limits, "unavailable", [
944
+ historicalCause("object_read_failed", "inspection", {
945
+ commitId: inspectedCommitId,
946
+ }),
947
+ ], { ...endpointFields, resolvedLowerBoundary });
948
+ }
949
+ const metadataText = stdoutText(metadataCommand, "historical_commit_metadata");
950
+ if (typeof metadataText !== "string")
951
+ return metadataText;
952
+ const metadata = parseGitCommitMetadata(metadataText, objectFormat);
953
+ if (metadata === null)
954
+ return malformed("historical_commit_metadata");
955
+ let entry = entryCache.get(inspectedCommitId);
956
+ if (entry === undefined) {
957
+ const read = readHistoricalTreeEntry(executable, repositoryRoot, inspectedCommitId, relativePath, objectFormat, "historical_snapshot_tree");
958
+ if ("ok" in read)
959
+ return read;
960
+ entry = read;
961
+ entryCache.set(inspectedCommitId, entry);
962
+ }
963
+ let blobId = null;
964
+ let source = null;
965
+ let sourceDigest = null;
966
+ if (entry.kind === "object" && entry.objectType === "blob") {
967
+ blobId = entry.objectId;
968
+ const sourceCommand = runGit(executable, repositoryRoot, "historical_snapshot_blob", ["cat-file", "blob", blobId]);
969
+ if (!sourceCommand.ok) {
970
+ return sourceCommand.kind === "failure"
971
+ ? sourceCommand.failure
972
+ : historicalEvidenceResult(request, limits, "unavailable", [
973
+ historicalCause("object_read_failed", "inspection", {
974
+ commitId: inspectedCommitId,
975
+ }),
976
+ ], { ...endpointFields, resolvedLowerBoundary });
977
+ }
978
+ if (sourceCommand.stdout.length > limits.rawBytesPerSnapshot) {
979
+ return historicalEvidenceResult(request, limits, "unavailable", [
980
+ historicalCause("hard_limit", "inspection", {
981
+ commitId: inspectedCommitId,
982
+ limit: "rawBytesPerSnapshot",
983
+ actual: sourceCommand.stdout.length,
984
+ }),
985
+ ], { ...endpointFields, resolvedLowerBoundary });
986
+ }
987
+ const nextAggregate = aggregateRawSnapshotBytes + sourceCommand.stdout.length;
988
+ if (nextAggregate > limits.aggregateRawSnapshotBytes) {
989
+ return historicalEvidenceResult(request, limits, "unavailable", [
990
+ historicalCause("hard_limit", "inspection", {
991
+ commitId: inspectedCommitId,
992
+ limit: "aggregateRawSnapshotBytes",
993
+ actual: nextAggregate,
994
+ }),
995
+ ], { ...endpointFields, resolvedLowerBoundary });
996
+ }
997
+ aggregateRawSnapshotBytes = nextAggregate;
998
+ source = new Uint8Array(sourceCommand.stdout);
999
+ sourceDigest = digestDocumentBytes(source);
1000
+ }
1001
+ pending.push(Object.freeze({
1002
+ commitId: inspectedCommitId,
1003
+ parentCommitIds: Object.freeze([...metadata.parentCommitIds]),
1004
+ blobId,
1005
+ sourceDigest,
1006
+ source,
1007
+ recordedAt: metadata.recordedAt,
1008
+ isEndpoint: inspectedCommitId === resolvedEndpoint,
1009
+ isLowerBoundary: inspectedCommitId === resolvedLowerBoundary,
1010
+ }));
1011
+ }
1012
+ const readSnapshotDigest = digestDocumentBytes(Buffer.from(JSON.stringify({
1013
+ model: "Perttool.HistoricalGitEvidence.v1",
1014
+ objectFormat,
1015
+ repositoryId,
1016
+ repositoryRelativePath: relativePath,
1017
+ resolvedEndpoint,
1018
+ resolvedLowerBoundary,
1019
+ snapshots: pending.map((snapshot) => ({
1020
+ commitId: snapshot.commitId,
1021
+ parentCommitIds: snapshot.parentCommitIds,
1022
+ blobId: snapshot.blobId,
1023
+ sourceDigest: snapshot.sourceDigest,
1024
+ })),
1025
+ }), "utf8"));
1026
+ const repositoryReadSnapshotId = `git-read:${readSnapshotDigest}`;
1027
+ if (dependencies.afterEvidence !== undefined) {
1028
+ await dependencies.afterEvidence();
1029
+ }
1030
+ const finalEndpoint = resolveHistoricalRevision(executable, repositoryRoot, requestedEndpoint, objectFormat, "historical_recheck_endpoint");
1031
+ if (!finalEndpoint.ok) {
1032
+ if ("failure" in finalEndpoint)
1033
+ return finalEndpoint.failure;
1034
+ return historicalEvidenceResult(request, limits, "unavailable", [historicalCause("repository_race", "endpoint")], { ...endpointFields, resolvedLowerBoundary });
1035
+ }
1036
+ let finalLowerCommit = null;
1037
+ if (request.lowerBoundary !== undefined) {
1038
+ const finalLower = resolveHistoricalRevision(executable, repositoryRoot, request.lowerBoundary, objectFormat, "historical_recheck_lower");
1039
+ if (!finalLower.ok) {
1040
+ if ("failure" in finalLower)
1041
+ return finalLower.failure;
1042
+ return historicalEvidenceResult(request, limits, "unavailable", [historicalCause("repository_race", "lower_boundary")], { ...endpointFields, resolvedLowerBoundary });
1043
+ }
1044
+ finalLowerCommit = finalLower.commitId;
1045
+ }
1046
+ const finalTarget = await captureTarget(targetPath);
1047
+ let finalCommonDirectory = null;
1048
+ try {
1049
+ finalCommonDirectory = await realpath(commonText);
1050
+ }
1051
+ catch {
1052
+ finalCommonDirectory = null;
1053
+ }
1054
+ if (finalEndpoint.commitId !== resolvedEndpoint ||
1055
+ finalLowerCommit !== resolvedLowerBoundary ||
1056
+ finalCommonDirectory !== commonDirectory ||
1057
+ !finalTarget.ok ||
1058
+ !sameTarget(initialTarget.capture, finalTarget.capture)) {
1059
+ return historicalEvidenceResult(request, limits, "unavailable", [historicalCause("repository_race", "repository")], { ...endpointFields, resolvedLowerBoundary });
1060
+ }
1061
+ const snapshots = pending.map((snapshot) => Object.freeze({
1062
+ modelVersion: HISTORICAL_GIT_EVIDENCE_MODEL_VERSION,
1063
+ objectFormat,
1064
+ repositoryId,
1065
+ repositoryReadSnapshotId,
1066
+ repositoryRelativePath: relativePath,
1067
+ commitId: snapshot.commitId,
1068
+ parentCommitIds: snapshot.parentCommitIds,
1069
+ blobId: snapshot.blobId,
1070
+ sourceDigest: snapshot.sourceDigest,
1071
+ source: snapshot.source,
1072
+ recordedAt: snapshot.recordedAt,
1073
+ isMergeCommit: snapshot.parentCommitIds.length > 1,
1074
+ isEndpoint: snapshot.isEndpoint,
1075
+ isLowerBoundary: snapshot.isLowerBoundary,
1076
+ }));
1077
+ const causes = shallow && resolvedLowerBoundary === null
1078
+ ? [
1079
+ historicalCause("shallow_origin", "repository", {
1080
+ commitId: inspectedCommitIds[0] ?? null,
1081
+ }),
1082
+ ]
1083
+ : [];
1084
+ return historicalEvidenceResult(request, limits, causes.length === 0 ? "complete" : "incomplete", causes, {
1085
+ ...endpointFields,
1086
+ repositoryReadSnapshotId,
1087
+ resolvedLowerBoundary,
1088
+ oldestInspectedCommitId: inspectedCommitIds[0] ?? null,
1089
+ aggregateRawSnapshotBytes,
1090
+ inspectedCommitIds,
1091
+ snapshots,
1092
+ });
1093
+ }
577
1094
  function unavailableAdvanceBaseline(cause, operation, fields = {}) {
578
1095
  return {
579
1096
  ok: true,