@amistio/cli 0.1.18 → 0.1.19

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, project-context refresh, issue-diagnosis, app-evaluation, and security-posture 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, project-context refresh, issue-diagnosis, app-evaluation, security-posture, and implementation-verification work until updated.
19
19
 
20
20
  Repository brain auto-sync is disabled until the repository link option is enabled in the app. After pairing, run `amistio sync watch` from the paired checkout to push recognized external brain Markdown/MDX files and HTML companions under `docs/html/<area>/`, including local ADRs, plans, prompts, workflows, memory, context, architecture, and feature docs, to the app for review. `amistio run --watch` also runs the same cycle between work polls when the option is enabled. The CLI skips templates, unsupported paths, oversized files, unchanged managed docs, and conflicts instead of silently overwriting web state.
21
21
 
@@ -43,10 +43,12 @@ When `--tool codex` uses the Codex SDK, intermediate progress can be quiet until
43
43
 
44
44
  `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.
45
45
 
46
- 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; 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, or security remediation plan in the app.
46
+ 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 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, or security remediation plan in the app.
47
47
 
48
48
  Approved implementation work uses Git as the handoff boundary. After the local tool completes successfully, the runner commits the isolated worktree, pushes an `amistio/work/...` branch to the linked GitHub remote, opens or reuses a pull request with the locally authenticated `gh` CLI, reports only safe PR metadata to Amistio, and removes the local worktree after the PR URL is durable. Prepare the runner machine with Git commit identity, push permission to the linked remote, and `gh auth status`. If commit, 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.
49
49
 
50
+ Failed or stale work can be requeued from the web Tasks panel. Requeue creates a new linked work attempt and preserves the original terminal attempt for audit history; it is blocked while equivalent work is already active or when the paired runner does not advertise the needed work kind. Completed implementation status is separate from proof: queue `implementationVerification` from Tasks when a plan needs source-aware evidence before cleanup or implementation status decisions.
51
+
50
52
  Runner setup and local-tool execution use bounded failure controls. `amistio run --watch` retries Git worktree preflight failures by releasing the claim for another attempt, then fails the work item after `--max-preflight-attempts` attempts, defaulting to 3. Active local-tool runs renew the work lease, and `--tool-timeout-seconds` caps tool execution, defaulting to 1800 seconds.
51
53
 
52
54
  Watch mode prints a completed-work success once per work item, keeps fresh completion visible briefly, and returns old completed work to the ready state when no queued, running, blocked, failed, review, or runner-readiness action needs attention.
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ var itemTypeSchema = z.enum([
26
26
  "securityPostureSnapshot",
27
27
  "appEvaluationScan",
28
28
  "appEvaluationFinding",
29
+ "implementationVerification",
29
30
  "projectContextMap",
30
31
  "projectContextRefresh",
31
32
  "projectSystemDiagram",
@@ -78,9 +79,12 @@ var workStatusSchema = z.enum([
78
79
  ]);
79
80
  var sourceSchema = z.enum(["web", "repo", "generated", "runner"]);
80
81
  var executionModeSchema = z.enum(["localRunner", "cloudSandbox"]);
81
- var workKindSchema = z.enum(["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "projectContextRefresh"]);
82
+ var workKindSchema = z.enum(["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "projectContextRefresh", "implementationVerification"]);
82
83
  var implementationHandoffStatusSchema = z.enum(["notStarted", "noChanges", "prReady", "blocked", "failed"]);
83
84
  var implementationHandoffCleanupStatusSchema = z.enum(["notApplicable", "pending", "completed", "failed"]);
85
+ var implementationVerificationOutcomeSchema = z.enum(["verifiedImplemented", "partiallyImplemented", "notImplemented", "inconclusive", "verificationBlocked"]);
86
+ var implementationVerificationStatusSchema = z.enum(["queued", "running", "completed", "failed", "blocked", "stale"]);
87
+ var implementationProofStatusSchema = z.enum(["unverified", "verificationQueued", "verificationRunning", "verifiedImplemented", "partiallyImplemented", "notImplemented", "inconclusive", "verificationBlocked", "humanOverride"]);
84
88
  var implementationHandoffSchema = z.object({
85
89
  provider: z.string().trim().min(1).max(80).optional(),
86
90
  status: implementationHandoffStatusSchema,
@@ -105,7 +109,7 @@ var issueStatusSchema = z.enum(["queued", "diagnosing", "analysisReady", "approv
105
109
  var issueApprovalStateSchema = z.enum(["proposed", "approved", "changesRequested", "rejected", "implemented", "blocked"]);
106
110
  var securityScanStatusSchema = z.enum(["queued", "running", "completed", "failed", "stale", "skipped"]);
107
111
  var securityScanSourceSchema = z.enum(["nightly", "manual", "runner"]);
108
- var securityFindingCategorySchema = z.enum(["owasp", "dependency", "supplyChain", "secretHygiene", "authentication", "authorization", "tenantIsolation", "headers", "redirects", "csrf", "cors", "ciCd", "logging", "runnerBoundary", "other"]);
112
+ var securityFindingCategorySchema = z.enum(["owasp", "dependency", "supplyChain", "secretHygiene", "authentication", "authorization", "tenantIsolation", "headers", "redirects", "csrf", "cors", "ciCd", "logging", "rateLimiting", "runnerBoundary", "other"]);
109
113
  var securityFindingSeveritySchema = z.enum(["info", "low", "medium", "high", "critical"]);
110
114
  var securityFindingConfidenceSchema = z.enum(["low", "medium", "high"]);
111
115
  var securityFindingStatusSchema = z.enum(["open", "planReady", "approved", "changesRequested", "dismissed", "acceptedRisk", "remediationQueued", "resolved", "failed"]);
@@ -156,6 +160,14 @@ var activityEventTypeSchema = z.enum([
156
160
  "appEvaluationFindingReviewed",
157
161
  "appEvaluationPlanCreated",
158
162
  "appEvaluationImplementationQueued",
163
+ "workRequeueRequested",
164
+ "workRequeueQueued",
165
+ "workRequeueBlocked",
166
+ "implementationVerificationQueued",
167
+ "implementationVerificationStarted",
168
+ "implementationVerificationCompleted",
169
+ "implementationVerificationBlocked",
170
+ "implementationVerificationOverride",
159
171
  "projectContextRefreshQueued",
160
172
  "projectContextRefreshStarted",
161
173
  "projectContextRefreshCompleted",
@@ -423,8 +435,20 @@ var workItemSchema = baseItemSchema.extend({
423
435
  securityFindingId: z.string().min(1).optional(),
424
436
  appEvaluationScanId: z.string().min(1).optional(),
425
437
  appEvaluationFindingId: z.string().min(1).optional(),
438
+ implementationVerificationId: z.string().min(1).optional(),
426
439
  projectContextRefreshId: z.string().min(1).optional(),
427
440
  contextMissId: z.string().min(1).optional(),
441
+ sourceWorkItemId: z.string().min(1).optional(),
442
+ requeueReason: z.string().trim().min(1).max(600).optional(),
443
+ requeuedByUserId: z.string().min(1).optional(),
444
+ requeuedAt: isoDateTimeSchema.optional(),
445
+ sourceAttempt: z.number().int().nonnegative().optional(),
446
+ sourceTerminalStatus: workStatusSchema.optional(),
447
+ sourceFailureSummary: z.string().trim().min(1).max(1e3).optional(),
448
+ implementationProofStatus: implementationProofStatusSchema.optional(),
449
+ implementationVerificationOutcome: implementationVerificationOutcomeSchema.optional(),
450
+ implementationVerificationResultId: z.string().min(1).optional(),
451
+ humanImplementationOverrideReason: z.string().trim().min(1).max(1e3).optional(),
428
452
  repositoryLinkId: z.string().min(1).optional(),
429
453
  reviewThreadId: z.string().min(1).optional(),
430
454
  reviewDocumentId: z.string().min(1).optional(),
@@ -965,6 +989,59 @@ var projectContextPackSchema = z.object({
965
989
  tokenEstimate: z.number().int().nonnegative().default(0),
966
990
  generatedAt: isoDateTimeSchema
967
991
  });
992
+ var implementationVerificationCheckStatusSchema = z.enum(["passed", "failed", "blocked", "skipped"]);
993
+ var implementationVerificationRecommendationSchema = z.enum(["none", "requeue", "createFollowUpPlan", "requestHumanReview", "markBlocked"]);
994
+ var implementationVerificationEvidenceSchema = z.object({
995
+ acceptanceCriterion: z.string().trim().min(1).max(500),
996
+ status: z.enum(["satisfied", "partial", "missing", "unknown"]),
997
+ summary: z.string().trim().min(1).max(1200),
998
+ citations: z.array(projectContextCitationSchema).default([])
999
+ });
1000
+ var implementationVerificationCheckSchema = z.object({
1001
+ name: z.string().trim().min(1).max(200),
1002
+ status: implementationVerificationCheckStatusSchema,
1003
+ summary: z.string().trim().min(1).max(1200),
1004
+ safePaths: z.array(projectContextRepoPathSchema).default([])
1005
+ });
1006
+ var implementationVerificationResultSchema = z.object({
1007
+ outcome: implementationVerificationOutcomeSchema,
1008
+ summary: z.string().trim().min(1).max(2e3),
1009
+ evidence: z.array(implementationVerificationEvidenceSchema).min(1),
1010
+ checks: z.array(implementationVerificationCheckSchema).default([]),
1011
+ gaps: z.array(z.string().trim().min(1).max(600)).default([]),
1012
+ branch: z.string().trim().min(1).max(200).optional(),
1013
+ pullRequestUrl: z.string().trim().url().max(500).optional(),
1014
+ worktreeKey: z.string().trim().min(1).max(300).optional(),
1015
+ recommendation: implementationVerificationRecommendationSchema.default("none"),
1016
+ verificationPlan: z.array(z.string().trim().min(1).max(300)).default([]),
1017
+ warnings: z.array(z.string().trim().min(1).max(600)).default([])
1018
+ });
1019
+ var implementationVerificationItemSchema = baseItemSchema.extend({
1020
+ type: z.literal("implementationVerification"),
1021
+ projectId: z.string().min(1),
1022
+ implementationVerificationId: z.string().min(1),
1023
+ status: implementationVerificationStatusSchema,
1024
+ source: z.enum(["manual", "runner", "appEvaluation", "planCleanup", "system"]).default("manual"),
1025
+ planDocumentId: z.string().min(1).optional(),
1026
+ planDocumentRevision: z.number().int().nonnegative().optional(),
1027
+ sourceWorkItemId: z.string().min(1).optional(),
1028
+ verificationWorkItemId: z.string().min(1).optional(),
1029
+ repositoryLinkId: z.string().min(1).optional(),
1030
+ runnerId: z.string().min(1).optional(),
1031
+ sourceRevision: z.string().trim().min(1).max(160).optional(),
1032
+ brainRevision: z.number().int().nonnegative().optional(),
1033
+ contextPackId: z.string().min(1).optional(),
1034
+ outcome: implementationVerificationOutcomeSchema.optional(),
1035
+ result: implementationVerificationResultSchema.optional(),
1036
+ summary: z.string().trim().min(1).max(2e3).optional(),
1037
+ startedAt: isoDateTimeSchema.optional(),
1038
+ completedAt: isoDateTimeSchema.optional(),
1039
+ failedAt: isoDateTimeSchema.optional(),
1040
+ overriddenByUserId: z.string().min(1).optional(),
1041
+ overrideReason: z.string().trim().min(1).max(1e3).optional(),
1042
+ overriddenAt: isoDateTimeSchema.optional(),
1043
+ error: z.string().max(1e3).optional()
1044
+ });
968
1045
  var contextMissItemSchema = baseItemSchema.extend({
969
1046
  type: z.literal("contextMiss"),
970
1047
  projectId: z.string().min(1),
@@ -1275,6 +1352,7 @@ var activityEventItemSchema = baseItemSchema.extend({
1275
1352
  relatedSecurityFindingId: z.string().min(1).optional(),
1276
1353
  relatedAppEvaluationScanId: z.string().min(1).optional(),
1277
1354
  relatedAppEvaluationFindingId: z.string().min(1).optional(),
1355
+ relatedImplementationVerificationId: z.string().min(1).optional(),
1278
1356
  relatedProjectContextRefreshId: z.string().min(1).optional(),
1279
1357
  relatedProjectSystemDiagramId: z.string().min(1).optional(),
1280
1358
  relatedContextMissId: z.string().min(1).optional(),
@@ -1340,6 +1418,7 @@ var projectItemUnionSchema = z.discriminatedUnion("type", [
1340
1418
  securityPostureSnapshotItemSchema,
1341
1419
  appEvaluationScanItemSchema,
1342
1420
  appEvaluationFindingItemSchema,
1421
+ implementationVerificationItemSchema,
1343
1422
  projectContextMapItemSchema,
1344
1423
  projectContextRefreshItemSchema,
1345
1424
  projectSystemDiagramItemSchema,
@@ -2414,6 +2493,16 @@ var ApiClient = class {
2414
2493
  }
2415
2494
  );
2416
2495
  }
2496
+ async submitImplementationVerificationResult(projectId, workItemId, result) {
2497
+ return this.request(
2498
+ `/projects/${projectId}/work-items/${workItemId}/implementation-verification-result`,
2499
+ z3.object({ verification: implementationVerificationItemSchema, workItem: workItemSchema, sourceWorkItem: workItemSchema.optional() }),
2500
+ {
2501
+ method: "POST",
2502
+ body: JSON.stringify(result)
2503
+ }
2504
+ );
2505
+ }
2417
2506
  async request(urlPath, schema, init) {
2418
2507
  const response = await fetch(resolveApiUrl(this.options.apiUrl, urlPath), {
2419
2508
  ...init,
@@ -4467,9 +4556,60 @@ var appEvaluationStart = "AMISTIO_APP_EVALUATION_START";
4467
4556
  var appEvaluationEnd = "AMISTIO_APP_EVALUATION_END";
4468
4557
  var projectContextRefreshStart = "AMISTIO_PROJECT_CONTEXT_REFRESH_START";
4469
4558
  var projectContextRefreshEnd = "AMISTIO_PROJECT_CONTEXT_REFRESH_END";
4559
+ var implementationVerificationStart = "AMISTIO_IMPLEMENTATION_VERIFICATION_START";
4560
+ var implementationVerificationEnd = "AMISTIO_IMPLEMENTATION_VERIFICATION_END";
4470
4561
  var validProjectContextSliceKinds = /* @__PURE__ */ new Set(["overview", "architecture", "domain", "data", "api", "frontend", "backend", "cli", "workflow", "operations", "security", "testing", "unknown"]);
4471
4562
  var validProjectContextEntityTypes = /* @__PURE__ */ new Set(["project", "system", "component", "domain", "tool", "decision", "feature", "risk", "team", "workflow", "unknown"]);
4472
4563
  var validProjectContextRelationTypes = /* @__PURE__ */ new Set(["uses", "depends_on", "decides", "supersedes", "touches", "blocks", "implements", "mentions"]);
4564
+ function createImplementationVerificationPrompt(workItem) {
4565
+ return [
4566
+ "# Amistio Implementation Verification",
4567
+ "",
4568
+ "You are running locally through the Amistio CLI inside the user's repository.",
4569
+ "Verify whether the linked implementation work was actually completed. This is a read-only proof pass.",
4570
+ "Do not modify files, create branches, commit, install packages, run implementation prompts, or make destructive changes.",
4571
+ "Use local repository inspection and focused verification commands only when they are safe for this checkout.",
4572
+ "",
4573
+ "## Work Item",
4574
+ "",
4575
+ `Title: ${workItem.title}`,
4576
+ `Work item ID: ${workItem.workItemId}`,
4577
+ `Project ID: ${workItem.projectId}`,
4578
+ `Implementation verification ID: ${workItem.implementationVerificationId ?? "unknown"}`,
4579
+ `Source work item ID: ${workItem.sourceWorkItemId ?? "unknown"}`,
4580
+ "",
4581
+ "## Verification Request",
4582
+ "",
4583
+ workItem.sourceWish ?? "Verify that the linked implementation plan was completed in the local repository.",
4584
+ "",
4585
+ "## Verification Requirements",
4586
+ "",
4587
+ "- Compare the approved plan or prompt requirements against the current local repository state.",
4588
+ "- Look beyond runner status. Use source evidence, generated handoff details, PR/branch/worktree metadata when present, and targeted checks.",
4589
+ "- Treat missing acceptance criteria as an explicit gap instead of guessing.",
4590
+ "- Record evidence for at least one acceptance criterion with safe repository-relative citations.",
4591
+ "- Use outcome verifiedImplemented only when the implementation is materially present and checks support it.",
4592
+ "- Use partiallyImplemented, notImplemented, inconclusive, or verificationBlocked when evidence is incomplete, absent, contradictory, or checks cannot run.",
4593
+ "",
4594
+ "## Data Safety",
4595
+ "",
4596
+ "- Persist summaries, safe paths, short citation excerpts, and verification status only.",
4597
+ "- Do not include raw source dumps, secrets, env vars, process lists, absolute local paths, credential values, tokens, provider sessions, or destructive shell output.",
4598
+ "- Citations and safePaths must be repository-relative and must not use ../ traversal.",
4599
+ "",
4600
+ "## Output Contract",
4601
+ "",
4602
+ "Print exactly one JSON object between the markers below. The CLI will submit only this structured verification result back to Amistio.",
4603
+ "Accepted outcome values: verifiedImplemented, partiallyImplemented, notImplemented, inconclusive, verificationBlocked.",
4604
+ "Accepted recommendation values: none, requeue, createFollowUpPlan, requestHumanReview, markBlocked.",
4605
+ "",
4606
+ implementationVerificationStart,
4607
+ '{"outcome":"partiallyImplemented","summary":"The implementation is present in the workspace, but one acceptance check could not be confirmed.","evidence":[{"acceptanceCriterion":"The requested behavior is wired through the runner lifecycle.","status":"partial","summary":"The route and UI wiring exist, but the runner result submission still needs validation.","citations":[{"source":"localSource","repoPath":"src/apps/web/components/workspace-client.tsx","excerpt":"Implementation verification queued for the local runner."}]}],"checks":[{"name":"Focused typecheck","status":"passed","summary":"The touched package typechecked successfully.","safePaths":["src/apps/web"]}],"gaps":["Runner result submission was not exercised end to end."],"recommendation":"requestHumanReview","verificationPlan":["Run focused CLI tests","Review the verification evidence in Amistio"],"warnings":[]}',
4608
+ implementationVerificationEnd,
4609
+ "",
4610
+ "Do not put Markdown fences around the markers. Do not implement or fix anything during this verification pass."
4611
+ ].join("\n");
4612
+ }
4473
4613
  function createWorkExecutionPrompt(workItem, context) {
4474
4614
  if (workItem.workKind === "brainGeneration") {
4475
4615
  return createBrainGenerationPrompt(workItem);
@@ -4495,6 +4635,9 @@ function createWorkExecutionPrompt(workItem, context) {
4495
4635
  if (workItem.workKind === "projectContextRefresh") {
4496
4636
  return createProjectContextRefreshPrompt(workItem, context?.projectContextRefresh);
4497
4637
  }
4638
+ if (workItem.workKind === "implementationVerification") {
4639
+ return createImplementationVerificationPrompt(workItem);
4640
+ }
4498
4641
  return [
4499
4642
  "# Amistio Work Execution",
4500
4643
  "",
@@ -4645,6 +4788,7 @@ function createSecurityPostureScanPrompt(workItem, context) {
4645
4788
  "## Output Contract",
4646
4789
  "",
4647
4790
  "Print exactly one JSON object between the markers below. The CLI will submit only this structured scan result back to Amistio.",
4791
+ "Accepted category values: owasp, dependency, supplyChain, secretHygiene, authentication, authorization, tenantIsolation, headers, redirects, csrf, cors, ciCd, logging, rateLimiting, runnerBoundary, other.",
4648
4792
  "",
4649
4793
  securityPostureStart,
4650
4794
  '{"summary":"Security posture is mostly healthy, but dependency advisory review is not automated.","baselineVersion":"amistio-security-baseline-v1","postureScore":82,"postureGrade":"B","categorySummaries":[{"category":"dependency","status":"warning","summary":"Dependency audit automation is incomplete."}],"findings":[{"title":"Dependency audit is not enforced in CI","category":"dependency","severity":"medium","confidence":"high","summary":"The repository has a lockfile, but CI does not appear to fail on known vulnerable dependencies.","standardReferences":[{"standard":"OWASP ASVS","control":"V14.2 Dependency"}],"affectedSurfaces":["CI verification"],"evidence":["No dependency audit step was found in the CI verification summary."],"recommendedRemediation":"Add a dependency advisory check to the existing verification pipeline and document how failures are handled.","verificationPlan":["Run the updated CI verification command locally","Confirm a known advisory fails the check in a controlled test"],"safePaths":["package.json","pnpm-lock.yaml"]}],"verificationPlan":["Run focused dependency and CI checks","Review Security panel findings for approval"]}',
@@ -5045,6 +5189,16 @@ function parseProjectContextRefreshResult(output, options = {}) {
5045
5189
  const normalized = normalizeProjectContextRefreshPaths(normalizeProjectContextRefreshEnums(parsed), options);
5046
5190
  return projectContextRefreshResultSchema.parse(normalized);
5047
5191
  }
5192
+ function parseImplementationVerificationResult(output) {
5193
+ const start = output.indexOf(implementationVerificationStart);
5194
+ const end = output.indexOf(implementationVerificationEnd, start + implementationVerificationStart.length);
5195
+ if (start === -1 || end === -1 || end <= start) {
5196
+ throw new Error("Local AI verification did not return an Amistio implementation verification block.");
5197
+ }
5198
+ const payload = output.slice(start + implementationVerificationStart.length, end).trim();
5199
+ const parsed = JSON.parse(stripJsonFence(payload));
5200
+ return implementationVerificationResultSchema.parse(parsed);
5201
+ }
5048
5202
  function projectContextRefreshSubmissionFailureSummary(result) {
5049
5203
  if (result.refresh.status !== "failed" && result.workItem.status !== "failed") {
5050
5204
  return void 0;
@@ -6118,7 +6272,7 @@ var DEFAULT_MAX_PREFLIGHT_ATTEMPTS = 3;
6118
6272
  var DEFAULT_TOOL_TIMEOUT_SECONDS = 30 * 60;
6119
6273
  var RUNNER_WORK_LEASE_SECONDS = 300;
6120
6274
  var RUNNER_WORK_LEASE_RENEWAL_MS = 12e4;
6121
- var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "projectContextRefresh"];
6275
+ var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "projectContextRefresh", "implementationVerification"];
6122
6276
  program.name("amistio").description("Amistio project brain CLI").version(CLI_VERSION);
6123
6277
  program.command("init").description("Create Amistio control-plane folders for a new project").option("--root <path>", "Repository root", defaultRoot).action(async (options) => {
6124
6278
  const created = await initControlPlane(options.root);
@@ -7154,6 +7308,24 @@ async function runNextWorkItem({
7154
7308
  return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
7155
7309
  }
7156
7310
  }
7311
+ if (result.workItem.workKind === "implementationVerification") {
7312
+ try {
7313
+ return await finalizeImplementationVerificationWork({
7314
+ apiClient,
7315
+ durationMs: Date.now() - startedAt,
7316
+ projectId,
7317
+ repositoryLinkId,
7318
+ runnerId,
7319
+ sessionContext,
7320
+ toolConfig,
7321
+ toolName: preview.toolName,
7322
+ toolResult,
7323
+ workItem: result.workItem
7324
+ });
7325
+ } catch (error) {
7326
+ return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
7327
+ }
7328
+ }
7157
7329
  let finalStatus = toolResult.exitCode === 0 ? "completed" : "failed";
7158
7330
  const durationMs = Date.now() - startedAt;
7159
7331
  const failureExcerpt = toolResult.exitCode === 0 ? void 0 : truncateLogExcerpt(toolResult.stderr || toolResult.stdout);
@@ -8091,6 +8263,92 @@ ${toolResult.stderr}`, { repositoryRoot: executionRoot });
8091
8263
  console.error(refreshError ?? "Local runner project context refresh failed.");
8092
8264
  return { status: "failed", exitCode: toolResult.exitCode || 1 };
8093
8265
  }
8266
+ async function finalizeImplementationVerificationWork({
8267
+ apiClient,
8268
+ durationMs,
8269
+ projectId,
8270
+ repositoryLinkId,
8271
+ runnerId,
8272
+ sessionContext,
8273
+ toolConfig,
8274
+ toolName,
8275
+ toolResult,
8276
+ workItem
8277
+ }) {
8278
+ let verificationResult = void 0;
8279
+ let verificationError;
8280
+ if (toolResult.exitCode === 0) {
8281
+ try {
8282
+ verificationResult = parseImplementationVerificationResult(`${toolResult.stdout}
8283
+ ${toolResult.stderr}`);
8284
+ } catch (error) {
8285
+ verificationError = errorMessage3(error);
8286
+ }
8287
+ } else {
8288
+ verificationError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
8289
+ }
8290
+ const finalStatus = verificationResult ? "completed" : "failed";
8291
+ const updatedToolSession = await finalizeToolSession({
8292
+ apiClient,
8293
+ projectId,
8294
+ status: finalStatus,
8295
+ runnerId,
8296
+ workItemId: workItem.workItemId,
8297
+ stdout: toolResult.stdout,
8298
+ ...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
8299
+ ...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
8300
+ ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
8301
+ ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
8302
+ ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
8303
+ });
8304
+ const sessionTelemetry = {
8305
+ sessionPolicy: sessionContext.policy,
8306
+ sessionDecision: sessionContext.decision,
8307
+ sessionDecisionReason: sessionContext.reason,
8308
+ ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
8309
+ ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
8310
+ };
8311
+ if (verificationResult) {
8312
+ const result = await apiClient.submitImplementationVerificationResult(projectId, workItem.workItemId, {
8313
+ status: "completed",
8314
+ runnerId,
8315
+ idempotencyKey: `implementation_verification_${workItem.workItemId}_${randomUUID()}`,
8316
+ result: verificationResult,
8317
+ tool: toolName,
8318
+ durationMs,
8319
+ ...sessionTelemetry,
8320
+ message: `${toolName} returned implementation verification proof.`
8321
+ });
8322
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
8323
+ status: verificationResult.outcome === "verifiedImplemented" ? "completed" : verificationResult.outcome === "verificationBlocked" ? "blocked" : "warning",
8324
+ summary: verificationResult.summary,
8325
+ idempotencyKey: `runner_milestone_implementation_verification_completed_${workItem.workItemId}_${result.workItem.idempotencyKey}`,
8326
+ metadata: { tool: toolName, durationMs, outcome: verificationResult.outcome, recommendation: verificationResult.recommendation, gapCount: verificationResult.gaps.length, verificationSummary: verificationResult.verificationPlan.join(" | ") }
8327
+ });
8328
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
8329
+ console.log("Implementation verification returned for review.");
8330
+ return { status: "completed", exitCode: 0 };
8331
+ }
8332
+ const failedResult = await apiClient.submitImplementationVerificationResult(projectId, workItem.workItemId, {
8333
+ status: "failed",
8334
+ runnerId,
8335
+ idempotencyKey: `implementation_verification_${workItem.workItemId}_${randomUUID()}`,
8336
+ tool: toolName,
8337
+ durationMs,
8338
+ ...sessionTelemetry,
8339
+ message: `${toolName} did not produce valid implementation verification proof.`,
8340
+ ...verificationError ? { error: verificationError } : {}
8341
+ });
8342
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
8343
+ status: "failed",
8344
+ summary: verificationError ?? `${toolName} did not produce valid implementation verification proof.`,
8345
+ idempotencyKey: `runner_milestone_implementation_verification_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
8346
+ metadata: { tool: toolName, durationMs, verificationSummary: "Implementation verification output did not include valid structured JSON." }
8347
+ });
8348
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
8349
+ console.error(verificationError ?? "Local runner implementation verification failed.");
8350
+ return { status: "failed", exitCode: toolResult.exitCode || 1 };
8351
+ }
8094
8352
  async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
8095
8353
  if (workItem.workKind === "assistantQuestion") {
8096
8354
  const emptyProjectContext = { maps: [], refreshes: [], misses: [] };