@sunstack/cli 0.1.0 → 0.3.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,34 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0
4
+
5
+ Release date: 2026-10-30.
6
+
7
+ - Add noul-based evidence selection, kind containment, kind-based field sources and
8
+ strict-child remainder exclusions. Selection and rules share model-request limits.
9
+ - Report every asked candidate and per-file/per-kind counts, including files with
10
+ no candidates. Selected kinds require credentials for `--evidence`; `--check`
11
+ remains credential-free. Uncertain and incomplete selection affect run status.
12
+ - Recognize numbered and Step section headings at H2 through H6, excluding H1.
13
+ Configurations using those generic utilities can gain evidence within the new
14
+ sections and changed fields or results that read them.
15
+ - Run the fresh-install smoke in TypeScript with a current rulebook fixture.
16
+
17
+ ## 0.2.0
18
+
19
+ This pre-1.0 minor release cuts over the lint configuration and rulebook format.
20
+
21
+ - Replace lint's `rule_sets` configuration with marked Markdown `rulebook` pairs,
22
+ checked-file globs, an ast-grep evidence project and `decision_model`. Rule
23
+ entries run explicit mechanical and model decision steps on evidence pieces;
24
+ asking rules always require a configured model and credential.
25
+ - Add `sunstack lint --check` for credential-free admission and rule listing,
26
+ and `--evidence [KIND]` for inspecting picked evidence without model requests.
27
+ `--init` writes a generic configuration skeleton.
28
+ - Report uncertain decisions with their step, sent state and answers. Exit 5
29
+ means uncertainty when cancellation, incomplete work or failing findings do
30
+ not take precedence.
31
+ - Remove native RuleSet `rule_sources`, `marker_prefix` and `max_question_words`.
32
+ Native JSONL `rule_paths` and conversation operations remain supported. Existing
33
+ lint configurations must migrate to the [current configuration](README.md#repository-lint);
34
+ removed keys are refused with replacement guidance.
package/README.md CHANGED
@@ -1,4 +1,177 @@
1
- # sunstack exec
1
+ # sunstack
2
+
3
+ Lint repository documents with a marked rulebook, ast-grep evidence and explicit
4
+ decision steps. Use `sunstack exec` for the native JSON protocol.
5
+
6
+ ## Repository lint
7
+
8
+ Create a configuration skeleton with `sunstack lint --init`, then add the
9
+ referenced Markdown rulebook, JSON rule entries and ast-grep project. Init writes
10
+ only `sunstack.lint.json` and refuses to overwrite an existing file.
11
+
12
+ ```json
13
+ {
14
+ "rulebook": [{ "markdown": "rulebook.md", "rules": "rulebook.rules.json" }],
15
+ "marker_prefix": "K",
16
+ "check": { "files": ["docs/**/*.md"], "ignore": ["docs/archive/**"] },
17
+ "evidence": {
18
+ "ast_grep": "evidence/sgconfig.yml",
19
+ "kinds": {
20
+ "piece": {
21
+ "rule": "your-rule-id",
22
+ "fields": { "body": { "from": "remainder" } }
23
+ }
24
+ }
25
+ },
26
+ "decision_model": { "model": "jev-latest" }
27
+ }
28
+ ```
29
+
30
+ Replace `your-rule-id` with a rule in your ast-grep project. An end-of-line marker
31
+ such as `<!-- K001 -->` binds a Markdown standard to its `K001` JSON entry.
32
+ Each entry names an evidence kind and a decision graph. Steps can check fields
33
+ mechanically, ask authored questions, combine answers and route to pass or a
34
+ finding. Questions reach the model as authored, with one piece's bounded field
35
+ view. The binary embeds six generic Markdown utilities; rulebooks, questions and
36
+ domain-specific evidence rules belong to the caller.
37
+
38
+ Rulebook and ast-grep project paths resolve relative to the configuration file.
39
+ Checked-file globs resolve from its Git repository root, or from the config
40
+ directory outside Git. Without `--config`, lint searches the current directory
41
+ and its parents for `sunstack.lint.json`.
42
+
43
+ ```sh
44
+ sunstack lint --config sunstack.lint.json --check
45
+ sunstack lint --config sunstack.lint.json --all --evidence
46
+ sunstack lint --config sunstack.lint.json --all --evidence piece
47
+ sunstack lint --config sunstack.lint.json --all --format json
48
+ ```
49
+
50
+ `--check` validates configuration, the rulebook and evidence project, then lists
51
+ the assembled rules and selected/contained kinds as JSON. It reads no checked
52
+ files, sends no model requests and needs no credential. `--evidence [KIND]` prints
53
+ picked pieces, selection outcomes and file counts as JSON. It runs evidence
54
+ selection when configured, requiring a credential, but sends no rule questions.
55
+ Without selected kinds, the evidence listing remains credential-free.
56
+
57
+ An ordinary lint run uses changed evidence by default and requires Git for that
58
+ default scope. Use `--all` for every configured target, `--since REV` for changes
59
+ since a revision including worktree edits, or name files or directories for full
60
+ scope within those paths. Findings retain their rule, marker and source
61
+ location. Available output formats are `default`, `json`, `github` and `sarif`.
62
+ The JSON report includes `results`, one entry per evaluated rule/piece pair in
63
+ logical report order. Each entry has the rule's `name` and marker, the piece's
64
+ file and first and last line, its outcome (`pass`, `finding`, `not_applicable`,
65
+ `uncertain` or `incomplete`) and its ending `step` (null before any step ran).
66
+
67
+ The decision model is always configured and enabled for asking rules; there is
68
+ no model-off switch. Ordinary runs with asking rules require an OpenRouter
69
+ credential before checked-file acquisition, even if a later mechanical branch
70
+ would avoid an ask. A selected evidence kind also requires a credential, including
71
+ for `--evidence`, even when the rules are mechanical. With neither selected kinds
72
+ nor asking rules, no key is needed. Credential lookup and
73
+ saving are described below under the native protocol.
74
+
75
+ Lint exits 0 for clean or nothing selected, 1 for error findings or warnings over
76
+ `--max-warnings`, 2 for invalid input, 3 for incomplete work, 4 for cancellation,
77
+ and 5 for uncertain decisions. Uncertainty retains the decision step, sent state
78
+ and answers for the caller to inspect. Cancellation takes precedence over
79
+ incomplete work, then findings, then uncertainty. A warning alone does not fail
80
+ unless it exceeds `--max-warnings`.
81
+
82
+ See the checked-in [worked configuration](tests/fixtures/rulebook-lint/skill-rules.config.json)
83
+ and its [rulebook entries](tests/fixtures/rulebook-lint/rulebook/steering.rules.json)
84
+ for a complete test example. Copy the fixture directory to an isolated root to
85
+ try it; its checked-file globs are relative to that root. The example's
86
+ skill-specific rules are test content.
87
+
88
+ ## Selected and contained evidence
89
+
90
+ Add `select` to an evidence kind to ask one noul question about each mechanical
91
+ candidate. Add `within` to keep a kind's matches only inside or equal to a final
92
+ piece of another kind in the same file. This fragment belongs in `evidence.kinds`;
93
+ the rule IDs must exist in the caller's ast-grep project:
94
+
95
+ ```json
96
+ {
97
+ "workflow_section": {
98
+ "rule": "workflow-candidate",
99
+ "fields": {
100
+ "heading": { "from": "first_line" },
101
+ "body": { "from": "remainder", "exclude": { "rule": "section" } },
102
+ "outline": { "from": "same_file", "rule": "section" }
103
+ },
104
+ "select": {
105
+ "question": {
106
+ "type": "noul",
107
+ "instructions": {
108
+ "check": "body",
109
+ "against": "heading",
110
+ "context": ["outline"],
111
+ "task": "Is this section an ordered workflow?"
112
+ }
113
+ },
114
+ "yes_at_or_above": 0.7,
115
+ "no_at_or_below": 0.3,
116
+ "shared": ["outline"]
117
+ }
118
+ },
119
+ "step": {
120
+ "rule": "step",
121
+ "within": "workflow_section",
122
+ "fields": { "body": { "from": "remainder" } }
123
+ },
124
+ "load": {
125
+ "rule": "load",
126
+ "fields": { "step": { "from": "enclosing", "kind": "step" } }
127
+ }
128
+ }
129
+ ```
130
+
131
+ Answers at or above 0.7 select the candidate; at or below 0.3 reject it; values
132
+ between them are uncertain. Only selected candidates become pieces. A failed
133
+ selection remains incomplete; it never falls back to selecting the candidate.
134
+ Kinds build in dependency order before any rules run. Cycles, undefined kinds,
135
+ invalid thresholds, non-noul selection questions and target-field references in
136
+ selection questions are refused by `--check`.
137
+
138
+ `inside`, `enclosing` and `same_file` accept exactly one of `rule` or `kind`.
139
+ A rule source reads raw matches; a kind source reads final pieces after selection
140
+ and containment. `remainder.exclude` removes only strictly contained ranges,
141
+ including child sections; an equal range never erases the piece itself. A target's
142
+ `as_kind` must remain target-free and independent of selection.
143
+
144
+ The model receives consecutive candidates of one file and kind, with one question
145
+ per candidate. Fields in `shared` must come from `same_file`; their view is bounded
146
+ once and sent once per request. Each candidate has its own bounded view without
147
+ shared or target fields. Questions cannot refer to target fields, and selection
148
+ requests carry no target opening or target-file fields. Requests split to satisfy
149
+ encoded character, byte, question-count and input-token limits. Selection and rule
150
+ requests share the run's request count, concurrency and usage accounting.
151
+
152
+ Under a change scope, selection considers all candidates of each file with a
153
+ changed hunk or a changed load target. Other files retain their candidate counts
154
+ with `asked: 0`. Final pieces are still picked by their own changed ranges or
155
+ changed targets; changing only a parent heading does not pick every child step.
156
+
157
+ JSON reports and evidence listings add `selections` and `file_counts`. Each asked
158
+ candidate reports its kind, source location, first line and outcome, with a yes
159
+ probability or an incomplete reason. Counts cover every built file and configured
160
+ kind, including zero-candidate files: matches, pieces, picked and scan-limited,
161
+ plus candidate/asked/outcome counts for selected kinds. The default format prints
162
+ selection totals and files with candidates but none selected. Selection uncertainty
163
+ uses exit 5 unless a higher-priority status applies; any incomplete candidate
164
+ makes the run incomplete with `selection_incomplete`. GitHub and SARIF formatting
165
+ is unchanged.
166
+
167
+ The generic numbered-heading and Step-heading utilities recognize levels H2
168
+ through H6; H1 titles are excluded. Existing configurations otherwise preserve
169
+ 0.2.0 behavior, apart from these extra heading matches and evidence, fields and
170
+ results derived from their sections. See the complete
171
+ [selection fixture](tests/fixtures/rulebook-lint/selection/skill-rules.config.json)
172
+ for working configuration inputs; its domain-specific question is test content.
173
+
174
+ ## Native JSON protocol
2
175
 
3
176
  Run `sunstack exec` (or `cargo run -p sunstack -- exec` from the repository root).
4
177
  One process consumes one JSON request on stdin and writes one JSON response on
@@ -6,7 +179,7 @@ stdout. Relative input paths resolve from the process working directory. Run
6
179
  from the repository root when using the reviewed examples.
7
180
 
8
181
  ```json
9
- {"protocol_version":1,"operation":"evaluate_rules","input":{}}
182
+ { "protocol_version": 1, "operation": "evaluate_rules", "input": {} }
10
183
  ```
11
184
 
12
185
  Replace `input` with the native operation input. Supported operations are
@@ -29,7 +202,7 @@ package bindings. A relative repository root resolves from this process's CWD.
29
202
  For example, select current unstaged changes on a repository binding with:
30
203
 
31
204
  ```json
32
- {"change_scope":{"kind":"uncommitted","layers":["unstaged"]}}
205
+ { "change_scope": { "kind": "uncommitted", "layers": ["unstaged"] } }
33
206
  ```
34
207
 
35
208
  This is a binding fragment, not a complete invocation. ChangeSet selectors need
@@ -45,8 +218,22 @@ Preparing rules sends no model request. A waiting gate leaves target comparison
45
218
  pending; evaluation resolves it only when the native gate permits the work.
46
219
  The CLI forwards these native outcomes and uses the same exit meanings above.
47
220
 
48
- The Jev backend reads `OPENROUTER_API_KEY` from the process environment, never
49
- stdin. Rule preparation uses the selected backend for encoding and accounting
221
+ The binary uses a non-empty `OPENROUTER_API_KEY` first, otherwise the OS credential
222
+ store (`sunstack` / `openrouter-api-key`): macOS login Keychain or Linux Secret
223
+ Service. When the environment key is present, a run never reads or writes the store.
224
+ Runs never save credentials automatically. Use `sunstack credentials save` to save
225
+ the current environment key explicitly; it is listed in `sunstack --help`.
226
+ The command reports its destination without printing the key and refuses a missing
227
+ or empty variable. Set
228
+ `SUNSTACK_CREDENTIAL_STORE=off` to disable both lookup and saving (for CI or a one-off
229
+ key). Library callers pass the key as a value to `sunstack_lint::run`.
230
+ Without a terminal on stdin, macOS access that needs Keychain approval is treated
231
+ as unavailable immediately. Run once from a terminal to approve a changed build,
232
+ or supply the environment key. Interactive terminal runs may show the Keychain dialog.
233
+ On Linux, a locked Secret Service collection may prompt during fallback lookup or explicit saving;
234
+ use `SUNSTACK_CREDENTIAL_STORE=off` for unattended runs.
235
+
236
+ Credentials never come from stdin. Rule preparation uses the selected backend for encoding and accounting
50
237
  when model work is reachable; it sends no provider request. Local-only rule
51
238
  preparation and evaluation construct no backend and need no credential. A
52
239
  model-based judgment, applicability gate or Router still requires valid backend
@@ -63,41 +250,20 @@ It accepts no backend, attempt or request-budget settings. A clean check exits 0
63
250
  refusals exit 2 with definition file, line and column. Printed rules are returned
64
251
  only after a clean check and include each native definition and its source.
65
252
 
66
- RuleSets can combine existing `rule_paths` JSONL files with
67
- `rule_sources: [{"markdown":"standards.md","rules":"standards.rules.json"}]`.
68
- Paths resolve relative to the RuleSet file. A RuleSet with sources needs a `marker_prefix`
69
- such as `K`; an end-of-line `<!-- K014 -->` binds its standard to the `K014`
70
- entry in the strict rules object. Entry `name` becomes the native rule ID;
71
- `target.input` is always explicit, and optional `context` uses native evidence
72
- declarations. The target declaration reserves ID `target`. The assembled question
73
- uses visible Markdown wording, with a default cap of 400 words configurable by
74
- `max_question_words`. Both the Markdown and rules file are definition dependencies.
75
-
76
- At runtime, invalid source entries are listed in `skipped_rules`, while valid
77
- rules continue. An unreadable or malformed source contributes one source-level
78
- skip. `rule_coverage` is `full`, `partial` or `none`; partial runs can complete
79
- with exit 0, while no valid rule yields incomplete exit 3. Measurements keep
80
- `invalid_rule_entries` and `skipped_rule_sources` separate from applicability's
81
- `rules_skipped`. Existing JSONL, RuleSet-header and invocation errors retain
82
- their strict refusal behavior.
83
-
84
- Run the complete credential-free check/print example from the repository root:
85
-
86
- ```sh
87
- scripts/cargo-debug-signing-runner.sh target/debug/sunstack exec \
88
- < docs/specs/2026-10-12-rust-rule-engine/examples/marked-standards.check.request.json
89
- ```
90
-
91
- The [check input schema](../../docs/specs/2026-10-12-rust-rule-engine/schemas/check-rules-input.schema.json)
92
- and [rules-object schema](../../docs/specs/2026-10-12-rust-rule-engine/schemas/rule-source.schema.json)
93
- describe these boundaries. Preparation and evaluation reports now require coverage,
94
- rule evaluations, skipped rules and their counters; strict report consumers must
95
- update together.
253
+ Native RuleSets load `rule_paths` JSONL files, with paths relative to the RuleSet
254
+ file. Use the repository lint route above for marked Markdown rulebooks. Native
255
+ preparation and evaluation retain their coverage, rule evaluations, skipped rules
256
+ and counters. JSONL, RuleSet-header and invocation errors remain strict refusals.
96
257
 
97
258
  Document targets can select Markdown units independently:
98
259
 
99
260
  ```json
100
- {"kind":"markdown_units","mode":"call_site","units":["paragraph","list_item"],"pattern":"\\bMUST\\s+load\\b"}
261
+ {
262
+ "kind": "markdown_units",
263
+ "mode": "call_site",
264
+ "units": ["paragraph", "list_item"],
265
+ "pattern": "\\bMUST\\s+load\\b"
266
+ }
101
267
  ```
102
268
 
103
269
  `mode: unit` produces one occurrence per requested section, paragraph or list
@@ -126,24 +292,48 @@ The evidence library also supports `frontmatter_field` with a literal top-level
126
292
  field. Missing or malformed blocks yield explicit unavailable data without an
127
293
  invented span. Noul and composite rules reject this mechanical-only target.
128
294
 
129
- A rules entry can replace its judgment threshold with one local check:
295
+ A rules entry can use a native mechanical verdict. Put the check under
296
+ `verdict.check` and select the checked field in the evidence declaration:
130
297
 
131
298
  ```json
132
- {"name":"skill.description","target":{"input":"skill","select":{"kind":"frontmatter_field","field":"description"}},"expect":{"starts_with":"Use when"},"severity":"error"}
299
+ {
300
+ "rule_id": "skill.description",
301
+ "evidence": [
302
+ {
303
+ "id": "description",
304
+ "input": "subject",
305
+ "requirement": "required",
306
+ "select": { "kind": "frontmatter_field", "field": "description" }
307
+ }
308
+ ],
309
+ "target": "description",
310
+ "applicability": [],
311
+ "questions": {},
312
+ "verdict": {
313
+ "kind": "mechanical",
314
+ "explanation": "Description names when to use the skill.",
315
+ "check": { "kind": "expect", "expect": { "starts_with": "Use when" } }
316
+ },
317
+ "severity": "error"
318
+ }
133
319
  ```
134
320
 
135
- `expect` accepts exactly one of `matches`, `not_matches`, `starts_with` or
321
+ Save each native definition as one JSONL line in a RuleSet's `rule_paths`, and
322
+ declare its `subject` input as a document in the RuleSet header. Bind that input
323
+ to the document when calling `check_rules` or `evaluate_rules`.
324
+
325
+ `verdict.check.expect` accepts exactly one of `matches`, `not_matches`, `starts_with` or
136
326
  `present: true`. Text predicates use the unit's rendered-visible text, excluding
137
327
  inline code. A frontmatter string uses its decoded text; other values use
138
328
  canonical JSON text. Null is present. Every predicate fails on an absent field;
139
329
  a missing or malformed header is unresolved.
140
330
 
331
+ For an AST check, use `verdict.check: {"kind":"ast_grep","ast_grep":{...}}`.
141
332
  `ast_grep` accepts a native rule object validated by ast-grep-config 0.51.0.
142
333
  It sees the full original Markdown syntax tree, including ancestors outside
143
334
  the unit. A match fully contained in the target unit is a failure; sibling
144
335
  matches outside it are excluded. Frontmatter fields use `expect`; runtime unit
145
- kinds do not select the metadata block. The marked standard becomes the local
146
- result's explanation and is not sent to a model.
336
+ kinds do not select the metadata block. Local checks send no model request.
147
337
 
148
338
  Reports use `checked` occurrences with `completed` pass/fail, `unresolved` or
149
339
  `cancelled` local outcomes. A failed check retains its declared severity and
@@ -151,12 +341,6 @@ can complete with exit 0. Unresolved checks make the report incomplete. Local
151
341
  checks create no model answers, references, receipts or confidence and consume
152
342
  no model question/request budget. Applicability and routing still govern them.
153
343
 
154
- The [mechanical invocation](../../docs/specs/2026-10-12-rust-rule-engine/examples/mechanical.invocation.json)
155
- checks a description and a load instruction without a credential. Wrap that
156
- native input in the usual `evaluate_rules` protocol frame. The companion
157
- [check request](../../docs/specs/2026-10-12-rust-rule-engine/examples/mechanical.check.request.json)
158
- validates and prints both standards without reading the reviewed skill.
159
-
160
344
  Rust callers supply `RuleBackendFactory` in `RuleExecutionContext`. Selection
161
345
  is credential-free; the invocation calls `prepare` once when its first model
162
346
  request reaches encoding and caches the result. Concrete built backends already
@@ -19,7 +19,7 @@
19
19
  "hasInstallScript": true,
20
20
  "license": "MIT",
21
21
  "name": "@sunstack/cli",
22
- "version": "0.1.0"
22
+ "version": "0.3.0"
23
23
  },
24
24
  "node_modules/detect-libc": {
25
25
  "engines": {
@@ -48,5 +48,5 @@
48
48
  }
49
49
  },
50
50
  "requires": true,
51
- "version": "0.1.0"
51
+ "version": "0.3.0"
52
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "artifactDownloadUrls": [
3
- "https://github.com/ShravanSunder/sunstack-tools/releases/download/v0.1.0"
3
+ "https://github.com/ShravanSunder/sunstack-tools/releases/download/v0.3.0"
4
4
  ],
5
5
  "bin": {
6
6
  "sunstack": "run-sunstack.js"
@@ -88,7 +88,7 @@
88
88
  "zipExt": ".tar.gz"
89
89
  }
90
90
  },
91
- "version": "0.1.0",
91
+ "version": "0.3.0",
92
92
  "volta": {
93
93
  "node": "18.14.1",
94
94
  "npm": "9.5.0"