@amistio/cli 0.1.30 → 0.1.32
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 +4 -2
- package/dist/index.js +322 -13
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,9 +15,11 @@ 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, 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.
|
|
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
|
+
|
|
22
|
+
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 explicit HTML artifacts under `docs/html/<area>/`, including local ADRs, plans, prompts, workflows, memory, context, architecture, and feature docs, to the app for review. Markdown is the default generation format; HTML appears only when a runner or user explicitly generated an HTML artifact. `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
23
|
|
|
22
24
|
Repository autopilot is disabled until the repository link option is enabled in the app. When enabled, Amistio can attach an audited low-risk autopilot authorization to eligible runner work, including generated brain approval, impact preview, issue diagnosis, security posture scan, app evaluation cleanup, low-risk implementation handoff, requeue, and implementation verification. The Runner panel shows and updates safe work scopes, allowed candidate types, max risk, optional runner binding, daily/concurrent/failure budgets, expiry/review/cooldown windows, and pause state. The CLI shows authorization id, candidate id/type, outcome, policy version, and work kind in `amistio work list`, claim logs, runner prompts, and milestone activity. Autopilot does not widen local runner permissions: pairing, supported work kinds, runner identity, Git worktree isolation, redaction, local-tool permission controls, and unsafe/review-required/blocked/paused/budget stops still apply.
|
|
23
25
|
|
package/dist/index.js
CHANGED
|
@@ -226,6 +226,7 @@ var autopilotCandidateLinksSchema = z.object({
|
|
|
226
226
|
securityFindingId: z.string().min(1).optional(),
|
|
227
227
|
appEvaluationScanId: z.string().min(1).optional(),
|
|
228
228
|
appEvaluationFindingId: z.string().min(1).optional(),
|
|
229
|
+
brainConsolidationScanId: z.string().min(1).optional(),
|
|
229
230
|
implementationVerificationId: z.string().min(1).optional(),
|
|
230
231
|
testQualityScanId: z.string().min(1).optional(),
|
|
231
232
|
testQualityFindingId: z.string().min(1).optional(),
|
|
@@ -313,6 +314,9 @@ var brainConsolidationScanSourceSchema = z.enum(["manual", "system", "runner"]);
|
|
|
313
314
|
var brainConsolidationProposalStatusSchema = z.enum(["open", "approved", "changesRequested", "dismissed", "acceptedRisk", "resolved", "failed"]);
|
|
314
315
|
var brainConsolidationProposalActionSchema = z.enum(["archiveDuplicates", "supersedeDuplicates", "keepCanonical", "manualReview"]);
|
|
315
316
|
var brainConsolidationRiskLevelSchema = z.enum(["low", "medium", "high", "critical"]);
|
|
317
|
+
var brainConsolidationRecommendationCoverageSchema = z.enum(["full", "partial", "notCovered", "ambiguous"]);
|
|
318
|
+
var brainConsolidationRecommendationConfidenceSchema = z.enum(["low", "medium", "high"]);
|
|
319
|
+
var brainConsolidationRecommendationActionSchema = z.enum(["archiveDuplicate", "proposeArchive", "manualReview", "skip"]);
|
|
316
320
|
var activityEventTypeSchema = z.enum([
|
|
317
321
|
"autopilotAuthorized",
|
|
318
322
|
"autopilotReviewRequired",
|
|
@@ -633,6 +637,9 @@ var brainConsolidationScanItemSchema = baseItemSchema.extend({
|
|
|
633
637
|
brainConsolidationScanId: z.string().min(1),
|
|
634
638
|
source: brainConsolidationScanSourceSchema,
|
|
635
639
|
status: brainConsolidationScanStatusSchema,
|
|
640
|
+
workItemId: z.string().min(1).optional(),
|
|
641
|
+
repositoryLinkId: z.string().min(1).optional(),
|
|
642
|
+
runnerId: z.string().min(1).optional(),
|
|
636
643
|
activeReviewCount: z.number().int().nonnegative(),
|
|
637
644
|
exactDuplicateGroupCount: z.number().int().nonnegative(),
|
|
638
645
|
autoArchivedCount: z.number().int().nonnegative(),
|
|
@@ -659,6 +666,22 @@ var brainConsolidationProposalItemSchema = baseItemSchema.extend({
|
|
|
659
666
|
reviewedByUserId: z.string().min(1).optional(),
|
|
660
667
|
reviewedAt: isoDateTimeSchema.optional()
|
|
661
668
|
});
|
|
669
|
+
var brainConsolidationRecommendationSchema = z.object({
|
|
670
|
+
recommendationId: z.string().trim().min(1).max(160).optional(),
|
|
671
|
+
canonicalDocumentId: z.string().min(1),
|
|
672
|
+
candidateDocumentIds: z.array(z.string().min(1)).min(1).max(25),
|
|
673
|
+
coverage: brainConsolidationRecommendationCoverageSchema,
|
|
674
|
+
confidence: brainConsolidationRecommendationConfidenceSchema.default("medium"),
|
|
675
|
+
recommendedAction: brainConsolidationRecommendationActionSchema,
|
|
676
|
+
riskLevel: brainConsolidationRiskLevelSchema.default("medium"),
|
|
677
|
+
evidence: z.array(z.string().trim().min(1).max(300)).min(1).max(10),
|
|
678
|
+
reason: z.string().trim().min(1).max(800)
|
|
679
|
+
});
|
|
680
|
+
var brainConsolidationScanResultSchema = z.object({
|
|
681
|
+
summary: z.string().trim().min(1).max(2e3),
|
|
682
|
+
recommendations: z.array(brainConsolidationRecommendationSchema).max(100).default([]),
|
|
683
|
+
warnings: z.array(z.string().trim().min(1).max(600)).default([])
|
|
684
|
+
});
|
|
662
685
|
var generatedDraftItemSchema = baseItemSchema.extend({
|
|
663
686
|
type: z.literal("generatedDraft"),
|
|
664
687
|
projectId: z.string().min(1),
|
|
@@ -709,6 +732,7 @@ var workItemSchema = baseItemSchema.extend({
|
|
|
709
732
|
securityFindingId: z.string().min(1).optional(),
|
|
710
733
|
appEvaluationScanId: z.string().min(1).optional(),
|
|
711
734
|
appEvaluationFindingId: z.string().min(1).optional(),
|
|
735
|
+
brainConsolidationScanId: z.string().min(1).optional(),
|
|
712
736
|
implementationVerificationId: z.string().min(1).optional(),
|
|
713
737
|
testQualityScanId: z.string().min(1).optional(),
|
|
714
738
|
testQualityFindingId: z.string().min(1).optional(),
|
|
@@ -2975,6 +2999,16 @@ var ApiClient = class {
|
|
|
2975
2999
|
}
|
|
2976
3000
|
);
|
|
2977
3001
|
}
|
|
3002
|
+
async submitBrainConsolidationScanResult(projectId, workItemId, result) {
|
|
3003
|
+
return this.request(
|
|
3004
|
+
`/projects/${projectId}/work-items/${workItemId}/brain-consolidation-result`,
|
|
3005
|
+
z3.object({ scan: brainConsolidationScanItemSchema, proposals: z3.array(brainConsolidationProposalItemSchema), autoArchivedDocuments: z3.array(brainDocumentItemSchema), workItem: workItemSchema }),
|
|
3006
|
+
{
|
|
3007
|
+
method: "POST",
|
|
3008
|
+
body: JSON.stringify(result)
|
|
3009
|
+
}
|
|
3010
|
+
);
|
|
3011
|
+
}
|
|
2978
3012
|
async submitProjectContextRefreshResult(projectId, workItemId, result) {
|
|
2979
3013
|
return this.request(
|
|
2980
3014
|
`/projects/${projectId}/work-items/${workItemId}/project-context-result`,
|
|
@@ -3233,6 +3267,13 @@ var appEvaluationScanResultMutationSchema = z4.discriminatedUnion("status", [
|
|
|
3233
3267
|
}),
|
|
3234
3268
|
resultFinalizationFailureSchema
|
|
3235
3269
|
]);
|
|
3270
|
+
var brainConsolidationScanResultMutationSchema = z4.discriminatedUnion("status", [
|
|
3271
|
+
resultFinalizationTelemetrySchema.extend({
|
|
3272
|
+
status: z4.literal("completed"),
|
|
3273
|
+
result: brainConsolidationScanResultSchema
|
|
3274
|
+
}),
|
|
3275
|
+
resultFinalizationFailureSchema
|
|
3276
|
+
]);
|
|
3236
3277
|
var projectContextRefreshResultMutationSchema = z4.discriminatedUnion("status", [
|
|
3237
3278
|
resultFinalizationTelemetrySchema.extend({
|
|
3238
3279
|
status: z4.literal("completed"),
|
|
@@ -3267,6 +3308,7 @@ var durableResultMutationSchema = z4.discriminatedUnion("resultKind", [
|
|
|
3267
3308
|
z4.object({ resultKind: z4.literal("issueDiagnosisResult"), result: issueDiagnosisResultMutationSchema }),
|
|
3268
3309
|
z4.object({ resultKind: z4.literal("securityPostureScanResult"), result: securityPostureScanResultMutationSchema }),
|
|
3269
3310
|
z4.object({ resultKind: z4.literal("appEvaluationScanResult"), result: appEvaluationScanResultMutationSchema }),
|
|
3311
|
+
z4.object({ resultKind: z4.literal("brainConsolidationScanResult"), result: brainConsolidationScanResultMutationSchema }),
|
|
3270
3312
|
z4.object({ resultKind: z4.literal("projectContextRefreshResult"), result: projectContextRefreshResultMutationSchema }),
|
|
3271
3313
|
z4.object({ resultKind: z4.literal("implementationVerificationResult"), result: implementationVerificationResultMutationSchema }),
|
|
3272
3314
|
z4.object({ resultKind: z4.literal("testQualityScanResult"), result: testQualityScanResultMutationSchema }),
|
|
@@ -3281,7 +3323,7 @@ var durableResultFinalizationEntrySchema = z4.object({
|
|
|
3281
3323
|
repositoryLinkId: z4.string().min(1),
|
|
3282
3324
|
runnerId: z4.string().min(1),
|
|
3283
3325
|
workItemId: z4.string().min(1),
|
|
3284
|
-
workKind: z4.enum(["assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "projectContextRefresh", "implementationVerification", "testQualityScan", "implementationTestGate"]),
|
|
3326
|
+
workKind: z4.enum(["assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "brainConsolidationScan", "projectContextRefresh", "implementationVerification", "testQualityScan", "implementationTestGate"]),
|
|
3285
3327
|
attempt: z4.number().int().nonnegative(),
|
|
3286
3328
|
idempotencyKey: z4.string().min(1),
|
|
3287
3329
|
retryCount: z4.number().int().nonnegative(),
|
|
@@ -4698,8 +4740,32 @@ function runProcess(command, args, timeoutMs) {
|
|
|
4698
4740
|
});
|
|
4699
4741
|
}
|
|
4700
4742
|
|
|
4743
|
+
// src/tool-session-lifecycle.ts
|
|
4744
|
+
var TOOL_SESSION_MAX_IDLE_MS = 6 * 60 * 60 * 1e3;
|
|
4745
|
+
function completedToolSessionStatus(session) {
|
|
4746
|
+
return session.resumabilityScope === "none" ? "closed" : "open";
|
|
4747
|
+
}
|
|
4748
|
+
function completedToolSessionClosedReason(session) {
|
|
4749
|
+
if (session.resumabilityScope !== "none") {
|
|
4750
|
+
return void 0;
|
|
4751
|
+
}
|
|
4752
|
+
return "Completed one-shot tool run; this session is not reusable.";
|
|
4753
|
+
}
|
|
4754
|
+
function staleToolSessionClosedReason(session, now = /* @__PURE__ */ new Date()) {
|
|
4755
|
+
if (session.status !== "open") {
|
|
4756
|
+
return void 0;
|
|
4757
|
+
}
|
|
4758
|
+
const lastActivityMs = Date.parse(session.lastActivityAt);
|
|
4759
|
+
if (!Number.isFinite(lastActivityMs)) {
|
|
4760
|
+
return "Session has an invalid last activity timestamp; closing to prevent stale context reuse.";
|
|
4761
|
+
}
|
|
4762
|
+
if (lastActivityMs + TOOL_SESSION_MAX_IDLE_MS >= now.getTime()) {
|
|
4763
|
+
return void 0;
|
|
4764
|
+
}
|
|
4765
|
+
return "Session idle window expired; closing to prevent stale context reuse.";
|
|
4766
|
+
}
|
|
4767
|
+
|
|
4701
4768
|
// src/session-policy.ts
|
|
4702
|
-
var maxIdleMs = 24 * 60 * 60 * 1e3;
|
|
4703
4769
|
var maxTotalMs = 7 * 24 * 60 * 60 * 1e3;
|
|
4704
4770
|
var maxMessageCount = 80;
|
|
4705
4771
|
var maxEstimatedTokens = 12e4;
|
|
@@ -4783,10 +4849,10 @@ function sessionIneligibleReason(session, input, now) {
|
|
|
4783
4849
|
if (input.workItem.executionWorktreeKey && session.executionWorktreeKey !== input.workItem.executionWorktreeKey) {
|
|
4784
4850
|
return "worktree scope mismatch";
|
|
4785
4851
|
}
|
|
4786
|
-
if (session.status === "closed" || session.status === "archived" || session.status === "blocked" || session.status === "unavailable") {
|
|
4852
|
+
if (session.status === "closed" || session.status === "archived" || session.status === "blocked" || session.status === "unavailable" || session.status === "active") {
|
|
4787
4853
|
return `session is ${session.status}`;
|
|
4788
4854
|
}
|
|
4789
|
-
if (Date.parse(session.lastActivityAt) +
|
|
4855
|
+
if (Date.parse(session.lastActivityAt) + TOOL_SESSION_MAX_IDLE_MS < now.getTime()) {
|
|
4790
4856
|
return "session is idle past the reuse window";
|
|
4791
4857
|
}
|
|
4792
4858
|
if (Date.parse(session.createdAt) + maxTotalMs < now.getTime()) {
|
|
@@ -5404,6 +5470,8 @@ var securityPostureStart = "AMISTIO_SECURITY_POSTURE_START";
|
|
|
5404
5470
|
var securityPostureEnd = "AMISTIO_SECURITY_POSTURE_END";
|
|
5405
5471
|
var appEvaluationStart = "AMISTIO_APP_EVALUATION_START";
|
|
5406
5472
|
var appEvaluationEnd = "AMISTIO_APP_EVALUATION_END";
|
|
5473
|
+
var brainConsolidationStart = "AMISTIO_BRAIN_CONSOLIDATION_START";
|
|
5474
|
+
var brainConsolidationEnd = "AMISTIO_BRAIN_CONSOLIDATION_END";
|
|
5407
5475
|
var projectContextRefreshStart = "AMISTIO_PROJECT_CONTEXT_REFRESH_START";
|
|
5408
5476
|
var projectContextRefreshEnd = "AMISTIO_PROJECT_CONTEXT_REFRESH_END";
|
|
5409
5477
|
var implementationVerificationStart = "AMISTIO_IMPLEMENTATION_VERIFICATION_START";
|
|
@@ -5752,6 +5820,9 @@ function createWorkExecutionPrompt(workItem, context) {
|
|
|
5752
5820
|
if (workItem.workKind === "appEvaluationScan") {
|
|
5753
5821
|
return createAppEvaluationScanPrompt(workItem, context?.appEvaluationScan);
|
|
5754
5822
|
}
|
|
5823
|
+
if (workItem.workKind === "brainConsolidationScan") {
|
|
5824
|
+
return createBrainConsolidationScanPrompt(workItem, context?.brainConsolidationScan);
|
|
5825
|
+
}
|
|
5755
5826
|
if (workItem.workKind === "projectContextRefresh") {
|
|
5756
5827
|
return createProjectContextRefreshPrompt(workItem, context?.projectContextRefresh);
|
|
5757
5828
|
}
|
|
@@ -5984,8 +6055,11 @@ function createAppEvaluationScanPrompt(workItem, context) {
|
|
|
5984
6055
|
"",
|
|
5985
6056
|
"- Check app verification health: lint, typecheck, test, build, CI references, flaky or missing gates.",
|
|
5986
6057
|
"- Check product and docs drift between approved context, plans, prompts, and current implementation.",
|
|
5987
|
-
"- Check old plans and prompts for
|
|
5988
|
-
"- Treat active proposed, approved, ready, or in-progress plans/prompts with accepted controlling ADRs, features, or work artifacts as active
|
|
6058
|
+
"- Check old plans and prompts for lifecycle reconciliation, but only propose cleanup actions such as markCompleted, markSuperseded, or archive when direct controlling evidence shows they are completed, superseded, or obsolete.",
|
|
6059
|
+
"- Treat active proposed, approved, ready, or in-progress plans/prompts with accepted controlling ADRs, features, or work artifacts as active backlog. Do not classify them as cleanup material solely because they are older, unexecuted, or not yet fully implemented; use keepActive when they still describe valid pending or approved work.",
|
|
6060
|
+
"- 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
|
+
"- 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
|
+
"- 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.",
|
|
5989
6063
|
"- 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.",
|
|
5990
6064
|
"- Check missing memory or workflow updates when repeated lessons or operational rules are visible.",
|
|
5991
6065
|
"- Check release readiness, UX, accessibility, performance, reliability, and security-posture follow-through at a summary level.",
|
|
@@ -6014,6 +6088,91 @@ function createAppEvaluationScanPrompt(workItem, context) {
|
|
|
6014
6088
|
"Do not put Markdown fences around the markers. Do not implement improvements or cleanup."
|
|
6015
6089
|
].join("\n");
|
|
6016
6090
|
}
|
|
6091
|
+
function createBrainConsolidationScanPrompt(workItem, context) {
|
|
6092
|
+
const documents = context?.documents ?? [];
|
|
6093
|
+
const activeReviewDocuments = documents.filter(isActiveReviewPromptDocument).slice(0, 160);
|
|
6094
|
+
const canonicalDocuments = documents.filter(isCanonicalPromptDocument).slice(0, 120);
|
|
6095
|
+
const activeReviewContext = activeReviewDocuments.map((document) => formatBrainConsolidationDocument(document)).join("\n\n");
|
|
6096
|
+
const canonicalContext = canonicalDocuments.map((document) => formatBrainConsolidationDocument(document)).join("\n\n");
|
|
6097
|
+
return [
|
|
6098
|
+
"# Amistio Semantic Brain Consolidation Scan",
|
|
6099
|
+
"",
|
|
6100
|
+
"You are running locally through the Amistio CLI inside the user's repository.",
|
|
6101
|
+
"Run a read-only semantic duplicate and coverage scan for Project Brain review documents.",
|
|
6102
|
+
"Do not modify files, create branches, commit, push, archive documents, approve documents, or run implementation prompts.",
|
|
6103
|
+
"Return bounded recommendations only; the Amistio server decides what is safe to auto-archive.",
|
|
6104
|
+
"",
|
|
6105
|
+
"## Work Item",
|
|
6106
|
+
"",
|
|
6107
|
+
`Title: ${workItem.title}`,
|
|
6108
|
+
`Work item ID: ${workItem.workItemId}`,
|
|
6109
|
+
`Project ID: ${workItem.projectId}`,
|
|
6110
|
+
`Brain consolidation scan ID: ${workItem.brainConsolidationScanId ?? "unknown"}`,
|
|
6111
|
+
"",
|
|
6112
|
+
"## Scan Request",
|
|
6113
|
+
"",
|
|
6114
|
+
workItem.sourceWish ?? "Compare active generated review documents against approved or synced Project Brain documents and recommend safe consolidation actions.",
|
|
6115
|
+
"",
|
|
6116
|
+
"## Active Review Documents",
|
|
6117
|
+
"",
|
|
6118
|
+
activeReviewContext || "No active review documents were provided in the scan context.",
|
|
6119
|
+
"",
|
|
6120
|
+
"## Approved Or Synced Canonical Documents",
|
|
6121
|
+
"",
|
|
6122
|
+
canonicalContext || "No approved or synced canonical documents were provided in the scan context.",
|
|
6123
|
+
"",
|
|
6124
|
+
"## Requirements",
|
|
6125
|
+
"",
|
|
6126
|
+
"- Compare active review documents to approved or synced canonical documents by documentId, title, repoPath, documentType, source, syncState, frontmatter lineage, and content excerpt.",
|
|
6127
|
+
"- Recommend archiveDuplicate only when a generated draft is fully covered by a canonical approved/synced document and adds no distinct requirement, decision, plan step, or memory entry.",
|
|
6128
|
+
"- Use proposeArchive for partial, ambiguous, user-owned, repo-sourced, dirty, or medium-risk matches that need human review.",
|
|
6129
|
+
"- Use manualReview when the candidate appears related but may contain unique requirements or unresolved conflicts.",
|
|
6130
|
+
"- Use skip for documents that are not duplicates or cannot be evaluated safely from the available context.",
|
|
6131
|
+
"- Keep each recommendation focused on one canonical document and one or more candidate document IDs.",
|
|
6132
|
+
"",
|
|
6133
|
+
"## Data Safety",
|
|
6134
|
+
"",
|
|
6135
|
+
"- Persist only document IDs, short evidence, bounded summaries, and recommendations.",
|
|
6136
|
+
"- Do not include raw source dumps, secrets, env vars, process lists, absolute local paths, credential values, tokens, provider sessions, or destructive shell output.",
|
|
6137
|
+
"",
|
|
6138
|
+
"## Output Contract",
|
|
6139
|
+
"",
|
|
6140
|
+
"Print exactly one JSON object between the markers below. The CLI will submit only this structured consolidation result back to Amistio.",
|
|
6141
|
+
"Accepted coverage values: full, partial, notCovered, ambiguous.",
|
|
6142
|
+
"Accepted confidence values: low, medium, high.",
|
|
6143
|
+
"Accepted recommendedAction values: archiveDuplicate, proposeArchive, manualReview, skip.",
|
|
6144
|
+
"Accepted riskLevel values: low, medium, high, critical.",
|
|
6145
|
+
"",
|
|
6146
|
+
brainConsolidationStart,
|
|
6147
|
+
'{"summary":"Compared active generated review documents against approved canonical documents.","recommendations":[{"canonicalDocumentId":"doc_approved","candidateDocumentIds":["doc_review_duplicate"],"coverage":"full","confidence":"high","recommendedAction":"archiveDuplicate","riskLevel":"low","evidence":["The candidate repeats the accepted decision without adding requirements."],"reason":"The generated review draft is fully covered by the approved canonical document."}],"warnings":[]}',
|
|
6148
|
+
brainConsolidationEnd,
|
|
6149
|
+
"",
|
|
6150
|
+
"Do not put Markdown fences around the markers. Do not archive, approve, or edit documents."
|
|
6151
|
+
].join("\n");
|
|
6152
|
+
}
|
|
6153
|
+
function formatBrainConsolidationDocument(document) {
|
|
6154
|
+
return [
|
|
6155
|
+
`### ${document.title}`,
|
|
6156
|
+
`documentId: ${document.documentId}`,
|
|
6157
|
+
`documentType: ${document.documentType}`,
|
|
6158
|
+
`repoPath: ${document.repoPath}`,
|
|
6159
|
+
`contentFormat: ${document.contentFormat ?? "markdown"}`,
|
|
6160
|
+
`source: ${document.source}`,
|
|
6161
|
+
`status: ${document.status}`,
|
|
6162
|
+
`syncState: ${document.syncState}`,
|
|
6163
|
+
`revision: ${document.revision}`,
|
|
6164
|
+
`approvedRevision: ${document.approvedRevision ?? "none"}`,
|
|
6165
|
+
`contentHash: ${document.contentHash}`,
|
|
6166
|
+
`frontmatter: ${JSON.stringify(document.frontmatter)}`,
|
|
6167
|
+
document.content.slice(0, 1600)
|
|
6168
|
+
].join("\n");
|
|
6169
|
+
}
|
|
6170
|
+
function isActiveReviewPromptDocument(document) {
|
|
6171
|
+
return document.syncState === "draft" && ["draft", "drafted", "reviewing", "changesRequested"].includes(document.status);
|
|
6172
|
+
}
|
|
6173
|
+
function isCanonicalPromptDocument(document) {
|
|
6174
|
+
return document.syncState === "approved" || document.syncState === "synced" || document.status === "approved";
|
|
6175
|
+
}
|
|
6017
6176
|
function createIssueDiagnosisPrompt(workItem, context) {
|
|
6018
6177
|
const issue = context?.issue;
|
|
6019
6178
|
const approvedContext = (context?.documents ?? []).filter((document) => document.status === "approved" || document.syncState === "approved" || document.syncState === "synced").slice(0, 16).map((document) => [
|
|
@@ -6339,6 +6498,16 @@ function parseAppEvaluationScanResult(output) {
|
|
|
6339
6498
|
const normalized = normalizeAppEvaluationScanResultPaths(normalizeAppEvaluationScanResultEnums(parsed));
|
|
6340
6499
|
return appEvaluationScanResultSchema.parse(normalized);
|
|
6341
6500
|
}
|
|
6501
|
+
function parseBrainConsolidationScanResult(output) {
|
|
6502
|
+
const start = output.indexOf(brainConsolidationStart);
|
|
6503
|
+
const end = output.indexOf(brainConsolidationEnd, start + brainConsolidationStart.length);
|
|
6504
|
+
if (start === -1 || end === -1 || end <= start) {
|
|
6505
|
+
throw new Error("Local AI scan did not return an Amistio brain consolidation block.");
|
|
6506
|
+
}
|
|
6507
|
+
const payload = output.slice(start + brainConsolidationStart.length, end).trim();
|
|
6508
|
+
const parsed = JSON.parse(stripJsonFence(payload));
|
|
6509
|
+
return brainConsolidationScanResultSchema.parse(parsed);
|
|
6510
|
+
}
|
|
6342
6511
|
function parseProjectContextRefreshResult(output, options = {}) {
|
|
6343
6512
|
const start = output.indexOf(projectContextRefreshStart);
|
|
6344
6513
|
const end = output.indexOf(projectContextRefreshEnd, start + projectContextRefreshStart.length);
|
|
@@ -6491,7 +6660,7 @@ function createBrainGenerationPrompt(workItem) {
|
|
|
6491
6660
|
"- plan -> docs/plans/",
|
|
6492
6661
|
"- prompt -> docs/prompts/",
|
|
6493
6662
|
"- workflow -> docs/workflows/",
|
|
6494
|
-
"- HTML
|
|
6663
|
+
"- HTML artifacts -> docs/html/<same-folder>/ using .html, for example docs/html/plans/PLAN-example.html",
|
|
6495
6664
|
"",
|
|
6496
6665
|
artifactFormatPreferenceInstructions(artifactFormatPreference),
|
|
6497
6666
|
"",
|
|
@@ -6768,10 +6937,10 @@ function artifactFormatPreferenceInstructions(preference) {
|
|
|
6768
6937
|
return "The user chose HTML. Generate .html artifacts under docs/html/<folder>/ and set contentFormat to html.";
|
|
6769
6938
|
}
|
|
6770
6939
|
if (preference === "both") {
|
|
6771
|
-
return "The user chose both formats.
|
|
6940
|
+
return "The user chose both formats. Choose one format per artifact purpose; do not generate matching Markdown and HTML companion copies of the same document.";
|
|
6772
6941
|
}
|
|
6773
6942
|
if (preference === "auto") {
|
|
6774
|
-
return "Choose Markdown or HTML based on the artifact content. Use Markdown for text-first docs and HTML for visual or layout-sensitive artifacts.";
|
|
6943
|
+
return "Choose Markdown or HTML based on the artifact content. Use Markdown for text-first docs and HTML only for visual or layout-sensitive artifacts.";
|
|
6775
6944
|
}
|
|
6776
6945
|
return "The user chose Markdown. Generate Markdown artifacts under docs/<folder>/ and set contentFormat to markdown or omit it.";
|
|
6777
6946
|
}
|
|
@@ -7836,7 +8005,7 @@ var DEFAULT_TOOL_TIMEOUT_SECONDS = 30 * 60;
|
|
|
7836
8005
|
var RUNNER_WORK_LEASE_SECONDS = 300;
|
|
7837
8006
|
var RUNNER_WORK_LEASE_RENEWAL_MS = 12e4;
|
|
7838
8007
|
var MAX_CONCURRENT_RUNNER_WORK = 4;
|
|
7839
|
-
var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "projectContextRefresh", "implementationVerification", "testQualityScan", "implementationTestGate"];
|
|
8008
|
+
var runnerSupportedWorkKinds = ["brainGeneration", "implementation", "planRevision", "assistantQuestion", "impactPreview", "issueDiagnosis", "securityPostureScan", "appEvaluationScan", "brainConsolidationScan", "projectContextRefresh", "implementationVerification", "testQualityScan", "implementationTestGate"];
|
|
7840
8009
|
program.name("amistio").description("Amistio project brain CLI").version(CLI_VERSION);
|
|
7841
8010
|
program.command("init").description("Create Amistio control-plane folders for a new project").option("--root <path>", "Repository root", defaultRoot).action(async (options) => {
|
|
7842
8011
|
const created = await initControlPlane(options.root);
|
|
@@ -8917,6 +9086,24 @@ async function runNextWorkItem({
|
|
|
8917
9086
|
return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
|
|
8918
9087
|
}
|
|
8919
9088
|
}
|
|
9089
|
+
if (result.workItem.workKind === "brainConsolidationScan") {
|
|
9090
|
+
try {
|
|
9091
|
+
return await finalizeBrainConsolidationScanWork({
|
|
9092
|
+
apiClient,
|
|
9093
|
+
durationMs: Date.now() - startedAt,
|
|
9094
|
+
projectId,
|
|
9095
|
+
repositoryLinkId,
|
|
9096
|
+
runnerId,
|
|
9097
|
+
sessionContext,
|
|
9098
|
+
toolConfig,
|
|
9099
|
+
toolName: preview.toolName,
|
|
9100
|
+
toolResult,
|
|
9101
|
+
workItem: result.workItem
|
|
9102
|
+
});
|
|
9103
|
+
} catch (error) {
|
|
9104
|
+
return recordFinalizationFailure({ apiClient, error, isolationTelemetry, projectId, repositoryLinkId, runnerId, sessionContext, toolConfig, toolName: preview.toolName, workItem: result.workItem, durationMs: Date.now() - startedAt });
|
|
9105
|
+
}
|
|
9106
|
+
}
|
|
8920
9107
|
if (result.workItem.workKind === "projectContextRefresh") {
|
|
8921
9108
|
try {
|
|
8922
9109
|
return await finalizeProjectContextRefreshWork({
|
|
@@ -9519,6 +9706,9 @@ async function submitDurableResultMutation(apiClient, entry) {
|
|
|
9519
9706
|
if (entry.resultKind === "appEvaluationScanResult") {
|
|
9520
9707
|
return apiClient.submitAppEvaluationScanResult(entry.projectId, entry.workItemId, entry.result);
|
|
9521
9708
|
}
|
|
9709
|
+
if (entry.resultKind === "brainConsolidationScanResult") {
|
|
9710
|
+
return apiClient.submitBrainConsolidationScanResult(entry.projectId, entry.workItemId, entry.result);
|
|
9711
|
+
}
|
|
9522
9712
|
if (entry.resultKind === "projectContextRefreshResult") {
|
|
9523
9713
|
return apiClient.submitProjectContextRefreshResult(entry.projectId, entry.workItemId, entry.result);
|
|
9524
9714
|
}
|
|
@@ -9538,6 +9728,7 @@ function durableResultResponseWorkItem(response) {
|
|
|
9538
9728
|
}
|
|
9539
9729
|
function runnerResultFinalizationLabel(entry) {
|
|
9540
9730
|
if (entry.workKind === "appEvaluationScan") return "app evaluation scan";
|
|
9731
|
+
if (entry.workKind === "brainConsolidationScan") return "brain consolidation scan";
|
|
9541
9732
|
if (entry.workKind === "securityPostureScan") return "security posture scan";
|
|
9542
9733
|
if (entry.workKind === "projectContextRefresh") return "project context refresh";
|
|
9543
9734
|
if (entry.workKind === "implementationVerification") return "implementation verification";
|
|
@@ -10203,6 +10394,101 @@ ${toolResult.stderr}`);
|
|
|
10203
10394
|
console.error(scanError ?? "Local runner app evaluation scan failed.");
|
|
10204
10395
|
return { status: "failed", exitCode: toolResult.exitCode || 1 };
|
|
10205
10396
|
}
|
|
10397
|
+
async function finalizeBrainConsolidationScanWork({
|
|
10398
|
+
apiClient,
|
|
10399
|
+
durationMs,
|
|
10400
|
+
projectId,
|
|
10401
|
+
repositoryLinkId,
|
|
10402
|
+
runnerId,
|
|
10403
|
+
sessionContext,
|
|
10404
|
+
toolConfig,
|
|
10405
|
+
toolName,
|
|
10406
|
+
toolResult,
|
|
10407
|
+
workItem
|
|
10408
|
+
}) {
|
|
10409
|
+
let scanResult = void 0;
|
|
10410
|
+
let scanError;
|
|
10411
|
+
if (toolResult.exitCode === 0) {
|
|
10412
|
+
try {
|
|
10413
|
+
scanResult = parseBrainConsolidationScanResult(`${toolResult.stdout}
|
|
10414
|
+
${toolResult.stderr}`);
|
|
10415
|
+
} catch (error) {
|
|
10416
|
+
scanError = errorMessage3(error);
|
|
10417
|
+
}
|
|
10418
|
+
} else {
|
|
10419
|
+
scanError = truncateLogExcerpt(toolResult.stderr || toolResult.stdout) || `${toolName} exited with code ${toolResult.exitCode}.`;
|
|
10420
|
+
}
|
|
10421
|
+
const finalStatus = scanResult ? "completed" : "failed";
|
|
10422
|
+
const sessionTelemetry = pendingSessionTelemetry(sessionContext);
|
|
10423
|
+
if (scanResult) {
|
|
10424
|
+
const resultMutation = {
|
|
10425
|
+
status: "completed",
|
|
10426
|
+
runnerId,
|
|
10427
|
+
idempotencyKey: `brain_consolidation_${workItem.workItemId}_${randomUUID()}`,
|
|
10428
|
+
result: scanResult,
|
|
10429
|
+
tool: toolName,
|
|
10430
|
+
durationMs,
|
|
10431
|
+
...sessionTelemetry,
|
|
10432
|
+
message: `${toolName} returned a semantic brain consolidation scan.`
|
|
10433
|
+
};
|
|
10434
|
+
const result = await submitPrimaryRunnerResult(apiClient, {
|
|
10435
|
+
accountId: workItem.accountId,
|
|
10436
|
+
projectId,
|
|
10437
|
+
repositoryLinkId,
|
|
10438
|
+
runnerId,
|
|
10439
|
+
workItemId: workItem.workItemId,
|
|
10440
|
+
workKind: "brainConsolidationScan",
|
|
10441
|
+
resultKind: "brainConsolidationScanResult",
|
|
10442
|
+
attempt: workItem.attempt,
|
|
10443
|
+
idempotencyKey: resultMutation.idempotencyKey,
|
|
10444
|
+
result: resultMutation
|
|
10445
|
+
});
|
|
10446
|
+
if (!result) return { status: "failed", exitCode: 1 };
|
|
10447
|
+
await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
|
|
10448
|
+
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
10449
|
+
status: "completed",
|
|
10450
|
+
summary: `${toolName} returned a semantic brain consolidation scan.`,
|
|
10451
|
+
idempotencyKey: `runner_milestone_brain_consolidation_completed_${workItem.workItemId}_${result.workItem.idempotencyKey}`,
|
|
10452
|
+
metadata: { tool: toolName, durationMs, recommendationCount: scanResult.recommendations.length, autoArchivedCount: result.autoArchivedDocuments.length, proposalCount: result.proposals.length, verificationSummary: scanResult.summary }
|
|
10453
|
+
});
|
|
10454
|
+
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
10455
|
+
console.log(`Brain consolidation scan returned: archived ${result.autoArchivedDocuments.length}, opened ${result.proposals.length} proposal${result.proposals.length === 1 ? "" : "s"}.`);
|
|
10456
|
+
return { status: "completed", exitCode: 0 };
|
|
10457
|
+
}
|
|
10458
|
+
const failedMutation = {
|
|
10459
|
+
status: "failed",
|
|
10460
|
+
runnerId,
|
|
10461
|
+
idempotencyKey: `brain_consolidation_${workItem.workItemId}_${randomUUID()}`,
|
|
10462
|
+
tool: toolName,
|
|
10463
|
+
durationMs,
|
|
10464
|
+
...sessionTelemetry,
|
|
10465
|
+
message: `${toolName} did not produce a valid semantic brain consolidation scan.`,
|
|
10466
|
+
...scanError ? { error: scanError } : {}
|
|
10467
|
+
};
|
|
10468
|
+
const failedResult = await submitPrimaryRunnerResult(apiClient, {
|
|
10469
|
+
accountId: workItem.accountId,
|
|
10470
|
+
projectId,
|
|
10471
|
+
repositoryLinkId,
|
|
10472
|
+
runnerId,
|
|
10473
|
+
workItemId: workItem.workItemId,
|
|
10474
|
+
workKind: "brainConsolidationScan",
|
|
10475
|
+
resultKind: "brainConsolidationScanResult",
|
|
10476
|
+
attempt: workItem.attempt,
|
|
10477
|
+
idempotencyKey: failedMutation.idempotencyKey,
|
|
10478
|
+
result: failedMutation
|
|
10479
|
+
});
|
|
10480
|
+
if (!failedResult) return { status: "failed", exitCode: 1 };
|
|
10481
|
+
await finalizeToolSessionBestEffort({ apiClient, projectId, runnerId, sessionContext, status: finalStatus, toolResult, workItemId: workItem.workItemId });
|
|
10482
|
+
await recordRunnerMilestone(apiClient, projectId, workItem, runnerId, repositoryLinkId, {
|
|
10483
|
+
status: "failed",
|
|
10484
|
+
summary: scanError ?? `${toolName} did not produce a valid semantic brain consolidation scan.`,
|
|
10485
|
+
idempotencyKey: `runner_milestone_brain_consolidation_failed_${workItem.workItemId}_${failedResult.workItem.idempotencyKey}`,
|
|
10486
|
+
metadata: { tool: toolName, durationMs, verificationSummary: "Brain consolidation output did not include valid structured JSON." }
|
|
10487
|
+
});
|
|
10488
|
+
await apiClient.sendRunnerHeartbeat(projectId, runnerId, repositoryLinkId, "online", runnerHeartbeatMetadata(toolConfig));
|
|
10489
|
+
console.error(scanError ?? "Local runner brain consolidation scan failed.");
|
|
10490
|
+
return { status: "failed", exitCode: toolResult.exitCode || 1 };
|
|
10491
|
+
}
|
|
10206
10492
|
async function finalizeProjectContextRefreshWork({
|
|
10207
10493
|
apiClient,
|
|
10208
10494
|
durationMs,
|
|
@@ -10652,6 +10938,12 @@ async function createRunnerWorkPrompt(apiClient, projectId, workItem) {
|
|
|
10652
10938
|
appEvaluationScan: { documents: documents2 }
|
|
10653
10939
|
});
|
|
10654
10940
|
}
|
|
10941
|
+
if (workItem.workKind === "brainConsolidationScan") {
|
|
10942
|
+
const { documents: documents2 } = await apiClient.listBrainDocuments(projectId);
|
|
10943
|
+
return createWorkExecutionPrompt(workItem, {
|
|
10944
|
+
brainConsolidationScan: { documents: documents2 }
|
|
10945
|
+
});
|
|
10946
|
+
}
|
|
10655
10947
|
if (workItem.workKind === "testQualityScan") {
|
|
10656
10948
|
const { documents: documents2 } = await apiClient.listBrainDocuments(projectId);
|
|
10657
10949
|
return createWorkExecutionPrompt(workItem, {
|
|
@@ -10759,6 +11051,8 @@ async function prepareToolSession({
|
|
|
10759
11051
|
workItem
|
|
10760
11052
|
}) {
|
|
10761
11053
|
const { toolSessions } = await apiClient.listToolSessions(projectId);
|
|
11054
|
+
const now = /* @__PURE__ */ new Date();
|
|
11055
|
+
await closeStaleToolSessionsBestEffort(apiClient, projectId, toolSessions, now);
|
|
10762
11056
|
const selection = selectToolSession({
|
|
10763
11057
|
policy: sessionPolicy,
|
|
10764
11058
|
workItem,
|
|
@@ -10767,7 +11061,8 @@ async function prepareToolSession({
|
|
|
10767
11061
|
runnerId,
|
|
10768
11062
|
repositoryLinkId,
|
|
10769
11063
|
machineId,
|
|
10770
|
-
supportsSessionReuse
|
|
11064
|
+
supportsSessionReuse,
|
|
11065
|
+
now
|
|
10771
11066
|
});
|
|
10772
11067
|
if (selection.decision === "skipped") {
|
|
10773
11068
|
return selection;
|
|
@@ -10809,6 +11104,18 @@ async function prepareToolSession({
|
|
|
10809
11104
|
});
|
|
10810
11105
|
return { ...selection, toolSession };
|
|
10811
11106
|
}
|
|
11107
|
+
async function closeStaleToolSessionsBestEffort(apiClient, projectId, toolSessions, now) {
|
|
11108
|
+
const staleSessions = toolSessions.map((session) => ({ session, reason: staleToolSessionClosedReason(session, now) })).filter((item) => Boolean(item.reason));
|
|
11109
|
+
if (!staleSessions.length) {
|
|
11110
|
+
return;
|
|
11111
|
+
}
|
|
11112
|
+
const settlements = await Promise.allSettled(staleSessions.map(({ session, reason }) => apiClient.updateToolSession(projectId, session.toolSessionId, {
|
|
11113
|
+
status: "closed",
|
|
11114
|
+
closedReason: reason,
|
|
11115
|
+
summary: session.summary ?? reason
|
|
11116
|
+
})));
|
|
11117
|
+
logRejectedSettlements("close stale tool sessions", settlements);
|
|
11118
|
+
}
|
|
10812
11119
|
async function finalizeToolSession({
|
|
10813
11120
|
apiClient,
|
|
10814
11121
|
costUsd,
|
|
@@ -10826,8 +11133,10 @@ async function finalizeToolSession({
|
|
|
10826
11133
|
return void 0;
|
|
10827
11134
|
}
|
|
10828
11135
|
const summary = summarizeToolOutput(stdout) ?? session.summary;
|
|
11136
|
+
const nextStatus = status === "completed" ? completedToolSessionStatus(session) : "blocked";
|
|
11137
|
+
const closedReason = status === "completed" ? completedToolSessionClosedReason(session) : "Last run failed or returned a non-zero exit code.";
|
|
10829
11138
|
const { toolSession } = await apiClient.updateToolSession(projectId, session.toolSessionId, {
|
|
10830
|
-
status:
|
|
11139
|
+
status: nextStatus,
|
|
10831
11140
|
runnerId,
|
|
10832
11141
|
lastWorkItemId: workItemId,
|
|
10833
11142
|
messageCount: (session.messageCount ?? 0) + (messageCount ?? 1),
|
|
@@ -10835,7 +11144,7 @@ async function finalizeToolSession({
|
|
|
10835
11144
|
...tokensIn !== void 0 ? { estimatedInputTokens: (session.estimatedInputTokens ?? 0) + tokensIn } : {},
|
|
10836
11145
|
...tokensOut !== void 0 ? { estimatedOutputTokens: (session.estimatedOutputTokens ?? 0) + tokensOut } : {},
|
|
10837
11146
|
...costUsd !== void 0 ? { costUsd: (session.costUsd ?? 0) + costUsd } : {},
|
|
10838
|
-
...
|
|
11147
|
+
...closedReason ? { closedReason } : {}
|
|
10839
11148
|
});
|
|
10840
11149
|
return toolSession;
|
|
10841
11150
|
}
|