@nanocollective/sentinel 0.1.0-alpha.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 (163) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +66 -0
  3. package/dist/cli.d.ts +12 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +254 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/config/parse.d.ts +13 -0
  8. package/dist/config/parse.d.ts.map +1 -0
  9. package/dist/config/parse.js +271 -0
  10. package/dist/config/parse.js.map +1 -0
  11. package/dist/config/repo-override.d.ts +13 -0
  12. package/dist/config/repo-override.d.ts.map +1 -0
  13. package/dist/config/repo-override.js +128 -0
  14. package/dist/config/repo-override.js.map +1 -0
  15. package/dist/config/types.d.ts +86 -0
  16. package/dist/config/types.d.ts.map +1 -0
  17. package/dist/config/types.js +8 -0
  18. package/dist/config/types.js.map +1 -0
  19. package/dist/dedup/hash.d.ts +10 -0
  20. package/dist/dedup/hash.d.ts.map +1 -0
  21. package/dist/dedup/hash.js +18 -0
  22. package/dist/dedup/hash.js.map +1 -0
  23. package/dist/dedup/markers.d.ts +13 -0
  24. package/dist/dedup/markers.d.ts.map +1 -0
  25. package/dist/dedup/markers.js +42 -0
  26. package/dist/dedup/markers.js.map +1 -0
  27. package/dist/dedup/plan.d.ts +45 -0
  28. package/dist/dedup/plan.d.ts.map +1 -0
  29. package/dist/dedup/plan.js +83 -0
  30. package/dist/dedup/plan.js.map +1 -0
  31. package/dist/dedup/reconcile.d.ts +29 -0
  32. package/dist/dedup/reconcile.d.ts.map +1 -0
  33. package/dist/dedup/reconcile.js +73 -0
  34. package/dist/dedup/reconcile.js.map +1 -0
  35. package/dist/findings/types.d.ts +53 -0
  36. package/dist/findings/types.d.ts.map +1 -0
  37. package/dist/findings/types.js +28 -0
  38. package/dist/findings/types.js.map +1 -0
  39. package/dist/findings/validate.d.ts +36 -0
  40. package/dist/findings/validate.d.ts.map +1 -0
  41. package/dist/findings/validate.js +178 -0
  42. package/dist/findings/validate.js.map +1 -0
  43. package/dist/index.d.ts +48 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +39 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/init/args.d.ts +20 -0
  48. package/dist/init/args.d.ts.map +1 -0
  49. package/dist/init/args.js +80 -0
  50. package/dist/init/args.js.map +1 -0
  51. package/dist/init/plan.d.ts +9 -0
  52. package/dist/init/plan.d.ts.map +1 -0
  53. package/dist/init/plan.js +17 -0
  54. package/dist/init/plan.js.map +1 -0
  55. package/dist/init/scaffold.d.ts +17 -0
  56. package/dist/init/scaffold.d.ts.map +1 -0
  57. package/dist/init/scaffold.js +27 -0
  58. package/dist/init/scaffold.js.map +1 -0
  59. package/dist/init/templates.d.ts +28 -0
  60. package/dist/init/templates.d.ts.map +1 -0
  61. package/dist/init/templates.js +187 -0
  62. package/dist/init/templates.js.map +1 -0
  63. package/dist/init/types.d.ts +29 -0
  64. package/dist/init/types.d.ts.map +1 -0
  65. package/dist/init/types.js +15 -0
  66. package/dist/init/types.js.map +1 -0
  67. package/dist/issues/body.d.ts +12 -0
  68. package/dist/issues/body.d.ts.map +1 -0
  69. package/dist/issues/body.js +53 -0
  70. package/dist/issues/body.js.map +1 -0
  71. package/dist/issues/file.d.ts +24 -0
  72. package/dist/issues/file.d.ts.map +1 -0
  73. package/dist/issues/file.js +61 -0
  74. package/dist/issues/file.js.map +1 -0
  75. package/dist/issues/gh-client.d.ts +21 -0
  76. package/dist/issues/gh-client.d.ts.map +1 -0
  77. package/dist/issues/gh-client.js +133 -0
  78. package/dist/issues/gh-client.js.map +1 -0
  79. package/dist/issues/types.d.ts +85 -0
  80. package/dist/issues/types.d.ts.map +1 -0
  81. package/dist/issues/types.js +8 -0
  82. package/dist/issues/types.js.map +1 -0
  83. package/dist/orchestrator/audit.d.ts +14 -0
  84. package/dist/orchestrator/audit.d.ts.map +1 -0
  85. package/dist/orchestrator/audit.js +47 -0
  86. package/dist/orchestrator/audit.js.map +1 -0
  87. package/dist/orchestrator/auto-fix.d.ts +31 -0
  88. package/dist/orchestrator/auto-fix.d.ts.map +1 -0
  89. package/dist/orchestrator/auto-fix.js +59 -0
  90. package/dist/orchestrator/auto-fix.js.map +1 -0
  91. package/dist/orchestrator/extract.d.ts +14 -0
  92. package/dist/orchestrator/extract.d.ts.map +1 -0
  93. package/dist/orchestrator/extract.js +70 -0
  94. package/dist/orchestrator/extract.js.map +1 -0
  95. package/dist/orchestrator/nanocoder-runner.d.ts +33 -0
  96. package/dist/orchestrator/nanocoder-runner.d.ts.map +1 -0
  97. package/dist/orchestrator/nanocoder-runner.js +125 -0
  98. package/dist/orchestrator/nanocoder-runner.js.map +1 -0
  99. package/dist/orchestrator/types.d.ts +51 -0
  100. package/dist/orchestrator/types.d.ts.map +1 -0
  101. package/dist/orchestrator/types.js +8 -0
  102. package/dist/orchestrator/types.js.map +1 -0
  103. package/dist/prompt/build.d.ts +14 -0
  104. package/dist/prompt/build.d.ts.map +1 -0
  105. package/dist/prompt/build.js +123 -0
  106. package/dist/prompt/build.js.map +1 -0
  107. package/dist/prompt/types.d.ts +34 -0
  108. package/dist/prompt/types.d.ts.map +1 -0
  109. package/dist/prompt/types.js +8 -0
  110. package/dist/prompt/types.js.map +1 -0
  111. package/dist/rule-packs/dependencies.d.ts +20 -0
  112. package/dist/rule-packs/dependencies.d.ts.map +1 -0
  113. package/dist/rule-packs/dependencies.js +58 -0
  114. package/dist/rule-packs/dependencies.js.map +1 -0
  115. package/dist/rule-packs/glob.d.ts +23 -0
  116. package/dist/rule-packs/glob.d.ts.map +1 -0
  117. package/dist/rule-packs/glob.js +66 -0
  118. package/dist/rule-packs/glob.js.map +1 -0
  119. package/dist/rule-packs/parse.d.ts +21 -0
  120. package/dist/rule-packs/parse.d.ts.map +1 -0
  121. package/dist/rule-packs/parse.js +230 -0
  122. package/dist/rule-packs/parse.js.map +1 -0
  123. package/dist/rule-packs/types.d.ts +55 -0
  124. package/dist/rule-packs/types.d.ts.map +1 -0
  125. package/dist/rule-packs/types.js +10 -0
  126. package/dist/rule-packs/types.js.map +1 -0
  127. package/dist/run/audit.d.ts +20 -0
  128. package/dist/run/audit.d.ts.map +1 -0
  129. package/dist/run/audit.js +28 -0
  130. package/dist/run/audit.js.map +1 -0
  131. package/dist/run/clone.d.ts +23 -0
  132. package/dist/run/clone.d.ts.map +1 -0
  133. package/dist/run/clone.js +51 -0
  134. package/dist/run/clone.js.map +1 -0
  135. package/dist/run/preview.d.ts +34 -0
  136. package/dist/run/preview.d.ts.map +1 -0
  137. package/dist/run/preview.js +68 -0
  138. package/dist/run/preview.js.map +1 -0
  139. package/dist/run/report.d.ts +10 -0
  140. package/dist/run/report.d.ts.map +1 -0
  141. package/dist/run/report.js +73 -0
  142. package/dist/run/report.js.map +1 -0
  143. package/dist/run/run.d.ts +74 -0
  144. package/dist/run/run.d.ts.map +1 -0
  145. package/dist/run/run.js +131 -0
  146. package/dist/run/run.js.map +1 -0
  147. package/dist/run/select.d.ts +14 -0
  148. package/dist/run/select.d.ts.map +1 -0
  149. package/dist/run/select.js +31 -0
  150. package/dist/run/select.js.map +1 -0
  151. package/dist/run/sources.d.ts +11 -0
  152. package/dist/run/sources.d.ts.map +1 -0
  153. package/dist/run/sources.js +106 -0
  154. package/dist/run/sources.js.map +1 -0
  155. package/dist/run/types.d.ts +55 -0
  156. package/dist/run/types.d.ts.map +1 -0
  157. package/dist/run/types.js +7 -0
  158. package/dist/run/types.js.map +1 -0
  159. package/dist/suppression/apply.d.ts +33 -0
  160. package/dist/suppression/apply.d.ts.map +1 -0
  161. package/dist/suppression/apply.js +55 -0
  162. package/dist/suppression/apply.js.map +1 -0
  163. package/package.json +118 -0
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Pure content generators for the files `sentinel init` scaffolds. Each returns
3
+ * a string; the generated sentinel.yaml parses under parseConfig and the
4
+ * starter pack parses under parseRulePack (both covered by tests).
5
+ */
6
+ import type { InitOptions } from './types.js';
7
+ /** The `sentinel.yaml` config, seeded from the init options. */
8
+ export declare function sentinelYaml(options: InitOptions): string;
9
+ /** The scheduled GitHub Actions audit workflow. */
10
+ export declare function workflowYaml(options: InitOptions): string;
11
+ /**
12
+ * The disabled starter pack. The leading underscore in its directory keeps
13
+ * Sentinel from loading it; the user opts in by copying it to a real path. It
14
+ * exercises every manifest field and the four body sections from the authoring
15
+ * guide.
16
+ */
17
+ export declare function starterPack(): string;
18
+ /**
19
+ * A nanocoder `agents.config.json` template. Sentinel points nanocoder at this
20
+ * file (via NANOCODER_CONFIG_DIR) so the provider/model wiring lives in the
21
+ * config repo — the same shape ContentForest uses. Local providers (Ollama,
22
+ * LM Studio) are usually auto-detected and need no entry here; the example
23
+ * below is a cloud provider to edit or delete.
24
+ */
25
+ export declare function nanocoderConfig(): string;
26
+ /** The config repo README pointing at the pack authoring docs. */
27
+ export declare function configReadme(options: InitOptions): string;
28
+ //# sourceMappingURL=templates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../source/init/templates.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,YAAY,CAAC;AAY5C,gEAAgE;AAChE,wBAAgB,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAwBzD;AAED,mDAAmD;AACnD,wBAAgB,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAgDzD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAmCpC;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAkBxC;AAED,kEAAkE;AAClE,wBAAgB,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAiCzD"}
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Pure content generators for the files `sentinel init` scaffolds. Each returns
3
+ * a string; the generated sentinel.yaml parses under parseConfig and the
4
+ * starter pack parses under parseRulePack (both covered by tests).
5
+ */
6
+ function targetsBlock(targets) {
7
+ const list = targets.length > 0 ? targets : ['your-org/your-repo'];
8
+ return list
9
+ .map(repo => ` - repo: ${repo}\n rule_packs: [example] # replace with your pack names`)
10
+ .join('\n');
11
+ }
12
+ /** The `sentinel.yaml` config, seeded from the init options. */
13
+ export function sentinelYaml(options) {
14
+ return `# Sentinel configuration — one file per install.
15
+ # Docs: https://docs.nanocollective.org/sentinel/docs/configuration
16
+
17
+ # Repositories to audit and the rule packs that apply to each.
18
+ targets:
19
+ ${targetsBlock(options.targets)}
20
+
21
+ # When the scheduled audit runs (cron, UTC).
22
+ schedule: "${options.schedule}"
23
+
24
+ # Findings below this severity appear in the summary but do not file issues.
25
+ severity_threshold: ${options.severityThreshold} # low | medium | high | critical
26
+
27
+ # Which Nanocoder provider and model to use. Local-first by default.
28
+ model:
29
+ provider: ${options.provider}
30
+ model: ${options.model}
31
+
32
+ # How findings are filed as issues.
33
+ issues:
34
+ label: ${options.label}
35
+ aggregate_to_config_repo: false
36
+ `;
37
+ }
38
+ /** The scheduled GitHub Actions audit workflow. */
39
+ export function workflowYaml(options) {
40
+ return `name: Sentinel
41
+
42
+ on:
43
+ schedule:
44
+ - cron: "${options.schedule}"
45
+ workflow_dispatch:
46
+ inputs:
47
+ dry_run:
48
+ description: "Run the full audit but file no issues"
49
+ type: boolean
50
+ default: false
51
+
52
+ # One audit at a time; never cancel an in-progress run.
53
+ concurrency:
54
+ group: sentinel
55
+ cancel-in-progress: false
56
+
57
+ permissions:
58
+ contents: read
59
+ issues: write
60
+
61
+ jobs:
62
+ audit:
63
+ runs-on: ubuntu-latest
64
+ steps:
65
+ - name: Check out configuration
66
+ uses: actions/checkout@v4
67
+
68
+ - name: Set up Node.js
69
+ uses: actions/setup-node@v4
70
+ with:
71
+ node-version: "22"
72
+
73
+ - name: Run Sentinel
74
+ env:
75
+ # A token that can read the audited repos and open issues on them.
76
+ # The default GITHUB_TOKEN only reaches THIS repository, so to audit
77
+ # other repos add a PAT (or GitHub App token) as the SENTINEL_TOKEN
78
+ # secret with repo + issues scope.
79
+ GH_TOKEN: \${{ secrets.SENTINEL_TOKEN || secrets.GITHUB_TOKEN }}
80
+ GITHUB_TOKEN: \${{ secrets.SENTINEL_TOKEN || secrets.GITHUB_TOKEN }}
81
+ run: >-
82
+ npx -y @nanocollective/sentinel@latest run
83
+ --workspace "$RUNNER_TEMP/sentinel"
84
+ --output "$GITHUB_STEP_SUMMARY"
85
+ \${{ github.event.inputs.dry_run == 'true' && '--dry-run' || '' }}
86
+ `;
87
+ }
88
+ /**
89
+ * The disabled starter pack. The leading underscore in its directory keeps
90
+ * Sentinel from loading it; the user opts in by copying it to a real path. It
91
+ * exercises every manifest field and the four body sections from the authoring
92
+ * guide.
93
+ */
94
+ export function starterPack() {
95
+ return `---
96
+ name: example
97
+ version: 0.1.0
98
+ description: "Illustrative starter pack — demonstrates every manifest field. Not enabled."
99
+ applies_to:
100
+ paths: ["src/**/*.ts"]
101
+ languages: ["typescript"]
102
+ severity_weighting:
103
+ sql-injection: critical
104
+ unbounded-query: medium
105
+ depends_on: []
106
+ category: security
107
+ ---
108
+
109
+ # What this pack audits
110
+
111
+ You are reviewing a TypeScript service. This is an illustrative example — replace
112
+ it with rules that describe the code your organisation actually ships.
113
+
114
+ ## Flag these
115
+
116
+ - **SQL injection.** User input reaching a database query without parameterisation.
117
+ - **Unbounded query.** A query that can return an entire table with no limit.
118
+
119
+ ## Severity guidance
120
+
121
+ - SQL injection on a user-facing path: **critical**.
122
+ - An unbounded query in a hot path: **medium**.
123
+
124
+ ## Do not flag
125
+
126
+ - Parameterised queries built with the project's query builder — those are safe.
127
+ - Queries in \`**/*.spec.ts\` test files.
128
+ `;
129
+ }
130
+ /**
131
+ * A nanocoder `agents.config.json` template. Sentinel points nanocoder at this
132
+ * file (via NANOCODER_CONFIG_DIR) so the provider/model wiring lives in the
133
+ * config repo — the same shape ContentForest uses. Local providers (Ollama,
134
+ * LM Studio) are usually auto-detected and need no entry here; the example
135
+ * below is a cloud provider to edit or delete.
136
+ */
137
+ export function nanocoderConfig() {
138
+ return `${JSON.stringify({
139
+ nanocoder: {
140
+ providers: [
141
+ {
142
+ name: 'Example cloud provider — edit or remove',
143
+ sdkProvider: 'anthropic',
144
+ baseUrl: 'https://api.example.com/anthropic/v1',
145
+ apiKey: '${SENTINEL_MODEL_KEY}',
146
+ models: ['example-model'],
147
+ },
148
+ ],
149
+ },
150
+ }, null, 2)}\n`;
151
+ }
152
+ /** The config repo README pointing at the pack authoring docs. */
153
+ export function configReadme(options) {
154
+ return `# Sentinel configuration
155
+
156
+ This repository configures [Sentinel](https://docs.nanocollective.org/sentinel/docs)
157
+ for your organisation. It audits the repositories listed in \`sentinel.yaml\` on a
158
+ schedule and files findings as issues.
159
+
160
+ ## Getting started
161
+
162
+ Sentinel ships **no rule packs** — it does nothing until you write one.
163
+
164
+ 1. Edit \`sentinel.yaml\`: set your real \`targets\` and the model you want.
165
+ 2. Write your first rule pack in \`rule-packs/\`. A disabled example lives in
166
+ \`rule-packs/_starter/example.md\`; copy it to \`rule-packs/<name>.md\` and edit.
167
+ See the [authoring guide](https://docs.nanocollective.org/sentinel/docs/rule-packs/authoring).
168
+ 3. Commit and push. The audit runs on its schedule (\`${options.schedule}\`), or
169
+ dispatch the **Sentinel** workflow manually (use dry-run first).
170
+
171
+ ## Model configuration
172
+
173
+ \`sentinel.yaml\` names *which* model to use (id + provider). The provider
174
+ *wiring* (endpoint, API key) lives in \`agents.config.json\`, which Sentinel hands
175
+ to Nanocoder — the same shape ContentForest uses. Local providers (Ollama, LM
176
+ Studio) are usually auto-detected and need no entry; for a cloud provider, edit
177
+ the example block and set its key as an environment variable / Actions secret.
178
+
179
+ ## Layout
180
+
181
+ - \`sentinel.yaml\` — targets, schedule, model, and issue routing.
182
+ - \`agents.config.json\` — Nanocoder provider/model wiring.
183
+ - \`rule-packs/\` — your rule packs (you author these).
184
+ - \`.github/workflows/sentinel.yml\` — the scheduled audit.
185
+ `;
186
+ }
187
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../source/init/templates.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,SAAS,YAAY,CAAC,OAAiB;IACtC,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC;IACnE,OAAO,IAAI;SACT,GAAG,CACH,IAAI,CAAC,EAAE,CACN,aAAa,IAAI,4DAA4D,CAC9E;SACA,IAAI,CAAC,IAAI,CAAC,CAAC;AACd,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,YAAY,CAAC,OAAoB;IAChD,OAAO;;;;;EAKN,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC;;;aAGlB,OAAO,CAAC,QAAQ;;;sBAGP,OAAO,CAAC,iBAAiB;;;;cAIjC,OAAO,CAAC,QAAQ;WACnB,OAAO,CAAC,KAAK;;;;WAIb,OAAO,CAAC,KAAK;;CAEvB,CAAC;AACF,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,YAAY,CAAC,OAAoB;IAChD,OAAO;;;;eAIO,OAAO,CAAC,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0C9B,CAAC;AACF,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW;IAC1B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCP,CAAC;AACF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,eAAe;IAC9B,OAAO,GAAG,IAAI,CAAC,SAAS,CACvB;QACC,SAAS,EAAE;YACV,SAAS,EAAE;gBACV;oBACC,IAAI,EAAE,yCAAyC;oBAC/C,WAAW,EAAE,WAAW;oBACxB,OAAO,EAAE,sCAAsC;oBAC/C,MAAM,EAAE,uBAAuB;oBAC/B,MAAM,EAAE,CAAC,eAAe,CAAC;iBACzB;aACD;SACD;KACD,EACD,IAAI,EACJ,CAAC,CACD,IAAI,CAAC;AACP,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,YAAY,CAAC,OAAoB;IAChD,OAAO;;;;;;;;;;;;;;wDAcgD,OAAO,CAAC,QAAQ;;;;;;;;;;;;;;;;;CAiBvE,CAAC;AACF,CAAC"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Types for `sentinel init`, the scaffolder. The resolved options drive pure
3
+ * content generation (templates.ts) and a file plan (plan.ts); the writer
4
+ * (scaffold.ts) puts them on disk.
5
+ */
6
+ import type { Severity } from '../findings/types.js';
7
+ /** The fully-resolved options an init run scaffolds from. */
8
+ export interface InitOptions {
9
+ /** Nanocoder model provider (ollama, lmstudio, a cloud provider, …). */
10
+ provider: string;
11
+ /** Model identifier for that provider. */
12
+ model: string;
13
+ /** Cron schedule (UTC) for the audit workflow. */
14
+ schedule: string;
15
+ /** Repositories to seed `targets` with, as owner/name. */
16
+ targets: string[];
17
+ /** Filing threshold to seed the config with. */
18
+ severityThreshold: Severity;
19
+ /** Label applied to filed issues. */
20
+ label: string;
21
+ }
22
+ /** Defaults applied to anything the user does not supply. */
23
+ export declare const DEFAULT_INIT_OPTIONS: InitOptions;
24
+ /** One file the scaffolder will write, as a repo-relative path and content. */
25
+ export interface ScaffoldFile {
26
+ path: string;
27
+ content: string;
28
+ }
29
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../source/init/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAC;AAEnD,6DAA6D;AAC7D,MAAM,WAAW,WAAW;IAC3B,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAC;IACjB,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,QAAQ,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,gDAAgD;IAChD,iBAAiB,EAAE,QAAQ,CAAC;IAC5B,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;CACd;AAED,6DAA6D;AAC7D,eAAO,MAAM,oBAAoB,EAAE,WAOlC,CAAC;AAEF,+EAA+E;AAC/E,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CAChB"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Types for `sentinel init`, the scaffolder. The resolved options drive pure
3
+ * content generation (templates.ts) and a file plan (plan.ts); the writer
4
+ * (scaffold.ts) puts them on disk.
5
+ */
6
+ /** Defaults applied to anything the user does not supply. */
7
+ export const DEFAULT_INIT_OPTIONS = {
8
+ provider: 'ollama',
9
+ model: 'llama3.1:70b',
10
+ schedule: '0 6 * * *',
11
+ targets: [],
12
+ severityThreshold: 'medium',
13
+ label: 'sentinel',
14
+ };
15
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../source/init/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAoBH,6DAA6D;AAC7D,MAAM,CAAC,MAAM,oBAAoB,GAAgB;IAChD,QAAQ,EAAE,QAAQ;IAClB,KAAK,EAAE,cAAc;IACrB,QAAQ,EAAE,WAAW;IACrB,OAAO,EAAE,EAAE;IACX,iBAAiB,EAAE,QAAQ;IAC3B,KAAK,EAAE,UAAU;CACjB,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Build the title and Markdown body of an issue from a finding. Pure and
3
+ * heavily tested — this is the human-facing surface of the whole tool, so it
4
+ * degrades gracefully when the optional model-authored fields are absent.
5
+ */
6
+ import type { Finding } from '../findings/types.js';
7
+ import type { FilingContext } from './types.js';
8
+ /** A concise, scannable issue title. */
9
+ export declare function buildIssueTitle(finding: Finding): string;
10
+ /** The full issue body. */
11
+ export declare function buildIssueBody(finding: Finding, context: FilingContext): string;
12
+ //# sourceMappingURL=body.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../../source/issues/body.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,sBAAsB,CAAC;AAClD,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,YAAY,CAAC;AAkB9C,wCAAwC;AACxC,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAMxD;AAED,2BAA2B;AAC3B,wBAAgB,cAAc,CAC7B,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,aAAa,GACpB,MAAM,CA+CR"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Build the title and Markdown body of an issue from a finding. Pure and
3
+ * heavily tested — this is the human-facing surface of the whole tool, so it
4
+ * degrades gracefully when the optional model-authored fields are absent.
5
+ */
6
+ const TITLE_HEADLINE_MAX = 80;
7
+ function truncate(text, max) {
8
+ const clean = text.replace(/\s+/g, ' ').trim();
9
+ return clean.length > max ? `${clean.slice(0, max - 1)}…` : clean;
10
+ }
11
+ /** A code fence longer than any backtick run in the snippet, so it can't break. */
12
+ function fenceFor(snippet) {
13
+ const longestRun = Math.max(0, ...[...snippet.matchAll(/`+/g)].map(match => match[0].length));
14
+ return '`'.repeat(Math.max(3, longestRun + 1));
15
+ }
16
+ /** A concise, scannable issue title. */
17
+ export function buildIssueTitle(finding) {
18
+ const headline = truncate(finding.summary ?? finding.rule, TITLE_HEADLINE_MAX);
19
+ return `Sentinel [${finding.severity}] ${headline} (${finding.file}:${finding.lineRange.start})`;
20
+ }
21
+ /** The full issue body. */
22
+ export function buildIssueBody(finding, context) {
23
+ const { file, lineRange, rule, severity, confidence, category } = finding;
24
+ const parts = [];
25
+ parts.push(finding.summary ?? `A ${category} finding produced by rule \`${rule}\`.`);
26
+ parts.push([
27
+ `**Severity:** ${severity} · **Confidence:** ${confidence} · **Category:** ${category}`,
28
+ `**Location:** \`${file}\` lines ${lineRange.start}–${lineRange.end}`,
29
+ `**Rule:** \`${rule}\`${context.packVersion ? ` (pack ${context.packVersion})` : ''}`,
30
+ ].join('\n'));
31
+ if (finding.rationale) {
32
+ parts.push(['### Why this severity', '', finding.rationale].join('\n'));
33
+ }
34
+ const fence = fenceFor(finding.offendingSnippet);
35
+ parts.push([
36
+ '### Offending code',
37
+ `${fence}`,
38
+ finding.offendingSnippet,
39
+ `${fence}`,
40
+ ].join('\n'));
41
+ if (finding.suggestedNextSteps) {
42
+ parts.push(['### Suggested next steps', '', finding.suggestedNextSteps].join('\n'));
43
+ }
44
+ const configLink = context.configRepo
45
+ ? ` Configured in [\`${context.configRepo}\`](https://github.com/${context.configRepo}).`
46
+ : '';
47
+ parts.push([
48
+ '---',
49
+ `🛡️ Filed by Sentinel.${configLink} If this is a false positive, close this issue as \`sentinel:false-positive\` (or add that label) and Sentinel will not refile it.`,
50
+ ].join('\n'));
51
+ return parts.join('\n\n');
52
+ }
53
+ //# sourceMappingURL=body.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"body.js","sourceRoot":"","sources":["../../source/issues/body.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAE9B,SAAS,QAAQ,CAAC,IAAY,EAAE,GAAW;IAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/C,OAAO,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC;AACnE,CAAC;AAED,mFAAmF;AACnF,SAAS,QAAQ,CAAC,OAAe;IAChC,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAC1B,CAAC,EACD,GAAG,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAC7D,CAAC;IACF,OAAO,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,eAAe,CAAC,OAAgB;IAC/C,MAAM,QAAQ,GAAG,QAAQ,CACxB,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,EAC/B,kBAAkB,CAClB,CAAC;IACF,OAAO,aAAa,OAAO,CAAC,QAAQ,KAAK,QAAQ,KAAK,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,SAAS,CAAC,KAAK,GAAG,CAAC;AAClG,CAAC;AAED,2BAA2B;AAC3B,MAAM,UAAU,cAAc,CAC7B,OAAgB,EAChB,OAAsB;IAEtB,MAAM,EAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAC,GAAG,OAAO,CAAC;IACxE,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,CAAC,IAAI,CACT,OAAO,CAAC,OAAO,IAAI,KAAK,QAAQ,+BAA+B,IAAI,KAAK,CACxE,CAAC;IAEF,KAAK,CAAC,IAAI,CACT;QACC,iBAAiB,QAAQ,sBAAsB,UAAU,oBAAoB,QAAQ,EAAE;QACvF,mBAAmB,IAAI,YAAY,SAAS,CAAC,KAAK,IAAI,SAAS,CAAC,GAAG,EAAE;QACrE,eAAe,IAAI,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,OAAO,CAAC,WAAW,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;KACrF,CAAC,IAAI,CAAC,IAAI,CAAC,CACZ,CAAC;IAEF,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,CAAC,uBAAuB,EAAE,EAAE,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACjD,KAAK,CAAC,IAAI,CACT;QACC,oBAAoB;QACpB,GAAG,KAAK,EAAE;QACV,OAAO,CAAC,gBAAgB;QACxB,GAAG,KAAK,EAAE;KACV,CAAC,IAAI,CAAC,IAAI,CAAC,CACZ,CAAC;IAEF,IAAI,OAAO,CAAC,kBAAkB,EAAE,CAAC;QAChC,KAAK,CAAC,IAAI,CACT,CAAC,0BAA0B,EAAE,EAAE,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CACvE,CAAC;IACH,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU;QACpC,CAAC,CAAC,qBAAqB,OAAO,CAAC,UAAU,0BAA0B,OAAO,CAAC,UAAU,IAAI;QACzF,CAAC,CAAC,EAAE,CAAC;IACN,KAAK,CAAC,IAAI,CACT;QACC,KAAK;QACL,yBAAyB,UAAU,oIAAoI;KACvK,CAAC,IAAI,CAAC,IAAI,CAAC,CACZ,CAAC;IAEF,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * File findings as issues: gate by the severity threshold, build issue content,
3
+ * route to the audited repo (or the config repo when aggregating), and create
4
+ * each issue through the injected {@link GitHubClient}.
5
+ *
6
+ * Dedup (task #7) will slot in front of the create call; this module is the
7
+ * plain filing path it wraps.
8
+ */
9
+ import type { SentinelConfig } from '../config/types.js';
10
+ import { type Finding } from '../findings/types.js';
11
+ import type { FilingContext, FilingResult, GitHubClient, IssueContent } from './types.js';
12
+ /** Findings at or above the configured filing threshold. */
13
+ export declare function qualifyingFindings(findings: Finding[], config: SentinelConfig): Finding[];
14
+ /** Which repository issues are filed on for this config. */
15
+ export declare function targetRepoFor(config: SentinelConfig, context: FilingContext): string;
16
+ /** Render one finding into filable issue content. */
17
+ export declare function buildIssueContent(finding: Finding, config: SentinelConfig, context: FilingContext): IssueContent;
18
+ /**
19
+ * File every qualifying finding. Findings below the threshold are returned in
20
+ * `skipped`; a create that throws is captured in `errors` so one bad issue does
21
+ * not abort the batch.
22
+ */
23
+ export declare function fileFindings(findings: Finding[], config: SentinelConfig, client: GitHubClient, context: FilingContext): Promise<FilingResult>;
24
+ //# sourceMappingURL=file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../source/issues/file.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,oBAAoB,CAAC;AAGvD,OAAO,EAAC,KAAK,OAAO,EAAyB,MAAM,sBAAsB,CAAC;AAE1E,OAAO,KAAK,EACX,aAAa,EACb,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,MAAM,YAAY,CAAC;AAEpB,4DAA4D;AAC5D,wBAAgB,kBAAkB,CACjC,QAAQ,EAAE,OAAO,EAAE,EACnB,MAAM,EAAE,cAAc,GACpB,OAAO,EAAE,CAIX;AAED,4DAA4D;AAC5D,wBAAgB,aAAa,CAC5B,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,aAAa,GACpB,MAAM,CAKR;AAED,qDAAqD;AACrD,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,aAAa,GACpB,YAAY,CAYd;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CACjC,QAAQ,EAAE,OAAO,EAAE,EACnB,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,YAAY,EACpB,OAAO,EAAE,aAAa,GACpB,OAAO,CAAC,YAAY,CAAC,CAuBvB"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * File findings as issues: gate by the severity threshold, build issue content,
3
+ * route to the audited repo (or the config repo when aggregating), and create
4
+ * each issue through the injected {@link GitHubClient}.
5
+ *
6
+ * Dedup (task #7) will slot in front of the create call; this module is the
7
+ * plain filing path it wraps.
8
+ */
9
+ import { findingHash } from '../dedup/hash.js';
10
+ import { upsertMarker } from '../dedup/markers.js';
11
+ import { meetsSeverityThreshold } from '../findings/types.js';
12
+ import { buildIssueBody, buildIssueTitle } from './body.js';
13
+ /** Findings at or above the configured filing threshold. */
14
+ export function qualifyingFindings(findings, config) {
15
+ return findings.filter(finding => meetsSeverityThreshold(finding.severity, config.severityThreshold));
16
+ }
17
+ /** Which repository issues are filed on for this config. */
18
+ export function targetRepoFor(config, context) {
19
+ if (config.issues.aggregateToConfigRepo && context.configRepo) {
20
+ return context.configRepo;
21
+ }
22
+ return context.auditedRepo;
23
+ }
24
+ /** Render one finding into filable issue content. */
25
+ export function buildIssueContent(finding, config, context) {
26
+ const body = upsertMarker(buildIssueBody(finding, context), 'hash', findingHash(finding));
27
+ return {
28
+ title: buildIssueTitle(finding),
29
+ body,
30
+ labels: [config.issues.label],
31
+ assignees: config.issues.assignee ? [config.issues.assignee] : [],
32
+ };
33
+ }
34
+ /**
35
+ * File every qualifying finding. Findings below the threshold are returned in
36
+ * `skipped`; a create that throws is captured in `errors` so one bad issue does
37
+ * not abort the batch.
38
+ */
39
+ export async function fileFindings(findings, config, client, context) {
40
+ const targetRepo = targetRepoFor(config, context);
41
+ const result = { targetRepo, filed: [], skipped: [], errors: [] };
42
+ for (const finding of findings) {
43
+ if (!meetsSeverityThreshold(finding.severity, config.severityThreshold)) {
44
+ result.skipped.push(finding);
45
+ continue;
46
+ }
47
+ const content = buildIssueContent(finding, config, context);
48
+ try {
49
+ const issue = await client.createIssue({ repo: targetRepo, ...content });
50
+ result.filed.push({ finding, issue });
51
+ }
52
+ catch (error) {
53
+ result.errors.push({
54
+ finding,
55
+ error: error instanceof Error ? error.message : String(error),
56
+ });
57
+ }
58
+ }
59
+ return result;
60
+ }
61
+ //# sourceMappingURL=file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.js","sourceRoot":"","sources":["../../source/issues/file.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAC,WAAW,EAAC,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAC,YAAY,EAAC,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAe,sBAAsB,EAAC,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAC,cAAc,EAAE,eAAe,EAAC,MAAM,WAAW,CAAC;AAQ1D,4DAA4D;AAC5D,MAAM,UAAU,kBAAkB,CACjC,QAAmB,EACnB,MAAsB;IAEtB,OAAO,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAChC,sBAAsB,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,iBAAiB,CAAC,CAClE,CAAC;AACH,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,aAAa,CAC5B,MAAsB,EACtB,OAAsB;IAEtB,IAAI,MAAM,CAAC,MAAM,CAAC,qBAAqB,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;QAC/D,OAAO,OAAO,CAAC,UAAU,CAAC;IAC3B,CAAC;IACD,OAAO,OAAO,CAAC,WAAW,CAAC;AAC5B,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,iBAAiB,CAChC,OAAgB,EAChB,MAAsB,EACtB,OAAsB;IAEtB,MAAM,IAAI,GAAG,YAAY,CACxB,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,EAChC,MAAM,EACN,WAAW,CAAC,OAAO,CAAC,CACpB,CAAC;IACF,OAAO;QACN,KAAK,EAAE,eAAe,CAAC,OAAO,CAAC;QAC/B,IAAI;QACJ,MAAM,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;QAC7B,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE;KACjE,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CACjC,QAAmB,EACnB,MAAsB,EACtB,MAAoB,EACpB,OAAsB;IAEtB,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClD,MAAM,MAAM,GAAiB,EAAC,UAAU,EAAE,KAAK,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAC,CAAC;IAE9E,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAChC,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,CAAC,iBAAiB,CAAC,EAAE,CAAC;YACzE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC7B,SAAS;QACV,CAAC;QAED,MAAM,OAAO,GAAG,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC;QAC5D,IAAI,CAAC;YACJ,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,EAAC,IAAI,EAAE,UAAU,EAAE,GAAG,OAAO,EAAC,CAAC,CAAC;YACvE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,EAAC,OAAO,EAAE,KAAK,EAAC,CAAC,CAAC;QACrC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;gBAClB,OAAO;gBACP,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;aAC7D,CAAC,CAAC;QACJ,CAAC;IACF,CAAC;IAED,OAAO,MAAM,CAAC;AACf,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The real {@link GitHubClient}, backed by the `gh` CLI. In GitHub Actions `gh`
3
+ * is preinstalled and authenticated from the workflow token; locally it uses
4
+ * the user's `gh auth` session. Arg building is pure and tested; the spawn and
5
+ * URL parsing are not.
6
+ */
7
+ import type { CreatedIssue, CreateIssueParams, ExistingIssue, ReconcileClient } from './types.js';
8
+ /** Build the `gh issue create` argv for the given params. Pure and tested. */
9
+ export declare function buildGhIssueArgs(params: CreateIssueParams): string[];
10
+ /** Parse the issue number from the URL `gh issue create` prints on success. */
11
+ export declare function parseIssueUrl(output: string): CreatedIssue | null;
12
+ /** Build the `gh issue list` argv that returns the JSON dedup needs. */
13
+ export declare function buildGhListArgs(repo: string, label: string): string[];
14
+ /** Build the `gh issue edit` argv that replaces an issue body. */
15
+ export declare function buildGhEditArgs(repo: string, issueNumber: number, body: string): string[];
16
+ /** Build the `gh issue close` argv. */
17
+ export declare function buildGhCloseArgs(repo: string, issueNumber: number, reason?: string, comment?: string): string[];
18
+ /** Parse the JSON `gh issue list --json ...` prints into ExistingIssues. */
19
+ export declare function parseIssueList(json: string): ExistingIssue[];
20
+ export declare const ghIssueClient: ReconcileClient;
21
+ //# sourceMappingURL=gh-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gh-client.d.ts","sourceRoot":"","sources":["../../source/issues/gh-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EACX,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,MAAM,YAAY,CAAC;AAEpB,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,EAAE,CAkBpE;AAED,+EAA+E;AAC/E,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAOjE;AAED,wEAAwE;AACxE,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAerE;AAED,kEAAkE;AAClE,wBAAgB,eAAe,CAC9B,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,MAAM,GACV,MAAM,EAAE,CAEV;AAED,uCAAuC;AACvC,wBAAgB,gBAAgB,CAC/B,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,GACd,MAAM,EAAE,CASV;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAe5D;AA2BD,eAAO,MAAM,aAAa,EAAE,eA4C3B,CAAC"}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * The real {@link GitHubClient}, backed by the `gh` CLI. In GitHub Actions `gh`
3
+ * is preinstalled and authenticated from the workflow token; locally it uses
4
+ * the user's `gh auth` session. Arg building is pure and tested; the spawn and
5
+ * URL parsing are not.
6
+ */
7
+ import { spawnSync } from 'node:child_process';
8
+ /** Build the `gh issue create` argv for the given params. Pure and tested. */
9
+ export function buildGhIssueArgs(params) {
10
+ const args = [
11
+ 'issue',
12
+ 'create',
13
+ '--repo',
14
+ params.repo,
15
+ '--title',
16
+ params.title,
17
+ '--body',
18
+ params.body,
19
+ ];
20
+ for (const label of params.labels) {
21
+ args.push('--label', label);
22
+ }
23
+ for (const assignee of params.assignees) {
24
+ args.push('--assignee', assignee);
25
+ }
26
+ return args;
27
+ }
28
+ /** Parse the issue number from the URL `gh issue create` prints on success. */
29
+ export function parseIssueUrl(output) {
30
+ const url = output.trim().split('\n').pop()?.trim() ?? '';
31
+ const match = url.match(/\/issues\/(\d+)\s*$/);
32
+ if (!match) {
33
+ return null;
34
+ }
35
+ return { number: Number(match[1]), url };
36
+ }
37
+ /** Build the `gh issue list` argv that returns the JSON dedup needs. */
38
+ export function buildGhListArgs(repo, label) {
39
+ return [
40
+ 'issue',
41
+ 'list',
42
+ '--repo',
43
+ repo,
44
+ '--label',
45
+ label,
46
+ '--state',
47
+ 'all',
48
+ '--limit',
49
+ '1000',
50
+ '--json',
51
+ 'number,url,state,labels,body',
52
+ ];
53
+ }
54
+ /** Build the `gh issue edit` argv that replaces an issue body. */
55
+ export function buildGhEditArgs(repo, issueNumber, body) {
56
+ return ['issue', 'edit', String(issueNumber), '--repo', repo, '--body', body];
57
+ }
58
+ /** Build the `gh issue close` argv. */
59
+ export function buildGhCloseArgs(repo, issueNumber, reason, comment) {
60
+ const args = ['issue', 'close', String(issueNumber), '--repo', repo];
61
+ if (reason) {
62
+ args.push('--reason', reason);
63
+ }
64
+ if (comment) {
65
+ args.push('--comment', comment);
66
+ }
67
+ return args;
68
+ }
69
+ /** Parse the JSON `gh issue list --json ...` prints into ExistingIssues. */
70
+ export function parseIssueList(json) {
71
+ const raw = JSON.parse(json);
72
+ return raw.map(item => ({
73
+ number: item.number,
74
+ url: item.url,
75
+ state: item.state.toLowerCase() === 'open' ? 'open' : 'closed',
76
+ labels: item.labels.map(label => label.name),
77
+ body: item.body,
78
+ }));
79
+ }
80
+ /* c8 ignore start -- spawns the gh CLI; not exercised in unit tests. */
81
+ function runGh(args) {
82
+ const result = spawnSync('gh', args, {
83
+ encoding: 'utf8',
84
+ stdio: ['ignore', 'pipe', 'pipe'],
85
+ env: process.env,
86
+ });
87
+ if (result.error) {
88
+ const code = result.error.code;
89
+ if (code === 'ENOENT') {
90
+ throw new Error('`gh` is not on PATH. Install the GitHub CLI.');
91
+ }
92
+ throw result.error;
93
+ }
94
+ return {
95
+ status: result.status ?? 1,
96
+ stdout: result.stdout ?? '',
97
+ stderr: result.stderr ?? '',
98
+ };
99
+ }
100
+ export const ghIssueClient = {
101
+ async createIssue(params) {
102
+ const result = runGh(buildGhIssueArgs(params));
103
+ if (result.status !== 0) {
104
+ throw new Error(`gh issue create failed (status ${result.status}): ${result.stderr}`.trim());
105
+ }
106
+ const created = parseIssueUrl(result.stdout);
107
+ if (!created) {
108
+ throw new Error(`could not parse issue URL from gh output: ${result.stdout}`);
109
+ }
110
+ return created;
111
+ },
112
+ async listIssues({ repo, label }) {
113
+ const result = runGh(buildGhListArgs(repo, label));
114
+ if (result.status !== 0) {
115
+ throw new Error(`gh issue list failed (status ${result.status}): ${result.stderr}`.trim());
116
+ }
117
+ return parseIssueList(result.stdout);
118
+ },
119
+ async updateIssue({ repo, number, body }) {
120
+ const result = runGh(buildGhEditArgs(repo, number, body));
121
+ if (result.status !== 0) {
122
+ throw new Error(`gh issue edit failed (status ${result.status}): ${result.stderr}`.trim());
123
+ }
124
+ },
125
+ async closeIssue({ repo, number, reason, comment }) {
126
+ const result = runGh(buildGhCloseArgs(repo, number, reason, comment));
127
+ if (result.status !== 0) {
128
+ throw new Error(`gh issue close failed (status ${result.status}): ${result.stderr}`.trim());
129
+ }
130
+ },
131
+ };
132
+ /* c8 ignore stop */
133
+ //# sourceMappingURL=gh-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gh-client.js","sourceRoot":"","sources":["../../source/issues/gh-client.ts"],"names":[],"mappings":"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"}