@amistio/cli 0.1.18 → 0.1.20

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
@@ -9,13 +9,13 @@ npm install -g @amistio/cli
9
9
  amistio --help
10
10
  ```
11
11
 
12
- The package install only installs the `amistio` command. Repository cloning, project pairing, credential storage, sync watching, and runner execution happen only when the user explicitly runs commands such as `amistio bootstrap`, `amistio pair`, `amistio sync watch`, or `amistio run --watch`.
12
+ The package install only installs the `amistio` command. Repository cloning, project pairing, credential storage, sync watching, and runner execution happen only when the user explicitly runs commands such as `amistio bootstrap`, `amistio import`, `amistio pair`, `amistio sync watch`, or `amistio run --watch`. When the app copies a personal project into an organization, the CLI command syntax stays the same; create the org-scoped code and run `amistio import <code>` from the intended local checkout.
13
13
 
14
14
  Runner lifecycle controls in the web app, such as update, restart, and remove, apply only to the runner paired by that user unless the active organization role is an admin role. The runner API binds command polling, command status, logs, activity, and tool sessions to the local runner credential that produced them.
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"]);
@@ -125,6 +129,7 @@ var activityEventTypeSchema = z.enum([
125
129
  "documentGenerated",
126
130
  "documentReview",
127
131
  "documentSync",
132
+ "projectTransferred",
128
133
  "workQueued",
129
134
  "workClaimed",
130
135
  "workStatusChanged",
@@ -156,6 +161,14 @@ var activityEventTypeSchema = z.enum([
156
161
  "appEvaluationFindingReviewed",
157
162
  "appEvaluationPlanCreated",
158
163
  "appEvaluationImplementationQueued",
164
+ "workRequeueRequested",
165
+ "workRequeueQueued",
166
+ "workRequeueBlocked",
167
+ "implementationVerificationQueued",
168
+ "implementationVerificationStarted",
169
+ "implementationVerificationCompleted",
170
+ "implementationVerificationBlocked",
171
+ "implementationVerificationOverride",
159
172
  "projectContextRefreshQueued",
160
173
  "projectContextRefreshStarted",
161
174
  "projectContextRefreshCompleted",
@@ -335,7 +348,11 @@ var projectItemSchema = baseItemSchema.extend({
335
348
  status: projectStatusSchema.default("active"),
336
349
  archivedAt: isoDateTimeSchema.optional(),
337
350
  archivedByUserId: z.string().min(1).optional(),
338
- archiveReason: z.string().min(1).optional()
351
+ archiveReason: z.string().min(1).optional(),
352
+ transferredFromAccountId: z.string().min(1).optional(),
353
+ transferredFromProjectId: z.string().min(1).optional(),
354
+ transferredByUserId: z.string().min(1).optional(),
355
+ transferredAt: isoDateTimeSchema.optional()
339
356
  });
340
357
  var repositoryLinkItemSchema = baseItemSchema.extend({
341
358
  type: z.literal("repositoryLink"),
@@ -423,8 +440,20 @@ var workItemSchema = baseItemSchema.extend({
423
440
  securityFindingId: z.string().min(1).optional(),
424
441
  appEvaluationScanId: z.string().min(1).optional(),
425
442
  appEvaluationFindingId: z.string().min(1).optional(),
443
+ implementationVerificationId: z.string().min(1).optional(),
426
444
  projectContextRefreshId: z.string().min(1).optional(),
427
445
  contextMissId: z.string().min(1).optional(),
446
+ sourceWorkItemId: z.string().min(1).optional(),
447
+ requeueReason: z.string().trim().min(1).max(600).optional(),
448
+ requeuedByUserId: z.string().min(1).optional(),
449
+ requeuedAt: isoDateTimeSchema.optional(),
450
+ sourceAttempt: z.number().int().nonnegative().optional(),
451
+ sourceTerminalStatus: workStatusSchema.optional(),
452
+ sourceFailureSummary: z.string().trim().min(1).max(1e3).optional(),
453
+ implementationProofStatus: implementationProofStatusSchema.optional(),
454
+ implementationVerificationOutcome: implementationVerificationOutcomeSchema.optional(),
455
+ implementationVerificationResultId: z.string().min(1).optional(),
456
+ humanImplementationOverrideReason: z.string().trim().min(1).max(1e3).optional(),
428
457
  repositoryLinkId: z.string().min(1).optional(),
429
458
  reviewThreadId: z.string().min(1).optional(),
430
459
  reviewDocumentId: z.string().min(1).optional(),
@@ -965,6 +994,59 @@ var projectContextPackSchema = z.object({
965
994
  tokenEstimate: z.number().int().nonnegative().default(0),
966
995
  generatedAt: isoDateTimeSchema
967
996
  });
997
+ var implementationVerificationCheckStatusSchema = z.enum(["passed", "failed", "blocked", "skipped"]);
998
+ var implementationVerificationRecommendationSchema = z.enum(["none", "requeue", "createFollowUpPlan", "requestHumanReview", "markBlocked"]);
999
+ var implementationVerificationEvidenceSchema = z.object({
1000
+ acceptanceCriterion: z.string().trim().min(1).max(500),
1001
+ status: z.enum(["satisfied", "partial", "missing", "unknown"]),
1002
+ summary: z.string().trim().min(1).max(1200),
1003
+ citations: z.array(projectContextCitationSchema).default([])
1004
+ });
1005
+ var implementationVerificationCheckSchema = z.object({
1006
+ name: z.string().trim().min(1).max(200),
1007
+ status: implementationVerificationCheckStatusSchema,
1008
+ summary: z.string().trim().min(1).max(1200),
1009
+ safePaths: z.array(projectContextRepoPathSchema).default([])
1010
+ });
1011
+ var implementationVerificationResultSchema = z.object({
1012
+ outcome: implementationVerificationOutcomeSchema,
1013
+ summary: z.string().trim().min(1).max(2e3),
1014
+ evidence: z.array(implementationVerificationEvidenceSchema).min(1),
1015
+ checks: z.array(implementationVerificationCheckSchema).default([]),
1016
+ gaps: z.array(z.string().trim().min(1).max(600)).default([]),
1017
+ branch: z.string().trim().min(1).max(200).optional(),
1018
+ pullRequestUrl: z.string().trim().url().max(500).optional(),
1019
+ worktreeKey: z.string().trim().min(1).max(300).optional(),
1020
+ recommendation: implementationVerificationRecommendationSchema.default("none"),
1021
+ verificationPlan: z.array(z.string().trim().min(1).max(300)).default([]),
1022
+ warnings: z.array(z.string().trim().min(1).max(600)).default([])
1023
+ });
1024
+ var implementationVerificationItemSchema = baseItemSchema.extend({
1025
+ type: z.literal("implementationVerification"),
1026
+ projectId: z.string().min(1),
1027
+ implementationVerificationId: z.string().min(1),
1028
+ status: implementationVerificationStatusSchema,
1029
+ source: z.enum(["manual", "runner", "appEvaluation", "planCleanup", "system"]).default("manual"),
1030
+ planDocumentId: z.string().min(1).optional(),
1031
+ planDocumentRevision: z.number().int().nonnegative().optional(),
1032
+ sourceWorkItemId: z.string().min(1).optional(),
1033
+ verificationWorkItemId: z.string().min(1).optional(),
1034
+ repositoryLinkId: z.string().min(1).optional(),
1035
+ runnerId: z.string().min(1).optional(),
1036
+ sourceRevision: z.string().trim().min(1).max(160).optional(),
1037
+ brainRevision: z.number().int().nonnegative().optional(),
1038
+ contextPackId: z.string().min(1).optional(),
1039
+ outcome: implementationVerificationOutcomeSchema.optional(),
1040
+ result: implementationVerificationResultSchema.optional(),
1041
+ summary: z.string().trim().min(1).max(2e3).optional(),
1042
+ startedAt: isoDateTimeSchema.optional(),
1043
+ completedAt: isoDateTimeSchema.optional(),
1044
+ failedAt: isoDateTimeSchema.optional(),
1045
+ overriddenByUserId: z.string().min(1).optional(),
1046
+ overrideReason: z.string().trim().min(1).max(1e3).optional(),
1047
+ overriddenAt: isoDateTimeSchema.optional(),
1048
+ error: z.string().max(1e3).optional()
1049
+ });
968
1050
  var contextMissItemSchema = baseItemSchema.extend({
969
1051
  type: z.literal("contextMiss"),
970
1052
  projectId: z.string().min(1),
@@ -1275,6 +1357,7 @@ var activityEventItemSchema = baseItemSchema.extend({
1275
1357
  relatedSecurityFindingId: z.string().min(1).optional(),
1276
1358
  relatedAppEvaluationScanId: z.string().min(1).optional(),
1277
1359
  relatedAppEvaluationFindingId: z.string().min(1).optional(),
1360
+ relatedImplementationVerificationId: z.string().min(1).optional(),
1278
1361
  relatedProjectContextRefreshId: z.string().min(1).optional(),
1279
1362
  relatedProjectSystemDiagramId: z.string().min(1).optional(),
1280
1363
  relatedContextMissId: z.string().min(1).optional(),
@@ -1340,6 +1423,7 @@ var projectItemUnionSchema = z.discriminatedUnion("type", [
1340
1423
  securityPostureSnapshotItemSchema,
1341
1424
  appEvaluationScanItemSchema,
1342
1425
  appEvaluationFindingItemSchema,
1426
+ implementationVerificationItemSchema,
1343
1427
  projectContextMapItemSchema,
1344
1428
  projectContextRefreshItemSchema,
1345
1429
  projectSystemDiagramItemSchema,
@@ -2414,6 +2498,16 @@ var ApiClient = class {
2414
2498
  }
2415
2499
  );
2416
2500
  }
2501
+ async submitImplementationVerificationResult(projectId, workItemId, result) {
2502
+ return this.request(
2503
+ `/projects/${projectId}/work-items/${workItemId}/implementation-verification-result`,
2504
+ z3.object({ verification: implementationVerificationItemSchema, workItem: workItemSchema, sourceWorkItem: workItemSchema.optional() }),
2505
+ {
2506
+ method: "POST",
2507
+ body: JSON.stringify(result)
2508
+ }
2509
+ );
2510
+ }
2417
2511
  async request(urlPath, schema, init) {
2418
2512
  const response = await fetch(resolveApiUrl(this.options.apiUrl, urlPath), {
2419
2513
  ...init,
@@ -4467,9 +4561,64 @@ var appEvaluationStart = "AMISTIO_APP_EVALUATION_START";
4467
4561
  var appEvaluationEnd = "AMISTIO_APP_EVALUATION_END";
4468
4562
  var projectContextRefreshStart = "AMISTIO_PROJECT_CONTEXT_REFRESH_START";
4469
4563
  var projectContextRefreshEnd = "AMISTIO_PROJECT_CONTEXT_REFRESH_END";
4564
+ var implementationVerificationStart = "AMISTIO_IMPLEMENTATION_VERIFICATION_START";
4565
+ var implementationVerificationEnd = "AMISTIO_IMPLEMENTATION_VERIFICATION_END";
4566
+ var projectContextMissingAreaMaxLength = 160;
4567
+ var projectContextCoverageWarningMaxLength = 300;
4568
+ var projectContextVerificationPlanMaxLength = 300;
4569
+ var projectContextTopLevelWarningMaxLength = 600;
4470
4570
  var validProjectContextSliceKinds = /* @__PURE__ */ new Set(["overview", "architecture", "domain", "data", "api", "frontend", "backend", "cli", "workflow", "operations", "security", "testing", "unknown"]);
4471
4571
  var validProjectContextEntityTypes = /* @__PURE__ */ new Set(["project", "system", "component", "domain", "tool", "decision", "feature", "risk", "team", "workflow", "unknown"]);
4472
4572
  var validProjectContextRelationTypes = /* @__PURE__ */ new Set(["uses", "depends_on", "decides", "supersedes", "touches", "blocks", "implements", "mentions"]);
4573
+ function createImplementationVerificationPrompt(workItem) {
4574
+ return [
4575
+ "# Amistio Implementation Verification",
4576
+ "",
4577
+ "You are running locally through the Amistio CLI inside the user's repository.",
4578
+ "Verify whether the linked implementation work was actually completed. This is a read-only proof pass.",
4579
+ "Do not modify files, create branches, commit, install packages, run implementation prompts, or make destructive changes.",
4580
+ "Use local repository inspection and focused verification commands only when they are safe for this checkout.",
4581
+ "",
4582
+ "## Work Item",
4583
+ "",
4584
+ `Title: ${workItem.title}`,
4585
+ `Work item ID: ${workItem.workItemId}`,
4586
+ `Project ID: ${workItem.projectId}`,
4587
+ `Implementation verification ID: ${workItem.implementationVerificationId ?? "unknown"}`,
4588
+ `Source work item ID: ${workItem.sourceWorkItemId ?? "unknown"}`,
4589
+ "",
4590
+ "## Verification Request",
4591
+ "",
4592
+ workItem.sourceWish ?? "Verify that the linked implementation plan was completed in the local repository.",
4593
+ "",
4594
+ "## Verification Requirements",
4595
+ "",
4596
+ "- Compare the approved plan or prompt requirements against the current local repository state.",
4597
+ "- Look beyond runner status. Use source evidence, generated handoff details, PR/branch/worktree metadata when present, and targeted checks.",
4598
+ "- Treat missing acceptance criteria as an explicit gap instead of guessing.",
4599
+ "- Record evidence for at least one acceptance criterion with safe repository-relative citations.",
4600
+ "- Use outcome verifiedImplemented only when the implementation is materially present and checks support it.",
4601
+ "- Use partiallyImplemented, notImplemented, inconclusive, or verificationBlocked when evidence is incomplete, absent, contradictory, or checks cannot run.",
4602
+ "",
4603
+ "## Data Safety",
4604
+ "",
4605
+ "- Persist summaries, safe paths, short citation excerpts, and verification status only.",
4606
+ "- Do not include raw source dumps, secrets, env vars, process lists, absolute local paths, credential values, tokens, provider sessions, or destructive shell output.",
4607
+ "- Citations and safePaths must be repository-relative and must not use ../ traversal.",
4608
+ "",
4609
+ "## Output Contract",
4610
+ "",
4611
+ "Print exactly one JSON object between the markers below. The CLI will submit only this structured verification result back to Amistio.",
4612
+ "Accepted outcome values: verifiedImplemented, partiallyImplemented, notImplemented, inconclusive, verificationBlocked.",
4613
+ "Accepted recommendation values: none, requeue, createFollowUpPlan, requestHumanReview, markBlocked.",
4614
+ "",
4615
+ implementationVerificationStart,
4616
+ '{"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":[]}',
4617
+ implementationVerificationEnd,
4618
+ "",
4619
+ "Do not put Markdown fences around the markers. Do not implement or fix anything during this verification pass."
4620
+ ].join("\n");
4621
+ }
4473
4622
  function createWorkExecutionPrompt(workItem, context) {
4474
4623
  if (workItem.workKind === "brainGeneration") {
4475
4624
  return createBrainGenerationPrompt(workItem);
@@ -4495,6 +4644,9 @@ function createWorkExecutionPrompt(workItem, context) {
4495
4644
  if (workItem.workKind === "projectContextRefresh") {
4496
4645
  return createProjectContextRefreshPrompt(workItem, context?.projectContextRefresh);
4497
4646
  }
4647
+ if (workItem.workKind === "implementationVerification") {
4648
+ return createImplementationVerificationPrompt(workItem);
4649
+ }
4498
4650
  return [
4499
4651
  "# Amistio Work Execution",
4500
4652
  "",
@@ -4576,6 +4728,8 @@ function createProjectContextRefreshPrompt(workItem, context) {
4576
4728
  "- Capture entities and relations that explain how the app is put together and where future work should look first.",
4577
4729
  "- Prefer summaries, repository-relative paths, short citations, tags, and freshness status over raw source excerpts.",
4578
4730
  "- Mark stale or missing areas explicitly instead of guessing.",
4731
+ "- Keep coverage.missingAreas entries concise noun phrases under 160 characters.",
4732
+ "- Keep coverage.warnings and verificationPlan entries under 300 characters; keep top-level warnings under 600 characters.",
4579
4733
  "- Keep repoPaths repository-relative; never include /absolute paths or ../ traversal.",
4580
4734
  "- If local inspection prints a path inside this checkout, convert it to the repository-relative path before returning JSON.",
4581
4735
  "",
@@ -4645,6 +4799,7 @@ function createSecurityPostureScanPrompt(workItem, context) {
4645
4799
  "## Output Contract",
4646
4800
  "",
4647
4801
  "Print exactly one JSON object between the markers below. The CLI will submit only this structured scan result back to Amistio.",
4802
+ "Accepted category values: owasp, dependency, supplyChain, secretHygiene, authentication, authorization, tenantIsolation, headers, redirects, csrf, cors, ciCd, logging, rateLimiting, runnerBoundary, other.",
4648
4803
  "",
4649
4804
  securityPostureStart,
4650
4805
  '{"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"]}',
@@ -5042,9 +5197,19 @@ function parseProjectContextRefreshResult(output, options = {}) {
5042
5197
  }
5043
5198
  const payload = output.slice(start + projectContextRefreshStart.length, end).trim();
5044
5199
  const parsed = JSON.parse(stripJsonFence(payload));
5045
- const normalized = normalizeProjectContextRefreshPaths(normalizeProjectContextRefreshEnums(parsed), options);
5200
+ const normalized = normalizeProjectContextRefreshBoundedText(normalizeProjectContextRefreshPaths(normalizeProjectContextRefreshEnums(parsed), options));
5046
5201
  return projectContextRefreshResultSchema.parse(normalized);
5047
5202
  }
5203
+ function parseImplementationVerificationResult(output) {
5204
+ const start = output.indexOf(implementationVerificationStart);
5205
+ const end = output.indexOf(implementationVerificationEnd, start + implementationVerificationStart.length);
5206
+ if (start === -1 || end === -1 || end <= start) {
5207
+ throw new Error("Local AI verification did not return an Amistio implementation verification block.");
5208
+ }
5209
+ const payload = output.slice(start + implementationVerificationStart.length, end).trim();
5210
+ const parsed = JSON.parse(stripJsonFence(payload));
5211
+ return implementationVerificationResultSchema.parse(parsed);
5212
+ }
5048
5213
  function projectContextRefreshSubmissionFailureSummary(result) {
5049
5214
  if (result.refresh.status !== "failed" && result.workItem.status !== "failed") {
5050
5215
  return void 0;
@@ -5162,6 +5327,42 @@ function normalizeProjectContextSliceKind(value) {
5162
5327
  }
5163
5328
  return "unknown";
5164
5329
  }
5330
+ function normalizeProjectContextRefreshBoundedText(value) {
5331
+ if (!isObjectRecord(value)) {
5332
+ return value;
5333
+ }
5334
+ const normalized = { ...value };
5335
+ if (isObjectRecord(normalized.coverage)) {
5336
+ const coverage = { ...normalized.coverage };
5337
+ coverage.missingAreas = normalizeBoundedStringArray(coverage.missingAreas, projectContextMissingAreaMaxLength);
5338
+ coverage.warnings = normalizeBoundedStringArray(coverage.warnings, projectContextCoverageWarningMaxLength);
5339
+ normalized.coverage = coverage;
5340
+ }
5341
+ normalized.verificationPlan = normalizeBoundedStringArray(normalized.verificationPlan, projectContextVerificationPlanMaxLength);
5342
+ normalized.warnings = normalizeBoundedStringArray(normalized.warnings, projectContextTopLevelWarningMaxLength);
5343
+ return normalized;
5344
+ }
5345
+ function normalizeBoundedStringArray(value, maxLength) {
5346
+ if (!Array.isArray(value)) {
5347
+ return value;
5348
+ }
5349
+ return value.flatMap((entry) => {
5350
+ if (typeof entry !== "string") {
5351
+ return [entry];
5352
+ }
5353
+ const trimmed = entry.trim();
5354
+ return trimmed ? [shortenBoundedString(trimmed, maxLength)] : [];
5355
+ });
5356
+ }
5357
+ function shortenBoundedString(value, maxLength) {
5358
+ if (value.length <= maxLength) {
5359
+ return value;
5360
+ }
5361
+ if (maxLength <= 3) {
5362
+ return value.slice(0, maxLength);
5363
+ }
5364
+ return `${value.slice(0, maxLength - 3).trimEnd()}...`;
5365
+ }
5165
5366
  function normalizeProjectContextRefreshPaths(value, options) {
5166
5367
  if (!isObjectRecord(value)) {
5167
5368
  return value;
@@ -6118,7 +6319,7 @@ var DEFAULT_MAX_PREFLIGHT_ATTEMPTS = 3;
6118
6319
  var DEFAULT_TOOL_TIMEOUT_SECONDS = 30 * 60;
6119
6320
  var RUNNER_WORK_LEASE_SECONDS = 300;
6120
6321
  var RUNNER_WORK_LEASE_RENEWAL_MS = 12e4;
6121
- var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "projectContextRefresh"];
6322
+ var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "projectContextRefresh", "implementationVerification"];
6122
6323
  program.name("amistio").description("Amistio project brain CLI").version(CLI_VERSION);
6123
6324
  program.command("init").description("Create Amistio control-plane folders for a new project").option("--root <path>", "Repository root", defaultRoot).action(async (options) => {
6124
6325
  const created = await initControlPlane(options.root);
@@ -7154,6 +7355,24 @@ async function runNextWorkItem({
7154
7355
  return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
7155
7356
  }
7156
7357
  }
7358
+ if (result.workItem.workKind === "implementationVerification") {
7359
+ try {
7360
+ return await finalizeImplementationVerificationWork({
7361
+ apiClient,
7362
+ durationMs: Date.now() - startedAt,
7363
+ projectId,
7364
+ repositoryLinkId,
7365
+ runnerId,
7366
+ sessionContext,
7367
+ toolConfig,
7368
+ toolName: preview.toolName,
7369
+ toolResult,
7370
+ workItem: result.workItem
7371
+ });
7372
+ } catch (error) {
7373
+ return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
7374
+ }
7375
+ }
7157
7376
  let finalStatus = toolResult.exitCode === 0 ? "completed" : "failed";
7158
7377
  const durationMs = Date.now() - startedAt;
7159
7378
  const failureExcerpt = toolResult.exitCode === 0 ? void 0 : truncateLogExcerpt(toolResult.stderr || toolResult.stdout);
@@ -8091,6 +8310,92 @@ ${toolResult.stderr}`, { repositoryRoot: executionRoot });
8091
8310
  console.error(refreshError ?? "Local runner project context refresh failed.");
8092
8311
  return { status: "failed", exitCode: toolResult.exitCode || 1 };
8093
8312
  }
8313
+ async function finalizeImplementationVerificationWork({
8314
+ apiClient,
8315
+ durationMs,
8316
+ projectId,
8317
+ repositoryLinkId,
8318
+ runnerId,
8319
+ sessionContext,
8320
+ toolConfig,
8321
+ toolName,
8322
+ toolResult,
8323
+ workItem
8324
+ }) {
8325
+ let verificationResult = void 0;
8326
+ let verificationError;
8327
+ if (toolResult.exitCode === 0) {
8328
+ try {
8329
+ verificationResult = parseImplementationVerificationResult(`${toolResult.stdout}
8330
+ ${toolResult.stderr}`);
8331
+ } catch (error) {
8332
+ verificationError = errorMessage3(error);
8333
+ }
8334
+ } else {
8335
+ verificationError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
8336
+ }
8337
+ const finalStatus = verificationResult ? "completed" : "failed";
8338
+ const updatedToolSession = await finalizeToolSession({
8339
+ apiClient,
8340
+ projectId,
8341
+ status: finalStatus,
8342
+ runnerId,
8343
+ workItemId: workItem.workItemId,
8344
+ stdout: toolResult.stdout,
8345
+ ...sessionContext.toolSession ? { session: sessionContext.toolSession } : {},
8346
+ ...toolResult.messageCount !== void 0 ? { messageCount: toolResult.messageCount } : {},
8347
+ ...toolResult.tokensIn !== void 0 ? { tokensIn: toolResult.tokensIn } : {},
8348
+ ...toolResult.tokensOut !== void 0 ? { tokensOut: toolResult.tokensOut } : {},
8349
+ ...toolResult.costUsd !== void 0 ? { costUsd: toolResult.costUsd } : {}
8350
+ });
8351
+ const sessionTelemetry = {
8352
+ sessionPolicy: sessionContext.policy,
8353
+ sessionDecision: sessionContext.decision,
8354
+ sessionDecisionReason: sessionContext.reason,
8355
+ ...updatedToolSession ? { toolSessionId: updatedToolSession.toolSessionId } : {},
8356
+ ...updatedToolSession?.sessionGroupKey ? { sessionGroupKey: updatedToolSession.sessionGroupKey } : {}
8357
+ };
8358
+ if (verificationResult) {
8359
+ const result = await apiClient.submitImplementationVerificationResult(projectId, workItem.workItemId, {
8360
+ status: "completed",
8361
+ runnerId,
8362
+ idempotencyKey: `implementation_verification_${workItem.workItemId}_${randomUUID()}`,
8363
+ result: verificationResult,
8364
+ tool: toolName,
8365
+ durationMs,
8366
+ ...sessionTelemetry,
8367
+ message: `${toolName} returned implementation verification proof.`
8368
+ });
8369
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
8370
+ status: verificationResult.outcome === "verifiedImplemented" ? "completed" : verificationResult.outcome === "verificationBlocked" ? "blocked" : "warning",
8371
+ summary: verificationResult.summary,
8372
+ idempotencyKey: `runner_milestone_implementation_verification_completed_${workItem.workItemId}_${result.workItem.idempotencyKey}`,
8373
+ metadata: { tool: toolName, durationMs, outcome: verificationResult.outcome, recommendation: verificationResult.recommendation, gapCount: verificationResult.gaps.length, verificationSummary: verificationResult.verificationPlan.join(" | ") }
8374
+ });
8375
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
8376
+ console.log("Implementation verification returned for review.");
8377
+ return { status: "completed", exitCode: 0 };
8378
+ }
8379
+ const failedResult = await apiClient.submitImplementationVerificationResult(projectId, workItem.workItemId, {
8380
+ status: "failed",
8381
+ runnerId,
8382
+ idempotencyKey: `implementation_verification_${workItem.workItemId}_${randomUUID()}`,
8383
+ tool: toolName,
8384
+ durationMs,
8385
+ ...sessionTelemetry,
8386
+ message: `${toolName} did not produce valid implementation verification proof.`,
8387
+ ...verificationError ? { error: verificationError } : {}
8388
+ });
8389
+ await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
8390
+ status: "failed",
8391
+ summary: verificationError ?? `${toolName} did not produce valid implementation verification proof.`,
8392
+ idempotencyKey: `runner_milestone_implementation_verification_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
8393
+ metadata: { tool: toolName, durationMs, verificationSummary: "Implementation verification output did not include valid structured JSON." }
8394
+ });
8395
+ await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
8396
+ console.error(verificationError ?? "Local runner implementation verification failed.");
8397
+ return { status: "failed", exitCode: toolResult.exitCode || 1 };
8398
+ }
8094
8399
  async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
8095
8400
  if (workItem.workKind === "assistantQuestion") {
8096
8401
  const emptyProjectContext = { maps: [], refreshes: [], misses: [] };