@amistio/cli 0.1.32 → 0.1.34

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
@@ -15,7 +15,7 @@ Runner lifecycle controls in the web app, such as update, restart, and remove, a
15
15
 
16
16
  Runner Update installs the official `@amistio/cli` package and then refreshes the runner runtime. Background runners attempt a replacement restart so the next heartbeat reports the new CLI version. If replacement restart metadata is missing or restart fails after a successful install, the old runner still stops and reports manual restart guidance instead of continuing to heartbeat the stale runtime. Foreground `amistio run --watch` sessions stop after a successful install with restart guidance; start the command again to load the updated package.
17
17
 
18
- Current runners advertise the work kinds they can claim. Older runners that do not send this capability can continue legacy brain generation, implementation, and plan revision work, but they will skip source-aware assistant, impact-preview, semantic brain consolidation, project-context refresh, issue-diagnosis, app-evaluation, security-posture, Test-quality, implementation-Test-gate, and implementation-verification work until updated.
18
+ Current runners advertise the work kinds they can claim. Older runners that do not send this capability can continue legacy brain generation, implementation, and plan revision work, but they will skip source-aware assistant, impact-preview, semantic brain consolidation, project-context refresh, issue-diagnosis, app-evaluation, security-posture, Test-quality, implementation-Test-gate, and implementation-verification work until updated. Normal runner polling also refreshes review-only self-maintenance health in the Evaluate panel with bounded counts, trend, and safe record IDs for operational drift; it does not upload source, full document bodies, secrets, commands, local paths, or mutate cleanup targets.
19
19
 
20
20
  Tool session reuse is bounded. One-shot tool sessions close after successful completion, failed runs are blocked, active sessions are treated as already in use, and reusable sessions idle for more than six hours are closed before the next claim selects context. This keeps follow-up work from inheriting stale local AI context while preserving recent reusable sessions for related work.
21
21
 
@@ -38,6 +38,7 @@ amistio run --watch --tool opencode --provider anthropic --model-id claude-opus-
38
38
  amistio run --watch --max-concurrent-work 2 --tool opencode
39
39
  amistio run --watch --background --tool opencode
40
40
  amistio runner status
41
+ amistio runner smoke-session-lifecycle
41
42
  ```
42
43
 
43
44
  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.
@@ -48,6 +49,8 @@ When `--tool codex` uses the Codex SDK, intermediate progress can be quiet until
48
49
 
49
50
  `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.
50
51
 
52
+ `amistio runner smoke-session-lifecycle` runs a local no-claim smoke for the runner tool-session lifecycle. It does not contact the API, claim production work, inspect source, or mutate local runner state; it verifies that completed one-shot sessions close, active sessions are treated as in use, stale sessions are not selected for reuse, and fresh related reusable sessions can still continue.
53
+
51
54
  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.
52
55
 
53
56
  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.
package/dist/index.js CHANGED
@@ -4897,6 +4897,134 @@ function tokens(value) {
4897
4897
  return value.toLowerCase().split(/[^a-z0-9]+/).filter((token) => token.length >= 4);
4898
4898
  }
4899
4899
 
4900
+ // src/runner-activation-smoke.ts
4901
+ function runRunnerActivationSmoke(now = /* @__PURE__ */ new Date()) {
4902
+ const reusableSession = createSmokeSession({ now, status: "open", lastActivityAt: new Date(now.getTime() - 60 * 60 * 1e3).toISOString() });
4903
+ const activeSession = createSmokeSession({ now, status: "active", lastActivityAt: new Date(now.getTime() - 60 * 60 * 1e3).toISOString() });
4904
+ const staleOpenSession = createSmokeSession({ now, status: "open", lastActivityAt: new Date(now.getTime() - 7 * 60 * 60 * 1e3).toISOString() });
4905
+ const workItem = createSmokeWorkItem(now);
4906
+ const oneShotStatus = completedToolSessionStatus({ resumabilityScope: "none" });
4907
+ const oneShotClosedReason = completedToolSessionClosedReason({ resumabilityScope: "none" });
4908
+ const reusableStatus = completedToolSessionStatus({ resumabilityScope: "localMachine" });
4909
+ const activeSelection = selectToolSession({
4910
+ policy: "auto",
4911
+ workItem,
4912
+ sessions: [activeSession],
4913
+ toolName: "opencode",
4914
+ runnerId: "runner_smoke",
4915
+ repositoryLinkId: "repo_smoke",
4916
+ machineId: "machine_smoke",
4917
+ supportsSessionReuse: true,
4918
+ now
4919
+ });
4920
+ const staleSelection = selectToolSession({
4921
+ policy: "auto",
4922
+ workItem,
4923
+ sessions: [staleOpenSession],
4924
+ toolName: "opencode",
4925
+ runnerId: "runner_smoke",
4926
+ repositoryLinkId: "repo_smoke",
4927
+ machineId: "machine_smoke",
4928
+ supportsSessionReuse: true,
4929
+ now
4930
+ });
4931
+ const reusableSelection = selectToolSession({
4932
+ policy: "auto",
4933
+ workItem,
4934
+ sessions: [reusableSession],
4935
+ toolName: "opencode",
4936
+ runnerId: "runner_smoke",
4937
+ repositoryLinkId: "repo_smoke",
4938
+ machineId: "machine_smoke",
4939
+ supportsSessionReuse: true,
4940
+ now
4941
+ });
4942
+ const staleClosedReason = staleToolSessionClosedReason(staleOpenSession, now);
4943
+ const checks = [
4944
+ {
4945
+ name: "completed-one-shot-closes",
4946
+ passed: oneShotStatus === "closed" && Boolean(oneShotClosedReason),
4947
+ detail: `completed one-shot status=${oneShotStatus}`
4948
+ },
4949
+ {
4950
+ name: "completed-reusable-stays-open",
4951
+ passed: reusableStatus === "open",
4952
+ detail: `completed reusable status=${reusableStatus}`
4953
+ },
4954
+ {
4955
+ name: "active-session-not-reused",
4956
+ passed: activeSelection.decision === "created",
4957
+ detail: `active selection decision=${activeSelection.decision}`
4958
+ },
4959
+ {
4960
+ name: "stale-session-not-reused",
4961
+ passed: staleSelection.decision === "created" && Boolean(staleClosedReason),
4962
+ detail: `stale selection decision=${staleSelection.decision}`
4963
+ },
4964
+ {
4965
+ name: "fresh-reusable-session-reused",
4966
+ passed: reusableSelection.decision === "continued",
4967
+ detail: `fresh reusable selection decision=${reusableSelection.decision}`
4968
+ }
4969
+ ];
4970
+ return {
4971
+ checkedAt: now.toISOString(),
4972
+ passed: checks.every((check) => check.passed),
4973
+ checks
4974
+ };
4975
+ }
4976
+ function createSmokeWorkItem(now) {
4977
+ const timestamp = now.toISOString();
4978
+ return {
4979
+ id: "work_smoke",
4980
+ type: "workItem",
4981
+ schemaVersion: 1,
4982
+ accountId: "acct_smoke",
4983
+ projectId: "proj_smoke",
4984
+ workItemId: "work_smoke",
4985
+ workKind: "implementation",
4986
+ status: "running",
4987
+ title: "Implement runner session smoke",
4988
+ requestedBy: "user_smoke",
4989
+ requestedByUserId: "user_smoke",
4990
+ attempt: 1,
4991
+ idempotencyKey: "smoke_session_lifecycle",
4992
+ sessionGroupKey: "session_smoke",
4993
+ lastStatusAt: timestamp,
4994
+ createdAt: timestamp,
4995
+ updatedAt: timestamp
4996
+ };
4997
+ }
4998
+ function createSmokeSession({ now, status, lastActivityAt }) {
4999
+ const timestamp = now.toISOString();
5000
+ return {
5001
+ id: `tool_session_smoke_${status}`,
5002
+ type: "toolSession",
5003
+ schemaVersion: 1,
5004
+ accountId: "acct_smoke",
5005
+ projectId: "proj_smoke",
5006
+ toolSessionId: `tool_session_smoke_${status}`,
5007
+ repositoryLinkId: "repo_smoke",
5008
+ tool: "opencode",
5009
+ provider: "opencode",
5010
+ resumabilityScope: "localMachine",
5011
+ title: "Implement runner session smoke",
5012
+ summary: "Smoke test session",
5013
+ tags: ["runner", "session", "smoke"],
5014
+ relatedDocumentIds: [],
5015
+ status,
5016
+ runnerId: "runner_smoke",
5017
+ machineId: "machine_smoke",
5018
+ lastWorkItemId: "work_previous",
5019
+ lastActivityAt,
5020
+ messageCount: 1,
5021
+ reusePolicy: "auto",
5022
+ sessionGroupKey: "session_smoke",
5023
+ createdAt: timestamp,
5024
+ updatedAt: timestamp
5025
+ };
5026
+ }
5027
+
4900
5028
  // src/sync.ts
4901
5029
  import { execFile as execFile3 } from "node:child_process";
4902
5030
  import { createHash as createHash5 } from "node:crypto";
@@ -6060,6 +6188,7 @@ function createAppEvaluationScanPrompt(workItem, context) {
6060
6188
  "- Treat intentionally in-progress feature tracks as still-active work when their controlling plan/feature has unchecked requirements or explicit follow-up gaps. For example, a completed first implementation prompt does not make the broader feature stale if PLAN/FEAT evidence says remaining lifecycle work is still open; return proposedLifecycleAction keepActive with evidence instead of cleanup.",
6061
6189
  "- Treat prompt frontmatter status Ready as an active execution backlog state by default, not as stale review debt. Only flag a Ready prompt for metadata correction when its controlling plan, feature, prompt index, or verification evidence unambiguously proves the prompt has already completed or been superseded.",
6062
6190
  "- Treat implemented umbrella plans that explicitly label unchecked checklist items as deferred follow-ups, future candidates, roadmap backlog, or split-out hardening phases as valid deferred backlog. Do not mark the umbrella incomplete or stale solely because those deferred items remain unchecked; use keepActive or no cleanup, and recommend a fresh focused plan only when a concrete deferred slice has current evidence and approval.",
6191
+ "- Treat reusable hygiene tooling and recurring health-loop prompts/plans as active operational backlog when the one-time cleanup or prevention pass has executed but the repeatable utility or recurring workflow remains explicitly Ready, Proposed, or unchecked. Do not archive them just because the initial cleanup succeeded; use keepActive unless controlling evidence proves the reusable work was completed, superseded, or rejected.",
6063
6192
  "- 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.",
6064
6193
  "- Check missing memory or workflow updates when repeated lessons or operational rules are visible.",
6065
6194
  "- Check release readiness, UX, accessibility, performance, reliability, and security-posture follow-through at a summary level.",
@@ -8497,6 +8626,20 @@ runner.command("status").description("Show background runner status for the pair
8497
8626
  console.log(` Resource usage: ${formatRunnerResourceUsage(heartbeat?.resourceUsage)}`);
8498
8627
  }
8499
8628
  });
8629
+ runner.command("smoke-session-lifecycle").description("Run a local no-claim smoke for runner tool-session lifecycle behavior").option("--json", "Print the smoke report as JSON").action((options) => {
8630
+ const report = runRunnerActivationSmoke();
8631
+ if (options.json) {
8632
+ console.log(JSON.stringify(report, null, 2));
8633
+ } else {
8634
+ console.log(`Runner session lifecycle smoke: ${report.passed ? "passed" : "failed"}`);
8635
+ for (const check of report.checks) {
8636
+ console.log(` ${check.passed ? "ok" : "fail"} ${check.name}: ${check.detail}`);
8637
+ }
8638
+ }
8639
+ if (!report.passed) {
8640
+ process.exitCode = 1;
8641
+ }
8642
+ });
8500
8643
  runner.command("stop").description("Stop a background runner for the paired repository").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--root <path>", "Repository root", defaultRoot).option("--runner-id <runnerId>", "Runner ID to stop when multiple background runners exist").action(async (options) => {
8501
8644
  const context = await loadPairedApiContext(options.root, options.apiUrl);
8502
8645
  if (!context) {