@goliapkg/sentori-cli 1.8.0 → 1.10.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 (53) hide show
  1. package/LINT.md +237 -0
  2. package/README.md +65 -8
  3. package/examples/insight-lint.toml +32 -0
  4. package/lib/artifacts.d.ts +7 -0
  5. package/lib/artifacts.d.ts.map +1 -1
  6. package/lib/artifacts.js +1 -1
  7. package/lib/artifacts.js.map +1 -1
  8. package/lib/compatibility.js +1 -1
  9. package/lib/compatibility.js.map +1 -1
  10. package/lib/index.js +13 -5
  11. package/lib/index.js.map +1 -1
  12. package/lib/issue.d.ts +13 -2
  13. package/lib/issue.d.ts.map +1 -1
  14. package/lib/issue.js +12 -1
  15. package/lib/issue.js.map +1 -1
  16. package/lib/lint/config.d.ts +19 -0
  17. package/lib/lint/config.d.ts.map +1 -0
  18. package/lib/lint/config.js +149 -0
  19. package/lib/lint/config.js.map +1 -0
  20. package/lib/lint/index.d.ts +4 -0
  21. package/lib/lint/index.d.ts.map +1 -0
  22. package/lib/lint/index.js +204 -0
  23. package/lib/lint/index.js.map +1 -0
  24. package/lib/lint/report.d.ts +48 -0
  25. package/lib/lint/report.d.ts.map +1 -0
  26. package/lib/lint/report.js +136 -0
  27. package/lib/lint/report.js.map +1 -0
  28. package/lib/lint/rules.d.ts +28 -0
  29. package/lib/lint/rules.d.ts.map +1 -0
  30. package/lib/lint/rules.js +57 -0
  31. package/lib/lint/rules.js.map +1 -0
  32. package/lib/mcp.d.ts +6 -0
  33. package/lib/mcp.d.ts.map +1 -1
  34. package/lib/mcp.js +228 -65
  35. package/lib/mcp.js.map +1 -1
  36. package/lib/workflow.d.ts.map +1 -1
  37. package/lib/workflow.js +12 -2
  38. package/lib/workflow.js.map +1 -1
  39. package/package.json +4 -2
  40. package/src/__tests__/issue.test.ts +27 -0
  41. package/src/__tests__/lint.test.ts +185 -0
  42. package/src/__tests__/mcp.test.ts +128 -1
  43. package/src/__tests__/workflow.test.ts +41 -1
  44. package/src/artifacts.ts +3 -1
  45. package/src/compatibility.ts +1 -1
  46. package/src/index.ts +12 -5
  47. package/src/issue.ts +16 -5
  48. package/src/lint/config.ts +102 -0
  49. package/src/lint/index.ts +163 -0
  50. package/src/lint/report.ts +129 -0
  51. package/src/lint/rules.ts +64 -0
  52. package/src/mcp.ts +205 -67
  53. package/src/workflow.ts +7 -2
package/LINT.md ADDED
@@ -0,0 +1,237 @@
1
+ # Swift and Kotlin lint preview 0.3.0
2
+
3
+ `sentori lint` is a local quality gate. The existing `sentori-cli` command remains
4
+ available, including `sentori-cli lint`. This preview checks Swift and Kotlin sources. There is no upload, build plugin, or server dependency.
5
+
6
+ The analyzer is a separate developer tool. Installing the iOS runtime SDK does
7
+ not add SwiftSyntax or change the SDK's supported Swift version.
8
+
9
+ ## Build and run
10
+
11
+ From a Sentori source checkout, with Node 18+ and the toolchain for each language
12
+ you scan (Swift 6.4; Java 17+ for Kotlin):
13
+
14
+ ```sh
15
+ swift build --package-path sdk/native/ios/Lint -c release --jobs 2
16
+ export SENTORI_LINT_SWIFT="$PWD/sdk/native/ios/Lint/.build/release/sentori-lint-swift"
17
+ # Kotlin analyzer (standalone; no Android SDK or Gradle project needed):
18
+ (cd sdk/native/android/lint && ./gradlew --no-daemon check installDist)
19
+ export SENTORI_LINT_KOTLIN="$PWD/sdk/native/android/lint/build/install/sentori-lint-kotlin/bin/sentori-lint-kotlin"
20
+ bun run --cwd sdk/cli build
21
+ ```
22
+
23
+ The Swift analyzer's only package dependency is the official `swiftlang/swift-syntax`,
24
+ locked to stable 604.0.0. It reads a versioned JSON request on stdin and emits a
25
+ single JSON response on stdout. `sentori-lint-swift --version` prints its version.
26
+ The CLI rejects incompatible, incomplete, or malformed analyzer responses.
27
+
28
+ After installing `@goliapkg/sentori-cli`, use `sentori lint`. From a source checkout,
29
+ invoke `node /path/to/sentori/sdk/cli/lib/index.js lint`.
30
+ `--swift-analyzer /absolute/path/to/sentori-lint-swift` overrides the environment;
31
+ otherwise the CLI looks for `sentori-lint-swift` on PATH. No binary is downloaded
32
+ automatically. `--kotlin-analyzer /absolute/path/to/sentori-lint-kotlin` similarly
33
+ overrides `SENTORI_LINT_KOTLIN`, then PATH. Keep the entire Gradle `installDist`
34
+ directory together: its launcher needs the jars in the adjacent `lib/` directory.
35
+ The Kotlin analyzer uses the official Kotlin compiler PSI 2.4.21 plus its standard
36
+ library; it neither loads the application's Gradle build nor runs compiler plugins.
37
+ Only analyzers for selected languages are required. Both return findings to the
38
+ CLI, which applies policy, emits text/JSON/SARIF and determines the gate exit code.
39
+
40
+ Place `sentori-lint.toml` at the repository root. Then:
41
+
42
+ ```sh
43
+ sentori lint
44
+ sentori lint --format sarif > sentori-lint.sarif
45
+ sentori lint --explain SWCON001
46
+ sentori lint --changed HEAD
47
+ sentori lint Sources/Feature
48
+ sentori lint --fix
49
+ ```
50
+
51
+ Files come from Git's tracked and untracked, non-ignored file lists. Paths are
52
+ relative to the Git root, and positional paths are resolved from the working
53
+ directory. Deleted files are omitted; source symlinks are rejected. Generated sources are
54
+ excluded only when explicitly configured. Configured test files participate in function-length checks only; other rules skip them. All `#if`
55
+ branches are analyzed, independent of the platform running the tool. The analyzer
56
+ parses syntax, not types or runtime behavior.
57
+
58
+ `--fix` delegates formatting to the installed toolchain's `swift format` and
59
+ rescans afterward. It never rewrites forced casts, error handling, or concurrency
60
+ semantics. A scan selecting Kotlin sources rejects `--fix` before modifying any file;
61
+ select Swift paths explicitly to format only those sources. Inspect the resulting source diff before committing it.
62
+
63
+ Exit codes are **0** for a passing gate, **1** for rule/file count regressions or hard-limit violations,
64
+ and **2** for invalid configuration, parse failures, missing tools, or other
65
+ execution errors. A full scan selecting no Swift or Kotlin files fails explicitly. An empty
66
+ incremental scan succeeds and reports zero files. Both `.swift` and `.kt` files participate; Kotlin scripts (`.kts`) are outside this preview.
67
+
68
+ ## Configuration
69
+
70
+ This preview supports a strict TOML subset: single-line quoted string values,
71
+ single-line arrays of double-quoted strings, `version = 1`, a `[rules]` table,
72
+ and `[[allow]]` entries. Comments are supported outside strings. Unsupported
73
+ syntax, unknown keys, duplicate keys, and unknown rule IDs fail rather than
74
+ silently changing policy. There is no runtime TOML library dependency.
75
+
76
+ ```toml
77
+ version = 1
78
+ include = ["Sources/**"]
79
+ exclude = []
80
+ tests = ["**/Tests/**", "**/*Tests.swift", "**/*Test.swift"]
81
+ excludePathRegex = ["generated"]
82
+
83
+ [rules]
84
+ SWSAFE001 = "error"
85
+ SWSAFE002 = "error"
86
+
87
+ [[allow]]
88
+ rule = "SWCON002"
89
+ path = "Sources/LockedBox.swift"
90
+ reason = "All mutable state is protected by the instance lock; reviewed in PR 123."
91
+ ```
92
+
93
+ Path patterns support `*`, `**`, and `?`, use `/`, and match the entire
94
+ repository-relative path. Brace expansion and character classes are rejected.
95
+ `excludePathRegex` contains case-insensitive JavaScript regular expressions,
96
+ matched anywhere in the repository-relative path. An allowance needs a known
97
+ rule, a relative path pattern, and a nonempty reason. Allowances remain visible
98
+ in text/JSON and as external SARIF suppressions. Source comments cannot suppress
99
+ findings. Safety, concurrency and length rules default to `error`; parameter and Android style policies default to `warn`. The three
100
+ `SWSAFE` rules can be `off` or `warn`;
101
+ the `SWCON` and `KTCON` rules always require a reviewed allowance to bypass the gate.
102
+ `SWAPI001` and `SWAPI002` default to `warn`. Enabled length rules always treat hard-limit
103
+ violations as errors, even when their target checks are configured as warnings.
104
+
105
+ [The insight example](examples/insight-lint.toml) restricts scanning to its four
106
+ active trees, applies only function-length checks to configured test files, and
107
+ excludes any path containing `generated`, case-insensitively. It deliberately includes no allowances.
108
+
109
+ ## Rules
110
+
111
+ These are **project policies informed by official documentation**, not claims
112
+ that either language prohibits these language features. `--explain` supplies the official
113
+ reference, section, group, rationale, severity, and fixability for each rule.
114
+
115
+ | Rule | Check | Baseline permitted |
116
+ | --- | --- | --- |
117
+ | SWSAFE001 | `try!` expressions | Yes |
118
+ | SWSAFE002 | `as!` expressions | Yes |
119
+ | SWCON001 | `nonisolated(unsafe)` modifiers | No |
120
+ | SWCON002 | `@unchecked Sendable` conformances | No |
121
+ | SWCON003 | `Task.detached` / `Swift.Task.detached` calls | No |
122
+ | SWMAINT001 | Function length: target 50, hard limit 200 lines | Target only |
123
+ | SWMAINT002 | File length: target 300, hard limit 500 lines | Target only |
124
+ | KTSAFE001 | Kotlin postfix not-null assertions (`value!!`) | Yes |
125
+ | KTCON001 | Imported/qualified `kotlinx.coroutines.GlobalScope` uses | No |
126
+ | KTCON002 | Imported/qualified `kotlinx.coroutines.runBlocking` calls in main sources | No |
127
+ | KTMAINT001 | Kotlin function length: target 50, hard limit 200 lines | Target only |
128
+ | KTMAINT002 | Kotlin file length: target 300, hard limit 500 lines | Target only |
129
+ | KTAPI001 | More than 5 function/constructor parameters (warning) | If configured as error |
130
+ | KTAPI002 | Explicit non-nullable `Boolean` parameters (warning) | If configured as error |
131
+ | AKS001 | Wildcard imports (warning) | If configured as error |
132
+ | AKS002 | Control-flow braces, with single-line branch exceptions (warning) | If configured as error |
133
+ | AKS003 | ASCII `const val` names outside UPPER_SNAKE_CASE (warning) | If configured as error |
134
+ | SWSAFE003 | Postfix force unwrap expressions (`value!`) | Yes |
135
+ | SWAPI001 | More than 5 explicit declaration parameters (default warning) | If configured as error |
136
+ | SWAPI002 | Explicit `Bool` parameters (default warning) | If configured as error |
137
+
138
+ The detached-task rule recognizes explicit names, including generic arguments;
139
+ it does not resolve shadowed `Task` names, aliases, or indirect calls. An
140
+ allowance can document a same-spelled unrelated type. Findings use 1-based UTF-16
141
+ columns, including Unicode and CRLF source files. Strings and comments are not
142
+ analyzed as code.
143
+
144
+ Function length starts at the `func` keyword's physical line and ends at its
145
+ body's closing brace, counting blank lines and comments. Attribute lines before
146
+ `func` are not counted. Named nested/operator functions and test functions are
147
+ included. Initializers, accessors, and closures are not measured by this rule.
148
+ File length counts physical lines, including comments and blanks, without a
149
+ phantom line after the final terminator. LF, CRLF, and lone CR are supported.
150
+ The numeric limits are project policies, not official Swift limits.
151
+
152
+ Parameter count includes functions, initializers, and subscripts, including
153
+ parameters with defaults and variadics; it excludes closure parameters and
154
+ function-type argument lists. The Bool policy matches `Bool`, `Swift.Bool`, and
155
+ attributed forms such as `inout Bool`, but not optional or collection types,
156
+ aliases, or closure parameters. Force unwraps use expression nodes and never
157
+ count IUO type declarations (`var value: String!`) or logical negation.
158
+
159
+ Kotlin functions are measured from `fun` through the end of the body, including
160
+ named nested and expression-body functions. Constructors, accessors and lambdas
161
+ are excluded. File and function limits use the same physical-line policy as Swift.
162
+ Configured tests participate only in function length, including its hard limit.
163
+
164
+ Coroutine checks recognize explicit, aliased and wildcard imports and fully
165
+ qualified names; they do not resolve local shadowing, indirect calls or type aliases.
166
+ Imports themselves are not GlobalScope usages. `!!` checks parse expression nodes,
167
+ so comments and string text do not create debt; code inside string interpolation
168
+ does. Kotlin findings also use 1-based UTF-16 columns with LF, CRLF and lone CR support.
169
+
170
+ SPM/Gradle integration plugins and server uploads are not part of this preview.
171
+
172
+ ## Baselines
173
+
174
+ ```sh
175
+ sentori lint --baseline sentori-lint-baseline.json --init-baseline
176
+ sentori lint --baseline sentori-lint-baseline.json
177
+ sentori lint --baseline sentori-lint-baseline.json --update-baseline
178
+ ```
179
+
180
+ Initialization requires a nonexistent destination and a successful full scan.
181
+ Resolve or explicitly allow concurrency escape hatches and hard-limit
182
+ violations before initializing.
183
+ Thereafter, updates can only lower existing error counts or remove resolved
184
+ entries; they cannot add files/rules or increase counts. A new file starts at
185
+ zero. Warnings do not fail the gate and are not baseline debt. Partial scans and
186
+ `--changed` cannot initialize/update baselines. Tool or parse errors never write
187
+ a baseline, and rule regressions leave it unchanged.
188
+
189
+ Length targets participate in the count ratchet. Their hard limits are checked
190
+ independently: a baselined 51-line function growing to 201 lines fails even though
191
+ its rule/file count stays at one. Hard-limit findings never enter a baseline;
192
+ only an explicit allowance with a reason can waive them. JSON findings include
193
+ `measured` and `hardLimitExceeded`; aggregate regressions distinguish `count`
194
+ from `hard-limit`. SARIF preserves these measurements in result properties.
195
+
196
+ Counts are tracked by rule and file, not diagnostic fingerprint. Replacing one
197
+ finding with another in the same rule/file bucket is therefore not detected.
198
+ Renamed files are treated as new files. Full scans can drop debt for deleted files.
199
+
200
+ The baseline records the ruleset and a configuration digest. Changing exclusions,
201
+ allowances, or severities requires an explicit policy migration: retain the old
202
+ baseline, initialize a different file under the reviewed new policy, and review
203
+ the count differences before replacing it. `--update-baseline` does not perform
204
+ that migration or accept a policy change silently. Updates use a lock and atomic
205
+ replacement. A lock left by a killed writer must be removed only after confirming
206
+ that writer has exited.
207
+
208
+ SARIF 2.1.0 output includes **all enabled findings**, including baseline debt;
209
+ a count baseline cannot truthfully mark individual findings as unchanged.
210
+ Aggregate regressions appear in run properties. Consumers should use the CLI
211
+ exit status for the ratchet gate rather than treating every SARIF error-level
212
+ finding as a new violation. JSON includes scanned files, scope completeness,
213
+ findings, allowances, regressions, and whether the baseline can decrease.
214
+
215
+ ## Moving from preview 0.1.0
216
+
217
+ The 0.2.1 analyzer and CLI use the `swift-quality-v2.1` ruleset. Pair matching
218
+ versions; a mismatched analyzer or a 0.1.0 baseline fails explicitly. Retain the
219
+ old baseline, initialize a separate 0.2.1 baseline, and review the old rule counts
220
+ as well as the new debt before adopting it. This is an explicit policy migration,
221
+ not a way to increase the old rules' allowances unnoticed. Test files now
222
+ participate in function-length checks, so the total scanned-file count increases
223
+ while the previous five rules still exclude configured tests.
224
+
225
+ Preview 0.2.1 also changes the parameter-count default from the 0.2.0 error
226
+ level to warning. Its ruleset revision rejects old baselines so this policy
227
+ change cannot silently erase error debt. Explicit `error` remains supported.
228
+
229
+
230
+ ## Moving to preview 0.3.0
231
+
232
+ Swift's analyzer protocol remains `swift-syntax-604.0.0/swift-quality-v2.1`;
233
+ Kotlin uses `kotlin-2.4.21/kotlin-quality-v1`. The CLI verifies each independently.
234
+ Reports and baselines record the combined ruleset. Retain any 0.2.1 baseline,
235
+ initialize a separate 0.3.0 baseline, and compare the Swift counts before adopting
236
+ it alongside the new Kotlin debt. An old baseline is rejected explicitly; updating
237
+ never silently migrates policies. JSON `language` is `swift`, `kotlin`, or `mixed`.
package/README.md CHANGED
@@ -7,7 +7,7 @@ machine and does not add runtime work to the application.
7
7
 
8
8
  ## Install the correct version
9
9
 
10
- This guide requires **CLI 1.8.0**. Check registry availability before updating
10
+ This guide requires **CLI 1.10.0**. Check registry availability before updating
11
11
  your build pipeline. The older 1.5.0 package has only the
12
12
  `sentori-cli` executable and does not have `init`, `doctor`, `upload all`, ZIP
13
13
  packaging or exact-file `verify`. Do not install 1.5.0 and run this guide's commands.
@@ -16,17 +16,17 @@ Use Node **18.17 or newer**; use a maintained Node release for CI. Pin the CLI a
16
16
  development dependency and commit the package lock:
17
17
 
18
18
  ```sh
19
- npm view @goliapkg/sentori-cli@1.8.0 version
20
- npm install --save-dev --save-exact @goliapkg/sentori-cli@1.8.0
19
+ npm view @goliapkg/sentori-cli@1.10.0 version
20
+ npm install --save-dev --save-exact @goliapkg/sentori-cli@1.10.0
21
21
  npx --no-install sentori --version
22
22
  ```
23
23
 
24
- The last command must print `sentori 1.8.0`. If npm cannot find that version, use
24
+ The last command must print `sentori 1.10.0`. If npm cannot find that version, use
25
25
  the tested package supplied by the Sentori team, without assuming `latest` has
26
26
  these features:
27
27
 
28
28
  ```sh
29
- npm install --save-dev --save-exact /absolute/path/goliapkg-sentori-cli-1.8.0.tgz
29
+ npm install --save-dev --save-exact /absolute/path/goliapkg-sentori-cli-1.10.0.tgz
30
30
  npx --no-install sentori --version
31
31
  ```
32
32
 
@@ -49,7 +49,7 @@ package if the local executable is missing. The server maintenance binary
49
49
 
50
50
  | Value | Meaning |
51
51
  | --- | --- |
52
- | CLI `1.8.0` | Build-tool package version, shown by `sentori --version` |
52
+ | CLI `1.10.0` | Build-tool package version, shown by `sentori --version` |
53
53
  | Server version | Running server binary, reported by `sentori doctor` |
54
54
  | SDK version | Independently versioned app library; it need not equal the CLI or server |
55
55
  | Config `version: 1` | Upload configuration schema, not a product version |
@@ -277,7 +277,7 @@ Native tooling. A Metro map alone does not resolve Hermes bytecode offsets.
277
277
 
278
278
  | Result | Action |
279
279
  | --- | --- |
280
- | No `sentori` binary / unknown `init` | Check the installed package version; use 1.8.0 or its supplied tarball, with `npx --no-install` |
280
+ | No `sentori` binary / unknown `init` | Check the installed package version; use 1.10.0 or its supplied tarball, with `npx --no-install` |
281
281
  | `doctor` rejects ZIP capability | Upgrade/deploy the compatible server; CLI installation cannot enable server support |
282
282
  | 401 / `admin_token_required` | Use an API-scope token for the correct project and instance |
283
283
  | Local file missing | Build first; resolve paths relative to the configuration file |
@@ -302,7 +302,7 @@ an API token into browser or mobile bundles.
302
302
  export SENTORI_API_TOKEN=st_...
303
303
  npx --no-install sentori project show --json
304
304
  npx --no-install sentori app list --json
305
- npx --no-install sentori issue list --app <app-uuid> --environment production --json
305
+ npx --no-install sentori issue list --app <app-uuid> --environment production --json --limit 50 --offset 0
306
306
  ```
307
307
 
308
308
  These project/app commands require the CLI release shipped with server 5.1.17;
@@ -349,3 +349,60 @@ or `runs --id` returns zero for passed, one for site failed and two for monitori
349
349
  errors, unfinished/cancelled runs or transport errors. `--wait` requires `--app`
350
350
  and waits up to 330 seconds. API and invalid-command errors always return two.
351
351
  The configuration contract is documented in [the protocol](../../docs/protocol.md#web-monitoring).
352
+
353
+ Issue lists use offset pagination: `--limit` accepts 1–200 (default 50), and
354
+ `--offset` is a non-negative integer (default 0). JSON output includes `issues`,
355
+ `total`, `limit`, `offset`, and `nextOffset`; request the returned `nextOffset`
356
+ until it is null. These metadata fields and offset support require a server
357
+ with API pagination support. Ordering reflects live impact, so rows can move
358
+ between pages while new events arrive; this is not a snapshot export.
359
+
360
+ ## Issue automation (MCP)
361
+
362
+
363
+ ```sh
364
+ npx --no-install sentori mcp serve --api-url "$SENTORI_API_URL" --token "$SENTORI_TOKEN"
365
+ ```
366
+
367
+ Then point a MCP-compatible agent (Claude Code config / Cursor / etc.)
368
+ at the spawned stdio server. Use a project `api`-scope token; an SDK ingest
369
+ token cannot read issues.
370
+
371
+ - `sentori_issue_watch`: durable changes with resumable `cursor`, bounded
372
+ `limit` and long polling through `waitSeconds` (0–25).
373
+ - `sentori_issue_read`: structured evidence, including missing context,
374
+ activity and authenticated attachment links.
375
+ - `sentori_issue_bundle`: preserves its Markdown default; also accepts
376
+ `format: "json"`.
377
+ - `sentori_issue_attachment`: read screenshot images or text/JSON diagnostics
378
+ from the bundle, with a 1 MiB inline limit.
379
+ - `sentori_issue_list`, `sentori_issue_note`, `sentori_issue_resolve`: ranked
380
+ triage and investigation/fix writeback.
381
+
382
+ Persist `nextCursor` only after durably enqueueing the page. An empty page
383
+ is a heartbeat; keep watching. Deduplicate replayed changes by cursor and
384
+ fetch the current bundle before acting. Watch is a change feed, not an
385
+ exclusive work queue. JSON results include MCP `structuredContent`.
386
+
387
+ The same interfaces work directly over HTTP with bearer authentication,
388
+ without MCP or a browser. See the [machine consumption contract](../../docs/protocol.md#machine-issue-consumption-read-and-watch)
389
+ for replay, errors, evidence limits, and the consumer checkpoint rules.
390
+
391
+ → Full reference: `sentori-cli --help`
392
+ → Recipes: [sentori.golia.jp/docs/recipes/sourcemap-upload](https://sentori.golia.jp/docs/recipes/sourcemap-upload)
393
+
394
+
395
+ `sentori verify` and the verification after `sentori upload all` require server
396
+ 5.3.0 or newer. They request `GET /v1/releases/{release}/artifacts?verify=true`,
397
+ which rereads stored blobs, compares their SHA256 and byte size, and repeats
398
+ format validation. Inventory metadata alone cannot pass verification. Missing,
399
+ truncated or changed blobs fail verification; rerunning the upload repairs a
400
+ corrupt blob from the original bytes. Verification describes storage at the
401
+ moment of the check and does not prove that an event matches this build.
402
+
403
+ ## Local Swift and Kotlin quality checks
404
+
405
+ `sentori lint` checks safety, concurrency and structure with standalone SwiftSyntax
406
+ and Kotlin PSI analyzers, a decreasing baseline, and JSON/SARIF reports. See
407
+ [LINT.md](LINT.md) for installation, rule metadata, configuration, and limitations.
408
+ Lint runs locally and does not need a server or API token.
@@ -0,0 +1,32 @@
1
+ version = 1
2
+ include = ["insight-ios/**", "insight-android/**", "insight-common-ios/**", "insight-common-android/**"]
3
+ exclude = ["insight-consumer-*/**"]
4
+ # Test files participate in function-length checks only.
5
+ tests = ["insight-common-ios/Tests/**", "**/*Tests.swift", "**/*Test.swift", "**/src/test/**", "**/src/androidTest/**", "insight-common-android/contract/src/**"]
6
+ # Case-insensitive regular expressions matched against repository-relative paths.
7
+ excludePathRegex = ["generated"]
8
+
9
+ [rules]
10
+ SWSAFE001 = "error"
11
+ SWSAFE002 = "error"
12
+ SWCON001 = "error"
13
+ SWCON002 = "error"
14
+ SWCON003 = "error"
15
+ SWMAINT001 = "error"
16
+ SWMAINT002 = "error"
17
+ SWSAFE003 = "error"
18
+ SWAPI001 = "warn"
19
+ SWAPI002 = "warn"
20
+ KTSAFE001 = "error"
21
+ KTCON001 = "error"
22
+ KTCON002 = "error"
23
+ KTMAINT001 = "error"
24
+ KTMAINT002 = "error"
25
+ KTAPI001 = "warn"
26
+ KTAPI002 = "warn"
27
+ AKS001 = "warn"
28
+ AKS002 = "warn"
29
+ AKS003 = "warn"
30
+
31
+ # Allowances use [[allow]] entries with rule, path, and a nonempty reason.
32
+ # Add only reviewed exceptions. No source comment can suppress a finding.
@@ -10,6 +10,12 @@ export type ArtifactRow = {
10
10
  * the check existed — "never looked at", not "looked at and
11
11
  * fine". `false` means it stored and will symbolicate nothing. */
12
12
  usable?: boolean | null;
13
+ storageVerification?: {
14
+ integrity: string;
15
+ actualContentHash?: string;
16
+ actualSizeBytes?: number;
17
+ usable?: boolean | null;
18
+ };
13
19
  };
14
20
  export type ArtifactsResponse = {
15
21
  /** Additive capability advertisement. Absent on older servers. */
@@ -32,5 +38,6 @@ export declare function fetchArtifacts(opts: {
32
38
  apiUrl: string;
33
39
  release: string;
34
40
  token: string;
41
+ verify?: boolean;
35
42
  }): Promise<ArtifactsResponse>;
36
43
  //# sourceMappingURL=artifacts.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"AAqBA,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,kEAAkE;IAClE,OAAO,EAAE,IAAI,GAAG,MAAM,CAAA;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB;;uEAEmE;IACnE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;CACxB,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,kEAAkE;IAClE,aAAa,CAAC,EAAE;QACd,aAAa,EAAE,MAAM,CAAA;QACrB,gBAAgB,EAAE,MAAM,CAAA;QACxB,mBAAmB,EAAE,MAAM,EAAE,CAAA;QAC7B,YAAY,EAAE,MAAM,EAAE,CAAA;KACvB,CAAA;IACD,OAAO,EAAE,MAAM,CAAA;IACf;;sEAEkE;IAClE,KAAK,EAAE,OAAO,CAAA;IACd,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,SAAS,EAAE,WAAW,EAAE,CAAA;CACzB,CAAA;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;CACd,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAkB7B"}
1
+ {"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"AAqBA,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,kEAAkE;IAClE,OAAO,EAAE,IAAI,GAAG,MAAM,CAAA;IACtB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB;;uEAEmE;IACnE,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;IACvB,mBAAmB,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAAC,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAA;CAC3H,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG;IAC9B,kEAAkE;IAClE,aAAa,CAAC,EAAE;QACd,aAAa,EAAE,MAAM,CAAA;QACrB,gBAAgB,EAAE,MAAM,CAAA;QACxB,mBAAmB,EAAE,MAAM,EAAE,CAAA;QAC7B,YAAY,EAAE,MAAM,EAAE,CAAA;KACvB,CAAA;IACD,OAAO,EAAE,MAAM,CAAA;IACf;;sEAEkE;IAClE,KAAK,EAAE,OAAO,CAAA;IACd,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,SAAS,EAAE,WAAW,EAAE,CAAA;CACzB,CAAA;AAED,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAkB7B"}
package/lib/artifacts.js CHANGED
@@ -20,7 +20,7 @@
20
20
  // readable. It does not re-group them.
21
21
  export async function fetchArtifacts(opts) {
22
22
  const url = `${opts.apiUrl.replace(/\/+$/, '')}/v1/releases/` +
23
- `${encodeURIComponent(opts.release)}/artifacts`;
23
+ `${encodeURIComponent(opts.release)}/artifacts${opts.verify ? "?verify=true" : ""}`;
24
24
  const resp = await fetch(url, {
25
25
  headers: { Authorization: `Bearer ${opts.token}` },
26
26
  });
@@ -1 +1 @@
1
- {"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,EAAE;AACF,qEAAqE;AACrE,iEAAiE;AACjE,iEAAiE;AACjE,+DAA+D;AAC/D,sEAAsE;AACtE,oEAAoE;AACpE,kCAAkC;AAClC,EAAE;AACF,iEAAiE;AACjE,kEAAkE;AAClE,mEAAmE;AACnE,mEAAmE;AACnE,6CAA6C;AAC7C,EAAE;AACF,gEAAgE;AAChE,+DAA+D;AAC/D,4DAA4D;AAC5D,uCAAuC;AAkCvC,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAIpC;IACC,MAAM,GAAG,GACP,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,eAAe;QACjD,GAAG,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAA;IACjD,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC5B,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,IAAI,CAAC,KAAK,EAAE,EAAE;KACnD,CAAC,CAAA;IACF,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;QAChD,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CACjF,CAAA;IACH,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAuB,CAAA;IACrD,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;QACxH,MAAM,IAAI,KAAK,CAAC,0GAA0G,CAAC,CAAA;IAC7H,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC"}
1
+ {"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../src/artifacts.ts"],"names":[],"mappings":"AAAA,iDAAiD;AACjD,EAAE;AACF,qEAAqE;AACrE,iEAAiE;AACjE,iEAAiE;AACjE,+DAA+D;AAC/D,sEAAsE;AACtE,oEAAoE;AACpE,kCAAkC;AAClC,EAAE;AACF,iEAAiE;AACjE,kEAAkE;AAClE,mEAAmE;AACnE,mEAAmE;AACnE,6CAA6C;AAC7C,EAAE;AACF,gEAAgE;AAChE,+DAA+D;AAC/D,4DAA4D;AAC5D,uCAAuC;AAmCvC,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAKpC;IACC,MAAM,GAAG,GACP,GAAG,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,eAAe;QACjD,GAAG,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;IACrF,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAC5B,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,IAAI,CAAC,KAAK,EAAE,EAAE;KACnD,CAAC,CAAA;IACF,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAA;QAChD,MAAM,IAAI,KAAK,CACb,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CACjF,CAAA;IACH,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAuB,CAAA;IACrD,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;QACxH,MAAM,IAAI,KAAK,CAAC,0GAA0G,CAAC,CAAA;IAC7H,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC"}
@@ -7,7 +7,7 @@ export function checkCompatibility(result, sourceZip) {
7
7
  if (info === undefined) {
8
8
  if (sourceZip)
9
9
  throw new Error('This server does not advertise source ZIP support. No files were uploaded. Upgrade the Sentori server to a build advertising artifact protocol 1 and sourceBundleFormats: ["zip"], then run sentori doctor. Existing CLI 1.5 pipelines may continue using legacy JSON sources.');
10
- return 'Legacy artifact API: server capabilities are not advertised. Source maps, dSYM and mapping uploads remain available; verification still requires matching SHA-256 bytes and usable: true.';
10
+ return 'Legacy artifact API: server capabilities are not advertised. Source maps, dSYM and mapping uploads remain available; verification requires a server that supplies fresh storageVerification proof; inventory metadata alone cannot pass.';
11
11
  }
12
12
  if (!info || typeof info.serverVersion !== 'string' || info.artifactProtocol !== 1
13
13
  || !Array.isArray(info.sourceBundleFormats) || !Array.isArray(info.verification)
@@ -1 +1 @@
1
- {"version":3,"file":"compatibility.js","sourceRoot":"","sources":["../src/compatibility.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAGtC,mFAAmF;AACnF,MAAM,CAAC,MAAM,WAAW,GAAW,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAA;AAExH,mFAAmF;AACnF,MAAM,UAAU,kBAAkB,CAAC,MAAyB,EAAE,SAAkB;IAC9E,MAAM,IAAI,GAAG,MAAM,CAAC,aAAa,CAAA;IACjC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,IAAI,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,gRAAgR,CAAC,CAAA;QAChT,OAAO,2LAA2L,CAAA;IACpM,CAAC;IACD,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,aAAa,KAAK,QAAQ,IAAI,IAAI,CAAC,gBAAgB,KAAK,CAAC;WAC7E,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC;WAC7E,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpF,MAAM,IAAI,KAAK,CAAC,0LAA0L,CAAC,CAAA;IAC7M,CAAC;IACD,IAAI,SAAS,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,IAAI,CAAC,aAAa,qGAAqG,CAAC,CAAA;IACtN,OAAO,eAAe,WAAW,YAAY,IAAI,CAAC,aAAa,qCAAqC,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,GAAG,CAAA;AAC/K,CAAC"}
1
+ {"version":3,"file":"compatibility.js","sourceRoot":"","sources":["../src/compatibility.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAGtC,mFAAmF;AACnF,MAAM,CAAC,MAAM,WAAW,GAAW,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAA;AAExH,mFAAmF;AACnF,MAAM,UAAU,kBAAkB,CAAC,MAAyB,EAAE,SAAkB;IAC9E,MAAM,IAAI,GAAG,MAAM,CAAC,aAAa,CAAA;IACjC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,IAAI,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,gRAAgR,CAAC,CAAA;QAChT,OAAO,0OAA0O,CAAA;IACnP,CAAC;IACD,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,aAAa,KAAK,QAAQ,IAAI,IAAI,CAAC,gBAAgB,KAAK,CAAC;WAC7E,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC;WAC7E,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpF,MAAM,IAAI,KAAK,CAAC,0LAA0L,CAAC,CAAA;IAC7M,CAAC;IACD,IAAI,SAAS,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kBAAkB,IAAI,CAAC,aAAa,qGAAqG,CAAC,CAAA;IACtN,OAAO,eAAe,WAAW,YAAY,IAAI,CAAC,aAAa,qCAAqC,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,GAAG,CAAA;AAC/K,CAAC"}
package/lib/index.js CHANGED
@@ -4,7 +4,7 @@ import { parseArgs } from 'node:util';
4
4
  import { runWorkflow, runSourcesPack, WORKFLOW_HELP } from './workflow.js';
5
5
  import { checkCompatibility, CLI_VERSION } from './compatibility.js';
6
6
  import { listApps, showProject } from './apps.js';
7
- import { fetchBundle, formatIssueLine, listIssues, noteIssue, resolveIssue, } from './issue.js';
7
+ import { fetchBundle, formatIssueLine, listIssuesPage, noteIssue, resolveIssue, } from './issue.js';
8
8
  import { isStrict, lenientFail, stripStrict } from './lenient.js';
9
9
  import { runMcpServer } from './mcp.js';
10
10
  import { uploadDsym, uploadMapping } from './native-artifacts.js';
@@ -34,6 +34,9 @@ Legacy kind-presence gate (use verify for exact planned files):
34
34
  catches the case a local "we ran the upload" ledger cannot:
35
35
  the upload step that stopped being called at all.
36
36
 
37
+ Local Swift and Kotlin quality gate (fails on regressions or tool errors):
38
+ sentori lint [paths...] --help
39
+
37
40
  Regression tripwires (design: probes):
38
41
  sentori probes sync --release <r> --token <t> [--dir .]
39
42
  Statically scans source for sentori.probe('REF') call sites and
@@ -43,7 +46,7 @@ CI triage (the same /api surface an AI agent uses):
43
46
  sentori project show [--json] project selected by the API token
44
47
  sentori app list [--json] apps within that project
45
48
  sentori issue list [--status open] [--kind error] [--app <uuid>]
46
- [--platform <platform>] [--environment <env>] [--release <release>] [--json]
49
+ [--platform <platform>] [--environment <env>] [--release <release>] [--limit 50] [--offset 0] [--json]
47
50
  sentori issue resolve <issue-id> [--in-release <r>]
48
51
  sentori issue note <issue-id> --body "fixed in abc123"
49
52
  sentori issue bundle <issue-id>
@@ -454,7 +457,7 @@ async function cmdIssueList(argv) {
454
457
  try {
455
458
  parsed = parseArgs({
456
459
  args: argv,
457
- options: { ...ISSUE_OPTS, kind: { type: 'string' }, status: { type: 'string' }, app: { type: 'string' }, platform: { type: 'string' }, environment: { type: 'string' }, release: { type: 'string' }, json: { type: 'boolean' } },
460
+ options: { ...ISSUE_OPTS, kind: { type: 'string' }, status: { type: 'string' }, app: { type: 'string' }, platform: { type: 'string' }, environment: { type: 'string' }, release: { type: 'string' }, limit: { type: 'string' }, offset: { type: 'string' }, json: { type: 'boolean' } },
458
461
  });
459
462
  }
460
463
  catch (e) {
@@ -469,18 +472,21 @@ async function cmdIssueList(argv) {
469
472
  if (!cfg)
470
473
  return 2;
471
474
  try {
472
- const rows = await listIssues(cfg, {
475
+ const page = await listIssuesPage(cfg, {
473
476
  kind: parsed.values.kind,
474
477
  status: parsed.values.status ?? 'open',
475
478
  app: parsed.values.app,
476
479
  platform: parsed.values.platform,
477
480
  environment: parsed.values.environment,
478
481
  release: parsed.values.release,
482
+ limit: parsed.values.limit === undefined ? undefined : Number(parsed.values.limit),
483
+ offset: parsed.values.offset === undefined ? undefined : Number(parsed.values.offset),
479
484
  });
480
485
  if (parsed.values.json) {
481
- console.log(JSON.stringify({ issues: rows }, null, 2));
486
+ console.log(JSON.stringify(page, null, 2));
482
487
  return 0;
483
488
  }
489
+ const rows = page.issues;
484
490
  if (rows.length === 0) {
485
491
  console.log('(no matching issues)');
486
492
  return 0;
@@ -710,6 +716,8 @@ async function main(argv) {
710
716
  console.log(HELP);
711
717
  return 0;
712
718
  }
719
+ if (argv[0] === 'lint')
720
+ return (await import('./lint/index.js')).cmdLint(argv.slice(1));
713
721
  const [a, b, ...rest] = argv;
714
722
  if (a === 'web')
715
723
  return runWeb(argv.slice(1));