@hassangameryt/freeflow 0.7.3 → 0.9.0
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/.agents/plugins/marketplace.json +2 -2
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +3 -3
- package/.cursor-plugin/plugin.json +1 -1
- package/NOTICE +14 -0
- package/README.md +101 -159
- package/capabilities/cognitive-routing/SKILL.md +47 -210
- package/capabilities/cognitive-routing/references/coordinator.md +163 -0
- package/capabilities/cognitive-routing/references/worker.md +38 -0
- package/capabilities/compaction/SKILL.md +49 -0
- package/capabilities/compaction/references/summary-format.md +250 -0
- package/capabilities/messaging/SKILL.md +49 -0
- package/capabilities/task-memory/SKILL.md +87 -0
- package/command-surface.json +8 -52
- package/gemini-extension.json +1 -1
- package/hooks/claude/hooks.json +13 -3
- package/hooks/codex/hooks.json +2 -1
- package/hooks/shared/runtime-context.mjs +73 -83
- package/licenses/Apache-2.0.txt +55 -0
- package/package.json +42 -12
- package/pi-extension/README.md +41 -0
- package/pi-extension/dist/ask-user/ask.js +108 -0
- package/pi-extension/dist/ask-user/form.js +250 -0
- package/pi-extension/dist/ask-user/questions.js +66 -0
- package/pi-extension/dist/ask-user/tool.js +124 -0
- package/pi-extension/dist/cognitive-routing/assembler.js +417 -0
- package/pi-extension/dist/cognitive-routing/bootstrap.js +90 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/config.js +8 -6
- package/pi-extension/dist/cognitive-routing/economics.js +45 -0
- package/pi-extension/dist/cognitive-routing/event-schema.js +388 -0
- package/pi-extension/dist/{session-sources/events.js → cognitive-routing/event-store.js} +107 -41
- package/pi-extension/dist/cognitive-routing/gate.js +256 -0
- package/pi-extension/dist/cognitive-routing/handlers.js +901 -0
- package/pi-extension/dist/cognitive-routing/handoffs.js +350 -0
- package/pi-extension/dist/cognitive-routing/model-control.js +476 -0
- package/pi-extension/dist/cognitive-routing/projection.js +520 -0
- package/pi-extension/dist/cognitive-routing/provenance.js +83 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/render.js +15 -15
- package/pi-extension/dist/cognitive-routing/runtime.js +398 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/schemas.js +47 -11
- package/pi-extension/dist/cognitive-routing/session.js +223 -0
- package/pi-extension/dist/{session-sources → cognitive-routing}/sources.js +126 -87
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/state.js +55 -386
- package/pi-extension/dist/cognitive-routing/status.js +81 -0
- package/pi-extension/dist/cognitive-routing/tools.js +74 -0
- package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/types.js +14 -11
- package/pi-extension/dist/compaction/carry.js +205 -0
- package/pi-extension/dist/compaction/config.js +24 -0
- package/pi-extension/dist/compaction/controller.js +494 -0
- package/pi-extension/dist/compaction/harness.js +93 -0
- package/pi-extension/dist/compaction/results.js +159 -0
- package/pi-extension/dist/compaction/thresholds.js +35 -0
- package/pi-extension/dist/compaction/tool.js +57 -0
- package/pi-extension/dist/host/branch.js +13 -0
- package/pi-extension/dist/host/canonical.js +12 -0
- package/pi-extension/dist/host/catalog.js +62 -0
- package/pi-extension/dist/host/config-validation.js +233 -0
- package/pi-extension/dist/host/config.js +361 -0
- package/pi-extension/dist/host/inbound.js +142 -0
- package/pi-extension/dist/host/projection-tags.js +60 -0
- package/pi-extension/dist/host/prompts.js +93 -0
- package/pi-extension/dist/{session-sources → host}/read-only-session.js +117 -1
- package/pi-extension/dist/{runtime → host}/request-history.js +125 -39
- package/pi-extension/dist/host/rows.js +25 -0
- package/pi-extension/dist/host/runtime-state.js +204 -0
- package/pi-extension/dist/host/section-keeper.js +85 -0
- package/pi-extension/dist/host/settings/freeflow-command.js +2323 -0
- package/pi-extension/dist/{settings/settings-tui.js → host/settings/settings-view.js} +205 -73
- package/pi-extension/dist/host/staging.js +28 -0
- package/pi-extension/dist/host/status.js +115 -0
- package/pi-extension/dist/index.js +1004 -188
- package/pi-extension/dist/messaging/config.js +27 -0
- package/pi-extension/dist/messaging/messaging.js +398 -0
- package/pi-extension/dist/messaging/registry.js +57 -0
- package/pi-extension/dist/messaging/render.js +90 -0
- package/pi-extension/dist/messaging/tools.js +109 -0
- package/pi-extension/dist/messaging/transport.js +101 -0
- package/pi-extension/dist/provider-support/cache/anchor.js +21 -0
- package/pi-extension/dist/provider-support/cache/anthropic.js +102 -0
- package/pi-extension/dist/provider-support/cache/health.js +73 -0
- package/pi-extension/dist/provider-support/cache/index.js +58 -0
- package/pi-extension/dist/provider-support/cache/keepalive.js +222 -0
- package/pi-extension/dist/provider-support/cache/monitor.js +17 -0
- package/pi-extension/dist/provider-support/cache/openai-responses.js +48 -0
- package/pi-extension/dist/provider-support/effort.js +13 -0
- package/pi-extension/dist/provider-support/index.js +44 -3
- package/pi-extension/dist/provider-support/{astra → openai}/adapter.js +46 -29
- package/pi-extension/dist/provider-support/{astra → openai}/history.js +10 -8
- package/pi-extension/dist/provider-support/{astra → openai}/index.js +8 -4
- package/pi-extension/dist/provider-support/{astra → openai}/session-state.js +29 -38
- package/pi-extension/dist/provider-support/routes.js +17 -0
- package/pi-extension/dist/task-memory/config.js +49 -0
- package/pi-extension/dist/task-memory/registry.js +119 -0
- package/pi-extension/dist/task-memory/render.js +107 -0
- package/pi-extension/dist/task-memory/runtime.js +994 -0
- package/pi-extension/dist/task-memory/tool.js +233 -0
- package/pi-extension/dist/tool-execution/apply-patch/grammar.js +23 -0
- package/pi-extension/dist/tool-execution/apply-patch/match.js +38 -0
- package/pi-extension/dist/tool-execution/apply-patch/parser.js +177 -0
- package/pi-extension/dist/tool-execution/apply-patch/plan.js +201 -0
- package/pi-extension/dist/tool-execution/apply-patch/tool.js +84 -0
- package/pi-extension/dist/tool-execution/background.js +430 -0
- package/pi-extension/dist/tool-execution/bash-guard.js +23 -0
- package/pi-extension/dist/tool-execution/config.js +33 -0
- package/pi-extension/dist/tool-execution/edit-messages.js +120 -0
- package/pi-extension/dist/tool-execution/file-state.js +138 -0
- package/pi-extension/dist/tool-execution/file-tracking.js +163 -0
- package/pi-extension/dist/tool-execution/guidance-notices.js +124 -0
- package/pi-extension/dist/tool-execution/prompt.js +54 -0
- package/pi-extension/dist/tool-execution/skill-reads.js +143 -0
- package/pi-extension/dist/tool-execution/tools.js +16 -0
- package/pi-extension/freeflow/README.md +9 -0
- package/runtime/prompts/cognitive-routing.md +4 -4
- package/runtime/prompts/core.md +29 -72
- package/runtime/prompts/interaction-contract.md +18 -8
- package/runtime/prompts/tool-execution.md +25 -0
- package/runtime/prompts/working-method.md +25 -0
- package/skills/deliver-work/SKILL.md +76 -0
- package/skills/deliver-work/references/branch-closeout.md +134 -0
- package/skills/deliver-work/references/commit.md +83 -0
- package/skills/deliver-work/references/launch.md +94 -0
- package/skills/deliver-work/references/release.md +85 -0
- package/skills/deliver-work/scripts/commit-state.mjs +139 -0
- package/skills/deliver-work/scripts/delivery-state.mjs +107 -0
- package/skills/deliver-work/scripts/lib/git-state.mjs +178 -0
- package/skills/deliver-work/scripts/push-route.mjs +122 -0
- package/skills/deliver-work/scripts/release-preflight.mjs +177 -0
- package/skills/discuss-work/SKILL.md +148 -0
- package/skills/{discuss → discuss-work}/references/discussion-continuity.md +13 -50
- package/skills/discuss-work/references/learning-action.md +36 -0
- package/skills/discuss-work/scripts/choices.mjs +101 -0
- package/skills/discuss-work/scripts/lib/choices.mjs +159 -0
- package/skills/evaluate-behavior/SKILL.md +107 -0
- package/skills/evaluate-behavior/references/authoring.md +381 -0
- package/skills/evaluate-behavior/references/coding-tasks.md +145 -0
- package/skills/evaluate-behavior/references/conversations-pipelines-judges.md +177 -0
- package/skills/evaluate-behavior/references/design-and-measurement.md +273 -0
- package/skills/evaluate-behavior/references/running-and-evidence.md +245 -0
- package/skills/evaluate-behavior/scripts/claude-guard.mjs +76 -0
- package/skills/evaluate-behavior/scripts/guard-rules.mjs +309 -0
- package/skills/evaluate-behavior/scripts/lib/agreement.mjs +90 -0
- package/skills/evaluate-behavior/scripts/lib/arms.mjs +55 -0
- package/skills/evaluate-behavior/scripts/lib/budget.mjs +87 -0
- package/skills/evaluate-behavior/scripts/lib/build-source.mjs +287 -0
- package/skills/evaluate-behavior/scripts/lib/clear.mjs +74 -0
- package/skills/evaluate-behavior/scripts/lib/container.mjs +395 -0
- package/skills/evaluate-behavior/scripts/lib/decision.mjs +418 -0
- package/skills/evaluate-behavior/scripts/lib/definitions.mjs +1072 -0
- package/skills/evaluate-behavior/scripts/lib/freeflow.mjs +103 -0
- package/skills/evaluate-behavior/scripts/lib/grade-command.mjs +591 -0
- package/skills/evaluate-behavior/scripts/lib/grade-comparison.mjs +90 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-interaction.mjs +19 -18
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-support.mjs +32 -5
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-workspace.mjs +28 -12
- package/skills/evaluate-behavior/scripts/lib/grade.mjs +101 -0
- package/skills/evaluate-behavior/scripts/lib/hooks.mjs +98 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/confinement.mjs +196 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/constraints.mjs +111 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/index.mjs +48 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/isolation.mjs +74 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/launch.mjs +123 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/model-call.mjs +73 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/recorder.mjs +178 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/session.mjs +494 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/skills.mjs +81 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/index.mjs +31 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/names.mjs +3 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/confinement.mjs +173 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/host.mjs +148 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/index.mjs +70 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/isolation.mjs +81 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/model-call.mjs +79 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/process.mjs +241 -0
- package/skills/{evaluate-skill/scripts/lib → evaluate-behavior/scripts/lib/hosts/pi}/rpc.mjs +144 -26
- package/skills/evaluate-behavior/scripts/lib/identity.mjs +82 -0
- package/skills/evaluate-behavior/scripts/lib/judge.mjs +688 -0
- package/skills/evaluate-behavior/scripts/lib/metrics.mjs +332 -0
- package/skills/evaluate-behavior/scripts/lib/model-call.mjs +17 -0
- package/skills/evaluate-behavior/scripts/lib/participants.mjs +616 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/process.mjs +22 -0
- package/skills/evaluate-behavior/scripts/lib/queue.mjs +54 -0
- package/skills/evaluate-behavior/scripts/lib/reference.mjs +343 -0
- package/skills/evaluate-behavior/scripts/lib/reuse.mjs +277 -0
- package/skills/evaluate-behavior/scripts/lib/review.mjs +933 -0
- package/skills/evaluate-behavior/scripts/lib/runner.mjs +732 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/sandbox.mjs +121 -14
- package/skills/evaluate-behavior/scripts/lib/scan-shell.mjs +457 -0
- package/skills/evaluate-behavior/scripts/lib/scan.mjs +479 -0
- package/skills/evaluate-behavior/scripts/lib/seal.mjs +91 -0
- package/skills/evaluate-behavior/scripts/lib/shell.mjs +560 -0
- package/skills/evaluate-behavior/scripts/lib/source-override.mjs +64 -0
- package/skills/evaluate-behavior/scripts/lib/subject-file.mjs +88 -0
- package/skills/{evaluate-skill/scripts/lib/pi.mjs → evaluate-behavior/scripts/lib/subject.mjs} +239 -262
- package/skills/evaluate-behavior/scripts/lib/task-copy.mjs +124 -0
- package/skills/evaluate-behavior/scripts/lib/trials.mjs +524 -0
- package/skills/evaluate-behavior/scripts/lib/validity.mjs +68 -0
- package/skills/evaluate-behavior/scripts/lib/view-html.mjs +454 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/view.mjs +218 -11
- package/skills/evaluate-behavior/scripts/pi-command-tool.mjs +159 -0
- package/skills/evaluate-behavior/scripts/pi-container-route.mjs +23 -0
- package/skills/evaluate-behavior/scripts/pi-guard.mjs +77 -0
- package/skills/evaluate-behavior/scripts/pi-login-refresh.mjs +31 -0
- package/skills/evaluate-behavior/scripts/skill-eval.mjs +586 -0
- package/skills/execute-work/SKILL.md +96 -72
- package/skills/execute-work/references/code-practices.md +27 -49
- package/skills/execute-work/references/design.md +271 -0
- package/skills/execute-work/references/diagnose.md +117 -0
- package/skills/execute-work/references/migration.md +108 -0
- package/skills/execute-work/references/simplify.md +65 -0
- package/skills/execute-work/scripts/self-review.mjs +422 -0
- package/skills/execute-work/scripts/touch-map.mjs +290 -0
- package/skills/review-work/SKILL.md +94 -64
- package/skills/review-work/references/adjudication.md +94 -0
- package/skills/review-work/references/artifact.md +198 -0
- package/skills/review-work/references/evals-and-judges.md +66 -0
- package/skills/review-work/references/work.md +177 -0
- package/skills/review-work/scripts/check-brief.mjs +128 -0
- package/skills/review-work/scripts/review-packet.mjs +233 -0
- package/skills/setup-freeflow/SKILL.md +69 -16
- package/skills/track-work/SKILL.md +135 -151
- package/skills/track-work/references/handoff.md +190 -0
- package/skills/track-work/references/projects.md +51 -0
- package/skills/track-work/references/working-record-format.md +201 -344
- package/skills/track-work/scripts/lib/document.mjs +79 -19
- package/skills/track-work/scripts/lib/format.mjs +18 -1
- package/skills/track-work/scripts/lib/recover.mjs +254 -0
- package/skills/track-work/scripts/lib/registry.mjs +656 -0
- package/skills/track-work/scripts/lib/search.mjs +40 -0
- package/skills/track-work/scripts/lib/store.mjs +53 -21
- package/skills/track-work/scripts/lib/transitions.mjs +79 -4
- package/skills/track-work/scripts/lib/views.mjs +50 -1
- package/skills/track-work/scripts/working-record.mjs +263 -22
- package/skills/verify-work/SKILL.md +64 -47
- package/skills/verify-work/references/feature-map.md +120 -0
- package/skills/verify-work/references/runtime-evidence.md +203 -0
- package/skills/verify-work/references/test-design.md +27 -6
- package/skills/verify-work/scripts/test-changes.mjs +246 -0
- package/skills/write-docs/SKILL.md +171 -0
- package/skills/write-docs/references/documentation-shapes.md +184 -0
- package/skills/write-docs/references/spec-and-plan-shapes.md +308 -0
- package/skills/write-docs/scripts/artifact-check.mjs +148 -0
- package/skills/write-docs/scripts/docs-check.mjs +229 -0
- package/skills/write-docs/scripts/plan-lint.mjs +123 -0
- package/skills/write-skill/SKILL.md +165 -165
- package/skills/write-skill/references/activation-and-wording.md +143 -0
- package/skills/write-skill/references/placement-and-dependencies.md +154 -0
- package/skills/write-skill/scripts/lib/compare.mjs +701 -0
- package/skills/write-skill/scripts/lib/similarity.mjs +56 -0
- package/skills/write-skill/scripts/lib/skill-author.mjs +197 -3
- package/skills/write-skill/scripts/skill-author.mjs +67 -8
- package/capabilities/context-virtualization/SKILL.md +0 -103
- package/capabilities/conversation-history/SKILL.md +0 -53
- package/hooks/tests/check-host-adapters.test.mjs +0 -82
- package/hooks/tests/check-runtime-context-hook.sh +0 -212
- package/pi-extension/dist/cognitive-routing-v2/projection.js +0 -330
- package/pi-extension/dist/cognitive-routing-v2/runtime.js +0 -2181
- package/pi-extension/dist/cognitive-routing-v2/tools.js +0 -60
- package/pi-extension/dist/context-virtualization/commands.js +0 -89
- package/pi-extension/dist/context-virtualization/projector.js +0 -42
- package/pi-extension/dist/context-virtualization/runtime.js +0 -378
- package/pi-extension/dist/context-virtualization/state.js +0 -57
- package/pi-extension/dist/context-virtualization/types.js +0 -10
- package/pi-extension/dist/conversation-history/passages.js +0 -66
- package/pi-extension/dist/conversation-history/ranking.js +0 -105
- package/pi-extension/dist/conversation-history/runtime.js +0 -226
- package/pi-extension/dist/conversation-history/search.js +0 -56
- package/pi-extension/dist/conversation-history/types.js +0 -1
- package/pi-extension/dist/freeflow-context/resolver.js +0 -120
- package/pi-extension/dist/freeflow-context/runtime.js +0 -80
- package/pi-extension/dist/freeflow-context/source-projector.js +0 -95
- package/pi-extension/dist/freeflow-context/tool.js +0 -314
- package/pi-extension/dist/freeflow-context/types.js +0 -12
- package/pi-extension/dist/freeflow-context/visibility.js +0 -28
- package/pi-extension/dist/integrations/mcp-config.js +0 -29
- package/pi-extension/dist/runtime/runtime-context.js +0 -754
- package/pi-extension/dist/runtime/runtime-identity.js +0 -5
- package/pi-extension/dist/session-sources/provenance.js +0 -32
- package/pi-extension/dist/settings/settings-ui.js +0 -1360
- package/pi-extension/dist/ui/text-component.js +0 -57
- package/pi-extension/src/cognitive-routing-v2/budget.ts +0 -42
- package/pi-extension/src/cognitive-routing-v2/config.ts +0 -173
- package/pi-extension/src/cognitive-routing-v2/projection.ts +0 -375
- package/pi-extension/src/cognitive-routing-v2/render.ts +0 -277
- package/pi-extension/src/cognitive-routing-v2/runtime.ts +0 -2241
- package/pi-extension/src/cognitive-routing-v2/schemas.ts +0 -180
- package/pi-extension/src/cognitive-routing-v2/state.ts +0 -839
- package/pi-extension/src/cognitive-routing-v2/tools.ts +0 -62
- package/pi-extension/src/cognitive-routing-v2/types.ts +0 -254
- package/pi-extension/src/context-virtualization/commands.ts +0 -111
- package/pi-extension/src/context-virtualization/projector.ts +0 -62
- package/pi-extension/src/context-virtualization/runtime.ts +0 -428
- package/pi-extension/src/context-virtualization/state.ts +0 -79
- package/pi-extension/src/context-virtualization/types.ts +0 -92
- package/pi-extension/src/conversation-history/passages.ts +0 -81
- package/pi-extension/src/conversation-history/ranking.ts +0 -155
- package/pi-extension/src/conversation-history/runtime.ts +0 -250
- package/pi-extension/src/conversation-history/search.ts +0 -79
- package/pi-extension/src/conversation-history/types.ts +0 -31
- package/pi-extension/src/freeflow-context/resolver.ts +0 -139
- package/pi-extension/src/freeflow-context/runtime.ts +0 -100
- package/pi-extension/src/freeflow-context/source-projector.ts +0 -106
- package/pi-extension/src/freeflow-context/tool.ts +0 -366
- package/pi-extension/src/freeflow-context/types.ts +0 -70
- package/pi-extension/src/freeflow-context/visibility.ts +0 -53
- package/pi-extension/src/index.ts +0 -390
- package/pi-extension/src/integrations/mcp-config.ts +0 -32
- package/pi-extension/src/provider-support/astra/adapter.ts +0 -108
- package/pi-extension/src/provider-support/astra/history.ts +0 -145
- package/pi-extension/src/provider-support/astra/index.ts +0 -12
- package/pi-extension/src/provider-support/astra/session-state.ts +0 -113
- package/pi-extension/src/provider-support/index.ts +0 -5
- package/pi-extension/src/runtime/request-history.ts +0 -243
- package/pi-extension/src/runtime/runtime-context.ts +0 -860
- package/pi-extension/src/runtime/runtime-identity.ts +0 -8
- package/pi-extension/src/session-sources/events.ts +0 -230
- package/pi-extension/src/session-sources/provenance.ts +0 -39
- package/pi-extension/src/session-sources/read-only-session.ts +0 -170
- package/pi-extension/src/session-sources/sources.ts +0 -334
- package/pi-extension/src/settings/settings-tui.ts +0 -501
- package/pi-extension/src/settings/settings-ui.ts +0 -1652
- package/pi-extension/src/ui/text-component.ts +0 -66
- package/pi-extension/tests/cache-reuse/history.test.js +0 -117
- package/pi-extension/tests/cache-reuse/native.test.js +0 -91
- package/pi-extension/tests/cache-reuse/settings.test.js +0 -211
- package/pi-extension/tests/cognitive-routing-v2/communication.test.js +0 -577
- package/pi-extension/tests/cognitive-routing-v2/compaction-input.test.js +0 -183
- package/pi-extension/tests/cognitive-routing-v2/delegation.test.js +0 -557
- package/pi-extension/tests/cognitive-routing-v2/native.test.js +0 -474
- package/pi-extension/tests/cognitive-routing-v2/performance.test.js +0 -87
- package/pi-extension/tests/cognitive-routing-v2/persistence.test.js +0 -300
- package/pi-extension/tests/cognitive-routing-v2/projection.test.js +0 -241
- package/pi-extension/tests/cognitive-routing-v2/recovery.test.js +0 -1034
- package/pi-extension/tests/cognitive-routing-v2/render.test.js +0 -244
- package/pi-extension/tests/cognitive-routing-v2/runtime.test.js +0 -423
- package/pi-extension/tests/cognitive-routing-v2/stabilization.test.js +0 -521
- package/pi-extension/tests/cognitive-routing-v2/state.test.js +0 -586
- package/pi-extension/tests/context-virtualization/extension.test.js +0 -775
- package/pi-extension/tests/context-virtualization/projector.test.js +0 -110
- package/pi-extension/tests/context-virtualization/runtime.test.js +0 -230
- package/pi-extension/tests/conversation-history/runtime.test.js +0 -98
- package/pi-extension/tests/conversation-history/search.test.js +0 -97
- package/pi-extension/tests/fixtures/pi-host.js +0 -4
- package/pi-extension/tests/fixtures/routing-native.js +0 -191
- package/pi-extension/tests/integration/mode-free-core.test.js +0 -220
- package/pi-extension/tests/integration/pi-extension.test.js +0 -1447
- package/pi-extension/tests/integration/prompt-architecture.test.js +0 -447
- package/pi-extension/tests/integration/unaffected-regressions.test.js +0 -478
- package/pi-extension/tests/provider-support/astra-boundaries.test.js +0 -75
- package/pi-extension/tests/provider-support/astra.test.js +0 -306
- package/pi-extension/tests/provider-support/cli.test.js +0 -92
- package/pi-extension/tests/provider-support/native.test.js +0 -183
- package/pi-extension/tests/settings/settings-tui.test.js +0 -243
- package/pi-extension/tsconfig.json +0 -16
- package/plugin.json +0 -33
- package/runtime/prompts/context-virtualization.md +0 -3
- package/runtime/prompts/conversation-history.md +0 -3
- package/skills/action-selection/SKILL.md +0 -100
- package/skills/action-selection/references/trajectory-stalls.md +0 -71
- package/skills/bypass/SKILL.md +0 -153
- package/skills/commit-work/SKILL.md +0 -145
- package/skills/commit-work/references/git-state-edges.md +0 -94
- package/skills/commit-work/references/simple-push.md +0 -92
- package/skills/commit-work/references/staging-decisions.md +0 -106
- package/skills/decision-gate/SKILL.md +0 -106
- package/skills/design-for-depth/SKILL.md +0 -117
- package/skills/design-for-depth/references/design-pressure-signals.md +0 -182
- package/skills/design-for-depth/references/implementation-decisions.md +0 -76
- package/skills/design-for-depth/references/interface-design-loop.md +0 -136
- package/skills/design-for-depth/references/module-and-dependency-design.md +0 -147
- package/skills/design-for-depth/references/software-design-philosophy.md +0 -202
- package/skills/design-for-depth/references/state-and-failure-boundaries.md +0 -136
- package/skills/diagnose-failure/SKILL.md +0 -125
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +0 -48
- package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +0 -51
- package/skills/discuss/SKILL.md +0 -116
- package/skills/evaluate-skill/SKILL.md +0 -219
- package/skills/evaluate-skill/references/evaluation-definition-schema.md +0 -385
- package/skills/evaluate-skill/references/evaluation-design.md +0 -112
- package/skills/evaluate-skill/references/execution-and-evidence.md +0 -90
- package/skills/evaluate-skill/references/review-and-revision.md +0 -92
- package/skills/evaluate-skill/scripts/lib/definitions.mjs +0 -407
- package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +0 -60
- package/skills/evaluate-skill/scripts/lib/grade.mjs +0 -64
- package/skills/evaluate-skill/scripts/lib/runner.mjs +0 -368
- package/skills/evaluate-skill/scripts/pi-guard.mjs +0 -164
- package/skills/evaluate-skill/scripts/skill-eval.mjs +0 -102
- package/skills/execute-work/references/execute-work-edges.md +0 -68
- package/skills/finish-branch/SKILL.md +0 -117
- package/skills/finish-branch/references/integration-options.md +0 -80
- package/skills/handoff/SKILL.md +0 -82
- package/skills/handoff/references/handoff-templates.md +0 -150
- package/skills/launch-work/SKILL.md +0 -116
- package/skills/launch-work/references/launch-readiness.md +0 -85
- package/skills/migration-work/SKILL.md +0 -115
- package/skills/migration-work/references/migration-lifecycle.md +0 -82
- package/skills/release-work/SKILL.md +0 -118
- package/skills/release-work/references/release-evidence.md +0 -78
- package/skills/review-artifact/SKILL.md +0 -142
- package/skills/review-artifact/references/adjudicate-artifact-review.md +0 -101
- package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +0 -159
- package/skills/review-work/references/adjudicate-work-review.md +0 -97
- package/skills/review-work/references/independent-work-reviewer-contract.md +0 -139
- package/skills/review-work/references/security-risk-lens.md +0 -69
- package/skills/setup-freeflow/references/activation-contract.md +0 -109
- package/skills/setup-freeflow/references/host-setup.md +0 -67
- package/skills/simplify-code/SKILL.md +0 -85
- package/skills/simplify-code/references/simplification-patterns.md +0 -70
- package/skills/verify-work/references/browser-runtime-evidence.md +0 -55
- package/skills/verify-work/references/integration-evidence.md +0 -47
- package/skills/verify-work/references/performance-evidence.md +0 -57
- package/skills/workflow/SKILL.md +0 -127
- package/skills/workflow/references/domain-skill-composition.md +0 -43
- package/skills/write-plan/SKILL.md +0 -92
- package/skills/write-plan/references/plan-shapes.md +0 -156
- package/skills/write-skill/references/activation-boundaries.md +0 -104
- package/skills/write-skill/references/agent-first-instructions.md +0 -121
- package/skills/write-skill/references/progressive-disclosure.md +0 -127
- package/skills/write-skill/references/skill-dependency-graphs.md +0 -190
- package/skills/write-spec/SKILL.md +0 -92
- package/skills/write-spec/references/artifact-standards.md +0 -86
- package/skills/write-spec/references/decision-records.md +0 -78
- package/skills/write-spec/references/spec-shapes.md +0 -143
- /package/pi-extension/dist/{cognitive-routing-v2 → cognitive-routing}/budget.js +0 -0
- /package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/evidence.mjs +0 -0
- /package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-observer.mjs +0 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
// The transcript scan: after each run, flags what a sealed subject should not have tried and an honest result should
|
|
2
|
+
// not contain. A flag is evidence for a person to look at, not a verdict: the run stays in the results, graded as
|
|
3
|
+
// usual, but it is held out of every count that supports a claim until a person clears each flag with
|
|
4
|
+
// `skill-eval clear`, which records why in `clearances.json` beside the run (the run's own files never change).
|
|
5
|
+
//
|
|
6
|
+
// Kinds:
|
|
7
|
+
// - outside-read: a read, ls, find or grep whose path is outside what the run was given (blocked or not; the attempt
|
|
8
|
+
// is the evidence), or a bash command naming a host path under the user's home or the definition root that is
|
|
9
|
+
// outside it, or a credential location under a home (`~/.ssh`, `~/.pi`, ...). What the run was given is its
|
|
10
|
+
// workspace, its declared resources and its own arm directory except `isolation/`, where the host's login lives.
|
|
11
|
+
// - network: a bash command that reaches for an external target: an HTTP client, socket tool, remote shell or Git
|
|
12
|
+
// remote whose target is not this machine or the container's own network, code that fetches an external URL, or,
|
|
13
|
+
// when the run's network was "none", an install that names a package from a registry. A URL in a payload or in
|
|
14
|
+
// tool output is not an attempt (`lib/scan-shell.mjs`).
|
|
15
|
+
// - instruction-file: a read of CLAUDE.md, AGENTS.md or .claude/ outside the workspace, or a bash command reaching
|
|
16
|
+
// for one outside it.
|
|
17
|
+
// - answer-copy: the subject's answer or written files contain the hidden tests (several distinctive lines of a
|
|
18
|
+
// grader file, or a required test's name) or most of the reference solution's distinctive lines. A distinctive line
|
|
19
|
+
// is long, carries several words and is not an import, so boilerplate any code shares is never a copy.
|
|
20
|
+
//
|
|
21
|
+
// A file the task gives the subject (in the workspace, or a declared skill or context) is never flagged.
|
|
22
|
+
|
|
23
|
+
import { lstat, readdir, readFile, writeFile } from "node:fs/promises";
|
|
24
|
+
import os from "node:os";
|
|
25
|
+
import path from "node:path";
|
|
26
|
+
|
|
27
|
+
import { registerPostRunHook } from "./hooks.mjs";
|
|
28
|
+
import { networkAttempts, program, simpleCommands } from "./scan-shell.mjs";
|
|
29
|
+
import { readRegularFile, readSubjectFile, skippedNote } from "./subject-file.mjs";
|
|
30
|
+
|
|
31
|
+
export const CLEARANCES = "clearances.json";
|
|
32
|
+
const READ_TOOLS = new Set(["read", "ls", "find", "grep"]);
|
|
33
|
+
const INSTRUCTION_FILE = /(?:^|\/)(?:CLAUDE\.md|AGENTS\.md|\.claude)(?:\/|$)/;
|
|
34
|
+
const INSTRUCTION_NAME = /CLAUDE\.md|AGENTS\.md|\.claude\b/;
|
|
35
|
+
// Where a home keeps logins and keys. A shell's HOME may be an isolated directory, but naming one of these is still
|
|
36
|
+
// reaching for a credential.
|
|
37
|
+
const CREDENTIAL =
|
|
38
|
+
/^(?:~|\$HOME|\$\{HOME\})\/(?:\.ssh|\.aws|\.gnupg|\.netrc|\.npmrc|\.git-credentials|\.docker|\.kube|\.config\/gh|\.pi|\.claude|\.codex)(?:\/|$)/;
|
|
39
|
+
// A line long enough, with enough words, not to coincide by chance with code the subject wrote itself.
|
|
40
|
+
const DISTINCTIVE_LENGTH = 24;
|
|
41
|
+
const DISTINCTIVE_WORDS = 4;
|
|
42
|
+
// Lines every module of a language shares: imports and requires are never evidence of a copy.
|
|
43
|
+
const BOILERPLATE =
|
|
44
|
+
/^(?:import\b|export\s+(?:\*|\{[^}]*\})\s+from\b|from\s+\S+\s+import\b|(?:const|let|var)\s+.*=\s*require\()/;
|
|
45
|
+
// How many consecutive distinctive lines of one hidden grader file make a copy.
|
|
46
|
+
const GRADER_LINES = 3;
|
|
47
|
+
const OUTPUT_FILE_LIMIT = 512 * 1024;
|
|
48
|
+
|
|
49
|
+
function isContained(root, target) {
|
|
50
|
+
const relative = path.relative(root, target);
|
|
51
|
+
return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Every tool call the run recorded, with its turn. */
|
|
55
|
+
function toolCalls(run) {
|
|
56
|
+
if (Array.isArray(run.turns)) {
|
|
57
|
+
return run.turns.flatMap((turn) =>
|
|
58
|
+
(Array.isArray(turn?.toolActivity) ? turn.toolActivity : []).map((call) => ({
|
|
59
|
+
...call,
|
|
60
|
+
turn: turn.turn ?? null,
|
|
61
|
+
})),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return (Array.isArray(run.toolActivity) ? run.toolActivity : []).map((call) => ({ ...call, turn: 1 }));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Where a path a tool was given points on the host: a path under the shell's guest workspace maps to the workspace,
|
|
69
|
+
* and `~` is the user's home.
|
|
70
|
+
*/
|
|
71
|
+
function hostPath(requested, workspace, guest, home) {
|
|
72
|
+
const value = requested.startsWith("@") ? requested.slice(1) : requested;
|
|
73
|
+
if (typeof guest === "string" && (value === guest || value.startsWith(`${guest}/`))) {
|
|
74
|
+
return path.join(workspace, value.slice(guest.length));
|
|
75
|
+
}
|
|
76
|
+
if (value === "~" || value.startsWith("~/")) return path.join(home, value.slice(1));
|
|
77
|
+
return path.resolve(workspace, value);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Path-like words of a shell command, quotes and separators removed. */
|
|
81
|
+
function commandPaths(command) {
|
|
82
|
+
return command
|
|
83
|
+
.split(/[\s;|&()<>=,'"`]+/)
|
|
84
|
+
.map((word) => word.trim())
|
|
85
|
+
.filter((word) => word.startsWith("/") || word.startsWith("~") || word.startsWith("$HOME") || word.includes(".."));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const TEMPORARY = [
|
|
89
|
+
...new Set([
|
|
90
|
+
"/tmp",
|
|
91
|
+
"/private/tmp",
|
|
92
|
+
"/var/tmp",
|
|
93
|
+
"/private/var/tmp",
|
|
94
|
+
"/var/folders",
|
|
95
|
+
"/private/var/folders",
|
|
96
|
+
os.tmpdir(),
|
|
97
|
+
]),
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
/** Whether a host path is under a temp directory, where a subject's commands keep their scratch files. */
|
|
101
|
+
function isTemporary(target) {
|
|
102
|
+
return TEMPORARY.some((entry) => isContained(entry, target));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function flagOf(kind, detail, evidence) {
|
|
106
|
+
return { kind, detail, evidence };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The arm directory a run was made in: its workspace's parent, where the run's own files are. */
|
|
110
|
+
function armDirectoryOf(run) {
|
|
111
|
+
return typeof run?.workspace === "string" && path.basename(run.workspace) === "workspace"
|
|
112
|
+
? path.dirname(run.workspace)
|
|
113
|
+
: null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The scan's flags for one run, in a stable order with IDs `<kind>-<n>`.
|
|
118
|
+
*
|
|
119
|
+
* Files the subject wrote that the scan did not read (a link it made, a FIFO) are added to `skipped` with why.
|
|
120
|
+
*
|
|
121
|
+
* @param {{ run: any, group?: any, arm?: string, root?: string, home?: string, directory?: string | null, copies?: boolean, skipped?: {path: string, note: string}[] }} input
|
|
122
|
+
*/
|
|
123
|
+
export async function scanRun({
|
|
124
|
+
run,
|
|
125
|
+
group = null,
|
|
126
|
+
arm = null,
|
|
127
|
+
root = null,
|
|
128
|
+
home = os.homedir(),
|
|
129
|
+
directory = null,
|
|
130
|
+
copies = true,
|
|
131
|
+
skipped = [],
|
|
132
|
+
}) {
|
|
133
|
+
const workspace = typeof run?.workspace === "string" ? run.workspace : null;
|
|
134
|
+
const shell = arm === null ? undefined : group?.variants?.[arm]?.shell;
|
|
135
|
+
const guest = shell?.workspace;
|
|
136
|
+
// A container's bash starts in the workspace at its host path (the guest path is a link to it); another isolation's
|
|
137
|
+
// starts at its guest path.
|
|
138
|
+
const startsAtGuest = typeof guest === "string" && shell?.container === undefined;
|
|
139
|
+
const hostRoots = [home, root].filter((entry) => typeof entry === "string" && entry !== "");
|
|
140
|
+
// The arm directory holds the run's resources and package copies; only its isolation holds what no tool may read.
|
|
141
|
+
const armDirectory = directory ?? armDirectoryOf(run);
|
|
142
|
+
const readable = readableRoots(run, armDirectory);
|
|
143
|
+
const inside = (target) =>
|
|
144
|
+
readable.some((entry) => isContained(entry, target)) ||
|
|
145
|
+
(armDirectory !== null &&
|
|
146
|
+
isContained(armDirectory, target) &&
|
|
147
|
+
!isContained(path.join(armDirectory, "isolation"), target));
|
|
148
|
+
const isGuest = (word) => typeof guest === "string" && (word === guest || word.startsWith(`${guest}/`));
|
|
149
|
+
// Where a word of a bash command points on the host, or null when it is no host path: a guest path is the
|
|
150
|
+
// workspace, and a relative path with `..` starts from where bash starts.
|
|
151
|
+
const bashTarget = (word) => {
|
|
152
|
+
if (isGuest(word)) return null;
|
|
153
|
+
if (path.isAbsolute(word)) return path.normalize(word);
|
|
154
|
+
if (!word.split("/").includes("..") || workspace === null) return null;
|
|
155
|
+
if (startsAtGuest) {
|
|
156
|
+
const resolved = path.posix.resolve(guest, word);
|
|
157
|
+
return isGuest(resolved) ? null : path.normalize(resolved);
|
|
158
|
+
}
|
|
159
|
+
return path.resolve(workspace, word);
|
|
160
|
+
};
|
|
161
|
+
const flags = [];
|
|
162
|
+
const sealed =
|
|
163
|
+
(run?.sealing?.network ?? (arm === null ? undefined : group?.configurations?.[arm]?.network) ?? null) === "none";
|
|
164
|
+
|
|
165
|
+
for (const call of toolCalls(run ?? {})) {
|
|
166
|
+
const where = { turn: call.turn, tool: call.toolName };
|
|
167
|
+
if (READ_TOOLS.has(call.toolName) && typeof call.args?.path === "string" && workspace !== null) {
|
|
168
|
+
const target = hostPath(call.args.path, workspace, guest, home);
|
|
169
|
+
// A scratch file under the system's temp directory is the subject's own (its bash wrote it there), unless the
|
|
170
|
+
// temp directory holds the definition root or a home.
|
|
171
|
+
const scratch = isTemporary(target) && !hostRoots.some((entry) => isContained(entry, target));
|
|
172
|
+
if (!inside(target) && !scratch) {
|
|
173
|
+
const kind = INSTRUCTION_FILE.test(target) ? "instruction-file" : "outside-read";
|
|
174
|
+
flags.push(
|
|
175
|
+
flagOf(kind, `${call.toolName} of ${call.args.path}, outside the workspace and declared resources`, {
|
|
176
|
+
...where,
|
|
177
|
+
path: call.args.path,
|
|
178
|
+
blocked: call.isError === true,
|
|
179
|
+
}),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (call.toolName !== "bash" || typeof call.args?.command !== "string") continue;
|
|
184
|
+
const command = call.args.command;
|
|
185
|
+
for (const attempt of networkAttempts(command, { sealed })) {
|
|
186
|
+
flags.push(
|
|
187
|
+
flagOf("network", `a bash command reaches for the network: ${attempt.program} ${attempt.target}`.trim(), {
|
|
188
|
+
...where,
|
|
189
|
+
command,
|
|
190
|
+
program: attempt.program,
|
|
191
|
+
target: attempt.target,
|
|
192
|
+
}),
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
const named = new Set();
|
|
196
|
+
for (const word of commandPaths(command)) {
|
|
197
|
+
const credential = CREDENTIAL.test(word);
|
|
198
|
+
const target = credential ? null : bashTarget(word);
|
|
199
|
+
if (!credential) {
|
|
200
|
+
if (target === null || inside(target) || !hostRoots.some((entry) => isContained(entry, target))) continue;
|
|
201
|
+
}
|
|
202
|
+
if (named.has(word)) continue;
|
|
203
|
+
named.add(word);
|
|
204
|
+
const kind = INSTRUCTION_FILE.test(target ?? word) ? "instruction-file" : "outside-read";
|
|
205
|
+
flags.push(
|
|
206
|
+
flagOf(kind, `a bash command names ${word}, a host path outside the workspace`, { ...where, command }),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
if (
|
|
210
|
+
INSTRUCTION_NAME.test(command) &&
|
|
211
|
+
!flags.some((flag) => flag.kind === "instruction-file" && flag.evidence.command === command)
|
|
212
|
+
) {
|
|
213
|
+
if (reachesInstructionFile(command, { inside, bashTarget, isGuest })) {
|
|
214
|
+
flags.push(
|
|
215
|
+
flagOf("instruction-file", "a bash command reaches for an instruction file outside the workspace", {
|
|
216
|
+
...where,
|
|
217
|
+
command,
|
|
218
|
+
}),
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (copies && group !== null && root !== null && arm !== null) {
|
|
224
|
+
flags.push(...(await answerCopies({ run, group, arm, root, skipped })));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const counts = {};
|
|
228
|
+
return flags.map((flag) => {
|
|
229
|
+
counts[flag.kind] = (counts[flag.kind] ?? 0) + 1;
|
|
230
|
+
return { id: `${flag.kind}-${counts[flag.kind]}`, ...flag };
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Whether a bash command reaches for an instruction file outside the workspace: one of its simple commands names one
|
|
236
|
+
* through `~`, `..` or a host path outside, or searches for one (`find`, `locate`) from the filesystem's root, a home
|
|
237
|
+
* or a parent. A relative name is the workspace's own file.
|
|
238
|
+
*/
|
|
239
|
+
function reachesInstructionFile(command, { inside, bashTarget, isGuest }) {
|
|
240
|
+
for (const { words } of simpleCommands(command)) {
|
|
241
|
+
if (!words.some((word) => INSTRUCTION_NAME.test(word))) continue;
|
|
242
|
+
for (const word of words) {
|
|
243
|
+
if (!INSTRUCTION_NAME.test(word)) continue;
|
|
244
|
+
if (word.startsWith("~") || word.startsWith("$HOME")) return true;
|
|
245
|
+
const target = bashTarget(word);
|
|
246
|
+
if (target !== null && !inside(target)) return true;
|
|
247
|
+
}
|
|
248
|
+
const { name, args } = program(words);
|
|
249
|
+
if (name === "locate") return true;
|
|
250
|
+
if (name === "find") {
|
|
251
|
+
const start = args.find((arg) => !arg.startsWith("-")) ?? ".";
|
|
252
|
+
if (start === "/" || start.startsWith("~") || start.startsWith("$HOME") || start.split("/").includes("..")) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
if (path.isAbsolute(start) && !isGuest(start) && !inside(path.normalize(start))) return true;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* The roots the run's tools could read: recorded on the run, or rebuilt from its resources for older runs, which kept
|
|
263
|
+
* Freeflow's package and runtime bundles in the group's `resources/`, beside the arm directory.
|
|
264
|
+
*/
|
|
265
|
+
function readableRoots(run, armDirectory) {
|
|
266
|
+
if (Array.isArray(run?.resources?.readableRoots)) return run.resources.readableRoots;
|
|
267
|
+
const roots = typeof run?.workspace === "string" ? [run.workspace] : [];
|
|
268
|
+
for (const entry of [...(run?.resources?.skills ?? []), ...(run?.resources?.context ?? [])]) {
|
|
269
|
+
if (typeof entry?.path === "string") roots.push(entry.path);
|
|
270
|
+
}
|
|
271
|
+
if (armDirectory !== null) {
|
|
272
|
+
const groupDirectory = path.dirname(armDirectory);
|
|
273
|
+
const metadata = run?.resources?.freeflow?.metadata;
|
|
274
|
+
if (typeof metadata === "string") roots.push(path.join(groupDirectory, path.dirname(metadata)));
|
|
275
|
+
roots.push(path.join(groupDirectory, "resources", "runtime"));
|
|
276
|
+
}
|
|
277
|
+
return roots;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function lines(text) {
|
|
281
|
+
return text
|
|
282
|
+
.split(/\r?\n/)
|
|
283
|
+
.map((line) => line.trim())
|
|
284
|
+
.filter((line) => line.length >= DISTINCTIVE_LENGTH);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Whether a line could only be copied: long, several distinct words, and no import or require. */
|
|
288
|
+
function isDistinctive(line) {
|
|
289
|
+
if (line.length < DISTINCTIVE_LENGTH || BOILERPLATE.test(line)) return false;
|
|
290
|
+
return new Set(line.match(/[A-Za-z_][A-Za-z0-9_]{2,}/g) ?? []).size >= DISTINCTIVE_WORDS;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function filesUnder(target) {
|
|
294
|
+
const entry = await lstat(target).catch(() => null);
|
|
295
|
+
if (entry === null) return [];
|
|
296
|
+
if (entry.isFile()) return [target];
|
|
297
|
+
if (!entry.isDirectory()) return [];
|
|
298
|
+
const files = [];
|
|
299
|
+
for (const child of await readdir(target)) files.push(...(await filesUnder(path.join(target, child))));
|
|
300
|
+
return files;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** A definition file's text (a grader, the fixture, the reference solution), never through a link, to a bound. */
|
|
304
|
+
async function textFile(file) {
|
|
305
|
+
const result = await readRegularFile(file, OUTPUT_FILE_LIMIT);
|
|
306
|
+
return result.skipped === undefined ? result.text : "";
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** Hidden-test and reference-solution text found in what the subject answered or wrote. */
|
|
310
|
+
async function answerCopies({ run, group, arm, root, skipped }) {
|
|
311
|
+
const fixtureLines = new Set();
|
|
312
|
+
const fixture = run.resources?.fixture?.snapshotPath ?? run.resources?.fixture?.sourcePath ?? null;
|
|
313
|
+
if (typeof fixture === "string") {
|
|
314
|
+
for (const file of await filesUnder(fixture))
|
|
315
|
+
for (const line of lines(await textFile(file))) fixtureLines.add(line);
|
|
316
|
+
}
|
|
317
|
+
const distinctive = (text) => [
|
|
318
|
+
...new Set(lines(text).filter((line) => isDistinctive(line) && !fixtureLines.has(line))),
|
|
319
|
+
];
|
|
320
|
+
|
|
321
|
+
const output = [
|
|
322
|
+
run.response ?? "",
|
|
323
|
+
...(Array.isArray(run.turns) ? run.turns.map((turn) => turn?.response ?? "") : []),
|
|
324
|
+
];
|
|
325
|
+
const changes = run.effects?.changes ?? {};
|
|
326
|
+
for (const relative of [...(changes.created ?? []), ...(changes.modified ?? [])]) {
|
|
327
|
+
if (typeof run.workspace !== "string") continue;
|
|
328
|
+
// A file the subject wrote is read only as a regular file inside its workspace, never through a link it made.
|
|
329
|
+
const file = await readSubjectFile(run.workspace, relative, { limit: OUTPUT_FILE_LIMIT });
|
|
330
|
+
if (file.skipped === undefined) output.push(file.text);
|
|
331
|
+
else if (file.skipped !== "missing" && file.skipped !== "binary")
|
|
332
|
+
skipped.push({ path: relative, note: skippedNote(file) });
|
|
333
|
+
}
|
|
334
|
+
const produced = new Set(output.flatMap((text) => lines(text)));
|
|
335
|
+
const producedText = output.join("\n");
|
|
336
|
+
const flags = [];
|
|
337
|
+
|
|
338
|
+
for (const expectation of group.expectations ?? []) {
|
|
339
|
+
if (expectation.kind !== "command" || expectation.variant !== arm) continue;
|
|
340
|
+
for (const source of Object.values(expectation.files ?? {})) {
|
|
341
|
+
for (const file of await filesUnder(path.resolve(root, source))) {
|
|
342
|
+
// Scattered shared lines are idioms any code for the task might write (start a server, read a response); a
|
|
343
|
+
// copy is a block of consecutive lines, as in a copied test, or a large share of the file.
|
|
344
|
+
const grader = distinctive(await textFile(file));
|
|
345
|
+
const copied = grader.filter((line) => produced.has(line));
|
|
346
|
+
let block = 0;
|
|
347
|
+
let longest = 0;
|
|
348
|
+
for (const line of grader) {
|
|
349
|
+
block = produced.has(line) ? block + 1 : 0;
|
|
350
|
+
longest = Math.max(longest, block);
|
|
351
|
+
}
|
|
352
|
+
if (longest >= GRADER_LINES || (copied.length >= GRADER_LINES && copied.length * 4 >= grader.length)) {
|
|
353
|
+
flags.push(
|
|
354
|
+
flagOf("answer-copy", `the subject's output contains lines of the hidden grader file ${source}`, {
|
|
355
|
+
source,
|
|
356
|
+
matched: copied.length,
|
|
357
|
+
of: grader.length,
|
|
358
|
+
consecutive: longest,
|
|
359
|
+
lines: copied.slice(0, 5),
|
|
360
|
+
}),
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const names = (expectation.pass?.tests?.required ?? []).filter(
|
|
366
|
+
(name) => name.length >= DISTINCTIVE_LENGTH && producedText.includes(name),
|
|
367
|
+
);
|
|
368
|
+
if (names.length > 0) {
|
|
369
|
+
flags.push(
|
|
370
|
+
flagOf("answer-copy", `the subject's output names hidden tests of ${expectation.id}`, {
|
|
371
|
+
expectation: expectation.id,
|
|
372
|
+
names,
|
|
373
|
+
}),
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const reference = group.reference_solution;
|
|
379
|
+
if (reference && (reference.arm === undefined || reference.arm === arm)) {
|
|
380
|
+
const source = path.resolve(root, reference.patch ?? reference.files);
|
|
381
|
+
let text = "";
|
|
382
|
+
for (const file of await filesUnder(source)) text += `${await textFile(file)}\n`;
|
|
383
|
+
// A patch's added lines are the solution; its context and removed lines are the fixture.
|
|
384
|
+
if (reference.patch !== undefined) {
|
|
385
|
+
text = text
|
|
386
|
+
.split(/\r?\n/)
|
|
387
|
+
.filter((line) => line.startsWith("+") && !line.startsWith("+++"))
|
|
388
|
+
.map((line) => line.slice(1))
|
|
389
|
+
.join("\n");
|
|
390
|
+
}
|
|
391
|
+
const solution = distinctive(text);
|
|
392
|
+
const copied = solution.filter((line) => produced.has(line));
|
|
393
|
+
if (copied.length >= 3 && copied.length * 2 >= solution.length) {
|
|
394
|
+
flags.push(
|
|
395
|
+
flagOf("answer-copy", "the subject's output contains most of the reference solution's distinctive lines", {
|
|
396
|
+
source: reference.patch ?? reference.files,
|
|
397
|
+
matched: copied.length,
|
|
398
|
+
of: solution.length,
|
|
399
|
+
lines: copied.slice(0, 5),
|
|
400
|
+
}),
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return flags;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Post-run hook: the scan's flags land in run.json under `postRun.scan`, with any written file it did not read under
|
|
409
|
+
* `skipped`. A pipeline's earlier stages are scanned too, from their stored runs, with each flag's ID prefixed by its
|
|
410
|
+
* stage (`setup.network-1`).
|
|
411
|
+
*/
|
|
412
|
+
export async function scanHook({ group, arm, run, root, directory }) {
|
|
413
|
+
const skipped = [];
|
|
414
|
+
const flags = await scanRun({ run, group, arm, root, directory, skipped });
|
|
415
|
+
for (const stage of run.participants?.stages ?? []) {
|
|
416
|
+
if (stage.graded || typeof stage.run !== "string") continue;
|
|
417
|
+
const stored = path.join(path.dirname(directory), stage.run);
|
|
418
|
+
let stageRun;
|
|
419
|
+
try {
|
|
420
|
+
stageRun = JSON.parse(await readFile(stored, "utf8"));
|
|
421
|
+
stageRun.transcript = JSON.parse(await readFile(path.join(path.dirname(stored), "transcript.json"), "utf8"));
|
|
422
|
+
} catch {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
// An earlier stage's answer is the graded stage's material, not a result, so only its tool use is scanned.
|
|
426
|
+
const stageDirectory = path.dirname(stored);
|
|
427
|
+
for (const flag of await scanRun({ run: stageRun, group, arm, root, directory: stageDirectory, copies: false })) {
|
|
428
|
+
flags.push({ ...flag, id: `${stage.id}.${flag.id}`, stage: stage.id });
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
// What the scan left unread, and why, so a person can see that a linked or special file was not looked into.
|
|
432
|
+
return skipped.length > 0 ? { flags, skipped } : { flags };
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/** A run's clearances, from `clearances.json` in its arm directory; empty when there is none. */
|
|
436
|
+
export async function readClearances(armDirectory) {
|
|
437
|
+
try {
|
|
438
|
+
const value = JSON.parse(await readFile(path.join(armDirectory, CLEARANCES), "utf8"));
|
|
439
|
+
return Array.isArray(value?.cleared) ? value.cleared : [];
|
|
440
|
+
} catch {
|
|
441
|
+
return [];
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** The flag IDs of a stored run that no person has cleared. */
|
|
446
|
+
export async function unclearedFlags(run, armDirectory) {
|
|
447
|
+
const flags = Array.isArray(run?.postRun?.scan?.flags) ? run.postRun.scan.flags : [];
|
|
448
|
+
if (flags.length === 0) return [];
|
|
449
|
+
const cleared = new Set((await readClearances(armDirectory)).map((entry) => entry.flag));
|
|
450
|
+
return flags.map((flag) => flag.id).filter((id) => !cleared.has(id));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Records that a person cleared some of a run's flags, with their reason, in `clearances.json` beside the run.
|
|
455
|
+
* Returns the flags cleared now; refuses an ID the run does not have.
|
|
456
|
+
*
|
|
457
|
+
* @param {string} armDirectory
|
|
458
|
+
* @param {any} run the stored run.json
|
|
459
|
+
* @param {string[] | "all"} ids
|
|
460
|
+
* @param {string} reason
|
|
461
|
+
*/
|
|
462
|
+
export async function clearFlags(armDirectory, run, ids, reason) {
|
|
463
|
+
if (typeof reason !== "string" || reason.trim() === "") throw new Error("clearing a flag needs a --reason");
|
|
464
|
+
const flags = Array.isArray(run?.postRun?.scan?.flags) ? run.postRun.scan.flags.map((flag) => flag.id) : [];
|
|
465
|
+
const selected = ids === "all" ? flags : ids;
|
|
466
|
+
const unknown = selected.filter((id) => !flags.includes(id));
|
|
467
|
+
if (unknown.length > 0) {
|
|
468
|
+
throw new Error(`the run has no flag ${unknown.join(", ")}; its flags: ${flags.join(", ") || "none"}`);
|
|
469
|
+
}
|
|
470
|
+
const existing = await readClearances(armDirectory);
|
|
471
|
+
const already = new Set(existing.map((entry) => entry.flag));
|
|
472
|
+
const at = new Date().toISOString();
|
|
473
|
+
const added = selected.filter((id) => !already.has(id)).map((flag) => ({ flag, reason: reason.trim(), at }));
|
|
474
|
+
const cleared = [...existing, ...added];
|
|
475
|
+
await writeFile(path.join(armDirectory, CLEARANCES), `${JSON.stringify({ schema_version: 1, cleared }, null, 2)}\n`);
|
|
476
|
+
return added.map((entry) => entry.flag);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
registerPostRunHook("scan", scanHook);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// Sealing: what code the subject could have written may reach. Three paths run such code: the subject's `bash`,
|
|
2
|
+
// its declared commands (`run_command`, which run the workspace's tests and scripts), and command graders (hidden
|
|
3
|
+
// tests that import the subject's code). The configuration's `network` declares one policy for all three:
|
|
4
|
+
//
|
|
5
|
+
// - "none": sealed. Every path runs confined with no network and none of the host's files: bash behind a probed shell
|
|
6
|
+
// or in the arm's container, declared commands and graders in the arm's container. A sealed arm that would run
|
|
7
|
+
// declared commands or graders on the host is refused at load.
|
|
8
|
+
// - "host": declared unsealed. Declared commands and graders run on the host as the user, with the network and the
|
|
9
|
+
// user's files, including the provider login on disk.
|
|
10
|
+
//
|
|
11
|
+
// Without a declaration, the arm gets what its paths allow: no network when every path that exists is confined,
|
|
12
|
+
// otherwise the host's. Each run records what held as `sealing`. The subject's host process itself (Pi or Claude Code)
|
|
13
|
+
// always keeps the network, which it needs to reach the model, and reads only its copied login.
|
|
14
|
+
|
|
15
|
+
import { SHELL_TOOLS } from "../guard-rules.mjs";
|
|
16
|
+
|
|
17
|
+
const POLICIES = new Set(["none", "host"]);
|
|
18
|
+
|
|
19
|
+
/** @param {(code: string, message: string) => never} fail */
|
|
20
|
+
export function validateNetwork(value, label, fail) {
|
|
21
|
+
if (!POLICIES.has(value)) fail("invalid-definition", `${label} must be "none" (sealed) or "host"`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Where each path that runs subject-written code runs for one arm, from its resolved variant: `bash` is "container",
|
|
26
|
+
* "shell" or null (no bash), and `commands` and `graders` are "container", "host" or null (none declared).
|
|
27
|
+
*
|
|
28
|
+
* @param {any} group the group as validated (tools, commands, expectations)
|
|
29
|
+
* @param {string} arm
|
|
30
|
+
* @param {any} shell the arm's resolved shell, or undefined
|
|
31
|
+
*/
|
|
32
|
+
export function sealPaths(group, arm, shell) {
|
|
33
|
+
const container = shell?.container !== undefined;
|
|
34
|
+
const hasCommands = (group.commands?.length ?? 0) > 0;
|
|
35
|
+
const hasGraders = (group.expectations ?? []).some(
|
|
36
|
+
(expectation) => expectation.kind === "command" && expectation.variant === arm,
|
|
37
|
+
);
|
|
38
|
+
return {
|
|
39
|
+
// Freeflow's background commands run in the same shell as bash, in the container (guard-rules.mjs).
|
|
40
|
+
bash: (group.tools ?? []).some((tool) => SHELL_TOOLS.has(tool)) ? (container ? "container" : "shell") : null,
|
|
41
|
+
commands: hasCommands ? (container ? "container" : "host") : null,
|
|
42
|
+
graders: hasGraders ? (container ? "container" : "host") : null,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The arm's sealing record: the network subject-written code actually had (`none` only when every path is confined),
|
|
48
|
+
* the declared policy (null when the definition declares none), where each path runs, and whether the provider
|
|
49
|
+
* login on disk was out of reach of subject-written code. With bash, the login is "out of reach" only when the
|
|
50
|
+
* arm's confinement probe of a marker in the host's login directory held; otherwise it is "not shown out of reach"
|
|
51
|
+
* ("not probed" before the probes run).
|
|
52
|
+
*
|
|
53
|
+
* @param {{ bash: string | null, commands: string | null, graders: string | null }} paths
|
|
54
|
+
* @param {string | null | undefined} declared
|
|
55
|
+
* @param {any} [confinement] the arm's confinement record, from its shell probes
|
|
56
|
+
*/
|
|
57
|
+
export function sealing(paths, declared, confinement = undefined) {
|
|
58
|
+
const onHost = [paths.commands, paths.graders].includes("host");
|
|
59
|
+
return {
|
|
60
|
+
network: onHost ? "host" : "none",
|
|
61
|
+
declared: declared ?? null,
|
|
62
|
+
...paths,
|
|
63
|
+
login: loginReach(paths, onHost, confinement),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function loginReach(paths, onHost, confinement) {
|
|
68
|
+
if (onHost) return "reachable from code run on the host";
|
|
69
|
+
if (paths.bash === null) return "out of reach";
|
|
70
|
+
if (confinement === undefined) return "not probed";
|
|
71
|
+
const probe = (confinement.probes ?? []).find((entry) => entry.id === "read-login");
|
|
72
|
+
return probe?.held === true ? "out of reach" : "not shown out of reach";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** One arm's sealing record from a loaded group (its resolved variant and declared configuration). */
|
|
76
|
+
export function armSealing(group, arm, confinement = undefined) {
|
|
77
|
+
return sealing(
|
|
78
|
+
sealPaths(group, arm, group.variants?.[arm]?.shell),
|
|
79
|
+
group.configurations?.[arm]?.network,
|
|
80
|
+
confinement,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Why a declared "none" cannot hold for an arm, or null. */
|
|
85
|
+
export function sealProblem(arm, paths, declared) {
|
|
86
|
+
if (declared !== "none") return null;
|
|
87
|
+
const onHost = ["commands", "graders"].filter((key) => paths[key] === "host");
|
|
88
|
+
if (onHost.length === 0) return null;
|
|
89
|
+
const what = onHost.map((key) => (key === "commands" ? "declared commands" : "command graders")).join(" and ");
|
|
90
|
+
return `arm ${arm} declares network "none", but its ${what} would run on the host; declare a container environment, or network "host"`;
|
|
91
|
+
}
|