@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
|
@@ -5,6 +5,7 @@ import { durableContextEvidenceEntries, hasDurableContextEvidence } from "./cont
|
|
|
5
5
|
import { matchesAnyPath, normalizePathCandidate } from "./policy-core.js";
|
|
6
6
|
import { baselineReturnRepresentationConflicts, returnRepresentationGuidance } from "./return-contract.js";
|
|
7
7
|
import { hasLengthPrefixedIdentityKey, tenantAssertionSignals } from "./tenant-contract.js";
|
|
8
|
+
import { criterionBehaviorProofDisposition, criterionRequiresBehavioralProof, durableBehaviorProofRequired, genericCriterionEvidence, genericFallbackEvidence, isVerificationOnlyCriterion, verifierCommandsCoverTests } from "./acceptance-behavior-proof.js";
|
|
8
9
|
import { latestObservedVerificationEvidence, meaningfulVerificationCommands, verificationEvidenceProvesStableTree } from "./verification-intelligence.js";
|
|
9
10
|
import {
|
|
10
11
|
acceptanceContractProofGuidance,
|
|
@@ -34,7 +35,7 @@ const EVIDENCE_FIELDS = new Set(["kind", "summary", "paths", "command", "exitCod
|
|
|
34
35
|
const PROVENANCE_FIELDS = new Set(["assurance", "disposition", "repairCount", "retryCount", "finalRecoveryDisposition", "failureRef", "recoveryRef", "handoffRef", "recordedAt"]);
|
|
35
36
|
const FAILURE_REF_FIELDS = new Set(["evidenceDigest", "category", "captureRef"]);
|
|
36
37
|
const RECOVERY_REF_FIELDS = new Set(["policyVersion", "action", "reasonCodes"]);
|
|
37
|
-
const HELPER_USAGE_FIELDS = new Set(["mode", "used", "reasonCodes", "helpers", "recordedAt"]);
|
|
38
|
+
const HELPER_USAGE_FIELDS = new Set(["mode", "used", "decision", "projectedSavingsRatio", "reasonCodes", "helpers", "recordedAt"]);
|
|
38
39
|
const HELPER_ENTRY_FIELDS = new Set(["role", "disposition", "requestRef", "outputDigest", "calls", "tokens"]);
|
|
39
40
|
const HELPER_ROLES = new Set(["retriever", "scout", "planner", "worker", "reviewer", "oracle", "researcher"]);
|
|
40
41
|
const RECEIPT_DISPOSITIONS = new Set(["first-pass-success", "repaired-success", "blocked", "partial", "failed", "pending"]);
|
|
@@ -43,7 +44,7 @@ const RECOVERY_ACTIONS = new Set(["repair", "retry", "fresh-session", "ask-opera
|
|
|
43
44
|
const FAILURE_CATEGORIES = new Set(["passed", "compile-typecheck", "test-assertion", "lint-format", "dependency-config", "environment", "provider-network", "permission-policy", "scope-protected-path", "flaky-infrastructure", "unknown"]);
|
|
44
45
|
const HASH = /^[a-f0-9]{64}$/;
|
|
45
46
|
const SAFE_REF = /^[a-z0-9.][a-z0-9:._/-]{0,511}$/i;
|
|
46
|
-
const READ_ONLY_BOUNDARY = /\b(?:read-only|no
|
|
47
|
+
const READ_ONLY_BOUNDARY = /\b(?:read-only|no\s+(?:code|source|project|file|workspace|repo(?:sitory)?)?\s*(?:edits?|changes?|mutations?)|do not\s+(?:edit|change|modify|mutate|touch)(?: files?| source| project| repo| workspace)?|leave\s+(?:the\s+)?(?:source|project|repo|workspace)\s+(?:unchanged|unmodified)|khong\s+(?:sua|edit|thay\s+doi)(?: file| source| project)?)\b/;
|
|
47
48
|
|
|
48
49
|
function sha256(value) {
|
|
49
50
|
return crypto.createHash("sha256").update(String(value ?? "")).digest("hex");
|
|
@@ -80,11 +81,9 @@ function normalizedText(value) {
|
|
|
80
81
|
.replace(/[\u0300-\u036f]/g, "")
|
|
81
82
|
.toLowerCase();
|
|
82
83
|
}
|
|
83
|
-
|
|
84
84
|
function includesAny(text, patterns) {
|
|
85
85
|
return patterns.some((pattern) => pattern.test(text));
|
|
86
86
|
}
|
|
87
|
-
|
|
88
87
|
function pathBoundaryMentioned(text, patterns) {
|
|
89
88
|
const value = normalizedText(text);
|
|
90
89
|
return uniqueStrings(patterns).some((pattern) => {
|
|
@@ -95,20 +94,35 @@ function pathBoundaryMentioned(text, patterns) {
|
|
|
95
94
|
return value.includes(lower) || (Boolean(basename) && value.includes(basename));
|
|
96
95
|
});
|
|
97
96
|
}
|
|
98
|
-
|
|
97
|
+
function readOnlyBoundaryTiedToPattern(text, patterns) {
|
|
98
|
+
return normalizedText(text)
|
|
99
|
+
.split(/(?:\r?\n+|[.!?;]+|\s+[—–]\s+)/)
|
|
100
|
+
.some((clause) => READ_ONLY_BOUNDARY.test(clause) && pathBoundaryMentioned(clause, patterns));
|
|
101
|
+
}
|
|
99
102
|
function changedFilesRespectBoundaries(changedFiles, boundaryPatterns) {
|
|
100
103
|
const patterns = uniqueStrings(boundaryPatterns);
|
|
101
104
|
if (patterns.length === 0) return true;
|
|
102
105
|
return uniqueStrings(changedFiles).every((file) => !matchesAnyPath(file, patterns));
|
|
103
106
|
}
|
|
107
|
+
function acceptanceReadOnlyInferenceOptions(input, text) {
|
|
108
|
+
const boundaryPatterns = uniqueStrings([
|
|
109
|
+
...(Array.isArray(input?.outOfScope) ? input.outOfScope : []),
|
|
110
|
+
...(Array.isArray(input?.protectedPaths) ? input.protectedPaths : [])
|
|
111
|
+
]);
|
|
112
|
+
return {
|
|
113
|
+
mutationPolicy: input?.mutationPolicy,
|
|
114
|
+
readOnlyBoundary: boundaryPatterns.length > 0
|
|
115
|
+
&& readOnlyBoundaryTiedToPattern(text, boundaryPatterns)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
104
118
|
|
|
105
|
-
export function inferAcceptanceObligations(text, changeMode = "source-change") {
|
|
119
|
+
export function inferAcceptanceObligations(text, changeMode = "source-change", options = {}) {
|
|
106
120
|
const value = normalizedText(text);
|
|
107
121
|
const accessControlText = value
|
|
108
122
|
.replace(/`[^`\n]*`/g, " ")
|
|
109
123
|
.replace(/\b(?:[a-z0-9_.@-]+\/)+[a-z0-9_.@/-]+\b/g, " ");
|
|
110
124
|
const obligations = [];
|
|
111
|
-
if (changeMode === "read-only" ||
|
|
125
|
+
if (changeMode === "read-only" || options.mutationPolicy === "forbidden" || options.readOnlyBoundary === true) {
|
|
112
126
|
obligations.push("read-only-evidence");
|
|
113
127
|
}
|
|
114
128
|
const actorAccessControl = includesAny(accessControlText, [
|
|
@@ -191,7 +205,6 @@ const GENERATED_ACCEPTANCE_TEXTS = new Set([
|
|
|
191
205
|
"authorization-deny-case", "tenant-boundary", "tenant-storage-isolation", "invalid-input-rejection", "boundary-case",
|
|
192
206
|
"backward-compatibility", "read-only-evidence", "verification-evidence"
|
|
193
207
|
].map((obligation) => generatedCriterionForObligation(obligation)));
|
|
194
|
-
|
|
195
208
|
function acceptanceTaskText(task) {
|
|
196
209
|
return [
|
|
197
210
|
task?.summary,
|
|
@@ -227,8 +240,9 @@ function semanticConflictReasons(obligation, task, corpus, criterion) {
|
|
|
227
240
|
|
|
228
241
|
/**
|
|
229
242
|
* Surface a concrete existing return contract before implementation starts.
|
|
230
|
-
* This is intentionally source-derived and bounded to exact
|
|
231
|
-
*
|
|
243
|
+
* This is intentionally source-derived and bounded to exact known files; it
|
|
244
|
+
* does not turn the task's initial focus into write authority or guess from
|
|
245
|
+
* benchmark scenario names and hidden expectations.
|
|
232
246
|
*/
|
|
233
247
|
export function acceptanceBaselineGuidance(task, options = {}) {
|
|
234
248
|
if (!options.cwd) return [];
|
|
@@ -247,7 +261,11 @@ export function acceptanceSemanticConflicts(task, options = {}) {
|
|
|
247
261
|
: { ...emptyAcceptanceCorpus(changedFiles.length > 0 ? changedFiles : ["inline.js"]), sourceText: normalizedText(options.sourceText), allText: normalizedText(options.sourceText) };
|
|
248
262
|
if (!corpus.adapter.proofCapable) return [];
|
|
249
263
|
const obligations = task?.acceptanceReceipt?.criteria?.map((criterion) => criterion.obligation)
|
|
250
|
-
?? inferAcceptanceObligations(
|
|
264
|
+
?? inferAcceptanceObligations(
|
|
265
|
+
acceptanceTaskText(task),
|
|
266
|
+
task?.changeMode,
|
|
267
|
+
acceptanceReadOnlyInferenceOptions(task, acceptanceTaskText(task))
|
|
268
|
+
);
|
|
251
269
|
return uniqueStrings([
|
|
252
270
|
...obligations.flatMap((obligation) => semanticConflictReasons(obligation, task, corpus)),
|
|
253
271
|
...(options.cwd ? baselineReturnRepresentationConflicts(acceptanceTaskText(task), options.cwd, corpus.sourceFiles) : [])
|
|
@@ -258,7 +276,6 @@ function criterionId(text, obligation, index) {
|
|
|
258
276
|
const prefix = compactId(obligation ?? "criterion").slice(0, 24);
|
|
259
277
|
return `ac-${String(index + 1).padStart(2, "0")}-${prefix}-${sha256(text).slice(0, 8)}`.slice(0, 80);
|
|
260
278
|
}
|
|
261
|
-
|
|
262
279
|
export function buildAcceptanceReceipt(input = {}) {
|
|
263
280
|
const changeMode = input.changeMode === "read-only" ? "read-only" : "source-change";
|
|
264
281
|
const baseText = [
|
|
@@ -266,12 +283,20 @@ export function buildAcceptanceReceipt(input = {}) {
|
|
|
266
283
|
input.expectedOutput,
|
|
267
284
|
...(Array.isArray(input.acceptanceCriteria) ? input.acceptanceCriteria : [])
|
|
268
285
|
].filter(Boolean).join("\n");
|
|
269
|
-
const obligations = inferAcceptanceObligations(
|
|
286
|
+
const obligations = inferAcceptanceObligations(
|
|
287
|
+
baseText,
|
|
288
|
+
changeMode,
|
|
289
|
+
acceptanceReadOnlyInferenceOptions(input, baseText)
|
|
290
|
+
);
|
|
270
291
|
const texts = uniqueStrings(Array.isArray(input.acceptanceCriteria) ? input.acceptanceCriteria : []);
|
|
271
292
|
const generatedObligations = new Map();
|
|
272
293
|
for (const obligation of obligations) {
|
|
273
294
|
const generated = generatedCriterionForObligation(obligation);
|
|
274
|
-
if (!texts.some((text) => inferAcceptanceObligations(
|
|
295
|
+
if (!texts.some((text) => inferAcceptanceObligations(
|
|
296
|
+
text,
|
|
297
|
+
changeMode,
|
|
298
|
+
acceptanceReadOnlyInferenceOptions(input, text)
|
|
299
|
+
).includes(obligation))) {
|
|
275
300
|
texts.push(generated);
|
|
276
301
|
generatedObligations.set(generated, obligation);
|
|
277
302
|
}
|
|
@@ -282,17 +307,23 @@ export function buildAcceptanceReceipt(input = {}) {
|
|
|
282
307
|
: generatedCriterionForObligation("verification-evidence"));
|
|
283
308
|
}
|
|
284
309
|
const acceptanceCriteria = texts.slice(0, MAX_CRITERIA);
|
|
310
|
+
const durableBehaviorProof = durableBehaviorProofRequired(baseText, changeMode);
|
|
285
311
|
const criteria = acceptanceCriteria.map((text, index) => {
|
|
286
|
-
const inferred = inferAcceptanceObligations(
|
|
312
|
+
const inferred = inferAcceptanceObligations(
|
|
313
|
+
text,
|
|
314
|
+
changeMode,
|
|
315
|
+
acceptanceReadOnlyInferenceOptions(input, text)
|
|
316
|
+
);
|
|
287
317
|
const obligation = generatedObligations.get(text)
|
|
288
318
|
?? inferred.find((item) => item !== "verification-evidence" && item !== "backward-compatibility")
|
|
289
|
-
??
|
|
319
|
+
?? (isVerificationOnlyCriterion(text) ? "verification-evidence" : undefined)
|
|
320
|
+
?? (inferred.includes("backward-compatibility") ? "backward-compatibility" : undefined)
|
|
290
321
|
?? "requested-behavior";
|
|
291
322
|
return {
|
|
292
323
|
id: criterionId(text, obligation, index),
|
|
293
324
|
hash: sha256(text),
|
|
294
325
|
obligation,
|
|
295
|
-
priority: CRITICAL_OBLIGATIONS.has(obligation) ? "critical" : "normal",
|
|
326
|
+
priority: CRITICAL_OBLIGATIONS.has(obligation) || (durableBehaviorProof && obligation !== "verification-evidence") ? "critical" : "normal",
|
|
296
327
|
status: "pending",
|
|
297
328
|
evidence: []
|
|
298
329
|
};
|
|
@@ -351,20 +382,30 @@ export function normalizeAcceptanceReceipt(value) {
|
|
|
351
382
|
|
|
352
383
|
function normalizeHelperUsage(value) {
|
|
353
384
|
if (!isRecord(value) || !["off", "recommend", "on"].includes(value.mode)) return undefined;
|
|
354
|
-
const
|
|
385
|
+
const rawHelpers = Array.isArray(value.helpers) ? value.helpers.slice(0, 3).filter(isRecord) : [];
|
|
386
|
+
const helpers = rawHelpers.map((item) => ({
|
|
355
387
|
role: HELPER_ROLES.has(item.role) ? item.role : "scout",
|
|
356
388
|
disposition: compactId(item.disposition ?? "unknown"),
|
|
357
389
|
requestRef: HASH.test(String(item.requestRef)) ? item.requestRef : sha256(item.requestRef),
|
|
358
390
|
outputDigest: item.outputDigest === null || HASH.test(String(item.outputDigest)) ? item.outputDigest : null,
|
|
359
391
|
calls: Number.isInteger(item.calls) ? Math.max(0, Math.min(100, item.calls)) : 0,
|
|
360
392
|
tokens: Number.isInteger(item.tokens) ? Math.max(0, Math.min(100_000_000, item.tokens)) : 0
|
|
361
|
-
}))
|
|
362
|
-
|
|
393
|
+
}));
|
|
394
|
+
const used = helpers.some((item) => !["solo", "recommend", "unavailable", "blocked"].includes(item.disposition));
|
|
395
|
+
const helperDecision = rawHelpers.find((item) => ["dispatch", "skip"].includes(item.decision))?.decision;
|
|
396
|
+
const decision = ["dispatch", "skip"].includes(value.decision) ? value.decision : helperDecision ?? (used ? "dispatch" : "skip");
|
|
397
|
+
const rawProjectedSavings = typeof value.projectedSavingsRatio === "number"
|
|
398
|
+
? value.projectedSavingsRatio
|
|
399
|
+
: rawHelpers.find((item) => typeof item.projectedSavingsRatio === "number")?.projectedSavingsRatio;
|
|
400
|
+
const projectedSavingsRatio = typeof rawProjectedSavings === "number" && Number.isFinite(rawProjectedSavings)
|
|
401
|
+
? Math.max(-10, Math.min(1, rawProjectedSavings))
|
|
402
|
+
: null;
|
|
403
|
+
return { mode: value.mode, used, decision, projectedSavingsRatio, reasonCodes: uniqueStrings(value.reasonCodes).map(compactId).slice(0, 16), helpers, recordedAt: validTimestamp(value.recordedAt) ? value.recordedAt : new Date().toISOString() };
|
|
363
404
|
}
|
|
364
405
|
|
|
365
406
|
export function applyAcceptanceHelperUsage(task, input = {}) {
|
|
366
407
|
const receipt = normalizeAcceptanceReceipt(task?.acceptanceReceipt); if (!receipt) return task;
|
|
367
|
-
const helperUsage = normalizeHelperUsage({ mode: input.mode ?? "off", reasonCodes: input.reasonCodes ?? [], helpers: input.helpers ?? [], recordedAt: input.recordedAt ?? new Date().toISOString() });
|
|
408
|
+
const helperUsage = normalizeHelperUsage({ mode: input.mode ?? "off", decision: input.decision, projectedSavingsRatio: input.projectedSavingsRatio, reasonCodes: input.reasonCodes ?? [], helpers: input.helpers ?? [], recordedAt: input.recordedAt ?? new Date().toISOString() });
|
|
368
409
|
return { ...task, acceptanceReceipt: { ...receipt, criteria: structuredClone(task.acceptanceReceipt.criteria), helperUsage } };
|
|
369
410
|
}
|
|
370
411
|
|
|
@@ -633,8 +674,8 @@ function hasStructuredCompositeIdentity(sourceText) {
|
|
|
633
674
|
return jsonTuple || nestedLookup || hasLengthPrefixedIdentityKey(text);
|
|
634
675
|
}
|
|
635
676
|
|
|
636
|
-
function focusedContractEvidence(obligation, task, corpus, verifierEvidence, criterion) {
|
|
637
|
-
if (!verifierEvidence || corpus.sourceFiles.length === 0 || corpus.testFiles
|
|
677
|
+
function focusedContractEvidence(obligation, task, corpus, verifierEvidence, criterion, cwd) {
|
|
678
|
+
if (!verifierEvidence || corpus.sourceFiles.length === 0 || !verifierCommandsCoverTests(task, corpus.testFiles, cwd)) return undefined;
|
|
638
679
|
const targets = namedCodeTargets(task, criterion, corpus.sourceText);
|
|
639
680
|
const targetCoverage = targets.length === 0 || targets.every((target) => (
|
|
640
681
|
new RegExp(`\\b${escapeRegex(target.toLowerCase())}\\b`).test(corpus.testText)
|
|
@@ -681,7 +722,7 @@ function hasCurrentPassingVerifier(task, currentWorkingTreeDigest) {
|
|
|
681
722
|
});
|
|
682
723
|
}
|
|
683
724
|
|
|
684
|
-
function evidenceForObligation(obligation, task, corpus, currentWorkingTreeDigest, criterion) {
|
|
725
|
+
function evidenceForObligation(obligation, task, corpus, currentWorkingTreeDigest, criterion, cwd) {
|
|
685
726
|
const passingVerifier = hasCurrentPassingVerifier(task, currentWorkingTreeDigest);
|
|
686
727
|
const latestVerifyEvidence = latestObservedVerificationEvidence(task?.verifyEvidence);
|
|
687
728
|
const verifyEvidence = meaningfulVerificationCommands(task?.verifyCommands ?? [])
|
|
@@ -699,7 +740,7 @@ function evidenceForObligation(obligation, task, corpus, currentWorkingTreeDiges
|
|
|
699
740
|
|
|
700
741
|
if (obligation === "read-only-evidence") {
|
|
701
742
|
const readOnlyEvidence = durableContextEvidenceEntries(task);
|
|
702
|
-
const readOnlyOk = task.changeMode === "read-only"
|
|
743
|
+
const readOnlyOk = (task.changeMode === "read-only" || task.mutationPolicy === "forbidden")
|
|
703
744
|
&& corpus.files.length === 0
|
|
704
745
|
&& hasDurableContextEvidence(task);
|
|
705
746
|
if (readOnlyOk) return {
|
|
@@ -716,9 +757,9 @@ function evidenceForObligation(obligation, task, corpus, currentWorkingTreeDiges
|
|
|
716
757
|
].filter(Boolean).join("\n");
|
|
717
758
|
const combinedText = `${taskText}\n${corpus.allText}`;
|
|
718
759
|
const sourceChangeReadOnlyOk = corpus.files.length > 0
|
|
719
|
-
&&
|
|
760
|
+
&& boundaryPatterns.length > 0
|
|
761
|
+
&& readOnlyBoundaryTiedToPattern(combinedText, boundaryPatterns)
|
|
720
762
|
&& changedFilesRespectBoundaries(corpus.files, boundaryPatterns)
|
|
721
|
-
&& (boundaryPatterns.length === 0 || pathBoundaryMentioned(combinedText, boundaryPatterns))
|
|
722
763
|
&& hasDurableContextEvidence(task);
|
|
723
764
|
if (sourceChangeReadOnlyOk) {
|
|
724
765
|
return {
|
|
@@ -732,23 +773,21 @@ function evidenceForObligation(obligation, task, corpus, currentWorkingTreeDiges
|
|
|
732
773
|
return undefined;
|
|
733
774
|
}
|
|
734
775
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
return verifierEvidence;
|
|
738
|
-
}
|
|
776
|
+
const generic = genericCriterionEvidence({ obligation, task, criterion, taskText: acceptanceTaskText(task), corpus, verifierEvidence, passingVerifier, cwd });
|
|
777
|
+
if (generic.handled) return generic.evidence;
|
|
739
778
|
|
|
740
779
|
if (!corpus.adapter.proofCapable) return undefined;
|
|
741
780
|
|
|
742
781
|
if (obligation === "tenant-boundary") {
|
|
743
|
-
return focusedContractEvidence(obligation, task, corpus, verifierEvidence, criterion);
|
|
782
|
+
return focusedContractEvidence(obligation, task, corpus, verifierEvidence, criterion, cwd);
|
|
744
783
|
}
|
|
745
784
|
|
|
746
785
|
if (obligation === "tenant-storage-isolation") {
|
|
747
|
-
return focusedContractEvidence(obligation, task, corpus, verifierEvidence, criterion);
|
|
786
|
+
return focusedContractEvidence(obligation, task, corpus, verifierEvidence, criterion, cwd);
|
|
748
787
|
}
|
|
749
788
|
|
|
750
789
|
if (obligation === "authorization-deny-case") {
|
|
751
|
-
return focusedContractEvidence(obligation, task, corpus, verifierEvidence, criterion);
|
|
790
|
+
return focusedContractEvidence(obligation, task, corpus, verifierEvidence, criterion, cwd);
|
|
752
791
|
}
|
|
753
792
|
|
|
754
793
|
if (obligation === "invalid-input-rejection") {
|
|
@@ -761,7 +800,7 @@ function evidenceForObligation(obligation, task, corpus, currentWorkingTreeDiges
|
|
|
761
800
|
namedTargets: namedCodeTargets(task, criterion, corpus.sourceText),
|
|
762
801
|
provenanceTargets: explicitlyCallableTargets(task, criterion, corpus.sourceText)
|
|
763
802
|
});
|
|
764
|
-
if (passingVerifier && corpus.sourceFiles.length > 0 && corpus.testFiles
|
|
803
|
+
if (passingVerifier && corpus.sourceFiles.length > 0 && verifierCommandsCoverTests(task, corpus.testFiles, cwd) && sourceOk && testOk) {
|
|
765
804
|
return {
|
|
766
805
|
...verifierEvidence,
|
|
767
806
|
kind: "verifier-backed-focused-test",
|
|
@@ -779,7 +818,7 @@ function evidenceForObligation(obligation, task, corpus, currentWorkingTreeDiges
|
|
|
779
818
|
const sourceOk = /math\.ceil|ceil|clamp|min|max|round|zero|return\s+0|\?\?|nullish|falsey|falsy|default|retrylimit|enabled|label/.test(corpus.sourceText)
|
|
780
819
|
|| expirySourceOk
|
|
781
820
|
|| (passingVerifier && corpus.sourceFiles.length > 0 && testOk);
|
|
782
|
-
if (passingVerifier && corpus.sourceFiles.length > 0 && corpus.testFiles
|
|
821
|
+
if (passingVerifier && corpus.sourceFiles.length > 0 && verifierCommandsCoverTests(task, corpus.testFiles, cwd) && sourceOk && testOk) {
|
|
783
822
|
return {
|
|
784
823
|
...verifierEvidence,
|
|
785
824
|
kind: "verifier-backed-focused-test",
|
|
@@ -790,12 +829,12 @@ function evidenceForObligation(obligation, task, corpus, currentWorkingTreeDiges
|
|
|
790
829
|
return undefined;
|
|
791
830
|
}
|
|
792
831
|
|
|
793
|
-
return
|
|
832
|
+
return genericFallbackEvidence({ behavioral: generic.behavioral, corpus, verifierEvidence, passingVerifier });
|
|
794
833
|
}
|
|
795
834
|
|
|
796
835
|
export function refreshAcceptanceReceipt(task, options = {}) {
|
|
797
836
|
const receipt = normalizeAcceptanceReceipt(task?.acceptanceReceipt);
|
|
798
|
-
if (!receipt) return { task, missing: [], criticalMissing: [], changed: false };
|
|
837
|
+
if (!receipt) return { task, missing: [], criticalMissing: [], adapterAbstained: [], changed: false };
|
|
799
838
|
const cwd = options.cwd;
|
|
800
839
|
const changedFiles = uniqueStrings(options.changedFiles ?? task.changedFiles ?? task.observedChangedFiles ?? []);
|
|
801
840
|
const currentWorkingTreeDigest = options.currentWorkingTreeDigest;
|
|
@@ -810,7 +849,7 @@ export function refreshAcceptanceReceipt(task, options = {}) {
|
|
|
810
849
|
criterion.evidence = [];
|
|
811
850
|
const evidence = task?.workingTreeDigestAlgorithm === WORKING_TREE_DIGEST_ALGORITHM
|
|
812
851
|
&& isCurrentWorkingTreeDigest(currentWorkingTreeDigest)
|
|
813
|
-
? evidenceForObligation(criterion.obligation, task, corpus, currentWorkingTreeDigest, criterion)
|
|
852
|
+
? evidenceForObligation(criterion.obligation, task, corpus, currentWorkingTreeDigest, criterion, cwd)
|
|
814
853
|
: undefined;
|
|
815
854
|
if (evidence) addEvidence(criterion, evidence, recordedAt);
|
|
816
855
|
const nextEvidence = (criterion.evidence ?? []).map(evidenceKey);
|
|
@@ -818,17 +857,16 @@ export function refreshAcceptanceReceipt(task, options = {}) {
|
|
|
818
857
|
continue;
|
|
819
858
|
}
|
|
820
859
|
if (criterion.status === "satisfied") continue;
|
|
821
|
-
const evidence = evidenceForObligation(criterion.obligation, task, corpus, currentWorkingTreeDigest, criterion);
|
|
860
|
+
const evidence = evidenceForObligation(criterion.obligation, task, corpus, currentWorkingTreeDigest, criterion, cwd);
|
|
822
861
|
if (evidence) changed = addEvidence(criterion, evidence, recordedAt) || changed;
|
|
823
862
|
}
|
|
824
863
|
const missing = receipt.criteria.filter((criterion) => criterion.status !== "satisfied");
|
|
825
|
-
const
|
|
864
|
+
const passingVerifier = hasCurrentPassingVerifier(task, currentWorkingTreeDigest);
|
|
865
|
+
const adapterAbstained = missing.filter((criterion) => criterionBehaviorProofDisposition({ obligation: criterion.obligation, task, criterion, taskText: acceptanceTaskText(task), corpus, passingVerifier }) === "unknown");
|
|
866
|
+
const criticalMissing = missing.filter((criterion) => (criterion.priority === "critical" || criterionRequiresBehavioralProof(task, criterion, acceptanceTaskText(task))
|
|
867
|
+
));
|
|
826
868
|
return {
|
|
827
|
-
task: { ...task, acceptanceReceipt: receipt },
|
|
828
|
-
receipt,
|
|
829
|
-
missing,
|
|
830
|
-
criticalMissing,
|
|
831
|
-
changed
|
|
869
|
+
task: { ...task, acceptanceReceipt: receipt }, receipt, missing, criticalMissing, adapterAbstained, changed
|
|
832
870
|
};
|
|
833
871
|
}
|
|
834
872
|
|
|
@@ -850,9 +888,12 @@ export function acceptanceCriticalRecoveryProjection(task, options = {}) {
|
|
|
850
888
|
const verifierCurrent = hasCurrentPassingVerifier(task, currentWorkingTreeDigest);
|
|
851
889
|
const projections = [];
|
|
852
890
|
for (const criterion of receipt.criteria.filter((item) => item.priority === "critical")) {
|
|
891
|
+
const disposition = criterionBehaviorProofDisposition({ obligation: criterion.obligation, task, criterion, taskText: acceptanceTaskText(task), corpus, passingVerifier: verifierCurrent });
|
|
853
892
|
const criterionText = (Array.isArray(task?.acceptanceCriteria) ? task.acceptanceCriteria : [])
|
|
854
893
|
.find((text) => typeof text === "string" && sha256(text) === criterion.hash && !GENERATED_ACCEPTANCE_TEXTS.has(text));
|
|
855
|
-
if (!criterionText
|
|
894
|
+
if (!criterionText) continue;
|
|
895
|
+
if (disposition === "unknown") { projections.push({ criterionId: criterion.id, criterionHash: criterion.hash, criterionText: criterionText.slice(0, 700), targets: [], missingDimensions: ["adapter-linkage"], proofHints: ["Add a focused executable test with a direct relative import to the changed source, or configure a deterministic language adapter that resolves this helper/barrel path; then rerun the exact verifier."] }); continue; }
|
|
896
|
+
if (evidenceForObligation(criterion.obligation, task, corpus, currentWorkingTreeDigest, criterion, cwd)) continue;
|
|
856
897
|
const targets = namedCodeTargets(task, criterion, corpus.sourceText);
|
|
857
898
|
const missingDimensions = [];
|
|
858
899
|
if (!verifierCurrent) missingDimensions.push("current-verifier");
|
|
@@ -902,7 +943,7 @@ export function acceptanceReceiptValidationErrors(value) {
|
|
|
902
943
|
}
|
|
903
944
|
if (value.helperUsage !== undefined) {
|
|
904
945
|
const usage = value.helperUsage;
|
|
905
|
-
if (!isRecord(usage) || hasUnsupportedField(usage, HELPER_USAGE_FIELDS) || !["off", "recommend", "on"].includes(usage.mode) || typeof usage.used !== "boolean" || !Array.isArray(usage.reasonCodes) || usage.reasonCodes.length > 16 || usage.reasonCodes.some((item) => typeof item !== "string" || compactId(item) !== item) || !Array.isArray(usage.helpers) || usage.helpers.length > 3 || !validTimestamp(usage.recordedAt)) errors.push("acceptanceReceipt helperUsage is invalid");
|
|
946
|
+
if (!isRecord(usage) || hasUnsupportedField(usage, HELPER_USAGE_FIELDS) || !["off", "recommend", "on"].includes(usage.mode) || typeof usage.used !== "boolean" || (usage.decision !== undefined && !["dispatch", "skip"].includes(usage.decision)) || (usage.projectedSavingsRatio !== undefined && usage.projectedSavingsRatio !== null && (typeof usage.projectedSavingsRatio !== "number" || !Number.isFinite(usage.projectedSavingsRatio) || usage.projectedSavingsRatio < -10 || usage.projectedSavingsRatio > 1)) || !Array.isArray(usage.reasonCodes) || usage.reasonCodes.length > 16 || usage.reasonCodes.some((item) => typeof item !== "string" || compactId(item) !== item) || !Array.isArray(usage.helpers) || usage.helpers.length > 3 || !validTimestamp(usage.recordedAt)) errors.push("acceptanceReceipt helperUsage is invalid");
|
|
906
947
|
else for (const helper of usage.helpers) if (!isRecord(helper) || hasUnsupportedField(helper, HELPER_ENTRY_FIELDS) || !HELPER_ROLES.has(helper.role) || typeof helper.disposition !== "string" || compactId(helper.disposition) !== helper.disposition || !HASH.test(String(helper.requestRef)) || (helper.outputDigest !== null && !HASH.test(String(helper.outputDigest))) || !Number.isInteger(helper.calls) || helper.calls < 0 || helper.calls > 100 || !Number.isInteger(helper.tokens) || helper.tokens < 0 || helper.tokens > 100_000_000) errors.push("acceptanceReceipt helper entry is invalid");
|
|
907
948
|
}
|
|
908
949
|
if (!Array.isArray(value.criteria) || value.criteria.length === 0 || value.criteria.length > MAX_CRITERIA) {
|
|
@@ -14,6 +14,7 @@ import { contextMetricPartition } from "./context-efficiency-metrics.js";
|
|
|
14
14
|
import { writeContextEfficiencyReport } from "./context-efficiency-report.js";
|
|
15
15
|
import { inspectContextTelemetryIntegrity } from "./context-telemetry-integrity.js";
|
|
16
16
|
import { directExplicitImportLinks, extractJavaScriptModuleImports } from "./context-import-links.js";
|
|
17
|
+
import { queryPathCandidates } from "./context-query-paths.js";
|
|
17
18
|
import { redactSensitiveProjectFileText } from "../security/sensitive-data.js";
|
|
18
19
|
const INDEX_SCHEMA_VERSION = 2;
|
|
19
20
|
const TELEMETRY_SCHEMA_VERSION = 2;
|
|
@@ -492,17 +493,6 @@ function tokenizeQuery(query) {
|
|
|
492
493
|
return [...new Set(terms.map((term) => term.toLowerCase()).filter((term) => !STOP_TERMS.has(foldSearchSignal(term))))].slice(0, 16);
|
|
493
494
|
}
|
|
494
495
|
|
|
495
|
-
function queryPathCandidates(query) {
|
|
496
|
-
const pattern = /(?:^|[\s"'`(])((?:\.{0,2}\/)?[\p{L}\p{N}_.@-]+(?:\/[\p{L}\p{N}_.@-]+)+|[\p{L}\p{N}_.@-]+\.[\p{L}\p{N}]{1,8}|\.[\p{L}\p{N}_@-]{1,32})(?=$|[\s"'`),:])/gu;
|
|
497
|
-
const explicitRoots = /^(?:\.github|app|apps|backend|bin|config|docs|examples|frontend|lib|logs|packages|pages|public|scripts|services|spec|src|test|tests|vendor|__tests__)\//i;
|
|
498
|
-
return [...String(query ?? "").matchAll(pattern)].flatMap((match) => {
|
|
499
|
-
const raw = match[1], candidate = normalizeRelative(raw);
|
|
500
|
-
const explicit = candidate && (raw.startsWith("./") || raw.startsWith("../") || explicitRoots.test(candidate)
|
|
501
|
-
|| candidate.split("/").length >= 3 || /(?:^|\/)(?:\.[^/]+|[^/]+\.[\p{L}\p{N}]{1,8})$/u.test(candidate));
|
|
502
|
-
return explicit ? [candidate] : [];
|
|
503
|
-
});
|
|
504
|
-
}
|
|
505
|
-
|
|
506
496
|
function ftsQuery(terms) {
|
|
507
497
|
return terms.map((term) => `"${term.replaceAll('"', '""')}"`).join(" OR ");
|
|
508
498
|
}
|
|
@@ -3,6 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
5
|
import { resolveProjectProfileDocument } from "../capabilities/project-profile.js";
|
|
6
|
+
import { WORKSPACE_PRIVATE_STATE_PATTERNS } from "./workspace-evidence-roots.js";
|
|
6
7
|
|
|
7
8
|
const DEFAULT_CONTEXT_INDEX_PATH = ".pi/context-index.json";
|
|
8
9
|
const CONTEXT_ENGINE_STATE_PATTERN = ".pi/piagent-state/**";
|
|
@@ -67,17 +68,20 @@ export function effectiveProtectedPaths(policy = {}, profile = {}) {
|
|
|
67
68
|
const contextIndexPath = contextIndexStatePath(profile);
|
|
68
69
|
return {
|
|
69
70
|
readProtectedPaths: uniqueStrings([
|
|
71
|
+
...WORKSPACE_PRIVATE_STATE_PATTERNS,
|
|
70
72
|
...baseReadProtectedPaths,
|
|
71
73
|
...profileProtectedPaths,
|
|
72
74
|
contextIndexPath
|
|
73
75
|
]),
|
|
74
76
|
writeProtectedPaths: uniqueStrings([
|
|
77
|
+
...WORKSPACE_PRIVATE_STATE_PATTERNS,
|
|
75
78
|
...policyProtectedPaths,
|
|
76
79
|
...profileProtectedPaths,
|
|
77
80
|
...readOnlyPaths,
|
|
78
81
|
contextIndexPath
|
|
79
82
|
]),
|
|
80
83
|
shellProtectedPaths: uniqueStrings([
|
|
84
|
+
...WORKSPACE_PRIVATE_STATE_PATTERNS,
|
|
81
85
|
...baseReadProtectedPaths,
|
|
82
86
|
...profileShellProtectedPaths,
|
|
83
87
|
...readOnlyPaths,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
function normalizeRelative(value) {
|
|
4
|
+
return String(value ?? "").replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+/g, "/");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function safeProjectRelativeQueryPath(raw) {
|
|
8
|
+
const candidate = normalizeRelative(raw);
|
|
9
|
+
if (!candidate || candidate.length > 512 || path.posix.isAbsolute(candidate)) return undefined;
|
|
10
|
+
if (candidate.split("/").some((segment) => !segment || segment === "." || segment === "..")) return undefined;
|
|
11
|
+
return candidate;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function safeGlobSegment(segment) {
|
|
15
|
+
let bracketDepth = 0;
|
|
16
|
+
let braceDepth = 0;
|
|
17
|
+
for (const character of segment) {
|
|
18
|
+
if (character === "[") {
|
|
19
|
+
if (++bracketDepth > 1) return false;
|
|
20
|
+
} else if (character === "]") {
|
|
21
|
+
if (--bracketDepth < 0) return false;
|
|
22
|
+
} else if (character === "{") {
|
|
23
|
+
if (++braceDepth > 1) return false;
|
|
24
|
+
} else if (character === "}") {
|
|
25
|
+
if (--braceDepth < 0) return false;
|
|
26
|
+
} else if ((character === "," && braceDepth === 0) || (character === "!" && bracketDepth === 0)) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return bracketDepth === 0 && braceDepth === 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function safeProjectRelativeGlob(raw) {
|
|
34
|
+
const candidate = safeProjectRelativeQueryPath(String(raw ?? "").replace(/[.,]+$/u, ""));
|
|
35
|
+
if (!candidate || !candidate.includes("/") || !/[*?\[\]{}]/u.test(candidate)) return undefined;
|
|
36
|
+
if (!/^[\p{L}\p{N}_.@*?{}\[\]!,-]+(?:\/[\p{L}\p{N}_.@*?{}\[\]!,-]+)+$/u.test(candidate)) return undefined;
|
|
37
|
+
return candidate.split("/").every(safeGlobSegment) ? candidate : undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function queryPathCandidates(query) {
|
|
41
|
+
const pattern = /(?:^|[\s"'`(])((?:\.{0,2}\/)?[\p{L}\p{N}_.@-]+(?:\/[\p{L}\p{N}_.@-]+)+|[\p{L}\p{N}_.@-]+\.[\p{L}\p{N}]{1,8}|\.[\p{L}\p{N}_@-]{1,32})(?=$|[\s"'`),:])/gu;
|
|
42
|
+
const globPattern = /(?:^|[\s"'(\x60=,:])((?:\.\/)?[\p{L}\p{N}_.@*?{}\[\]!,-]+(?:\/[\p{L}\p{N}_.@*?{}\[\]!,-]+)+)(?=$|[\s"'\x60),:;])/gu;
|
|
43
|
+
const explicitRoots = /^(?:\.github|app|apps|backend|bin|config|docs|examples|frontend|lib|logs|packages|pages|public|scripts|services|spec|src|test|tests|vendor|__tests__)\//i;
|
|
44
|
+
const text = String(query ?? "");
|
|
45
|
+
const candidates = [...text.matchAll(pattern)].flatMap((match) => {
|
|
46
|
+
const raw = match[1], candidate = safeProjectRelativeQueryPath(raw);
|
|
47
|
+
const explicit = candidate && (raw.startsWith("./") || explicitRoots.test(candidate)
|
|
48
|
+
|| candidate.split("/").length >= 3 || /(?:^|\/)(?:\.[^/]+|[^/]+\.[\p{L}\p{N}]{1,8})$/u.test(candidate));
|
|
49
|
+
return explicit ? [{ candidate, index: match.index ?? 0 }] : [];
|
|
50
|
+
});
|
|
51
|
+
for (const match of text.matchAll(globPattern)) {
|
|
52
|
+
const candidate = safeProjectRelativeGlob(match[1]);
|
|
53
|
+
if (candidate) candidates.push({ candidate, index: match.index ?? 0 });
|
|
54
|
+
}
|
|
55
|
+
candidates.sort((left, right) => left.index - right.index);
|
|
56
|
+
return [...new Set(candidates.map((entry) => entry.candidate))];
|
|
57
|
+
}
|
|
@@ -36,7 +36,20 @@ function executablePlannedTest(candidate, plannedPaths, plannedSelectionComplete
|
|
|
36
36
|
&& plannedSelectionComplete
|
|
37
37
|
&& acceptanceLanguageAdapterForPath(candidate.path).disposition === "supported"
|
|
38
38
|
&& !/\.d\.[cm]?ts$/i.test(candidate.path)
|
|
39
|
-
&& /(?:^|[._-])(?:test|tests|spec)(?:[._-]|$)/i.test(basename);
|
|
39
|
+
&& /(?:^|[._-])(?:assertions?|test|tests|spec)(?:[._-]|$)/i.test(basename);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function explicitFocusedTestNavigation(text) {
|
|
43
|
+
const clauses = String(text ?? "").split(/(?:[;\n]+|(?<=[.!?])\s+)/).map((clause) => clause.trim()).filter(Boolean);
|
|
44
|
+
return clauses.some((clause) => {
|
|
45
|
+
const positive = /\b(?:add|author|create|include|prove|update|write)\b.{0,80}\b(?:assertions?|coverage|specs?|tests?)\b/i.test(clause)
|
|
46
|
+
|| /\b(?:behavioral|deterministic|directly[- ]linked|durable|executable|focused|integration|live|nearest|scoped)\s+(?:assertions?|coverage|specs?|tests?)\b/i.test(clause)
|
|
47
|
+
|| /\bensure\b.{0,100}\b(?:cover(?:ed|age)?|test(?:ed|s)?|specs?|assert(?:ed|ions?))\b/i.test(clause)
|
|
48
|
+
|| /\bensure\b.{0,80}\b(?:assertions?|coverage|specs?|tests?)\b.{0,60}\bcover(?:ed|s|age)?\b/i.test(clause);
|
|
49
|
+
const negative = /\b(?:do\s+not|don't|dont|never|without)\b.{0,100}\b(?:assertions?|coverage|specs?|tests?)\b/i.test(clause)
|
|
50
|
+
|| /\b(?:assertions?|coverage|specs?|tests?)\b.{0,30}\b(?:not|never|unchanged|untouched)\b/i.test(clause);
|
|
51
|
+
return positive && !negative;
|
|
52
|
+
});
|
|
40
53
|
}
|
|
41
54
|
|
|
42
55
|
const DIRECT_EXPLICIT_LINK_KINDS = new Set(["explicit-imports-candidate", "candidate-imports-explicit"]);
|
|
@@ -281,9 +294,9 @@ export function composeCriterionContextEntries(input = {}, options = {}) {
|
|
|
281
294
|
// establish singleton cardinality, and only a test-shaped JS/TS filename is
|
|
282
295
|
// eligible. This remains navigation context; acceptance still depends on a
|
|
283
296
|
// changed live assertion and final verification.
|
|
284
|
-
if (!selected.some((entry) => testPath(entry.path))) {
|
|
297
|
+
if (!selected.some((entry) => testPath(entry.path)) && explicitFocusedTestNavigation(criteriaText)) {
|
|
285
298
|
const scopedTests = values.filter((candidate) => executablePlannedTest(candidate, plannedPaths, input.plannedSelectionComplete === true));
|
|
286
|
-
if (scopedTests.length === 1) add(scopedTests[0], "
|
|
299
|
+
if (scopedTests.length === 1) add(scopedTests[0], "Operator-requested sole scoped test target (navigation context; not acceptance proof)");
|
|
287
300
|
}
|
|
288
301
|
|
|
289
302
|
relatedTests
|
|
@@ -114,7 +114,10 @@ function targetHintsFor(obligation, scope, mode) {
|
|
|
114
114
|
const stem = basename.replace(/\.[a-z0-9]+$/i, "");
|
|
115
115
|
return text.includes(normalized) || (basename.length >= 3 && text.includes(basename)) || (stem.length >= 4 && text.includes(stem));
|
|
116
116
|
});
|
|
117
|
-
|
|
117
|
+
const requestedTestScopes = /\btests?\b/i.test(text)
|
|
118
|
+
? scope.filter((candidate) => /(?:^|\/)(?:test|tests|spec|__tests__)(?:\/|$)|[._-](?:test|spec)\.[cm]?[jt]sx?$/i.test(candidate))
|
|
119
|
+
: [];
|
|
120
|
+
return (matched.length > 0 ? [...new Set([...matched, ...requestedTestScopes])] : scope).slice(0, 4);
|
|
118
121
|
}
|
|
119
122
|
|
|
120
123
|
function criterionLabel(obligation) {
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { BasePolicy, ResolvedOrchestrationPolicy } from "./guard-types.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Fail-closed policy used only when the packaged JSON policy cannot be read.
|
|
5
|
+
* Tests lock this copy to the file-backed policy so recovery never becomes the
|
|
6
|
+
* less restrictive path.
|
|
7
|
+
*/
|
|
8
|
+
export function fallbackBasePolicy(
|
|
9
|
+
contextIndexFile: string,
|
|
10
|
+
orchestrationPolicy: ResolvedOrchestrationPolicy
|
|
11
|
+
): BasePolicy {
|
|
12
|
+
return {
|
|
13
|
+
protectedPaths: [
|
|
14
|
+
".git/**", "**/.git/**", "**/.hg/**", "**/.svn/**", "**/.pi/**",
|
|
15
|
+
"**/.aws/**", "**/.azure/**", "**/.docker/**", "**/.gnupg/**", "**/.kube/**", "**/.ssh/**",
|
|
16
|
+
"**/.git-credentials", "**/.netrc", "**/.npmrc", "**/.pypirc",
|
|
17
|
+
"**/auth.json", "**/credential.json", "**/credentials.json", "**/secret.json", "**/secrets.json", "**/token.json", "**/tokens.json",
|
|
18
|
+
"**/.env", "**/.env.*", "**/node_modules/**", "**/dist/**", ".pi/piagent-state/**", ".pi/settings.json",
|
|
19
|
+
".pi/piagent-profile.json", ".pi/piagent-profile.lock.json", contextIndexFile
|
|
20
|
+
],
|
|
21
|
+
shellProtectedPaths: [
|
|
22
|
+
".git/**", "**/.git/**", "**/.hg/**", "**/.svn/**", "**/.pi/**",
|
|
23
|
+
"**/.aws/**", "**/.azure/**", "**/.docker/**", "**/.gnupg/**", "**/.kube/**", "**/.ssh/**",
|
|
24
|
+
"**/.git-credentials", "**/.netrc", "**/.npmrc", "**/.pypirc",
|
|
25
|
+
"**/auth.json", "**/credential.json", "**/credentials.json", "**/secret.json", "**/secrets.json", "**/token.json", "**/tokens.json",
|
|
26
|
+
"**/.env", "**/.env.*", ".pi/piagent-state/**", ".pi/settings.json", ".pi/piagent-profile.json",
|
|
27
|
+
".pi/piagent-profile.lock.json", contextIndexFile
|
|
28
|
+
],
|
|
29
|
+
blockedCommandPatterns: ["rm -rf /", "rm -rf ~", "rm -rf $HOME", "git reset --hard", "git clean -fd", "sudo ", "chmod -R 777"],
|
|
30
|
+
requireConfirmationPatterns: ["deploy", "release", "publish", "migration", "gh pr merge", "git push"],
|
|
31
|
+
defaultRequiredContext: ["AGENTS.md", "README.md"],
|
|
32
|
+
permissionProfiles: {
|
|
33
|
+
defaultMode: "workspace-write",
|
|
34
|
+
allowedModes: ["read-only", "workspace-write", "trusted-full-access"]
|
|
35
|
+
},
|
|
36
|
+
execPolicy: {
|
|
37
|
+
defaultMode: "enforce",
|
|
38
|
+
bannedPrefixSuggestions: [
|
|
39
|
+
["python"], ["python3"], ["node"], ["node", "-e"], ["bash"], ["bash", "-lc"],
|
|
40
|
+
["sh"], ["sh", "-c"], ["zsh"], ["zsh", "-lc"], ["git"], ["sudo"], ["env"]
|
|
41
|
+
],
|
|
42
|
+
rules: [{
|
|
43
|
+
id: "prompt-git-add-broad",
|
|
44
|
+
action: "prompt",
|
|
45
|
+
match: "regex",
|
|
46
|
+
value: "(?:^|\\s)git\\s+(?:-C\\s+\\S+\\s+)?add\\s+(?:(?:--all|-A)(?:\\s+(?:\\.|:/))?|--\\s+(?:\\.|:/)|(?:\\.|:/))(?:\\s|$)",
|
|
47
|
+
reason: "Broad git staging can include unrelated or sensitive changes; inspect git status/diff and confirm the exact scope first."
|
|
48
|
+
}]
|
|
49
|
+
},
|
|
50
|
+
contextBudget: {
|
|
51
|
+
defaultMode: "enforce",
|
|
52
|
+
contextDeltaShadow: "sample",
|
|
53
|
+
maxContextFileChars: 50000,
|
|
54
|
+
maxMemoryFileChars: 20000,
|
|
55
|
+
maxManifestFiles: 80,
|
|
56
|
+
warnFragmentChars: 4000
|
|
57
|
+
},
|
|
58
|
+
toolRegistry: {
|
|
59
|
+
defaultMode: "advisory",
|
|
60
|
+
alwaysAllowedTools: [
|
|
61
|
+
"piagent_tools", "piagent_context_engine", "piagent_context", "piagent_permission_status",
|
|
62
|
+
"piagent_exec_policy_check", "piagent_context_budget", "piagent_tool_policy_check",
|
|
63
|
+
"piagent_task_gate_check", "piagent_usage_snapshot", "piagent_orchestration_policy",
|
|
64
|
+
"piagent_memory_status", "piagent_memory_note", "piagent_memory_search",
|
|
65
|
+
"piagent_memory_citation_record", "piagent_context_index_status", "piagent_context_index_record",
|
|
66
|
+
"piagent_context_index_search", "piagent_profile_options", "piagent_profile_apply",
|
|
67
|
+
"piagent_profile_tech_options", "piagent_profile_tech_apply", "piagent_profile_tech_context_record",
|
|
68
|
+
"piagent_project_onboarding_record", "piagent_task_start", "piagent_task_progress",
|
|
69
|
+
"piagent_source_checkout", "piagent_context_record", "piagent_verify_record", "piagent_trace_record"
|
|
70
|
+
],
|
|
71
|
+
toolCapabilities: {
|
|
72
|
+
bash: ["shell"], shell: ["shell"], exec: ["shell"],
|
|
73
|
+
read: ["filesystem-readonly"], grep: ["filesystem-readonly"], find: ["filesystem-readonly"], ls: ["filesystem-readonly"],
|
|
74
|
+
write: ["filesystem-write"], edit: ["filesystem-write"], apply_patch: ["filesystem-write"],
|
|
75
|
+
browser: ["browser"], github: ["github"]
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
externalActionPolicy: {
|
|
79
|
+
defaultMode: "enforce",
|
|
80
|
+
providerKeywords: [
|
|
81
|
+
"github", "gitlab", "bitbucket", "vercel", "netlify", "cloudflare", "aws", "gcp", "azure",
|
|
82
|
+
"slack", "teams", "jira", "linear", "notion", "figma", "stripe", "supabase", "firebase"
|
|
83
|
+
],
|
|
84
|
+
writeVerbs: [
|
|
85
|
+
"add", "approve", "archive", "assign", "close", "comment", "create", "delete", "deploy",
|
|
86
|
+
"dispatch", "merge", "open", "post", "publish", "push", "release", "remove", "reopen", "run",
|
|
87
|
+
"send", "submit", "trigger", "update", "upload", "write"
|
|
88
|
+
],
|
|
89
|
+
safeVerbs: ["fetch", "find", "get", "inspect", "list", "read", "search", "show", "view"]
|
|
90
|
+
},
|
|
91
|
+
finalGate: {
|
|
92
|
+
defaultMode: "enforce",
|
|
93
|
+
requireTaskContract: true,
|
|
94
|
+
requireContextManifest: true,
|
|
95
|
+
requireVerifyEvidence: true,
|
|
96
|
+
requireTrace: true,
|
|
97
|
+
requirePassingVerify: true
|
|
98
|
+
},
|
|
99
|
+
orchestrationPolicy
|
|
100
|
+
};
|
|
101
|
+
}
|