@piagent/platform 1.5.5 → 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 +166 -0
- package/README.md +25 -21
- package/README.vi.md +34 -5
- package/adapters/web-frontend/profile.json +2 -4
- package/architecture/layers.json +18 -3
- package/benchmarks/deep-logic-v1/SPECIALIST_PROTOCOL.vi.md +122 -0
- package/benchmarks/deep-logic-v1/grade.mjs +272 -10
- package/benchmarks/deep-logic-v1/project/apps/admin/src/billing-summary.js +3 -0
- package/benchmarks/deep-logic-v1/project/benchmark-contract/public-smoke.test.js +166 -0
- package/benchmarks/deep-logic-v1/project/package.json +1 -1
- package/benchmarks/deep-logic-v1/project/packages/billing/src/close-period.js +3 -0
- package/benchmarks/deep-logic-v1/project/packages/billing/src/plan-timeline.js +3 -0
- package/benchmarks/deep-logic-v1/project/test/smoke.test.js +7 -1
- package/benchmarks/deep-logic-v1/prompts/budgeted-context-graph.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/fair-dependency-scheduler.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/layered-policy-resolution.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/resumable-stream-assembly.md +3 -1
- package/benchmarks/deep-logic-v1/prompts/revision-event-reconciliation.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/temporal-usage-billing-close.md +123 -0
- package/benchmarks/deep-logic-v1/prompts/transactional-config-merge.md +2 -0
- package/benchmarks/deep-logic-v1/suite.json +51 -3
- package/benchmarks/deep-logic-v1/variant.mjs +22 -1
- package/benchmarks/production-v1/spend-control.v1.json +92 -0
- package/benchmarks/production-v1/suite.json +46 -3
- package/catalog/capabilities.json +18 -18
- package/docs/authority-manifest.md +1 -1
- package/docs/auto-delegation-policy.md +38 -73
- package/docs/command-reference-vietnamese.md +27 -35
- package/docs/context-engine.md +12 -4
- package/docs/context-window-policy.md +37 -1
- package/docs/distribution-standard.md +7 -7
- package/docs/en/architecture.md +27 -13
- package/docs/languages.json +4 -4
- package/docs/model-options.md +8 -5
- package/docs/operator-manual-vietnamese.md +27 -45
- package/docs/project-adapters.md +2 -2
- package/docs/publishing-for-teams.md +10 -0
- package/docs/quality-benchmark.md +427 -62
- package/docs/release-install-policy.md +10 -9
- package/docs/runtime-harness-standard.md +3 -3
- package/docs/runtime-policy-design.md +4 -4
- package/docs/runtime-quality-baseline.md +13 -6
- package/docs/security-threat-model.md +2 -2
- 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 +3 -3
- package/docs/team-onboarding.md +2 -2
- package/docs/usage-observability.md +7 -2
- package/docs/vi/architecture.md +27 -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/session-live-state-v1.invalid.json +18 -0
- package/evals/fixtures/piagent-webui/session-live-state-v1.valid.json +27 -0
- 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 +3 -3
- 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 +7 -6
- package/evals/fs5-pilot-protocol.v5.json +9 -7
- 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 +3 -1
- package/packages/piagent-core/README.md +13 -4
- package/packages/piagent-core/benchmark/benchmark-assurance.js +4 -1
- package/packages/piagent-core/benchmark/benchmark-bootstrap.js +91 -9
- package/packages/piagent-core/benchmark/benchmark-claim-restrictions.js +38 -7
- package/packages/piagent-core/benchmark/benchmark-cli.js +7 -7
- package/packages/piagent-core/benchmark/benchmark-codex-relative-efficiency.js +698 -0
- package/packages/piagent-core/benchmark/benchmark-codex.js +3 -1
- package/packages/piagent-core/benchmark/benchmark-comparison.js +271 -2
- package/packages/piagent-core/benchmark/benchmark-core.js +736 -664
- package/packages/piagent-core/benchmark/benchmark-execution-guard.js +7 -5
- package/packages/piagent-core/benchmark/benchmark-forensics.js +441 -4
- package/packages/piagent-core/benchmark/benchmark-host-readiness.js +639 -0
- package/packages/piagent-core/benchmark/benchmark-matrix.js +22 -4
- package/packages/piagent-core/benchmark/benchmark-normalized-cost.js +317 -0
- package/packages/piagent-core/benchmark/benchmark-preflight.js +5 -1
- package/packages/piagent-core/benchmark/benchmark-process.js +6 -5
- package/packages/piagent-core/benchmark/benchmark-provider-free-evidence.js +345 -0
- package/packages/piagent-core/benchmark/benchmark-provider-wire.js +126 -0
- package/packages/piagent-core/benchmark/benchmark-record-validation.js +252 -0
- package/packages/piagent-core/benchmark/benchmark-report.js +130 -12
- package/packages/piagent-core/benchmark/benchmark-resume-recovery.js +14 -0
- package/packages/piagent-core/benchmark/benchmark-runner-policy.js +26 -0
- package/packages/piagent-core/benchmark/benchmark-runtime-causal.js +244 -0
- package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +979 -0
- package/packages/piagent-core/benchmark/benchmark-statistics.js +64 -0
- package/packages/piagent-core/benchmark/benchmark-stop-policy.js +8 -5
- package/packages/piagent-core/benchmark/benchmark-suite-identity.js +31 -0
- package/packages/piagent-core/benchmark/benchmark-suite-runtime.js +13 -9
- package/packages/piagent-core/benchmark/benchmark-suite.js +149 -2
- package/packages/piagent-core/benchmark/benchmark-summary-support.js +380 -0
- package/packages/piagent-core/benchmark/benchmark-timing-diagnostics.js +971 -0
- package/packages/piagent-core/benchmark/benchmark-usage.js +322 -0
- package/packages/piagent-core/benchmark/benchmark-workflow.js +166 -0
- package/packages/piagent-core/capabilities/runtime-integrity.js +3 -0
- package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +1080 -0
- package/packages/piagent-core/extensions/acceptance-behavior-proof.js +806 -0
- package/packages/piagent-core/extensions/acceptance-contract-semantics.js +83 -69
- package/packages/piagent-core/extensions/acceptance-error-classes.js +160 -0
- package/packages/piagent-core/extensions/acceptance-executable-evidence.js +12 -9
- package/packages/piagent-core/extensions/acceptance-receipt.js +123 -59
- package/packages/piagent-core/extensions/acceptance-state-machine-evidence.js +518 -0
- package/packages/piagent-core/extensions/acceptance-test-binding-evidence.js +60 -0
- package/packages/piagent-core/extensions/context-efficiency-metrics.js +302 -0
- package/packages/piagent-core/extensions/context-efficiency-report.js +167 -0
- package/packages/piagent-core/extensions/context-engine.js +149 -156
- package/packages/piagent-core/extensions/context-evidence.js +48 -0
- package/packages/piagent-core/extensions/context-import-links.js +296 -0
- 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/context-telemetry-integrity.js +65 -0
- package/packages/piagent-core/extensions/criterion-context-pack.js +435 -7
- package/packages/piagent-core/extensions/criterion-graph.js +82 -9
- package/packages/piagent-core/extensions/fallback-base-policy.ts +101 -0
- package/packages/piagent-core/extensions/guard-types.ts +6 -0
- package/packages/piagent-core/extensions/javascript-regex-evidence.js +32 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +420 -260
- package/packages/piagent-core/extensions/task-contract-view.js +17 -3
- package/packages/piagent-core/extensions/task-lifecycle.js +39 -2
- package/packages/piagent-core/extensions/task-state.js +109 -142
- package/packages/piagent-core/extensions/verification-intelligence.js +52 -13
- package/packages/piagent-core/extensions/workspace-evidence-roots.js +193 -0
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/policies/base-policy.json +44 -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/context/context-delivery.ts +103 -0
- package/packages/piagent-core/runtime/context/context-delta-shadow.ts +53 -0
- package/packages/piagent-core/runtime/context/context-efficiency-format.ts +47 -0
- package/packages/piagent-core/runtime/context/context-evidence-qualification.ts +32 -0
- package/packages/piagent-core/runtime/context/prefix-telemetry.ts +47 -0
- package/packages/piagent-core/runtime/context/repository-memory-hints.ts +27 -0
- package/packages/piagent-core/runtime/hooks/agent-prompt-telemetry.ts +56 -0
- package/packages/piagent-core/runtime/hooks/agent-start-hook.ts +162 -102
- package/packages/piagent-core/runtime/hooks/completion-hook.ts +12 -12
- package/packages/piagent-core/runtime/hooks/input-hook.ts +17 -1
- package/packages/piagent-core/runtime/hooks/session-hooks.ts +22 -2
- package/packages/piagent-core/runtime/hooks/session-start-hook.ts +107 -3
- package/packages/piagent-core/runtime/hooks/tool-result-content-guards.ts +95 -0
- package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +134 -166
- package/packages/piagent-core/runtime/hooks/tool-result-value-helpers.ts +66 -0
- package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +4 -2
- package/packages/piagent-core/runtime/inspection/activity-recovery.ts +104 -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-compaction-history.ts +1 -1
- package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +1 -1
- package/packages/piagent-core/runtime/inspection/task-subagent-tree.ts +27 -4
- package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +70 -0
- package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +73 -9
- package/packages/piagent-core/runtime/model/authenticated-catalog.ts +17 -9
- package/packages/piagent-core/runtime/model/openai-codex-reasoning.ts +83 -0
- package/packages/piagent-core/runtime/model/provider-wire-fingerprint.ts +238 -0
- 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/activity-inspector.ts +7 -2
- package/packages/piagent-core/runtime/product/efficiency-metrics.ts +7 -2
- package/packages/piagent-core/runtime/quality/edit-freshness-guard.ts +102 -0
- package/packages/piagent-core/runtime/quality/exact-output-contract.ts +22 -11
- package/packages/piagent-core/runtime/quality/performance-assurance-signals.ts +23 -0
- package/packages/piagent-core/runtime/quality/performance-assurance.ts +9 -5
- package/packages/piagent-core/runtime/recovery/continuation-budget.ts +1 -1
- package/packages/piagent-core/runtime/recovery/edit-recovery-context.ts +113 -0
- package/packages/piagent-core/runtime/recovery/edit-recovery-delivery.ts +42 -0
- 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 +60 -13
- package/packages/piagent-core/runtime/recovery/semantic-repair-runtime.ts +55 -2
- package/packages/piagent-core/runtime/recovery/task-evidence-coverage-refresh.ts +217 -0
- package/packages/piagent-core/runtime/registration/context-commands.ts +82 -7
- 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 +50 -15
- package/packages/piagent-core/runtime/registration/session-commands.ts +1 -1
- package/packages/piagent-core/runtime/registration/task-completion-tools.ts +61 -25
- package/packages/piagent-core/runtime/registration/task-evidence-tools.ts +21 -12
- package/packages/piagent-core/runtime/registration/task-start-guidance.ts +169 -0
- package/packages/piagent-core/runtime/registration/task-start-manifest.ts +54 -0
- package/packages/piagent-core/runtime/registration/task-start-retry-helpers.ts +37 -0
- package/packages/piagent-core/runtime/registration/task-start-tool.ts +108 -103
- 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 +161 -5
- 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/apply-patch-tool.ts +500 -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/trajectory/trajectory-runtime.ts +2 -1
- 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 +207 -38
- package/packages/piagent-core/security/sensitive-data.js +13 -218
- package/packages/piagent-core/security/sensitive-project-file.js +42 -0
- package/packages/piagent-core/security/sensitive-source-expression.js +330 -0
- package/packages/piagent-core/security/sensitive-source-formats.js +394 -0
- package/packages/piagent-core/security/sensitive-text.js +234 -0
- 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 +7 -5
- package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-rD9WJKxs.js → MarkdownMessage-B4XehTCU.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/index-CgOEyRPG.js +176 -0
- 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-events.ts +109 -5
- package/packages/piagent-webui/gateway/gateway-service.ts +3 -0
- package/packages/piagent-webui/gateway/gateway-session-stream.ts +122 -20
- package/packages/piagent-webui/gateway/session-command-controller.ts +18 -3
- package/packages/piagent-webui/gateway/session-effective-options.ts +47 -0
- package/packages/piagent-webui/gateway/session-live-state.ts +21 -0
- 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-start.ts +5 -1
- package/packages/piagent-webui/gateway/session-operation-watchdog.ts +165 -0
- package/packages/piagent-webui/gateway/session-runtime-factory.ts +45 -0
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +141 -125
- 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/loopback-server.ts +8 -0
- package/packages/piagent-webui/server/transcript-projection.ts +113 -9
- package/packages/piagent-webui/shared/text-visibility.ts +10 -0
- package/schemas/codex-relative-efficiency-v1.schema.json +155 -0
- package/schemas/piagent-webui/catalog-v1.json +9 -0
- package/schemas/piagent-webui/gateway-protocol-v1.schema.json +53 -3
- package/schemas/piagent-webui/runtime-event-v2.schema.json +15 -5
- package/schemas/piagent-webui/session-live-state-v1.schema.json +76 -0
- 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 +27 -1
- package/scripts/benchmark-runner-core.mjs +367 -363
- package/scripts/benchmark-runner-finalization.mjs +313 -0
- package/scripts/benchmark-runner-host-readiness.mjs +94 -0
- package/scripts/benchmark-runner-support.mjs +200 -0
- package/scripts/benchmark-session.mjs +95 -41
- package/scripts/configure-model-scope.sh +4 -3
- package/scripts/configure-subagents.sh +42 -34
- package/scripts/helper-evaluation.mjs +2 -2
- package/scripts/install-global.sh +2 -2
- package/scripts/pi-usage-history.mjs +37 -10
- package/scripts/setup.sh +2 -2
- package/scripts/team-doctor.sh +1 -1
- package/scripts/verify-local.sh +10 -1
- package/templates/global/settings.json +2 -1
- package/packages/piagent-webui/dist/client/assets/index-BZRFw1u6.js +0 -174
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { acceptanceReceiptProvenanceSummary, acceptanceReceiptSummary } from "./acceptance-receipt.js";
|
|
2
|
+
import { isDurableContextEvidenceEntry } from "./context-evidence.js";
|
|
2
3
|
import { runtimeLifecycleMode } from "./task-lifecycle.js";
|
|
3
4
|
import { latestObservedVerificationEvidence, meaningfulVerificationCommands, verificationEvidenceProvesStableTree } from "./verification-intelligence.js";
|
|
4
5
|
import { isCurrentWorkingTreeDigest, WORKING_TREE_DIGEST_ALGORITHM } from "./working-tree-digest.js";
|
|
@@ -25,6 +26,7 @@ export function compactTaskDetails(task) {
|
|
|
25
26
|
sessionId: task.sessionId,
|
|
26
27
|
sessionName: task.sessionName,
|
|
27
28
|
changeMode: task.changeMode,
|
|
29
|
+
mutationPolicy: task.mutationPolicy ?? (task.changeMode === "read-only" ? "forbidden" : "required"),
|
|
28
30
|
attempt: task.attempt,
|
|
29
31
|
maxAttempts: task.maxAttempts,
|
|
30
32
|
previousAttempts: task.previousAttempts,
|
|
@@ -51,12 +53,24 @@ export function compactTaskDetails(task) {
|
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
export function mergeObservedTaskContext(task, entries, maxManifestFiles, redact = (value) => String(value ?? "")) {
|
|
54
|
-
const known = new
|
|
56
|
+
const known = new Map((task.contextManifest ?? []).map((item, index) => [item.path, index]));
|
|
57
|
+
let durableCount = (task.contextManifest ?? []).filter(isDurableContextEvidenceEntry).length;
|
|
55
58
|
const added = [];
|
|
56
59
|
for (const entry of entries ?? []) {
|
|
57
|
-
|
|
60
|
+
const existingIndex = known.get(entry.path);
|
|
61
|
+
if (existingIndex !== undefined) {
|
|
62
|
+
const existing = task.contextManifest[existingIndex];
|
|
63
|
+
if (isDurableContextEvidenceEntry(existing) || !isDurableContextEvidenceEntry(entry)) continue;
|
|
64
|
+
if (durableCount >= maxManifestFiles) continue;
|
|
65
|
+
task.contextManifest[existingIndex] = { path: entry.path, reason: redact(entry.reason) };
|
|
66
|
+
durableCount += 1;
|
|
67
|
+
added.push(entry.path);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (durableCount >= maxManifestFiles || !isDurableContextEvidenceEntry(entry)) continue;
|
|
58
71
|
task.contextManifest.push({ path: entry.path, reason: redact(entry.reason) });
|
|
59
|
-
|
|
72
|
+
durableCount += 1;
|
|
73
|
+
known.set(entry.path, task.contextManifest.length - 1);
|
|
60
74
|
added.push(entry.path);
|
|
61
75
|
}
|
|
62
76
|
return added;
|
|
@@ -31,16 +31,20 @@ const ASSISTED_READ_ONLY_PLAN = [
|
|
|
31
31
|
{ id: "review", role: "piagent-reviewer", mode: "review", dependsOn: ["scout"] }
|
|
32
32
|
];
|
|
33
33
|
|
|
34
|
+
function mutationForbidden(task) {
|
|
35
|
+
return task?.changeMode === "read-only" || task?.mutationPolicy === "forbidden";
|
|
36
|
+
}
|
|
37
|
+
|
|
34
38
|
export function runtimeLifecycleMode(task) {
|
|
35
39
|
if (!Array.isArray(task?.workPlan)) return "manual";
|
|
36
40
|
if (
|
|
37
|
-
task
|
|
41
|
+
mutationForbidden(task)
|
|
38
42
|
&& (task.riskLane === "tiny" || (task.intakeMode === "runtime" && task.riskLane === "normal"))
|
|
39
43
|
&& task.workPlan.length === AUTOMATIC_READ_ONLY_PLAN.length
|
|
40
44
|
&& task.workPlan.every((step, index) => matchesStep(step, AUTOMATIC_READ_ONLY_PLAN[index]))
|
|
41
45
|
) return "automatic-readonly";
|
|
42
46
|
if (
|
|
43
|
-
task
|
|
47
|
+
mutationForbidden(task)
|
|
44
48
|
&& task.riskLane === "normal"
|
|
45
49
|
&& task.workPlan.length === ASSISTED_READ_ONLY_PLAN.length
|
|
46
50
|
&& task.workPlan.every((step, index) => matchesStep(step, ASSISTED_READ_ONLY_PLAN[index]))
|
|
@@ -68,6 +72,7 @@ function nonEmptyStrings(value) {
|
|
|
68
72
|
function sourceContractReady(task) {
|
|
69
73
|
return task?.schemaVersion === 2
|
|
70
74
|
&& task.changeMode === "source-change"
|
|
75
|
+
&& task.mutationPolicy !== "forbidden"
|
|
71
76
|
&& task.trace?.outcome === "pending"
|
|
72
77
|
&& nonEmptyStrings(task.acceptanceCriteria)
|
|
73
78
|
&& nonEmptyStrings(task.scope)
|
|
@@ -190,3 +195,35 @@ export function applyRuntimeLifecycleObservation(task, observation, recordedAt =
|
|
|
190
195
|
}
|
|
191
196
|
return { changed, mode };
|
|
192
197
|
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* A default assisted read-only review is a presentation checkpoint, not a
|
|
201
|
+
* second reasoning pass. Once runtime-owned read evidence exists and the model
|
|
202
|
+
* emits its final handoff, completing this one exact checkpoint preserves the
|
|
203
|
+
* substantive answer instead of forcing a token-heavy retry. Custom/manual
|
|
204
|
+
* plans and every source-change review remain explicit.
|
|
205
|
+
*/
|
|
206
|
+
export function applyAssistedReadOnlyFinalHandoff(
|
|
207
|
+
task,
|
|
208
|
+
{ durableReadEvidence = false, finalHandoffObserved = false } = {},
|
|
209
|
+
recordedAt = new Date().toISOString()
|
|
210
|
+
) {
|
|
211
|
+
const mode = runtimeLifecycleMode(task);
|
|
212
|
+
if (mode !== "assisted-readonly" || !durableReadEvidence || !finalHandoffObserved) {
|
|
213
|
+
return { changed: false, mode };
|
|
214
|
+
}
|
|
215
|
+
const scout = task.workPlan.find((step) => step.id === "scout");
|
|
216
|
+
const review = task.workPlan.find((step) => step.id === "review");
|
|
217
|
+
if (scout?.status !== "done" || review?.status !== "in-progress") {
|
|
218
|
+
return { changed: false, mode };
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
changed: setStep(
|
|
222
|
+
review,
|
|
223
|
+
"done",
|
|
224
|
+
"Runtime observed a final handoff grounded in durable read-only evidence.",
|
|
225
|
+
recordedAt
|
|
226
|
+
),
|
|
227
|
+
mode
|
|
228
|
+
};
|
|
229
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { execFileSync } from "node:child_process";
|
|
5
4
|
import { ensurePrivateStateDirectory, resolveLocalStatePath } from "./local-state-path.js";
|
|
6
5
|
import { appendTaskJournalEvent, readTaskJournal } from "./task-journal.js";
|
|
7
6
|
import {
|
|
@@ -21,6 +20,7 @@ import {
|
|
|
21
20
|
} from "./task-digest-state.js";
|
|
22
21
|
export { taskDigestMigrationArchiveStatus } from "./task-digest-state.js";
|
|
23
22
|
import { acceptanceReceiptWithoutWorkingTreeProof, normalizedTaskDigestFields, taskDigestContractValidationErrors } from "./task-digest-contract.js";
|
|
23
|
+
import { unicodeCodePointLength } from "./verification-intelligence.js";
|
|
24
24
|
import {
|
|
25
25
|
WORKING_TREE_DIGEST_ALGORITHM,
|
|
26
26
|
isCurrentWorkingTreeDigest,
|
|
@@ -28,13 +28,31 @@ import {
|
|
|
28
28
|
unavailableWorkingTreeHash,
|
|
29
29
|
versionWorkingTreeHash
|
|
30
30
|
} from "./working-tree-digest.js";
|
|
31
|
+
import {
|
|
32
|
+
directChildGitEvidenceRoots,
|
|
33
|
+
gitEvidenceRootDetails,
|
|
34
|
+
gitEvidenceRoots,
|
|
35
|
+
gitOutput,
|
|
36
|
+
isGitWorkingTree,
|
|
37
|
+
nonGitWorkspaceFileDetails,
|
|
38
|
+
nonGitWorkspaceFiles,
|
|
39
|
+
normalizedGitPath,
|
|
40
|
+
pathWithinNonGitWorkspaceEvidenceRoot,
|
|
41
|
+
prefixedGitPath
|
|
42
|
+
} from "./workspace-evidence-roots.js";
|
|
43
|
+
export { isGitWorkingTree } from "./workspace-evidence-roots.js";
|
|
31
44
|
|
|
32
45
|
export const TASK_CONTRACT_SCHEMA_VERSION = 2; export const DEFAULT_MAX_TASK_ATTEMPTS = 3;
|
|
46
|
+
export const OPERATOR_REQUEST_MAX_CHARS = 8_000;
|
|
47
|
+
export const TASK_SUMMARY_MAX_CHARS = 2_000;
|
|
48
|
+
export const TASK_EXPECTED_OUTPUT_MAX_CHARS = 2_000;
|
|
49
|
+
export const TASK_ACCEPTANCE_CRITERION_MAX_CHARS = 1_000;
|
|
50
|
+
export const TASK_ACCEPTANCE_CRITERIA_MAX = 12;
|
|
33
51
|
const TASK_OUTCOMES = ["pending", "completed", "blocked", "partial", "failed"], TERMINAL_TASK_OUTCOMES = new Set(["completed", "blocked", "partial", "failed"]);
|
|
34
52
|
const REVIEW_LENSES = ["correctness", "tests", "scope", "security", "docs", "release", "package"];
|
|
35
53
|
const TASK_CONTRACT_FIELDS = new Set([
|
|
36
|
-
"schemaVersion", "taskRunId", "taskId", "sessionId", "sessionName", "changeMode", "attempt", "maxAttempts",
|
|
37
|
-
"previousAttempts", "summary", "riskLane", "intakeMode", "expectedOutput", "acceptanceCriteria", "criterionGraph", "scope", "outOfScope",
|
|
54
|
+
"schemaVersion", "taskRunId", "taskId", "sessionId", "sessionName", "changeMode", "mutationPolicy", "attempt", "maxAttempts",
|
|
55
|
+
"previousAttempts", "summary", "operatorRequest", "operatorRequestDigest", "riskLane", "intakeMode", "expectedOutput", "acceptanceCriteria", "criterionGraph", "scope", "outOfScope",
|
|
38
56
|
"protectedPaths", "requiredContext", "contextManifest", "memoryCitations", "mcpCapabilities", "verifyGroup",
|
|
39
57
|
"verifyCommands", "workPlan", "reviewLenses", "orchestration", "acceptanceReceipt", "authoritySnapshot", "workingTreeDigestAlgorithm", "workingTreeDigestMigration", "baselineChangedFiles", "baselineFileDigests",
|
|
40
58
|
"observedChangedFiles", "finalWorkingTreeFiles", "finalFileDigests", "changedFiles", "verifyEvidence", "trace",
|
|
@@ -47,14 +65,6 @@ const ORCHESTRATION_FIELDS = new Set(["mode", "subagents", "reason", "fieldGuide
|
|
|
47
65
|
const MODEL_ROLE_FIELDS = new Set(["planner", "worker", "reviewer", "watchdog"]);
|
|
48
66
|
const VERIFY_EVIDENCE_FIELDS = new Set(["command", "exitCode", "summary", "recordedAt", "observed", "observedAt", "isError", "matchedProfileCommand", "preWorkingTreeDigest", "workingTreeDigest"]);
|
|
49
67
|
const TRACE_FIELDS = new Set(["outcome", "friction", "notes", "recordedAt"]);
|
|
50
|
-
const WORKSPACE_EVIDENCE_MAX_FILES = 2000;
|
|
51
|
-
const WORKSPACE_EVIDENCE_SKIP_NAMES = new Set([
|
|
52
|
-
".git", ".hg", ".svn", ".pi", "node_modules", "dist", "build", ".next", "coverage"
|
|
53
|
-
]);
|
|
54
|
-
const WORKSPACE_EVIDENCE_PRIORITY_NAMES = new Map([
|
|
55
|
-
["plans", 0]
|
|
56
|
-
]);
|
|
57
|
-
|
|
58
68
|
export function safeTaskId(value) {
|
|
59
69
|
const normalized = String(value ?? "")
|
|
60
70
|
.trim()
|
|
@@ -132,6 +142,10 @@ function stringArray(value) {
|
|
|
132
142
|
return Array.isArray(value) ? [...new Set(value.filter((item) => typeof item === "string" && item.trim()).map((item) => item.trim()))] : [];
|
|
133
143
|
}
|
|
134
144
|
|
|
145
|
+
function exactCommandArray(value) {
|
|
146
|
+
return Array.isArray(value) ? [...new Set(value.filter((item) => typeof item === "string" && item.trim()))] : [];
|
|
147
|
+
}
|
|
148
|
+
|
|
135
149
|
function stringRecord(value) {
|
|
136
150
|
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
137
151
|
return Object.fromEntries(Object.entries(value)
|
|
@@ -147,6 +161,10 @@ function validTimestamp(value) {
|
|
|
147
161
|
return typeof value === "string" && value.trim().length > 0 && Number.isFinite(Date.parse(value));
|
|
148
162
|
}
|
|
149
163
|
|
|
164
|
+
export function operatorRequestDigest(value) {
|
|
165
|
+
return `operator-request-v1:${crypto.createHash("sha256").update(String(value ?? "")).digest("hex")}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
150
168
|
function normalizedTimestamp(value, fallback) {
|
|
151
169
|
return validTimestamp(value) ? value : fallback;
|
|
152
170
|
}
|
|
@@ -187,6 +205,16 @@ export function taskContractValidationErrors(input) {
|
|
|
187
205
|
if (typeof input.taskId === "string" && safeTaskId(input.taskId) !== input.taskId) errors.push("taskId must be normalized");
|
|
188
206
|
if (typeof input.summary === "string" && input.summary.trim().length < 10) errors.push("summary must be at least 10 characters");
|
|
189
207
|
if (typeof input.expectedOutput === "string" && input.expectedOutput.trim().length < 10) errors.push("expectedOutput must be at least 10 characters");
|
|
208
|
+
const hasOperatorRequest = input.operatorRequest !== undefined;
|
|
209
|
+
const hasOperatorRequestDigest = input.operatorRequestDigest !== undefined;
|
|
210
|
+
if (hasOperatorRequest !== hasOperatorRequestDigest) errors.push("operatorRequest and operatorRequestDigest must be present together");
|
|
211
|
+
if (hasOperatorRequest) {
|
|
212
|
+
if (typeof input.operatorRequest !== "string" || !input.operatorRequest.trim()) errors.push("operatorRequest must be a non-empty string");
|
|
213
|
+
else {
|
|
214
|
+
if (unicodeCodePointLength(input.operatorRequest) > OPERATOR_REQUEST_MAX_CHARS) errors.push(`operatorRequest must contain at most ${OPERATOR_REQUEST_MAX_CHARS} Unicode characters`);
|
|
215
|
+
if (input.operatorRequestDigest !== operatorRequestDigest(input.operatorRequest)) errors.push("operatorRequestDigest does not match operatorRequest");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
190
218
|
for (const field of ["createdAt", "updatedAt"]) {
|
|
191
219
|
if (typeof input[field] === "string" && !validTimestamp(input[field])) errors.push(`${field} must be a valid timestamp`);
|
|
192
220
|
}
|
|
@@ -198,6 +226,8 @@ export function taskContractValidationErrors(input) {
|
|
|
198
226
|
}
|
|
199
227
|
if (input.failedAt !== undefined && !["research", "plan", "execute", "verify", "review"].includes(input.failedAt)) errors.push("failedAt is invalid");
|
|
200
228
|
if (!["source-change", "read-only"].includes(input.changeMode)) errors.push("changeMode is invalid");
|
|
229
|
+
if (input.mutationPolicy !== undefined && !["required", "forbidden"].includes(input.mutationPolicy)) errors.push("mutationPolicy is invalid");
|
|
230
|
+
if (input.changeMode === "read-only" && input.mutationPolicy === "required") errors.push("read-only tasks cannot require mutation");
|
|
201
231
|
if (!["tiny", "normal", "high-risk"].includes(input.riskLane)) errors.push("riskLane is invalid");
|
|
202
232
|
if (input.intakeMode !== undefined && !["model", "runtime"].includes(input.intakeMode)) errors.push("intakeMode is invalid");
|
|
203
233
|
errors.push(...taskDigestContractValidationErrors(input));
|
|
@@ -227,6 +257,7 @@ export function taskContractValidationErrors(input) {
|
|
|
227
257
|
}
|
|
228
258
|
if (Array.isArray(input.acceptanceCriteria) && input.acceptanceCriteria.length === 0) errors.push("acceptanceCriteria must not be empty");
|
|
229
259
|
if (Array.isArray(input.scope) && input.scope.length === 0) errors.push("scope must not be empty");
|
|
260
|
+
if (Array.isArray(input.scope) && input.scope.length > 2000) errors.push("scope must contain at most 2000 entries");
|
|
230
261
|
if (Array.isArray(input.previousAttempts) && input.previousAttempts.length > 10) errors.push("previousAttempts must contain at most 10 entries");
|
|
231
262
|
if (Array.isArray(input.workPlan) && input.workPlan.length > 12) errors.push("workPlan must contain at most 12 entries");
|
|
232
263
|
if (Array.isArray(input.reviewLenses)) {
|
|
@@ -390,10 +421,13 @@ export function normalizeTaskContract(input, options = {}) {
|
|
|
390
421
|
sessionId,
|
|
391
422
|
sessionName: String(input.sessionName ?? options.sessionName ?? "").trim() || undefined,
|
|
392
423
|
changeMode: input.changeMode === "read-only" ? "read-only" : "source-change",
|
|
424
|
+
mutationPolicy: input.changeMode === "read-only" || input.mutationPolicy === "forbidden" ? "forbidden" : "required",
|
|
393
425
|
attempt: positiveInteger(input.attempt, 1, 100),
|
|
394
426
|
maxAttempts: positiveInteger(input.maxAttempts, DEFAULT_MAX_TASK_ATTEMPTS, 10),
|
|
395
427
|
previousAttempts: Array.isArray(input.previousAttempts) ? input.previousAttempts.slice(-10).map((item) => pickFields(item, PREVIOUS_ATTEMPT_FIELDS)) : [],
|
|
396
428
|
summary: typeof input.summary === "string" ? input.summary : "",
|
|
429
|
+
operatorRequest: typeof input.operatorRequest === "string" && input.operatorRequest.trim() ? input.operatorRequest : undefined,
|
|
430
|
+
operatorRequestDigest: typeof input.operatorRequestDigest === "string" && input.operatorRequestDigest.trim() ? input.operatorRequestDigest : undefined,
|
|
397
431
|
riskLane: ["tiny", "normal", "high-risk"].includes(input.riskLane) ? input.riskLane : "normal",
|
|
398
432
|
intakeMode: ["model", "runtime"].includes(input.intakeMode) ? input.intakeMode : "model",
|
|
399
433
|
expectedOutput: typeof input.expectedOutput === "string" ? input.expectedOutput : "", acceptanceCriteria: stringArray(input.acceptanceCriteria),
|
|
@@ -405,7 +439,7 @@ export function normalizeTaskContract(input, options = {}) {
|
|
|
405
439
|
memoryCitations: Array.isArray(input.memoryCitations) ? input.memoryCitations.map((item) => pickFields(item, CITATION_FIELDS)) : [],
|
|
406
440
|
mcpCapabilities: stringArray(input.mcpCapabilities),
|
|
407
441
|
verifyGroup: typeof input.verifyGroup === "string" && input.verifyGroup.trim() ? input.verifyGroup.trim() : undefined,
|
|
408
|
-
verifyCommands:
|
|
442
|
+
verifyCommands: exactCommandArray(input.verifyCommands),
|
|
409
443
|
workPlan: Array.isArray(input.workPlan) ? input.workPlan.map((item) => pickFields(item, WORK_PLAN_FIELDS)) : [],
|
|
410
444
|
reviewLenses: stringArray(input.reviewLenses),
|
|
411
445
|
acceptanceReceipt: normalizeAcceptanceReceipt(legacyDigestContract
|
|
@@ -576,13 +610,6 @@ export function summarizeAttempt(task) {
|
|
|
576
610
|
};
|
|
577
611
|
}
|
|
578
612
|
|
|
579
|
-
function gitOutput(cwd, args, options = {}) {
|
|
580
|
-
return execFileSync("git", ["-C", cwd, ...args], {
|
|
581
|
-
encoding: options.encoding ?? "utf8",
|
|
582
|
-
maxBuffer: options.maxBuffer ?? 16 * 1024 * 1024,
|
|
583
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
613
|
function gitHasHead(cwd) {
|
|
587
614
|
try {
|
|
588
615
|
gitOutput(cwd, ["rev-parse", "--verify", "HEAD"]);
|
|
@@ -591,20 +618,6 @@ function gitHasHead(cwd) {
|
|
|
591
618
|
return false;
|
|
592
619
|
}
|
|
593
620
|
}
|
|
594
|
-
export function isGitWorkingTree(cwd) {
|
|
595
|
-
try {
|
|
596
|
-
return gitOutput(cwd, ["rev-parse", "--is-inside-work-tree"]).trim() === "true";
|
|
597
|
-
} catch {
|
|
598
|
-
return false;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
function normalizedGitPath(value) {
|
|
602
|
-
return String(value ?? "").replaceAll("\\", "/").replace(/^\/+/, "");
|
|
603
|
-
}
|
|
604
|
-
function prefixedGitPath(prefix, file) {
|
|
605
|
-
const normalized = normalizedGitPath(file);
|
|
606
|
-
return prefix ? `${prefix}/${normalized}` : normalized;
|
|
607
|
-
}
|
|
608
621
|
|
|
609
622
|
function workingTreeFilesForGitRoot(cwd, observeRename) {
|
|
610
623
|
if (!isGitWorkingTree(cwd)) return [];
|
|
@@ -615,90 +628,8 @@ function workingTreeFilesForGitRoot(cwd, observeRename) {
|
|
|
615
628
|
return [...new Set([...changed, ...untracked].map(normalizedGitPath).filter((item) => item && !item.startsWith(".pi/piagent-state/")))].sort();
|
|
616
629
|
}
|
|
617
630
|
|
|
618
|
-
function directChildGitEvidenceRoots(cwd) {
|
|
619
|
-
const roots = [];
|
|
620
|
-
let entries;
|
|
621
|
-
try {
|
|
622
|
-
entries = fs.readdirSync(cwd, { withFileTypes: true });
|
|
623
|
-
} catch {
|
|
624
|
-
return roots;
|
|
625
|
-
}
|
|
626
|
-
for (const entry of entries) {
|
|
627
|
-
if (!entry.isDirectory() || entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
628
|
-
const child = path.join(cwd, entry.name);
|
|
629
|
-
try {
|
|
630
|
-
if (fs.lstatSync(child).isSymbolicLink()) continue;
|
|
631
|
-
if (!isGitWorkingTree(child)) continue;
|
|
632
|
-
const topLevel = gitOutput(child, ["rev-parse", "--show-toplevel"]).trim();
|
|
633
|
-
if (fs.realpathSync(topLevel) !== fs.realpathSync(child)) continue;
|
|
634
|
-
roots.push({ cwd: child, prefix: normalizedGitPath(entry.name) });
|
|
635
|
-
} catch {
|
|
636
|
-
// Ignore unreadable or non-standard child folders; source-change start
|
|
637
|
-
// only needs at least one reliable evidence root.
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
return roots.sort((left, right) => left.prefix.localeCompare(right.prefix));
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
function gitEvidenceRoots(cwd) {
|
|
644
|
-
if (isGitWorkingTree(cwd)) return [{ cwd, prefix: "" }];
|
|
645
|
-
return directChildGitEvidenceRoots(cwd);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
function nonGitWorkspaceFiles(cwd, gitRootPrefixes) {
|
|
649
|
-
const files = [];
|
|
650
|
-
const gitRoots = new Set(gitRootPrefixes.filter(Boolean));
|
|
651
|
-
function orderedEntries(entries) {
|
|
652
|
-
return [...entries].sort((left, right) => {
|
|
653
|
-
const leftPriority = WORKSPACE_EVIDENCE_PRIORITY_NAMES.get(left.name.toLowerCase()) ?? 10;
|
|
654
|
-
const rightPriority = WORKSPACE_EVIDENCE_PRIORITY_NAMES.get(right.name.toLowerCase()) ?? 10;
|
|
655
|
-
if (leftPriority !== rightPriority) return leftPriority - rightPriority;
|
|
656
|
-
return left.name.localeCompare(right.name, "en-US");
|
|
657
|
-
});
|
|
658
|
-
}
|
|
659
|
-
function visit(directory, relativeDirectory) {
|
|
660
|
-
if (files.length >= WORKSPACE_EVIDENCE_MAX_FILES) return;
|
|
661
|
-
let entries;
|
|
662
|
-
try {
|
|
663
|
-
entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
664
|
-
} catch {
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
for (const entry of orderedEntries(entries)) {
|
|
668
|
-
if (files.length >= WORKSPACE_EVIDENCE_MAX_FILES) return;
|
|
669
|
-
if (WORKSPACE_EVIDENCE_SKIP_NAMES.has(entry.name)) continue;
|
|
670
|
-
const relative = relativeDirectory ? `${relativeDirectory}/${entry.name}` : entry.name;
|
|
671
|
-
const topSegment = relative.split("/")[0];
|
|
672
|
-
if (gitRoots.has(topSegment)) continue;
|
|
673
|
-
const absolute = path.join(directory, entry.name);
|
|
674
|
-
try {
|
|
675
|
-
if (fs.lstatSync(absolute).isSymbolicLink()) continue;
|
|
676
|
-
} catch {
|
|
677
|
-
continue;
|
|
678
|
-
}
|
|
679
|
-
if (entry.isDirectory()) {
|
|
680
|
-
visit(absolute, relative);
|
|
681
|
-
} else if (entry.isFile()) {
|
|
682
|
-
files.push(normalizedGitPath(relative));
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
visit(cwd, "");
|
|
687
|
-
return files.sort();
|
|
688
|
-
}
|
|
689
|
-
|
|
690
631
|
function nonGitWorkspaceFileDigest(cwd, file, protectedProjectPath = null) { return streamedWorkingTreeFileDigest(cwd, file, false, protectedProjectPath); }
|
|
691
632
|
|
|
692
|
-
function pathWithinNonGitWorkspaceEvidenceRoot(candidate, gitRootPrefixes) {
|
|
693
|
-
const topSegment = candidate.split("/")[0];
|
|
694
|
-
return Boolean(
|
|
695
|
-
topSegment
|
|
696
|
-
&& !WORKSPACE_EVIDENCE_SKIP_NAMES.has(topSegment)
|
|
697
|
-
&& !topSegment.startsWith(".")
|
|
698
|
-
&& !gitRootPrefixes.includes(topSegment)
|
|
699
|
-
);
|
|
700
|
-
}
|
|
701
|
-
|
|
702
633
|
export function hasGitEvidenceRoot(cwd) {
|
|
703
634
|
return gitEvidenceRoots(cwd).length > 0;
|
|
704
635
|
}
|
|
@@ -739,35 +670,36 @@ export function workingTreeFiles(cwd) {
|
|
|
739
670
|
return [];
|
|
740
671
|
}
|
|
741
672
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
const limit = Number.isInteger(maxFiles) ? Math.max(1, Math.min(50_000, maxFiles)) : 10_000;
|
|
745
|
-
const files = [];
|
|
673
|
+
export function repositoryFileManifestDetails(cwd, maxFiles = 10_000) {
|
|
674
|
+
const limit = Number.isInteger(maxFiles) ? Math.max(1, Math.min(50_000, maxFiles)) : 10_000, files = new Set();
|
|
746
675
|
try {
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
.
|
|
676
|
+
const rootDetails = gitEvidenceRootDetails(cwd), roots = rootDetails.roots;
|
|
677
|
+
const add = (candidate) => {
|
|
678
|
+
if (!candidate || candidate.startsWith(".pi/") || candidate.startsWith(".git/") || candidate.split("/").includes("node_modules")
|
|
679
|
+
|| /(?:^|\/)\.env(?:\.|$)/i.test(candidate) || /(?:^|\/)(?:auth|credentials?|secrets?|tokens?)\.json$/i.test(candidate)) return true;
|
|
680
|
+
files.add(candidate);
|
|
681
|
+
return files.size <= limit;
|
|
682
|
+
};
|
|
683
|
+
for (const root of roots) {
|
|
684
|
+
const listed = gitOutput(root.cwd, ["ls-files", "--cached", "--others", "--exclude-standard", "-z"]).split("\0").filter(Boolean);
|
|
751
685
|
for (const file of listed) {
|
|
752
686
|
const candidate = prefixedGitPath(root.prefix, file);
|
|
753
|
-
if (
|
|
754
|
-
!candidate
|
|
755
|
-
|| candidate.startsWith(".pi/")
|
|
756
|
-
|| candidate.startsWith(".git/")
|
|
757
|
-
|| candidate.split("/").includes("node_modules")
|
|
758
|
-
|| /(?:^|\/)\.env(?:\.|$)/i.test(candidate)
|
|
759
|
-
|| /(?:^|\/)(?:auth|credentials?|secrets?|tokens?)\.json$/i.test(candidate)
|
|
760
|
-
) continue;
|
|
761
|
-
files.push(candidate);
|
|
762
|
-
if (files.length >= limit) return [...new Set(files)].sort();
|
|
687
|
+
if (!add(candidate)) return { files: [...files].slice(0, limit).sort(), complete: false, candidateCount: files.size };
|
|
763
688
|
}
|
|
764
689
|
}
|
|
690
|
+
if (!isGitWorkingTree(cwd) && roots.length > 0) {
|
|
691
|
+
const looseDetails = nonGitWorkspaceFileDetails(cwd, roots.map((root) => root.prefix));
|
|
692
|
+
const looseFiles = looseDetails.files;
|
|
693
|
+
for (const file of looseFiles) if (!add(file)) return { files: [...files].slice(0, limit).sort(), complete: false, candidateCount: files.size };
|
|
694
|
+
if (!looseDetails.complete) return { files: [...files].sort(), complete: false, candidateCount: files.size };
|
|
695
|
+
}
|
|
696
|
+
if (!rootDetails.complete) return { files: [...files].sort(), complete: false, candidateCount: files.size };
|
|
765
697
|
} catch {
|
|
766
|
-
return [];
|
|
698
|
+
return { files: [], complete: false, candidateCount: 0 };
|
|
767
699
|
}
|
|
768
|
-
return [...
|
|
700
|
+
return { files: [...files].sort(), complete: true, candidateCount: files.size };
|
|
769
701
|
}
|
|
770
|
-
|
|
702
|
+
export function repositoryFileManifest(cwd, maxFiles = 10_000) { return repositoryFileManifestDetails(cwd, maxFiles).files; }
|
|
771
703
|
function changedPathsFromNameStatus(output, observeRename) {
|
|
772
704
|
const fields = String(output ?? "").split("\0").filter(Boolean);
|
|
773
705
|
const files = [];
|
|
@@ -785,6 +717,10 @@ function changedPathsFromNameStatus(output, observeRename) {
|
|
|
785
717
|
}
|
|
786
718
|
|
|
787
719
|
const WORKING_TREE_STREAM_CHUNK_BYTES = 1024 * 1024;
|
|
720
|
+
// This path cannot collide with enumerated project evidence because `.pi` is
|
|
721
|
+
// intentionally excluded from workspace discovery. Its unavailable digest
|
|
722
|
+
// turns a partial inventory into an explicitly non-proof-capable snapshot.
|
|
723
|
+
const WORKING_TREE_ENUMERATION_EVIDENCE_PATH = ".pi/piagent-state/.working-tree-enumeration-unavailable";
|
|
788
724
|
function unavailableWorkingTreeDigest(file, reason, stat) {
|
|
789
725
|
return unavailableWorkingTreeHash(crypto.createHash("sha256")
|
|
790
726
|
.update(`${file}\0${reason}\0${stat?.mode ?? ""}\0${stat?.size ?? ""}\0${stat?.mtimeMs ?? ""}\0${stat?.ctimeMs ?? ""}`)
|
|
@@ -864,7 +800,18 @@ function streamedWorkingTreeFileDigest(root, file, hasHead, protectedProjectPath
|
|
|
864
800
|
export function workingTreeSnapshot(cwd, options = {}) {
|
|
865
801
|
const snapshot = {};
|
|
866
802
|
const isProtectedProjectPath = typeof options.isProtectedProjectPath === "function" ? options.isProtectedProjectPath : () => false;
|
|
867
|
-
|
|
803
|
+
let rootDetails;
|
|
804
|
+
try {
|
|
805
|
+
rootDetails = gitEvidenceRootDetails(cwd);
|
|
806
|
+
} catch (error) {
|
|
807
|
+
snapshot[WORKING_TREE_ENUMERATION_EVIDENCE_PATH] = unavailableWorkingTreeDigest(
|
|
808
|
+
WORKING_TREE_ENUMERATION_EVIDENCE_PATH,
|
|
809
|
+
`root-enumeration:${error?.code ?? "failed"}`
|
|
810
|
+
);
|
|
811
|
+
return snapshot;
|
|
812
|
+
}
|
|
813
|
+
const roots = rootDetails.roots;
|
|
814
|
+
const incompleteReasons = rootDetails.complete ? [] : ["root-enumeration-incomplete"];
|
|
868
815
|
for (const root of roots) {
|
|
869
816
|
const protectedAliases = new Set();
|
|
870
817
|
const observeRename = (source, destination) => {
|
|
@@ -872,13 +819,33 @@ export function workingTreeSnapshot(cwd, options = {}) {
|
|
|
872
819
|
if (paths.some((file) => file === ".pi/piagent-state" || file.startsWith(".pi/piagent-state/") || isProtectedProjectPath(prefixedGitPath(root.prefix, file))))
|
|
873
820
|
paths.forEach((file) => protectedAliases.add(file));
|
|
874
821
|
};
|
|
875
|
-
|
|
876
|
-
const
|
|
877
|
-
|
|
822
|
+
try {
|
|
823
|
+
const hasHead = gitHasHead(root.cwd); for (const file of workingTreeFilesForGitRoot(root.cwd, observeRename)) {
|
|
824
|
+
const projectPath = prefixedGitPath(root.prefix, file);
|
|
825
|
+
snapshot[projectPath] = streamedWorkingTreeFileDigest(root.cwd, file, hasHead, isProtectedProjectPath(projectPath) || protectedAliases.has(file) ? projectPath : null);
|
|
826
|
+
}
|
|
827
|
+
} catch {
|
|
828
|
+
incompleteReasons.push(`git-root-enumeration-incomplete:${root.prefix || "."}`);
|
|
878
829
|
}
|
|
879
|
-
}
|
|
830
|
+
}
|
|
831
|
+
if (!roots.some((root) => root.prefix === "") && roots.length > 0) {
|
|
832
|
+
const looseDetails = nonGitWorkspaceFileDetails(
|
|
833
|
+
cwd,
|
|
834
|
+
roots.map((root) => root.prefix),
|
|
835
|
+
options.maxNonGitWorkspaceFiles
|
|
836
|
+
);
|
|
837
|
+
for (const file of looseDetails.files) {
|
|
880
838
|
snapshot[file] = nonGitWorkspaceFileDigest(cwd, file, isProtectedProjectPath(file) ? file : null);
|
|
881
|
-
|
|
839
|
+
}
|
|
840
|
+
if (!looseDetails.complete) incompleteReasons.push("non-git-enumeration-incomplete");
|
|
841
|
+
}
|
|
842
|
+
if (incompleteReasons.length > 0) {
|
|
843
|
+
snapshot[WORKING_TREE_ENUMERATION_EVIDENCE_PATH] = unavailableWorkingTreeDigest(
|
|
844
|
+
WORKING_TREE_ENUMERATION_EVIDENCE_PATH,
|
|
845
|
+
incompleteReasons.sort().join(",")
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
return snapshot;
|
|
882
849
|
}
|
|
883
850
|
|
|
884
851
|
export function taskStateMigrationStatus(cwd) {
|
|
@@ -13,8 +13,22 @@ import {
|
|
|
13
13
|
} from "./failure-types.ts";
|
|
14
14
|
import { isCurrentWorkingTreeDigest } from "./working-tree-digest.js";
|
|
15
15
|
|
|
16
|
+
export const MAX_VERIFY_COMMANDS = 2;
|
|
17
|
+
export const MAX_VERIFY_COMMAND_CHARS = 900;
|
|
18
|
+
export const MAX_VERIFY_COMMAND_BYTES = 900;
|
|
19
|
+
export const MAX_VERIFY_COMMAND_TOTAL_BYTES = MAX_VERIFY_COMMANDS * MAX_VERIFY_COMMAND_BYTES;
|
|
20
|
+
export const MAX_VERIFY_COMMAND_TOTAL_CHARS = MAX_VERIFY_COMMANDS * MAX_VERIFY_COMMAND_CHARS;
|
|
21
|
+
|
|
22
|
+
export function unicodeCodePointLength(value) {
|
|
23
|
+
return Array.from(String(value ?? "")).length;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function utf8ByteLength(value) {
|
|
27
|
+
return Buffer.byteLength(String(value ?? ""), "utf8");
|
|
28
|
+
}
|
|
29
|
+
|
|
16
30
|
function uniqueStrings(values) {
|
|
17
|
-
return [...new Set((Array.isArray(values) ? values : []).filter((value) => typeof value === "string" && value.trim())
|
|
31
|
+
return [...new Set((Array.isArray(values) ? values : []).filter((value) => typeof value === "string" && value.trim()))];
|
|
18
32
|
}
|
|
19
33
|
|
|
20
34
|
export function meaningfulVerificationCommands(commands = []) {
|
|
@@ -27,6 +41,25 @@ export function meaningfulVerificationCommands(commands = []) {
|
|
|
27
41
|
});
|
|
28
42
|
}
|
|
29
43
|
|
|
44
|
+
export function verifierCommandBoundsError(commands = []) {
|
|
45
|
+
const supplied = (Array.isArray(commands) ? commands : []).filter((command) => typeof command === "string");
|
|
46
|
+
if (supplied.some((command) => /[\r\n]/.test(command))) {
|
|
47
|
+
return "Exact verify commands must be single-line values without CR or LF characters; move multiline logic into a checked-in script before task intake.";
|
|
48
|
+
}
|
|
49
|
+
const normalized = uniqueStrings(supplied);
|
|
50
|
+
if (normalized.length > MAX_VERIFY_COMMANDS) {
|
|
51
|
+
return `Verify plans support at most ${MAX_VERIFY_COMMANDS} exact commands; consolidate or shorten the project verifier before task intake.`;
|
|
52
|
+
}
|
|
53
|
+
const oversized = normalized.find((command) => unicodeCodePointLength(command) > MAX_VERIFY_COMMAND_CHARS);
|
|
54
|
+
if (oversized) return `An exact verify command exceeds ${MAX_VERIFY_COMMAND_CHARS} characters; shorten it or move verifier logic into a checked-in script before task intake.`;
|
|
55
|
+
const oversizedTraffic = normalized.find((command) => utf8ByteLength(command) > MAX_VERIFY_COMMAND_BYTES);
|
|
56
|
+
if (oversizedTraffic) return `An exact verify command exceeds ${MAX_VERIFY_COMMAND_BYTES} UTF-8 bytes; shorten it or move verifier logic into a checked-in script before task intake.`;
|
|
57
|
+
if (normalized.reduce((total, command) => total + utf8ByteLength(command), 0) > MAX_VERIFY_COMMAND_TOTAL_BYTES) {
|
|
58
|
+
return `Exact verify commands exceed the ${MAX_VERIFY_COMMAND_TOTAL_BYTES}-byte aggregate limit; consolidate or shorten the project verifier before task intake.`;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
30
63
|
/** Return the last durably recorded observed execution for each exact command. */
|
|
31
64
|
export function latestObservedVerificationEvidence(evidence = []) {
|
|
32
65
|
const latest = new Map();
|
|
@@ -100,7 +133,10 @@ function parsedSignals(text, exitCode, structuredEvents) {
|
|
|
100
133
|
const lower = text.toLowerCase();
|
|
101
134
|
const signals = structuredEvents.map((event) => STRUCTURED_SIGNALS[event]).filter(Boolean);
|
|
102
135
|
const add = (signal, pattern) => { if (pattern.test(lower)) signals.push(signal); };
|
|
103
|
-
|
|
136
|
+
// Task scope is advisory. Only concrete filesystem-safety wording may be
|
|
137
|
+
// inferred from unstructured output; scope policy must arrive as a trusted
|
|
138
|
+
// structured event so ordinary assertion text cannot stop recovery.
|
|
139
|
+
add("scope-diagnostic", /\b(protected path|symlink escape)\b/);
|
|
104
140
|
add("policy-diagnostic", /\b(policy (?:blocked|denied|violation)|capability lock|guard blocked)\b/);
|
|
105
141
|
add("permission-diagnostic", /\b(eacces|eperm|permission denied|not authorized|unauthorized|forbidden)\b/);
|
|
106
142
|
add("provider-rate-limit", /\b(?:http\s*)?429\b|\brate[ -]?limit(?:ed|ing)?\b/);
|
|
@@ -191,7 +227,7 @@ const RECORDED_CATEGORY_HINTS = Object.freeze({
|
|
|
191
227
|
environment: "command not found in runtime environment",
|
|
192
228
|
"provider-network": "provider API request timed out",
|
|
193
229
|
"permission-policy": "permission denied by policy",
|
|
194
|
-
"scope-protected-path": "
|
|
230
|
+
"scope-protected-path": "protected path is forbidden",
|
|
195
231
|
"flaky-infrastructure": "EADDRINUSE: port is already in use"
|
|
196
232
|
});
|
|
197
233
|
|
|
@@ -202,16 +238,15 @@ export function classifyRecordedVerificationFailure(summary, exitCode = 1) {
|
|
|
202
238
|
return classifyVerificationFailure(hint ? `${text}\n${hint}` : text, exitCode);
|
|
203
239
|
}
|
|
204
240
|
|
|
205
|
-
const
|
|
241
|
+
const COMPLETION_POLICY_BOUNDARY = /(?:^read-only task has observed changes\b|^mutation-forbidden task has observed changes\b|\bprotected(?:\/read-only| or read-only)? paths?\b|\bread-only path\b)/i;
|
|
206
242
|
|
|
207
243
|
export function classifyCompletionGateFailure(missing = [], summary = "", exitCode = 1) {
|
|
208
244
|
const missingItems = Array.isArray(missing) ? missing.map((item) => String(item)) : [];
|
|
209
|
-
//
|
|
210
|
-
// completion failures.
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
structuredEvents: ["scope-violation"]
|
|
245
|
+
// Protected-path and read-only mutation boundaries are structural
|
|
246
|
+
// completion failures. Task scope is advisory and never enters this path.
|
|
247
|
+
if (missingItems.some((item) => COMPLETION_POLICY_BOUNDARY.test(item))) {
|
|
248
|
+
return classifyVerificationFailure("Completion gate rejected a protected-path or read-only boundary.", 1, {
|
|
249
|
+
structuredEvents: ["protected-path"]
|
|
215
250
|
});
|
|
216
251
|
}
|
|
217
252
|
if (missingItems.some((item) => /^critical acceptance evidence\b/i.test(item))) {
|
|
@@ -235,8 +270,8 @@ export function chooseVerificationScope(profileVerifyCommands = {}, changedFiles
|
|
|
235
270
|
|| /^(?:README|CHANGELOG|CONTRIBUTING|SECURITY)(?:\.|$)/i.test(file);
|
|
236
271
|
const hasDocs = files.some(isDocumentationFile);
|
|
237
272
|
const hasDocsOnly = files.length > 0 && files.every(isDocumentationFile);
|
|
238
|
-
const hasFrontend = files.some((file) => /(
|
|
239
|
-
const hasBackend = files.some((file) => /(
|
|
273
|
+
const hasFrontend = files.some((file) => /(^|[\/_-])(?:app|pages|components|frontend|web|client)(?=[\/_-]|$)|\.(?:tsx|jsx|css|scss|vue|svelte)$/i.test(file));
|
|
274
|
+
const hasBackend = files.some((file) => /(^|[\/_-])(?:api|server|backend|services)(?=[\/_-]|$)|\.(?:go|py|rs|java|kt|cs|rb|php|sql)$/i.test(file));
|
|
240
275
|
const selectedGroups = [];
|
|
241
276
|
if (hasDocsOnly && groups.docs) {
|
|
242
277
|
selectedGroups.push("docs");
|
|
@@ -295,7 +330,9 @@ export function selectVerificationPlan(profile, requestedGroup, changeMode, cwd,
|
|
|
295
330
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
296
331
|
const scripts = manifest.scripts && typeof manifest.scripts === "object" ? manifest.scripts : {};
|
|
297
332
|
const selected = ["type-check", "typecheck", "lint", "test"].filter((name) => typeof scripts[name] === "string");
|
|
298
|
-
if (selected.length > 0)
|
|
333
|
+
if (selected.length > 0) {
|
|
334
|
+
commands = [selected.map((name) => name === "test" ? "npm test" : `npm run ${name}`).join(" && ")];
|
|
335
|
+
}
|
|
299
336
|
}
|
|
300
337
|
} catch {
|
|
301
338
|
// Preserve the profile's fail-closed verifier when package metadata is absent or invalid.
|
|
@@ -308,5 +345,7 @@ export function selectVerificationPlan(profile, requestedGroup, changeMode, cwd,
|
|
|
308
345
|
error: `Verify group ${group ?? "(none)"} has no meaningful command. Configure .pi/piagent-profile.json before source changes.`
|
|
309
346
|
};
|
|
310
347
|
}
|
|
348
|
+
const boundsError = verifierCommandBoundsError(commands);
|
|
349
|
+
if (boundsError) return { group, commands, error: boundsError };
|
|
311
350
|
return { group, commands };
|
|
312
351
|
}
|