@sunstack/cli 0.1.0 → 0.2.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,20 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0
4
+
5
+ This pre-1.0 minor release cuts over the lint configuration and rulebook format.
6
+
7
+ - Replace lint's `rule_sets` configuration with marked Markdown `rulebook` pairs,
8
+ checked-file globs, an ast-grep evidence project and `decision_model`. Rule
9
+ entries run explicit mechanical and model decision steps on evidence pieces;
10
+ asking rules always require a configured model and credential.
11
+ - Add `sunstack lint --check` for credential-free admission and rule listing,
12
+ and `--evidence [KIND]` for inspecting picked evidence without model requests.
13
+ `--init` writes a generic configuration skeleton.
14
+ - Report uncertain decisions with their step, sent state and answers. Exit 5
15
+ means uncertainty when cancellation, incomplete work or failing findings do
16
+ not take precedence.
17
+ - Remove native RuleSet `rule_sources`, `marker_prefix` and `max_question_words`.
18
+ Native JSONL `rule_paths` and conversation operations remain supported. Existing
19
+ lint configurations must migrate to the [current configuration](README.md#repository-lint);
20
+ removed keys are refused with replacement guidance.
package/README.md CHANGED
@@ -1,4 +1,87 @@
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 as JSON. It reads no checked files and sends no model
52
+ requests. `--evidence [KIND]` prints picked evidence and its fields as JSON without
53
+ asking the model. Both routes work without a credential.
54
+
55
+ An ordinary lint run uses changed evidence by default and requires Git for that
56
+ default scope. Use `--all` for every configured target, `--since REV` for changes
57
+ since a revision including worktree edits, or name files or directories for full
58
+ scope within those paths. Findings retain their rule, marker and source
59
+ location. Available output formats are `default`, `json`, `github` and `sarif`.
60
+ The JSON report includes `results`, one entry per evaluated rule/piece pair in
61
+ logical report order. Each entry has the rule's `name` and marker, the piece's
62
+ file and first and last line, its outcome (`pass`, `finding`, `not_applicable`,
63
+ `uncertain` or `incomplete`) and its ending `step` (null before any step ran).
64
+
65
+ The decision model is always configured and enabled for asking rules; there is
66
+ no model-off switch. Ordinary runs with asking rules require an OpenRouter
67
+ credential before checked-file acquisition, even if a later mechanical branch
68
+ would avoid an ask. Mechanical-only rules need no key. Credential lookup and
69
+ saving are described below under the native protocol.
70
+
71
+ Lint exits 0 for clean or nothing selected, 1 for error findings or warnings over
72
+ `--max-warnings`, 2 for invalid input, 3 for incomplete work, 4 for cancellation,
73
+ and 5 for uncertain decisions. Uncertainty retains the decision step, sent state
74
+ and answers for the caller to inspect. Cancellation takes precedence over
75
+ incomplete work, then findings, then uncertainty. A warning alone does not fail
76
+ unless it exceeds `--max-warnings`.
77
+
78
+ See the checked-in [worked configuration](tests/fixtures/rulebook-lint/skill-rules.config.json)
79
+ and its [rulebook entries](tests/fixtures/rulebook-lint/rulebook/steering.rules.json)
80
+ for a complete test example. Copy the fixture directory to an isolated root to
81
+ try it; its checked-file globs are relative to that root. The example's
82
+ skill-specific rules are test content.
83
+
84
+ ## Native JSON protocol
2
85
 
3
86
  Run `sunstack exec` (or `cargo run -p sunstack -- exec` from the repository root).
4
87
  One process consumes one JSON request on stdin and writes one JSON response on
@@ -6,7 +89,7 @@ stdout. Relative input paths resolve from the process working directory. Run
6
89
  from the repository root when using the reviewed examples.
7
90
 
8
91
  ```json
9
- {"protocol_version":1,"operation":"evaluate_rules","input":{}}
92
+ { "protocol_version": 1, "operation": "evaluate_rules", "input": {} }
10
93
  ```
11
94
 
12
95
  Replace `input` with the native operation input. Supported operations are
@@ -29,7 +112,7 @@ package bindings. A relative repository root resolves from this process's CWD.
29
112
  For example, select current unstaged changes on a repository binding with:
30
113
 
31
114
  ```json
32
- {"change_scope":{"kind":"uncommitted","layers":["unstaged"]}}
115
+ { "change_scope": { "kind": "uncommitted", "layers": ["unstaged"] } }
33
116
  ```
34
117
 
35
118
  This is a binding fragment, not a complete invocation. ChangeSet selectors need
@@ -45,8 +128,22 @@ Preparing rules sends no model request. A waiting gate leaves target comparison
45
128
  pending; evaluation resolves it only when the native gate permits the work.
46
129
  The CLI forwards these native outcomes and uses the same exit meanings above.
47
130
 
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
131
+ The binary uses a non-empty `OPENROUTER_API_KEY` first, otherwise the OS credential
132
+ store (`sunstack` / `openrouter-api-key`): macOS login Keychain or Linux Secret
133
+ Service. When the environment key is present, a run never reads or writes the store.
134
+ Runs never save credentials automatically. Use `sunstack credentials save` to save
135
+ the current environment key explicitly; it is listed in `sunstack --help`.
136
+ The command reports its destination without printing the key and refuses a missing
137
+ or empty variable. Set
138
+ `SUNSTACK_CREDENTIAL_STORE=off` to disable both lookup and saving (for CI or a one-off
139
+ key). Library callers pass the key as a value to `sunstack_lint::run`.
140
+ Without a terminal on stdin, macOS access that needs Keychain approval is treated
141
+ as unavailable immediately. Run once from a terminal to approve a changed build,
142
+ or supply the environment key. Interactive terminal runs may show the Keychain dialog.
143
+ On Linux, a locked Secret Service collection may prompt during fallback lookup or explicit saving;
144
+ use `SUNSTACK_CREDENTIAL_STORE=off` for unattended runs.
145
+
146
+ Credentials never come from stdin. Rule preparation uses the selected backend for encoding and accounting
50
147
  when model work is reachable; it sends no provider request. Local-only rule
51
148
  preparation and evaluation construct no backend and need no credential. A
52
149
  model-based judgment, applicability gate or Router still requires valid backend
@@ -63,41 +160,20 @@ It accepts no backend, attempt or request-budget settings. A clean check exits 0
63
160
  refusals exit 2 with definition file, line and column. Printed rules are returned
64
161
  only after a clean check and include each native definition and its source.
65
162
 
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.
163
+ Native RuleSets load `rule_paths` JSONL files, with paths relative to the RuleSet
164
+ file. Use the repository lint route above for marked Markdown rulebooks. Native
165
+ preparation and evaluation retain their coverage, rule evaluations, skipped rules
166
+ and counters. JSONL, RuleSet-header and invocation errors remain strict refusals.
96
167
 
97
168
  Document targets can select Markdown units independently:
98
169
 
99
170
  ```json
100
- {"kind":"markdown_units","mode":"call_site","units":["paragraph","list_item"],"pattern":"\\bMUST\\s+load\\b"}
171
+ {
172
+ "kind": "markdown_units",
173
+ "mode": "call_site",
174
+ "units": ["paragraph", "list_item"],
175
+ "pattern": "\\bMUST\\s+load\\b"
176
+ }
101
177
  ```
102
178
 
103
179
  `mode: unit` produces one occurrence per requested section, paragraph or list
@@ -126,24 +202,48 @@ The evidence library also supports `frontmatter_field` with a literal top-level
126
202
  field. Missing or malformed blocks yield explicit unavailable data without an
127
203
  invented span. Noul and composite rules reject this mechanical-only target.
128
204
 
129
- A rules entry can replace its judgment threshold with one local check:
205
+ A rules entry can use a native mechanical verdict. Put the check under
206
+ `verdict.check` and select the checked field in the evidence declaration:
130
207
 
131
208
  ```json
132
- {"name":"skill.description","target":{"input":"skill","select":{"kind":"frontmatter_field","field":"description"}},"expect":{"starts_with":"Use when"},"severity":"error"}
209
+ {
210
+ "rule_id": "skill.description",
211
+ "evidence": [
212
+ {
213
+ "id": "description",
214
+ "input": "subject",
215
+ "requirement": "required",
216
+ "select": { "kind": "frontmatter_field", "field": "description" }
217
+ }
218
+ ],
219
+ "target": "description",
220
+ "applicability": [],
221
+ "questions": {},
222
+ "verdict": {
223
+ "kind": "mechanical",
224
+ "explanation": "Description names when to use the skill.",
225
+ "check": { "kind": "expect", "expect": { "starts_with": "Use when" } }
226
+ },
227
+ "severity": "error"
228
+ }
133
229
  ```
134
230
 
135
- `expect` accepts exactly one of `matches`, `not_matches`, `starts_with` or
231
+ Save each native definition as one JSONL line in a RuleSet's `rule_paths`, and
232
+ declare its `subject` input as a document in the RuleSet header. Bind that input
233
+ to the document when calling `check_rules` or `evaluate_rules`.
234
+
235
+ `verdict.check.expect` accepts exactly one of `matches`, `not_matches`, `starts_with` or
136
236
  `present: true`. Text predicates use the unit's rendered-visible text, excluding
137
237
  inline code. A frontmatter string uses its decoded text; other values use
138
238
  canonical JSON text. Null is present. Every predicate fails on an absent field;
139
239
  a missing or malformed header is unresolved.
140
240
 
241
+ For an AST check, use `verdict.check: {"kind":"ast_grep","ast_grep":{...}}`.
141
242
  `ast_grep` accepts a native rule object validated by ast-grep-config 0.51.0.
142
243
  It sees the full original Markdown syntax tree, including ancestors outside
143
244
  the unit. A match fully contained in the target unit is a failure; sibling
144
245
  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.
246
+ kinds do not select the metadata block. Local checks send no model request.
147
247
 
148
248
  Reports use `checked` occurrences with `completed` pass/fail, `unresolved` or
149
249
  `cancelled` local outcomes. A failed check retains its declared severity and
@@ -151,12 +251,6 @@ can complete with exit 0. Unresolved checks make the report incomplete. Local
151
251
  checks create no model answers, references, receipts or confidence and consume
152
252
  no model question/request budget. Applicability and routing still govern them.
153
253
 
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
254
  Rust callers supply `RuleBackendFactory` in `RuleExecutionContext`. Selection
161
255
  is credential-free; the invocation calls `prepare` once when its first model
162
256
  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.2.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.2.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.2.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.2.0",
92
92
  "volta": {
93
93
  "node": "18.14.1",
94
94
  "npm": "9.5.0"