@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
|
@@ -30,7 +30,14 @@ export async function prepareFixture({ declaredPath, root, groupDirectory }) {
|
|
|
30
30
|
const files = await fingerprintDirectory(sourcePath);
|
|
31
31
|
const snapshotPath = path.join(groupDirectory, "resources", "fixture");
|
|
32
32
|
await mkdir(path.dirname(snapshotPath), { recursive: true });
|
|
33
|
-
|
|
33
|
+
// Links are copied as they are: Node otherwise rewrites a relative link into an absolute one into the source.
|
|
34
|
+
await cp(sourcePath, snapshotPath, {
|
|
35
|
+
recursive: true,
|
|
36
|
+
dereference: false,
|
|
37
|
+
verbatimSymlinks: true,
|
|
38
|
+
errorOnExist: true,
|
|
39
|
+
force: false,
|
|
40
|
+
});
|
|
34
41
|
const snapshotFiles = await fingerprintDirectory(await realpath(snapshotPath));
|
|
35
42
|
const sourceFilesAfter = await fingerprintDirectory(sourcePath);
|
|
36
43
|
if (
|
|
@@ -57,11 +64,14 @@ export async function materializeFixture({ fixture, workspace }) {
|
|
|
57
64
|
await cp(fixture.snapshotPath, workspace, {
|
|
58
65
|
recursive: true,
|
|
59
66
|
dereference: false,
|
|
67
|
+
verbatimSymlinks: true,
|
|
60
68
|
errorOnExist: true,
|
|
61
69
|
force: false,
|
|
62
70
|
});
|
|
63
71
|
const materializedPath = await realpath(workspace);
|
|
64
|
-
|
|
72
|
+
// A workspace may hold links a subject made (an earlier stage's workspace), recorded rather than followed; a
|
|
73
|
+
// declared fixture's links were already checked when it was snapshotted.
|
|
74
|
+
const materializedFiles = await fingerprintDirectory(materializedPath, { links: "record" });
|
|
65
75
|
if (JSON.stringify(materializedFiles) !== JSON.stringify(fixture.files)) {
|
|
66
76
|
throw new Error(`declared fixture changed while it was being copied: ${fixture.declaredPath}`);
|
|
67
77
|
}
|
|
@@ -110,6 +120,7 @@ export async function prepareRuntime({ runtime, root, groupDirectory }) {
|
|
|
110
120
|
return {
|
|
111
121
|
host: runtime.host,
|
|
112
122
|
session: runtime.session,
|
|
123
|
+
prompt: runtime.prompt ?? "isolated",
|
|
113
124
|
environment: { ...runtime.environment },
|
|
114
125
|
extensions: bundles,
|
|
115
126
|
};
|
|
@@ -125,6 +136,7 @@ export async function materializeRuntime({ runtime, variantDirectory }) {
|
|
|
125
136
|
await cp(bundle.root, destination, {
|
|
126
137
|
recursive: true,
|
|
127
138
|
dereference: false,
|
|
139
|
+
verbatimSymlinks: true,
|
|
128
140
|
errorOnExist: true,
|
|
129
141
|
force: false,
|
|
130
142
|
});
|
|
@@ -145,6 +157,7 @@ export async function materializeRuntime({ runtime, variantDirectory }) {
|
|
|
145
157
|
return {
|
|
146
158
|
host: runtime.host,
|
|
147
159
|
session: runtime.session,
|
|
160
|
+
prompt: runtime.prompt ?? "isolated",
|
|
148
161
|
environment: { ...runtime.environment },
|
|
149
162
|
extensions: bundles,
|
|
150
163
|
};
|
|
@@ -226,13 +239,84 @@ export async function materializeEnvironment({ environment, root, variantDirecto
|
|
|
226
239
|
};
|
|
227
240
|
}
|
|
228
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Snapshots each of the group's two arms' target skill into `destinationRoot/<arm>` and checks that the two differ
|
|
244
|
+
* only in the frontmatter description: every other file, and SKILL.md outside its description, must be
|
|
245
|
+
* byte-identical. Throws with the first difference found.
|
|
246
|
+
*/
|
|
247
|
+
export async function verifyDescriptionOnlyRevision({ variants, root, destinationRoot }) {
|
|
248
|
+
const canonicalRoot = await realpath(root);
|
|
249
|
+
const snapshots = {};
|
|
250
|
+
const [reference, revised] = Object.keys(variants);
|
|
251
|
+
for (const variant of [reference, revised]) {
|
|
252
|
+
const environment = variants[variant];
|
|
253
|
+
const declaredPath = environment.skills[environment.target];
|
|
254
|
+
const destination = path.join(destinationRoot, variant);
|
|
255
|
+
if (environment.source.kind === "working-tree") {
|
|
256
|
+
snapshots[variant] = await snapshotWorkingTreeResource({
|
|
257
|
+
canonicalRoot,
|
|
258
|
+
declaredPath,
|
|
259
|
+
destination,
|
|
260
|
+
requireDirectory: true,
|
|
261
|
+
label: "skill",
|
|
262
|
+
});
|
|
263
|
+
} else {
|
|
264
|
+
const git = await prepareGitSource(canonicalRoot, environment.source.ref);
|
|
265
|
+
snapshots[variant] = await snapshotGitResource({
|
|
266
|
+
...git,
|
|
267
|
+
declaredPath,
|
|
268
|
+
destination,
|
|
269
|
+
requireDirectory: true,
|
|
270
|
+
label: "skill",
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const baselineFiles = new Map(snapshots[reference].files.map((file) => [file.path, file]));
|
|
275
|
+
const candidateFiles = new Map(snapshots[revised].files.map((file) => [file.path, file]));
|
|
276
|
+
for (const filePath of new Set([...baselineFiles.keys(), ...candidateFiles.keys()])) {
|
|
277
|
+
if (filePath === "SKILL.md") continue;
|
|
278
|
+
const baselineFile = baselineFiles.get(filePath);
|
|
279
|
+
const candidateFile = candidateFiles.get(filePath);
|
|
280
|
+
if (!baselineFile || !candidateFile || baselineFile.sha256 !== candidateFile.sha256) {
|
|
281
|
+
throw new Error(`description-only revision changes ${filePath} outside SKILL.md`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const [baselineSkill, candidateSkill] = await Promise.all(
|
|
285
|
+
[snapshots[reference], snapshots[revised]].map((snapshot) =>
|
|
286
|
+
readFile(path.join(snapshot.path, "SKILL.md"), "utf8"),
|
|
287
|
+
),
|
|
288
|
+
);
|
|
289
|
+
if (withoutDescription(baselineSkill) !== withoutDescription(candidateSkill)) {
|
|
290
|
+
throw new Error("description-only revision changes SKILL.md outside its frontmatter description");
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Removes the frontmatter `description` entry (one line, or a block scalar with its indented continuation).
|
|
295
|
+
function withoutDescription(text) {
|
|
296
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---(\r?\n|$)/.exec(text);
|
|
297
|
+
if (!match) return text;
|
|
298
|
+
const kept = [];
|
|
299
|
+
let skipping = false;
|
|
300
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
301
|
+
if (/^description\s*:/.test(line)) {
|
|
302
|
+
skipping = true;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (skipping && /^\s+\S/.test(line)) continue;
|
|
306
|
+
skipping = false;
|
|
307
|
+
kept.push(line);
|
|
308
|
+
}
|
|
309
|
+
return `---\n${kept.join("\n")}\n---${match[2]}${text.slice(match[0].length)}`;
|
|
310
|
+
}
|
|
311
|
+
|
|
229
312
|
export async function verifyEnvironment(environment) {
|
|
230
313
|
for (const [label, resources] of [
|
|
231
314
|
["skill", environment.skills],
|
|
232
315
|
["context", environment.context],
|
|
233
316
|
]) {
|
|
234
317
|
for (const resource of resources) {
|
|
235
|
-
|
|
318
|
+
// An earlier stage's delivered work may hold links its subject made; it is checked the way it was recorded.
|
|
319
|
+
const current = await fingerprintDirectory(resource.path, { links: resource.links ?? "strict" });
|
|
236
320
|
if (JSON.stringify(current) !== JSON.stringify(resource.files)) {
|
|
237
321
|
throw new Error(`declared ${label} changed during subject execution: ${resource.declaredPath}`);
|
|
238
322
|
}
|
|
@@ -251,9 +335,12 @@ export async function snapshotWorkspace({ workspace, variantDirectory, turn, bef
|
|
|
251
335
|
await rm(snapshotPath, { recursive: true, force: true });
|
|
252
336
|
await mkdir(path.dirname(snapshotPath), { recursive: true });
|
|
253
337
|
|
|
254
|
-
const sourceBefore = await fingerprintDirectory(workspace);
|
|
338
|
+
const sourceBefore = await fingerprintDirectory(workspace, WORKSPACE);
|
|
255
339
|
await cp(workspace, snapshotPath, { recursive: true, force: false, errorOnExist: true, verbatimSymlinks: true });
|
|
256
|
-
const [sourceAfter, files] = await Promise.all([
|
|
340
|
+
const [sourceAfter, files] = await Promise.all([
|
|
341
|
+
fingerprintDirectory(workspace, WORKSPACE),
|
|
342
|
+
fingerprintDirectory(snapshotPath, WORKSPACE),
|
|
343
|
+
]);
|
|
257
344
|
if (
|
|
258
345
|
JSON.stringify(sourceBefore) !== JSON.stringify(sourceAfter) ||
|
|
259
346
|
JSON.stringify(sourceAfter) !== JSON.stringify(files)
|
|
@@ -263,10 +350,22 @@ export async function snapshotWorkspace({ workspace, variantDirectory, turn, bef
|
|
|
263
350
|
return { path: snapshotPath, files, changes: workspaceChanges(before, files) };
|
|
264
351
|
}
|
|
265
352
|
|
|
266
|
-
|
|
353
|
+
/** How a subject's workspace is fingerprinted: its links are the subject's own work, recorded and never followed. */
|
|
354
|
+
export const WORKSPACE = Object.freeze({ links: "record" });
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Every entry under `root`, sorted: files and links with a SHA-256, directories, and each link's target. A declared
|
|
358
|
+
* resource (`links: "strict"`, the default) may hold only relative links that stay inside it. A subject's workspace
|
|
359
|
+
* (`links: "record"`) keeps any link the subject made, such as one an installer pointed at the container's
|
|
360
|
+
* workdir: it is recorded with its target, as a link, and nothing outside is ever read through it.
|
|
361
|
+
*
|
|
362
|
+
* @param {string} root
|
|
363
|
+
* @param {{ links?: "strict" | "record" }} [options]
|
|
364
|
+
*/
|
|
365
|
+
export async function fingerprintDirectory(root, { links = "strict" } = {}) {
|
|
267
366
|
const canonicalRoot = await realpath(root);
|
|
268
367
|
const files = [];
|
|
269
|
-
await walk(canonicalRoot, canonicalRoot, files);
|
|
368
|
+
await walk(canonicalRoot, canonicalRoot, files, links);
|
|
270
369
|
return files.sort((left, right) => comparePaths(left.path, right.path));
|
|
271
370
|
}
|
|
272
371
|
|
|
@@ -302,7 +401,13 @@ async function snapshotWorkingTreeResource({ canonicalRoot, declaredPath, destin
|
|
|
302
401
|
: [{ path: path.basename(sourcePath), type: "file", sha256: sha256(await readFile(sourcePath)) }];
|
|
303
402
|
if (sourceStat.isDirectory()) {
|
|
304
403
|
await mkdir(path.dirname(destination), { recursive: true });
|
|
305
|
-
await cp(sourcePath, destination, {
|
|
404
|
+
await cp(sourcePath, destination, {
|
|
405
|
+
recursive: true,
|
|
406
|
+
dereference: false,
|
|
407
|
+
verbatimSymlinks: true,
|
|
408
|
+
errorOnExist: true,
|
|
409
|
+
force: false,
|
|
410
|
+
});
|
|
306
411
|
} else {
|
|
307
412
|
await mkdir(destination, { recursive: true });
|
|
308
413
|
await copyFile(sourcePath, path.join(destination, path.basename(sourcePath)));
|
|
@@ -460,7 +565,7 @@ async function contextFiles(root, fingerprints) {
|
|
|
460
565
|
return files;
|
|
461
566
|
}
|
|
462
567
|
|
|
463
|
-
async function walk(root, directory, files) {
|
|
568
|
+
async function walk(root, directory, files, links) {
|
|
464
569
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
465
570
|
entries.sort((left, right) => comparePaths(left.name, right.name));
|
|
466
571
|
for (const entry of entries) {
|
|
@@ -469,14 +574,16 @@ async function walk(root, directory, files) {
|
|
|
469
574
|
const entryStat = await lstat(entryPath);
|
|
470
575
|
if (entryStat.isSymbolicLink()) {
|
|
471
576
|
const target = await readlink(entryPath);
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
577
|
+
if (links !== "record") {
|
|
578
|
+
const canonicalTarget = await realpath(entryPath).catch(() => null);
|
|
579
|
+
if (path.isAbsolute(target) || canonicalTarget === null || !isContained(root, canonicalTarget)) {
|
|
580
|
+
throw new Error(`resource symlink escapes its declared root: ${relativePath}`);
|
|
581
|
+
}
|
|
475
582
|
}
|
|
476
|
-
files.push({ path: relativePath, type: "symlink", sha256: sha256(`symlink\0${target}`) });
|
|
583
|
+
files.push({ path: relativePath, type: "symlink", sha256: sha256(`symlink\0${target}`), target });
|
|
477
584
|
} else if (entryStat.isDirectory()) {
|
|
478
585
|
files.push({ path: relativePath, type: "directory", sha256: sha256("directory") });
|
|
479
|
-
await walk(root, entryPath, files);
|
|
586
|
+
await walk(root, entryPath, files, links);
|
|
480
587
|
} else if (entryStat.isFile()) {
|
|
481
588
|
files.push({ path: relativePath, type: "file", sha256: sha256(await readFile(entryPath)) });
|
|
482
589
|
} else {
|
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
// The transcript scan's reading of a bash command: its simple commands, each as its words, and the network targets
|
|
2
|
+
// they reach for. It is a heuristic over shell text, not a shell: it follows quotes, `bash -c` strings, command
|
|
3
|
+
// substitutions and heredocs fed to a shell, and expands variables assigned a literal earlier in the same command.
|
|
4
|
+
// A target it cannot resolve (a variable read from a file) is not shown to be external, so it is not reported.
|
|
5
|
+
|
|
6
|
+
const SHELLS = new Set(["bash", "sh", "zsh", "dash", "ksh"]);
|
|
7
|
+
// Programs that only run the program after them.
|
|
8
|
+
const WRAPPERS = new Set(["sudo", "nohup", "command", "exec", "time", "nice", "stdbuf", "caffeinate"]);
|
|
9
|
+
const MAX_DEPTH = 4;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The simple commands of a shell command line, each `{ words, bodies }`: its words with quotes removed, and the
|
|
13
|
+
* text of each heredoc fed to it. A string run by a shell (`bash -c`), a command substitution and a heredoc fed to a
|
|
14
|
+
* shell are parsed as commands too; a heredoc fed to anything else (a script for `node`, a JSON payload) is data.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} text
|
|
17
|
+
* @returns {{ words: string[], bodies: string[] }[]}
|
|
18
|
+
*/
|
|
19
|
+
export function simpleCommands(text, depth = 0) {
|
|
20
|
+
const commands = [];
|
|
21
|
+
const nested = [];
|
|
22
|
+
let current = { words: [], bodies: [] };
|
|
23
|
+
let word = null;
|
|
24
|
+
let heredocs = [];
|
|
25
|
+
const endWord = () => {
|
|
26
|
+
if (word !== null) current.words.push(word);
|
|
27
|
+
word = null;
|
|
28
|
+
};
|
|
29
|
+
const endCommand = () => {
|
|
30
|
+
endWord();
|
|
31
|
+
if (current.words.length > 0) commands.push(current);
|
|
32
|
+
current = { words: [], bodies: [] };
|
|
33
|
+
};
|
|
34
|
+
let index = 0;
|
|
35
|
+
while (index < text.length) {
|
|
36
|
+
const char = text[index];
|
|
37
|
+
if (char === "\\") {
|
|
38
|
+
if (text[index + 1] !== "\n") word = (word ?? "") + (text[index + 1] ?? "");
|
|
39
|
+
index += 2;
|
|
40
|
+
} else if (char === "'") {
|
|
41
|
+
const end = text.indexOf("'", index + 1);
|
|
42
|
+
const stop = end === -1 ? text.length : end;
|
|
43
|
+
word = (word ?? "") + text.slice(index + 1, stop);
|
|
44
|
+
index = stop + 1;
|
|
45
|
+
} else if (char === '"') {
|
|
46
|
+
let cursor = index + 1;
|
|
47
|
+
let value = "";
|
|
48
|
+
while (cursor < text.length && text[cursor] !== '"') {
|
|
49
|
+
if (text[cursor] === "\\" && cursor + 1 < text.length) {
|
|
50
|
+
value += text[cursor + 1];
|
|
51
|
+
cursor += 2;
|
|
52
|
+
} else {
|
|
53
|
+
value += text[cursor];
|
|
54
|
+
cursor += 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
word = (word ?? "") + value;
|
|
58
|
+
index = cursor + 1;
|
|
59
|
+
} else if (char === "$" && text[index + 1] === "(" && text[index + 2] !== "(") {
|
|
60
|
+
const end = closing(text, index + 1);
|
|
61
|
+
nested.push(text.slice(index + 2, end));
|
|
62
|
+
word = (word ?? "") + text.slice(index, end + 1);
|
|
63
|
+
index = end + 1;
|
|
64
|
+
} else if (char === "`") {
|
|
65
|
+
const end = text.indexOf("`", index + 1);
|
|
66
|
+
const stop = end === -1 ? text.length : end;
|
|
67
|
+
nested.push(text.slice(index + 1, stop));
|
|
68
|
+
word = (word ?? "") + text.slice(index, stop + 1);
|
|
69
|
+
index = stop + 1;
|
|
70
|
+
} else if (char === "#" && word === null) {
|
|
71
|
+
const end = text.indexOf("\n", index);
|
|
72
|
+
index = end === -1 ? text.length : end;
|
|
73
|
+
} else if (char === "<" && text[index + 1] === "<" && text[index + 2] !== "<") {
|
|
74
|
+
endWord();
|
|
75
|
+
let cursor = index + 2;
|
|
76
|
+
const strip = text[cursor] === "-";
|
|
77
|
+
if (strip) cursor += 1;
|
|
78
|
+
while (text[cursor] === " " || text[cursor] === "\t") cursor += 1;
|
|
79
|
+
let delimiter = "";
|
|
80
|
+
while (cursor < text.length && !/[\s;&|<>()]/.test(text[cursor])) {
|
|
81
|
+
if (text[cursor] !== "'" && text[cursor] !== '"' && text[cursor] !== "\\") delimiter += text[cursor];
|
|
82
|
+
cursor += 1;
|
|
83
|
+
}
|
|
84
|
+
heredocs.push({ delimiter, strip, owner: current });
|
|
85
|
+
index = cursor;
|
|
86
|
+
} else if (char === "<" || char === ">") {
|
|
87
|
+
endWord();
|
|
88
|
+
index += 1;
|
|
89
|
+
// `2>&1`, `>&2`: a descriptor, not a command separator.
|
|
90
|
+
if (text[index] === "&") index += 1;
|
|
91
|
+
while (text[index] === ">" || text[index] === "|") index += 1;
|
|
92
|
+
} else if (char === "\n") {
|
|
93
|
+
endCommand();
|
|
94
|
+
index += 1;
|
|
95
|
+
for (const heredoc of heredocs) {
|
|
96
|
+
const body = [];
|
|
97
|
+
while (index < text.length) {
|
|
98
|
+
const end = text.indexOf("\n", index);
|
|
99
|
+
const line = text.slice(index, end === -1 ? text.length : end);
|
|
100
|
+
index = end === -1 ? text.length : end + 1;
|
|
101
|
+
if ((heredoc.strip ? line.replace(/^\t+/, "") : line) === heredoc.delimiter) break;
|
|
102
|
+
body.push(line);
|
|
103
|
+
}
|
|
104
|
+
heredoc.owner.bodies.push(body.join("\n"));
|
|
105
|
+
}
|
|
106
|
+
heredocs = [];
|
|
107
|
+
} else if (";&|()".includes(char)) {
|
|
108
|
+
endCommand();
|
|
109
|
+
index += 1;
|
|
110
|
+
} else if (char === " " || char === "\t" || char === "\r") {
|
|
111
|
+
endWord();
|
|
112
|
+
index += 1;
|
|
113
|
+
} else {
|
|
114
|
+
word = (word ?? "") + char;
|
|
115
|
+
index += 1;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
endCommand();
|
|
119
|
+
if (depth >= MAX_DEPTH) return commands;
|
|
120
|
+
const all = [];
|
|
121
|
+
for (const command of commands) {
|
|
122
|
+
all.push(command);
|
|
123
|
+
const { name, args } = program(command.words);
|
|
124
|
+
if (SHELLS.has(name)) {
|
|
125
|
+
const flag = args.findIndex((arg) => /^-[a-z]*c[a-z]*$/.test(arg));
|
|
126
|
+
if (flag !== -1 && typeof args[flag + 1] === "string") all.push(...simpleCommands(args[flag + 1], depth + 1));
|
|
127
|
+
for (const body of command.bodies) all.push(...simpleCommands(body, depth + 1));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const inner of nested) all.push(...simpleCommands(inner, depth + 1));
|
|
131
|
+
return all;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The index of the parenthesis that closes the one at `open`, or the text's end. */
|
|
135
|
+
function closing(text, open) {
|
|
136
|
+
let depth = 0;
|
|
137
|
+
for (let index = open; index < text.length; index += 1) {
|
|
138
|
+
if (text[index] === "(") depth += 1;
|
|
139
|
+
else if (text[index] === ")") {
|
|
140
|
+
depth -= 1;
|
|
141
|
+
if (depth === 0) return index;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return text.length;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const ASSIGNMENT = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/s;
|
|
148
|
+
|
|
149
|
+
/** The program a simple command runs and its arguments: leading assignments and wrappers (`sudo`, `env`) skipped. */
|
|
150
|
+
export function program(words) {
|
|
151
|
+
let index = 0;
|
|
152
|
+
while (index < words.length) {
|
|
153
|
+
const word = words[index];
|
|
154
|
+
if (ASSIGNMENT.test(word)) {
|
|
155
|
+
index += 1;
|
|
156
|
+
} else if (WRAPPERS.has(word)) {
|
|
157
|
+
index += 1;
|
|
158
|
+
while (index < words.length && words[index].startsWith("-")) index += 1;
|
|
159
|
+
} else if (word === "env") {
|
|
160
|
+
index += 1;
|
|
161
|
+
while (index < words.length && (words[index].startsWith("-") || ASSIGNMENT.test(words[index]))) index += 1;
|
|
162
|
+
} else if (word === "timeout") {
|
|
163
|
+
index += 1;
|
|
164
|
+
while (index < words.length && words[index].startsWith("-")) index += 1;
|
|
165
|
+
index += 1;
|
|
166
|
+
} else {
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const name = (words[index] ?? "").split("/").at(-1);
|
|
171
|
+
return { name, args: words.slice(index + 1) };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Variables a command line assigns a literal, in order: `NAME=value` and `export NAME=value`. */
|
|
175
|
+
export function assignments(commands) {
|
|
176
|
+
const values = new Map();
|
|
177
|
+
for (const { words } of commands) {
|
|
178
|
+
for (const word of words[0] === "export" || words[0] === "local" ? words.slice(1) : words) {
|
|
179
|
+
const match = ASSIGNMENT.exec(word);
|
|
180
|
+
if (match === null) break;
|
|
181
|
+
if (!match[2].includes("$(") && !match[2].includes("`")) values.set(match[1], expand(match[2], values));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return values;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** `$NAME` and `${NAME}` replaced by their literal values; unknown variables are left as they are. */
|
|
188
|
+
export function expand(word, values) {
|
|
189
|
+
return word.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\$([A-Za-z_][A-Za-z0-9_]*)/g, (whole, braced, bare) => {
|
|
190
|
+
const value = values.get(braced ?? bare);
|
|
191
|
+
return value === undefined ? whole : value;
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Whether a host is this machine or the container's own network: loopback, `0.0.0.0`, `*.localhost`, or a name with
|
|
197
|
+
* no dot (a container's service name, which a sealed run cannot resolve beyond its own network anyway).
|
|
198
|
+
*/
|
|
199
|
+
export function isInternalHost(host) {
|
|
200
|
+
const name = host.toLowerCase().replace(/^\[|\]$/g, "");
|
|
201
|
+
if (name === "localhost" || name.endsWith(".localhost") || name === "0.0.0.0" || name === "::1") return true;
|
|
202
|
+
if (/^127(?:\.\d{1,3}){3}$/.test(name)) return true;
|
|
203
|
+
return !name.includes(".") && !name.includes(":");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const SCHEME_URL = /^([a-z][a-z0-9+.-]*):\/\/(?:[^@/?#\s]*@)?(\[[^\]]+\]|[^:/?#\s]+)/i;
|
|
207
|
+
const BARE_HOST = /^(?:[^@/\s]+@)?([A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+)(?::\d+)?(?:[/?#].*)?$/s;
|
|
208
|
+
|
|
209
|
+
// A bare word ending like a file name is a file, not a host (`out.json`, `notes.md`).
|
|
210
|
+
const FILE_ENDING = /\.(?:json|md|txt|log|js|mjs|cjs|ts|html|css|csv|xml|ya?ml|sh|py|out|body|tmp|lock|tgz|gz|zip)$/i;
|
|
211
|
+
|
|
212
|
+
/** The host a URL (or, with `bare`, a `host[:port]/path`) names; null when it is neither or still has a variable. */
|
|
213
|
+
function hostOf(word, { bare = true } = {}) {
|
|
214
|
+
if (word.includes("$")) return null;
|
|
215
|
+
const url = SCHEME_URL.exec(word);
|
|
216
|
+
if (url !== null) return url[1].toLowerCase() === "file" ? null : url[2];
|
|
217
|
+
if (!bare) return null;
|
|
218
|
+
const host = BARE_HOST.exec(word)?.[1] ?? null;
|
|
219
|
+
if (host === null || FILE_ENDING.test(host)) return null;
|
|
220
|
+
// A version or other number (1.2.3) is no host; a dotted IPv4 address is.
|
|
221
|
+
if (/^\d+(?:\.\d+)*$/.test(host) && !/^\d+\.\d+\.\d+\.\d+$/.test(host)) return null;
|
|
222
|
+
return host;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Options of an HTTP client that take a value: the value is a header, payload, file or number, never the target.
|
|
226
|
+
const HTTP_VALUE_OPTIONS = new Set([
|
|
227
|
+
"-A",
|
|
228
|
+
"-b",
|
|
229
|
+
"-c",
|
|
230
|
+
"-C",
|
|
231
|
+
"-d",
|
|
232
|
+
"-D",
|
|
233
|
+
"-e",
|
|
234
|
+
"-E",
|
|
235
|
+
"-F",
|
|
236
|
+
"-H",
|
|
237
|
+
"-K",
|
|
238
|
+
"-m",
|
|
239
|
+
"-o",
|
|
240
|
+
"-O",
|
|
241
|
+
"-P",
|
|
242
|
+
"-r",
|
|
243
|
+
"-T",
|
|
244
|
+
"-t",
|
|
245
|
+
"-u",
|
|
246
|
+
"-U",
|
|
247
|
+
"-w",
|
|
248
|
+
"-X",
|
|
249
|
+
"-a",
|
|
250
|
+
"-i",
|
|
251
|
+
"--cacert",
|
|
252
|
+
"--cert",
|
|
253
|
+
"--config",
|
|
254
|
+
"--connect-timeout",
|
|
255
|
+
"--cookie",
|
|
256
|
+
"--cookie-jar",
|
|
257
|
+
"--data",
|
|
258
|
+
"--data-ascii",
|
|
259
|
+
"--data-binary",
|
|
260
|
+
"--data-raw",
|
|
261
|
+
"--data-urlencode",
|
|
262
|
+
"--dump-header",
|
|
263
|
+
"--form",
|
|
264
|
+
"--header",
|
|
265
|
+
"--key",
|
|
266
|
+
"--max-time",
|
|
267
|
+
"--output",
|
|
268
|
+
"--output-document",
|
|
269
|
+
"--post-data",
|
|
270
|
+
"--post-file",
|
|
271
|
+
"--referer",
|
|
272
|
+
"--request",
|
|
273
|
+
"--retry",
|
|
274
|
+
"--timeout",
|
|
275
|
+
"--tries",
|
|
276
|
+
"--upload-file",
|
|
277
|
+
"--user",
|
|
278
|
+
"--user-agent",
|
|
279
|
+
"--write-out",
|
|
280
|
+
]);
|
|
281
|
+
const SOCKET_VALUE_OPTIONS = new Set(["-w", "-p", "-s", "-x", "-X", "-i", "-q", "-O", "-I", "-T", "-V"]);
|
|
282
|
+
const SSH_VALUE_OPTIONS = new Set([
|
|
283
|
+
"-p",
|
|
284
|
+
"-P",
|
|
285
|
+
"-i",
|
|
286
|
+
"-l",
|
|
287
|
+
"-o",
|
|
288
|
+
"-F",
|
|
289
|
+
"-J",
|
|
290
|
+
"-b",
|
|
291
|
+
"-c",
|
|
292
|
+
"-D",
|
|
293
|
+
"-E",
|
|
294
|
+
"-e",
|
|
295
|
+
"-L",
|
|
296
|
+
"-m",
|
|
297
|
+
"-O",
|
|
298
|
+
"-Q",
|
|
299
|
+
"-R",
|
|
300
|
+
"-S",
|
|
301
|
+
"-W",
|
|
302
|
+
"-w",
|
|
303
|
+
"-B",
|
|
304
|
+
]);
|
|
305
|
+
|
|
306
|
+
/** Positional arguments, with the values of the given options skipped. */
|
|
307
|
+
function positionals(args, valueOptions) {
|
|
308
|
+
const found = [];
|
|
309
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
310
|
+
const arg = args[index];
|
|
311
|
+
if (arg === "--url") {
|
|
312
|
+
if (args[index + 1] !== undefined) found.push(args[index + 1]);
|
|
313
|
+
index += 1;
|
|
314
|
+
} else if (valueOptions.has(arg)) {
|
|
315
|
+
index += 1;
|
|
316
|
+
} else if (!arg.startsWith("-") || arg === "-") {
|
|
317
|
+
if (arg !== "-") found.push(arg);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return found;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const PACKAGE_MANAGERS = {
|
|
324
|
+
npm: ["install", "i", "add", "update", "up", "upgrade"],
|
|
325
|
+
pnpm: ["install", "i", "add", "update", "up"],
|
|
326
|
+
yarn: ["add", "upgrade", "up"],
|
|
327
|
+
bun: ["add", "install", "i"],
|
|
328
|
+
pip: ["install", "download"],
|
|
329
|
+
pip3: ["install", "download"],
|
|
330
|
+
gem: ["install"],
|
|
331
|
+
cargo: ["install", "add", "fetch"],
|
|
332
|
+
go: ["get", "install"],
|
|
333
|
+
};
|
|
334
|
+
const SYSTEM_PACKAGES = {
|
|
335
|
+
apt: ["install", "update", "upgrade", "dist-upgrade"],
|
|
336
|
+
"apt-get": ["install", "update", "upgrade", "dist-upgrade"],
|
|
337
|
+
apk: ["add", "update", "upgrade"],
|
|
338
|
+
brew: ["install", "update", "upgrade", "tap"],
|
|
339
|
+
yum: ["install", "update", "upgrade"],
|
|
340
|
+
dnf: ["install", "update", "upgrade"],
|
|
341
|
+
};
|
|
342
|
+
// Options of a package manager that take a value that is not a package.
|
|
343
|
+
const PACKAGE_VALUE_OPTIONS = new Set([
|
|
344
|
+
"--prefix",
|
|
345
|
+
"-C",
|
|
346
|
+
"--cache",
|
|
347
|
+
"--registry",
|
|
348
|
+
"--workspace",
|
|
349
|
+
"-w",
|
|
350
|
+
"--dir",
|
|
351
|
+
"--cwd",
|
|
352
|
+
"-t",
|
|
353
|
+
"--target",
|
|
354
|
+
"-r",
|
|
355
|
+
"--requirement",
|
|
356
|
+
"-c",
|
|
357
|
+
"--constraint",
|
|
358
|
+
"-i",
|
|
359
|
+
"--index-url",
|
|
360
|
+
"--extra-index-url",
|
|
361
|
+
"--root",
|
|
362
|
+
]);
|
|
363
|
+
|
|
364
|
+
/** Whether a package spec names something local (a path, a file: or link: spec, a tarball beside it). */
|
|
365
|
+
function localSpec(spec) {
|
|
366
|
+
return /^(?:\.|\/|~|file:|link:|workspace:|portal:)/.test(spec) || /\.(?:tgz|tar\.gz|whl|gem)$/.test(spec);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* The network attempts in one bash command: each `{ program, target }`. An HTTP client, socket tool, remote shell or
|
|
371
|
+
* Git remote counts only with an external target; a package install counts only when `sealed` (the run's network
|
|
372
|
+
* was "none") and it names a package from a registry, since installing the project's own dependencies or a local
|
|
373
|
+
* path is ordinary work. Code in the command (a `node -e` script, a heredoc) counts when it fetches an external URL.
|
|
374
|
+
*
|
|
375
|
+
* @param {string} command
|
|
376
|
+
* @param {{ sealed?: boolean }} [options]
|
|
377
|
+
*/
|
|
378
|
+
export function networkAttempts(command, { sealed = false } = {}) {
|
|
379
|
+
const commands = simpleCommands(command);
|
|
380
|
+
const values = assignments(commands);
|
|
381
|
+
const attempts = [];
|
|
382
|
+
const external = (name, words, options) => {
|
|
383
|
+
for (const word of words) {
|
|
384
|
+
const host = hostOf(expand(word, values), options);
|
|
385
|
+
if (host !== null && !isInternalHost(host)) attempts.push({ program: name, target: expand(word, values) });
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
for (const { words } of commands) {
|
|
389
|
+
const { name, args } = program(words);
|
|
390
|
+
if (["curl", "wget", "http", "https", "xh"].includes(name)) {
|
|
391
|
+
external(name, positionals(args, HTTP_VALUE_OPTIONS));
|
|
392
|
+
} else if (["nc", "ncat", "netcat", "telnet"].includes(name)) {
|
|
393
|
+
external(name, positionals(args, SOCKET_VALUE_OPTIONS).slice(0, 1), { bare: true });
|
|
394
|
+
} else if (name === "ssh") {
|
|
395
|
+
// A remote shell's host may be an alias with no dot, so only a loopback name is this machine.
|
|
396
|
+
const [host] = positionals(args, SSH_VALUE_OPTIONS);
|
|
397
|
+
const target = host === undefined ? null : expand(host, values).replace(/^[^@]*@/, "");
|
|
398
|
+
if (target !== null && !target.includes("$") && !isLoopbackName(target)) attempts.push({ program: name, target });
|
|
399
|
+
} else if (["scp", "sftp", "rsync"].includes(name)) {
|
|
400
|
+
for (const arg of positionals(args, SSH_VALUE_OPTIONS)) {
|
|
401
|
+
const value = expand(arg, values);
|
|
402
|
+
const remote = /^(?:[^@/\s]+@)?([^:/\s]+):/.exec(value);
|
|
403
|
+
const host =
|
|
404
|
+
SCHEME_URL.exec(value)?.[2] ?? (value.startsWith("/") || value.startsWith(".") ? null : remote?.[1]);
|
|
405
|
+
if (host && !host.includes("$") && !isLoopbackName(host)) attempts.push({ program: name, target: value });
|
|
406
|
+
}
|
|
407
|
+
} else if (name === "git") {
|
|
408
|
+
const rest = args.filter((arg, index) => !(args[index - 1] === "-C" || args[index - 1] === "-c"));
|
|
409
|
+
const subcommand = rest.find((arg) => !arg.startsWith("-") && !ASSIGNMENT.test(arg));
|
|
410
|
+
if (["clone", "fetch", "pull", "push", "ls-remote"].includes(subcommand)) {
|
|
411
|
+
const after = rest.slice(rest.indexOf(subcommand) + 1).filter((arg) => !arg.startsWith("-"));
|
|
412
|
+
const remotes = after
|
|
413
|
+
.map((arg) => expand(arg, values))
|
|
414
|
+
.map((arg) => SCHEME_URL.exec(arg)?.[2] ?? /^[^@/\s]+@([^:/\s]+):/.exec(arg)?.[1] ?? null);
|
|
415
|
+
const named = remotes.filter((host) => host !== null);
|
|
416
|
+
if (named.some((host) => !isLoopbackName(host))) {
|
|
417
|
+
attempts.push({ program: `git ${subcommand}`, target: after.join(" ") });
|
|
418
|
+
} else if (named.length === 0 && subcommand !== "clone" && after.every((arg) => !/^[./~]/.test(arg))) {
|
|
419
|
+
// A remote by name (origin): whatever it points at is reached.
|
|
420
|
+
attempts.push({ program: `git ${subcommand}`, target: after[0] ?? "the default remote" });
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
} else if (sealed && (name in PACKAGE_MANAGERS || name in SYSTEM_PACKAGES || isPythonPip(name, args))) {
|
|
424
|
+
const pip = isPythonPip(name, args);
|
|
425
|
+
const effective = pip ? args.slice(args.indexOf("pip") + 1) : args;
|
|
426
|
+
const manager = pip ? "pip" : name;
|
|
427
|
+
const subcommand = effective.find((arg) => !arg.startsWith("-"));
|
|
428
|
+
if (effective.includes("--offline")) continue;
|
|
429
|
+
if (name in SYSTEM_PACKAGES) {
|
|
430
|
+
if (SYSTEM_PACKAGES[name].includes(subcommand)) attempts.push({ program: `${name} ${subcommand}`, target: "" });
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
if (!PACKAGE_MANAGERS[manager].includes(subcommand)) continue;
|
|
434
|
+
const specs = positionals(effective.slice(effective.indexOf(subcommand) + 1), PACKAGE_VALUE_OPTIONS)
|
|
435
|
+
.map((spec) => expand(spec, values))
|
|
436
|
+
.filter((spec) => !spec.includes("$") && !localSpec(spec));
|
|
437
|
+
if (specs.length > 0) attempts.push({ program: `${manager} ${subcommand}`, target: specs.join(" ") });
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
// Code the command runs that fetches a literal URL: fetch(), http.get(), requests.get(), urlopen(), WebSocket.
|
|
441
|
+
const code =
|
|
442
|
+
/\b(?:fetch|https?\.(?:get|request)|axios(?:\.(?:get|post|put|request))?|got|requests\.(?:get|post|put|head)|urlopen|WebSocket)\(\s*(?:new URL\(\s*)?(["'`])([^"'`]*)\1/g;
|
|
443
|
+
for (const match of command.matchAll(code)) {
|
|
444
|
+
const host = hostOf(match[2].replace(/\$\{[^}]*\}/g, "x"), { bare: false });
|
|
445
|
+
if (host !== null && !isInternalHost(host)) attempts.push({ program: "code", target: match[2] });
|
|
446
|
+
}
|
|
447
|
+
return attempts;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function isLoopbackName(host) {
|
|
451
|
+
const name = host.toLowerCase().replace(/^\[|\]$/g, "");
|
|
452
|
+
return name === "localhost" || name.endsWith(".localhost") || name === "::1" || /^127(?:\.\d{1,3}){3}$/.test(name);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function isPythonPip(name, args) {
|
|
456
|
+
return /^python(?:3(?:\.\d+)?)?$/.test(name) && args[0] === "-m" && args[1] === "pip";
|
|
457
|
+
}
|