triss-coworker 0.32.0 → 0.34.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 CHANGED
@@ -7,6 +7,54 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.34.0] — 2026-08-13
11
+
12
+ ### Added
13
+
14
+ - `triss exec` deterministically routes a task to `ask`, `review`, `coder run`,
15
+ or `chat`. Its JSON `--explain` mode validates the selected route without
16
+ model, Git, integration, update-check, stdin, network, or filesystem work.
17
+ - `ask` and `review` support a shared `evidence` response contract across CLI
18
+ and MCP, with explicit Outcome, Evidence, Uncertainty, and Decision sections.
19
+ - Help, completion, README, MCP documentation, and generated agent instructions
20
+ cover the new routing and evidence interfaces.
21
+
22
+ ### Changed
23
+
24
+ - Core CLI and MCP token budgets, plus coder timeouts, use strict shared
25
+ positive-number validation. Malformed and partially numeric values now fail
26
+ before corpus, Git, model, or subprocess work.
27
+ - Lexical `exec` routing is conservative: ambiguous or read-only status wording
28
+ remains chat, while only explicit review or implementation intent selects an
29
+ agentic route.
30
+
31
+ ### Fixed
32
+
33
+ - Managed `AGENTS.md` and `CLAUDE.md` initialization now plans all destinations
34
+ before mutation, rejects aliases and malformed marker layouts, preserves
35
+ unrelated bytes and file modes, installs complete files atomically, and uses
36
+ identity-aware rollback without clobbering intervening user changes.
37
+ - Clean text reviews retain their legacy message even when terminal colors are
38
+ enabled, and `exec --explain` remains side-effect free in interactive shells.
39
+
40
+ ## [0.33.0] — 2026-08-13
41
+
42
+ ### Added
43
+
44
+ - Automatic, cached stable-release notices for interactive CLI and initialized
45
+ MCP sessions, with `TRISS_UPDATE_CHECK=0` as the passive-check opt-out.
46
+ - `triss update` text/JSON status plus explicit receipt-backed standalone apply
47
+ and verified offline rollback. Package-manager, source, legacy, and unknown
48
+ installations remain read-only.
49
+ - An npm-free standalone artifact/installer with per-version integrity
50
+ inventories, journaled activation/recovery, retained-size reporting, and
51
+ guarded public Release promotion.
52
+
53
+ ### Security
54
+
55
+ - Update endpoints are fixed and strictly allowlisted, downloads/extraction are
56
+ bounded, and standalone writes require validated receipt ownership.
57
+
10
58
  ## [0.32.0] — 2026-08-12
11
59
 
12
60
  ### Added
@@ -1008,7 +1056,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1008
1056
 
1009
1057
  - Initial release of `triss-coworker`.
1010
1058
 
1011
- [Unreleased]: https://github.com/ayleen/triss-coworker/compare/v0.31.0...HEAD
1059
+ [Unreleased]: https://github.com/ayleen/triss-coworker/compare/v0.34.0...HEAD
1060
+ [0.34.0]: https://github.com/ayleen/triss-coworker/compare/v0.33.0...v0.34.0
1061
+ [0.33.0]: https://github.com/ayleen/triss-coworker/compare/v0.32.0...v0.33.0
1062
+ [0.32.0]: https://github.com/ayleen/triss-coworker/compare/v0.31.1...v0.32.0
1063
+ [0.31.1]: https://github.com/ayleen/triss-coworker/compare/v0.31.0...v0.31.1
1012
1064
  [0.31.0]: https://github.com/ayleen/triss-coworker/compare/v0.30.0...v0.31.0
1013
1065
  [0.30.0]: https://github.com/ayleen/triss-coworker/compare/v0.29.0...v0.30.0
1014
1066
  [0.29.0]: https://github.com/ayleen/triss-coworker/compare/v0.28.0...v0.29.0
package/README.md CHANGED
@@ -50,8 +50,10 @@ tokens of tracker chatter into its own context. Adding a new provider
50
50
 
51
51
  - **Node.js ≥ 22** (LTS). Check with `node --version`.
52
52
  - Don't have it? Install via [nvm](https://github.com/nvm-sh/nvm), [fnm](https://github.com/Schniz/fnm), Homebrew (`brew install node`), or [nodejs.org](https://nodejs.org/).
53
- - **npm** (ships with Node.js) — used for the `npm install -g` path. `pnpm` and `yarn` also work.
54
- - **git** — used by the bash one-liner installer to clone the repo.
53
+ - **npm** (ships with Node.js) — used only for the package-manager install path.
54
+ `pnpm` and `yarn` also work.
55
+ - The standalone bash installer and update discovery do not require npm, pnpm,
56
+ yarn, git, tar, unzip, or a checksum utility once a standalone Release exists.
55
57
  - A **DeepSeek API key** (free tier works) for the worker model: <https://platform.deepseek.com/>.
56
58
 
57
59
  Triss has no other runtime dependencies.
@@ -84,6 +86,14 @@ binary is `triss`.
84
86
  curl -fsSL https://raw.githubusercontent.com/ayleen/triss-coworker/main/install.sh | bash
85
87
  ```
86
88
 
89
+ This installs a receipt-backed standalone copy under
90
+ `~/.local/share/triss` and links `~/.local/bin/triss`. Override those paths for
91
+ the install command with `TRISS_STANDALONE_HOME` and `TRISS_BIN_DIR`.
92
+ `TRISS_HOME` remains reserved for the old git installer and is never interpreted
93
+ as standalone write authority. During the first-release transition only, a
94
+ verified missing standalone asset may use the previous git+npm path for a fresh
95
+ empty legacy target; existing legacy checkouts are never pulled or modified.
96
+
87
97
  ### Option C — from source
88
98
 
89
99
  ```bash
@@ -101,6 +111,34 @@ triss --help
101
111
  triss status
102
112
  ```
103
113
 
114
+ ## Updates
115
+
116
+ During successful interactive CLI use, Triss checks a fixed GitHub Release
117
+ endpoint at most once per cache window and prints a throttled update notice to
118
+ stderr. A due check is awaited and adds at most 1.1 seconds; a fresh cache adds
119
+ no network wait. Long-running MCP servers begin the same check only after the
120
+ MCP initialization handshake and use MCP logging plus stderr fallback.
121
+
122
+ ```bash
123
+ triss update # fresh status and guidance; no file changes
124
+ triss update --json # one machine-readable object
125
+ triss update --apply # receipt-backed standalone installs only
126
+ triss update --rollback # offline rollback to a verified prior version
127
+ ```
128
+
129
+ Apply and rollback require confirmation; add `--yes` for non-interactive
130
+ operation confirmation. Breaking a proven-stale update lock is a separate
131
+ authorization: add `--break-lock` (and also `--yes` when non-interactive).
132
+ Package-manager, source, ephemeral, legacy-git, and unknown installations remain
133
+ read-only and receive installation-specific guidance.
134
+
135
+ A valid Release that requires a newer Node is reported as available but cannot
136
+ be applied until Node is upgraded. Standalone status reports retained version
137
+ count and receipt-recorded payload bytes; automatic pruning is not performed.
138
+ Set `TRISS_UPDATE_CHECK=0` to disable passive checks and notices. Explicit
139
+ `triss update` remains available. A running MCP host must be restarted after an
140
+ update to load the new version.
141
+
104
142
  ## Configure
105
143
 
106
144
  ```bash
@@ -265,6 +303,7 @@ and writing**.
265
303
  | Command | Does | Replaces |
266
304
  | --------------- | ----------------------------------------------------- | ----------------------------------- |
267
305
  | `triss ask` | Reads files, URLs, and/or piped stdin — returns a summary | The agent reading the source itself |
306
+ | `triss exec` | Deterministically routes a task to `ask`, `review`, `coder run`, or `chat`; `--explain` prints the JSON decision without executing | Manually choosing the delegation command |
268
307
  | `triss chat` | Bare prompt to the worker model — no corpus | A separate `gpt`-style CLI |
269
308
  | `triss write` | Generates code/docs from a spec + reference file | The agent typing out boilerplate |
270
309
  | `triss extract` | Pulls readable transcript from JSONL session logs | Manually scraping `~/.claude/...` |
@@ -278,6 +317,7 @@ and writing**.
278
317
  | `triss init` | Drops a tiny (~15 line) delegation block into `CLAUDE.md` / `AGENTS.md` | Hand-writing routing rules |
279
318
  | `triss agent-help` | Prints the full delegation cookbook on demand (the nano block points here) | A 200-line CLAUDE.md that always loads |
280
319
  | `triss status` | Shows current model + key + .env sources | — |
320
+ | `triss update` | Checks for updates; explicitly applies/rolls back standalone installs | npm registry polling or silent self-update |
281
321
  | `triss config` | Interactive credential management | Manual `.env` editing |
282
322
  | `triss mcp` | Register Triss as MCP server in Claude Code | Editing `~/.claude.json` by hand |
283
323
  | `triss completion` | Shell completion script (bash/zsh) | Hand-rolled completion |
@@ -299,12 +339,54 @@ triss ask --paths "src/**/*.ts" \
299
339
  triss ask --paths "src/**/*.ts" \
300
340
  --question "Find SQL injection risks" \
301
341
  --provider kimi --model pro # Kimi K3 via MOONSHOT_API_KEY
342
+
343
+ # Optional evidence-oriented Markdown contract (text is the default)
344
+ triss ask --paths "src/**/*.ts" --question "Find SQL injection risks" \
345
+ --format evidence
302
346
  ```
303
347
 
304
348
  Typical output is a focused answer with cited file paths, not a raw file dump.
305
349
  That is the whole trick: the primary agent gets the useful bits, not a
306
350
  firehose wearing a moustache.
307
351
 
352
+ `--format evidence` appends a trusted instruction requiring the model-authored
353
+ Markdown sections `Outcome`, `Evidence`, `Uncertainty`, and `Decision required`.
354
+ It does not make Triss semantically validate the model's claims. The same
355
+ optional contract is available to `triss review` and MCP `triss_ask` /
356
+ `triss_review` as `response_format: "evidence"`; defaults remain unchanged.
357
+
358
+ ### `triss exec`
359
+
360
+ ```bash
361
+ triss exec --explain --paths README.md "summarize this project"
362
+ triss exec --explain --review "review the current branch"
363
+ triss exec --code "add a bounded validation"
364
+ ```
365
+
366
+ Repeat `--paths <path>` or `--urls <url>` when routing more than one source;
367
+ the final positional argument remains the task:
368
+
369
+ ```bash
370
+ triss exec --paths README.md --paths package.json "summarize this project"
371
+ ```
372
+
373
+ Routing is deterministic: explicit review inputs (`--pr`, `--base`,
374
+ `--review`) take precedence, then `--paths`/`--urls` for `ask`, `--code`, and
375
+ `--chat`. Without an explicit route, review/audit language selects `review`,
376
+ explicit implementation/change language selects `coder run`, and everything
377
+ else selects `chat`. Read-only status phrases such as `show build status` stay
378
+ on `chat`. Contradictory explicit signals and stdin-only routing fail
379
+ closed. Options unsupported by the selected route also fail instead of being
380
+ silently discarded. In `--explain`, such a failure is represented by
381
+ `route: null` with its reason rather than thrown. `--explain` emits
382
+ schema-versioned JSON and performs no model, Git, network, stdin, or filesystem
383
+ mutation.
384
+
385
+ `exec --max-tokens` is forwarded to ask, review, and chat. For coder routing it
386
+ is supported with `--engine crush`; the OpenCode engine has no per-run token
387
+ budget flag, so `exec --code --max-tokens ...` without Crush fails explicitly
388
+ instead of silently ignoring the requested cap.
389
+
308
390
  ### `triss write`
309
391
 
310
392
  ```bash
@@ -923,6 +1005,11 @@ touch the variables directly. The full reference (worker model,
923
1005
  integrations, tunables, security toggles) lives in
924
1006
  [docs/configuration.md](docs/configuration.md#environment-reference).
925
1007
 
1008
+ `TRISS_UPDATE_CHECK=0` disables passive update checks. Installer-only
1009
+ `TRISS_STANDALONE_HOME` and `TRISS_BIN_DIR` select standalone paths and are
1010
+ recorded in the ownership receipt; later environment changes do not grant write
1011
+ authority.
1012
+
926
1013
  `.env` files are loaded from `~/.config/triss/.env` (global) and
927
1014
  `<project-root>/.triss.env` (project, overrides global). Real `process.env`
928
1015
  always wins. See [docs/configuration.md](docs/configuration.md#recipes--common-setups-end-to-end)
@@ -1008,8 +1095,6 @@ test suite.
1008
1095
 
1009
1096
  **Planned**:
1010
1097
 
1011
- - [ ] `triss exec <task>` — auto-route a freeform task to the right
1012
- sub-command
1013
1098
  - [ ] More provider recipe blocks in the docs (Kimi, Ollama,
1014
1099
  OpenRouter — examples already present, but with terse setup steps)
1015
1100
 
package/bin/triss.js CHANGED
@@ -8,7 +8,7 @@ if (nodeMajor < 22) {
8
8
  process.exit(1);
9
9
  }
10
10
 
11
- import { Command } from 'commander';
11
+ import { Command, InvalidArgumentError } from 'commander';
12
12
  import { readFileSync } from 'node:fs';
13
13
  import pc from 'picocolors';
14
14
  import { runAsk } from '../src/commands/ask.js';
@@ -29,6 +29,8 @@ import { runInit } from '../src/commands/init.js';
29
29
  import { runAgentHelp } from '../src/commands/agent-help.js';
30
30
  import { runStatus } from '../src/commands/status.js';
31
31
  import { runCompletion } from '../src/commands/completion.js';
32
+ import { runExec } from '../src/commands/exec.js';
33
+ import { runUpdate } from '../src/commands/update.js';
32
34
  import {
33
35
  runWizard,
34
36
  runSet,
@@ -42,11 +44,38 @@ import { runCoderInit, runCoderRun, runCoderClean } from '../src/commands/coder.
42
44
  import { runCoderModels, runCoderModelSet, runCoderModelRollback } from '../src/commands/coder-models.js';
43
45
  import { loadIntegrations } from '../src/integrations/_registry.js';
44
46
  import { withCall } from '../src/call-context.js';
47
+ import { positiveIntegerOption, positiveNumberOption } from '../src/option-validation.js';
48
+ import { loadEnvFiles } from '../src/config.js';
49
+ import {
50
+ runDefaultPassiveCliCheck,
51
+ shouldSuppressPassiveCheck,
52
+ } from '../src/update/passive.js';
53
+ import { isExecExplainInvocation } from '../src/cli-argv.js';
45
54
 
46
55
  const packageJson = JSON.parse(
47
56
  readFileSync(new URL('../package.json', import.meta.url), 'utf8'),
48
57
  );
49
58
 
59
+ function collectOption(value, previous) {
60
+ return previous.concat(value);
61
+ }
62
+
63
+ function parsePositiveInteger(value) {
64
+ try {
65
+ return positiveIntegerOption(value);
66
+ } catch (error) {
67
+ throw new InvalidArgumentError(error.message);
68
+ }
69
+ }
70
+
71
+ function parsePositiveNumber(value) {
72
+ try {
73
+ return positiveNumberOption(value);
74
+ } catch (error) {
75
+ throw new InvalidArgumentError(error.message);
76
+ }
77
+ }
78
+
50
79
  const program = new Command();
51
80
  program
52
81
  .name('triss')
@@ -56,6 +85,19 @@ program
56
85
  )
57
86
  .version(packageJson.version);
58
87
 
88
+ program.hook('postAction', async () => {
89
+ loadEnvFiles();
90
+ const argv = process.argv.slice(2);
91
+ if (shouldSuppressPassiveCheck({
92
+ argv,
93
+ stderrIsTTY: Boolean(process.stderr.isTTY),
94
+ ci: /^(1|true|yes)$/i.test(process.env.CI || ''),
95
+ optOut: process.env.TRISS_UPDATE_CHECK === '0',
96
+ commandFailed: Boolean(process.exitCode),
97
+ })) return;
98
+ await runDefaultPassiveCliCheck({ currentVersion: packageJson.version });
99
+ });
100
+
59
101
  program
60
102
  .command('init')
61
103
  .description('Add the triss delegation block to your project (or globally)')
@@ -74,8 +116,9 @@ program
74
116
  .requiredOption('-q, --question <text>', 'question to answer about the corpus')
75
117
  .option('--provider <name>', 'inference provider: worker (default), deepseek (alias), glm, or kimi (alias: moonshot)')
76
118
  .option('-m, --model <name>', 'model preset (flash | pro) or full model id')
77
- .option('--max-tokens <n>', 'token budget for reasoning + answer', (v) => parseInt(v, 10), 8192)
119
+ .option('--max-tokens <n>', 'token budget for reasoning + answer', parsePositiveInteger, 8192)
78
120
  .option('--system <text>', 'override the system prompt')
121
+ .option('--format <format>', 'response format: text (default) or evidence')
79
122
  .option('--stream', 'force streaming even when stdout is not a TTY')
80
123
  .option('--no-stream', "disable streaming output (default streams when stdout is a TTY)")
81
124
  // Commander also supplies its Command instance to action handlers. Adapt
@@ -89,7 +132,7 @@ program
89
132
  .requiredOption('-t, --target <path>', 'output file path')
90
133
  .option('-c, --context <path>', 'reference file to mimic in style')
91
134
  .option('-m, --model <name>', 'model preset (flash | pro) or full model id')
92
- .option('--max-tokens <n>', 'token budget for reasoning + output', (v) => parseInt(v, 10), 16384)
135
+ .option('--max-tokens <n>', 'token budget for reasoning + output', parsePositiveInteger, 16384)
93
136
  .action(wrap(runWrite));
94
137
 
95
138
  program
@@ -121,6 +164,39 @@ program
121
164
  .option('--no-stream', 'disable streaming output (default streams when stdout is a TTY)')
122
165
  .action((prompt, opts) => wrap(runChat)(prompt, opts));
123
166
 
167
+ program
168
+ .command('exec [task]')
169
+ .description('Route a task deterministically to ask, review, coder run, or chat')
170
+ .option('--explain', 'print the JSON route decision without executing anything')
171
+ .option('--pr <number>', 'explicitly route to review a GitHub PR')
172
+ .option('--base <branch>', 'explicitly route to review against a base branch')
173
+ .option('--review', 'explicitly route to review')
174
+ .option('--skip-issue', 'forward review linked-ticket lookup suppression')
175
+ .option('--paths <path>', 'explicitly route to ask with a file path; repeat for multiple paths', collectOption, [])
176
+ .option('--urls <url>', 'explicitly route to ask with a URL; repeat for multiple URLs', collectOption, [])
177
+ .option('--code', 'explicitly route to coder run')
178
+ .option('--chat', 'explicitly route to chat')
179
+ .option('--stdin', 'let the selected downstream command read piped input')
180
+ .option('--provider <name>', 'forward the inference provider')
181
+ .option('-m, --model <name>', 'forward the model')
182
+ .option('--max-tokens <n>', 'forward the token budget')
183
+ .option('--format <format>', 'forward ask/review response format: text or evidence')
184
+ .option('--system <text>', 'forward the system prompt to ask/chat')
185
+ .option('--stream', 'forward streaming preference')
186
+ .option('--no-stream', 'disable forwarded streaming')
187
+ .option('--engine <name>', 'forward coder engine')
188
+ .option('--agent <name>', 'forward coder agent template')
189
+ .option('--session <id>', 'forward coder session')
190
+ .option('--continue', 'continue the most recent coder session')
191
+ .option('--small-model <p/m>', 'forward coder small model')
192
+ .option('--isolate', 'forward coder isolation')
193
+ .option('--no-isolate', 'disable coder isolation')
194
+ .option('--restrict', 'forward coder restriction')
195
+ .option('--no-restrict', 'disable coder restriction')
196
+ .option('--cwd <path>', 'forward coder working directory')
197
+ .option('--timeout <sec>', 'forward coder timeout')
198
+ .action((task, opts) => wrap(runExec)(task, opts));
199
+
124
200
  program
125
201
  .command('commit-msg')
126
202
  .description('Generate a Git commit message from staged changes (Conventional Commits by default).')
@@ -142,6 +218,7 @@ program
142
218
  .option('--provider <name>', 'inference provider: worker (default), deepseek (alias), glm, or kimi (alias: moonshot)')
143
219
  .option('-m, --model <name>', 'model preset (flash | pro) or full model id (default: pro)')
144
220
  .option('--max-tokens <n>', 'token budget for the review (default 8192)')
221
+ .option('--format <format>', 'response format: text (default) or evidence')
145
222
  .option('--stream', 'force streaming even when stdout is not a TTY')
146
223
  .option('--no-stream', 'disable streaming output (default streams when stdout is a TTY)')
147
224
  .action((pr, opts) => wrap(runReview)(pr, opts));
@@ -169,6 +246,19 @@ program
169
246
  .description('Show current configuration: API key, models, .env sources')
170
247
  .action(wrap(runStatus));
171
248
 
249
+ program
250
+ .command('update')
251
+ .description('Check for a newer stable release; explicitly update standalone installs')
252
+ .option('--json', 'emit one machine-readable status object')
253
+ .option('--apply', 'apply the newer release to a validated standalone install')
254
+ .option('--rollback', 'switch to the previous verified standalone version')
255
+ .option('--yes', 'skip apply/rollback confirmation in non-interactive use')
256
+ .option('--break-lock', 'separately authorize breaking a proven-stale update lock')
257
+ // Commander passes its Command instance as a second action argument. The
258
+ // update runner has a second parameter reserved for injected dependencies
259
+ // in tests, so keep the CLI boundary options-only.
260
+ .action((opts) => wrap(runUpdate)(opts));
261
+
172
262
  program
173
263
  .command('completion <shell>')
174
264
  .description('Print shell completion script (bash | zsh). eval `triss completion bash`')
@@ -262,7 +352,7 @@ coder
262
352
  .option('--restrict', 'crush only: enforce the allowlist via CLI --allow-bash/--allow-tool flags (--restrict-run). Opt-in (default OFF)')
263
353
  .option('--no-restrict', 'crush only: disable restrict (crush auto-approves every tool — the default)')
264
354
  .option('--cwd <path>', 'working directory (ignored with --isolate)')
265
- .option('--timeout <sec>', 'kill the engine after this many seconds', (v) => parseInt(v, 10), 900)
355
+ .option('--timeout <sec>', 'kill the engine after this many seconds', parsePositiveNumber, 900)
266
356
  .option('--stdin', 'read the prompt from piped stdin instead of the [prompt] argument')
267
357
  .option('--json', 'no-op — the envelope is always JSON; kept for symmetry with other commands')
268
358
  .action((prompt, opts) => wrap(runCoderRun)(prompt, opts));
@@ -365,7 +455,14 @@ mcp
365
455
  .action(wrap(runMcpStatus));
366
456
 
367
457
  // Plugin-style integrations (jira, linear, ...). See docs/extending.md.
368
- const integrations = await loadIntegrations();
458
+ // `exec --explain` is an inspection mode that performs no work at all, so it
459
+ // must not trigger integration *startup* side effects: the github integration
460
+ // bootstraps by spawning `gh auth token` (a credential child process) at load
461
+ // time. Explain skips the load entirely — its route decision needs none of
462
+ // the integration subcommands. Every other command still loads and registers
463
+ // them normally.
464
+ const isExplainOnly = isExecExplainInvocation(process.argv.slice(2));
465
+ const integrations = isExplainOnly ? [] : await loadIntegrations();
369
466
  for (const manifest of integrations) {
370
467
  const sub = program.command(manifest.name).description(manifest.description || manifest.name);
371
468
  manifest.register(sub, { wrap });