@b00dea/cockpit 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +474 -0
- package/LICENSE +21 -0
- package/README.md +25 -2
- package/bin/cockpit.mjs +33 -0
- package/cockpit.schema.json +2644 -0
- package/dist/autonomy.d.ts +45 -0
- package/dist/autonomy.js +173 -0
- package/dist/bounded-read.d.ts +14 -0
- package/dist/bounded-read.js +42 -0
- package/dist/browser/commands.d.ts +117 -0
- package/dist/browser/commands.js +453 -0
- package/dist/browser/package-paths.d.ts +1 -0
- package/dist/browser/package-paths.js +6 -0
- package/dist/browser/proxy.d.ts +22 -0
- package/dist/browser/proxy.js +37 -0
- package/dist/browser/runtime.d.ts +118 -0
- package/dist/browser/runtime.js +186 -0
- package/dist/browser/surfaces.d.ts +141 -0
- package/dist/browser/surfaces.js +561 -0
- package/dist/browser/ui-gate.d.ts +15 -0
- package/dist/browser/ui-gate.js +18 -0
- package/dist/canonical-json.d.ts +2 -0
- package/dist/canonical-json.js +5 -0
- package/dist/cli-guard.d.ts +5 -0
- package/dist/cli-guard.js +71 -0
- package/dist/cli-main.d.ts +176 -0
- package/dist/cli-main.js +1584 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +16 -0
- package/dist/command-table.d.ts +121 -0
- package/dist/command-table.js +664 -0
- package/dist/config/load.d.ts +183 -0
- package/dist/config/load.js +367 -0
- package/dist/config/sibling-toml.d.ts +48 -0
- package/dist/config/sibling-toml.js +160 -0
- package/dist/config/surface-tools-toml.d.ts +9 -0
- package/dist/config/surface-tools-toml.js +37 -0
- package/dist/config/unconfirmed-toml.d.ts +9 -0
- package/dist/config/unconfirmed-toml.js +29 -0
- package/dist/config/verified-write.d.ts +43 -0
- package/dist/config/verified-write.js +89 -0
- package/dist/diagnostics.d.ts +26 -0
- package/dist/diagnostics.js +344 -0
- package/dist/docs/check.d.ts +16 -0
- package/dist/docs/check.js +197 -0
- package/dist/docs/decision-record.d.ts +34 -0
- package/dist/docs/decision-record.js +135 -0
- package/dist/docs/decisions.d.ts +16 -0
- package/dist/docs/decisions.js +103 -0
- package/dist/docs/markdown.d.ts +36 -0
- package/dist/docs/markdown.js +88 -0
- package/dist/docs/references.d.ts +32 -0
- package/dist/docs/references.js +148 -0
- package/dist/emit.d.ts +22 -0
- package/dist/emit.js +43 -0
- package/dist/entrypoint-kinds.d.ts +3 -0
- package/dist/entrypoint-kinds.js +11 -0
- package/dist/environment/application/confirm.d.ts +35 -0
- package/dist/environment/application/confirm.js +122 -0
- package/dist/environment/application/destructive.d.ts +6 -0
- package/dist/environment/application/destructive.js +429 -0
- package/dist/environment/application/doctor.d.ts +94 -0
- package/dist/environment/application/doctor.js +247 -0
- package/dist/environment/application/guard.d.ts +49 -0
- package/dist/environment/application/guard.js +677 -0
- package/dist/environment/application/health.d.ts +143 -0
- package/dist/environment/application/health.js +385 -0
- package/dist/environment/application/lifecycle-context.d.ts +235 -0
- package/dist/environment/application/lifecycle-context.js +154 -0
- package/dist/environment/application/lifecycle-state.d.ts +25 -0
- package/dist/environment/application/lifecycle-state.js +202 -0
- package/dist/environment/application/lifecycle.d.ts +18 -0
- package/dist/environment/application/lifecycle.js +737 -0
- package/dist/environment/application/logs.d.ts +45 -0
- package/dist/environment/application/logs.js +127 -0
- package/dist/environment/application/services.d.ts +60 -0
- package/dist/environment/application/services.js +89 -0
- package/dist/environment/application/setup.d.ts +129 -0
- package/dist/environment/application/setup.js +741 -0
- package/dist/environment/application/shared.d.ts +11 -0
- package/dist/environment/application/shared.js +61 -0
- package/dist/environment/application/siblings.d.ts +43 -0
- package/dist/environment/application/siblings.js +269 -0
- package/dist/environment/claims.d.ts +389 -0
- package/dist/environment/claims.js +1701 -0
- package/dist/environment/codex-hook-trust.d.ts +11 -0
- package/dist/environment/codex-hook-trust.js +185 -0
- package/dist/environment/compose-environment.d.ts +1 -0
- package/dist/environment/compose-environment.js +6 -0
- package/dist/environment/compose-ownership.d.ts +21 -0
- package/dist/environment/compose-ownership.js +117 -0
- package/dist/environment/compose-topology.d.ts +125 -0
- package/dist/environment/compose-topology.js +490 -0
- package/dist/environment/contract.d.ts +522 -0
- package/dist/environment/contract.js +794 -0
- package/dist/environment/declaration.d.ts +14 -0
- package/dist/environment/declaration.js +39 -0
- package/dist/environment/destructive-composition.d.ts +29 -0
- package/dist/environment/destructive-composition.js +333 -0
- package/dist/environment/docker-daemon.d.ts +30 -0
- package/dist/environment/docker-daemon.js +78 -0
- package/dist/environment/docker-endpoint.d.ts +10 -0
- package/dist/environment/docker-endpoint.js +19 -0
- package/dist/environment/env-schema-observation.d.ts +46 -0
- package/dist/environment/env-schema-observation.js +241 -0
- package/dist/environment/evidence.d.ts +136 -0
- package/dist/environment/evidence.js +437 -0
- package/dist/environment/executable.d.ts +38 -0
- package/dist/environment/executable.js +190 -0
- package/dist/environment/generated-application/authorities.d.ts +5 -0
- package/dist/environment/generated-application/authorities.js +61 -0
- package/dist/environment/generated-application/bootstrap-skill.d.ts +16 -0
- package/dist/environment/generated-application/bootstrap-skill.js +328 -0
- package/dist/environment/generated-application/check.d.ts +13 -0
- package/dist/environment/generated-application/check.js +191 -0
- package/dist/environment/generated-application/custom-surface.d.ts +26 -0
- package/dist/environment/generated-application/custom-surface.js +289 -0
- package/dist/environment/generated-application/prepare.d.ts +29 -0
- package/dist/environment/generated-application/prepare.js +278 -0
- package/dist/environment/generated-application/publication.d.ts +33 -0
- package/dist/environment/generated-application/publication.js +547 -0
- package/dist/environment/generated-application/shared.d.ts +492 -0
- package/dist/environment/generated-application/shared.js +354 -0
- package/dist/environment/generated-application/surface-observation.d.ts +4 -0
- package/dist/environment/generated-application/surface-observation.js +69 -0
- package/dist/environment/generated-application/surface-outputs.d.ts +71 -0
- package/dist/environment/generated-application/surface-outputs.js +400 -0
- package/dist/environment/generated-application/surfaces.d.ts +3 -0
- package/dist/environment/generated-application/surfaces.js +704 -0
- package/dist/environment/generated-application/sync.d.ts +6 -0
- package/dist/environment/generated-application/sync.js +124 -0
- package/dist/environment/git-observation.d.ts +202 -0
- package/dist/environment/git-observation.js +553 -0
- package/dist/environment/health-composition.d.ts +12 -0
- package/dist/environment/health-composition.js +645 -0
- package/dist/environment/host-observation.d.ts +56 -0
- package/dist/environment/host-observation.js +52 -0
- package/dist/environment/identity.d.ts +48 -0
- package/dist/environment/identity.js +85 -0
- package/dist/environment/init-application.d.ts +333 -0
- package/dist/environment/init-application.js +726 -0
- package/dist/environment/install-policy-observation.d.ts +71 -0
- package/dist/environment/install-policy-observation.js +284 -0
- package/dist/environment/install-policy.d.ts +48 -0
- package/dist/environment/install-policy.js +96 -0
- package/dist/environment/instance.d.ts +33 -0
- package/dist/environment/instance.js +122 -0
- package/dist/environment/instruction-pointer.d.ts +19 -0
- package/dist/environment/instruction-pointer.js +144 -0
- package/dist/environment/lifecycle-composition.d.ts +31 -0
- package/dist/environment/lifecycle-composition.js +742 -0
- package/dist/environment/lifecycle-stage.d.ts +120 -0
- package/dist/environment/lifecycle-stage.js +408 -0
- package/dist/environment/lifecycle.d.ts +75 -0
- package/dist/environment/lifecycle.js +354 -0
- package/dist/environment/logs.d.ts +132 -0
- package/dist/environment/logs.js +519 -0
- package/dist/environment/map-limit.d.ts +2 -0
- package/dist/environment/map-limit.js +13 -0
- package/dist/environment/mcp-observation.d.ts +9 -0
- package/dist/environment/mcp-observation.js +46 -0
- package/dist/environment/observations.d.ts +46 -0
- package/dist/environment/observations.js +346 -0
- package/dist/environment/operator-application.d.ts +79 -0
- package/dist/environment/operator-application.js +197 -0
- package/dist/environment/ports.d.ts +182 -0
- package/dist/environment/ports.js +1205 -0
- package/dist/environment/posture-observation.d.ts +68 -0
- package/dist/environment/posture-observation.js +253 -0
- package/dist/environment/presence.d.ts +4 -0
- package/dist/environment/presence.js +26 -0
- package/dist/environment/read-composition.d.ts +32 -0
- package/dist/environment/read-composition.js +397 -0
- package/dist/environment/readiness-composition.d.ts +57 -0
- package/dist/environment/readiness-composition.js +151 -0
- package/dist/environment/reclaim.d.ts +370 -0
- package/dist/environment/reclaim.js +1940 -0
- package/dist/environment/repository-identity.d.ts +13 -0
- package/dist/environment/repository-identity.js +108 -0
- package/dist/environment/runtime-measurement-record.d.ts +38 -0
- package/dist/environment/runtime-measurement-record.js +111 -0
- package/dist/environment/runtime-measurement.d.ts +27 -0
- package/dist/environment/runtime-measurement.js +224 -0
- package/dist/environment/runtime-probe-residue.d.ts +43 -0
- package/dist/environment/runtime-probe-residue.js +148 -0
- package/dist/environment/runtime-probe.d.ts +72 -0
- package/dist/environment/runtime-probe.js +725 -0
- package/dist/environment/runtime.d.ts +551 -0
- package/dist/environment/runtime.js +2405 -0
- package/dist/environment/seed.d.ts +16 -0
- package/dist/environment/seed.js +43 -0
- package/dist/environment/selftest-application.d.ts +86 -0
- package/dist/environment/selftest-application.js +713 -0
- package/dist/environment/sensitive.d.ts +3 -0
- package/dist/environment/sensitive.js +48 -0
- package/dist/environment/served-checkout-grants.d.ts +53 -0
- package/dist/environment/served-checkout-grants.js +182 -0
- package/dist/environment/setup-observation.d.ts +62 -0
- package/dist/environment/setup-observation.js +1473 -0
- package/dist/environment/setup.d.ts +43 -0
- package/dist/environment/setup.js +176 -0
- package/dist/environment/sibling-checkouts.d.ts +119 -0
- package/dist/environment/sibling-checkouts.js +254 -0
- package/dist/environment/sibling-instructions.d.ts +33 -0
- package/dist/environment/sibling-instructions.js +224 -0
- package/dist/environment/sibling-observation.d.ts +27 -0
- package/dist/environment/sibling-observation.js +45 -0
- package/dist/environment/sibling-pull.d.ts +23 -0
- package/dist/environment/sibling-pull.js +95 -0
- package/dist/environment/sibling-registration.d.ts +45 -0
- package/dist/environment/sibling-registration.js +538 -0
- package/dist/environment/stack-inputs.d.ts +18 -0
- package/dist/environment/stack-inputs.js +210 -0
- package/dist/environment/stack-inventory-prune.d.ts +31 -0
- package/dist/environment/stack-inventory-prune.js +129 -0
- package/dist/environment/stack-inventory-report.d.ts +44 -0
- package/dist/environment/stack-inventory-report.js +78 -0
- package/dist/environment/stack-inventory.d.ts +94 -0
- package/dist/environment/stack-inventory.js +418 -0
- package/dist/environment/state.d.ts +410 -0
- package/dist/environment/state.js +2153 -0
- package/dist/environment/templates.d.ts +140 -0
- package/dist/environment/templates.js +685 -0
- package/dist/environment/tool-registration.d.ts +9 -0
- package/dist/environment/tool-registration.js +92 -0
- package/dist/environment/upgrade-application.d.ts +19 -0
- package/dist/environment/upgrade-application.js +175 -0
- package/dist/environment-exec/execute.d.ts +105 -0
- package/dist/environment-exec/execute.js +1179 -0
- package/dist/environment-exec/libexec-program.d.ts +6 -0
- package/dist/environment-exec/libexec-program.js +22 -0
- package/dist/environment-exec/verified-digest.d.ts +4 -0
- package/dist/environment-exec/verified-digest.js +16 -0
- package/dist/exact-keys.d.ts +7 -0
- package/dist/exact-keys.js +15 -0
- package/dist/fix-catalogue.d.ts +32 -0
- package/dist/fix-catalogue.js +60 -0
- package/dist/freshness.d.ts +29 -0
- package/dist/freshness.js +75 -0
- package/dist/generated-environment/adopt.d.ts +16 -0
- package/dist/generated-environment/adopt.js +271 -0
- package/dist/generated-environment/command.d.ts +47 -0
- package/dist/generated-environment/command.js +75 -0
- package/dist/generated-environment/transaction.d.ts +186 -0
- package/dist/generated-environment/transaction.js +1770 -0
- package/dist/grading/evaluate.d.ts +71 -0
- package/dist/grading/evaluate.js +403 -0
- package/dist/grading/grade.d.ts +33 -0
- package/dist/grading/grade.js +57 -0
- package/dist/guards/answer-confirmation.d.ts +41 -0
- package/dist/guards/answer-confirmation.js +109 -0
- package/dist/guards/evaluator.d.ts +130 -0
- package/dist/guards/evaluator.js +1258 -0
- package/dist/guards/harness-adapter.d.ts +17 -0
- package/dist/guards/harness-adapter.js +57 -0
- package/dist/guards/host.d.ts +8 -0
- package/dist/guards/host.js +10 -0
- package/dist/guards/invocation-parser.d.ts +66 -0
- package/dist/guards/invocation-parser.js +595 -0
- package/dist/guards/local-identity.d.ts +44 -0
- package/dist/guards/local-identity.js +129 -0
- package/dist/guards/policy-change.d.ts +43 -0
- package/dist/guards/policy-change.js +159 -0
- package/dist/guards/policy-pin.d.ts +13 -0
- package/dist/guards/policy-pin.js +177 -0
- package/dist/guards/protected-locations.d.ts +62 -0
- package/dist/guards/protected-locations.js +461 -0
- package/dist/guards/read-only.d.ts +37 -0
- package/dist/guards/read-only.js +315 -0
- package/dist/init/command.d.ts +41 -0
- package/dist/init/command.js +330 -0
- package/dist/init/derive.d.ts +53 -0
- package/dist/init/derive.js +819 -0
- package/dist/init/draft.d.ts +140 -0
- package/dist/init/draft.js +1228 -0
- package/dist/init/render.d.ts +47 -0
- package/dist/init/render.js +104 -0
- package/dist/init/scaffold.d.ts +53 -0
- package/dist/init/scaffold.js +121 -0
- package/dist/invocation.d.ts +13 -0
- package/dist/invocation.js +20 -0
- package/dist/package-metadata.d.ts +9 -0
- package/dist/package-metadata.js +19 -0
- package/dist/readiness.d.ts +53 -0
- package/dist/readiness.js +88 -0
- package/dist/references/named.d.ts +24 -0
- package/dist/references/named.js +27 -0
- package/dist/refusal.d.ts +15 -0
- package/dist/refusal.js +7 -0
- package/dist/registry-derivation.d.ts +31 -0
- package/dist/registry-derivation.js +32 -0
- package/dist/registry-resolve/command.d.ts +47 -0
- package/dist/registry-resolve/command.js +226 -0
- package/dist/registry-resolve/registry.d.ts +68 -0
- package/dist/registry-resolve/registry.js +292 -0
- package/dist/registry-resolve/render-shell.d.ts +18 -0
- package/dist/registry-resolve/render-shell.js +36 -0
- package/dist/repair.d.ts +29 -0
- package/dist/repair.js +8 -0
- package/dist/repository-path.d.ts +9 -0
- package/dist/repository-path.js +16 -0
- package/dist/schema-json.d.ts +1 -0
- package/dist/schema-json.js +177 -0
- package/dist/schema.d.ts +1289 -0
- package/dist/schema.js +985 -0
- package/dist/setup/command.d.ts +4 -0
- package/dist/setup/command.js +4 -0
- package/dist/sha256.d.ts +1 -0
- package/dist/sha256.js +4 -0
- package/dist/surfaces/adoption.d.ts +41 -0
- package/dist/surfaces/adoption.js +217 -0
- package/dist/surfaces/consumer-instructions.d.ts +20 -0
- package/dist/surfaces/consumer-instructions.js +43 -0
- package/dist/surfaces/contract.d.ts +42 -0
- package/dist/surfaces/contract.js +169 -0
- package/dist/surfaces/core-declarations.d.ts +24 -0
- package/dist/surfaces/core-declarations.js +79 -0
- package/dist/surfaces/development-markers.d.ts +1 -0
- package/dist/surfaces/development-markers.js +18 -0
- package/dist/surfaces/generator-adapter.d.ts +71 -0
- package/dist/surfaces/generator-adapter.js +809 -0
- package/dist/surfaces/harness-registry.d.ts +260 -0
- package/dist/surfaces/harness-registry.js +146 -0
- package/dist/surfaces/mcp.d.ts +145 -0
- package/dist/surfaces/mcp.js +349 -0
- package/dist/surfaces/merge.d.ts +30 -0
- package/dist/surfaces/merge.js +105 -0
- package/dist/surfaces/ownership.d.ts +190 -0
- package/dist/surfaces/ownership.js +795 -0
- package/dist/surfaces/projection.d.ts +136 -0
- package/dist/surfaces/projection.js +716 -0
- package/dist/surfaces/render.d.ts +79 -0
- package/dist/surfaces/render.js +636 -0
- package/dist/surfaces/restrictions.d.ts +130 -0
- package/dist/surfaces/restrictions.js +447 -0
- package/dist/surfaces/selftest.d.ts +162 -0
- package/dist/surfaces/selftest.js +393 -0
- package/dist/surfaces/skill-contract.d.ts +85 -0
- package/dist/surfaces/skill-contract.js +449 -0
- package/dist/surfaces/sync.d.ts +57 -0
- package/dist/surfaces/sync.js +164 -0
- package/dist/team-metadata.d.ts +41 -0
- package/dist/team-metadata.js +513 -0
- package/dist/testing/gates.d.ts +91 -0
- package/dist/testing/gates.js +182 -0
- package/dist/testing/hooks.d.ts +59 -0
- package/dist/testing/hooks.js +139 -0
- package/dist/testing/test-protection.d.ts +34 -0
- package/dist/testing/test-protection.js +137 -0
- package/dist/testing/verdict.d.ts +29 -0
- package/dist/testing/verdict.js +62 -0
- package/dist/testing/weakening.d.ts +15 -0
- package/dist/testing/weakening.js +30 -0
- package/dist/work-orders/application-closure.d.ts +7 -0
- package/dist/work-orders/application-closure.js +76 -0
- package/dist/work-orders/application-context.d.ts +162 -0
- package/dist/work-orders/application-context.js +137 -0
- package/dist/work-orders/application-decisions.d.ts +27 -0
- package/dist/work-orders/application-decisions.js +121 -0
- package/dist/work-orders/application-grading.d.ts +11 -0
- package/dist/work-orders/application-grading.js +212 -0
- package/dist/work-orders/application.d.ts +6 -0
- package/dist/work-orders/application.js +223 -0
- package/dist/work-orders/composition.d.ts +101 -0
- package/dist/work-orders/composition.js +1202 -0
- package/dist/work-orders/contract.d.ts +77 -0
- package/dist/work-orders/contract.js +140 -0
- package/dist/work-orders/decisions.d.ts +23 -0
- package/dist/work-orders/decisions.js +55 -0
- package/dist/work-orders/store.d.ts +207 -0
- package/dist/work-orders/store.js +291 -0
- package/docs/README.md +68 -0
- package/docs/architecture.md +136 -0
- package/docs/capability-matrix.md +58 -0
- package/docs/cli-reference.md +807 -0
- package/docs/command-list.md +62 -0
- package/docs/compose-patterns.md +99 -0
- package/docs/concepts.md +173 -0
- package/docs/configuration-essentials.md +177 -0
- package/docs/configuration-reference.md +636 -0
- package/docs/docs-check.md +76 -0
- package/docs/environment.md +948 -0
- package/docs/extending.md +43 -0
- package/docs/faq.md +113 -0
- package/docs/getting-started.md +692 -0
- package/docs/harness-surfaces.md +500 -0
- package/docs/joining-a-team.md +76 -0
- package/docs/release-policy.md +49 -0
- package/docs/safety-and-autonomy.md +463 -0
- package/docs/troubleshooting.md +382 -0
- package/docs/tutorial.md +171 -0
- package/docs/work-and-browser.md +420 -0
- package/libexec/browser-operation.cjs +270 -0
- package/libexec/fence-marker.cjs +101 -0
- package/libexec/fence-marker.d.cts +24 -0
- package/libexec/fenced-group-runner.cjs +77 -0
- package/libexec/fenced-supervisor.cjs +137 -0
- package/libexec/generated-filesystem.cjs +731 -0
- package/libexec/install-filesystem.cjs +225 -0
- package/libexec/rulesync-generator.NOTICE.md +1112 -0
- package/libexec/rulesync-generator.cjs +743 -0
- package/libexec/surface-generator-worker.cjs +266 -0
- package/libexec/surface-output-reader.cjs +99 -0
- package/libexec/surface-scratch-cleanup.cjs +14 -0
- package/libexec/validate-harness-evidence.d.mts +183 -0
- package/libexec/validate-harness-evidence.mjs +1312 -0
- package/package.json +74 -4
- package/surfaces/README.md +33 -0
- package/surfaces/agents/evaluator.agent.md +76 -0
- package/surfaces/guards/bash-guard.sh +48 -0
- package/surfaces/guards/claude.adapter.json +25 -0
- package/surfaces/guards/codex.adapter.json +24 -0
- package/surfaces/guards/opencode.adapter.json +25 -0
- package/surfaces/references/browser.md +63 -0
- package/surfaces/references/test-data-and-personas.md +84 -0
- package/surfaces/references/testing-tiers.md +11 -0
- package/surfaces/skills/cockpit-browser.skill.md +59 -0
- package/surfaces/skills/cockpit-explain.skill.md +32 -0
- package/surfaces/skills/cockpit-fix.skill.md +37 -0
- package/surfaces/skills/cockpit-setup.skill.md +39 -0
- package/surfaces/skills/cockpit-work.skill.md +34 -0
- package/surfaces/skills/collection.json +5 -0
- package/surfaces/skills/procedures/explain.md +66 -0
- package/surfaces/skills/procedures/fix.md +95 -0
- package/surfaces/skills/procedures/setup.md +452 -0
- package/surfaces/skills/procedures/work.md +134 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// Which root instruction files each agent tool loads in a repository, measured 2026-11-03 by asking
|
|
2
|
+
// each tool for words only a file it had loaded could tell it, beside a decoy file it must not load:
|
|
3
|
+
// Codex 0.166.0 and OpenCode 1.19.5 loaded AGENTS.md; Claude Code 2.1.317 loaded only CLAUDE.md (its
|
|
4
|
+
// built-in AGENTS.md reader is an option user settings can turn on and project settings cannot). With
|
|
5
|
+
// no AGENTS.md, OpenCode fell back to CLAUDE.md and Codex loaded nothing. None of the three loaded
|
|
6
|
+
// .cursorrules, .cursor/rules, GEMINI.md, .github/copilot-instructions.md or .windsurfrules. So a
|
|
7
|
+
// repository's instructions reach every tool alike only when AGENTS.md holds them and CLAUDE.md
|
|
8
|
+
// imports it; anything else leaves some tool without them.
|
|
9
|
+
import { randomBytes } from 'node:crypto';
|
|
10
|
+
import { sha256Hex } from '../sha256.js';
|
|
11
|
+
import { constants } from 'node:fs';
|
|
12
|
+
import { lstat, open, realpath, rename, unlink } from 'node:fs/promises';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { readBoundedInput } from '../config/load.js';
|
|
15
|
+
import { ENVIRONMENT_CODES, errorCause, isMissing, RefusalError } from './contract.js';
|
|
16
|
+
const CLAUDE_IMPORT = '@AGENTS.md\n';
|
|
17
|
+
const TOOL_ONLY_FILES = ['.cursorrules', '.cursor/rules', 'GEMINI.md', '.github/copilot-instructions.md', '.windsurfrules'];
|
|
18
|
+
async function readInstructionFile(path) {
|
|
19
|
+
try {
|
|
20
|
+
await lstat(path);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
if (isMissing(error))
|
|
24
|
+
return { kind: 'absent' };
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
// A link is read where it resolves, so CLAUDE.md linked to AGENTS.md counts as one file.
|
|
28
|
+
const real = await realpath(path);
|
|
29
|
+
const read = await readBoundedInput(real);
|
|
30
|
+
if (read.kind !== 'complete')
|
|
31
|
+
throw new RefusalError(`${path} is larger than cockpit reads`);
|
|
32
|
+
return { kind: 'present', real, bytes: read.bytes, text: new TextDecoder('utf-8', { fatal: true }).decode(read.bytes) };
|
|
33
|
+
}
|
|
34
|
+
async function classify(root) {
|
|
35
|
+
const agents = await readInstructionFile(join(root, 'AGENTS.md'));
|
|
36
|
+
const claude = await readInstructionFile(join(root, 'CLAUDE.md'));
|
|
37
|
+
// Claude Code resolves `@path` at the start of a line or after whitespace; an email address has neither.
|
|
38
|
+
const claudeImports = claude.kind === 'present' && /(^|\s)@\S/.test(claude.text);
|
|
39
|
+
if (agents.kind === 'absent')
|
|
40
|
+
return { state: claude.kind === 'absent' ? 'neither' : 'claude-only', claudeImports, agents, claude };
|
|
41
|
+
if (claude.kind === 'absent')
|
|
42
|
+
return { state: 'agents-only', claudeImports, agents, claude };
|
|
43
|
+
const linked = claude.real === agents.real || /^[ \t]*@(\.\/)?AGENTS\.md[ \t]*$/m.test(claude.text);
|
|
44
|
+
return { state: linked ? 'linked' : 'unlinked', claudeImports, agents, claude };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* One warning for each way a repository's root instruction files leave a listed agent tool without
|
|
48
|
+
* them. A team that uses only Claude Code loses nothing when a repository keeps CLAUDE.md alone.
|
|
49
|
+
*/
|
|
50
|
+
export async function inspectSiblingInstructions(repository, tools) {
|
|
51
|
+
const claude = tools.includes('claudecode');
|
|
52
|
+
// OpenCode falls back to CLAUDE.md when AGENTS.md is absent; Codex does not.
|
|
53
|
+
const agentsReaders = tools.includes('codexcli') || tools.includes('opencode');
|
|
54
|
+
const target = `sibling:${repository.slug}`;
|
|
55
|
+
const findings = [];
|
|
56
|
+
let classified;
|
|
57
|
+
const toolOnly = [];
|
|
58
|
+
try {
|
|
59
|
+
classified = await classify(repository.root);
|
|
60
|
+
for (const name of TOOL_ONLY_FILES) {
|
|
61
|
+
try {
|
|
62
|
+
await lstat(join(repository.root, name));
|
|
63
|
+
toolOnly.push(name);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (!isMissing(error))
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
return [{
|
|
73
|
+
severity: 'warning',
|
|
74
|
+
code: ENVIRONMENT_CODES.EVIDENCE_UNAVAILABLE,
|
|
75
|
+
message: `which agent tools load the instructions of sibling ${repository.slug} could not be checked: ${errorCause(error)}`,
|
|
76
|
+
target,
|
|
77
|
+
fix: `make the instruction files at the root of ${repository.root} readable UTF-8 files, then rerun this command`,
|
|
78
|
+
}];
|
|
79
|
+
}
|
|
80
|
+
if (classified.state === 'agents-only' && claude)
|
|
81
|
+
findings.push({
|
|
82
|
+
severity: 'warning',
|
|
83
|
+
// A literal, so the diagnostic inventory captures the fix beside the code.
|
|
84
|
+
code: 'SIBLING_INSTRUCTIONS_AGENTS_ONLY',
|
|
85
|
+
message: `sibling ${repository.slug} has AGENTS.md but no CLAUDE.md, so Claude Code started there loads none of its instructions`,
|
|
86
|
+
target,
|
|
87
|
+
fix: 'run npx --no cockpit setup --config <your cockpit.toml> to see the CLAUDE.md it proposes (one line, @AGENTS.md); once a person approves it, run npx --no cockpit setup --config <your cockpit.toml> --write-instruction-files',
|
|
88
|
+
});
|
|
89
|
+
if (classified.state === 'claude-only' && tools.includes('codexcli'))
|
|
90
|
+
findings.push({
|
|
91
|
+
severity: 'warning',
|
|
92
|
+
// A literal, so the diagnostic inventory captures the fix beside the code.
|
|
93
|
+
code: 'SIBLING_INSTRUCTIONS_CLAUDE_ONLY',
|
|
94
|
+
message: `sibling ${repository.slug} has CLAUDE.md but no AGENTS.md, so Codex started there loads none of its instructions`
|
|
95
|
+
+ (classified.claudeImports ? '; CLAUDE.md imports other files with @, which only Claude Code follows, so setup proposes no move' : ''),
|
|
96
|
+
target,
|
|
97
|
+
fix: 'run npx --no cockpit setup --config <your cockpit.toml> to see the move it proposes (CLAUDE.md copied byte for byte to AGENTS.md, CLAUDE.md replaced by @AGENTS.md) and, once a person approves it, rerun it with --write-instruction-files; when CLAUDE.md imports other files with @, setup proposes nothing and a person moves the shared instructions into AGENTS.md by hand',
|
|
98
|
+
});
|
|
99
|
+
if (classified.state === 'unlinked' && claude && agentsReaders)
|
|
100
|
+
findings.push({
|
|
101
|
+
severity: 'warning',
|
|
102
|
+
// A literal, so the diagnostic inventory captures the fix beside the code.
|
|
103
|
+
code: 'SIBLING_INSTRUCTIONS_UNLINKED',
|
|
104
|
+
message: `sibling ${repository.slug} has both AGENTS.md and CLAUDE.md, but CLAUDE.md does not import AGENTS.md, so Claude Code loads only CLAUDE.md and Codex only AGENTS.md`,
|
|
105
|
+
target,
|
|
106
|
+
fix: 'a person merges the instructions every tool needs into AGENTS.md, then makes CLAUDE.md a line reading @AGENTS.md followed only by instructions that apply to Claude Code alone',
|
|
107
|
+
});
|
|
108
|
+
if (classified.state === 'neither')
|
|
109
|
+
findings.push({
|
|
110
|
+
severity: 'warning',
|
|
111
|
+
// A literal, so the diagnostic inventory captures the fix beside the code.
|
|
112
|
+
code: 'SIBLING_INSTRUCTIONS_MISSING',
|
|
113
|
+
message: `sibling ${repository.slug} has neither AGENTS.md nor CLAUDE.md, so no agent tool started there loads instructions for it`,
|
|
114
|
+
target,
|
|
115
|
+
fix: 'a person writes AGENTS.md with what an agent must know to work in the repository, then runs npx --no cockpit setup --config <your cockpit.toml> --write-instruction-files to add the CLAUDE.md that imports it',
|
|
116
|
+
});
|
|
117
|
+
if (toolOnly.length > 0)
|
|
118
|
+
findings.push({
|
|
119
|
+
severity: 'warning',
|
|
120
|
+
// A literal, so the diagnostic inventory captures the fix beside the code.
|
|
121
|
+
code: 'SIBLING_INSTRUCTIONS_TOOL_ONLY',
|
|
122
|
+
message: `sibling ${repository.slug} keeps instructions in ${toolOnly.join(', ')}, which Claude Code, Codex and OpenCode do not load`,
|
|
123
|
+
target,
|
|
124
|
+
fix: 'a person moves the instructions every tool needs from those files into AGENTS.md, and keeps only settings specific to one tool where they are',
|
|
125
|
+
});
|
|
126
|
+
return findings;
|
|
127
|
+
}
|
|
128
|
+
async function createExclusive(path, bytes, mode) {
|
|
129
|
+
const handle = await open(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, mode);
|
|
130
|
+
try {
|
|
131
|
+
await handle.writeFile(bytes);
|
|
132
|
+
await handle.sync();
|
|
133
|
+
}
|
|
134
|
+
finally {
|
|
135
|
+
await handle.close();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
async function syncDirectory(path) {
|
|
139
|
+
const directory = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
140
|
+
try {
|
|
141
|
+
await directory.sync();
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
await directory.close();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function holds(path, expected) {
|
|
148
|
+
const read = await readBoundedInput(path);
|
|
149
|
+
return read.kind === 'complete' && Buffer.from(read.bytes).equals(Buffer.from(expected));
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Makes AGENTS.md the file every tool loads and CLAUDE.md the import Claude Code needs, in the two
|
|
153
|
+
* cases that need no judgement: AGENTS.md alone gains a CLAUDE.md reading `@AGENTS.md`, and CLAUDE.md
|
|
154
|
+
* alone, when it imports nothing of its own, moves byte for byte to AGENTS.md and becomes that import.
|
|
155
|
+
* Without `write` it only reports the change. It writes only ordinary files directly in a canonical
|
|
156
|
+
* root, never through a link, and refuses when a file changed after it was read.
|
|
157
|
+
*/
|
|
158
|
+
export async function synchronizeSiblingInstructionFiles(input) {
|
|
159
|
+
const root = input.siblingRoot;
|
|
160
|
+
const agentsPath = join(root, 'AGENTS.md');
|
|
161
|
+
const claudePath = join(root, 'CLAUDE.md');
|
|
162
|
+
let created;
|
|
163
|
+
try {
|
|
164
|
+
if (await realpath(root) !== root)
|
|
165
|
+
return { kind: 'refusal', root, reason: 'the sibling root must be a canonical directory without symlink redirects' };
|
|
166
|
+
const classified = await classify(root);
|
|
167
|
+
const change = classified.state === 'agents-only' ? 'claude-import'
|
|
168
|
+
: classified.state === 'claude-only' && !classified.claudeImports ? 'move-to-agents' : undefined;
|
|
169
|
+
if (!change)
|
|
170
|
+
return { kind: 'none', root };
|
|
171
|
+
const before = await lstat(change === 'claude-import' ? agentsPath : claudePath);
|
|
172
|
+
if (!before.isFile())
|
|
173
|
+
return { kind: 'refusal', root, reason: `${change === 'claude-import' ? 'AGENTS.md' : 'CLAUDE.md'} is not an ordinary file` };
|
|
174
|
+
if (!input.write)
|
|
175
|
+
return { kind: 'offered', root, change };
|
|
176
|
+
if (change === 'claude-import') {
|
|
177
|
+
await createExclusive(claudePath, CLAUDE_IMPORT, 0o644);
|
|
178
|
+
created = claudePath;
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
const original = classified.claude.kind === 'present' ? classified.claude.bytes : new Uint8Array();
|
|
182
|
+
await createExclusive(agentsPath, original, Number(before.mode) & 0o777);
|
|
183
|
+
created = agentsPath;
|
|
184
|
+
const temporary = join(root, `.CLAUDE.md.cockpit-instructions-${randomBytes(8).toString('hex')}`);
|
|
185
|
+
try {
|
|
186
|
+
await createExclusive(temporary, CLAUDE_IMPORT, Number(before.mode) & 0o777);
|
|
187
|
+
const current = await lstat(claudePath);
|
|
188
|
+
const reread = await readBoundedInput(claudePath);
|
|
189
|
+
if (!current.isFile() || current.dev !== before.dev || current.ino !== before.ino || current.size !== before.size
|
|
190
|
+
|| current.mtimeMs !== before.mtimeMs || reread.kind !== 'complete' || sha256Hex(reread.bytes) !== sha256Hex(original)) {
|
|
191
|
+
throw new RefusalError('CLAUDE.md changed before it could be replaced');
|
|
192
|
+
}
|
|
193
|
+
await rename(temporary, claudePath);
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
await unlink(temporary).catch((cleanup) => { if (!isMissing(cleanup))
|
|
197
|
+
throw cleanup; });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// From here the instructions live only in what was written; nothing is undone any more.
|
|
201
|
+
created = undefined;
|
|
202
|
+
await syncDirectory(root);
|
|
203
|
+
if (!await holds(claudePath, CLAUDE_IMPORT))
|
|
204
|
+
return { kind: 'refusal', root, reason: 'CLAUDE.md does not hold the import after writing' };
|
|
205
|
+
if (change === 'move-to-agents' && !await holds(agentsPath, classified.claude.kind === 'present' ? classified.claude.bytes : '')) {
|
|
206
|
+
return { kind: 'refusal', root, reason: 'AGENTS.md does not hold the moved instructions after writing' };
|
|
207
|
+
}
|
|
208
|
+
return { kind: 'written', root, change };
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
// A file this run created is removed when the change could not complete, so a half-made move
|
|
212
|
+
// never leaves two copies of the instructions that can drift apart.
|
|
213
|
+
if (created) {
|
|
214
|
+
try {
|
|
215
|
+
await unlink(created);
|
|
216
|
+
}
|
|
217
|
+
catch (cleanup) {
|
|
218
|
+
if (!isMissing(cleanup))
|
|
219
|
+
return { kind: 'refusal', root, reason: `${errorCause(error)}; ${created} could not be removed: ${errorCause(cleanup)}` };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return { kind: 'refusal', root, reason: errorCause(error) };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { CockpitConfig } from '../schema.js';
|
|
2
|
+
import { type EnvironmentObservation, type StackId } from './contract.js';
|
|
3
|
+
export declare const observationDeclarations: readonly [{
|
|
4
|
+
readonly id: 'served-checkouts';
|
|
5
|
+
readonly owner: "sibling-observation";
|
|
6
|
+
readonly postures: readonly ["environment"];
|
|
7
|
+
readonly stages: readonly [{
|
|
8
|
+
readonly stage: 'bootstrap';
|
|
9
|
+
readonly disposition: 'required';
|
|
10
|
+
}, {
|
|
11
|
+
readonly stage: 'steady';
|
|
12
|
+
readonly disposition: 'required';
|
|
13
|
+
}];
|
|
14
|
+
readonly code: "SIBLING_CHECKOUT_UNAVAILABLE";
|
|
15
|
+
readonly isIssued: typeof isSiblingObservation;
|
|
16
|
+
readonly issuedAt: typeof observationIssuedAt;
|
|
17
|
+
}];
|
|
18
|
+
declare function isSiblingObservation(value: unknown): value is EnvironmentObservation;
|
|
19
|
+
declare function observationIssuedAt(value: unknown): number | undefined;
|
|
20
|
+
/** One row: the directories that should serve a sibling and do not exist. Passing means none. */
|
|
21
|
+
export declare function observeServedCheckouts(input: Readonly<{
|
|
22
|
+
stackId: StackId;
|
|
23
|
+
observedAt: string;
|
|
24
|
+
configPath: string;
|
|
25
|
+
config: Pick<CockpitConfig, 'cockpit' | 'sibling'>;
|
|
26
|
+
}>): Promise<readonly EnvironmentObservation[]>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Owns: observing whether each checkout recorded to serve a sibling still exists. It is the one
|
|
2
|
+
// verdict preflight, doctor and `up` share: `up` refuses a vanished served checkout, so a gate that
|
|
3
|
+
// only warned about it would pass a stack `up` cannot start. Must not: decide the health verdict
|
|
4
|
+
// (observations.ts) or inspect whether a checkout belongs to its sibling (sibling-checkouts.ts).
|
|
5
|
+
import { dirname } from 'node:path';
|
|
6
|
+
import { ENVIRONMENT_CODES, RefusalError, snapshotEnvironmentObservation, } from './contract.js';
|
|
7
|
+
import { loadServedSiblings, SIBLING_CHECKOUTS_PATH, vanishedCheckoutFix, vanishedServedCheckouts } from './sibling-checkouts.js';
|
|
8
|
+
const OWNER = 'sibling-observation';
|
|
9
|
+
export const observationDeclarations = Object.freeze([
|
|
10
|
+
{ id: 'served-checkouts', owner: OWNER, postures: ['environment'], stages: [{ stage: 'bootstrap', disposition: 'required' }, { stage: 'steady', disposition: 'required' }], code: ENVIRONMENT_CODES.SIBLING_CHECKOUT_UNAVAILABLE, isIssued: isSiblingObservation, issuedAt: observationIssuedAt },
|
|
11
|
+
]);
|
|
12
|
+
const issuedObservations = new WeakMap();
|
|
13
|
+
function isSiblingObservation(value) {
|
|
14
|
+
return value !== null && typeof value === 'object' && issuedObservations.has(value);
|
|
15
|
+
}
|
|
16
|
+
function observationIssuedAt(value) {
|
|
17
|
+
return value !== null && typeof value === 'object' ? issuedObservations.get(value) : undefined;
|
|
18
|
+
}
|
|
19
|
+
/** One row: the directories that should serve a sibling and do not exist. Passing means none. */
|
|
20
|
+
export async function observeServedCheckouts(input) {
|
|
21
|
+
const served = await loadServedSiblings(dirname(input.configPath), input.config);
|
|
22
|
+
let missing;
|
|
23
|
+
let remedy;
|
|
24
|
+
if (served.kind === 'refusal') {
|
|
25
|
+
// `up` refuses an unreadable record as well: no one can say which checkout serves a sibling.
|
|
26
|
+
missing = [SIBLING_CHECKOUTS_PATH];
|
|
27
|
+
remedy = served.diagnostic.fix ?? `repair ${SIBLING_CHECKOUTS_PATH}`;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const vanished = await vanishedServedCheckouts(served.siblings);
|
|
31
|
+
missing = vanished.map(sibling => sibling.served);
|
|
32
|
+
remedy = vanished.length === 0 ? 'no action needed' : vanished.map(sibling => `${sibling.slug}: ${vanishedCheckoutFix(sibling.slug)}`).join('; ');
|
|
33
|
+
}
|
|
34
|
+
const snapshot = snapshotEnvironmentObservation({
|
|
35
|
+
code: ENVIRONMENT_CODES.SIBLING_CHECKOUT_UNAVAILABLE, owner: OWNER, stackId: input.stackId, source: 'served-checkouts',
|
|
36
|
+
observed: { state: 'available', value: missing, observedAt: input.observedAt },
|
|
37
|
+
expectation: { kind: 'equal', value: [] },
|
|
38
|
+
applicability: { state: 'required', posture: 'environment' },
|
|
39
|
+
freshness: { state: 'current' }, remedy,
|
|
40
|
+
});
|
|
41
|
+
if (!snapshot)
|
|
42
|
+
throw new RefusalError('served-checkouts observation is malformed');
|
|
43
|
+
issuedObservations.set(snapshot, Date.now());
|
|
44
|
+
return [snapshot];
|
|
45
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { GitRunner } from './sibling-checkouts.js';
|
|
2
|
+
export type PullRow = Readonly<{
|
|
3
|
+
state: 'advanced';
|
|
4
|
+
path: string;
|
|
5
|
+
branch: string;
|
|
6
|
+
upstream: string;
|
|
7
|
+
from: string;
|
|
8
|
+
to: string;
|
|
9
|
+
commits: number;
|
|
10
|
+
}> | Readonly<{
|
|
11
|
+
state: 'current';
|
|
12
|
+
path: string;
|
|
13
|
+
branch: string;
|
|
14
|
+
upstream: string;
|
|
15
|
+
ahead: number;
|
|
16
|
+
}> | Readonly<{
|
|
17
|
+
state: 'skipped';
|
|
18
|
+
path: string;
|
|
19
|
+
reason: string;
|
|
20
|
+
fix: string;
|
|
21
|
+
blocked: boolean;
|
|
22
|
+
}>;
|
|
23
|
+
export declare function fastForwardCheckout(git: GitRunner, path: string): Promise<PullRow>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owns bringing one checkout up to its upstream, the way a careful person would: fetch, then
|
|
3
|
+
* fast-forward the branch that is checked out, and nothing else. It never merges, rebases,
|
|
4
|
+
* stashes, switches branch or touches a modified file; a checkout it cannot fast-forward cleanly
|
|
5
|
+
* is left exactly as it was and reported with the reason. That is what makes it safe to run
|
|
6
|
+
* unprompted: the only change it can make is one `git merge --ff-only` would make.
|
|
7
|
+
*/
|
|
8
|
+
import { formatShellCommand } from '../fix-catalogue.js';
|
|
9
|
+
import { parseGitBranchStatus } from './git-observation.js';
|
|
10
|
+
// Keys that make fetch or merge spawn a command the repository's own configuration names. Agents cannot
|
|
11
|
+
// write under `.git`, so this is defence in depth. Git lower-cases section and variable names in its output.
|
|
12
|
+
const COMMAND_KEYS = /^(?:core\.(?:sshcommand|askpass|gitproxy)|credential\.(?:.+\.)?helper|remote\..+\.(?:uploadpack|receivepack))$/;
|
|
13
|
+
async function commandBearingKey(git, path) {
|
|
14
|
+
const listed = await git.run(path, 'pull:config', ['config', '--list', '--name-only']);
|
|
15
|
+
if (listed.kind !== 'complete')
|
|
16
|
+
return { reason: `its Git configuration could not be read (${listed.detail})` };
|
|
17
|
+
return listed.stdout.split('\n').find(key => COMMAND_KEYS.test(key));
|
|
18
|
+
}
|
|
19
|
+
async function branchStatus(git, path) {
|
|
20
|
+
const status = await git.run(path, 'pull:status', ['status', '--porcelain=v2', '--branch', '--untracked-files=no']);
|
|
21
|
+
if (status.kind !== 'complete')
|
|
22
|
+
return { reason: `git status failed (${status.detail})` };
|
|
23
|
+
const parsed = parseGitBranchStatus(status.stdout);
|
|
24
|
+
return parsed.kind === 'complete' ? parsed : { reason: parsed.reason };
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The filter drivers the upstream's attributes name that the repository's own configuration does
|
|
28
|
+
* not define. cockpit's Git reads no global configuration, where `git lfs install` puts its driver,
|
|
29
|
+
* and a checkout through an undefined filter writes the stored pointer silently, with no error.
|
|
30
|
+
*/
|
|
31
|
+
async function unconfiguredFilters(git, path) {
|
|
32
|
+
const named = await git.run(path, 'pull:filters', ['grep', '-h', '-o', '-I', '-E', 'filter=[^[:space:]]+', '@{upstream}', '--', ':(glob)**/.gitattributes']);
|
|
33
|
+
// grep exits 1 when nothing matches.
|
|
34
|
+
if (named.kind !== 'complete')
|
|
35
|
+
return named.exitCode === 1 ? [] : { reason: `its attributes could not be read (${named.detail})` };
|
|
36
|
+
const drivers = [...new Set(named.stdout.split('\n').map(line => line.trim().slice('filter='.length)).filter(Boolean))];
|
|
37
|
+
// One call lists every filter variable; git exits 1 when there are none. Section and variable
|
|
38
|
+
// names are lower-cased in the output, the driver (subsection) keeps its case.
|
|
39
|
+
const configured = await git.run(path, 'pull:filter-drivers', ['config', '--get-regexp', '^filter\\.']);
|
|
40
|
+
if (configured.kind !== 'complete' && configured.exitCode !== 1)
|
|
41
|
+
return { reason: `its Git configuration could not be read (${configured.detail})` };
|
|
42
|
+
const defined = new Set();
|
|
43
|
+
for (const line of configured.kind === 'complete' ? configured.stdout.split('\n') : []) {
|
|
44
|
+
const match = /^filter\.(.+)\.(smudge|process)(?:\s|$)/.exec(line);
|
|
45
|
+
if (match)
|
|
46
|
+
defined.add(match[1]);
|
|
47
|
+
}
|
|
48
|
+
return drivers.filter(driver => !defined.has(driver));
|
|
49
|
+
}
|
|
50
|
+
export async function fastForwardCheckout(git, path) {
|
|
51
|
+
const shell = (...args) => formatShellCommand(['git', '-C', path, ...args]);
|
|
52
|
+
const skipped = (reason, fix, blocked = true) => ({ state: 'skipped', path, reason, fix, blocked });
|
|
53
|
+
const before = await branchStatus(git, path);
|
|
54
|
+
if ('reason' in before)
|
|
55
|
+
return skipped(`it is not a readable Git checkout: ${before.reason}`, `inspect it with ${shell('status')}`);
|
|
56
|
+
if (before.revision === '(initial)')
|
|
57
|
+
return skipped('it has no commits yet', `commit in it, or ignore it until it has a history`);
|
|
58
|
+
if (before.branch === '(detached)')
|
|
59
|
+
return skipped('HEAD is detached, so there is no branch to move', `check out a branch with ${shell('switch', '<branch>')} if it should follow one`, false);
|
|
60
|
+
if (before.upstream === null)
|
|
61
|
+
return skipped(`branch ${before.branch} tracks no upstream`, `set one with ${shell('branch', '--set-upstream-to', '<remote>/<branch>')} if it should follow one`, false);
|
|
62
|
+
const command = await commandBearingKey(git, path);
|
|
63
|
+
if (typeof command === 'object')
|
|
64
|
+
return skipped(command.reason, `inspect it with ${shell('config', '--list')}`);
|
|
65
|
+
if (command !== undefined)
|
|
66
|
+
return skipped(`its Git configuration sets ${command}, which would run a command during the fetch`, `review ${shell('config', '--list', '--show-origin')}; if you trust the setting, run ${shell('pull', '--ff-only')} yourself`);
|
|
67
|
+
const fetched = await git.run(path, 'pull:fetch', ['fetch', '--quiet', '--prune'], { timeoutMs: 120_000 });
|
|
68
|
+
if (fetched.kind !== 'complete') {
|
|
69
|
+
return skipped(`fetching ${before.upstream} failed (${fetched.detail})`, `run ${shell('fetch')} yourself to see why; cockpit fetches without the global Git configuration, so a credential helper set only there is not used`);
|
|
70
|
+
}
|
|
71
|
+
const after = await branchStatus(git, path);
|
|
72
|
+
if ('reason' in after)
|
|
73
|
+
return skipped(`it could not be read after fetching: ${after.reason}`, `inspect it with ${shell('status')}`);
|
|
74
|
+
const upstream = after.upstream ?? before.upstream;
|
|
75
|
+
if (!after.adjacencyReported)
|
|
76
|
+
return skipped(`${upstream}, which ${after.branch} tracks, no longer exists`, `push the branch to recreate it, or stop tracking it with ${shell('branch', '--unset-upstream')}`);
|
|
77
|
+
if (after.behind === 0)
|
|
78
|
+
return { state: 'current', path, branch: after.branch, upstream, ahead: after.ahead };
|
|
79
|
+
if (after.ahead > 0)
|
|
80
|
+
return skipped(`${after.branch} has diverged from ${upstream} (${after.behind} behind, ${after.ahead} ahead)`, `reconcile it by hand, for example with ${shell('rebase', upstream)}; cockpit only fast-forwards`);
|
|
81
|
+
if (after.trackedDirtyCount > 0)
|
|
82
|
+
return skipped(`${after.branch} is ${after.behind} behind ${upstream}, but ${after.trackedDirtyCount} tracked file(s) are modified`, `commit or stash the changes (see ${shell('status', '--short')}), then run pull again; cockpit never moves a checkout with work in it`);
|
|
83
|
+
const filters = await unconfiguredFilters(git, path);
|
|
84
|
+
if ('reason' in filters)
|
|
85
|
+
return skipped(filters.reason, `inspect it with ${shell('check-attr', '-a', '--', '<path>')}`);
|
|
86
|
+
if (filters.length > 0)
|
|
87
|
+
return skipped(`it uses the Git filter ${filters.join(', ')} (Git LFS, for one), which is configured only outside the repository, `
|
|
88
|
+
+ 'so a fast-forward here would check out its placeholder files instead of their content', `run ${shell('pull', '--ff-only')} yourself`);
|
|
89
|
+
const merged = await git.run(path, 'pull:fast-forward', ['merge', '--ff-only', '--quiet', '@{upstream}'], { timeoutMs: 120_000 });
|
|
90
|
+
if (merged.kind !== 'complete')
|
|
91
|
+
return skipped(`fast-forwarding ${after.branch} to ${upstream} failed (${merged.detail})`, `run ${shell('merge', '--ff-only', '@{upstream}')} yourself to see what is in the way`);
|
|
92
|
+
const head = await git.run(path, 'pull:head', ['rev-parse', 'HEAD']);
|
|
93
|
+
return { state: 'advanced', path, branch: after.branch, upstream, from: after.revision,
|
|
94
|
+
to: head.kind === 'complete' ? head.stdout : 'unknown', commits: after.behind };
|
|
95
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { type ExecuteProbeOutcome } from '../environment-exec/execute.js';
|
|
2
|
+
import type { Diagnostic } from '../diagnostics.js';
|
|
3
|
+
import { type ExecutionStep } from './contract.js';
|
|
4
|
+
export interface SiblingAddInput {
|
|
5
|
+
readonly configPath: string;
|
|
6
|
+
readonly slug: string;
|
|
7
|
+
readonly create: boolean;
|
|
8
|
+
readonly from?: string;
|
|
9
|
+
readonly remote?: string;
|
|
10
|
+
readonly dir?: string;
|
|
11
|
+
readonly purpose?: string;
|
|
12
|
+
readonly varName?: string;
|
|
13
|
+
/** How the stack uses the checkout; defaulted from the declared runtime when absent. */
|
|
14
|
+
readonly mount?: string;
|
|
15
|
+
readonly instructionPointer?: boolean;
|
|
16
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
17
|
+
}
|
|
18
|
+
export interface SiblingRemoveInput {
|
|
19
|
+
readonly configPath: string;
|
|
20
|
+
readonly slug: string;
|
|
21
|
+
readonly env: Readonly<Record<string, string | undefined>>;
|
|
22
|
+
}
|
|
23
|
+
export interface SiblingDependencies {
|
|
24
|
+
readonly execute: (step: ExecutionStep) => Promise<ExecuteProbeOutcome>;
|
|
25
|
+
}
|
|
26
|
+
export interface SiblingOutcome {
|
|
27
|
+
readonly status: number;
|
|
28
|
+
readonly result: unknown;
|
|
29
|
+
readonly diagnostics: readonly Diagnostic[];
|
|
30
|
+
readonly artifact?: string | null;
|
|
31
|
+
readonly destination?: string | null;
|
|
32
|
+
}
|
|
33
|
+
export type GitRepositoryInitializationOutcome = Readonly<{
|
|
34
|
+
kind: 'complete';
|
|
35
|
+
}> | Readonly<{
|
|
36
|
+
kind: 'refusal';
|
|
37
|
+
reason: string;
|
|
38
|
+
}>;
|
|
39
|
+
/** Initialises one local repository through the authenticated process seam used by sibling creation. */
|
|
40
|
+
export declare function initializeLocalGitRepository(input: Readonly<{
|
|
41
|
+
path: string;
|
|
42
|
+
env: Readonly<Record<string, string | undefined>>;
|
|
43
|
+
}>, dependencies: SiblingDependencies): Promise<GitRepositoryInitializationOutcome>;
|
|
44
|
+
export declare function runSiblingAdd(input: SiblingAddInput, dependencies: SiblingDependencies): Promise<SiblingOutcome>;
|
|
45
|
+
export declare function runSiblingRemove(input: SiblingRemoveInput): Promise<SiblingOutcome>;
|