@amistio/cli 0.1.27 → 0.1.29

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/README.md CHANGED
@@ -38,7 +38,7 @@ amistio run --watch --background --tool opencode
38
38
  amistio runner status
39
39
  ```
40
40
 
41
- Provider-backed model preferences use sanitized catalog fields: `--provider`, `--model-id`, optional `--model-variant`, and `--reasoning-effort` (`auto`, `low`, `medium`, `high`, or `xhigh`). Provider credentials, API keys, and local secret paths stay in the local tool configuration; they are not stored in Amistio preferences or runner heartbeats.
41
+ Provider-backed model preferences use sanitized catalog fields: `--provider`, `--model-id`, optional `--model-variant`, and `--reasoning-effort` (`auto`, `low`, `medium`, `high`, or `xhigh`). Opencode catalog metadata is synthesized by Amistio or derived from readable local OpenCode JSON config until opencode exposes a native catalog. Provider credentials, API keys, and local secret paths stay in the local tool configuration; they are not stored in Amistio preferences or runner heartbeats.
42
42
 
43
43
  When `--tool copilot` uses the GitHub Copilot SDK, Amistio approves read-only permission requests by default and denies mutating, network, MCP, hook, memory, and shell requests. Set `AMISTIO_COPILOT_APPROVE_ALL=1` only on a local machine where broad Copilot SDK approval is intentional.
44
44
 
@@ -46,7 +46,7 @@ When `--tool codex` uses the Codex SDK, intermediate progress can be quiet until
46
46
 
47
47
  `amistio runner status` reports local background runner state, latest heartbeat, and bounded resource usage when available. Resource usage is latest-sample runner process memory/CPU plus safe aggregate system memory/load signals; it does not include source files, environment variables, command lines, process lists, credentials, or arbitrary local paths.
48
48
 
49
- The runner advertises its supported work kinds in heartbeats. Current runners can claim read-only `projectContextRefresh` jobs from the workspace Context panel and create due runner-driven refreshes when no fresh approved map exists. Context refreshes inspect the paired checkout locally without modifying files and submit only bounded summaries, slices, entities, relations, safe citations, confidence, freshness, and repo-relative paths. If a submitted context refresh contains unsafe evidence, unsafe paths, or a map too large to store safely, Amistio marks the refresh failed with a safe reason instead of storing the rejected raw result. Approved maps are reused as context packs for source-aware assistant and impact-preview work. Current runners can also claim read-only issue diagnosis jobs from the web Issues panel, generate root-cause analysis and a proposed fix, and submit that result without modifying source. They can claim manual read-only `appEvaluationScan` jobs from the workspace Evaluate panel and create at most one due hourly evaluation during normal watch/background polling when app evaluation is enabled for the repository link. Evaluation results contain bounded summaries, safe evidence, suggested actions, lifecycle proposals, and repo-relative paths only. Current runners can also claim manual read-only `securityPostureScan` jobs from the workspace Security panel and create due daily posture checks during normal watch/background polling. Security scan results contain bounded summaries, standard references, safe evidence, and repo-relative paths only. Current runners can claim manual read-only `testQualityScan` jobs from the workspace Test panel and create one due daily Test scan per repository when Test quality is enabled. Test scans run only existing lint, typecheck, test, coverage, build, or verify commands and submit bounded command summaries, coverage summaries, safe findings, blocked reasons, warnings, and repo-relative paths. Missing tests, missing coverage, low coverage, failing checks, flaky tests, and test gaps create reviewable plan-backed findings in the app. Current runners also claim `implementationTestGate` jobs before implementation completion, PR handoff, or runner-managed push; a passing gate is required unless the web Test panel records an audited override. Current runners can claim read-only `implementationVerification` jobs from Tasks to prove whether completed implementation work actually landed; verification submits bounded acceptance-criteria evidence, checks, gaps, outcome, and recommendation without mutating source. Source, secrets, environment variables, command lines, process lists, credentials, provider sessions, and arbitrary local paths stay local. Implementation or cleanup is queued separately only after the user approves an issue analysis, app evaluation finding, security remediation plan, or Test quality plan in the app.
49
+ The runner advertises its supported work kinds in heartbeats. Current runners can claim read-only `projectContextRefresh` jobs from the workspace Context panel and create due runner-driven refreshes when no fresh approved map exists. Context refreshes inspect the paired checkout locally without modifying files and submit only bounded summaries, slices, entities, relations, safe citations, confidence, freshness, and repo-relative paths. If a submitted context refresh contains unsafe evidence, unsafe paths, or a map too large to store safely, Amistio marks the refresh failed with a safe reason instead of storing the rejected raw result. Approved maps are reused as context packs for source-aware assistant and impact-preview work. Current runners can also claim read-only issue diagnosis jobs from the web Issues panel, generate root-cause analysis and a proposed fix, and submit that result without modifying source. They can claim manual read-only `appEvaluationScan` jobs from the workspace Evaluate panel and create at most one due hourly evaluation during normal watch/background polling when app evaluation is enabled for the repository link. Evaluation results contain bounded summaries, safe evidence, suggested actions, lifecycle proposals, and repo-relative paths only. Current runners can also claim manual read-only `securityPostureScan` jobs from the workspace Security panel and create due daily posture checks during normal watch/background polling. Security scan results contain bounded summaries, standard references, safe evidence, and repo-relative paths only. Current runners can claim manual read-only `testQualityScan` jobs from the workspace Test panel and create one due daily Test scan per repository when Test quality is enabled. Test scans run only existing lint, typecheck, test, coverage, build, or verify commands and submit bounded command summaries, coverage summaries, safe findings, blocked reasons, warnings, and repo-relative paths. Missing tests, missing coverage, low coverage, failing checks, flaky tests, and test gaps create reviewable plan-backed findings in the app. Current runners also claim `implementationTestGate` jobs before implementation completion, PR handoff, or runner-managed push; a passing gate is required unless the web Test panel records an audited override. Blocked implementation Test gates submit structured Test findings, such as `blockedEnvironment`, with safe evidence, a suggested action, and a verification plan. Current runners can claim read-only `implementationVerification` jobs from Tasks to prove whether completed implementation work actually landed; verification submits bounded acceptance-criteria evidence, checks, gaps, outcome, and recommendation without mutating source. Source, secrets, environment variables, command lines, process lists, credentials, provider sessions, and arbitrary local paths stay local. Implementation or cleanup is queued separately only after the user approves an issue analysis, app evaluation finding, security remediation plan, or Test quality plan in the app.
50
50
 
51
51
  Approved implementation work uses Git as the handoff boundary. During worktree preflight, the runner locally copies eligible ignored root dotenv files such as `.env.local` or `.env.test.local` from the paired checkout into the implementation worktree when the target is missing and ignored, so local tests can use the same machine configuration. Dotenv values, variable names, file contents, and local paths are not uploaded to Amistio, and copied dotenv files stay ignored so PR handoff does not commit them. After the local tool completes successfully, the runner materializes approved Markdown, MDX, and HTML project-brain artifacts for the same work scope into the isolated worktree before final Git status. It then commits all source and artifact changes, fetches and rebases from the linked remote's default branch, pushes an `amistio/work/...` branch, opens or reuses a pull request with the locally authenticated `gh` CLI, reports only safe PR and artifact-inclusion metadata to Amistio, and removes the local worktree after the PR URL is durable. Artifact-only materialization changes still create or reuse a PR; no-change completion requires no source changes and no approved artifact changes. Prepare the runner machine with Git commit identity, fetch/push permission to the linked remote, and `gh auth status`. If artifact materialization, commit, fetch/rebase, push, or PR creation fails, the work item is blocked and the branch/worktree stay on disk for manual recovery; source files and patches are not uploaded to Amistio.
52
52
 
@@ -60,6 +60,8 @@ Watch mode prints a completed-work success once per work item, keeps fresh compl
60
60
 
61
61
  Known validation failures such as `unsafe_context_path` are printed with attention-needed next steps. For project-context refresh path-safety failures, deploy the latest web/API fix, update and restart the runner when applicable, retry the refresh, and capture only bounded non-secret output if it repeats.
62
62
 
63
+ App-evaluation result finalization rejections print safe validation paths and preserve the local finalization evidence without exposing raw source or secrets. If a structured app-evaluation result is rejected, update and restart the runner, confirm the web/API deployment is current, and retry the evaluation before acting on cleanup or implementation recommendations.
64
+
63
65
  When brain generation or plan revision output is parsed but the Amistio API is temporarily unavailable during finalization, the runner keeps a safe pending result envelope in user-level Amistio config and replays it before claiming more work. The envelope uses a stable idempotency key and does not store raw tool stdout, provider sessions, credentials, or arbitrary local paths.
64
66
 
65
67
  For headless startup after login on supported user-level service managers:
package/dist/index.js CHANGED
@@ -2222,9 +2222,11 @@ function computeProjectNextAction(input) {
2222
2222
  if (failedPlanRevision) {
2223
2223
  return failedWorkAction(failedPlanRevision, "planRevisionFailed", "Plan revision failed", failedPlanRevision.lastStatusMessage ?? "Review the conversation and request another revision if needed.");
2224
2224
  }
2225
- const blockedWork = latestWorkItem(input.workItems.filter((item) => item.status === "blocked" || item.status === "changesRequested"));
2225
+ const blockedWorkItems = input.workItems.filter((item) => item.status === "blocked" || item.status === "changesRequested");
2226
+ const blockedWork = latestWorkItem(blockedWorkItems);
2226
2227
  if (blockedWork) {
2227
- return workAction(blockedWork, "workBlocked", "user", "danger", "Work is blocked", blockedWork.lastStatusMessage ?? "Review the blocked work item before the runner can continue.");
2228
+ const message = workStatusReason(blockedWork, "Review the blocked work item before the runner can continue.");
2229
+ return workAction(blockedWork, "workBlocked", "user", "danger", "Work is blocked", blockedWorkItems.length > 1 ? `Latest blocked work ${blockedWork.workItemId}: ${message}` : message);
2228
2230
  }
2229
2231
  const failedWork = latestWorkItem(input.workItems.filter((item) => item.status === "failed"));
2230
2232
  if (failedWork) {
@@ -2337,6 +2339,9 @@ function failedWorkAction(workItem, kind, fallbackTitle, fallbackMessage) {
2337
2339
  }
2338
2340
  return workAction(workItem, kind, "user", "danger", fallbackTitle, fallbackMessage);
2339
2341
  }
2342
+ function workStatusReason(workItem, fallbackMessage) {
2343
+ return workItem.blockerReason ?? workItem.sourceFailureSummary ?? workItem.lastStatusMessage ?? fallbackMessage;
2344
+ }
2340
2345
  function workAction(workItem, kind, actor, tone, title, message, detail) {
2341
2346
  return {
2342
2347
  kind,
@@ -3605,7 +3610,7 @@ async function detectLocalTools() {
3605
3610
  localToolAdapters.map(async (adapter) => {
3606
3611
  const sdkAvailable = await isSdkAvailable(adapter);
3607
3612
  const commandAvailable = adapter.executable ? await commandExists(adapter.executable) : false;
3608
- const providerCatalog = await detectProviderCatalog(adapter);
3613
+ const providerCatalog = await detectRunnerProviderCatalog(adapter);
3609
3614
  return {
3610
3615
  name: adapter.name,
3611
3616
  description: adapter.description,
@@ -3847,16 +3852,18 @@ async function commandExists(command) {
3847
3852
  lookup.on("close", (exitCode) => resolve(exitCode === 0));
3848
3853
  });
3849
3854
  }
3850
- async function detectProviderCatalog(adapter) {
3851
- const opencodeCatalog = adapter.name === "opencode" ? await loadOpencodeProviderCatalog() : void 0;
3852
- return mergeProviderCatalogs(adapter.providerCatalog, opencodeCatalog);
3855
+ async function detectRunnerProviderCatalog(adapter) {
3856
+ const localOpencodeConfigCatalog = adapter.name === "opencode" ? await loadLocalOpencodeProviderConfigCatalog() : void 0;
3857
+ return mergeProviderCatalogs(adapter.providerCatalog, localOpencodeConfigCatalog);
3853
3858
  }
3854
- async function loadOpencodeProviderCatalog() {
3855
- const configPaths = [
3859
+ function localOpencodeProviderConfigPaths() {
3860
+ return [
3856
3861
  path6.join(os3.homedir(), ".config", "opencode", "opencode.json"),
3857
3862
  path6.join(os3.homedir(), ".config", "opencode", "config.json"),
3858
3863
  path6.join(process.cwd(), "opencode.json")
3859
3864
  ];
3865
+ }
3866
+ async function loadLocalOpencodeProviderConfigCatalog(configPaths = localOpencodeProviderConfigPaths()) {
3860
3867
  for (const configPath of configPaths) {
3861
3868
  try {
3862
3869
  const parsed = JSON.parse(await readFile4(configPath, "utf8"));
@@ -5658,11 +5665,20 @@ function createImplementationTestGatePrompt(workItem) {
5658
5665
  "## Output Contract",
5659
5666
  "",
5660
5667
  "Print exactly one JSON object between the markers below. The CLI will submit only this structured gate result back to Amistio.",
5661
- "Accepted outcome values: passed, failed, blocked, overridden.",
5668
+ "Accepted outcome values: passed, failed, blocked, missingTests, belowThreshold, overridden.",
5669
+ "Accepted command kind values: verify, test, coverage, lint, typecheck, build, focused.",
5670
+ "Accepted command status values: passed, failed, skipped, missing, blocked.",
5671
+ "Prefer repository-documented verification commands or approved test profile commands over ad hoc package-script inference.",
5672
+ "For this Amistio monorepo, if plain Corepack pnpm fails before scripts with spawnSync pnpm ENOENT, retry the documented command corepack pnpm --config.verify-deps-before-run=false verify before declaring whole-app verification blocked.",
5673
+ "Accepted finding categories: missingTests, missingCoverage, missingCommand, lowCoverage, failingTests, failingQuality, failingQualityCheck, staleScan, blockedEnvironment, weakTests, flakyTests, unverifiedImplementation, testGap, other.",
5674
+ "Accepted finding severity values: info, low, medium, high, critical.",
5675
+ "Accepted finding confidence values: low, medium, high.",
5676
+ "Every finding must include title, category, severity, summary, suggestedAction, and a non-empty verificationPlan.",
5677
+ "Finding optional fields may include confidence, affectedSurfaces, evidence, safePaths, proposedPlanTitle, proposedPlanRepoPath, proposedPlanContent, and dedupeKey.",
5662
5678
  "Omit optional fields when unavailable; do not emit null for optional command summary fields such as exitCode, durationMs, outputExcerpt, commandId, or safePaths.",
5663
5679
  "",
5664
5680
  implementationTestGateStart,
5665
- '{"outcome":"passed","summary":"Focused checks and whole-app verification passed.","commandSummaries":[{"commandId":"verify","kind":"verify","label":"Whole-app verification","status":"passed","exitCode":0,"summary":"The repository verification script completed successfully.","safePaths":["package.json"]}],"coverage":{"status":"unknown","thresholds":{},"summary":"No coverage threshold was configured for this gate."},"findings":[],"blockedReasons":[],"redactionState":{"status":"clean","redactedFields":[]},"verificationPlan":["Record this gate result before marking implementation complete"],"warnings":[]}',
5681
+ '{"outcome":"blocked","summary":"Whole-app verification could not run because the local package-manager wrapper failed before repository checks started.","commandSummaries":[{"commandId":"verify","kind":"verify","label":"Whole-app verification","status":"blocked","summary":"Corepack could not spawn pnpm before repository verification started.","safePaths":["package.json"]}],"coverage":{"status":"unknown","thresholds":{},"summary":"Coverage was not produced because verification did not run."},"findings":[{"title":"Local verification toolchain is blocked","category":"blockedEnvironment","severity":"medium","confidence":"high","summary":"The implementation test gate could not start the repository verification command because the local package-manager toolchain failed before checks ran.","affectedSurfaces":["Local runner verification"],"evidence":["The verification command failed before repository checks started."],"safePaths":["package.json"],"suggestedAction":"Restore the local package-manager toolchain or run the repository-documented verification command, then rerun the implementation test gate.","verificationPlan":["Run the documented whole-app verification command","Rerun the implementation test gate"],"dedupeKey":"blocked-environment-package-manager"}],"blockedReasons":["The local package-manager toolchain could not start verification."],"redactionState":{"status":"clean","redactedFields":[]},"verificationPlan":["Restore local verification tooling","Rerun the implementation test gate before marking implementation complete"],"warnings":[]}',
5666
5682
  implementationTestGateEnd,
5667
5683
  "",
5668
5684
  "Do not put Markdown fences around the markers."
@@ -5927,6 +5943,8 @@ function createAppEvaluationScanPrompt(workItem, context) {
5927
5943
  "- When lifecycle metadata disagrees across indexes, frontmatter, feature specs, ADRs, and implementation evidence, cite the conflict and propose a metadata correction or verification step instead of archival/removal.",
5928
5944
  "- Check missing memory or workflow updates when repeated lessons or operational rules are visible.",
5929
5945
  "- Check release readiness, UX, accessibility, performance, reliability, and security-posture follow-through at a summary level.",
5946
+ "- Prefer repository-documented verification commands over ad hoc package-script inference.",
5947
+ "- For this Amistio monorepo, if plain Corepack pnpm fails before scripts with spawnSync pnpm ENOENT, retry the documented command corepack pnpm --config.verify-deps-before-run=false verify before declaring whole-app verification blocked.",
5930
5948
  "",
5931
5949
  "## Data Safety",
5932
5950
  "",
@@ -9143,7 +9161,7 @@ function startWorkLeaseRenewal({ apiClient, projectId, repositoryLinkId, runnerI
9143
9161
  }
9144
9162
  async function recordFinalizationFailure({ apiClient, durationMs, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName, workItem }) {
9145
9163
  const detail = truncateLogExcerpt(errorDetail(error));
9146
- const message = `${toolName} completed, but Amistio could not finalize the result.`;
9164
+ const message = `${toolName} completed, but Amistio could not finalize the result. ${safeFinalizationFailureSummary(error)}`;
9147
9165
  const settlements = await Promise.allSettled([
9148
9166
  apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig)),
9149
9167
  markToolSessionBlocked(apiClient, projectId, sessionContext.toolSession, errorMessage3(error)),
@@ -9183,6 +9201,12 @@ async function recordFinalizationFailure({ apiClient, durationMs, error, isolati
9183
9201
  console.error(detail);
9184
9202
  return { status: "failed", exitCode: 1, message };
9185
9203
  }
9204
+ function safeFinalizationFailureSummary(error) {
9205
+ if (error instanceof AmistioApiError) {
9206
+ return truncateLogExcerpt(error.message);
9207
+ }
9208
+ return "Review the runner log for the finalization error.";
9209
+ }
9186
9210
  function workItemIsolationTelemetry(workItem, isolation) {
9187
9211
  const implementationScopeId = isolation?.implementationScopeId ?? workItem.implementationScopeId;
9188
9212
  const executionBranch = isolation?.branch ?? workItem.executionBranch;