sarif-to-comment 0.1.1 → 0.2.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 (217) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +68 -12
  3. package/dist/artifact-files.cjs +273 -0
  4. package/dist/cli.cjs +1062 -0
  5. package/dist/github.cjs +1181 -0
  6. package/dist/index.cjs +39 -0
  7. package/dist/placement.cjs +649 -0
  8. package/dist/prepare-review.cjs +1623 -0
  9. package/dist/publication.cjs +1055 -0
  10. package/dist/publish-sarif-review.cjs +560 -0
  11. package/dist/replacements.cjs +459 -0
  12. package/dist/sarif-authoring.cjs +313 -0
  13. package/dist/sarif-common.cjs +657 -0
  14. package/dist/sarif-inspection.cjs +834 -0
  15. package/dist/sarif-to-comment.cjs +37 -0
  16. package/dist/sarif-to-comment.d.ts +1018 -0
  17. package/dist/staged-changes.cjs +1140 -0
  18. package/dist/staged-git.cjs +391 -0
  19. package/docs/api/index.md +1 -1
  20. package/docs/api/sarif-to-comment.addsarifcomment.md +91 -0
  21. package/docs/api/sarif-to-comment.addsarifcommentoutcome.md +15 -0
  22. package/docs/api/sarif-to-comment.addstagedchangesoutcome.md +15 -0
  23. package/docs/api/sarif-to-comment.addstagedchangestosarif.md +66 -0
  24. package/docs/api/sarif-to-comment.createsarifdocument.md +73 -0
  25. package/docs/api/sarif-to-comment.iaddedfinding.md +123 -0
  26. package/docs/api/sarif-to-comment.iaddedfinding.ref.md +13 -0
  27. package/docs/api/sarif-to-comment.iaddedfinding.resultindex.md +13 -0
  28. package/docs/api/sarif-to-comment.iaddedfinding.runindex.md +13 -0
  29. package/docs/api/sarif-to-comment.iaddedfinding.tool.md +13 -0
  30. package/docs/api/sarif-to-comment.iaddedsarifcommentoutcome.finding.md +13 -0
  31. package/docs/api/sarif-to-comment.iaddedsarifcommentoutcome.md +102 -0
  32. package/docs/api/sarif-to-comment.iaddedsarifcommentoutcome.sarif.md +13 -0
  33. package/docs/api/sarif-to-comment.iaddedsarifcommentoutcome.status.md +13 -0
  34. package/docs/api/sarif-to-comment.iaddedstagedchangesoutcome.md +102 -0
  35. package/docs/api/sarif-to-comment.iaddedstagedchangesoutcome.receipt.md +13 -0
  36. package/docs/api/sarif-to-comment.iaddedstagedchangesoutcome.sarif.md +13 -0
  37. package/docs/api/sarif-to-comment.iaddedstagedchangesoutcome.status.md +13 -0
  38. package/docs/api/sarif-to-comment.iaddstagedchangesinput.md +144 -0
  39. package/docs/api/sarif-to-comment.iaddstagedchangesinput.repository.md +13 -0
  40. package/docs/api/sarif-to-comment.iaddstagedchangesinput.reviewedcommit.md +13 -0
  41. package/docs/api/sarif-to-comment.iaddstagedchangesinput.sarif.md +13 -0
  42. package/docs/api/sarif-to-comment.iaddstagedchangesinput.sourcerooturi.md +13 -0
  43. package/docs/api/sarif-to-comment.iaddstagedchangesinput.worktree.md +13 -0
  44. package/docs/api/sarif-to-comment.icreatesarifdocumentoptions.md +81 -0
  45. package/docs/api/sarif-to-comment.icreatesarifdocumentoptions.source.md +13 -0
  46. package/docs/api/sarif-to-comment.icreatesarifdocumentoptions.tool.md +13 -0
  47. package/docs/api/sarif-to-comment.ifailedstagedchangesoutcome.markdown.md +13 -0
  48. package/docs/api/sarif-to-comment.ifailedstagedchangesoutcome.md +102 -0
  49. package/docs/api/sarif-to-comment.ifailedstagedchangesoutcome.problems.md +13 -0
  50. package/docs/api/sarif-to-comment.ifailedstagedchangesoutcome.status.md +13 -0
  51. package/docs/api/sarif-to-comment.igithubrepository.md +81 -0
  52. package/docs/api/sarif-to-comment.igithubrepository.owner.md +13 -0
  53. package/docs/api/sarif-to-comment.igithubrepository.repo.md +13 -0
  54. package/docs/api/sarif-to-comment.iinspectedoutcome.md +81 -0
  55. package/docs/api/sarif-to-comment.iinspectedoutcome.status.md +13 -0
  56. package/docs/api/sarif-to-comment.iinspectedoutcome.view.md +13 -0
  57. package/docs/api/sarif-to-comment.iinspectionartifactchange.artifactlocation.md +13 -0
  58. package/docs/api/sarif-to-comment.iinspectionartifactchange.md +144 -0
  59. package/docs/api/sarif-to-comment.iinspectionartifactchange.othercontent.md +13 -0
  60. package/docs/api/sarif-to-comment.iinspectionartifactchange.path.md +13 -0
  61. package/docs/api/sarif-to-comment.iinspectionartifactchange.replacements.md +13 -0
  62. package/docs/api/sarif-to-comment.iinspectionartifactchange.uri.md +13 -0
  63. package/docs/api/sarif-to-comment.iinspectiondiagnostic.md +102 -0
  64. package/docs/api/sarif-to-comment.iinspectiondiagnostic.message.md +13 -0
  65. package/docs/api/sarif-to-comment.iinspectiondiagnostic.pointer.md +13 -0
  66. package/docs/api/sarif-to-comment.iinspectiondiagnostic.severity.md +13 -0
  67. package/docs/api/sarif-to-comment.iinspectionexternalproperties.content.md +13 -0
  68. package/docs/api/sarif-to-comment.iinspectionexternalproperties.md +106 -0
  69. package/docs/api/sarif-to-comment.iinspectionexternalproperties.ref.md +13 -0
  70. package/docs/api/sarif-to-comment.iinspectionexternalproperties.results.md +13 -0
  71. package/docs/api/sarif-to-comment.iinspectionfileproposal.artifactindex.md +13 -0
  72. package/docs/api/sarif-to-comment.iinspectionfileproposal.content.md +13 -0
  73. package/docs/api/sarif-to-comment.iinspectionfileproposal.filemode.md +13 -0
  74. package/docs/api/sarif-to-comment.iinspectionfileproposal.md +186 -0
  75. package/docs/api/sarif-to-comment.iinspectionfileproposal.operation.md +13 -0
  76. package/docs/api/sarif-to-comment.iinspectionfileproposal.othercontent.md +13 -0
  77. package/docs/api/sarif-to-comment.iinspectionfileproposal.path.md +13 -0
  78. package/docs/api/sarif-to-comment.iinspectionfileproposal.ref.md +13 -0
  79. package/docs/api/sarif-to-comment.iinspectionfinding.approval.md +13 -0
  80. package/docs/api/sarif-to-comment.iinspectionfinding.baselinestate.md +13 -0
  81. package/docs/api/sarif-to-comment.iinspectionfinding.fileproposals.md +13 -0
  82. package/docs/api/sarif-to-comment.iinspectionfinding.fixes.md +13 -0
  83. package/docs/api/sarif-to-comment.iinspectionfinding.kind.md +13 -0
  84. package/docs/api/sarif-to-comment.iinspectionfinding.level.md +13 -0
  85. package/docs/api/sarif-to-comment.iinspectionfinding.locations.md +13 -0
  86. package/docs/api/sarif-to-comment.iinspectionfinding.md +333 -0
  87. package/docs/api/sarif-to-comment.iinspectionfinding.message.md +13 -0
  88. package/docs/api/sarif-to-comment.iinspectionfinding.othercontent.md +13 -0
  89. package/docs/api/sarif-to-comment.iinspectionfinding.ref.md +13 -0
  90. package/docs/api/sarif-to-comment.iinspectionfinding.relatedlocations.md +13 -0
  91. package/docs/api/sarif-to-comment.iinspectionfinding.resultindex.md +13 -0
  92. package/docs/api/sarif-to-comment.iinspectionfinding.ruleid.md +13 -0
  93. package/docs/api/sarif-to-comment.iinspectionfinding.runindex.md +13 -0
  94. package/docs/api/sarif-to-comment.iinspectionfix.changes.md +13 -0
  95. package/docs/api/sarif-to-comment.iinspectionfix.description.md +13 -0
  96. package/docs/api/sarif-to-comment.iinspectionfix.descriptioncontent.md +13 -0
  97. package/docs/api/sarif-to-comment.iinspectionfix.md +144 -0
  98. package/docs/api/sarif-to-comment.iinspectionfix.othercontent.md +13 -0
  99. package/docs/api/sarif-to-comment.iinspectionfix.ref.md +13 -0
  100. package/docs/api/sarif-to-comment.iinspectionlocation.artifactlocation.md +13 -0
  101. package/docs/api/sarif-to-comment.iinspectionlocation.charlength.md +13 -0
  102. package/docs/api/sarif-to-comment.iinspectionlocation.charoffset.md +13 -0
  103. package/docs/api/sarif-to-comment.iinspectionlocation.endcolumn.md +13 -0
  104. package/docs/api/sarif-to-comment.iinspectionlocation.endline.md +13 -0
  105. package/docs/api/sarif-to-comment.iinspectionlocation.logical.md +13 -0
  106. package/docs/api/sarif-to-comment.iinspectionlocation.md +354 -0
  107. package/docs/api/sarif-to-comment.iinspectionlocation.message.md +13 -0
  108. package/docs/api/sarif-to-comment.iinspectionlocation.messagecontent.md +13 -0
  109. package/docs/api/sarif-to-comment.iinspectionlocation.othercontent.md +13 -0
  110. package/docs/api/sarif-to-comment.iinspectionlocation.path.md +13 -0
  111. package/docs/api/sarif-to-comment.iinspectionlocation.snippet.md +13 -0
  112. package/docs/api/sarif-to-comment.iinspectionlocation.startcolumn.md +13 -0
  113. package/docs/api/sarif-to-comment.iinspectionlocation.startline.md +13 -0
  114. package/docs/api/sarif-to-comment.iinspectionlocation.uri.md +13 -0
  115. package/docs/api/sarif-to-comment.iinspectionlocation.uribaseid.md +13 -0
  116. package/docs/api/sarif-to-comment.iinspectionmessage.arguments.md +13 -0
  117. package/docs/api/sarif-to-comment.iinspectionmessage.id.md +13 -0
  118. package/docs/api/sarif-to-comment.iinspectionmessage.markdown.md +13 -0
  119. package/docs/api/sarif-to-comment.iinspectionmessage.md +165 -0
  120. package/docs/api/sarif-to-comment.iinspectionmessage.othercontent.md +13 -0
  121. package/docs/api/sarif-to-comment.iinspectionmessage.resolved.md +13 -0
  122. package/docs/api/sarif-to-comment.iinspectionmessage.text.md +13 -0
  123. package/docs/api/sarif-to-comment.iinspectionpreview.bytelength.md +13 -0
  124. package/docs/api/sarif-to-comment.iinspectionpreview.md +207 -0
  125. package/docs/api/sarif-to-comment.iinspectionpreview.othercontent.md +13 -0
  126. package/docs/api/sarif-to-comment.iinspectionpreview.shownchars.md +13 -0
  127. package/docs/api/sarif-to-comment.iinspectionpreview.shownlines.md +13 -0
  128. package/docs/api/sarif-to-comment.iinspectionpreview.state.md +13 -0
  129. package/docs/api/sarif-to-comment.iinspectionpreview.text.md +13 -0
  130. package/docs/api/sarif-to-comment.iinspectionpreview.totalchars.md +13 -0
  131. package/docs/api/sarif-to-comment.iinspectionpreview.totallines.md +13 -0
  132. package/docs/api/sarif-to-comment.iinspectionreplacement.deletedregion.md +13 -0
  133. package/docs/api/sarif-to-comment.iinspectionreplacement.inserted.md +13 -0
  134. package/docs/api/sarif-to-comment.iinspectionreplacement.md +102 -0
  135. package/docs/api/sarif-to-comment.iinspectionreplacement.othercontent.md +13 -0
  136. package/docs/api/sarif-to-comment.iinspectionrun.approval.md +13 -0
  137. package/docs/api/sarif-to-comment.iinspectionrun.columnkind.md +13 -0
  138. package/docs/api/sarif-to-comment.iinspectionrun.index.md +13 -0
  139. package/docs/api/sarif-to-comment.iinspectionrun.md +186 -0
  140. package/docs/api/sarif-to-comment.iinspectionrun.othercontent.md +13 -0
  141. package/docs/api/sarif-to-comment.iinspectionrun.ref.md +13 -0
  142. package/docs/api/sarif-to-comment.iinspectionrun.source.md +16 -0
  143. package/docs/api/sarif-to-comment.iinspectionrun.tool.md +16 -0
  144. package/docs/api/sarif-to-comment.iinspectsarifoptions.md +102 -0
  145. package/docs/api/sarif-to-comment.iinspectsarifoptions.previewchars.md +13 -0
  146. package/docs/api/sarif-to-comment.iinspectsarifoptions.previewlines.md +13 -0
  147. package/docs/api/sarif-to-comment.iinspectsarifoptions.sourcerooturi.md +13 -0
  148. package/docs/api/sarif-to-comment.iinvalidsarifoutcome.markdown.md +13 -0
  149. package/docs/api/sarif-to-comment.iinvalidsarifoutcome.md +102 -0
  150. package/docs/api/sarif-to-comment.iinvalidsarifoutcome.problems.md +13 -0
  151. package/docs/api/sarif-to-comment.iinvalidsarifoutcome.status.md +13 -0
  152. package/docs/api/sarif-to-comment.inewsarifrun.md +102 -0
  153. package/docs/api/sarif-to-comment.inewsarifrun.source.md +13 -0
  154. package/docs/api/sarif-to-comment.inewsarifrun.toolname.md +13 -0
  155. package/docs/api/sarif-to-comment.inewsarifrun.toolversion.md +13 -0
  156. package/docs/api/sarif-to-comment.inspectsarif.md +80 -0
  157. package/docs/api/sarif-to-comment.inspectsarifoutcome.md +15 -0
  158. package/docs/api/sarif-to-comment.iproblem.md +102 -0
  159. package/docs/api/sarif-to-comment.iproblem.message.md +13 -0
  160. package/docs/api/sarif-to-comment.iproblem.path.md +13 -0
  161. package/docs/api/sarif-to-comment.iproblem.pointer.md +13 -0
  162. package/docs/api/sarif-to-comment.isarifcomment.endline.md +13 -0
  163. package/docs/api/sarif-to-comment.isarifcomment.file.md +13 -0
  164. package/docs/api/sarif-to-comment.isarifcomment.level.md +13 -0
  165. package/docs/api/sarif-to-comment.isarifcomment.line.md +13 -0
  166. package/docs/api/sarif-to-comment.isarifcomment.md +207 -0
  167. package/docs/api/sarif-to-comment.isarifcomment.message.md +13 -0
  168. package/docs/api/sarif-to-comment.isarifcomment.messageformat.md +13 -0
  169. package/docs/api/sarif-to-comment.isarifcomment.ruleid.md +13 -0
  170. package/docs/api/sarif-to-comment.isarifcomment.run.md +13 -0
  171. package/docs/api/sarif-to-comment.isarifinspection.diagnostics.md +13 -0
  172. package/docs/api/sarif-to-comment.isarifinspection.externalproperties.md +13 -0
  173. package/docs/api/sarif-to-comment.isarifinspection.findings.md +13 -0
  174. package/docs/api/sarif-to-comment.isarifinspection.format.md +13 -0
  175. package/docs/api/sarif-to-comment.isarifinspection.log.md +15 -0
  176. package/docs/api/sarif-to-comment.isarifinspection.md +207 -0
  177. package/docs/api/sarif-to-comment.isarifinspection.runs.md +13 -0
  178. package/docs/api/sarif-to-comment.isarifinspection.summary.md +20 -0
  179. package/docs/api/sarif-to-comment.isarifinspection.version.md +13 -0
  180. package/docs/api/sarif-to-comment.isariflog._schema.md +13 -0
  181. package/docs/api/sarif-to-comment.isariflog.md +100 -0
  182. package/docs/api/sarif-to-comment.isariflog.runs.md +13 -0
  183. package/docs/api/sarif-to-comment.isariflog.version.md +13 -0
  184. package/docs/api/sarif-to-comment.isarifsourcebinding.commit.md +13 -0
  185. package/docs/api/sarif-to-comment.isarifsourcebinding.md +61 -0
  186. package/docs/api/sarif-to-comment.isariftoolidentity.md +81 -0
  187. package/docs/api/sarif-to-comment.isariftoolidentity.name.md +13 -0
  188. package/docs/api/sarif-to-comment.isariftoolidentity.version.md +13 -0
  189. package/docs/api/sarif-to-comment.istagedchangereceipt.associated.md +13 -0
  190. package/docs/api/sarif-to-comment.istagedchangereceipt.explainedby.md +13 -0
  191. package/docs/api/sarif-to-comment.istagedchangereceipt.md +144 -0
  192. package/docs/api/sarif-to-comment.istagedchangereceipt.operation.md +13 -0
  193. package/docs/api/sarif-to-comment.istagedchangereceipt.path.md +13 -0
  194. package/docs/api/sarif-to-comment.istagedchangereceipt.replacements.md +13 -0
  195. package/docs/api/sarif-to-comment.istagedchangesreceipt.addedrun.md +13 -0
  196. package/docs/api/sarif-to-comment.istagedchangesreceipt.boundruns.md +13 -0
  197. package/docs/api/sarif-to-comment.istagedchangesreceipt.changes.md +13 -0
  198. package/docs/api/sarif-to-comment.istagedchangesreceipt.md +144 -0
  199. package/docs/api/sarif-to-comment.istagedchangesreceipt.reviewedcommit.md +13 -0
  200. package/docs/api/sarif-to-comment.istagedchangesreceipt.warnings.md +13 -0
  201. package/docs/api/sarif-to-comment.istagedreplacementreceipt.associated.md +13 -0
  202. package/docs/api/sarif-to-comment.istagedreplacementreceipt.endline.md +13 -0
  203. package/docs/api/sarif-to-comment.istagedreplacementreceipt.explainedby.md +13 -0
  204. package/docs/api/sarif-to-comment.istagedreplacementreceipt.insertion.md +13 -0
  205. package/docs/api/sarif-to-comment.istagedreplacementreceipt.md +148 -0
  206. package/docs/api/sarif-to-comment.istagedreplacementreceipt.startline.md +13 -0
  207. package/docs/api/sarif-to-comment.md +432 -2
  208. package/docs/getting-started.md +126 -3
  209. package/package.json +31 -18
  210. package/bin/sarif-to-comment.cjs +0 -275
  211. package/src/github.cjs +0 -1092
  212. package/src/index.cjs +0 -514
  213. package/src/placement.cjs +0 -586
  214. package/src/prepare-review.cjs +0 -1547
  215. package/src/publication.cjs +0 -994
  216. package/src/replacements.cjs +0 -463
  217. package/types/index.d.ts +0 -217
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # sarif-to-comment
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 6d868ab: The implementation is now strict TypeScript, and the shipped TypeScript declarations are generated from it rather than written by hand.
8
+
9
+ - No change to the public API, the CLI or behavior. The declarations describe the same functions and types as before.
10
+ - The packaged runtime files moved from `src/` and `bin/` to `dist/`. The package entry points are unchanged: `require('sarif-to-comment')`, `import … from 'sarif-to-comment'`, its TypeScript types and the `sarif-to-comment` command resolve through `package.json` as before. Only code that reached into the package's internal file paths is affected.
11
+
12
+ ## 0.2.0
13
+
14
+ ### Minor Changes
15
+
16
+ - Write, inspect and extend SARIF without an analyzer, and turn staged Git changes into suggested fixes.
17
+
18
+ - New library functions `createSarifDocument`, `addSarifComment`, `inspectSarif` and `addStagedChangesToSarif`, with TypeScript declarations. They work on ordinary in-memory SARIF, never change their input, and accept SARIF from any producer.
19
+ - New CLI commands `init`, `add-comment`, `inspect`, `add-staged-changes` and `publish`.
20
+ - `--format human|json` on every command: JSON mode prints exactly one document for every outcome, errors included.
21
+ - `add-staged-changes` reads only the Git index, never unstaged working-tree content. It attaches a change to a finding only when the finding's lines lie within the change, and fails with an explanation for changes it cannot represent. An existing output file is preserved under a timestamped `.old.` name.
22
+ - Publishing is unchanged. The original flag-only command keeps its exact behavior, output and exit statuses.
23
+
3
24
  ## 0.1.1
4
25
 
5
26
  ### Patch Changes
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # sarif-to-comment
2
2
 
3
- Publish a ready [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html) document as **one GitHub draft pull request review**:
3
+ Write, inspect and publish [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/sarif-v2.1.0-errata01-os-complete.html) findings as **one GitHub draft pull request review**:
4
4
 
5
5
  - general feedback in the review body;
6
6
  - findings on changed lines as inline comments;
@@ -8,6 +8,13 @@ Publish a ready [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/err
8
8
 
9
9
  It is sent in a single create-review request.
10
10
 
11
+ The SARIF can come from any analyzer. Or you can write it yourself, with no analyzer:
12
+ - create a document and add findings on lines or line ranges;
13
+ - add the changes you staged with Git as suggested fixes on those findings;
14
+ - inspect the result before publishing.
15
+
16
+ Every step reads and writes ordinary SARIF and is optional. SARIF from an analyzer can be inspected, given staged changes or published directly, without any setup step.
17
+
11
18
  The scope is deliberately narrow:
12
19
 
13
20
  - **One-way.** SARIF goes to GitHub once. The tool never updates, reconciles, submits, restores or deletes a review afterwards. Supplying a new SARIF document (with a new state path) creates a separate review.
@@ -15,13 +22,16 @@ The scope is deliberately narrow:
15
22
  - **Whole review or nothing.** If any finding can't be published faithfully, nothing is published, and the tool explains why.
16
23
  - **Never duplicated.** A durable state file makes retries confirm the existing review instead of creating another.
17
24
 
18
- Requires Node.js 22 or later. There are two surfaces, the **library** (SARIF in memory) and the **CLI** (a SARIF file); both run the same code.
25
+ Requires Node.js 22 or later. There are two surfaces, the **library** (SARIF in memory) and the **CLI** (SARIF files); both run the same code.
19
26
 
20
27
  ## Documentation
21
28
 
22
29
  These documents are included in the package. The links open them on unpkg (for the latest published version) and need no access to the source repository.
23
30
 
24
- - **[Getting started](https://unpkg.com/sarif-to-comment/docs/getting-started.md):** credentials, the reviewed commit, source root and state path, a complete library example and a complete CLI example, and how to handle every outcome.
31
+ - **[Getting started](https://unpkg.com/sarif-to-comment/docs/getting-started.md):**
32
+ - credentials, the reviewed commit, the source root and the state path;
33
+ - complete library and CLI examples for publishing an analyzer's SARIF, and for writing a review yourself from staged changes;
34
+ - how to handle every outcome.
25
35
  - **[API reference](https://unpkg.com/sarif-to-comment/docs/api/index.md):** generated by API Documenter from the package's TypeScript declarations.
26
36
  - **[Changelog](https://unpkg.com/sarif-to-comment/CHANGELOG.md):** release notes produced by Changesets.
27
37
 
@@ -91,7 +101,8 @@ GH_TOKEN=... npx sarif-to-comment \
91
101
  --state /var/lib/my-linter/reviews/acme-widgets-42-run-1817.json
92
102
  ```
93
103
 
94
- - **Optional flags:** `--source-root FILE_URI`, `--old-source-commit FULLSHA` and `--ignore-approval-hold`. Run `sarif-to-comment --help` for details; it needs no token and makes no request.
104
+ - **Optional flags:** `--source-root FILE_URI`, `--old-source-commit FULLSHA`, `--ignore-approval-hold` and `--format human|json`. Run `sarif-to-comment --help` for details; it needs no token and makes no request.
105
+ - **Command form:** `sarif-to-comment publish` followed by the same flags does exactly the same thing. With `--format json` either form prints one JSON document (`status`, `review`, `statePath` and the Markdown `message`), with the same exit status.
95
106
  - **Same core as the library:** the CLI reads the file, calls the same `publishSarifReview`, and prints the same Markdown to stdout.
96
107
  - **File encoding:** the SARIF file must be UTF-8 JSON.
97
108
  - A leading UTF-8 byte-order mark is ignored, so the CLI and a library caller passing the same parsed document produce the same publication.
@@ -104,6 +115,35 @@ GH_TOKEN=... npx sarif-to-comment \
104
115
  | 3 | uncertain: retry with the same `--state` |
105
116
  | 1 | usage error, unreadable or unparsable SARIF file, refused request, or operational failure (details on stderr) |
106
117
 
118
+ ## Quick start: write a review yourself
119
+
120
+ ```sh
121
+ # In the repository, with your proposed change staged (git add); unstaged edits are ignored.
122
+ npx sarif-to-comment init --output review.sarif --tool-name "Review agent"
123
+ npx sarif-to-comment add-comment --sarif review.sarif --file src/parse.js --line 2 \
124
+ --message "Handle the empty-input case."
125
+ npx sarif-to-comment add-staged-changes --sarif review.sarif --output review.staged.sarif \
126
+ --worktree . --repo acme/widgets --commit c0dec0dec0dec0dec0dec0dec0dec0dec0dec0de
127
+ npx sarif-to-comment inspect --sarif review.staged.sarif
128
+ GH_TOKEN=... npx sarif-to-comment publish --sarif review.staged.sarif --repo acme/widgets --pull 42 \
129
+ --commit c0dec0dec0dec0dec0dec0dec0dec0dec0dec0de --state /var/lib/reviews/acme-widgets-42.json
130
+ ```
131
+
132
+ The library has the same operations for in-memory SARIF: `createSarifDocument`, `addSarifComment`, `inspectSarif`, `addStagedChangesToSarif` and `publishSarifReview`. Each returns a new document or a view and never changes its input.
133
+
134
+ - **Line numbers** refer to the reviewed commit. For a file the reviewed commit doesn't have, they refer to its staged content.
135
+ - **Associating changes with findings.** Only the staged index is read. A staged change becomes a fix on a finding only when the finding's lines lie within the lines the change replaces; neither is enlarged.
136
+ - A finding that only partly overlaps a change stays a comment. The receipt says so.
137
+ - A change that no finding explains is kept as a short factual finding attributed to `sarif-to-comment`. A change that only adds lines replaces no reviewed line, so it is never credited to a nearby finding; the receipt marks it `insertion: true`.
138
+ - Findings that already have fixes are never changed. If your staged change differs from such a fix on the same lines, the command fails rather than choose between them.
139
+ - **Files.** `init` refuses to overwrite an existing file. `add-comment` updates its SARIF file in place, atomically. `add-staged-changes` writes a separate output; if that output file already exists, it's first renamed to `<UTC time>.old.<name>`, and a failed run writes no output.
140
+ - **Output.** `inspect` shows every finding in full, with its locations and fixes, plus log-level and inline external properties verbatim. Only fix previews are shortened, visibly. It doesn't check whether the file can be published.
141
+ - `--format json` on any command prints one JSON document for every outcome, errors included. Exit statuses are the same in both formats.
142
+ - Exit statuses: 0 success; 2 content refused (not valid SARIF, or a staged change that can't be represented); 1 usage or operational error. `publish` keeps the exit statuses below.
143
+ - **Changes that can't be represented.** Staged edits to UTF-8 text files become fixes. Publication still checks native-suggestion compatibility: empty reviewed files, or files containing only a byte-order mark, have no source line for an inline suggestion and are blocked. File creations and deletions become proposed file operations: `inspect` shows them, but `publish` doesn't support them yet and blocks the review. `add-staged-changes` fails, naming the path, for mode changes, symbolic links, submodules, binary or non-UTF-8 files, conflicts and intent-to-add entries.
144
+
145
+ See the [getting-started guide](https://unpkg.com/sarif-to-comment/docs/getting-started.md#write-a-review-yourself) for complete, tested examples of both surfaces and of SARIF from an analyzer.
146
+
107
147
  ## Credentials
108
148
 
109
149
  Use a GitHub **personal access token or user token**. It needs permission to read the repository and to create pull request reviews; for a fine-grained token that is *Contents: Read* and *Pull requests: Read and write*. The CLI reads `GH_TOKEN`, or else `GITHUB_TOKEN`; there is no token flag.
@@ -181,19 +221,35 @@ If that comparison can't establish the old side, you may pass `oldSourceCommit`
181
221
  - The durability steps (write, flush, then send) are ordered for crash safety, but that has not been tested against power loss.
182
222
  - GitHub Enterprise Server and GitHub App installation tokens are not supported.
183
223
  - There is no review maintenance, re-review or synchronisation back to SARIF.
224
+ - Authoring can't yet remove or correct a finding in place, and there's no standalone check that a document can be published; `publish` performs every check. Staged file creations and deletions can be recorded in SARIF, but can't be published yet.
184
225
  - npm attaches a provenance attestation only when the source repository is public at publish time. A release published while the repository is private has no provenance attestation (see [Releasing](#releasing)).
185
226
 
186
227
  ## Development
187
228
 
188
229
  ```sh
189
230
  pnpm install
190
- pnpm test # node --test test/*.test.cjs
231
+ pnpm run build # rebuild dist/ from src/, roll up the declarations, regenerate api-report/ and docs/api/
191
232
  pnpm run check # lint, types, API report/docs freshness, release plan, tests (read-only)
192
- pnpm run build # regenerate api-report/ and docs/api/ after changing types/index.d.ts
233
+ pnpm test # refuse a missing or stale dist/, then node --test "test/**/*.test.mts"
193
234
  pnpm changeset # describe a change for the next release
194
235
  ```
195
236
 
196
- The public TypeScript declarations are written by hand in `types/index.d.ts` and must match the CommonJS runtime in `src/index.cjs`. API Extractor checks them and writes a reviewable API report (`api-report/`) and a doc model. API Documenter renders the doc model as the Markdown reference in `docs/api/`. `pnpm run check` fails when either is out of date.
237
+ Development and release tooling need Node 22.18.0 or later, enforced by `devEngines` in `package.json`: the tests and the build, check and release scripts are TypeScript that Node runs directly by type stripping. The published package still needs only Node 22 or later (`engines`).
238
+
239
+ Run `pnpm run build` before `pnpm run check` or `pnpm test`, and again after changing sources or build configuration. The tests exercise, and the package ships, the built `dist/`. `pnpm test` (and therefore `pnpm run check`) refuses a `dist/` that is missing, incomplete, or was built from different sources or configuration; it compares content hashes of every build input and output, not modification times.
240
+
241
+ The implementation is strict TypeScript, and there are no hand-written declarations. The public TypeScript declarations are generated from the implementation. `src/public-api.cts` declares the public API, and the CommonJS runtime entry `src/index.cts` is checked at compile time to export exactly its functions. `tsc` emits per-module declarations, API Extractor rolls them up into the shipped `dist/sarif-to-comment.d.ts` and writes a reviewable API report (`api-report/`) and a doc model, and API Documenter renders the doc model as the Markdown reference in `docs/api/`. `pnpm run check` fails when either is out of date.
242
+
243
+ `pnpm run build` rewrites `api-report/` and `docs/api/` whenever the public API or its TSDoc changes; commit them with the change. CI and the publish workflow build and then fail if either differs from the committed copy.
244
+
245
+ **Repository layout.**
246
+ - `src/*.cts`: the implementation. `tsc` compiles each module to CommonJS in `dist/*.cjs`; `src/index.cts` is the package entry and `src/sarif-to-comment.cts` the CLI executable.
247
+ - `dist/`: build output. It is not committed; the package ships its runtime modules and the rolled-up declarations.
248
+ - `test/`: `node:test` suites (`*.test.mts`), fixtures and helpers, run against `dist/`.
249
+ - `scripts/*.mts`: build, type-check, API documentation and release-guard tooling.
250
+ - `api-report/` and `docs/api/`: generated by `pnpm run build` and committed, so API changes are reviewed.
251
+ - `vendor/`: the official SARIF 2.1.0 schema used for validation.
252
+ - `.changeset/`: pending release notes.
197
253
 
198
254
  ## Releasing
199
255
 
@@ -201,16 +257,16 @@ Releases use [Changesets](https://changesets.dev/) for versioning and [npm trust
201
257
 
202
258
  ### Versions stay below 1.0.0
203
259
 
204
- `MAXIMUM_RELEASE_MAJOR` in `scripts/release-guard.cjs` is `0`. Until someone deliberately raises it in a reviewed change, nothing can version or publish `1.0.0` or higher, including prereleases such as `1.0.0-rc.0`:
260
+ `MAXIMUM_RELEASE_MAJOR` in `scripts/release-guard.mts` is `0`. Until someone deliberately raises it in a reviewed change, nothing can version or publish `1.0.0` or higher, including prereleases such as `1.0.0-rc.0`:
205
261
 
206
262
  - **`pnpm run check`** (run in CI on every pull request) fails if a pending changeset would reach 1.0.0. A `major` changeset is refused with an explanation. It is never quietly converted to a smaller bump; choose `minor` yourself if the change shouldn't start 1.0.
207
263
  - **`pnpm run release:version`** refuses the same plans before `changeset version` changes any file.
208
264
  - **What counts as the plan.** The guard doesn't parse changeset files itself. It runs the real `changeset version` in a throwaway copy and judges the version and changelog entry Changesets produces, so any front matter Changesets accepts is judged by its actual effect. That includes quoted values such as `"sarif-to-comment": "major"`. A changeset Changesets can't read is refused, not ignored.
209
- - **The publish workflow** refuses any `package.json` version of 1.0.0 or higher, and so does the `prepublishOnly` backstop for a manual publish.
265
+ - **The publish workflow** refuses any `package.json` version of 1.0.0 or higher, and so does the `prepublishOnly` backstop for a manual publish, which also refuses a missing or stale `dist/`.
210
266
 
211
267
  Changesets pre mode (prereleases) is not part of this release path.
212
268
 
213
- **Deliberately releasing 1.0.** In a reviewed change, raise `MAXIMUM_RELEASE_MAJOR` to `1` and update the test in `test/release.test.cjs` that pins its value. That is the only step: a `major` changeset then versions and publishes `1.0.0` through the normal flow above, while `2.0.0` and above stay blocked.
269
+ **Deliberately releasing 1.0.** In a reviewed change, raise `MAXIMUM_RELEASE_MAJOR` to `1` and update the test in `test/release.test.mts` that pins its value. That is the only step: a `major` changeset then versions and publishes `1.0.0` through the normal flow above, while `2.0.0` and above stay blocked.
214
270
 
215
271
  ### Making a release
216
272
 
@@ -229,8 +285,8 @@ Changesets pre mode (prereleases) is not part of this release path.
229
285
  - pre mode is off;
230
286
  - the package metadata is publishable, with the exact repository URL;
231
287
  - the commit is on `main`;
232
- - npm is at least 11.5.1 and Node at least 22.14.0.
233
- - **When allowed:** it runs `pnpm run check`, packs the tarball, verifies it contains exactly the distribution files, and publishes that tarball.
288
+ - npm is at least 11.5.1 and Node at least 22.18.0.
289
+ - **When allowed:** it builds `dist/`, runs `pnpm run check`, packs the tarball, verifies it contains exactly the distribution files, and publishes that tarball.
234
290
 
235
291
  Publishes never overlap, and a publish in progress is never cancelled.
236
292
  4. **If publishing fails**, what to do depends on where the cause is. A version npm has already accepted can never be republished.
@@ -0,0 +1,273 @@
1
+ "use strict";
2
+ /**
3
+ * File handling for the CLI's local SARIF artifacts. The library never touches
4
+ * files; the command-line interface uses this module so that every receipt it
5
+ * prints describes what actually happened on disk.
6
+ *
7
+ * Responsibilities and guarantees:
8
+ * - Reading: SARIF and message files are strict UTF-8 (a leading byte-order
9
+ * mark is an encoding signature and is removed); undecodable bytes are
10
+ * refused rather than replaced, so nothing is silently altered.
11
+ * - Exclusive creation (`createExclusive`): the content is written and
12
+ * flushed to a temporary sibling, then hard-linked to the destination,
13
+ * which fails if the destination exists. A file therefore appears whole or
14
+ * not at all, and an output that reappeared concurrently is never
15
+ * overwritten.
16
+ * - Ownership (`acquireOwnership`): cooperating sarif-to-comment commands
17
+ * editing or producing the same artifact exclude each other with an
18
+ * exclusively created marker file, `.<name>.sarif-to-comment-lock`, beside
19
+ * the artifact. A marker that already exists is reported, never taken over:
20
+ * only a person can know that its owner is gone. The owner removes it when
21
+ * it finishes, whatever the outcome.
22
+ * - In-place replacement (`replaceIfUnchanged`): the new content is written
23
+ * to a temporary sibling with the original mode; immediately before the
24
+ * atomic rename the file is re-read and compared with the bytes the edit
25
+ * was computed from, and an observed external change is refused. This is
26
+ * not a filesystem compare-and-swap: a non-cooperating writer acting
27
+ * between that check and the rename is outside the guarantee.
28
+ * - Output preservation (`archiveExisting`): an existing output is moved
29
+ * aside as `<YYYY-MM-DDTHH-mm-ss.SSSZ>.old.<name>` in its directory, the
30
+ * stamp being the file's birth time in UTC (or its modification time when
31
+ * the platform reports no birth time), with `-2`, `-3`, … appended to the
32
+ * stamp on collision. The archive is an exclusive hard link followed by
33
+ * removal of the original name, so it never overwrites anything.
34
+ *
35
+ * Failures a user must act on are `ArtifactError`s with actionable messages
36
+ * that name the paths involved.
37
+ */
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.ArtifactError = void 0;
40
+ exports.readTextFile = readTextFile;
41
+ exports.readJsonFile = readJsonFile;
42
+ exports.createExclusive = createExclusive;
43
+ exports.ownershipMarkerFor = ownershipMarkerFor;
44
+ exports.acquireOwnership = acquireOwnership;
45
+ exports.replaceIfUnchanged = replaceIfUnchanged;
46
+ exports.archiveExisting = archiveExisting;
47
+ exports.sameExistingFile = sameExistingFile;
48
+ const crypto = require("node:crypto");
49
+ const fs = require("node:fs");
50
+ const path = require("node:path");
51
+ /** A file problem the user must resolve; the message names the paths. */
52
+ class ArtifactError extends Error {
53
+ }
54
+ exports.ArtifactError = ArtifactError;
55
+ /**
56
+ * A caught failure's `message`, read as a template would read `err.message`.
57
+ * Node's file-system calls throw Errors (SystemError), so this is their
58
+ * message; a value without one reads as "undefined".
59
+ */
60
+ function messageOf(err) {
61
+ return String(typeof err === 'object' && err !== null && 'message' in err ? err.message : undefined);
62
+ }
63
+ /** A caught failure's Node error `code` (such as `EEXIST`), or undefined when it has none. */
64
+ function codeOf(err) {
65
+ return typeof err === 'object' && err !== null && 'code' in err ? err.code : undefined;
66
+ }
67
+ /**
68
+ * Decoder for UTF-8 text files: `fatal` refuses invalid bytes instead of
69
+ * substituting U+FFFD; the default `ignoreBOM: false` removes a leading BOM.
70
+ */
71
+ const UTF8 = new TextDecoder('utf-8', { fatal: true, ignoreBOM: false });
72
+ /** Reads a UTF-8 text file; `label` names it in errors (e.g. "message file"). */
73
+ function readTextFile(file, label) {
74
+ let bytes;
75
+ try {
76
+ bytes = fs.readFileSync(file);
77
+ }
78
+ catch (err) {
79
+ throw new ArtifactError(`cannot read ${label} ${file}: ${messageOf(err)}`);
80
+ }
81
+ try {
82
+ return { text: UTF8.decode(bytes), bytes };
83
+ }
84
+ catch {
85
+ throw new ArtifactError(`${label} ${file} is not valid UTF-8; it must be UTF-8 encoded.`);
86
+ }
87
+ }
88
+ /** Reads a UTF-8 JSON file: { value, bytes } (the bytes as read, for change detection). */
89
+ function readJsonFile(file, label) {
90
+ const { text, bytes } = readTextFile(file, label);
91
+ try {
92
+ const value = JSON.parse(text);
93
+ return { value, bytes };
94
+ }
95
+ catch (err) {
96
+ throw new ArtifactError(`${label} ${file} is not valid JSON: ${messageOf(err)}`);
97
+ }
98
+ }
99
+ /** Flushes a directory entry change (best effort where directories cannot be opened). */
100
+ function syncDirectory(dir) {
101
+ let fd;
102
+ try {
103
+ fd = fs.openSync(dir, 'r');
104
+ fs.fsyncSync(fd);
105
+ }
106
+ catch {
107
+ // Some platforms do not allow fsync on a directory; the rename/link itself is still atomic.
108
+ }
109
+ finally {
110
+ if (fd !== undefined)
111
+ fs.closeSync(fd);
112
+ }
113
+ }
114
+ /** Writes and flushes `text` to a new uniquely named temporary sibling of `file`. */
115
+ function writeTemporarySibling(file, text, mode) {
116
+ const temporary = path.join(path.dirname(file), `.${path.basename(file)}.${String(process.pid)}.${crypto.randomUUID()}.tmp`);
117
+ const fd = fs.openSync(temporary, 'wx', mode ?? 0o666);
118
+ try {
119
+ fs.writeFileSync(fd, text);
120
+ if (mode !== undefined)
121
+ fs.fchmodSync(fd, mode);
122
+ fs.fsyncSync(fd);
123
+ }
124
+ finally {
125
+ fs.closeSync(fd);
126
+ }
127
+ return temporary;
128
+ }
129
+ /** Removes a temporary file if it is still present. */
130
+ function discard(temporary) {
131
+ try {
132
+ fs.unlinkSync(temporary);
133
+ }
134
+ catch {
135
+ // Already gone.
136
+ }
137
+ }
138
+ /**
139
+ * Creates `file` with `text` only if it does not exist. `hooks.beforeLink`
140
+ * (tests only) runs after the temporary file is complete.
141
+ */
142
+ function createExclusive(file, text, hooks = {}) {
143
+ const temporary = writeTemporarySibling(file, text);
144
+ try {
145
+ if (hooks.beforeLink)
146
+ hooks.beforeLink();
147
+ try {
148
+ fs.linkSync(temporary, file);
149
+ }
150
+ catch (err) {
151
+ if (codeOf(err) === 'EEXIST') {
152
+ throw new ArtifactError(`${file} already exists (it may have been created by another program); it was left unchanged.`);
153
+ }
154
+ throw new ArtifactError(`cannot create ${file}: ${messageOf(err)}`);
155
+ }
156
+ }
157
+ finally {
158
+ discard(temporary);
159
+ }
160
+ syncDirectory(path.dirname(file));
161
+ }
162
+ /** The ownership marker for an artifact: `.<name>.sarif-to-comment-lock` in its directory. */
163
+ function ownershipMarkerFor(file) {
164
+ return path.join(path.dirname(file), `.${path.basename(file)}.sarif-to-comment-lock`);
165
+ }
166
+ /**
167
+ * Takes exclusive cooperating-writer ownership of `file` (which need not
168
+ * exist). Returns a release function. Throws ArtifactError if another owner
169
+ * holds it.
170
+ */
171
+ function acquireOwnership(file) {
172
+ const marker = ownershipMarkerFor(file);
173
+ const token = `${String(process.pid)} ${new Date().toISOString()} ${crypto.randomUUID()}\n`;
174
+ try {
175
+ fs.writeFileSync(marker, token, { flag: 'wx' });
176
+ }
177
+ catch (err) {
178
+ if (codeOf(err) === 'EEXIST') {
179
+ throw new ArtifactError(`${file} is being written by another sarif-to-comment command (ownership marker ${marker}). ` +
180
+ `Wait for it to finish. If no such command is running, the marker is stale: delete ${marker} and run again.`);
181
+ }
182
+ throw new ArtifactError(`cannot take ownership of ${file} (marker ${marker}): ${messageOf(err)}`);
183
+ }
184
+ return function release() {
185
+ // Remove only our own marker: never delete one another process created.
186
+ try {
187
+ if (fs.readFileSync(marker, 'utf8') === token)
188
+ fs.unlinkSync(marker);
189
+ }
190
+ catch {
191
+ // Already removed.
192
+ }
193
+ };
194
+ }
195
+ /**
196
+ * Atomically replaces `file` with `text`, provided its content still equals
197
+ * `expectedBytes` immediately before the rename. `hooks.beforeReplace` (tests
198
+ * only) runs after the temporary file is complete.
199
+ */
200
+ function replaceIfUnchanged(file, expectedBytes, text, hooks = {}) {
201
+ const { mode } = fs.statSync(file);
202
+ const temporary = writeTemporarySibling(file, text, mode & 0o7777);
203
+ try {
204
+ if (hooks.beforeReplace)
205
+ hooks.beforeReplace();
206
+ let current;
207
+ try {
208
+ current = fs.readFileSync(file);
209
+ }
210
+ catch (err) {
211
+ throw new ArtifactError(`${file} could not be re-read before replacement (${messageOf(err)}); it was not changed.`);
212
+ }
213
+ if (!current.equals(expectedBytes)) {
214
+ throw new ArtifactError(`${file} changed while this command was running; it was not overwritten. Run the command again.`);
215
+ }
216
+ fs.renameSync(temporary, file);
217
+ }
218
+ finally {
219
+ discard(temporary);
220
+ }
221
+ syncDirectory(path.dirname(file));
222
+ }
223
+ /** A stat time as the archive stamp, `YYYY-MM-DDTHH-mm-ss.SSSZ` (colons are not portable in names). */
224
+ function archiveStamp(milliseconds) {
225
+ return new Date(milliseconds).toISOString().replace(/:/g, '-');
226
+ }
227
+ /**
228
+ * Moves an existing `file` aside under its archive name. Returns
229
+ * { path, from, timeSource: 'birth' | 'modified' }, or null if `file` does not
230
+ * exist. `options.stat` (tests only) replaces fs.statSync.
231
+ */
232
+ function archiveExisting(file, { stat = fs.statSync } = {}) {
233
+ let info;
234
+ try {
235
+ info = stat(file);
236
+ }
237
+ catch (err) {
238
+ if (codeOf(err) === 'ENOENT')
239
+ return null;
240
+ throw new ArtifactError(`cannot examine existing output ${file}: ${messageOf(err)}`);
241
+ }
242
+ const birth = Number.isFinite(info.birthtimeMs) && info.birthtimeMs > 0;
243
+ const stamp = archiveStamp(birth ? info.birthtimeMs : info.mtimeMs);
244
+ const dir = path.dirname(file);
245
+ const name = path.basename(file);
246
+ for (let n = 1;; n += 1) {
247
+ const archive = path.join(dir, `${stamp}${n === 1 ? '' : `-${String(n)}`}.old.${name}`);
248
+ try {
249
+ fs.linkSync(file, archive);
250
+ }
251
+ catch (err) {
252
+ if (codeOf(err) === 'EEXIST')
253
+ continue;
254
+ throw new ArtifactError(`cannot archive existing output ${file} as ${archive}: ${messageOf(err)}`);
255
+ }
256
+ fs.unlinkSync(file);
257
+ syncDirectory(dir);
258
+ return { path: archive, from: file, timeSource: birth ? 'birth' : 'modified' };
259
+ }
260
+ }
261
+ /** True when two existing paths name the same file (including symbolic and hard links). */
262
+ function sameExistingFile(a, b) {
263
+ let sa;
264
+ let sb;
265
+ try {
266
+ sa = fs.statSync(a);
267
+ sb = fs.statSync(b);
268
+ }
269
+ catch {
270
+ return false;
271
+ }
272
+ return sa.dev === sb.dev && sa.ino === sb.ino;
273
+ }