@piagent/platform 1.6.0 → 1.6.1
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/CHANGELOG.md +49 -0
- package/README.md +17 -14
- package/README.vi.md +26 -1
- package/benchmarks/production-v1/spend-control.v1.json +27 -3
- package/benchmarks/production-v1/suite.json +5 -1
- package/catalog/capabilities.json +16 -16
- package/docs/authority-manifest.md +1 -1
- package/docs/auto-delegation-policy.md +38 -73
- package/docs/command-reference-vietnamese.md +23 -31
- package/docs/context-engine.md +5 -3
- package/docs/context-window-policy.md +37 -1
- package/docs/distribution-standard.md +7 -7
- package/docs/en/architecture.md +26 -13
- package/docs/languages.json +4 -4
- package/docs/operator-manual-vietnamese.md +21 -41
- package/docs/publishing-for-teams.md +5 -0
- package/docs/quality-benchmark.md +179 -23
- package/docs/release-install-policy.md +6 -6
- package/docs/runtime-harness-standard.md +3 -3
- package/docs/security-threat-model.md +1 -1
- package/docs/subagent-orchestration-capabilities.md +19 -51
- package/docs/subagents-and-multiagent.md +21 -47
- package/docs/task-implementation-contract.md +3 -3
- package/docs/task-lifecycle-vietnamese.md +1 -1
- package/docs/team-onboarding.md +2 -2
- package/docs/vi/architecture.md +26 -13
- package/docs-site/architecture.html +1 -1
- package/docs-site/benchmark.html +89 -95
- package/docs-site/commands.html +1 -1
- package/docs-site/content/benchmark.html +88 -94
- package/docs-site/content/en/benchmark.html +134 -90
- package/docs-site/content/en/index.html +19 -19
- package/docs-site/content/en/runtime.html +4 -4
- package/docs-site/content/en/whats-new.html +12 -1
- package/docs-site/content/en/workflows.html +2 -2
- package/docs-site/content/index.html +19 -19
- package/docs-site/content/runtime.html +4 -4
- package/docs-site/content/whats-new.html +12 -1
- package/docs-site/content/workflows.html +2 -2
- package/docs-site/context-engine.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +135 -91
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/index.html +20 -20
- package/docs-site/en/mcp-approval.html +1 -1
- package/docs-site/en/mcp-auth.html +1 -1
- package/docs-site/en/mcp-servers.html +1 -1
- package/docs-site/en/mcp.html +1 -1
- package/docs-site/en/permissions.html +1 -1
- package/docs-site/en/profiles.html +1 -1
- package/docs-site/en/quickstart.html +1 -1
- package/docs-site/en/runtime.html +5 -5
- package/docs-site/en/scope.html +1 -1
- package/docs-site/en/security.html +1 -1
- package/docs-site/en/team.html +1 -1
- package/docs-site/en/whats-new.html +13 -2
- package/docs-site/en/workflows.html +3 -3
- package/docs-site/index.html +20 -20
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/runtime.html +5 -5
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/whats-new.html +13 -2
- package/docs-site/workflows.html +3 -3
- package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
- package/evals/fixtures/benchmark-assurance-evidence.valid.json +1 -1
- package/evals/fixtures/codex-relative-efficiency-v1.invalid.json +4 -0
- package/evals/fixtures/codex-relative-efficiency-v1.valid.json +94 -0
- package/evals/fixtures/helper-request.invalid.json +1 -1
- package/evals/fixtures/helper-request.valid.json +1 -1
- package/evals/fixtures/piagent-webui/subagent-tree-v1.valid.json +7 -0
- package/evals/fixtures/task-authority-snapshot.valid.json +4 -4
- package/evals/fs4-readiness-matrix.v1.json +2 -2
- package/evals/fs5-causal-arm.v1.json +2 -2
- package/evals/fs5-pilot-protocol.v1.json +3 -3
- package/evals/fs5-pilot-protocol.v2.json +8 -8
- package/evals/fs5-pilot-protocol.v3.json +8 -8
- package/evals/fs5-pilot-protocol.v4.json +6 -6
- package/evals/fs5-pilot-protocol.v5.json +7 -6
- package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
- package/evals/runtime-conformance-v1/README.md +43 -0
- package/evals/runtime-conformance-v1/evaluate-paid-canary.mjs +198 -0
- package/evals/runtime-conformance-v1/lane.json +57 -0
- package/evals/runtime-conformance-v1/paid-canary.json +31 -0
- package/evals/runtime-conformance-v1/runner.mjs +456 -0
- package/package.json +1 -1
- package/packages/piagent-core/README.md +13 -4
- package/packages/piagent-core/benchmark/benchmark-codex-relative-efficiency.js +698 -0
- package/packages/piagent-core/benchmark/benchmark-core.js +4 -3
- package/packages/piagent-core/benchmark/benchmark-forensics.js +4 -2
- package/packages/piagent-core/benchmark/benchmark-preflight.js +3 -1
- package/packages/piagent-core/benchmark/benchmark-provider-free-evidence.js +345 -0
- package/packages/piagent-core/benchmark/benchmark-record-validation.js +74 -5
- package/packages/piagent-core/benchmark/benchmark-report.js +15 -1
- package/packages/piagent-core/benchmark/benchmark-runtime-causal.js +244 -0
- package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +201 -4
- package/packages/piagent-core/benchmark/benchmark-suite.js +22 -1
- package/packages/piagent-core/benchmark/benchmark-summary-support.js +4 -0
- package/packages/piagent-core/benchmark/benchmark-usage.js +15 -4
- package/packages/piagent-core/capabilities/runtime-integrity.js +3 -0
- package/packages/piagent-core/extensions/acceptance-behavior-proof.js +806 -0
- package/packages/piagent-core/extensions/acceptance-receipt.js +89 -48
- package/packages/piagent-core/extensions/context-engine.js +1 -11
- package/packages/piagent-core/extensions/context-index-policy.js +4 -0
- package/packages/piagent-core/extensions/context-query-paths.js +57 -0
- package/packages/piagent-core/extensions/criterion-context-pack.js +16 -3
- package/packages/piagent-core/extensions/criterion-graph.js +4 -1
- package/packages/piagent-core/extensions/fallback-base-policy.ts +101 -0
- package/packages/piagent-core/extensions/guard-types.ts +4 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +194 -226
- package/packages/piagent-core/extensions/task-lifecycle.js +32 -0
- package/packages/piagent-core/extensions/task-state.js +69 -8
- package/packages/piagent-core/extensions/verification-intelligence.js +49 -12
- package/packages/piagent-core/extensions/workspace-evidence-roots.js +52 -16
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/policies/base-policy.json +42 -2
- package/packages/piagent-core/policy/authority-manifest.v1.json +3 -3
- package/packages/piagent-core/prompts/be-to-fe.md +2 -8
- package/packages/piagent-core/prompts/plan.md +1 -1
- package/packages/piagent-core/prompts/platform-improve.md +2 -6
- package/packages/piagent-core/prompts/review.md +2 -7
- package/packages/piagent-core/prompts/scout.md +1 -1
- package/packages/piagent-core/prompts/task.md +4 -3
- package/packages/piagent-core/runtime/hooks/completion-hook.ts +7 -6
- package/packages/piagent-core/runtime/hooks/input-hook.ts +6 -0
- package/packages/piagent-core/runtime/hooks/session-start-hook.ts +100 -1
- package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +58 -72
- package/packages/piagent-core/runtime/hooks/tool-result-value-helpers.ts +66 -0
- package/packages/piagent-core/runtime/inspection/bounded-emission-guard.ts +286 -0
- package/packages/piagent-core/runtime/inspection/runtime-event-store.ts +37 -7
- package/packages/piagent-core/runtime/inspection/task-subagent-tree.ts +27 -4
- package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +20 -3
- package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +47 -11
- package/packages/piagent-core/runtime/orchestration/helper-lifecycle.ts +16 -12
- package/packages/piagent-core/runtime/orchestration/minimal-delegation-policy.ts +71 -0
- package/packages/piagent-core/runtime/orchestration/owned-work-budget.ts +28 -20
- package/packages/piagent-core/runtime/orchestration/role-policy.ts +27 -9
- package/packages/piagent-core/runtime/orchestration/subagent-tool-policy.ts +329 -0
- package/packages/piagent-core/runtime/policy/block-remedy.ts +0 -4
- package/packages/piagent-core/runtime/product/efficiency-metrics.ts +3 -0
- package/packages/piagent-core/runtime/quality/edit-freshness-guard.ts +102 -0
- package/packages/piagent-core/runtime/quality/exact-output-contract.ts +20 -10
- package/packages/piagent-core/runtime/quality/performance-assurance.ts +3 -3
- package/packages/piagent-core/runtime/recovery/continuation-budget.ts +1 -1
- package/packages/piagent-core/runtime/recovery/handoff-projection.ts +4 -1
- package/packages/piagent-core/runtime/recovery/pristine-task-policy-refresh.ts +91 -0
- package/packages/piagent-core/runtime/recovery/recovery-policy.ts +7 -4
- package/packages/piagent-core/runtime/recovery/resume-state.ts +112 -14
- package/packages/piagent-core/runtime/recovery/semantic-repair-handshake.ts +7 -13
- package/packages/piagent-core/runtime/recovery/task-evidence-coverage-refresh.ts +217 -0
- package/packages/piagent-core/runtime/registration/context-commands.ts +16 -2
- package/packages/piagent-core/runtime/registration/knowledge-tools.ts +9 -5
- package/packages/piagent-core/runtime/registration/operator-request-intake.ts +49 -0
- package/packages/piagent-core/runtime/registration/policy-tools.ts +4 -4
- package/packages/piagent-core/runtime/registration/session-commands.ts +1 -1
- package/packages/piagent-core/runtime/registration/task-completion-tools.ts +12 -9
- package/packages/piagent-core/runtime/registration/task-evidence-tools.ts +21 -12
- package/packages/piagent-core/runtime/registration/task-start-guidance.ts +19 -13
- package/packages/piagent-core/runtime/registration/task-start-manifest.ts +54 -0
- package/packages/piagent-core/runtime/registration/task-start-tool.ts +57 -57
- package/packages/piagent-core/runtime/runtime-limits.ts +16 -2
- package/packages/piagent-core/runtime/session/adaptive-context-analysis.ts +461 -0
- package/packages/piagent-core/runtime/session/adaptive-context-governor.ts +464 -0
- package/packages/piagent-core/runtime/session/adaptive-context-ledger.ts +265 -0
- package/packages/piagent-core/runtime/session/message-signals.ts +7 -3
- package/packages/piagent-core/runtime/session/operator-request-carry.ts +44 -0
- package/packages/piagent-core/runtime/session/runtime-state.ts +11 -7
- package/packages/piagent-core/runtime/session/source-checkout-read-grants.ts +35 -0
- package/packages/piagent-core/runtime/session/system-prompt.ts +76 -14
- package/packages/piagent-core/runtime/session/task-contract-presentation.ts +39 -0
- package/packages/piagent-core/runtime/tools/phase-tools.ts +8 -8
- package/packages/piagent-core/runtime/tools/tool-groups.ts +12 -2
- package/packages/piagent-core/runtime/verification/exact-verifier-reuse.ts +101 -0
- package/packages/piagent-core/runtime/workflows/runtime-intake-compaction.ts +164 -0
- package/packages/piagent-core/runtime/workflows/task-intake.ts +167 -39
- package/packages/piagent-core/subagents/piagent-oracle.md +5 -3
- package/packages/piagent-core/subagents/piagent-planner.md +6 -4
- package/packages/piagent-core/subagents/piagent-reviewer.md +5 -3
- package/packages/piagent-core/subagents/piagent-scout.md +4 -2
- package/packages/piagent-core/subagents/piagent-worker.md +6 -4
- package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-CxeZqTte.js → MarkdownMessage-B4XehTCU.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/{index-DynIMe8I.js → index-CgOEyRPG.js} +4 -4
- package/packages/piagent-webui/dist/client/index.html +1 -1
- package/packages/piagent-webui/extension/piagent-webui.ts +3 -1
- package/packages/piagent-webui/extension/same-session-bridge.ts +1 -1
- package/packages/piagent-webui/extension/session-stream-adapter.ts +33 -10
- package/packages/piagent-webui/gateway/gateway-session-stream.ts +37 -21
- package/packages/piagent-webui/gateway/session-operation-lifecycle.ts +237 -0
- package/packages/piagent-webui/gateway/session-operation-retry-control.ts +38 -0
- package/packages/piagent-webui/gateway/session-operation-watchdog.ts +9 -2
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +8 -7
- package/packages/piagent-webui/ownership/session-lease-store.ts +5 -2
- package/packages/piagent-webui/server/core-inspection-provider.ts +2 -1
- package/packages/piagent-webui/server/transcript-projection.ts +91 -5
- package/schemas/codex-relative-efficiency-v1.schema.json +155 -0
- package/schemas/piagent-webui/runtime-event-v2.schema.json +15 -5
- package/schemas/piagent-webui/subagent-tree-v1.schema.json +11 -1
- package/schemas/project-profile.schema.json +1 -1
- package/schemas/task-contract.schema.json +15 -0
- package/scripts/benchmark-runner-core.mjs +14 -6
- package/scripts/benchmark-runner-finalization.mjs +4 -0
- package/scripts/configure-subagents.sh +42 -34
- package/scripts/helper-evaluation.mjs +1 -1
- package/scripts/team-doctor.sh +1 -1
- package/scripts/verify-local.sh +5 -0
|
@@ -45,11 +45,11 @@ export const PIAGENT_DIAGNOSTIC_TOOLS = new Set<string>([
|
|
|
45
45
|
"piagent_context_budget", "piagent_context_index_status", "piagent_memory_status", "piagent_usage_snapshot"
|
|
46
46
|
]);
|
|
47
47
|
export const PIAGENT_MUTATION_CAPABLE_TOOLS = new Set<string>([
|
|
48
|
-
"piagent_context_engine", "
|
|
48
|
+
"piagent_context_engine", "piagent_profile_apply", "piagent_profile_tech_apply",
|
|
49
49
|
"piagent_profile_tech_context_record", "piagent_project_onboarding_record", "piagent_context_index_record", "piagent_memory_note"
|
|
50
50
|
]);
|
|
51
51
|
export const PIAGENT_SEQUENTIAL_TOOLS = new Set<string>([
|
|
52
|
-
...PIAGENT_MUTATION_CAPABLE_TOOLS, "piagent_tools", "piagent_task_start", "piagent_task_progress", "piagent_context_record",
|
|
52
|
+
...PIAGENT_MUTATION_CAPABLE_TOOLS, "piagent_source_checkout", "piagent_tools", "piagent_task_start", "piagent_task_progress", "piagent_context_record",
|
|
53
53
|
"piagent_verify_record", "piagent_trace_record", "piagent_memory_citation_record"
|
|
54
54
|
]);
|
|
55
55
|
|
|
@@ -83,15 +83,15 @@ const REQUIRED_HOST_TOOLS: Record<TrajectoryPhase, string[]> = {
|
|
|
83
83
|
|
|
84
84
|
const PHASE_TOOL_ALLOW: Record<TrajectoryPhase, string[]> = {
|
|
85
85
|
intake: [],
|
|
86
|
-
scout: ["piagent_task_progress", "piagent_context_index_search", "piagent_document_read"],
|
|
87
|
-
plan: ["piagent_task_progress", "piagent_orchestration_policy"],
|
|
88
|
-
execute: ["piagent_task_progress"],
|
|
86
|
+
scout: ["piagent_task_progress", "piagent_context_index_search", "piagent_document_read", "piagent_source_checkout"],
|
|
87
|
+
plan: ["piagent_task_progress", "piagent_orchestration_policy", "piagent_source_checkout"],
|
|
88
|
+
execute: ["piagent_task_progress", "piagent_source_checkout"],
|
|
89
89
|
// Runtime hooks own context, verifier, gate, and handoff evidence. Keeping
|
|
90
90
|
// their record tools model-visible duplicates work and creates false manual
|
|
91
91
|
// choreography in otherwise automatic tasks.
|
|
92
|
-
verify: [],
|
|
93
|
-
repair: ["piagent_task_progress", "piagent_context_index_search"],
|
|
94
|
-
review: ["piagent_task_progress"],
|
|
92
|
+
verify: ["piagent_source_checkout"],
|
|
93
|
+
repair: ["piagent_task_progress", "piagent_context_index_search", "piagent_source_checkout"],
|
|
94
|
+
review: ["piagent_task_progress", "piagent_source_checkout"],
|
|
95
95
|
handoff: [],
|
|
96
96
|
terminal: []
|
|
97
97
|
};
|
|
@@ -27,12 +27,12 @@ export const PIAGENT_TOOL_GROUPS = {
|
|
|
27
27
|
"piagent_context_index_status",
|
|
28
28
|
"piagent_context_index_search"
|
|
29
29
|
],
|
|
30
|
+
source: ["piagent_source_checkout"],
|
|
30
31
|
knowledge: [
|
|
31
32
|
"piagent_memory_status",
|
|
32
33
|
"piagent_memory_search",
|
|
33
34
|
"piagent_memory_citation_record",
|
|
34
35
|
"piagent_document_read",
|
|
35
|
-
"piagent_source_checkout",
|
|
36
36
|
"piagent_orchestration_policy"
|
|
37
37
|
],
|
|
38
38
|
onboarding: [
|
|
@@ -53,6 +53,13 @@ export type PiagentToolGroup = keyof typeof PIAGENT_TOOL_GROUPS;
|
|
|
53
53
|
export const PIAGENT_TOOL_ORDER = Object.values(PIAGENT_TOOL_GROUPS).flat();
|
|
54
54
|
export const PIAGENT_TOOL_NAMES = new Set<string>(PIAGENT_TOOL_ORDER);
|
|
55
55
|
|
|
56
|
+
const SUPPORTED_GIT_REPOSITORY_URL = /(?:^|[\s("'\[])https?:\/\/(?:www\.)?(?:github\.com|gitlab\.com|bitbucket\.org)\/[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+(?:\.git)?(?:[\/?#][^\s)"'\]]*)?/i;
|
|
57
|
+
const SUPPORTED_GIT_SSH_REFERENCE = /(?:^|[\s("'\[])git@(?:github\.com|gitlab\.com|bitbucket\.org):[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+(?:\.git)?(?:$|[\s)"'\]])/i;
|
|
58
|
+
|
|
59
|
+
export function hasSupportedGitRepositoryReference(prompt: string): boolean {
|
|
60
|
+
return SUPPORTED_GIT_REPOSITORY_URL.test(prompt) || SUPPORTED_GIT_SSH_REFERENCE.test(prompt);
|
|
61
|
+
}
|
|
62
|
+
|
|
56
63
|
export function toolGroupsForPrompt(prompt: string): PiagentToolGroup[] {
|
|
57
64
|
const signal = classifyContextTask(prompt);
|
|
58
65
|
const lower = prompt.toLowerCase();
|
|
@@ -68,7 +75,10 @@ export function toolGroupsForPrompt(prompt: string): PiagentToolGroup[] {
|
|
|
68
75
|
if (/\b(context (?:engine|index|search|diagnostic)|source checkout|vendor checkout)\b/.test(lower)) {
|
|
69
76
|
groups.add("retrieval");
|
|
70
77
|
}
|
|
71
|
-
if (/\b(
|
|
78
|
+
if (/\b(source checkout|vendor checkout)\b/.test(lower) || hasSupportedGitRepositoryReference(prompt)) {
|
|
79
|
+
groups.add("source");
|
|
80
|
+
}
|
|
81
|
+
if (/\b(document intake|project memory|orchestration policy|subagent policy)\b/.test(lower)) {
|
|
72
82
|
groups.add("knowledge");
|
|
73
83
|
}
|
|
74
84
|
if (/\b(permission|capability|exec policy|tool policy)\b/.test(lower)) groups.add("policy");
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import type { TaskContract } from "../../extensions/guard-types.ts";
|
|
4
|
+
import { extractShellCommandInput, normalizeShellCommandForPolicy } from "../../extensions/guard-shell-analysis.ts";
|
|
5
|
+
import { commandMatchesVerifyPlan } from "../../extensions/runtime-evidence.js";
|
|
6
|
+
import { passingVerifyCommandsForDigest } from "../../extensions/task-contract-view.js";
|
|
7
|
+
import {
|
|
8
|
+
workingTreeSnapshot,
|
|
9
|
+
workingTreeSnapshotHasUnavailableEvidence
|
|
10
|
+
} from "../../extensions/task-state.js";
|
|
11
|
+
import { workingTreeEvidenceDigest } from "../../extensions/task-lifecycle.js";
|
|
12
|
+
|
|
13
|
+
const SHELL_TOOLS = new Set(["bash", "shell", "exec"]);
|
|
14
|
+
const REUSE_COMMAND = "printf '%s\\n' 'Piagent reused exact verifier evidence for the unchanged working tree; the verifier was not rerun.'";
|
|
15
|
+
|
|
16
|
+
export type ExactVerifierReuseDecision = {
|
|
17
|
+
reused: boolean;
|
|
18
|
+
reasonCode: string;
|
|
19
|
+
commandDigest: string | null;
|
|
20
|
+
workingTreeDigest: string | null;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function messageText(message: unknown): string {
|
|
24
|
+
if (!message || typeof message !== "object") return "";
|
|
25
|
+
const content = (message as Record<string, any>).content;
|
|
26
|
+
if (typeof content === "string") return content;
|
|
27
|
+
if (!Array.isArray(content)) return "";
|
|
28
|
+
return content.filter((block) => block?.type === "text").map((block) => String(block.text ?? "")).join("\n");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function latestOperatorText(entries: unknown[]): string {
|
|
32
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
33
|
+
const entry = entries[index] as Record<string, any> | undefined;
|
|
34
|
+
if (entry?.type === "message" && entry.message?.role === "user") return messageText(entry.message);
|
|
35
|
+
}
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function operatorExplicitlyRequestedVerifierExecution(entries: unknown[]): boolean {
|
|
40
|
+
const value = latestOperatorText(entries).normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
|
|
41
|
+
return /\b(rerun|run again|test again|verify again|one more run|final run|last run)\b/.test(value)
|
|
42
|
+
|| /\b(run|execute)\b[^\n]{0,80}\b(test|tests|verify|verifier|lint|typecheck|build)\b/.test(value)
|
|
43
|
+
|| /\b(chay lai|test lai|verify lai|kiem tra lai|kiem thu lai|lan cuoi|chay (?:mot|1) lan nua)\b/.test(value)
|
|
44
|
+
|| /\b(chay|thuc hien)\b[^\n]{0,80}\b(test|kiem thu|verify|verifier|lint|typecheck|build)\b/.test(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function evaluateExactVerifierReuse(input: {
|
|
48
|
+
task?: TaskContract;
|
|
49
|
+
toolName: string;
|
|
50
|
+
toolInput: Record<string, unknown>;
|
|
51
|
+
workingTreeDigest: string | null;
|
|
52
|
+
sessionEntries?: unknown[];
|
|
53
|
+
}): ExactVerifierReuseDecision {
|
|
54
|
+
const no = (reasonCode: string): ExactVerifierReuseDecision => ({ reused: false, reasonCode, commandDigest: null, workingTreeDigest: input.workingTreeDigest });
|
|
55
|
+
const task = input.task;
|
|
56
|
+
if (!task || task.trace.outcome !== "pending" || task.changeMode !== "source-change") return no("no-pending-source-task");
|
|
57
|
+
if (!SHELL_TOOLS.has(input.toolName)) return no("not-shell-tool");
|
|
58
|
+
if (operatorExplicitlyRequestedVerifierExecution(input.sessionEntries ?? [])) return no("operator-requested-execution");
|
|
59
|
+
if (Object.hasOwn(input.toolInput, "args") && Array.isArray(input.toolInput.args) && input.toolInput.args.length > 0) return no("shell-args-present");
|
|
60
|
+
const extracted = extractShellCommandInput(input.toolInput);
|
|
61
|
+
if (!extracted.command || extracted.reason) return no("shell-command-unavailable");
|
|
62
|
+
const command = normalizeShellCommandForPolicy(extracted.command);
|
|
63
|
+
if (!commandMatchesVerifyPlan(command, task.verifyCommands)) return no("not-exact-verifier");
|
|
64
|
+
const passing = passingVerifyCommandsForDigest(task, input.workingTreeDigest);
|
|
65
|
+
const exactVerifier = task.verifyCommands.find((candidate) => (
|
|
66
|
+
passing.has(candidate.trim())
|
|
67
|
+
&& commandMatchesVerifyPlan(command, [candidate])
|
|
68
|
+
));
|
|
69
|
+
if (!exactVerifier) return no("current-tree-pass-missing");
|
|
70
|
+
const field = typeof input.toolInput.command === "string" ? "command" : typeof input.toolInput.cmd === "string" ? "cmd" : null;
|
|
71
|
+
if (!field) return no("mutable-command-field-missing");
|
|
72
|
+
input.toolInput[field] = REUSE_COMMAND;
|
|
73
|
+
return {
|
|
74
|
+
reused: true,
|
|
75
|
+
reasonCode: "current-tree-exact-verifier-reused",
|
|
76
|
+
commandDigest: crypto.createHash("sha256").update(exactVerifier.trim()).digest("hex"),
|
|
77
|
+
workingTreeDigest: input.workingTreeDigest
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function reuseCurrentTreeExactVerifier(input: {
|
|
82
|
+
cwd: string;
|
|
83
|
+
task?: TaskContract;
|
|
84
|
+
toolName: string;
|
|
85
|
+
toolInput: Record<string, unknown>;
|
|
86
|
+
sessionEntries?: unknown[];
|
|
87
|
+
}): ExactVerifierReuseDecision {
|
|
88
|
+
if (!input.task || !SHELL_TOOLS.has(input.toolName)) {
|
|
89
|
+
return { reused: false, reasonCode: "not-applicable", commandDigest: null, workingTreeDigest: null };
|
|
90
|
+
}
|
|
91
|
+
const extracted = extractShellCommandInput(input.toolInput);
|
|
92
|
+
const command = extracted.command ? normalizeShellCommandForPolicy(extracted.command) : "";
|
|
93
|
+
if (!command || !commandMatchesVerifyPlan(command, input.task.verifyCommands)) {
|
|
94
|
+
return { reused: false, reasonCode: "not-exact-verifier", commandDigest: null, workingTreeDigest: null };
|
|
95
|
+
}
|
|
96
|
+
const snapshot = workingTreeSnapshot(input.cwd) as Record<string, string>;
|
|
97
|
+
const workingTreeDigest = workingTreeSnapshotHasUnavailableEvidence(snapshot)
|
|
98
|
+
? null
|
|
99
|
+
: workingTreeEvidenceDigest(snapshot);
|
|
100
|
+
return evaluateExactVerifierReuse({ ...input, workingTreeDigest });
|
|
101
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { RUNTIME_INTAKE_MESSAGE_MAX_CHARS } from "../runtime-limits.ts";
|
|
2
|
+
import { verifierCommandBoundsError } from "../../extensions/verification-intelligence.js";
|
|
3
|
+
|
|
4
|
+
const COMPACTION_MARKER = "[Piagent intake guidance compacted; the complete operator request and durable Task Contract remain authoritative.]";
|
|
5
|
+
const MAX_EXACT_FINAL_OUTPUT_LINE_CHARS = 512;
|
|
6
|
+
|
|
7
|
+
function edgeBounded(value: string, maximum: number): string {
|
|
8
|
+
if (value.length <= maximum) return value;
|
|
9
|
+
const marker = " … ";
|
|
10
|
+
const available = maximum - marker.length;
|
|
11
|
+
const head = Math.floor(available * 0.6);
|
|
12
|
+
return `${value.slice(0, head).trimEnd()}${marker}${value.slice(-(available - head)).trimStart()}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function legacyHeadTail(text: string): string {
|
|
16
|
+
const marker = `\n\n${COMPACTION_MARKER}\n\n`;
|
|
17
|
+
const available = RUNTIME_INTAKE_MESSAGE_MAX_CHARS - marker.length;
|
|
18
|
+
const head = Math.floor(available * 0.58);
|
|
19
|
+
return `${text.slice(0, head).trimEnd()}${marker}${text.slice(-(available - head)).trimStart()}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function minimalCriticalProof(lines: string[]): string[] {
|
|
23
|
+
if (lines.length === 0) return [];
|
|
24
|
+
const tags = [...new Set((lines.join("\n").match(/\[(?:criterion-[0-9]{2}|fallback)(?::[^\]]+)?\]/g) ?? []).map((tag) => {
|
|
25
|
+
const identity = tag.match(/^\[(criterion-[0-9]{2}|fallback)/)?.[1] ?? "fallback";
|
|
26
|
+
if (tag.includes(":fallback")) return `[${identity}:fallback]`;
|
|
27
|
+
if (tag.includes(":")) return `[${identity}:candidate]`;
|
|
28
|
+
return `[${identity}]`;
|
|
29
|
+
}))].slice(0, 12);
|
|
30
|
+
return [
|
|
31
|
+
"Critical behavioral proof:",
|
|
32
|
+
`- ${tags.join("")} Prove every tagged observable clause with linked durable live assertions; the generic verifier alone is insufficient.`,
|
|
33
|
+
"Fallback tags require a focused test. Run proof after final mutation and before exact verifiers; prose or transient probes are insufficient."
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function minimalExecutionMap(lines: string[]): string[] {
|
|
38
|
+
if (lines.length === 0) return [];
|
|
39
|
+
const rows = lines.slice(1, 13).map((line) => {
|
|
40
|
+
const criterion = line.match(/^-\s+(criterion-[0-9]{2})\b/)?.[1];
|
|
41
|
+
const target = line.match(/^-\s+criterion-[0-9]{2}\s+(\S+)/)?.[1];
|
|
42
|
+
const after = line.match(/\bafter=(\S+)/)?.[1];
|
|
43
|
+
if (!criterion) return `- ${edgeBounded(line.replace(/^-\s*/, ""), 72)}`;
|
|
44
|
+
return `- ${criterion}${target ? ` ${edgeBounded(target, 38)}` : ""}${after ? ` after=${edgeBounded(after, 18)}` : ""}`;
|
|
45
|
+
});
|
|
46
|
+
return [lines[0], ...rows];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function identityOnlyExecutionMap(lines: string[]): string[] {
|
|
50
|
+
if (lines.length === 0) return [];
|
|
51
|
+
return [
|
|
52
|
+
lines[0],
|
|
53
|
+
...lines.slice(1, 13).map((line, index) => `- ${line.match(/\bcriterion-[0-9]{2}\b/)?.[0] ?? `criterion-${String(index + 1).padStart(2, "0")}`}`)
|
|
54
|
+
];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function structuredCompaction(text: string): string | undefined {
|
|
58
|
+
const lines = text.split("\n");
|
|
59
|
+
const critical = lines.indexOf("Critical behavioral proof:");
|
|
60
|
+
const exactOutput = lines.findIndex((line) => line.startsWith("Exact final-output contract:"));
|
|
61
|
+
const exact = lines.indexOf("Exact verifier commands:");
|
|
62
|
+
const execution = lines.indexOf("Execution map (planning only):");
|
|
63
|
+
const reuse = lines.findIndex((line, index) => index > execution && line.startsWith("Use runtime-delivered source;"));
|
|
64
|
+
if (!(exact >= 0 && execution > exact && reuse > execution)) return undefined;
|
|
65
|
+
const publicContract = critical >= 0
|
|
66
|
+
? lines.findIndex((line, index) => index > critical && index < exact && line === "Existing public contract:")
|
|
67
|
+
: -1;
|
|
68
|
+
const criticalLines = critical >= 0 ? lines.slice(critical, publicContract > critical ? publicContract : exact) : [];
|
|
69
|
+
const exactOutputLines = exactOutput >= 0 && exactOutput < exact ? [lines[exactOutput]] : [];
|
|
70
|
+
const exactLines = lines.slice(exact, execution);
|
|
71
|
+
const executionLines = lines.slice(execution, reuse);
|
|
72
|
+
const prefixEnd = [critical, exactOutput, exact].filter((index) => index >= 0).sort((left, right) => left - right)[0];
|
|
73
|
+
const prefix = lines.slice(0, prefixEnd).slice(0, 3);
|
|
74
|
+
const finalGuidance = [...lines].reverse().find(Boolean) ?? "";
|
|
75
|
+
const compose = (proof: string[], map: string[], compactPrefix = false) => [
|
|
76
|
+
...(compactPrefix ? prefix.map((line) => edgeBounded(line, 360)) : prefix),
|
|
77
|
+
COMPACTION_MARKER,
|
|
78
|
+
...proof,
|
|
79
|
+
...exactOutputLines,
|
|
80
|
+
...displayExactLines,
|
|
81
|
+
...map,
|
|
82
|
+
lines[reuse],
|
|
83
|
+
finalGuidance
|
|
84
|
+
].filter(Boolean).join("\n");
|
|
85
|
+
const presentedCommands = exactLines.slice(1)
|
|
86
|
+
.map((line) => line.match(/^Verifier\s+\d+\s+\(run as an exact standalone shell command\): (.*)$/)?.[1] ?? line)
|
|
87
|
+
.filter((command) => command.trim());
|
|
88
|
+
const displayExactLines = [
|
|
89
|
+
"Exact verifier commands:",
|
|
90
|
+
...presentedCommands.map((command, index) => `Verifier ${index + 1} (run as an exact standalone shell command): ${command}`)
|
|
91
|
+
];
|
|
92
|
+
const verifierError = verifierCommandBoundsError(presentedCommands);
|
|
93
|
+
if (verifierError) return [
|
|
94
|
+
...prefix.slice(0, 2).map((line) => edgeBounded(line, 220)),
|
|
95
|
+
COMPACTION_MARKER,
|
|
96
|
+
...exactOutputLines.map((line) => edgeBounded(line, 260)),
|
|
97
|
+
"Exact verifier commands:",
|
|
98
|
+
`Runtime intake refused: ${verifierError} No verifier was truncated or accepted as evidence; correct the profile and restart the task.`,
|
|
99
|
+
...minimalExecutionMap(executionLines),
|
|
100
|
+
edgeBounded(lines[reuse], 220),
|
|
101
|
+
"Stop this task until a bounded exact verifier is configured."
|
|
102
|
+
].filter(Boolean).join("\n");
|
|
103
|
+
if (exactOutputLines.some((line) => line.length > MAX_EXACT_FINAL_OUTPUT_LINE_CHARS)) return [
|
|
104
|
+
...prefix.slice(0, 2).map((line) => edgeBounded(line, 220)),
|
|
105
|
+
COMPACTION_MARKER,
|
|
106
|
+
"Exact final-output contract:",
|
|
107
|
+
`Runtime intake refused: the mandatory exact final-output guidance exceeds ${MAX_EXACT_FINAL_OUTPUT_LINE_CHARS} characters and cannot be carried verbatim. Shorten the directive and restart the task.`,
|
|
108
|
+
...displayExactLines,
|
|
109
|
+
...identityOnlyExecutionMap(executionLines).slice(0, 9),
|
|
110
|
+
"Stop this task until the exact final-output contract can be injected without mutation."
|
|
111
|
+
].filter(Boolean).join("\n");
|
|
112
|
+
const preferred = compose(criticalLines, executionLines);
|
|
113
|
+
if (preferred.length <= RUNTIME_INTAKE_MESSAGE_MAX_CHARS) return preferred;
|
|
114
|
+
const minimal = compose(minimalCriticalProof(criticalLines), minimalExecutionMap(executionLines), true);
|
|
115
|
+
if (minimal.length <= RUNTIME_INTAKE_MESSAGE_MAX_CHARS) return minimal;
|
|
116
|
+
const guaranteed = [
|
|
117
|
+
...prefix.slice(0, 2).map((line) => edgeBounded(line, 220)),
|
|
118
|
+
COMPACTION_MARKER,
|
|
119
|
+
...minimalCriticalProof(criticalLines),
|
|
120
|
+
...exactOutputLines,
|
|
121
|
+
...displayExactLines,
|
|
122
|
+
...minimalExecutionMap(executionLines),
|
|
123
|
+
edgeBounded(lines[reuse], 220),
|
|
124
|
+
edgeBounded(finalGuidance, 220)
|
|
125
|
+
].filter(Boolean).join("\n");
|
|
126
|
+
if (guaranteed.length <= RUNTIME_INTAKE_MESSAGE_MAX_CHARS) return guaranteed;
|
|
127
|
+
const final = [
|
|
128
|
+
...prefix.slice(0, 1).map((line) => edgeBounded(line, 120)),
|
|
129
|
+
COMPACTION_MARKER,
|
|
130
|
+
...minimalCriticalProof(criticalLines),
|
|
131
|
+
...exactOutputLines,
|
|
132
|
+
...displayExactLines,
|
|
133
|
+
...identityOnlyExecutionMap(executionLines),
|
|
134
|
+
edgeBounded(lines[reuse], 120),
|
|
135
|
+
edgeBounded(finalGuidance, 120)
|
|
136
|
+
].filter(Boolean).join("\n");
|
|
137
|
+
if (final.length <= RUNTIME_INTAKE_MESSAGE_MAX_CHARS) return final;
|
|
138
|
+
const absoluteFallback = [
|
|
139
|
+
COMPACTION_MARKER,
|
|
140
|
+
...minimalCriticalProof(criticalLines).slice(0, 2).map((line) => edgeBounded(line, 420)),
|
|
141
|
+
...exactOutputLines,
|
|
142
|
+
...displayExactLines,
|
|
143
|
+
...identityOnlyExecutionMap(executionLines).slice(0, 9),
|
|
144
|
+
edgeBounded(lines[reuse], 100),
|
|
145
|
+
edgeBounded(finalGuidance, 100)
|
|
146
|
+
].filter(Boolean).join("\n");
|
|
147
|
+
if (absoluteFallback.length <= RUNTIME_INTAKE_MESSAGE_MAX_CHARS) return absoluteFallback;
|
|
148
|
+
return [
|
|
149
|
+
COMPACTION_MARKER,
|
|
150
|
+
...exactOutputLines,
|
|
151
|
+
...displayExactLines,
|
|
152
|
+
"Runtime intake refused: mandatory exact output and verifier guidance cannot fit the bounded provider intake without mutation. Shorten the configured contract and restart the task.",
|
|
153
|
+
"Stop this task; do not infer or reconstruct omitted acceptance details."
|
|
154
|
+
].filter(Boolean).join("\n");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function boundedRuntimeIntakeMessage(value: string): string {
|
|
158
|
+
const text = String(value ?? "").replace(/\r\n/g, "\n");
|
|
159
|
+
const structured = structuredCompaction(text);
|
|
160
|
+
if (text.length <= RUNTIME_INTAKE_MESSAGE_MAX_CHARS) {
|
|
161
|
+
return structured?.includes("Runtime intake refused:") ? structured : text;
|
|
162
|
+
}
|
|
163
|
+
return structured ?? legacyHeadTail(text);
|
|
164
|
+
}
|
|
@@ -1,17 +1,41 @@
|
|
|
1
1
|
import { classifyContextTask } from "../../extensions/context-engine.js";
|
|
2
2
|
import { matchesAnyPath, matchesProtectedPath, normalizePathCandidate } from "../../extensions/policy-core.js";
|
|
3
3
|
import type { ReviewLens } from "../../extensions/guard-types.js";
|
|
4
|
-
import { LONG_INPUT_CHARS
|
|
4
|
+
import { LONG_INPUT_CHARS } from "../runtime-limits.ts";
|
|
5
|
+
export { boundedRuntimeIntakeMessage } from "./runtime-intake-compaction.ts";
|
|
5
6
|
|
|
6
7
|
const AUTO_INTAKE_MAX_PROMPT_CHARS = LONG_INPUT_CHARS;
|
|
7
8
|
const AUTO_TASK_SUMMARY_CHARS = 700;
|
|
8
9
|
const AUTO_ACCEPTANCE_CRITERION_CHARS = 600;
|
|
9
10
|
const AUTO_ACCEPTANCE_CRITERIA_MAX = 12;
|
|
10
|
-
const AUTO_INTAKE_CHANGE_INTENT = /\b(?:add|build|change|correct|create|fix|implement|modify|refactor|remove|rename|repair|replace|update|write|sua|them|doi|cap nhat|xoa|tao)\b/i;
|
|
11
|
+
const AUTO_INTAKE_CHANGE_INTENT = /\b(?:add|build|change|correct|create|fix|implement|modify|mutate|refactor|remove|rename|repair|replace|update|write|sua|them|doi|cap nhat|xoa|tao)\b/i;
|
|
11
12
|
const AUTO_INTAKE_READ_ONLY_LEAD = /^\s*\/?(?:analy[sz]e|audit|check|discuss|explain|inspect|plan|research|review|scout|summari[sz]e|why|how|can\s+(?:you|we)|kiem tra|nghien cuu|giai thich|danh gia)\b/i;
|
|
12
13
|
const AUTO_INTAKE_MANUAL_RISK = /\b(?:credential|database|deploy|destructive|encryption|external provider|payment|permission|production|publish|secret|token rotation)\b/i;
|
|
13
14
|
const AUTO_READ_ONLY_INTENT = /\b(?:analy[sz]e|audit|check|diagnos(?:e|is)|explain|inspect|investigate|plan|research|review|scout|summari[sz]e|triage|kiem tra|nghien cuu|giai thich|danh gia)\b/i;
|
|
14
|
-
|
|
15
|
+
// Candidate wording is separated from the following qualifier. This keeps a
|
|
16
|
+
// durable zero-delta instruction distinct from local path authority and from a
|
|
17
|
+
// temporary "inspect first, edit later" instruction.
|
|
18
|
+
const AUTO_NO_MUTATION_CANDIDATES = [
|
|
19
|
+
/\b(?:do not|don't|must not|never)\s+(?:edit|change|modify|mutate|touch|write(?:\s+to)?)\s+(?:(?:any|all|the|this|entire)\s+)?(?:files?|code|source(?:\s+files?)?|project(?:\s+files?)?|workspace|repo(?:sitory)?|anything)\b/i,
|
|
20
|
+
/\b(?:do not|don't|must not|never)\s+make\s+(?:any\s+)?(?:changes?|edits?|mutations?)(?:\s+to\s+(?:(?:any|all|the)\s+)?(?:files?|code|source(?:\s+files?)?|project(?:\s+files?)?|workspace|repo(?:sitory)?))?\b/i,
|
|
21
|
+
/\b(?:make\s+)?no\s+(?:(?:code|source|project|file|workspace|repo(?:sitory)?)\s+)?(?:changes?|edits?|mutations?)(?:\s+to\s+(?:(?:any|all|the)\s+)?(?:files?|code|source(?:\s+files?)?|project(?:\s+files?)?|workspace|repo(?:sitory)?))?\b/i,
|
|
22
|
+
/\bno\s+(?:project|source)\s+files?\s+(?:are\s+)?(?:changed|edited|modified|mutated)\b/i,
|
|
23
|
+
/\bwithout\s+(?:editing|changing|modifying|mutating|touching|writing(?:\s+to)?)\s+(?:(?:any|all|the)\s+)?(?:files?|code|source(?:\s+files?)?|project(?:\s+files?)?|workspace|repo(?:sitory)?)\b/i,
|
|
24
|
+
/\bleave\s+(?:(?:all|the|this|entire)\s+)?(?:files?|code|source|project|workspace|repo(?:sitory)?)\s+(?:fully\s+)?(?:unchanged|unmodified)\b/i,
|
|
25
|
+
/\b(?:zero\s+task\s+delta|mutation[- ]free)\b/i,
|
|
26
|
+
/\bkh(?:o|ô)ng\s+(?:s(?:u|ử)a|edit|thay\s+(?:d|đ)(?:o|ổ)i)\s+(?:b(?:a|ấ)t\s+k(?:y|ỳ)\s+)?(?:file|source|project|workspace|repo)\b/i
|
|
27
|
+
];
|
|
28
|
+
const AUTO_BOUNDARY_TAIL_PREFIX = "^(?:\\s|[,.;:!?()\\[\\]{}—–-])*(?:but\\s+)?";
|
|
29
|
+
const AUTO_LOCAL_BOUNDARY_TAIL = new RegExp(`${AUTO_BOUNDARY_TAIL_PREFIX}(?:outside|beyond|under|within|inside|in|on|for(?!\\s+now\\b)|except(?:\\s+for)?|other\\s+than|apart\\s+from|to\\b|only\\s+(?:(?:edit|change|modify|mutate|touch|write)\\b|(?:in|under|within|outside)\\b|(?:[a-z0-9_.@*?-]+\\/))|ngo(?:a|à)i\\b|tr(?:u|ừ)\\b)`, "i");
|
|
30
|
+
const AUTO_TEMPORARY_BOUNDARY_TAIL = new RegExp(`${AUTO_BOUNDARY_TAIL_PREFIX}(?:(?:not\\s+)?yet\\b|right\\s+now\\b|for\\s+now\\b|at\\s+(?:(?:this|the\\s+current)\\s+(?:step|stage|phase|time)|the\\s+moment)\\b|during\\s+(?:(?:this|the\\s+current)\\s+)?(?:step|stage|phase|planning)\\b|while\\b|unless\\b|until\\b|before\\b|hi(?:e|ệ)n\\s+t(?:a|ạ)i\\b|b(?:a|â)y\\s+gi(?:o|ờ)\\b)`, "i");
|
|
31
|
+
const AUTO_GLOBAL_READ_ONLY_PATTERNS = [
|
|
32
|
+
/^\s*\/?read-only\s*(?:$|[.!?:;—–-])/im,
|
|
33
|
+
/^\s*\/?read-only\s+(?:task|run|session)\s*(?:$|[.!?:;—–-]|\b(?:to|for)\b)/im,
|
|
34
|
+
/^\s*\/?read-only\s+(?:assessment|review)\s*(?:$|[.!?:;—–-]|\b(?:of|for)\b)/im,
|
|
35
|
+
/\b(?:as|in)\s+(?:a\s+)?read-only\s+(?:mode|task|run|session|assessment|review)\b/i,
|
|
36
|
+
/\b(?:use|perform|conduct|run)\s+(?:a\s+)?read-only\s+(?:task|run|assessment|review)\b/i,
|
|
37
|
+
/\b(?:keep\s+)?(?:this\s+|the\s+)?(?:task|run|session|workspace|project|repo|repository)\s+(?:(?:is|must|should)\s+(?:be\s+|remain\s+)?|remain\s+)?read-only\b/i
|
|
38
|
+
];
|
|
15
39
|
const AUTO_EXECUTION_INTENT = /(?:\b(?:run|execute|execution|rerun|re-run|chay)\b.{0,80}\b(?:tests?|build|checks?|gates?|lint|typecheck|package|pack|verify|verification)\b|\b(?:npm|pnpm|yarn|bun)\s+(?:test|pack|run\s+(?:build|check|lint|typecheck|verify))\b)/i;
|
|
16
40
|
|
|
17
41
|
export const AUTO_INTAKE_SNAPSHOT_PATTERNS = [
|
|
@@ -22,6 +46,27 @@ function uniqueStrings(values: string[]): string[] {
|
|
|
22
46
|
return [...new Set(values)];
|
|
23
47
|
}
|
|
24
48
|
|
|
49
|
+
function noMutationBoundarySignals(text: string): { taskWide: boolean; temporary: boolean } {
|
|
50
|
+
let temporary = false;
|
|
51
|
+
for (const pattern of AUTO_NO_MUTATION_CANDIDATES) {
|
|
52
|
+
const matcher = new RegExp(pattern.source, `${pattern.flags}g`);
|
|
53
|
+
for (const match of text.matchAll(matcher)) {
|
|
54
|
+
const tail = text.slice((match.index ?? 0) + match[0].length, (match.index ?? 0) + match[0].length + 160);
|
|
55
|
+
if (AUTO_LOCAL_BOUNDARY_TAIL.test(tail)) continue;
|
|
56
|
+
if (AUTO_TEMPORARY_BOUNDARY_TAIL.test(tail)) {
|
|
57
|
+
temporary = true;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
return { taskWide: true, temporary };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { taskWide: false, temporary };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function hasGlobalReadOnlyBoundary(text: string): boolean {
|
|
67
|
+
return AUTO_GLOBAL_READ_ONLY_PATTERNS.some((pattern) => pattern.test(text));
|
|
68
|
+
}
|
|
69
|
+
|
|
25
70
|
const PLAUSIBLE_SCOPE_ROOT = /^(?:\.github|app|apps|bin|config|docs|examples|lib|logs|packages|pages|public|scripts|spec|src|test|tests|vendor|__tests__)(?:\/|$)/i;
|
|
26
71
|
const PLAUSIBLE_SCOPE_FILE = /(?:^|\/)(?:\.[^/]+|[^/]+\.(?:bash|c|cc|cjs|cpp|css|csv|env|go|graphql|gql|h|hpp|html|java|js|json|jsx|kt|kts|md|mdx|mjs|php|proto|py|rb|rs|scss|sh|sql|svg|swift|toml|ts|tsx|txt|xml|yaml|yml))$/i;
|
|
27
72
|
|
|
@@ -37,18 +82,11 @@ export function automaticTaskSummary(prompt: string): string {
|
|
|
37
82
|
return String(prompt ?? "").replace(/\s+/g, " ").trim().slice(0, AUTO_TASK_SUMMARY_CHARS);
|
|
38
83
|
}
|
|
39
84
|
|
|
40
|
-
export function boundedRuntimeIntakeMessage(value: string): string {
|
|
41
|
-
const text = String(value ?? "").replace(/\r\n/g, "\n");
|
|
42
|
-
if (text.length <= RUNTIME_INTAKE_MESSAGE_MAX_CHARS) return text;
|
|
43
|
-
const marker = "\n\n[Piagent intake guidance compacted; the complete operator request and durable Task Contract remain authoritative.]\n\n";
|
|
44
|
-
const available = RUNTIME_INTAKE_MESSAGE_MAX_CHARS - marker.length;
|
|
45
|
-
const head = Math.floor(available * 0.58);
|
|
46
|
-
return `${text.slice(0, head).trimEnd()}${marker}${text.slice(-(available - head)).trimStart()}`;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
85
|
export function automaticTaskIntakeEligible(prompt: string, readProtectedPaths: string[]): boolean {
|
|
50
86
|
const text = String(prompt ?? "").trim();
|
|
51
87
|
if (!text || text.length > AUTO_INTAKE_MAX_PROMPT_CHARS) return false;
|
|
88
|
+
const noMutationBoundary = noMutationBoundarySignals(text);
|
|
89
|
+
if (noMutationBoundary.temporary && !noMutationBoundary.taskWide) return false;
|
|
52
90
|
const signal = classifyContextTask(text);
|
|
53
91
|
if (AUTO_EXECUTION_INTENT.test(text)) {
|
|
54
92
|
if (/\bpiagent_task_start\b/i.test(text)) return false;
|
|
@@ -65,8 +103,9 @@ export function automaticReadOnlyTaskIntakeEligible(prompt: string, readProtecte
|
|
|
65
103
|
if (!text || text.length > AUTO_INTAKE_MAX_PROMPT_CHARS) return false;
|
|
66
104
|
const signal = classifyContextTask(text);
|
|
67
105
|
if (signal.workflow === "usage" || signal.workflow === "permission" || signal.workflow === "context") return false;
|
|
68
|
-
|
|
69
|
-
if (
|
|
106
|
+
const readOnlyBoundary = noMutationBoundarySignals(text).taskWide || hasGlobalReadOnlyBoundary(text);
|
|
107
|
+
if (!AUTO_READ_ONLY_INTENT.test(text) && !readOnlyBoundary) return false;
|
|
108
|
+
if (AUTO_INTAKE_CHANGE_INTENT.test(text) && !readOnlyBoundary) return false;
|
|
70
109
|
if (/\bpiagent_task_start\b/i.test(text)) return false;
|
|
71
110
|
return !signal.paths.some((candidate) => matchesProtectedPath(candidate, readProtectedPaths));
|
|
72
111
|
}
|
|
@@ -81,36 +120,85 @@ export function automaticTaskMutationPolicy(
|
|
|
81
120
|
prompt: string,
|
|
82
121
|
changeMode: "source-change" | "read-only"
|
|
83
122
|
): "required" | "forbidden" {
|
|
84
|
-
|
|
123
|
+
const text = String(prompt ?? "");
|
|
124
|
+
// A feature can intentionally become read-only while the implementation
|
|
125
|
+
// still requires source changes. Only a task-wide boundary may suppress
|
|
126
|
+
// mutation; incidental domain wording such as "list/detail read-only" must
|
|
127
|
+
// never convert a delegated implementation into a zero-delta task.
|
|
128
|
+
return changeMode === "read-only"
|
|
129
|
+
|| noMutationBoundarySignals(text).taskWide
|
|
130
|
+
|| hasGlobalReadOnlyBoundary(text)
|
|
131
|
+
? "forbidden"
|
|
132
|
+
: "required";
|
|
85
133
|
}
|
|
86
134
|
|
|
87
135
|
export function automaticTaskRiskLane(prompt: string): "tiny" | "normal" {
|
|
88
136
|
return classifyContextTask(prompt).lane === "tiny" ? "tiny" : "normal";
|
|
89
137
|
}
|
|
90
138
|
|
|
139
|
+
const ATOMIC_CLAUSE_LEAD = /^(?:accept|add|build|change|correct|create|do not|emit|ensure|fail|fix|handle|implement|invalid|missing|modify|must|never|parse|preserve|reject|repair|return|support|throw|treat|update|verify|without)\b/i;
|
|
140
|
+
|
|
141
|
+
function topLevelImperativeClauses(value: string): string[] {
|
|
142
|
+
const clauses: string[] = [];
|
|
143
|
+
let current = "", quote = "", round = 0, square = 0, curly = 0;
|
|
144
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
145
|
+
const character = value[index], previous = value[index - 1] ?? "", next = value[index + 1] ?? "";
|
|
146
|
+
if (quote) {
|
|
147
|
+
current += character;
|
|
148
|
+
if (character === quote && previous !== "\\") quote = "";
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const apostropheInsideWord = character === "'" && /[\p{L}\p{N}]/u.test(previous) && /[\p{L}\p{N}]/u.test(next);
|
|
152
|
+
if ((character === "`" || character === '"' || character === "'") && !apostropheInsideWord) {
|
|
153
|
+
quote = character; current += character;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (character === "(") round += 1;
|
|
157
|
+
else if (character === ")") round = Math.max(0, round - 1);
|
|
158
|
+
else if (character === "[") square += 1;
|
|
159
|
+
else if (character === "]") square = Math.max(0, square - 1);
|
|
160
|
+
else if (character === "{") curly += 1;
|
|
161
|
+
else if (character === "}") curly = Math.max(0, curly - 1);
|
|
162
|
+
if (character === ";" && round === 0 && square === 0 && curly === 0 && ATOMIC_CLAUSE_LEAD.test(value.slice(index + 1).trimStart())) {
|
|
163
|
+
if (current.trim().length >= 8) clauses.push(current.trim());
|
|
164
|
+
current = "";
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
current += character;
|
|
168
|
+
}
|
|
169
|
+
if (current.trim()) clauses.push(current.trim());
|
|
170
|
+
return clauses;
|
|
171
|
+
}
|
|
172
|
+
|
|
91
173
|
function splitAcceptanceCriterion(value: string): string[] {
|
|
92
174
|
const normalized = value.replace(/^\s*(?:[-*+] |\d+[.)]\s+)/, "").replace(/\s+/g, " ").trim();
|
|
93
175
|
if (!normalized || normalized.length < 8) return [];
|
|
94
176
|
const label = normalized.match(/^(\[[^\]\n]{1,40}\])\s+/)?.[1] ?? "";
|
|
95
|
-
|
|
177
|
+
const body = label ? normalized.slice(label.length).trim() : normalized;
|
|
96
178
|
const prefix = label ? `${label} ` : "";
|
|
97
179
|
const available = AUTO_ACCEPTANCE_CRITERION_CHARS - prefix.length;
|
|
98
180
|
const fragments: string[] = [];
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
181
|
+
const units = topLevelImperativeClauses(body)
|
|
182
|
+
.flatMap((clause) => clause.split(/(?<=[.!?])\s+(?=(?:[`"'([{]|[\p{Lu}\p{N}]|(?:do|must|never|verify)\b))/u))
|
|
183
|
+
.filter((item) => item.trim().length >= 8);
|
|
184
|
+
for (const unit of units) {
|
|
185
|
+
let remaining = unit.trim();
|
|
186
|
+
while (remaining.length > available) {
|
|
187
|
+
const window = remaining.slice(0, available + 1);
|
|
188
|
+
const sentence = [...window.matchAll(/[.!?;:]\s+/g)].at(-1);
|
|
189
|
+
const clause = [...window.matchAll(/,\s+/g)].at(-1);
|
|
190
|
+
const whitespace = window.lastIndexOf(" ");
|
|
191
|
+
const preferred = sentence && sentence.index! + sentence[0].trimEnd().length >= Math.floor(available / 3)
|
|
192
|
+
? sentence.index! + sentence[0].trimEnd().length
|
|
193
|
+
: clause && clause.index! + 1 >= Math.floor(available / 2)
|
|
194
|
+
? clause.index! + 1
|
|
195
|
+
: whitespace;
|
|
196
|
+
const cut = preferred > 0 ? preferred : available;
|
|
197
|
+
fragments.push(`${prefix}${remaining.slice(0, cut).trim()}`);
|
|
198
|
+
remaining = remaining.slice(cut).trim();
|
|
199
|
+
}
|
|
200
|
+
if (remaining) fragments.push(`${prefix}${remaining}`);
|
|
112
201
|
}
|
|
113
|
-
if (remaining) fragments.push(`${prefix}${remaining}`);
|
|
114
202
|
return fragments;
|
|
115
203
|
}
|
|
116
204
|
|
|
@@ -143,18 +231,56 @@ function boundedAcceptanceCriteria(values: string[], limit: number): string[] {
|
|
|
143
231
|
return values.filter((criterion) => selected.has(criterion));
|
|
144
232
|
}
|
|
145
233
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
234
|
+
type AtomicCriterionGroup = { criteria: string[]; index: number };
|
|
235
|
+
|
|
236
|
+
function atomicCriterionScore(value: string): number {
|
|
237
|
+
const weighted = [
|
|
238
|
+
[/(?:\bdo not\b|\bdoes not\b|\bcannot\b|\bnever\b|\binvalid\b|\breject|\bthrow|\bfail(?:s|ed)?[- ]closed\b)/i, 8],
|
|
239
|
+
[/(?:\bidentical\b|\breplay|\bcanonical|\bidempoten)/i, 6],
|
|
240
|
+
[/(?:\bstale\b|changed owner|inclusive boundary)/i, 5],
|
|
241
|
+
[/\bexact(?:ly)?\b/i, 4],
|
|
242
|
+
[/(?:\bnon-finite\b|\bnon-json\b|\bmutat|\bdigest\b|\breturn|\boutput\b)/i, 3]
|
|
243
|
+
] as const;
|
|
244
|
+
return weighted.reduce((score, [pattern, weight]) => score + (pattern.test(value) ? weight : 0), 0);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function atomicCoverageCriteria(groups: AtomicCriterionGroup[], limit: number): string[] {
|
|
248
|
+
const candidates = groups.flatMap((group) => group.criteria.map((criterion, clause) => ({ criterion, group: group.index, clause })));
|
|
249
|
+
if (candidates.length <= limit) return uniqueStrings(candidates.map((item) => item.criterion));
|
|
250
|
+
const representatives = groups.filter((group) => group.criteria.length > 0).map((group) => group.criteria.at(-1)!);
|
|
251
|
+
const selected = new Set(boundedAcceptanceCriteria(representatives, Math.min(limit, representatives.length)));
|
|
252
|
+
const ranked = candidates.filter((item) => !selected.has(item.criterion)).sort((left, right) => (
|
|
253
|
+
atomicCriterionScore(right.criterion) - atomicCriterionScore(left.criterion)
|
|
254
|
+
|| left.group - right.group
|
|
255
|
+
|| left.clause - right.clause
|
|
256
|
+
));
|
|
257
|
+
for (const item of ranked) {
|
|
258
|
+
if (selected.size < limit) { selected.add(item.criterion); continue; }
|
|
259
|
+
if (groups.length <= limit || atomicCriterionScore(item.criterion) === 0) continue;
|
|
260
|
+
const sameGroupAlreadyCovered = candidates.some((candidate) => candidate.group === item.group && selected.has(candidate.criterion));
|
|
261
|
+
if (!sameGroupAlreadyCovered) continue;
|
|
262
|
+
const removable = candidates
|
|
263
|
+
.filter((candidate) => selected.has(candidate.criterion) && candidate.group !== item.group)
|
|
264
|
+
.sort((left, right) => (
|
|
265
|
+
Number(/^\[/.test(left.criterion)) - Number(/^\[/.test(right.criterion))
|
|
266
|
+
|| atomicCriterionScore(left.criterion) - atomicCriterionScore(right.criterion)
|
|
267
|
+
|| right.group - left.group
|
|
268
|
+
))
|
|
269
|
+
.find((candidate) => atomicCriterionScore(candidate.criterion) < atomicCriterionScore(item.criterion));
|
|
270
|
+
if (removable) { selected.delete(removable.criterion); selected.add(item.criterion); }
|
|
271
|
+
}
|
|
272
|
+
return uniqueStrings(candidates.filter((item) => selected.has(item.criterion)).map((item) => item.criterion)).slice(0, limit);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function automaticAcceptanceCriteria(prompt: string, changeMode: "source-change" | "read-only" = "source-change"): string[] {
|
|
150
276
|
const lines = String(prompt ?? "").split(/\r?\n/);
|
|
151
|
-
const
|
|
152
|
-
const obligation = /\b(?:add|change only|do not|emits?|ensure|exactly|fail(?:s|ed)?(?:[-\s]+)closed|invalid|missing|must|never|preserve|reject|returns?|throw|without)\b/i;
|
|
277
|
+
const criterionGroups: AtomicCriterionGroup[] = [];
|
|
278
|
+
const obligation = /\b(?:accept|add|build|change|change only|correct|create|do not|emits?|ensure|exactly|fail(?:s|ed)?(?:[-\s]+)closed|fix|handle|implement|invalid|missing|modify|must|never|parse|preserve|reject|repair|returns?|support|throw|treat|update|without)\b/i;
|
|
153
279
|
let current = "";
|
|
154
280
|
let currentIsBullet = false;
|
|
155
281
|
const push = (value: string) => {
|
|
156
282
|
if (isPathOnlyCriterion(value)) return;
|
|
157
|
-
|
|
283
|
+
criterionGroups.push({ criteria: splitAcceptanceCriterion(value), index: criterionGroups.length });
|
|
158
284
|
};
|
|
159
285
|
const flush = () => {
|
|
160
286
|
if (current && (currentIsBullet || obligation.test(current))) push(current);
|
|
@@ -178,8 +304,10 @@ export function automaticAcceptanceCriteria(
|
|
|
178
304
|
flush();
|
|
179
305
|
const generic = changeMode === "read-only"
|
|
180
306
|
? ["No project files are changed.", "The final response addresses the requested diagnostic result."]
|
|
181
|
-
: ["
|
|
182
|
-
const
|
|
307
|
+
: ["The configured verification command passes after the final mutation."];
|
|
308
|
+
const groupCount = criterionGroups.filter((group) => group.criteria.length > 0).length;
|
|
309
|
+
const reservedGeneric = Math.min(generic.length, Math.max(0, AUTO_ACCEPTANCE_CRITERIA_MAX - groupCount));
|
|
310
|
+
const uniqueCriteria = atomicCoverageCriteria(criterionGroups, AUTO_ACCEPTANCE_CRITERIA_MAX - reservedGeneric);
|
|
183
311
|
const selected = boundedAcceptanceCriteria(uniqueCriteria, AUTO_ACCEPTANCE_CRITERIA_MAX);
|
|
184
312
|
if (selected.length === AUTO_ACCEPTANCE_CRITERIA_MAX) return selected;
|
|
185
313
|
const missingGeneric = generic.filter((criterion) => !selected.includes(criterion));
|