project-tiny-context-harness 0.2.84 → 0.6.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/README.md +163 -428
- package/assets/README.md +283 -455
- package/assets/README.zh-CN.md +199 -123
- package/assets/agents/AGENTS_CORE.md +54 -67
- package/assets/context_templates/context.toml +3 -0
- package/assets/context_templates/global.md +6 -6
- package/assets/github/harness.yml +2 -2
- package/assets/skills/context_development_engineer/SKILL.md +91 -107
- package/assets/skills/context_product_plan/SKILL.md +18 -28
- package/assets/skills/context_surface_contract/SKILL.md +35 -38
- package/assets/skills/context_uiux_design/SKILL.md +31 -30
- package/assets/skills/long-task-workflow/SKILL.md +76 -0
- package/assets/skills/long-task-workflow/agents/openai.yaml +4 -0
- package/assets/skills/long-task-workflow/references/authority-lifecycle.md +41 -0
- package/assets/skills/long-task-workflow/references/contract-authoring.md +62 -0
- package/assets/skills/long-task-workflow/references/evidence-design.md +45 -0
- package/assets/skills/normal-long-task/SKILL.md +12 -489
- package/assets/skills/source-plan-authoring/SKILL.md +379 -0
- package/assets/tools/validate_context.py +293 -127
- package/dist/cli.js +3 -1
- package/dist/commands/check-modularity.js +33 -10
- package/dist/commands/composite-campaign.d.ts +1 -0
- package/dist/commands/composite-campaign.js +8 -0
- package/dist/commands/composite-long-task.d.ts +1 -6
- package/dist/commands/composite-long-task.js +7 -157
- package/dist/commands/delivery-set.d.ts +1 -0
- package/dist/commands/delivery-set.js +8 -0
- package/dist/commands/disable.d.ts +1 -0
- package/dist/commands/disable.js +15 -0
- package/dist/commands/enable.d.ts +1 -0
- package/dist/commands/enable.js +14 -0
- package/dist/commands/export-context-args.js +13 -4
- package/dist/commands/export-context.js +18 -7
- package/dist/commands/index.js +42 -30
- package/dist/commands/init.js +12 -6
- package/dist/commands/long-task-authoring.d.ts +3 -0
- package/dist/commands/long-task-authoring.js +92 -0
- package/dist/commands/long-task-explain.d.ts +1 -0
- package/dist/commands/long-task-explain.js +52 -0
- package/dist/commands/long-task.d.ts +1 -0
- package/dist/commands/long-task.js +226 -0
- package/dist/commands/package-source.js +2 -2
- package/dist/commands/upgrade.js +10 -10
- package/dist/index.d.ts +11 -1
- package/dist/index.js +8 -0
- package/dist/lib/config.js +34 -8
- package/dist/lib/context-default-footprint.d.ts +16 -0
- package/dist/lib/context-default-footprint.js +107 -0
- package/dist/lib/context-export.js +105 -41
- package/dist/lib/context-graph-snapshot.d.ts +18 -0
- package/dist/lib/context-graph-snapshot.js +171 -0
- package/dist/lib/context-manifest-schema.d.ts +28 -0
- package/dist/lib/context-manifest-schema.js +150 -0
- package/dist/lib/context-manifest.js +28 -20
- package/dist/lib/context-templates.js +5 -5
- package/dist/lib/design-md.js +1 -1
- package/dist/lib/doctor.js +54 -1
- package/dist/lib/harness-root.js +25 -8
- package/dist/lib/init.js +6 -3
- package/dist/lib/legacy-managed-scan.js +23 -7
- package/dist/lib/legacy-sdlc-migration.js +21 -11
- package/dist/lib/long-task-acceptance-reference.d.ts +22 -0
- package/dist/lib/long-task-acceptance-reference.js +46 -0
- package/dist/lib/long-task-acceptance-shape.d.ts +5 -0
- package/dist/lib/long-task-acceptance-shape.js +131 -0
- package/dist/lib/long-task-activation-validation.d.ts +21 -0
- package/dist/lib/long-task-activation-validation.js +136 -0
- package/dist/lib/long-task-artifacts.d.ts +5 -0
- package/dist/lib/long-task-artifacts.js +34 -0
- package/dist/lib/long-task-assertions-v2.d.ts +22 -0
- package/dist/lib/long-task-assertions-v2.js +212 -0
- package/dist/lib/long-task-authoring-authority-preview.d.ts +5 -0
- package/dist/lib/long-task-authoring-authority-preview.js +85 -0
- package/dist/lib/long-task-authoring-preflight-diagnostics.d.ts +8 -0
- package/dist/lib/long-task-authoring-preflight-diagnostics.js +179 -0
- package/dist/lib/long-task-authoring-preflight-repair-order.d.ts +8 -0
- package/dist/lib/long-task-authoring-preflight-repair-order.js +95 -0
- package/dist/lib/long-task-authoring-preflight-types.d.ts +46 -0
- package/dist/lib/long-task-authoring-preflight-types.js +38 -0
- package/dist/lib/long-task-authoring-preflight.d.ts +3 -0
- package/dist/lib/long-task-authoring-preflight.js +44 -0
- package/dist/lib/long-task-authority-material-diff.d.ts +17 -0
- package/dist/lib/long-task-authority-material-diff.js +152 -0
- package/dist/lib/long-task-authority-materials.d.ts +7 -0
- package/dist/lib/long-task-authority-materials.js +83 -0
- package/dist/lib/long-task-authority-policy.d.ts +168 -0
- package/dist/lib/long-task-authority-policy.js +181 -0
- package/dist/lib/long-task-authority-revision-details.d.ts +19 -0
- package/dist/lib/long-task-authority-revision-details.js +192 -0
- package/dist/lib/long-task-authority-revision-enforcement.d.ts +3 -0
- package/dist/lib/long-task-authority-revision-enforcement.js +41 -0
- package/dist/lib/long-task-authority-revision-types.d.ts +50 -0
- package/dist/lib/long-task-authority-revision-types.js +1 -0
- package/dist/lib/long-task-authority-revision.d.ts +4 -0
- package/dist/lib/long-task-authority-revision.js +188 -0
- package/dist/lib/long-task-authority-transition-policy.d.ts +62 -0
- package/dist/lib/long-task-authority-transition-policy.js +62 -0
- package/dist/lib/long-task-authority-types.d.ts +96 -0
- package/dist/lib/long-task-authority-types.js +1 -0
- package/dist/lib/long-task-authority.d.ts +7 -0
- package/dist/lib/long-task-authority.js +142 -0
- package/dist/lib/long-task-boundary-check.d.ts +15 -0
- package/dist/lib/long-task-boundary-check.js +9 -0
- package/dist/lib/long-task-check-evidence-decoder.d.ts +3 -0
- package/dist/lib/long-task-check-evidence-decoder.js +74 -0
- package/dist/lib/long-task-check-execution-policy.d.ts +18 -0
- package/dist/lib/long-task-check-execution-policy.js +36 -0
- package/dist/lib/long-task-check-runner.d.ts +2 -0
- package/dist/lib/long-task-check-runner.js +181 -0
- package/dist/lib/long-task-check-shape.d.ts +2 -0
- package/dist/lib/long-task-check-shape.js +136 -0
- package/dist/lib/long-task-claim-definitions.d.ts +7 -0
- package/dist/lib/long-task-claim-definitions.js +78 -0
- package/dist/lib/long-task-claim-proof-policy.d.ts +4 -0
- package/dist/lib/long-task-claim-proof-policy.js +57 -0
- package/dist/lib/long-task-claims.d.ts +10 -0
- package/dist/lib/long-task-claims.js +187 -0
- package/dist/lib/long-task-context-authority-topology.d.ts +7 -0
- package/dist/lib/long-task-context-authority-topology.js +37 -0
- package/dist/lib/long-task-context-authority.d.ts +14 -0
- package/dist/lib/long-task-context-authority.js +66 -0
- package/dist/lib/long-task-contract-types.d.ts +203 -0
- package/dist/lib/long-task-contract-types.js +1 -0
- package/dist/lib/long-task-counterfactual-claim-policy.d.ts +10 -0
- package/dist/lib/long-task-counterfactual-claim-policy.js +57 -0
- package/dist/lib/long-task-counterfactual-sandbox.d.ts +6 -0
- package/dist/lib/long-task-counterfactual-sandbox.js +87 -0
- package/dist/lib/long-task-counterfactual-types.d.ts +24 -0
- package/dist/lib/long-task-counterfactual-types.js +1 -0
- package/dist/lib/long-task-delivery-compiler.d.ts +10 -0
- package/dist/lib/long-task-delivery-compiler.js +129 -0
- package/dist/lib/long-task-delivery-parser.d.ts +13 -0
- package/dist/lib/long-task-delivery-parser.js +90 -0
- package/dist/lib/long-task-delivery-preflight.d.ts +5 -0
- package/dist/lib/long-task-delivery-preflight.js +151 -0
- package/dist/lib/long-task-delivery-shape.d.ts +5 -0
- package/dist/lib/long-task-delivery-shape.js +5 -0
- package/dist/lib/long-task-delivery-types.d.ts +8 -0
- package/dist/lib/long-task-delivery-types.js +8 -0
- package/dist/lib/long-task-delivery-validation.d.ts +3 -0
- package/dist/lib/long-task-delivery-validation.js +175 -0
- package/dist/lib/long-task-evidence-adapter-policy.d.ts +4 -0
- package/dist/lib/long-task-evidence-adapter-policy.js +15 -0
- package/dist/lib/long-task-evidence-adapter-types.d.ts +1 -0
- package/dist/lib/long-task-evidence-adapter-types.js +1 -0
- package/dist/lib/long-task-evidence-findings.d.ts +5 -0
- package/dist/lib/long-task-evidence-findings.js +113 -0
- package/dist/lib/long-task-evidence-sensitivity-policy.d.ts +4 -0
- package/dist/lib/long-task-evidence-sensitivity-policy.js +98 -0
- package/dist/lib/long-task-evidence-v2.d.ts +5 -0
- package/dist/lib/long-task-evidence-v2.js +226 -0
- package/dist/lib/long-task-explain-acceptance-link.d.ts +46 -0
- package/dist/lib/long-task-explain-acceptance-link.js +96 -0
- package/dist/lib/long-task-explain-claim-links.d.ts +45 -0
- package/dist/lib/long-task-explain-claim-links.js +88 -0
- package/dist/lib/long-task-explain-source-links.d.ts +93 -0
- package/dist/lib/long-task-explain-source-links.js +51 -0
- package/dist/lib/long-task-final-v2.d.ts +2 -0
- package/dist/lib/long-task-final-v2.js +124 -0
- package/dist/lib/long-task-finding-context.d.ts +3 -0
- package/dist/lib/long-task-finding-context.js +63 -0
- package/dist/lib/long-task-freshness.d.ts +3 -0
- package/dist/lib/long-task-freshness.js +80 -0
- package/dist/lib/long-task-hook-install.d.ts +9 -0
- package/dist/lib/long-task-hook-install.js +184 -0
- package/dist/lib/long-task-hook-preflight.d.ts +7 -0
- package/dist/lib/long-task-hook-preflight.js +58 -0
- package/dist/lib/long-task-observation-ownership.d.ts +2 -0
- package/dist/lib/long-task-observation-ownership.js +22 -0
- package/dist/lib/long-task-outcome-parser.d.ts +4 -0
- package/dist/lib/long-task-outcome-parser.js +100 -0
- package/dist/lib/long-task-paths.d.ts +49 -0
- package/dist/lib/long-task-paths.js +418 -0
- package/dist/lib/long-task-playwright-counterfactual-policy.d.ts +7 -0
- package/dist/lib/long-task-playwright-counterfactual-policy.js +125 -0
- package/dist/lib/long-task-playwright-evidence.d.ts +6 -0
- package/dist/lib/long-task-playwright-evidence.js +244 -0
- package/dist/lib/long-task-product-shape.d.ts +6 -0
- package/dist/lib/long-task-product-shape.js +73 -0
- package/dist/lib/long-task-progress.d.ts +4 -0
- package/dist/lib/long-task-progress.js +114 -0
- package/dist/lib/long-task-protected-files.d.ts +1 -0
- package/dist/lib/long-task-protected-files.js +30 -0
- package/dist/lib/long-task-required-proof-surfaces.d.ts +2 -0
- package/dist/lib/long-task-required-proof-surfaces.js +13 -0
- package/dist/lib/long-task-requirement-shape.d.ts +2 -0
- package/dist/lib/long-task-requirement-shape.js +18 -0
- package/dist/lib/long-task-risk-surfaces.d.ts +2 -0
- package/dist/lib/long-task-risk-surfaces.js +60 -0
- package/dist/lib/long-task-risk-types.d.ts +5 -0
- package/dist/lib/long-task-risk-types.js +12 -0
- package/dist/lib/long-task-risk.d.ts +9 -0
- package/dist/lib/long-task-risk.js +132 -0
- package/dist/lib/long-task-root-shape.d.ts +4 -0
- package/dist/lib/long-task-root-shape.js +86 -0
- package/dist/lib/long-task-runner-environment.d.ts +7 -0
- package/dist/lib/long-task-runner-environment.js +57 -0
- package/dist/lib/long-task-runner-files.d.ts +3 -0
- package/dist/lib/long-task-runner-files.js +39 -0
- package/dist/lib/long-task-runner-freeze.d.ts +2 -0
- package/dist/lib/long-task-runner-freeze.js +168 -0
- package/dist/lib/long-task-runtime-types.d.ts +214 -0
- package/dist/lib/long-task-runtime-types.js +1 -0
- package/dist/lib/long-task-shape-primitives.d.ts +17 -0
- package/dist/lib/long-task-shape-primitives.js +81 -0
- package/dist/lib/long-task-source-authority-types.d.ts +13 -0
- package/dist/lib/long-task-source-authority-types.js +1 -0
- package/dist/lib/long-task-source-claim-validation.d.ts +5 -0
- package/dist/lib/long-task-source-claim-validation.js +96 -0
- package/dist/lib/long-task-source-continuity.d.ts +4 -0
- package/dist/lib/long-task-source-continuity.js +57 -0
- package/dist/lib/long-task-source-inventory.d.ts +2 -0
- package/dist/lib/long-task-source-inventory.js +21 -0
- package/dist/lib/long-task-source-item-parser.d.ts +3 -0
- package/dist/lib/long-task-source-item-parser.js +112 -0
- package/dist/lib/long-task-source-shape.d.ts +2 -0
- package/dist/lib/long-task-source-shape.js +56 -0
- package/dist/lib/long-task-source-target-continuity.d.ts +4 -0
- package/dist/lib/long-task-source-target-continuity.js +80 -0
- package/dist/lib/long-task-source-target-index.d.ts +12 -0
- package/dist/lib/long-task-source-target-index.js +76 -0
- package/dist/lib/long-task-source-validation.d.ts +3 -0
- package/dist/lib/long-task-source-validation.js +46 -0
- package/dist/lib/long-task-state.d.ts +91 -0
- package/dist/lib/long-task-state.js +611 -0
- package/dist/lib/long-task-status-projection.d.ts +21 -0
- package/dist/lib/long-task-status-projection.js +116 -0
- package/dist/lib/long-task-status-v2.d.ts +37 -0
- package/dist/lib/long-task-status-v2.js +306 -0
- package/dist/lib/long-task-technical-shape.d.ts +3 -0
- package/dist/lib/long-task-technical-shape.js +44 -0
- package/dist/lib/long-task-verifier-authority.d.ts +12 -0
- package/dist/lib/long-task-verifier-authority.js +47 -0
- package/dist/lib/long-task-verifier-identity.d.ts +2 -0
- package/dist/lib/long-task-verifier-identity.js +55 -0
- package/dist/lib/long-task-verifier-v2.d.ts +13 -0
- package/dist/lib/long-task-verifier-v2.js +266 -0
- package/dist/lib/long-task-workspace-runtime-types.d.ts +23 -0
- package/dist/lib/long-task-workspace-runtime-types.js +1 -0
- package/dist/lib/long-task-workspace.d.ts +25 -0
- package/dist/lib/long-task-workspace.js +366 -0
- package/dist/lib/managed-file.js +20 -5
- package/dist/lib/migrations.js +156 -32
- package/dist/lib/modularity.d.ts +27 -1
- package/dist/lib/modularity.js +499 -23
- package/dist/lib/package-json-config.js +14 -5
- package/dist/lib/package-source.js +15 -3
- package/dist/lib/profiles.d.ts +13 -0
- package/dist/lib/profiles.js +66 -0
- package/dist/lib/schema-guard.js +4 -2
- package/dist/lib/source-files.js +14 -7
- package/dist/lib/source-pack-classify.js +91 -19
- package/dist/lib/source-pack-config.js +25 -8
- package/dist/lib/source-pack-export.js +84 -25
- package/dist/lib/source-pack-manifest.js +18 -6
- package/dist/lib/source-pack-records.js +35 -11
- package/dist/lib/source-pack-render.js +47 -18
- package/dist/lib/stable-json.d.ts +2 -0
- package/dist/lib/stable-json.js +21 -0
- package/dist/lib/strict-codec.d.ts +4 -0
- package/dist/lib/strict-codec.js +65 -0
- package/dist/lib/sync-engine.js +76 -24
- package/dist/lib/types.d.ts +8 -1
- package/dist/lib/upgrade.js +4 -3
- package/dist/lib/validators.js +218 -191
- package/dist/long-task-hook.d.ts +2 -0
- package/dist/long-task-hook.js +55 -0
- package/dist/schemas/long-task-delivery-v2/long-task-delivery-v2.schema.json +340 -0
- package/dist/schemas/long-task-delivery-v2/long-task-outcomes-v2.schema.json +18 -0
- package/migrations/README.md +8 -3
- package/package.json +17 -6
- package/source-mappings.yaml +0 -3
- package/assets/protected-harness-baseline.json +0 -20
- package/assets/skills/composite-long-task-workflow/SKILL.md +0 -244
- package/assets/skills/composite-long-task-workflow/assets/execution-binding.template.md +0 -57
- package/assets/skills/composite-long-task-workflow/assets/goal-objective.template.md +0 -17
- package/assets/skills/composite-long-task-workflow/references/composite-long-task-workflow-protocol.md +0 -675
- package/dist/commands/superpowers.d.ts +0 -1
- package/dist/commands/superpowers.js +0 -8
- package/dist/lib/composite-long-task-renderer.d.ts +0 -12
- package/dist/lib/composite-long-task-renderer.js +0 -109
- package/dist/lib/plan-acceptance-artifacts.d.ts +0 -1
- package/dist/lib/plan-acceptance-artifacts.js +0 -220
- package/dist/lib/plan-acceptance-evidence.d.ts +0 -2
- package/dist/lib/plan-acceptance-evidence.js +0 -108
- package/dist/lib/plan-acceptance-json.d.ts +0 -15
- package/dist/lib/plan-acceptance-json.js +0 -133
- package/dist/lib/plan-acceptance-validator.d.ts +0 -2
- package/dist/lib/plan-acceptance-validator.js +0 -209
- package/dist/lib/plan-contract-validator.d.ts +0 -2
- package/dist/lib/plan-contract-validator.js +0 -127
- package/dist/lib/plan-validator-common.d.ts +0 -24
- package/dist/lib/plan-validator-common.js +0 -196
- package/dist/lib/superpowers-task-ac010.d.ts +0 -6
- package/dist/lib/superpowers-task-ac010.js +0 -26
- package/dist/lib/superpowers-task-assertion-normalizers.d.ts +0 -3
- package/dist/lib/superpowers-task-assertion-normalizers.js +0 -74
- package/dist/lib/superpowers-task-assertions.d.ts +0 -20
- package/dist/lib/superpowers-task-assertions.js +0 -257
- package/dist/lib/superpowers-task-attempt.d.ts +0 -4
- package/dist/lib/superpowers-task-attempt.js +0 -102
- package/dist/lib/superpowers-task-command-run-correlation.d.ts +0 -8
- package/dist/lib/superpowers-task-command-run-correlation.js +0 -103
- package/dist/lib/superpowers-task-command-specs.d.ts +0 -3
- package/dist/lib/superpowers-task-command-specs.js +0 -52
- package/dist/lib/superpowers-task-compile-diagnostics.d.ts +0 -5
- package/dist/lib/superpowers-task-compile-diagnostics.js +0 -20
- package/dist/lib/superpowers-task-compile-guards.d.ts +0 -2
- package/dist/lib/superpowers-task-compile-guards.js +0 -66
- package/dist/lib/superpowers-task-compile.d.ts +0 -6
- package/dist/lib/superpowers-task-compile.js +0 -133
- package/dist/lib/superpowers-task-completion-output.d.ts +0 -52
- package/dist/lib/superpowers-task-completion-output.js +0 -228
- package/dist/lib/superpowers-task-conformance.d.ts +0 -2
- package/dist/lib/superpowers-task-conformance.js +0 -24
- package/dist/lib/superpowers-task-contradictions.d.ts +0 -6
- package/dist/lib/superpowers-task-contradictions.js +0 -126
- package/dist/lib/superpowers-task-current-evidence.d.ts +0 -3
- package/dist/lib/superpowers-task-current-evidence.js +0 -176
- package/dist/lib/superpowers-task-delivery.d.ts +0 -4
- package/dist/lib/superpowers-task-delivery.js +0 -96
- package/dist/lib/superpowers-task-derive.d.ts +0 -14
- package/dist/lib/superpowers-task-derive.js +0 -382
- package/dist/lib/superpowers-task-events.d.ts +0 -1
- package/dist/lib/superpowers-task-events.js +0 -13
- package/dist/lib/superpowers-task-evidence-kernel.d.ts +0 -19
- package/dist/lib/superpowers-task-evidence-kernel.js +0 -347
- package/dist/lib/superpowers-task-evidence-records.d.ts +0 -2
- package/dist/lib/superpowers-task-evidence-records.js +0 -55
- package/dist/lib/superpowers-task-evidence.d.ts +0 -10
- package/dist/lib/superpowers-task-evidence.js +0 -147
- package/dist/lib/superpowers-task-fields.d.ts +0 -22
- package/dist/lib/superpowers-task-fields.js +0 -276
- package/dist/lib/superpowers-task-final-card.d.ts +0 -3
- package/dist/lib/superpowers-task-final-card.js +0 -24
- package/dist/lib/superpowers-task-gates.d.ts +0 -12
- package/dist/lib/superpowers-task-gates.js +0 -155
- package/dist/lib/superpowers-task-harness-drift.d.ts +0 -11
- package/dist/lib/superpowers-task-harness-drift.js +0 -90
- package/dist/lib/superpowers-task-next-slices.d.ts +0 -1
- package/dist/lib/superpowers-task-next-slices.js +0 -12
- package/dist/lib/superpowers-task-protected-baseline.d.ts +0 -10
- package/dist/lib/superpowers-task-protected-baseline.js +0 -66
- package/dist/lib/superpowers-task-source-compile.d.ts +0 -5
- package/dist/lib/superpowers-task-source-compile.js +0 -226
- package/dist/lib/superpowers-task-source-parser.d.ts +0 -23
- package/dist/lib/superpowers-task-source-parser.js +0 -218
- package/dist/lib/superpowers-task-state-schema.d.ts +0 -440
- package/dist/lib/superpowers-task-state-schema.js +0 -66
- package/dist/lib/superpowers-task-state-shape.d.ts +0 -3
- package/dist/lib/superpowers-task-state-shape.js +0 -50
- package/dist/lib/superpowers-task-state.d.ts +0 -16
- package/dist/lib/superpowers-task-state.js +0 -246
- package/dist/lib/superpowers-task-status.d.ts +0 -2
- package/dist/lib/superpowers-task-status.js +0 -24
- package/dist/lib/superpowers-task-under-specified.d.ts +0 -7
- package/dist/lib/superpowers-task-under-specified.js +0 -61
- package/dist/lib/superpowers-task-unregistered-evidence.d.ts +0 -11
- package/dist/lib/superpowers-task-unregistered-evidence.js +0 -72
- package/dist/lib/superpowers-task-validator.d.ts +0 -5
- package/dist/lib/superpowers-task-validator.js +0 -290
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { deliveryCompileFreshness } from "./long-task-freshness.js";
|
|
2
|
+
import { assertVerifierAuthorityCurrent } from "./long-task-freshness.js";
|
|
3
|
+
import { enrichCheckResultFindings, enrichFinding, } from "./long-task-finding-context.js";
|
|
4
|
+
import { executeCheckRunner } from "./long-task-check-runner.js";
|
|
5
|
+
import { evaluateCheckEvidence, evaluateGlobalCounterfactuals, evaluateOutcomeCounterfactuals, } from "./long-task-evidence-v2.js";
|
|
6
|
+
import { findScopeEscapes, matchesRepoPattern } from "./long-task-paths.js";
|
|
7
|
+
import { activeAuthorityIdentityMatches, loadActiveLongTaskAuthority, writeProgressRecord, } from "./long-task-state.js";
|
|
8
|
+
import { createProgressRecord } from "./long-task-progress.js";
|
|
9
|
+
import { changedWorkspacePaths, createWorkspaceSnapshot, repositoryRoot, } from "./long-task-workspace.js";
|
|
10
|
+
export async function verifyDeliveryContract(workdir, selection = {}) {
|
|
11
|
+
const repository = await repositoryRoot(process.cwd());
|
|
12
|
+
const active = (await loadActiveLongTaskAuthority(repository, { migrate_legacy: true })).authority;
|
|
13
|
+
if (!active)
|
|
14
|
+
throw new Error("active_task_missing");
|
|
15
|
+
if (active.workdir !== (await resolved(workdir)))
|
|
16
|
+
throw new Error("active_task_workdir_mismatch");
|
|
17
|
+
await assertVerifierAuthorityCurrent(repository, active.verifier_identity);
|
|
18
|
+
const compiled = active.authority_snapshot;
|
|
19
|
+
const expectedAuthority = {
|
|
20
|
+
task_id: active.task_id,
|
|
21
|
+
authority_revision: active.authority_revision,
|
|
22
|
+
compiled_identity: active.active_authority_identity,
|
|
23
|
+
worktree_identity: active.worktree_identity,
|
|
24
|
+
};
|
|
25
|
+
const selected = selectChecks(compiled, selection);
|
|
26
|
+
const snapshot = await createWorkspaceSnapshot(compiled.repository_root, compiled.workdir, `verify-${compiled.task.id}`);
|
|
27
|
+
try {
|
|
28
|
+
const run = await runDeliveryChecks(compiled, snapshot, selected, true);
|
|
29
|
+
const records = run.check_results.map((result) => {
|
|
30
|
+
const check = selected.find((item) => item.internal_id === result.internal_id);
|
|
31
|
+
if (!check)
|
|
32
|
+
throw new Error(`compiled_check_not_found:${result.internal_id}`);
|
|
33
|
+
return createProgressRecord(compiled, snapshot.manifest, check, result);
|
|
34
|
+
});
|
|
35
|
+
let authorityChanged = false;
|
|
36
|
+
try {
|
|
37
|
+
const current = (await loadActiveLongTaskAuthority(repository)).authority;
|
|
38
|
+
authorityChanged =
|
|
39
|
+
!current || !activeAuthorityIdentityMatches(current, expectedAuthority);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
authorityChanged = true;
|
|
43
|
+
}
|
|
44
|
+
if (authorityChanged) {
|
|
45
|
+
run.findings.push({
|
|
46
|
+
code: "active_authority_changed_during_verify",
|
|
47
|
+
outcome_key: null,
|
|
48
|
+
check_key: null,
|
|
49
|
+
message: "Active Authority changed while targeted verification was running.",
|
|
50
|
+
next_action: "Discard the stale progress and rerun verification against the active Authority Revision.",
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
schema_version: "long-task-targeted-progress-v2",
|
|
54
|
+
compiled_identity: compiled.compiled_identity,
|
|
55
|
+
snapshot_sha256: snapshot.manifest.snapshot_sha256,
|
|
56
|
+
acceptance_authorized: false,
|
|
57
|
+
selected_outcome: selection.outcome ?? null,
|
|
58
|
+
selected_check: selection.check ?? null,
|
|
59
|
+
updated_progress_records: [],
|
|
60
|
+
check_results: run.check_results,
|
|
61
|
+
findings: run.findings,
|
|
62
|
+
completed_at: new Date().toISOString(),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
await Promise.all(records.map((record) => writeProgressRecord(workdir, record)));
|
|
66
|
+
return {
|
|
67
|
+
schema_version: "long-task-targeted-progress-v2",
|
|
68
|
+
compiled_identity: compiled.compiled_identity,
|
|
69
|
+
snapshot_sha256: snapshot.manifest.snapshot_sha256,
|
|
70
|
+
acceptance_authorized: false,
|
|
71
|
+
selected_outcome: selection.outcome ?? null,
|
|
72
|
+
selected_check: selection.check ?? null,
|
|
73
|
+
updated_progress_records: records.map((record) => record.check_internal_id),
|
|
74
|
+
check_results: run.check_results,
|
|
75
|
+
findings: run.findings,
|
|
76
|
+
completed_at: new Date().toISOString(),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
finally {
|
|
80
|
+
await snapshot.dispose();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function resolved(workdir) {
|
|
84
|
+
return (await import("node:path")).default.resolve(workdir);
|
|
85
|
+
}
|
|
86
|
+
export async function runDeliveryChecks(compiled, snapshot, checks, includeCounterfactuals, finalGate = false) {
|
|
87
|
+
const findings = await preRunFindings(compiled, snapshot.manifest);
|
|
88
|
+
if (finalGate)
|
|
89
|
+
findings.push(...finalPathFindings(compiled, snapshot.manifest));
|
|
90
|
+
if (findings.length)
|
|
91
|
+
return {
|
|
92
|
+
snapshot: snapshot.manifest,
|
|
93
|
+
check_results: [],
|
|
94
|
+
findings: findings.map((finding) => enrichFinding(compiled, finding)),
|
|
95
|
+
};
|
|
96
|
+
const rawExecutions = new Map();
|
|
97
|
+
const mainCheckResults = [];
|
|
98
|
+
for (const check of checks) {
|
|
99
|
+
let raw = rawExecutions.get(check.raw_execution_identity);
|
|
100
|
+
if (!raw) {
|
|
101
|
+
raw = await executeCheckRunner(check, snapshot.root);
|
|
102
|
+
rawExecutions.set(check.raw_execution_identity, raw);
|
|
103
|
+
}
|
|
104
|
+
const outcome = check.outcome_key
|
|
105
|
+
? compiled.outcomes.find((item) => item.key === check.outcome_key)
|
|
106
|
+
: undefined;
|
|
107
|
+
mainCheckResults.push(await evaluateCheckEvidence(check, raw, snapshot.root, outcome));
|
|
108
|
+
}
|
|
109
|
+
const counterfactualFindings = [];
|
|
110
|
+
if (includeCounterfactuals) {
|
|
111
|
+
const selectedGlobalCheckKeys = new Set(checks
|
|
112
|
+
.filter((check) => check.outcome_key === null)
|
|
113
|
+
.map((check) => check.key));
|
|
114
|
+
if (selectedGlobalCheckKeys.size)
|
|
115
|
+
counterfactualFindings.push(...(await evaluateGlobalCounterfactuals(compiled, snapshot.root, selectedGlobalCheckKeys, snapshot.manifest)));
|
|
116
|
+
const outcomeKeys = new Set(checks
|
|
117
|
+
.map((check) => check.outcome_key)
|
|
118
|
+
.filter((key) => Boolean(key)));
|
|
119
|
+
for (const outcome of compiled.outcomes.filter((item) => outcomeKeys.has(item.key))) {
|
|
120
|
+
const selectedKeys = new Set(checks
|
|
121
|
+
.filter((check) => check.outcome_key === outcome.key)
|
|
122
|
+
.map((check) => check.key));
|
|
123
|
+
counterfactualFindings.push(...(await evaluateOutcomeCounterfactuals({
|
|
124
|
+
...outcome,
|
|
125
|
+
acceptance: {
|
|
126
|
+
...outcome.acceptance,
|
|
127
|
+
counterfactual_controls: outcome.acceptance.counterfactual_controls.filter((control) => selectedKeys.has(control.check_key)),
|
|
128
|
+
},
|
|
129
|
+
}, snapshot.root, snapshot.manifest)));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const unassignedCounterfactuals = counterfactualFindings.filter((finding) => !mainCheckResults.some((result) => findingBelongsToCheck(finding, result)));
|
|
133
|
+
const unassignedFinding = unassignedCounterfactuals.length
|
|
134
|
+
? unassignedCounterfactualFinding(unassignedCounterfactuals)
|
|
135
|
+
: null;
|
|
136
|
+
const checkResults = applyCounterfactualFindings(mainCheckResults, counterfactualFindings, unassignedFinding).map((result) => enrichCheckResultFindings(compiled, result));
|
|
137
|
+
findings.push(...checkResults.flatMap((result) => result.findings));
|
|
138
|
+
if (unassignedFinding && !checkResults.length)
|
|
139
|
+
findings.push(unassignedFinding);
|
|
140
|
+
return {
|
|
141
|
+
snapshot: snapshot.manifest,
|
|
142
|
+
check_results: checkResults,
|
|
143
|
+
findings: findings.map((finding) => enrichFinding(compiled, finding)),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function applyCounterfactualFindings(checkResults, findings, unassignedFinding) {
|
|
147
|
+
return checkResults.map((result, index) => {
|
|
148
|
+
const projected = findings.filter((finding) => findingBelongsToCheck(finding, result));
|
|
149
|
+
const invariantFindings = unassignedFinding && index === 0 ? [unassignedFinding] : [];
|
|
150
|
+
const projectedFindings = [...projected, ...invariantFindings];
|
|
151
|
+
if (!projectedFindings.length)
|
|
152
|
+
return result;
|
|
153
|
+
return {
|
|
154
|
+
...result,
|
|
155
|
+
status: result.status === "passed" ? "invalid_evidence" : result.status,
|
|
156
|
+
claim_proofs: [],
|
|
157
|
+
findings: [...result.findings, ...projectedFindings],
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
function findingBelongsToCheck(finding, result) {
|
|
162
|
+
return (finding.check_key !== null &&
|
|
163
|
+
finding.check_key === result.check_key &&
|
|
164
|
+
finding.outcome_key === result.outcome_key);
|
|
165
|
+
}
|
|
166
|
+
function unassignedCounterfactualFinding(actual) {
|
|
167
|
+
return {
|
|
168
|
+
code: "counterfactual_finding_unassigned",
|
|
169
|
+
outcome_key: null,
|
|
170
|
+
check_key: null,
|
|
171
|
+
message: "A Counterfactual Finding could not be assigned to its owning Check Result.",
|
|
172
|
+
actual,
|
|
173
|
+
next_action: "Repair the Counterfactual evaluator ownership invariant before trusting verification progress.",
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function finalPathFindings(compiled, manifest) {
|
|
177
|
+
const findings = [];
|
|
178
|
+
for (const check of allCompiledChecks(compiled))
|
|
179
|
+
for (const pattern of check.expected_output_paths)
|
|
180
|
+
if (!manifest.files.some((file) => matchesRepoPattern(file.path, pattern)))
|
|
181
|
+
findings.push({
|
|
182
|
+
code: "expected_output_path_missing",
|
|
183
|
+
outcome_key: check.outcome_key,
|
|
184
|
+
check_key: check.key,
|
|
185
|
+
message: `Expected output path did not exist: ${pattern}`,
|
|
186
|
+
expected: pattern,
|
|
187
|
+
next_action: "Create the declared output and rerun verification.",
|
|
188
|
+
});
|
|
189
|
+
for (const outcome of compiled.outcomes)
|
|
190
|
+
for (const binding of outcome.technical.bindings)
|
|
191
|
+
if (binding.existence === "planned" &&
|
|
192
|
+
!plannedBindingRequiredPatterns(binding).every((pattern) => manifest.files.some((file) => matchesRepoPattern(file.path, pattern))))
|
|
193
|
+
findings.push({
|
|
194
|
+
code: "binding_missing",
|
|
195
|
+
outcome_key: outcome.key,
|
|
196
|
+
check_key: null,
|
|
197
|
+
claim_keys: outcome.generated_claims.some((claim) => claim.local_key === `obligation.${binding.key}`)
|
|
198
|
+
? [`obligation.${binding.key}`]
|
|
199
|
+
: [],
|
|
200
|
+
message: `Planned binding is missing: ${binding.target}`,
|
|
201
|
+
expected: binding.carrier_paths,
|
|
202
|
+
next_action: "Implement the declared binding and rerun verification.",
|
|
203
|
+
});
|
|
204
|
+
return findings;
|
|
205
|
+
}
|
|
206
|
+
function plannedBindingRequiredPatterns(binding) {
|
|
207
|
+
return binding.kind === "file" || binding.kind === "path_glob"
|
|
208
|
+
? [binding.target]
|
|
209
|
+
: binding.carrier_paths;
|
|
210
|
+
}
|
|
211
|
+
export function allCompiledChecks(compiled) {
|
|
212
|
+
return [
|
|
213
|
+
...compiled.global.acceptance.checks,
|
|
214
|
+
...compiled.outcomes.flatMap((outcome) => outcome.acceptance.checks),
|
|
215
|
+
];
|
|
216
|
+
}
|
|
217
|
+
function selectChecks(compiled, selection) {
|
|
218
|
+
const checks = allCompiledChecks(compiled);
|
|
219
|
+
if (selection.outcome &&
|
|
220
|
+
!compiled.outcomes.some((outcome) => outcome.key === selection.outcome))
|
|
221
|
+
throw new Error(`outcome_not_found:${selection.outcome}`);
|
|
222
|
+
const filtered = checks.filter((check) => (!selection.outcome || check.outcome_key === selection.outcome) &&
|
|
223
|
+
(!selection.check || check.key === selection.check));
|
|
224
|
+
if (selection.check && !filtered.length)
|
|
225
|
+
throw new Error(`check_not_found:${selection.check}`);
|
|
226
|
+
return filtered;
|
|
227
|
+
}
|
|
228
|
+
async function preRunFindings(compiled, current) {
|
|
229
|
+
const stale = await deliveryCompileFreshness(compiled);
|
|
230
|
+
if (stale.length)
|
|
231
|
+
return stale.map((code) => ({
|
|
232
|
+
code,
|
|
233
|
+
outcome_key: null,
|
|
234
|
+
check_key: null,
|
|
235
|
+
message: "A compiled Contract input changed.",
|
|
236
|
+
next_action: "Run ty-context long-task compile --revise after reviewing the Contract change.",
|
|
237
|
+
}));
|
|
238
|
+
const protectedAuthorityFiles = new Set([
|
|
239
|
+
...Object.keys(compiled.contract_files),
|
|
240
|
+
...Object.keys(compiled.source_hashes),
|
|
241
|
+
...Object.keys(compiled.context_snapshot.sha256),
|
|
242
|
+
...allCompiledChecks(compiled).flatMap((check) => Object.keys(check.verification_input_hashes)),
|
|
243
|
+
]);
|
|
244
|
+
const changed = changedWorkspacePaths(compiled.initial_task_base.workspace_manifest, current).filter((file) => !protectedAuthorityFiles.has(file));
|
|
245
|
+
const allowed = compiled.outcomes.flatMap((outcome) => [
|
|
246
|
+
...outcome.technical.expected_change_paths,
|
|
247
|
+
...outcome.technical.allowed_support_paths,
|
|
248
|
+
]);
|
|
249
|
+
const forbidden = [
|
|
250
|
+
...compiled.global.technical.forbidden_paths.map((entry) => entry.path),
|
|
251
|
+
...compiled.outcomes.flatMap((outcome) => outcome.technical.forbidden_paths),
|
|
252
|
+
];
|
|
253
|
+
const escaped = findScopeEscapes(changed, allowed, forbidden);
|
|
254
|
+
return escaped.length
|
|
255
|
+
? [
|
|
256
|
+
{
|
|
257
|
+
code: "scope_escape",
|
|
258
|
+
outcome_key: null,
|
|
259
|
+
check_key: null,
|
|
260
|
+
message: `Changed paths are outside the Contract boundary: ${escaped.join(",")}`,
|
|
261
|
+
actual: escaped,
|
|
262
|
+
next_action: "Review risk/ownership, revise declared change paths and recompile in the same Goal.",
|
|
263
|
+
},
|
|
264
|
+
]
|
|
265
|
+
: [];
|
|
266
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface WorkspaceFileV2 {
|
|
2
|
+
path: string;
|
|
3
|
+
mode: number;
|
|
4
|
+
size: number;
|
|
5
|
+
sha256: string;
|
|
6
|
+
}
|
|
7
|
+
export interface WorkspaceFingerprintV2 {
|
|
8
|
+
head: string;
|
|
9
|
+
head_tree: string;
|
|
10
|
+
index_tree: string;
|
|
11
|
+
staged_diff_sha256: string;
|
|
12
|
+
unstaged_diff_sha256: string;
|
|
13
|
+
untracked_sha256: string;
|
|
14
|
+
status_sha256: string;
|
|
15
|
+
identity: string;
|
|
16
|
+
}
|
|
17
|
+
export interface WorkspaceManifestV2 {
|
|
18
|
+
repository_root: string;
|
|
19
|
+
git_head: string;
|
|
20
|
+
files: WorkspaceFileV2[];
|
|
21
|
+
fingerprint: WorkspaceFingerprintV2;
|
|
22
|
+
snapshot_sha256: string;
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { WorkspaceFingerprintV2, WorkspaceManifestV2 } from "./long-task-delivery-types.js";
|
|
2
|
+
export interface WorkspaceSnapshotV2 {
|
|
3
|
+
root: string;
|
|
4
|
+
manifest: WorkspaceManifestV2;
|
|
5
|
+
preparation_ms: number;
|
|
6
|
+
dispose(): Promise<void>;
|
|
7
|
+
}
|
|
8
|
+
export declare function repositoryRoot(start: string): Promise<string>;
|
|
9
|
+
export declare function gitCommonDir(root: string): Promise<string>;
|
|
10
|
+
export declare function captureWorkspaceFingerprint(rootInput: string, excludedPrefixes?: string[]): Promise<WorkspaceFingerprintV2>;
|
|
11
|
+
export declare function captureWorkspaceManifest(rootInput: string, workdirInput: string, _copyRoot?: string, additionalExcludedWorkdirs?: string[]): Promise<WorkspaceManifestV2>;
|
|
12
|
+
export declare function createWorkspaceSnapshot(rootInput: string, workdirInput: string, label: string, additionalExcludedWorkdirs?: string[]): Promise<WorkspaceSnapshotV2>;
|
|
13
|
+
export declare function changedWorkspacePaths(baseline: WorkspaceManifestV2, current: WorkspaceManifestV2): string[];
|
|
14
|
+
export declare function currentGitState(root: string): Promise<{
|
|
15
|
+
head: string;
|
|
16
|
+
tree: string;
|
|
17
|
+
dirty: string[];
|
|
18
|
+
}>;
|
|
19
|
+
export declare function currentGitTree(root: string): Promise<string>;
|
|
20
|
+
export declare function gitPath(root: string, pathSpec: string): Promise<string>;
|
|
21
|
+
export declare function gitConfigGet(root: string, name: string): Promise<string | null>;
|
|
22
|
+
export declare function gitConfigSet(root: string, name: string, value: string): Promise<void>;
|
|
23
|
+
export declare function gitConfigUnset(root: string, name: string): Promise<void>;
|
|
24
|
+
export declare function resolveInsideRepository(rootInput: string, relativeInput: string, label: string): string;
|
|
25
|
+
export declare function repoRelative(rootInput: string, fileInput: string): string;
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { copyFile, mkdir, mkdtemp, readFile, readdir, rm, stat, symlink, } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { canonicalValueJson, sha256Hex } from "./strict-codec.js";
|
|
6
|
+
export async function repositoryRoot(start) {
|
|
7
|
+
return path.resolve(await gitOutput(path.resolve(start), ["rev-parse", "--show-toplevel"]));
|
|
8
|
+
}
|
|
9
|
+
export async function gitCommonDir(root) {
|
|
10
|
+
return path.resolve(await gitOutput(root, [
|
|
11
|
+
"rev-parse",
|
|
12
|
+
"--path-format=absolute",
|
|
13
|
+
"--git-common-dir",
|
|
14
|
+
]));
|
|
15
|
+
}
|
|
16
|
+
export async function captureWorkspaceFingerprint(rootInput, excludedPrefixes = []) {
|
|
17
|
+
const root = path.resolve(rootInput);
|
|
18
|
+
const [head, headTree, indexTree, staged, unstaged, statusBytes, untracked] = await Promise.all([
|
|
19
|
+
gitOutput(root, ["rev-parse", "HEAD"]),
|
|
20
|
+
gitOutput(root, ["rev-parse", "HEAD^{tree}"]),
|
|
21
|
+
gitOutput(root, ["write-tree"]),
|
|
22
|
+
gitBuffer(root, scopedDiffArgs(["diff", "--cached", "--binary", "--no-ext-diff"], excludedPrefixes)),
|
|
23
|
+
gitBuffer(root, scopedDiffArgs(["diff", "--binary", "--no-ext-diff"], excludedPrefixes)),
|
|
24
|
+
Promise.all([
|
|
25
|
+
gitBuffer(root, scopedDiffArgs(["diff", "--cached", "--raw", "-z", "-M"], excludedPrefixes)),
|
|
26
|
+
gitBuffer(root, scopedDiffArgs(["diff", "--raw", "-z", "-M"], excludedPrefixes)),
|
|
27
|
+
]).then((rows) => Buffer.concat(rows)),
|
|
28
|
+
untrackedIdentity(root, excludedPrefixes),
|
|
29
|
+
]);
|
|
30
|
+
const unsigned = {
|
|
31
|
+
head,
|
|
32
|
+
head_tree: headTree,
|
|
33
|
+
index_tree: indexTree,
|
|
34
|
+
staged_diff_sha256: sha256Hex(staged),
|
|
35
|
+
unstaged_diff_sha256: sha256Hex(unstaged),
|
|
36
|
+
untracked_sha256: untracked,
|
|
37
|
+
status_sha256: sha256Hex(statusBytes),
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
...unsigned,
|
|
41
|
+
identity: sha256Hex(canonicalValueJson(unsigned)),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export async function captureWorkspaceManifest(rootInput, workdirInput, _copyRoot, additionalExcludedWorkdirs = []) {
|
|
45
|
+
const root = path.resolve(rootInput);
|
|
46
|
+
const workdir = path.resolve(workdirInput);
|
|
47
|
+
const workdirRelative = repoRelative(root, workdir);
|
|
48
|
+
if (!workdirRelative)
|
|
49
|
+
throw new Error("long_task_workdir_must_not_be_repository_root");
|
|
50
|
+
const excluded = excludedPrefixes(root, [
|
|
51
|
+
workdir,
|
|
52
|
+
...additionalExcludedWorkdirs,
|
|
53
|
+
]);
|
|
54
|
+
const [indexBytes, modifiedBytes, untrackedBytes, fingerprint] = await Promise.all([
|
|
55
|
+
gitBuffer(root, ["ls-files", "--stage", "-z"]),
|
|
56
|
+
gitBuffer(root, ["diff", "--name-only", "-z"]),
|
|
57
|
+
gitBuffer(root, ["ls-files", "--others", "--exclude-standard", "-z"]),
|
|
58
|
+
captureWorkspaceFingerprint(root, excluded),
|
|
59
|
+
]);
|
|
60
|
+
const files = new Map();
|
|
61
|
+
for (const record of splitZero(indexBytes)) {
|
|
62
|
+
const tab = record.indexOf("\t");
|
|
63
|
+
if (tab < 0)
|
|
64
|
+
continue;
|
|
65
|
+
const [modeText, objectId, stage] = record.slice(0, tab).split(" ");
|
|
66
|
+
const relative = record.slice(tab + 1).replace(/\\/gu, "/");
|
|
67
|
+
if (stage !== "0" || excludedPath(relative, excluded))
|
|
68
|
+
continue;
|
|
69
|
+
files.set(relative, {
|
|
70
|
+
path: relative,
|
|
71
|
+
mode: Number.parseInt(modeText, 8),
|
|
72
|
+
size: 0,
|
|
73
|
+
sha256: `git:${objectId}`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const overlays = new Set([
|
|
77
|
+
...splitZero(modifiedBytes),
|
|
78
|
+
...splitZero(untrackedBytes),
|
|
79
|
+
]);
|
|
80
|
+
const overlayNames = [...overlays]
|
|
81
|
+
.map((raw) => raw.replace(/\\/gu, "/"))
|
|
82
|
+
.filter((relative) => !excludedPath(relative, excluded));
|
|
83
|
+
const overlayInfo = new Map(await Promise.all(overlayNames.map(async (relative) => [
|
|
84
|
+
relative,
|
|
85
|
+
await stat(path.join(root, ...relative.split("/")), {
|
|
86
|
+
bigint: true,
|
|
87
|
+
}).catch(() => null),
|
|
88
|
+
])));
|
|
89
|
+
const overlayHashes = await gitObjectIds(root, overlayNames.filter((relative) => overlayInfo.get(relative)?.isFile()));
|
|
90
|
+
for (const relative of overlayNames) {
|
|
91
|
+
const absolute = path.join(root, ...relative.split("/"));
|
|
92
|
+
const info = overlayInfo.get(relative);
|
|
93
|
+
if (!info?.isFile()) {
|
|
94
|
+
files.delete(relative);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const bytes = await readFile(absolute);
|
|
98
|
+
files.set(relative, {
|
|
99
|
+
path: relative,
|
|
100
|
+
mode: gitFileMode(Number(info.mode)),
|
|
101
|
+
size: bytes.length,
|
|
102
|
+
sha256: `git:${overlayHashes.get(relative)}`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
repository_root: root,
|
|
107
|
+
git_head: fingerprint.head,
|
|
108
|
+
files: [...files.values()].sort((a, b) => a.path.localeCompare(b.path)),
|
|
109
|
+
fingerprint,
|
|
110
|
+
snapshot_sha256: fingerprint.identity,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export async function createWorkspaceSnapshot(rootInput, workdirInput, label, additionalExcludedWorkdirs = []) {
|
|
114
|
+
const started = performance.now();
|
|
115
|
+
const root = path.resolve(rootInput);
|
|
116
|
+
const workdir = path.resolve(workdirInput);
|
|
117
|
+
const excluded = excludedPrefixes(root, [
|
|
118
|
+
workdir,
|
|
119
|
+
...additionalExcludedWorkdirs,
|
|
120
|
+
]);
|
|
121
|
+
const manifest = await captureWorkspaceManifest(root, workdir, undefined, additionalExcludedWorkdirs);
|
|
122
|
+
const before = manifest.fingerprint;
|
|
123
|
+
const temporary = await mkdtemp(path.join(os.tmpdir(), `ty-context-${safe(label)}-`));
|
|
124
|
+
try {
|
|
125
|
+
await gitVoid(root, [
|
|
126
|
+
"checkout-index",
|
|
127
|
+
"--all",
|
|
128
|
+
"--force",
|
|
129
|
+
`--prefix=${temporary.replace(/\\/gu, "/")}/`,
|
|
130
|
+
]);
|
|
131
|
+
const [modified, untracked] = await Promise.all([
|
|
132
|
+
gitBuffer(root, ["diff", "--name-only", "-z"]),
|
|
133
|
+
gitBuffer(root, ["ls-files", "--others", "--exclude-standard", "-z"]),
|
|
134
|
+
]);
|
|
135
|
+
for (const raw of new Set([
|
|
136
|
+
...splitZero(modified),
|
|
137
|
+
...splitZero(untracked),
|
|
138
|
+
])) {
|
|
139
|
+
const relative = raw.replace(/\\/gu, "/");
|
|
140
|
+
if (excludedPath(relative, excluded))
|
|
141
|
+
continue;
|
|
142
|
+
const source = path.join(root, ...relative.split("/"));
|
|
143
|
+
const target = path.join(temporary, ...relative.split("/"));
|
|
144
|
+
const info = await stat(source).catch(() => null);
|
|
145
|
+
if (!info?.isFile()) {
|
|
146
|
+
await rm(target, { recursive: true, force: true });
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
150
|
+
await copyFile(source, target);
|
|
151
|
+
}
|
|
152
|
+
await removeExcludedSnapshotPaths(temporary, excluded);
|
|
153
|
+
await linkDependencyTrees(root, temporary, [
|
|
154
|
+
workdir,
|
|
155
|
+
...additionalExcludedWorkdirs,
|
|
156
|
+
]);
|
|
157
|
+
const after = await captureWorkspaceFingerprint(root, excluded);
|
|
158
|
+
if (after.identity !== before.identity)
|
|
159
|
+
throw new Error("workspace_changed_during_snapshot");
|
|
160
|
+
return {
|
|
161
|
+
root: temporary,
|
|
162
|
+
manifest,
|
|
163
|
+
preparation_ms: performance.now() - started,
|
|
164
|
+
dispose: () => rm(temporary, { recursive: true, force: true }),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
await rm(temporary, { recursive: true, force: true });
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
export function changedWorkspacePaths(baseline, current) {
|
|
173
|
+
const before = new Map(baseline.files.map((file) => [file.path, `${file.mode}:${file.sha256}`]));
|
|
174
|
+
const after = new Map(current.files.map((file) => [file.path, `${file.mode}:${file.sha256}`]));
|
|
175
|
+
return [...new Set([...before.keys(), ...after.keys()])]
|
|
176
|
+
.filter((file) => before.get(file) !== after.get(file))
|
|
177
|
+
.sort();
|
|
178
|
+
}
|
|
179
|
+
export async function currentGitState(root) {
|
|
180
|
+
const [head, tree, raw] = await Promise.all([
|
|
181
|
+
gitOutput(root, ["rev-parse", "HEAD"]),
|
|
182
|
+
gitOutput(root, ["rev-parse", "HEAD^{tree}"]),
|
|
183
|
+
gitOutput(root, ["status", "--short", "--untracked-files=all"]),
|
|
184
|
+
]);
|
|
185
|
+
return {
|
|
186
|
+
head,
|
|
187
|
+
tree,
|
|
188
|
+
dirty: raw ? raw.split(/\r?\n/u).filter(Boolean) : [],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
export async function currentGitTree(root) {
|
|
192
|
+
return gitOutput(root, ["rev-parse", "HEAD^{tree}"]);
|
|
193
|
+
}
|
|
194
|
+
export async function gitPath(root, pathSpec) {
|
|
195
|
+
return path.resolve(await gitOutput(root, [
|
|
196
|
+
"rev-parse",
|
|
197
|
+
"--path-format=absolute",
|
|
198
|
+
"--git-path",
|
|
199
|
+
pathSpec,
|
|
200
|
+
]));
|
|
201
|
+
}
|
|
202
|
+
export async function gitConfigGet(root, name) {
|
|
203
|
+
try {
|
|
204
|
+
return await gitOutput(root, ["config", "--local", "--get", name]);
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
if (message(error).includes("git_exit:1"))
|
|
208
|
+
return null;
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
export async function gitConfigSet(root, name, value) {
|
|
213
|
+
await gitVoid(root, ["config", "--local", name, value]);
|
|
214
|
+
}
|
|
215
|
+
export async function gitConfigUnset(root, name) {
|
|
216
|
+
try {
|
|
217
|
+
await gitVoid(root, ["config", "--local", "--unset-all", name]);
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
if (!message(error).includes("git_exit:5"))
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
export function resolveInsideRepository(rootInput, relativeInput, label) {
|
|
225
|
+
if (!relativeInput || path.isAbsolute(relativeInput))
|
|
226
|
+
throw new Error(`unsafe_path:${label}:${relativeInput}`);
|
|
227
|
+
const normalized = relativeInput.replace(/\\/gu, "/");
|
|
228
|
+
if (normalized.split("/").includes(".."))
|
|
229
|
+
throw new Error(`unsafe_path:${label}:${relativeInput}`);
|
|
230
|
+
const root = path.resolve(rootInput);
|
|
231
|
+
const resolved = path.resolve(root, ...normalized.split("/"));
|
|
232
|
+
if (resolved !== root && !resolved.startsWith(`${root}${path.sep}`))
|
|
233
|
+
throw new Error(`unsafe_path:${label}:${relativeInput}`);
|
|
234
|
+
return resolved;
|
|
235
|
+
}
|
|
236
|
+
export function repoRelative(rootInput, fileInput) {
|
|
237
|
+
const value = path
|
|
238
|
+
.relative(path.resolve(rootInput), path.resolve(fileInput))
|
|
239
|
+
.replace(/\\/gu, "/");
|
|
240
|
+
if (value.startsWith("../") || path.isAbsolute(value))
|
|
241
|
+
throw new Error(`path_outside_repository:${fileInput}`);
|
|
242
|
+
return value;
|
|
243
|
+
}
|
|
244
|
+
async function untrackedIdentity(root, excluded) {
|
|
245
|
+
const names = splitZero(await gitBuffer(root, ["ls-files", "--others", "--exclude-standard", "-z"]))
|
|
246
|
+
.map((name) => name.replace(/\\/gu, "/"))
|
|
247
|
+
.filter((name) => !excludedPath(name, excluded))
|
|
248
|
+
.sort();
|
|
249
|
+
const hashes = await gitObjectIds(root, names);
|
|
250
|
+
const rows = names.map((name) => [
|
|
251
|
+
name,
|
|
252
|
+
hashes.get(name) ?? "missing",
|
|
253
|
+
]);
|
|
254
|
+
return sha256Hex(canonicalValueJson(rows));
|
|
255
|
+
}
|
|
256
|
+
function excludedPrefixes(root, workdirs) {
|
|
257
|
+
return [
|
|
258
|
+
...workdirs.map((workdir) => repoRelative(root, path.resolve(workdir))),
|
|
259
|
+
"project_context",
|
|
260
|
+
"tmp/ty-context/long-task-runs",
|
|
261
|
+
].filter(Boolean);
|
|
262
|
+
}
|
|
263
|
+
function excludedPath(relative, excluded) {
|
|
264
|
+
const normalized = relative.replace(/\\/gu, "/");
|
|
265
|
+
return (normalized.split("/").includes("node_modules") ||
|
|
266
|
+
excluded.some((prefix) => normalized === prefix || normalized.startsWith(`${prefix}/`)));
|
|
267
|
+
}
|
|
268
|
+
async function removeExcludedSnapshotPaths(snapshotRoot, excluded) {
|
|
269
|
+
for (const relative of excluded)
|
|
270
|
+
await rm(path.join(snapshotRoot, ...relative.split("/")), {
|
|
271
|
+
recursive: true,
|
|
272
|
+
force: true,
|
|
273
|
+
});
|
|
274
|
+
async function visit(directory) {
|
|
275
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
276
|
+
const target = path.join(directory, entry.name);
|
|
277
|
+
if (entry.isDirectory() && entry.name === "node_modules") {
|
|
278
|
+
await rm(target, { recursive: true, force: true });
|
|
279
|
+
}
|
|
280
|
+
else if (entry.isDirectory())
|
|
281
|
+
await visit(target);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
await visit(snapshotRoot);
|
|
285
|
+
}
|
|
286
|
+
async function linkDependencyTrees(sourceRoot, snapshotRoot, workdirs) {
|
|
287
|
+
const protectedWorkdirs = workdirs.map((workdir) => repoRelative(sourceRoot, workdir));
|
|
288
|
+
async function visit(directory, relative = "") {
|
|
289
|
+
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
290
|
+
const next = relative ? `${relative}/${entry.name}` : entry.name;
|
|
291
|
+
if (protectedWorkdirs.some((protectedWorkdir) => next === protectedWorkdir ||
|
|
292
|
+
next.startsWith(`${protectedWorkdir}/`)) ||
|
|
293
|
+
entry.name === ".git")
|
|
294
|
+
continue;
|
|
295
|
+
const source = path.join(directory, entry.name);
|
|
296
|
+
if (entry.isDirectory() && entry.name === "node_modules") {
|
|
297
|
+
const target = path.join(snapshotRoot, ...next.split("/"));
|
|
298
|
+
await mkdir(path.dirname(target), { recursive: true });
|
|
299
|
+
await rm(target, { recursive: true, force: true });
|
|
300
|
+
await symlink(source, target, process.platform === "win32" ? "junction" : "dir");
|
|
301
|
+
}
|
|
302
|
+
else if (entry.isDirectory())
|
|
303
|
+
await visit(source, next);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
await visit(sourceRoot);
|
|
307
|
+
}
|
|
308
|
+
function splitZero(value) {
|
|
309
|
+
return value.toString("utf8").split("\0").filter(Boolean);
|
|
310
|
+
}
|
|
311
|
+
function scopedDiffArgs(base, excluded) {
|
|
312
|
+
return [
|
|
313
|
+
...base,
|
|
314
|
+
"--",
|
|
315
|
+
".",
|
|
316
|
+
...excluded.map((prefix) => `:(exclude)${prefix}/**`),
|
|
317
|
+
":(exclude)**/node_modules/**",
|
|
318
|
+
];
|
|
319
|
+
}
|
|
320
|
+
async function gitObjectIds(root, names) {
|
|
321
|
+
if (!names.length)
|
|
322
|
+
return new Map();
|
|
323
|
+
const output = await gitBufferInput(root, ["hash-object", "--stdin-paths"], Buffer.from(`${names.join("\n")}\n`, "utf8"));
|
|
324
|
+
const ids = output.toString("utf8").trim().split(/\r?\n/u);
|
|
325
|
+
return new Map(names.map((name, index) => [name, ids[index]]));
|
|
326
|
+
}
|
|
327
|
+
function gitFileMode(mode) {
|
|
328
|
+
return mode & 0o111 ? 0o100755 : 0o100644;
|
|
329
|
+
}
|
|
330
|
+
async function gitOutput(root, argv) {
|
|
331
|
+
return (await gitBuffer(root, argv)).toString("utf8").trim();
|
|
332
|
+
}
|
|
333
|
+
async function gitVoid(root, argv) {
|
|
334
|
+
await gitBuffer(root, argv);
|
|
335
|
+
}
|
|
336
|
+
async function gitBuffer(root, argv) {
|
|
337
|
+
return gitBufferInput(root, argv);
|
|
338
|
+
}
|
|
339
|
+
async function gitBufferInput(root, argv, input) {
|
|
340
|
+
return new Promise((resolve, reject) => {
|
|
341
|
+
const child = spawn("git", argv, {
|
|
342
|
+
cwd: root,
|
|
343
|
+
shell: false,
|
|
344
|
+
windowsHide: true,
|
|
345
|
+
});
|
|
346
|
+
const stdout = [];
|
|
347
|
+
const stderr = [];
|
|
348
|
+
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
|
349
|
+
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
350
|
+
child.on("error", reject);
|
|
351
|
+
if (input)
|
|
352
|
+
child.stdin.end(input);
|
|
353
|
+
child.on("close", (code) => {
|
|
354
|
+
if (code === 0)
|
|
355
|
+
resolve(Buffer.concat(stdout));
|
|
356
|
+
else
|
|
357
|
+
reject(new Error(`git_exit:${code}:${argv.join(" ")}:${Buffer.concat(stderr).toString("utf8").trim()}`));
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
function safe(value) {
|
|
362
|
+
return value.replace(/[^A-Za-z0-9._-]/gu, "-").slice(0, 80);
|
|
363
|
+
}
|
|
364
|
+
function message(error) {
|
|
365
|
+
return error instanceof Error ? error.message : String(error);
|
|
366
|
+
}
|