@soleur/opencode 0.0.0-stage → 3.354.2
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/.claude-plugin/agents.manifest.json +476 -0
- package/.claude-plugin/plugin.json +42 -0
- package/README.md +44 -2
- package/agents/community/.gitkeep +0 -0
- package/agents/engineering/cto.md +87 -0
- package/agents/engineering/design/ddd-architect.md +95 -0
- package/agents/engineering/discovery/agent-finder.md +214 -0
- package/agents/engineering/discovery/functional-discovery.md +182 -0
- package/agents/engineering/infra/infra-security.md +151 -0
- package/agents/engineering/infra/platform-strategist.md +109 -0
- package/agents/engineering/infra/terraform-architect.md +171 -0
- package/agents/engineering/prompt-engineer.md +37 -0
- package/agents/engineering/research/best-practices-researcher.md +115 -0
- package/agents/engineering/research/framework-docs-researcher.md +93 -0
- package/agents/engineering/research/git-history-analyzer.md +53 -0
- package/agents/engineering/research/learnings-researcher.md +285 -0
- package/agents/engineering/research/repo-research-analyst.md +118 -0
- package/agents/engineering/review/agent-native-reviewer.md +279 -0
- package/agents/engineering/review/architecture-strategist.md +61 -0
- package/agents/engineering/review/code-quality-analyst.md +92 -0
- package/agents/engineering/review/code-simplicity-reviewer.md +109 -0
- package/agents/engineering/review/data-integrity-guardian.md +82 -0
- package/agents/engineering/review/data-migration-expert.md +98 -0
- package/agents/engineering/review/deployment-verification-agent.md +183 -0
- package/agents/engineering/review/dhh-rails-reviewer.md +46 -0
- package/agents/engineering/review/kieran-rails-reviewer.md +87 -0
- package/agents/engineering/review/legacy-code-expert.md +55 -0
- package/agents/engineering/review/observability-coverage-reviewer.md +147 -0
- package/agents/engineering/review/pattern-recognition-specialist.md +59 -0
- package/agents/engineering/review/performance-oracle.md +130 -0
- package/agents/engineering/review/security-sentinel.md +167 -0
- package/agents/engineering/review/semgrep-sast.md +36 -0
- package/agents/engineering/review/test-design-reviewer.md +150 -0
- package/agents/engineering/review/user-impact-reviewer.md +70 -0
- package/agents/engineering/workflow/pr-comment-resolver.md +69 -0
- package/agents/finance/budget-analyst.md +29 -0
- package/agents/finance/cfo.md +44 -0
- package/agents/finance/financial-reporter.md +30 -0
- package/agents/finance/revenue-analyst.md +30 -0
- package/agents/legal/clo.md +115 -0
- package/agents/legal/legal-compliance-auditor.md +110 -0
- package/agents/legal/legal-document-generator.md +134 -0
- package/agents/marketing/analytics-analyst.md +25 -0
- package/agents/marketing/brand-architect.md +181 -0
- package/agents/marketing/cmo.md +76 -0
- package/agents/marketing/conversion-optimizer.md +19 -0
- package/agents/marketing/copywriter.md +20 -0
- package/agents/marketing/fact-checker.md +70 -0
- package/agents/marketing/growth-strategist.md +145 -0
- package/agents/marketing/paid-media-strategist.md +28 -0
- package/agents/marketing/pricing-strategist.md +17 -0
- package/agents/marketing/programmatic-seo-specialist.md +25 -0
- package/agents/marketing/retention-strategist.md +23 -0
- package/agents/marketing/seo-aeo-analyst.md +164 -0
- package/agents/operations/coo.md +50 -0
- package/agents/operations/ops-advisor.md +78 -0
- package/agents/operations/ops-provisioner.md +101 -0
- package/agents/operations/ops-research.md +52 -0
- package/agents/operations/service-automator.md +278 -0
- package/agents/product/business-validator.md +206 -0
- package/agents/product/competitive-intelligence.md +89 -0
- package/agents/product/cpo.md +63 -0
- package/agents/product/design/ux-design-lead.md +209 -0
- package/agents/product/spec-flow-analyzer.md +122 -0
- package/agents/sales/cro.md +44 -0
- package/agents/sales/deal-architect.md +29 -0
- package/agents/sales/outbound-strategist.md +29 -0
- package/agents/sales/pipeline-analyst.md +29 -0
- package/agents/support/cco.md +48 -0
- package/agents/support/community-manager.md +570 -0
- package/agents/support/ticket-triage.md +70 -0
- package/bin/soleur-opencode.js +29 -0
- package/commands/go.md +660 -0
- package/commands/help.md +273 -0
- package/commands/sync.md +977 -0
- package/commands/version.md +103 -0
- package/hooks/antigravity-hooks.json +84 -0
- package/hooks/antigravity-session-start.sh +15 -0
- package/hooks/bare-root-cwd-guard.sh +131 -0
- package/hooks/browser-snapshot-credential-guard.sh +200 -0
- package/hooks/codex-session-start.sh +9 -0
- package/hooks/compaction-state.sh +525 -0
- package/hooks/destructive-command-guard.sh +1387 -0
- package/hooks/devin-session-start.sh +90 -0
- package/hooks/ext-filter-detect.sh +171 -0
- package/hooks/hooks.json +133 -0
- package/hooks/lib/hook-tool-kind.sh +24 -0
- package/hooks/lib/log-rotation.sh +176 -0
- package/hooks/lib/shell-argv.pl +576 -0
- package/hooks/memory-backstop.sh +1114 -0
- package/hooks/operator-stage-approval.sh +391 -0
- package/hooks/stop-hook.sh +367 -0
- package/hooks/unkept-promise-hook.sh +383 -0
- package/hooks/version-banner.sh +127 -0
- package/hooks/welcome-hook.sh +50 -0
- package/install.js +29 -0
- package/lib/agent-registry.ts +154 -0
- package/lib/c4-canonical-cli.mjs +34 -0
- package/lib/c4-canonical.mjs +46 -0
- package/lib/c4-from-components.ts +554 -0
- package/lib/go-routing.ts +81 -0
- package/lib/grok-inspect-contract.ts +311 -0
- package/lib/harness-model-map.ts +101 -0
- package/lib/harness-parity.ts +726 -0
- package/lib/harness-tool-map.ts +180 -0
- package/lib/harness.ts +716 -0
- package/lib/heartbeat-live-reconcile.ts +1153 -0
- package/lib/heartbeat-manifest.ts +361 -0
- package/lib/kb-coverage.ts +252 -0
- package/lib/pr-merge-poll.ts +103 -0
- package/lib/workflow-fidelity.ts +388 -0
- package/opencode/INSTRUCTIONS.md +171 -0
- package/opencode/agents/engineering/cto.md +10 -0
- package/opencode/agents/engineering/design/ddd-architect.md +10 -0
- package/opencode/agents/engineering/discovery/agent-finder.md +10 -0
- package/opencode/agents/engineering/discovery/functional-discovery.md +10 -0
- package/opencode/agents/engineering/infra/infra-security.md +10 -0
- package/opencode/agents/engineering/infra/platform-strategist.md +10 -0
- package/opencode/agents/engineering/infra/terraform-architect.md +10 -0
- package/opencode/agents/engineering/prompt-engineer.md +10 -0
- package/opencode/agents/engineering/research/best-practices-researcher.md +10 -0
- package/opencode/agents/engineering/research/framework-docs-researcher.md +10 -0
- package/opencode/agents/engineering/research/git-history-analyzer.md +10 -0
- package/opencode/agents/engineering/research/learnings-researcher.md +10 -0
- package/opencode/agents/engineering/research/repo-research-analyst.md +10 -0
- package/opencode/agents/engineering/review/agent-native-reviewer.md +10 -0
- package/opencode/agents/engineering/review/architecture-strategist.md +10 -0
- package/opencode/agents/engineering/review/code-quality-analyst.md +10 -0
- package/opencode/agents/engineering/review/code-simplicity-reviewer.md +10 -0
- package/opencode/agents/engineering/review/data-integrity-guardian.md +10 -0
- package/opencode/agents/engineering/review/data-migration-expert.md +10 -0
- package/opencode/agents/engineering/review/deployment-verification-agent.md +10 -0
- package/opencode/agents/engineering/review/dhh-rails-reviewer.md +10 -0
- package/opencode/agents/engineering/review/kieran-rails-reviewer.md +10 -0
- package/opencode/agents/engineering/review/legacy-code-expert.md +10 -0
- package/opencode/agents/engineering/review/observability-coverage-reviewer.md +10 -0
- package/opencode/agents/engineering/review/pattern-recognition-specialist.md +10 -0
- package/opencode/agents/engineering/review/performance-oracle.md +10 -0
- package/opencode/agents/engineering/review/security-sentinel.md +10 -0
- package/opencode/agents/engineering/review/semgrep-sast.md +10 -0
- package/opencode/agents/engineering/review/test-design-reviewer.md +10 -0
- package/opencode/agents/engineering/review/user-impact-reviewer.md +10 -0
- package/opencode/agents/engineering/workflow/pr-comment-resolver.md +10 -0
- package/opencode/agents/finance/budget-analyst.md +10 -0
- package/opencode/agents/finance/cfo.md +10 -0
- package/opencode/agents/finance/financial-reporter.md +10 -0
- package/opencode/agents/finance/revenue-analyst.md +10 -0
- package/opencode/agents/legal/clo.md +10 -0
- package/opencode/agents/legal/legal-compliance-auditor.md +10 -0
- package/opencode/agents/legal/legal-document-generator.md +10 -0
- package/opencode/agents/marketing/analytics-analyst.md +10 -0
- package/opencode/agents/marketing/brand-architect.md +10 -0
- package/opencode/agents/marketing/cmo.md +10 -0
- package/opencode/agents/marketing/conversion-optimizer.md +10 -0
- package/opencode/agents/marketing/copywriter.md +10 -0
- package/opencode/agents/marketing/fact-checker.md +10 -0
- package/opencode/agents/marketing/growth-strategist.md +10 -0
- package/opencode/agents/marketing/paid-media-strategist.md +10 -0
- package/opencode/agents/marketing/pricing-strategist.md +10 -0
- package/opencode/agents/marketing/programmatic-seo-specialist.md +10 -0
- package/opencode/agents/marketing/retention-strategist.md +10 -0
- package/opencode/agents/marketing/seo-aeo-analyst.md +10 -0
- package/opencode/agents/operations/coo.md +10 -0
- package/opencode/agents/operations/ops-advisor.md +10 -0
- package/opencode/agents/operations/ops-provisioner.md +10 -0
- package/opencode/agents/operations/ops-research.md +10 -0
- package/opencode/agents/operations/service-automator.md +10 -0
- package/opencode/agents/product/business-validator.md +10 -0
- package/opencode/agents/product/competitive-intelligence.md +10 -0
- package/opencode/agents/product/cpo.md +10 -0
- package/opencode/agents/product/design/ux-design-lead.md +10 -0
- package/opencode/agents/product/spec-flow-analyzer.md +10 -0
- package/opencode/agents/sales/cro.md +10 -0
- package/opencode/agents/sales/deal-architect.md +10 -0
- package/opencode/agents/sales/outbound-strategist.md +10 -0
- package/opencode/agents/sales/pipeline-analyst.md +10 -0
- package/opencode/agents/support/cco.md +10 -0
- package/opencode/agents/support/community-manager.md +10 -0
- package/opencode/agents/support/ticket-triage.md +10 -0
- package/opencode/commands/go.md +10 -0
- package/opencode/commands/help.md +10 -0
- package/opencode/commands/sync.md +10 -0
- package/opencode/commands/version.md +11 -0
- package/opencode/opencode.json +8 -0
- package/opencode/plugin.ts +818 -0
- package/package.json +36 -4
- package/scripts/admin-merge-ready.sh +545 -0
- package/scripts/admin-merge-ready.test.sh +753 -0
- package/scripts/affected-tests.sh +908 -0
- package/scripts/affected-tests.test.sh +1797 -0
- package/scripts/alpha-metrics.sh +78 -0
- package/scripts/alpha-metrics.test.sh +115 -0
- package/scripts/ancestor-signal-helpers.baseline.txt +16 -0
- package/scripts/audit-flag-flip.sh +107 -0
- package/scripts/check-red-on-main.sh +316 -0
- package/scripts/ci-head-verdict.sh +411 -0
- package/scripts/cloud-detect.sh +193 -0
- package/scripts/delegation-verify.sh +657 -0
- package/scripts/delegation-verify.test.sh +1424 -0
- package/scripts/deploy-arm.sh +475 -0
- package/scripts/domain-model-drift.sh +419 -0
- package/scripts/emit-decision.sh +144 -0
- package/scripts/emit-decision.test.sh +144 -0
- package/scripts/ext-filter-divergence-corpus.txt +15 -0
- package/scripts/ext-filter-divergence.sh +189 -0
- package/scripts/generate-c4-from-components.ts +442 -0
- package/scripts/grok-fidelity-gate.sh +68 -0
- package/scripts/grok-pre-push-gate.sh +232 -0
- package/scripts/harness-discovery-smoke.ts +906 -0
- package/scripts/harness-parity-census.ts +92 -0
- package/scripts/inngest-luks-alert-armed-probe.ts +29 -0
- package/scripts/install-opencode-adapter.ts +509 -0
- package/scripts/lib/affected-tests/common.sh +365 -0
- package/scripts/lib/affected-tests/go.sh +157 -0
- package/scripts/lib/affected-tests/js.sh +188 -0
- package/scripts/lib/affected-tests/nx-turbo.sh +98 -0
- package/scripts/lib/affected-tests/python.sh +185 -0
- package/scripts/lib/context-sanitize.sh +49 -0
- package/scripts/lib/domain-model-lib.sh +180 -0
- package/scripts/lib/ext-filter-grammar.tsv +34 -0
- package/scripts/lib/operator-script.sh +1236 -0
- package/scripts/lib/proc.sh +580 -0
- package/scripts/lib/session-state.sh +748 -0
- package/scripts/lib/tmp-classify.sh +843 -0
- package/scripts/lib/wrapper-boundary.sh +47 -0
- package/scripts/lint-ratchet.py +1547 -0
- package/scripts/monitor-pr-checks.sh +441 -0
- package/scripts/pipeline-tally.sh +756 -0
- package/scripts/pipeline-tally.test.sh +740 -0
- package/scripts/plugin-version.sh +528 -0
- package/scripts/precommit-guard.sh +115 -0
- package/scripts/precommit-guard.test.sh +254 -0
- package/scripts/reconcile-live-heartbeats.ts +705 -0
- package/scripts/render-c4-model.sh +217 -0
- package/scripts/resolve-git-root.sh +55 -0
- package/scripts/resolve-regenerable-conflicts.sh +363 -0
- package/scripts/resolve-regenerable-conflicts.test.sh +780 -0
- package/scripts/ruff-requirements.txt +22 -0
- package/scripts/run-in-pid-namespace.sh +98 -0
- package/scripts/run-in-pid-namespace.test.sh +290 -0
- package/scripts/scan-ancestor-signal-helpers.py +556 -0
- package/scripts/scan-ancestor-signal-helpers.test.sh +670 -0
- package/scripts/setup-ralph-loop.sh +238 -0
- package/scripts/sync-cursor-name-map.ts +316 -0
- package/scripts/sync-grok-agent-compat.ts +188 -0
- package/scripts/sync-npm-package.ts +165 -0
- package/scripts/sync-opencode-adapter.ts +498 -0
- package/scripts/sync-pr-behind.sh +647 -0
- package/scripts/sync-pr-behind.test.sh +252 -0
- package/scripts/taste-profile-update.sh +180 -0
- package/scripts/taste-profile-update.test.sh +167 -0
- package/scripts/write-budget-marker.sh +23 -0
- package/scripts/write-kb-coverage.ts +113 -0
- package/skills/admin-ip-refresh/SKILL.md +81 -0
- package/skills/admin-ip-refresh/references/admin-ip-refresh-procedure.md +201 -0
- package/skills/agent-browser/SKILL.md +698 -0
- package/skills/agent-browser/scripts/playwright-mcp-profile-slot.sh +304 -0
- package/skills/agent-browser/scripts/playwright-mcp-redact-proxy.py +964 -0
- package/skills/agent-browser/scripts/redact-a11y-snapshot.py +443 -0
- package/skills/agent-browser/test/fixtures/capture-playwright-mcp-fixtures.py +217 -0
- package/skills/agent-browser/test/fixtures/fake-passthrough-proxy.py +24 -0
- package/skills/agent-browser/test/fixtures/fake-playwright-mcp.py +131 -0
- package/skills/agent-browser/test/fixtures/make-proxy-mutant.py +48 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/dialog-link-injection.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/empty-content.json +5 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-and-result.json +4 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-code-bool.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-code-string.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-message-tree.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-no-data.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-string.json +4 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/error-with-data.json +9 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/evaluate-escaped-object.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/invalid-utf8.json +4 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/link-result.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/list-two-results.json +6 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/meta-in-result.json +13 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/no-content.json +5 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/quoted-key.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/resource-block.json +18 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/result-and-method.json +11 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/result-string.json +3 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped-one-row.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/run-code-escaped.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/structured-content.json +13 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/tools-list-no-tools.json +5 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/tree-no-credential.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/two-text-blocks.json +14 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/unparsable-line.json +3 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-brace.json +10 -0
- package/skills/agent-browser/test/fixtures/odd-shapes/value-contains-link-substring.json +10 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/close.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/evaluate.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/find.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/framing.json +6 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/initialize.json +14 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate-link.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/navigate.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/roots-list-request.json +5 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/screenshot.json +17 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-filename.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-json.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot-meta-raw.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/snapshot.json +12 -0
- package/skills/agent-browser/test/fixtures/playwright-mcp-0.0.78/tools-list.json +827 -0
- package/skills/agent-browser/test/fixtures/proxy-session-driver.py +138 -0
- package/skills/agent-browser/test/fixtures/session-facts.py +102 -0
- package/skills/agent-browser/test/playwright-mcp-lifetime.test.sh +1620 -0
- package/skills/agent-browser/test/playwright-mcp-redact-proxy.test.sh +1599 -0
- package/skills/agent-browser/test/redact-a11y-snapshot.test.sh +527 -0
- package/skills/agent-native-architecture/SKILL.md +479 -0
- package/skills/agent-native-architecture/references/action-parity-discipline.md +430 -0
- package/skills/agent-native-architecture/references/agent-execution-patterns.md +483 -0
- package/skills/agent-native-architecture/references/agent-native-testing.md +605 -0
- package/skills/agent-native-architecture/references/architecture-patterns.md +493 -0
- package/skills/agent-native-architecture/references/dynamic-context-injection.md +358 -0
- package/skills/agent-native-architecture/references/files-universal-interface.md +314 -0
- package/skills/agent-native-architecture/references/from-primitives-to-domain-tools.md +381 -0
- package/skills/agent-native-architecture/references/mcp-tool-design.md +524 -0
- package/skills/agent-native-architecture/references/mobile-patterns.md +897 -0
- package/skills/agent-native-architecture/references/product-implications.md +478 -0
- package/skills/agent-native-architecture/references/refactoring-to-prompt-native.md +342 -0
- package/skills/agent-native-architecture/references/self-modification.md +294 -0
- package/skills/agent-native-architecture/references/shared-workspace-architecture.md +700 -0
- package/skills/agent-native-architecture/references/system-prompt-design.md +263 -0
- package/skills/agent-native-audit/SKILL.md +300 -0
- package/skills/agent-native-audit/workflows/agent-native-audit.workflow.js +423 -0
- package/skills/andrew-kane-gem-writer/SKILL.md +189 -0
- package/skills/andrew-kane-gem-writer/references/database-adapters.md +231 -0
- package/skills/andrew-kane-gem-writer/references/module-organization.md +121 -0
- package/skills/andrew-kane-gem-writer/references/rails-integration.md +183 -0
- package/skills/andrew-kane-gem-writer/references/resources.md +126 -0
- package/skills/andrew-kane-gem-writer/references/testing-patterns.md +261 -0
- package/skills/architecture/SKILL.md +436 -0
- package/skills/architecture/references/adr-template.md +154 -0
- package/skills/architecture/references/likec4-reference.md +136 -0
- package/skills/architecture/references/nfr-reference.md +126 -0
- package/skills/archive-kb/SKILL.md +86 -0
- package/skills/archive-kb/scripts/archive-kb.sh +300 -0
- package/skills/archive-kb/test/archive-kb-partial-run.test.sh +276 -0
- package/skills/atdd-developer/SKILL.md +65 -0
- package/skills/brainstorm/SKILL.md +701 -0
- package/skills/brainstorm/references/brainstorm-brand-workshop.md +153 -0
- package/skills/brainstorm/references/brainstorm-domain-config.md +63 -0
- package/skills/brainstorm/references/brainstorm-validation-workshop.md +65 -0
- package/skills/brainstorm/references/ui-surface-terms.md +42 -0
- package/skills/brainstorm-techniques/SKILL.md +231 -0
- package/skills/brainstorm-techniques/references/decision-principles.md +150 -0
- package/skills/brainstorm-techniques/references/phase-boundaries.md +103 -0
- package/skills/brainstorm-techniques/references/standing-delegation.md +407 -0
- package/skills/brainstorm-techniques/references/technical-ruling-delegation.md +174 -0
- package/skills/campaign-calendar/SKILL.md +145 -0
- package/skills/cf-token-scope/SKILL.md +164 -0
- package/skills/cf-token-scope/references/widen-playbook.md +114 -0
- package/skills/cf-token-scope/scripts/cf-token-scope.sh +253 -0
- package/skills/changelog/SKILL.md +142 -0
- package/skills/code-to-prd/SKILL.md +66 -0
- package/skills/code-to-prd/references/banner-template.md +12 -0
- package/skills/code-to-prd/references/prd-template.md +41 -0
- package/skills/code-to-prd/scripts/code-to-prd.sh +634 -0
- package/skills/code-to-prd/test/code-to-prd.test.sh +447 -0
- package/skills/code-to-prd/test/fixture/.env.example +7 -0
- package/skills/code-to-prd/test/fixture/app/about/page.tsx +11 -0
- package/skills/code-to-prd/test/fixture/app/api/health/route.ts +10 -0
- package/skills/code-to-prd/test/fixture/app/page.tsx +14 -0
- package/skills/code-to-prd/test/fixture/next.config.mjs +3 -0
- package/skills/code-to-prd/test/fixture/package.json +6 -0
- package/skills/cohort-status/SKILL.md +62 -0
- package/skills/community/SKILL.md +210 -0
- package/skills/community/scripts/bsky-community.sh +644 -0
- package/skills/community/scripts/bsky-setup.sh +631 -0
- package/skills/community/scripts/community-router.sh +93 -0
- package/skills/community/scripts/discord-community.sh +487 -0
- package/skills/community/scripts/discord-setup.sh +784 -0
- package/skills/community/scripts/github-community.sh +706 -0
- package/skills/community/scripts/hn-community.sh +233 -0
- package/skills/community/scripts/lib/hmac-sha1-b64.sh +126 -0
- package/skills/community/scripts/linkedin-community.sh +750 -0
- package/skills/community/scripts/linkedin-setup.sh +758 -0
- package/skills/community/scripts/x-community.sh +890 -0
- package/skills/community/scripts/x-setup.sh +713 -0
- package/skills/community/test/community-argv.test.sh +2922 -0
- package/skills/community/test/github-community.test.sh +981 -0
- package/skills/competitive-analysis/SKILL.md +69 -0
- package/skills/competitive-analysis/references/peer-plugin-audit.md +218 -0
- package/skills/compound/SKILL.md +673 -0
- package/skills/compound/scripts/token-efficiency-report.sh +334 -0
- package/skills/compound/test/phase-16.test.sh +379 -0
- package/skills/compound-capture/SKILL.md +791 -0
- package/skills/compound-capture/assets/critical-pattern-template.md +37 -0
- package/skills/compound-capture/assets/resolution-template.md +116 -0
- package/skills/compound-capture/references/yaml-schema.md +67 -0
- package/skills/compound-capture/schema.yaml +181 -0
- package/skills/constraint-scaffold/SKILL.md +175 -0
- package/skills/constraint-scaffold/references/boundary-readme.template +35 -0
- package/skills/constraint-scaffold/references/constraint-gates-workflow.template +69 -0
- package/skills/constraint-scaffold/references/depcruise-config.template +230 -0
- package/skills/constraint-scaffold/references/fix-constraints-stage-a.template +240 -0
- package/skills/constraint-scaffold/references/fix-constraints-stage-b.template +313 -0
- package/skills/constraint-scaffold/references/shared-runner.template +135 -0
- package/skills/constraint-scaffold/scripts/constraint-scaffold.sh +589 -0
- package/skills/constraint-scaffold/test/bite-proof.test.sh +1338 -0
- package/skills/constraint-scaffold/test/boundary.test.sh +734 -0
- package/skills/constraint-scaffold/test/emit-fix-constraints.test.sh +193 -0
- package/skills/constraint-scaffold/test/generator.test.sh +93 -0
- package/skills/constraint-scaffold/test/parity.test.sh +285 -0
- package/skills/content-writer/SKILL.md +242 -0
- package/skills/content-writer/scripts/blog-jargon-scan.sh +42 -0
- package/skills/cron-delete/SKILL.md +56 -0
- package/skills/cron-list/SKILL.md +54 -0
- package/skills/deepen-plan/SKILL.md +937 -0
- package/skills/deepen-plan/workflows/deepen-plan.workflow.js +488 -0
- package/skills/deploy/SKILL.md +115 -0
- package/skills/deploy/references/hetzner-setup.md +67 -0
- package/skills/deploy/scripts/deploy.sh +37 -0
- package/skills/deploy-docs/SKILL.md +95 -0
- package/skills/dhh-rails-style/SKILL.md +203 -0
- package/skills/dhh-rails-style/references/architecture.md +717 -0
- package/skills/dhh-rails-style/references/controllers.md +323 -0
- package/skills/dhh-rails-style/references/frontend.md +556 -0
- package/skills/dhh-rails-style/references/gems.md +316 -0
- package/skills/dhh-rails-style/references/models.md +388 -0
- package/skills/dhh-rails-style/references/testing.md +354 -0
- package/skills/discord-content/SKILL.md +126 -0
- package/skills/docs-site/SKILL.md +214 -0
- package/skills/drain-labeled-backlog/SKILL.md +203 -0
- package/skills/drain-labeled-backlog/scripts/group-by-area.sh +208 -0
- package/skills/drain-labeled-backlog/workflows/drain-labeled-backlog.workflow.js +433 -0
- package/skills/drain-prs/SKILL.md +143 -0
- package/skills/drain-prs/scripts/triage-prs.sh +192 -0
- package/skills/drain-prs/test/argv-ceiling.test.sh +183 -0
- package/skills/dspy-ruby/SKILL.md +634 -0
- package/skills/dspy-ruby/assets/config-template.rb +359 -0
- package/skills/dspy-ruby/assets/module-template.rb +326 -0
- package/skills/dspy-ruby/assets/signature-template.rb +143 -0
- package/skills/dspy-ruby/references/core-concepts.md +268 -0
- package/skills/dspy-ruby/references/optimization.md +623 -0
- package/skills/dspy-ruby/references/providers.md +335 -0
- package/skills/eval-harness/README.md +194 -0
- package/skills/eval-harness/SKILL.md +157 -0
- package/skills/eval-harness/enums/go-routes.json +13 -0
- package/skills/eval-harness/enums/incident-threshold.json +5 -0
- package/skills/eval-harness/enums/lane.json +5 -0
- package/skills/eval-harness/enums/tool-selection.json +20 -0
- package/skills/eval-harness/enums/triage-levels.json +5 -0
- package/skills/eval-harness/gated-skills.json +34 -0
- package/skills/eval-harness/models.generated.json +5 -0
- package/skills/eval-harness/promptfooconfig-go-routing.yaml +35 -0
- package/skills/eval-harness/promptfooconfig-incident-threshold.yaml +32 -0
- package/skills/eval-harness/promptfooconfig-lane-inference.yaml +33 -0
- package/skills/eval-harness/promptfooconfig-ticket-triage.yaml +34 -0
- package/skills/eval-harness/promptfooconfig-tool-selection.yaml +33 -0
- package/skills/eval-harness/prompts/go-baseline.txt +6 -0
- package/skills/eval-harness/prompts/go-skill.txt +21 -0
- package/skills/eval-harness/prompts/incident-threshold-baseline.txt +6 -0
- package/skills/eval-harness/prompts/incident-threshold-skill.txt +12 -0
- package/skills/eval-harness/prompts/lane-inference-baseline.txt +6 -0
- package/skills/eval-harness/prompts/lane-inference-skill.txt +15 -0
- package/skills/eval-harness/prompts/tool-selection-baseline.txt +19 -0
- package/skills/eval-harness/prompts/tool-selection-skill.txt +22 -0
- package/skills/eval-harness/prompts/triage-baseline.txt +7 -0
- package/skills/eval-harness/prompts/triage-skill.txt +10 -0
- package/skills/eval-harness/scripts/eval-gate.cjs +318 -0
- package/skills/eval-harness/scripts/extract-block.cjs +87 -0
- package/skills/eval-harness/scripts/gate-classification.cjs +28 -0
- package/skills/eval-harness/scripts/gen-models.sh +41 -0
- package/skills/eval-harness/scripts/gen-skill-prompt.cjs +177 -0
- package/skills/eval-harness/scripts/measure-classification.cjs +30 -0
- package/skills/eval-harness/scripts/parse-label.cjs +93 -0
- package/skills/eval-harness/scripts/verdict.cjs +132 -0
- package/skills/eval-harness/tasks/go-routing.jsonl +8 -0
- package/skills/eval-harness/tasks/incident-threshold.jsonl +7 -0
- package/skills/eval-harness/tasks/lane-inference.jsonl +7 -0
- package/skills/eval-harness/tasks/ticket-triage.jsonl +6 -0
- package/skills/eval-harness/tasks/tool-selection.jsonl +15 -0
- package/skills/eval-harness/test/eval-gate.test.sh +132 -0
- package/skills/eval-harness/test/extract-block.test.sh +87 -0
- package/skills/eval-harness/test/gate-classification.test.sh +77 -0
- package/skills/eval-harness/test/gen-models.test.sh +75 -0
- package/skills/eval-harness/test/measure-classification.test.sh +74 -0
- package/skills/eval-harness/test/parse-label.test.sh +129 -0
- package/skills/eval-harness/test/registry-completeness.test.sh +109 -0
- package/skills/eval-harness/test/verdict.test.sh +123 -0
- package/skills/every-style-editor/SKILL.md +142 -0
- package/skills/every-style-editor/references/EVERY_WRITE_STYLE.md +529 -0
- package/skills/feature-tweet/SKILL.md +199 -0
- package/skills/feature-video/SKILL.md +412 -0
- package/skills/feature-video/scripts/check_deps.sh +281 -0
- package/skills/file-todos/SKILL.md +267 -0
- package/skills/file-todos/assets/todo-template.md +166 -0
- package/skills/fix-issue/SKILL.md +278 -0
- package/skills/fix-issue/references/agent-authored-exclusion.md +120 -0
- package/skills/fix-issue/references/exclude-label-jq-snippet.md +97 -0
- package/skills/flag-bootstrap/SETUP.md +142 -0
- package/skills/flag-create/SKILL.md +145 -0
- package/skills/flag-create/scripts/create.sh +273 -0
- package/skills/flag-delete/SKILL.md +167 -0
- package/skills/flag-delete/scripts/delete.sh +288 -0
- package/skills/flag-list/SKILL.md +99 -0
- package/skills/flag-list/scripts/list.sh +275 -0
- package/skills/flag-set-role/SKILL.md +229 -0
- package/skills/flag-set-role/scripts/flip.sh +774 -0
- package/skills/frontend-anti-slop/SKILL.md +106 -0
- package/skills/frontend-anti-slop/references/anti-patterns.md +199 -0
- package/skills/frontend-anti-slop/references/slop-rules.md +65 -0
- package/skills/frontend-anti-slop/scripts/tier1-scan.ts +525 -0
- package/skills/frontend-design/SKILL.md +77 -0
- package/skills/gdpr-gate/NOTICE +136 -0
- package/skills/gdpr-gate/SKILL.md +341 -0
- package/skills/gdpr-gate/references/fields.md +160 -0
- package/skills/gdpr-gate/references/layers/api-layer.md +355 -0
- package/skills/gdpr-gate/references/layers/auth-sessions.md +290 -0
- package/skills/gdpr-gate/references/layers/data-in-transit.md +331 -0
- package/skills/gdpr-gate/references/layers/data-lifecycle.md +341 -0
- package/skills/gdpr-gate/references/layers/frontend.md +249 -0
- package/skills/gdpr-gate/references/layers/testing-seeding.md +256 -0
- package/skills/gdpr-gate/references/leakage-vectors.md +248 -0
- package/skills/gdpr-gate/references/legacy/legal-consent-v1-prose.md +48 -0
- package/skills/gdpr-gate/references/legal-consent.md +140 -0
- package/skills/gdpr-gate/references/non-negotiables.md +60 -0
- package/skills/gdpr-gate/scripts/gdpr-gate.sh +271 -0
- package/skills/gdpr-gate/scripts/notice-frontmatter.sh +337 -0
- package/skills/gdpr-gate/scripts/path-denylist.txt +39 -0
- package/skills/gdpr-gate/scripts/repo-scan.sh +158 -0
- package/skills/gdpr-gate/scripts/vendor-drift-classify.sh +120 -0
- package/skills/gdpr-gate/scripts/vendor-pin-integrity.sh +237 -0
- package/skills/gemini-imagegen/SKILL.md +283 -0
- package/skills/gemini-imagegen/requirements.txt +5 -0
- package/skills/gemini-imagegen/scripts/_error_handling.py +146 -0
- package/skills/gemini-imagegen/scripts/compose_images.py +150 -0
- package/skills/gemini-imagegen/scripts/edit_image.py +137 -0
- package/skills/gemini-imagegen/scripts/gemini_images.py +256 -0
- package/skills/gemini-imagegen/scripts/generate_image.py +147 -0
- package/skills/gemini-imagegen/scripts/multi_turn_chat.py +223 -0
- package/skills/gemini-imagegen/scripts/test_error_handling.py +179 -0
- package/skills/git-worktree/SKILL.md +534 -0
- package/skills/git-worktree/scripts/git-repo-readiness-diag.sh +98 -0
- package/skills/git-worktree/scripts/worktree-manager.sh +4773 -0
- package/skills/git-worktree/test/create-from-origin-main.test.sh +116 -0
- package/skills/git-worktree/test/lease-protects-active.test.sh +1056 -0
- package/skills/git-worktree/test/no-repo-fail-loud.test.sh +88 -0
- package/skills/git-worktree/test/orphan-reaper-honest-count.test.sh +379 -0
- package/skills/git-worktree/test/reap-archive-persistence.test.sh +725 -0
- package/skills/git-worktree/test/stale-lock-sweep.test.sh +176 -0
- package/skills/go/SKILL.md +54 -0
- package/skills/growth/SKILL.md +164 -0
- package/skills/harvest-debt/SKILL.md +78 -0
- package/skills/harvest-debt/scripts/harvest-debt.sh +114 -0
- package/skills/heal-skill/SKILL.md +173 -0
- package/skills/help/SKILL.md +46 -0
- package/skills/incident/NOTICE +39 -0
- package/skills/incident/SKILL.md +395 -0
- package/skills/incident/scripts/dry-run.sh +448 -0
- package/skills/incident/scripts/redact-engine.py +412 -0
- package/skills/incident/scripts/redact-sentinel.sh +44 -0
- package/skills/incident/templates/pir.md +139 -0
- package/skills/incident/test/fixtures/dry-run-incident.json +26 -0
- package/skills/incident/test/fixtures/dry-run-secret-leak.json +26 -0
- package/skills/incident/test/fixtures/legacy-bash-scanner.sh +82 -0
- package/skills/incident/test/fixtures/positive-corpus.md +87 -0
- package/skills/incident/test/redact-sentinel.test.sh +1420 -0
- package/skills/invoice/SKILL.md +206 -0
- package/skills/kb-glossary/SKILL.md +71 -0
- package/skills/kb-glossary/references/glossary-format.md +111 -0
- package/skills/kb-glossary/references/rejected-request-register.md +115 -0
- package/skills/kb-search/SKILL.md +276 -0
- package/skills/kb-search/scripts/kb-search-cache.sh +123 -0
- package/skills/legal-audit/SKILL.md +161 -0
- package/skills/legal-generate/NOTICE +242 -0
- package/skills/legal-generate/SKILL.md +244 -0
- package/skills/legal-generate/references/templates/advisor-agreement/template.md +12 -0
- package/skills/legal-generate/references/templates/business-associate-agreement/template.md +112 -0
- package/skills/legal-generate/references/templates/cookie-notice/template.md +82 -0
- package/skills/legal-generate/references/templates/dpa-global/template.md +439 -0
- package/skills/legal-generate/references/templates/dpa-us/template.md +222 -0
- package/skills/legal-generate/references/templates/employee-offer-letter/template.md +75 -0
- package/skills/legal-generate/references/templates/master-services-agreement/template.md +252 -0
- package/skills/legal-generate/references/templates/mutual-nda/template.md +66 -0
- package/skills/legal-generate/references/templates/one-way-nda/template.md +38 -0
- package/skills/legal-generate/references/templates/privacy-policy-gdpr/template.md +427 -0
- package/skills/legal-generate/references/templates/privacy-policy-us/template.md +313 -0
- package/skills/legal-generate/references/templates/terms-of-use/template.md +75 -0
- package/skills/legal-generate/scripts/strip-vendor-credit.sh +96 -0
- package/skills/linear-fetch/SKILL.md +247 -0
- package/skills/linear-fetch/scripts/assert-no-linear-telemetry.sh +66 -0
- package/skills/linear-fetch/scripts/redact-linear-urls.sh +51 -0
- package/skills/linear-fetch/scripts/render-caller-template.sh +36 -0
- package/skills/linear-fetch/test/assert-no-linear-telemetry.test.sh +113 -0
- package/skills/linear-fetch/test/parity.test.sh +112 -0
- package/skills/linear-fetch/test/persist-safe-integration.test.sh +191 -0
- package/skills/linear-fetch/test/redact-linear-urls.test.sh +226 -0
- package/skills/merge-pr/SKILL.md +670 -0
- package/skills/model-launch-review/SKILL.md +255 -0
- package/skills/model-launch-review/scripts/audit-models.sh +501 -0
- package/skills/one-shot/SKILL.md +316 -0
- package/skills/one-shot/references/token-discipline.md +74 -0
- package/skills/operator-bootstrap/SKILL.md +412 -0
- package/skills/operator-bootstrap/template.sh +439 -0
- package/skills/operator-digest/SKILL.md +287 -0
- package/skills/operator-digest/assets/operator-digest.workflow.yml +175 -0
- package/skills/operator-digest/scripts/digest-scrub.sh +119 -0
- package/skills/operator-digest/scripts/provision-operator-digest-repo.sh +147 -0
- package/skills/operator-rephrase/SKILL.md +98 -0
- package/skills/pencil-setup/SKILL.md +201 -0
- package/skills/pencil-setup/scripts/check_deps.sh +556 -0
- package/skills/pencil-setup/scripts/copy_adapter.sh +80 -0
- package/skills/pencil-setup/scripts/package-lock.json +1172 -0
- package/skills/pencil-setup/scripts/package.json +14 -0
- package/skills/pencil-setup/scripts/pencil-error-enrichment.mjs +21 -0
- package/skills/pencil-setup/scripts/pencil-mcp-adapter.mjs +733 -0
- package/skills/pencil-setup/scripts/pencil-response-classify.mjs +38 -0
- package/skills/pencil-setup/scripts/pencil-save-gate.mjs +26 -0
- package/skills/pencil-setup/scripts/sanitize-filename.mjs +25 -0
- package/skills/plan/SKILL.md +1140 -0
- package/skills/plan/references/differential-migration-testing.md +185 -0
- package/skills/plan/references/plan-community-discovery.md +42 -0
- package/skills/plan/references/plan-founder-check.md +137 -0
- package/skills/plan/references/plan-functional-overlap.md +25 -0
- package/skills/plan/references/plan-host-fanout.md +51 -0
- package/skills/plan/references/plan-issue-templates.md +779 -0
- package/skills/plan/references/plan-network-outage-checklist.md +131 -0
- package/skills/plan/references/plan-scope-check.md +126 -0
- package/skills/plan/references/plan-sharp-edges.md +331 -0
- package/skills/plan/references/plan-signoff-lifecycle.md +12 -0
- package/skills/plan/references/webapp-cwv-observability.md +143 -0
- package/skills/plan-review/SKILL.md +91 -0
- package/skills/plan-review/lib/named-panel.mjs +58 -0
- package/skills/plan-review/workflows/plan-review.workflow.js +477 -0
- package/skills/postmerge/SKILL.md +642 -0
- package/skills/postmerge/references/deploy-status-debugging.md +141 -0
- package/skills/postmerge/references/graceful-degradation.md +20 -0
- package/skills/postmerge/references/sentry-error-count-delta.md +137 -0
- package/skills/preflight/SKILL.md +1553 -0
- package/skills/preflight/references/check-13-founder-check.md +286 -0
- package/skills/preflight/scripts/founder-check.py +1640 -0
- package/skills/preflight/scripts/parse-form-a.awk +107 -0
- package/skills/preflight/scripts/probe-verb-gate.sh +108 -0
- package/skills/product-roadmap/SKILL.md +357 -0
- package/skills/product-roadmap/scripts/roadmap-reconcile.sh +327 -0
- package/skills/provision-cloudflare/SKILL.md +67 -0
- package/skills/provision-cloudflare/scripts/provision-cloudflare.sh +271 -0
- package/skills/provision-doppler/SKILL.md +58 -0
- package/skills/provision-doppler/scripts/provision-doppler.sh +358 -0
- package/skills/provision-github/SKILL.md +59 -0
- package/skills/provision-github/scripts/provision-github.sh +300 -0
- package/skills/provision-hetzner/SKILL.md +76 -0
- package/skills/provision-hetzner/scripts/provision-hetzner.sh +262 -0
- package/skills/provision-hetzner/test/fixture/knowledge-base/legal/tenant-dpa-register.md +26 -0
- package/skills/provision-hetzner/test/fixture/provision-hetzner-dry-run.golden +34 -0
- package/skills/provision-hetzner/test/provision-hetzner-characterization.test.sh +227 -0
- package/skills/qa/SKILL.md +263 -0
- package/skills/qa/references/browser-preflight.md +22 -0
- package/skills/questionnaire-generate/SKILL.md +328 -0
- package/skills/questionnaire-generate/references/questionnaire.template +60 -0
- package/skills/rclone/SKILL.md +162 -0
- package/skills/rclone/scripts/check_setup.sh +63 -0
- package/skills/release-announce/SKILL.md +65 -0
- package/skills/release-docs/SKILL.md +92 -0
- package/skills/reproduce-bug/SKILL.md +358 -0
- package/skills/resolve-debt/SKILL.md +85 -0
- package/skills/resolve-debt/scripts/resolve-debt.py +584 -0
- package/skills/resolve-parallel/SKILL.md +45 -0
- package/skills/resolve-parallel/workflows/resolve-parallel.workflow.js +415 -0
- package/skills/resolve-pr-parallel/SKILL.md +68 -0
- package/skills/resolve-pr-parallel/workflows/resolve-pr-parallel.workflow.js +419 -0
- package/skills/resolve-todo-parallel/SKILL.md +57 -0
- package/skills/resolve-todo-parallel/workflows/resolve-todo-parallel.workflow.js +398 -0
- package/skills/review/SKILL.md +1620 -0
- package/skills/review/references/anti-slop-scanner-hook.md +40 -0
- package/skills/review/references/defect-classes-migrations.md +9 -0
- package/skills/review/references/generated-input-oracle-surfaces.md +15 -0
- package/skills/review/references/review-e2e-testing.md +116 -0
- package/skills/review/references/review-evidence-comment.md +36 -0
- package/skills/review/references/review-todo-structure.md +165 -0
- package/skills/review/references/risk-tier-and-fix-rounds.md +97 -0
- package/skills/review/references/semgrep-custom-rules.yaml +174 -0
- package/skills/review/references/ultrathink-phases.md +98 -0
- package/skills/review/references/wfs.md +6 -0
- package/skills/review/scripts/emit-review-trailer.sh +499 -0
- package/skills/review/scripts/ensure-semgrep.sh +99 -0
- package/skills/review/scripts/fix-round-seats.sh +197 -0
- package/skills/review/test/emit-review-trailer.test.sh +387 -0
- package/skills/review/test/ensure-semgrep.test.sh +111 -0
- package/skills/review/test/fix-round-seats.test.sh +237 -0
- package/skills/review/workflows/README.md +147 -0
- package/skills/review/workflows/review.workflow.js +667 -0
- package/skills/schedule/SKILL.md +796 -0
- package/skills/seo-aeo/SKILL.md +139 -0
- package/skills/seo-aeo/scripts/validate-csp.sh +233 -0
- package/skills/seo-aeo/scripts/validate-seo.sh +262 -0
- package/skills/ship/SKILL.md +3023 -0
- package/skills/ship/references/adr-ordinal-collision.md +47 -0
- package/skills/ship/references/affected-gate-contract.md +115 -0
- package/skills/ship/references/ci-run-diagnosis.md +29 -0
- package/skills/ship/references/ci-workflow-authoring.md +68 -0
- package/skills/ship/references/cleanup-merged-archival.md +39 -0
- package/skills/ship/references/counsel-review-clo-attestation.md +38 -0
- package/skills/ship/references/cto-ruling-armed-push.md +5 -0
- package/skills/ship/references/decision-challenges-render.md +71 -0
- package/skills/ship/references/delegated-act-authoring-form.md +7 -0
- package/skills/ship/references/detached-battery-verdict.md +13 -0
- package/skills/ship/references/epic-close-sweep.md +62 -0
- package/skills/ship/references/feature-tweet-draft.md +42 -0
- package/skills/ship/references/followthrough-issue-body-template.md +47 -0
- package/skills/ship/references/followthrough-stub-template.sh +92 -0
- package/skills/ship/references/merge-queue-dequeue.md +39 -0
- package/skills/ship/references/operator-holds.md +179 -0
- package/skills/ship/references/pipeline-tally-render.md +43 -0
- package/skills/ship/references/pr-overlap.md +153 -0
- package/skills/ship/references/queue-mode.md +7 -0
- package/skills/ship/references/ready-run-wait.md +36 -0
- package/skills/ship/references/red-on-main-quarantine.md +110 -0
- package/skills/ship/references/register-update-pr-pattern.md +88 -0
- package/skills/ship/references/settle-then-admin-merge.md +130 -0
- package/skills/ship/references/supabase-column-probe.md +13 -0
- package/skills/ship/scripts/auto-close-scan.sh +76 -0
- package/skills/ship/scripts/battery-owed.sh +403 -0
- package/skills/ship/scripts/hold-gate-eval.sh +410 -0
- package/skills/ship/scripts/net-issue-flow.sh +665 -0
- package/skills/ship/scripts/pr-overlap-preflight.sh +483 -0
- package/skills/ship/scripts/ship-pir-action-items-gate.sh +246 -0
- package/skills/ship/scripts/technical-ruling-gate.sh +444 -0
- package/skills/skill-creator/SKILL.md +299 -0
- package/skills/skill-creator/references/api-security.md +248 -0
- package/skills/skill-creator/references/authoring-levers.md +110 -0
- package/skills/skill-creator/references/be-clear-and-direct.md +587 -0
- package/skills/skill-creator/references/best-practices.md +431 -0
- package/skills/skill-creator/references/common-patterns.md +639 -0
- package/skills/skill-creator/references/core-principles.md +193 -0
- package/skills/skill-creator/references/executable-code.md +188 -0
- package/skills/skill-creator/references/iteration-and-testing.md +509 -0
- package/skills/skill-creator/references/official-spec.md +193 -0
- package/skills/skill-creator/references/recommended-structure.md +180 -0
- package/skills/skill-creator/references/skill-structure.md +396 -0
- package/skills/skill-creator/references/using-scripts.md +122 -0
- package/skills/skill-creator/references/using-templates.md +120 -0
- package/skills/skill-creator/references/workflows-and-validation.md +541 -0
- package/skills/skill-creator/scripts/init_skill.py +322 -0
- package/skills/skill-creator/scripts/package_skill.py +110 -0
- package/skills/skill-creator/scripts/quick_validate.py +64 -0
- package/skills/skill-creator/templates/router-skill.md +73 -0
- package/skills/skill-creator/templates/simple-skill.md +34 -0
- package/skills/skill-creator/workflows/add-reference.md +96 -0
- package/skills/skill-creator/workflows/add-script.md +104 -0
- package/skills/skill-creator/workflows/add-template.md +83 -0
- package/skills/skill-creator/workflows/add-workflow.md +128 -0
- package/skills/skill-creator/workflows/audit-skill.md +159 -0
- package/skills/skill-creator/workflows/create-domain-expertise-skill.md +623 -0
- package/skills/skill-creator/workflows/create-new-skill.md +207 -0
- package/skills/skill-creator/workflows/get-guidance.md +134 -0
- package/skills/skill-creator/workflows/upgrade-to-router.md +170 -0
- package/skills/skill-creator/workflows/verify-skill.md +226 -0
- package/skills/skill-security-scan/SKILL.md +145 -0
- package/skills/skill-security-scan/references/disclaimer.md +21 -0
- package/skills/skill-security-scan/references/first-party-allowlist.yaml +38 -0
- package/skills/skill-security-scan/references/override-artifact-schema.json +68 -0
- package/skills/skill-security-scan/references/override-mechanism.md +171 -0
- package/skills/skill-security-scan/references/redirect-domains.yaml +27 -0
- package/skills/skill-security-scan/references/regex-patterns.md +136 -0
- package/skills/skill-security-scan/references/rules/code-exec.yaml +70 -0
- package/skills/skill-security-scan/references/rules/filesystem-boundary.yaml +29 -0
- package/skills/skill-security-scan/references/rules/manifest.yaml +44 -0
- package/skills/skill-security-scan/references/rules/supply-chain.yaml +36 -0
- package/skills/skill-security-scan/references/scan-meta-schema.json +63 -0
- package/skills/skill-security-scan/references/test-fixtures/clean-soleur-style.skill.md +23 -0
- package/skills/skill-security-scan/references/test-fixtures/clean-third-party.skill.md +26 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-codeexec.skill.md +23 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-curl-pipe-bash.skill.md +69 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-prompt-injection.skill.md +17 -0
- package/skills/skill-security-scan/references/test-fixtures/malicious-telemetry-beacon.skill.md +23 -0
- package/skills/skill-security-scan/references/typosquat-targets.yaml +65 -0
- package/skills/skill-security-scan/scripts/check-codeexec.sh +41 -0
- package/skills/skill-security-scan/scripts/check-filesystem-boundary.sh +19 -0
- package/skills/skill-security-scan/scripts/check-prompt-injection.sh +95 -0
- package/skills/skill-security-scan/scripts/check-supply-chain.sh +219 -0
- package/skills/skill-security-scan/scripts/check-telemetry-surface.sh +126 -0
- package/skills/skill-security-scan/scripts/lib.sh +197 -0
- package/skills/skill-security-scan/scripts/parse-override.sh +231 -0
- package/skills/skill-security-scan/scripts/run-scan.sh +342 -0
- package/skills/skill-security-scan/scripts/run-self-test.sh +95 -0
- package/skills/skill-security-scan/test/argv-ceiling.test.sh +180 -0
- package/skills/skill-security-scan/test/meta-dir-reap.test.sh +411 -0
- package/skills/skill-security-scan/test/parse-override.test.sh +607 -0
- package/skills/skill-security-scan/test/redact-pii-ghs-shape.test.sh +251 -0
- package/skills/social-distribute/SKILL.md +485 -0
- package/skills/spec-templates/SKILL.md +274 -0
- package/skills/sync/SKILL.md +49 -0
- package/skills/test-browser/SKILL.md +464 -0
- package/skills/test-browser/references/feature-map.md +161 -0
- package/skills/test-fix-loop/SKILL.md +159 -0
- package/skills/test-fix-loop/references/determinism-checklist.md +154 -0
- package/skills/triage/SKILL.md +402 -0
- package/skills/trigger-cron/SKILL.md +136 -0
- package/skills/trigger-cron/scripts/trigger.sh +198 -0
- package/skills/user-set-role/SKILL.md +147 -0
- package/skills/user-set-role/scripts/set-role.sh +218 -0
- package/skills/user-story-writer/SKILL.md +77 -0
- package/skills/ux-audit/SKILL.md +251 -0
- package/skills/ux-audit/references/finding.schema.json +62 -0
- package/skills/ux-audit/references/route-list.yaml +95 -0
- package/skills/ux-audit/scripts/bot-fixture.ts +270 -0
- package/skills/ux-audit/scripts/bot-signin.ts +135 -0
- package/skills/ux-audit/scripts/dedup-hash.ts +46 -0
- package/skills/verification-scaffold/SKILL.md +73 -0
- package/skills/verification-scaffold/references/gitleaks.toml.template +13 -0
- package/skills/verification-scaffold/references/guardrail-audit.md +156 -0
- package/skills/verification-scaffold/references/guards/block-stash-hook.sh.template +72 -0
- package/skills/verification-scaffold/references/guards/commit-on-main-hook.sh.template +82 -0
- package/skills/verification-scaffold/references/guards/orphan-test-check.sh.template +102 -0
- package/skills/verification-scaffold/references/guards/precommit-guard.sh.template +115 -0
- package/skills/verification-scaffold/references/guards/secret-scan-hook.sh.template +176 -0
- package/skills/verification-scaffold/references/guards/verification-gates.yml.template +49 -0
- package/skills/verification-scaffold/references/guards/wrapper-boundary.sh.template +47 -0
- package/skills/verification-scaffold/references/guards.tsv +21 -0
- package/skills/verification-scaffold/references/verification-readme.template +55 -0
- package/skills/verification-scaffold/scripts/verification-scaffold.sh +785 -0
- package/skills/verification-scaffold/test/bite-proof.test.sh +159 -0
- package/skills/verification-scaffold/test/generator.test.sh +319 -0
- package/skills/verification-scaffold/test/manifest.test.sh +144 -0
- package/skills/work/SKILL.md +1480 -0
- package/skills/work/references/end-of-work-resume.md +7 -0
- package/skills/work/references/hook-test-main-cwd.md +24 -0
- package/skills/work/references/key-principles.md +83 -0
- package/skills/work/references/property-testing.md +122 -0
- package/skills/work/references/sql-security-definer-rpc-scaffold.sql +78 -0
- package/skills/work/references/work-agent-teams.md +97 -0
- package/skills/work/references/work-lifecycle-parallel.md +147 -0
- package/skills/work/references/work-lockfile-bumps.md +97 -0
- package/skills/work/references/work-scratch-sandboxes.md +212 -0
- package/skills/work/references/work-subagent-fanout.md +61 -0
- package/skills/work/references/work-test-harness-sharp-edges.md +73 -0
- package/skills/xcode-test/SKILL.md +360 -0
|
@@ -0,0 +1,657 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# shellcheck disable=SC2015,SC2018,SC2019 # A && B || C is the check idiom (C is a fail_* that exits); tr A-Z is the portable ASCII lowering
|
|
3
|
+
# The shared validity chokepoint for a Standing Delegation and its Production Envelope.
|
|
4
|
+
# Format source (the only one): plugins/soleur/skills/brainstorm-techniques/references/standing-delegation.md
|
|
5
|
+
#
|
|
6
|
+
# Property: the verifier reports `valid` only when a marker-first, unedited, unexpired, admin-authored,
|
|
7
|
+
# person-authored (not an App) comment on THIS repository's still-open epic carries a verbatim quote, a
|
|
8
|
+
# Confirmation that starts with Yes, the cited Scope item and the fixed Excluded sentence, and (when asked) a
|
|
9
|
+
# complete envelope READ FROM HEAD (never the working tree) for that same epic, whose every Command line is on
|
|
10
|
+
# a closed grammar (merge, ready, workflow dispatch, create-change), is covered by the cited Scope item's own
|
|
11
|
+
# words, and contains each asked command as a byte-identical FULL line. Anything it cannot evaluate is exit 2.
|
|
12
|
+
#
|
|
13
|
+
# Callers: technical-ruling-gate.sh, hold-gate-eval.sh and the CTO's pre-execution check. A caller that
|
|
14
|
+
# re-implements any conjunct here is the defect. Exit 2 always blocks its caller and is never retried into a pass.
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# delegation-verify.sh verify --delegation <comment-url> [--envelope <path[#title]>] [--command <line>]...
|
|
18
|
+
# [--audit --at <YYYY-MM-DDTHH:MM:SSZ>]
|
|
19
|
+
# delegation-verify.sh render --epic <N> --question-file <f> --answer-file <f> --scope-file <f> --expiry <YYYY-MM-DD>
|
|
20
|
+
#
|
|
21
|
+
# stdout (verify): `delegation: valid|invalid|unverifiable reason=<token>`; on `valid` always, next,
|
|
22
|
+
# `verified-created-at: <ISO time the delegation comment was created>` and `verified-expiry: <YYYY-MM-DD>` (the
|
|
23
|
+
# window a receipt is bound to); with an envelope and `valid` also
|
|
24
|
+
# `verified-envelope-sha256: <hex>` (the sha256 of the HEAD blob that was read), and with --command per command
|
|
25
|
+
# `verified-command: <the exact envelope line>` and `verified-sha256: <hex>`, so the caller runs the line byte for
|
|
26
|
+
# byte. stderr: one plain sentence. Exit 0 valid, 1 invalid, 2 unverifiable or usage.
|
|
27
|
+
# --audit (ship-time gate, for an act that already ran) REQUIRES --at <time the act ran>: it then enforces
|
|
28
|
+
# created_at <= at <= the end of the expiry day, and skips exactly one check, `epic-closed`: a delegated act that
|
|
29
|
+
# ran legitimately must not fail its own audit row after the epic closes. Plain verify enforces expiry and
|
|
30
|
+
# epic-open against now.
|
|
31
|
+
#
|
|
32
|
+
# Reason tokens: bad-url wrong-repo comment-unreadable api-error tool-missing usage no-marker wrong-issue
|
|
33
|
+
# epic-is-pr bot-author not-admin-author edited no-quote quote-too-short no-confirmation no-scope-item
|
|
34
|
+
# excluded-incomplete expiry-missing expiry-too-long expired before-delegation epic-closed envelope-missing
|
|
35
|
+
# envelope-not-committed envelope-outside-repo envelope-not-regular-file parse-ambiguous
|
|
36
|
+
# envelope-delegation-mismatch envelope-incomplete epic-mismatch rollback-unnamed non-delegable-command
|
|
37
|
+
# command-not-delegable scope-mismatch command-not-in-envelope.
|
|
38
|
+
#
|
|
39
|
+
# Every `gh` call is a GET (`gh api repos/...` or `gh repo view`), reads no stdin, is capped at 5 s and is retried
|
|
40
|
+
# once after 2 s on a transport error only; with neither `timeout` nor `gtimeout` the verifier answers tool-missing
|
|
41
|
+
# rather than run unbounded. There is no seam INSIDE this script: nothing in its arguments can make it answer
|
|
42
|
+
# `valid`. (The caller's PATH and gh credentials are, as for any script, the caller's to protect.)
|
|
43
|
+
# `set -uo pipefail`, deliberately NOT `-e`. Portable to bash 3.2 and mawk: no `mapfile`, `date -d`, `stat -c`,
|
|
44
|
+
# `sed -i`, `readlink -f`, `${var,,}` or associative arrays. Lines ending `# mut:<name>` are the mutation points
|
|
45
|
+
# the suite edits to prove each check can fail.
|
|
46
|
+
|
|
47
|
+
set -uo pipefail
|
|
48
|
+
export LC_ALL=C
|
|
49
|
+
|
|
50
|
+
MARKER='<!-- soleur-delegation v1 -->'
|
|
51
|
+
# The only Excluded paragraph a record may carry (compared after whitespace normalisation). Section 5 of the
|
|
52
|
+
# format reference holds the same text; the suite instantiates the reference's templates and requires `valid`.
|
|
53
|
+
EXCLUDED='**Excluded (still operator-only):** user-data loss or sole-copy destruction, money, legal-risk, brand, scope changes, force-push to main, bypassing required checks or rulesets, editing this delegation, credential rotation that can lock users out, anything on third-party or tenant systems, and any act whose rollback is not named.'
|
|
54
|
+
# Credential shapes `render` refuses to publish (the answer is posted verbatim on a public issue).
|
|
55
|
+
SECRET_RE='(gh[pousr]_[A-Za-z0-9._~+/=-]{20,})|(github_pat_[A-Za-z0-9._~+/=-]{20,})|(sk-[A-Za-z0-9_-]{20,})|(AKIA[0-9A-Z]{16})|(xox[baprsce]-[A-Za-z0-9-]{10,})|(dp\.[a-z]{1,4}\.[A-Za-z0-9]{20,})|(sbp_[A-Za-z0-9]{20,})|(eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)|(-----BEGIN [A-Z ]*PRIVATE KEY-----)'
|
|
56
|
+
ANSWER_CAP=600
|
|
57
|
+
WORK=""
|
|
58
|
+
AUDIT=0
|
|
59
|
+
cleanup() { [ -n "$WORK" ] && rm -rf "${WORK:?}"; return 0; }
|
|
60
|
+
trap cleanup EXIT
|
|
61
|
+
|
|
62
|
+
emit() { printf 'delegation: %s reason=%s\n' "$1" "$2"; }
|
|
63
|
+
fail_invalid() { emit invalid "$1"; printf '%s\n' "$2" >&2; exit 1; }
|
|
64
|
+
fail_unv() { emit unverifiable "$1"; printf '%s\n' "$2" >&2; exit 2; }
|
|
65
|
+
die_render() { printf 'delegation-verify render: %s\n' "$1" >&2; exit 2; }
|
|
66
|
+
|
|
67
|
+
lower() { printf '%s' "$1" | tr 'A-Z' 'a-z'; }
|
|
68
|
+
trim() { local s="$1"; s="${s#"${s%%[![:space:]]*}"}"; s="${s%"${s##*[![:space:]]}"}"; printf '%s' "$s"; }
|
|
69
|
+
squash() { printf '%s' "$1" | tr -s '[:space:]' ' '; }
|
|
70
|
+
|
|
71
|
+
TO=()
|
|
72
|
+
if command -v timeout >/dev/null 2>&1; then TO=(timeout 5)
|
|
73
|
+
elif command -v gtimeout >/dev/null 2>&1; then TO=(gtimeout 5); fi
|
|
74
|
+
|
|
75
|
+
# gh_get <outfile> <gh args...>: 0 ok; 4 a definitive HTTP 4xx answer; 5 transport or other failure (after one retry).
|
|
76
|
+
gh_get() {
|
|
77
|
+
local out="$1" try=0; shift
|
|
78
|
+
while :; do
|
|
79
|
+
if ${TO[@]+"${TO[@]}"} gh "$@" >"$out" 2>"$WORK/gh.err" </dev/null; then return 0; fi
|
|
80
|
+
if grep -qE 'HTTP 4[0-9][0-9]' "$WORK/gh.err"; then return 4; fi
|
|
81
|
+
try=$((try + 1))
|
|
82
|
+
[ "$try" -ge 2 ] && return 5
|
|
83
|
+
sleep 2
|
|
84
|
+
done
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
sha256_file() {
|
|
88
|
+
if command -v sha256sum >/dev/null 2>&1; then sha256sum < "$1" | cut -d' ' -f1
|
|
89
|
+
elif command -v shasum >/dev/null 2>&1; then shasum -a 256 < "$1" | cut -d' ' -f1
|
|
90
|
+
elif command -v openssl >/dev/null 2>&1; then openssl dgst -sha256 < "$1" | sed 's/^.* //'
|
|
91
|
+
else return 1; fi
|
|
92
|
+
}
|
|
93
|
+
sha256_of() { printf '%s' "$1" > "$WORK/sha.in"; sha256_file "$WORK/sha.in"; }
|
|
94
|
+
have_hash() { command -v sha256sum >/dev/null 2>&1 || command -v shasum >/dev/null 2>&1 || command -v openssl >/dev/null 2>&1; }
|
|
95
|
+
|
|
96
|
+
# valid_date <YYYY-MM-DD>: a real calendar date (round-trips through jq).
|
|
97
|
+
valid_date() {
|
|
98
|
+
local d="$1" e
|
|
99
|
+
[[ "$d" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]] || return 1
|
|
100
|
+
e="$(jq -rn --arg d "${d}T23:59:59Z" '$d | fromdateiso8601' 2>/dev/null)" || return 1
|
|
101
|
+
[ "$(jq -rn --argjson e "$e" '$e | strftime("%Y-%m-%d")' 2>/dev/null)" = "$d" ]
|
|
102
|
+
}
|
|
103
|
+
add_days() { jq -rn --arg d "$1" --argjson n "$2" '($d + "T00:00:00Z" | fromdateiso8601) + ($n * 86400) | strftime("%Y-%m-%d")'; }
|
|
104
|
+
# valid_instant <YYYY-MM-DDTHH:MM:SSZ>: a real UTC time (round-trips through jq).
|
|
105
|
+
valid_instant() {
|
|
106
|
+
[[ "$1" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$ ]] || return 1
|
|
107
|
+
[ "$(jq -rn --arg d "$1" '$d | fromdateiso8601 | todateiso8601' 2>/dev/null)" = "$1" ]
|
|
108
|
+
}
|
|
109
|
+
epoch_of() { jq -rn --arg d "$1" '$d | fromdateiso8601' 2>/dev/null; }
|
|
110
|
+
|
|
111
|
+
# fence helper shared by both envelope awk programs: a CommonMark fence is a run of >= 3 backticks or tildes
|
|
112
|
+
# (up to 3 spaces of indent); it closes only on the SAME character, a run at least as long, and no info string.
|
|
113
|
+
FENCE_AWK='
|
|
114
|
+
function fnc(t, s, ch, n) {
|
|
115
|
+
s = t; sub(/^ ? ? ?/, "", s); ch = substr(s, 1, 1)
|
|
116
|
+
if (ch != "`" && ch != "~") return 0
|
|
117
|
+
n = 0; while (substr(s, n + 1, 1) == ch) n++
|
|
118
|
+
if (n < 3) return 0
|
|
119
|
+
FCH = ch; FLEN = n; FREST = substr(s, n + 1)
|
|
120
|
+
if (ch == "`" && index(FREST, "`") > 0) return 0
|
|
121
|
+
return 1
|
|
122
|
+
}
|
|
123
|
+
'
|
|
124
|
+
|
|
125
|
+
# strip_html <file> <keep first line 1|0>: the file without <!-- ... --> comments (also across lines), so a scope item
|
|
126
|
+
# or an envelope section that a reader of the rendered text cannot see is not parsed either. Code fences and inline code
|
|
127
|
+
# spans are shown literally by a renderer, so they are left alone; <!--> and <!---> are one-line empty comments. A comment
|
|
128
|
+
# that is never closed hides the rest of the file, so it is flagged in $WORK/html.unterminated. The delegation marker is
|
|
129
|
+
# line 1 of the record and is kept verbatim.
|
|
130
|
+
strip_html() {
|
|
131
|
+
awk -v keep="$2" -v flag="$WORK/html.unterminated" "$FENCE_AWK"'
|
|
132
|
+
NR == 1 && keep == 1 { print; next }
|
|
133
|
+
{ t = $0; sub(/\r$/, "", t)
|
|
134
|
+
if (!inc && fnc(t)) {
|
|
135
|
+
if (!infence) { infence = 1; fch = FCH; flen = FLEN; print; next }
|
|
136
|
+
if (FCH == fch && FLEN >= flen && FREST ~ /^[ \t]*$/) { infence = 0; print; next }
|
|
137
|
+
}
|
|
138
|
+
if (infence && !inc) { print; next }
|
|
139
|
+
s = $0; out = ""; code = 0; n = length(s); i = 1
|
|
140
|
+
while (i <= n) {
|
|
141
|
+
c = substr(s, i, 1)
|
|
142
|
+
if (inc) { if (substr(s, i, 3) == "-->") { inc = 0; i += 3 } else i++; continue }
|
|
143
|
+
if (c == "`") { code = !code; out = out c; i++; continue }
|
|
144
|
+
if (!code && substr(s, i, 4) == "<!--") {
|
|
145
|
+
if (substr(s, i, 5) == "<!-->") { i += 5; continue }
|
|
146
|
+
if (substr(s, i, 6) == "<!--->") { i += 6; continue }
|
|
147
|
+
inc = 1; i += 4; continue
|
|
148
|
+
}
|
|
149
|
+
out = out c; i++
|
|
150
|
+
}
|
|
151
|
+
print out }
|
|
152
|
+
END { if (inc) print "1" > flag }
|
|
153
|
+
' "$1"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# parse_body <file>: sets P_MARKER_OK P_QUOTE P_CONF P_EXCL_OK P_EXPDATE and writes $WORK/scope.items / scope.nums.
|
|
157
|
+
parse_body() {
|
|
158
|
+
local b="$1" ex
|
|
159
|
+
P_MARKER_OK=0
|
|
160
|
+
[ "$(head -1 "$b")" = "$MARKER" ] && P_MARKER_OK=1
|
|
161
|
+
strip_html "$b" 1 > "$WORK/pb.txt"; b="$WORK/pb.txt" # mut:strip-body
|
|
162
|
+
awk '
|
|
163
|
+
/Operator instruction, verbatim/ { on = 1; next }
|
|
164
|
+
on && /^ ? ? ?>/ { sub(/^ ? ? ?> ?/, ""); print; got = 1; next }
|
|
165
|
+
on && got { exit }
|
|
166
|
+
' "$b" > "$WORK/quote.txt"
|
|
167
|
+
# The two label prefixes are boilerplate, so they do not count toward the 40-byte floor.
|
|
168
|
+
P_QUOTE="$(trim "$(tr '\n' ' ' < "$WORK/quote.txt" | sed -e 's/Question shown://g' -e 's/Operator answer://g')")"
|
|
169
|
+
P_CONF="$(trim "$(awk '
|
|
170
|
+
c { if ($0 ~ /^[ \t]*$/) next; if ($0 !~ /^\*\*/) print; exit }
|
|
171
|
+
/^\*\*Confirmation:\*\*/ { sub(/^\*\*Confirmation:\*\*[ \t]*/, ""); if ($0 != "") { print; exit } c = 1 }
|
|
172
|
+
' "$b")")"
|
|
173
|
+
awk '
|
|
174
|
+
/^\*\*Scope/ { on = 1; next }
|
|
175
|
+
on && /^\*\*/ { exit }
|
|
176
|
+
on && /^[0-9]+\.[ \t]+/ { if (n != "") print n "\t" txt; n = $0; sub(/\..*$/, "", n); txt = $0; sub(/^[0-9]+\.[ \t]+/, "", txt); next }
|
|
177
|
+
on && /^[ \t]*$/ { if (n != "") print n "\t" txt; n = ""; next }
|
|
178
|
+
on && n != "" { t = $0; sub(/^[ \t]+/, "", t); txt = txt " " t }
|
|
179
|
+
END { if (n != "") print n "\t" txt }
|
|
180
|
+
' "$b" > "$WORK/scope.items"
|
|
181
|
+
cut -f1 "$WORK/scope.items" > "$WORK/scope.nums"
|
|
182
|
+
# The Excluded paragraph is a fixed sentence: one paragraph, equal to it after whitespace normalisation.
|
|
183
|
+
ex="$(awk '/^\*\*Excluded/ { on = 1; print; next } on && /^\*\*/ { exit } on { print }' "$b")"
|
|
184
|
+
P_EXCL_OK=0
|
|
185
|
+
[ "$(trim "$(squash "$ex")")" = "$(trim "$(squash "$EXCLUDED")")" ] && [ "$(grep -c '^\*\*Excluded' "$b")" = 1 ] && P_EXCL_OK=1
|
|
186
|
+
P_EXPDATE="$(grep -m1 '^\*\*Expiry:\*\*' "$b" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}' | head -1)"
|
|
187
|
+
P_ONE_CONF=0; [ "$(grep -c '^\*\*Confirmation:\*\*' "$b")" = 1 ] && P_ONE_CONF=1
|
|
188
|
+
P_ONE_EXP=0; [ "$(grep -c '^\*\*Expiry:\*\*' "$b")" = 1 ] && P_ONE_EXP=1
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
# says_yes <text>: the Confirmation starts with the word Yes.
|
|
192
|
+
says_yes() { [[ "$1" =~ ^Yes([^[:alnum:]]|$) ]]; }
|
|
193
|
+
|
|
194
|
+
# render_body <epic> <date> <qfile> <afile> <scopefile> <expiry>: the delegation record on stdout.
|
|
195
|
+
render_body() {
|
|
196
|
+
local epic="$1" date="$2" qf="$3" af="$4" sf="$5" expiry="$6" first=1 n=0 line ans
|
|
197
|
+
printf '%s\n' "$MARKER"
|
|
198
|
+
printf '## Standing delegation - epic #%s (recorded %s)\n\n' "$epic" "$date"
|
|
199
|
+
printf '**Operator instruction, verbatim (%s):**\n' "$date"
|
|
200
|
+
first=1
|
|
201
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
202
|
+
line="${line%$'\r'}"
|
|
203
|
+
if [ "$first" = 1 ]; then printf '> Question shown: %s\n' "$line"; first=0
|
|
204
|
+
elif [ -z "$line" ]; then printf '>\n'
|
|
205
|
+
else printf '> %s\n' "$line"; fi
|
|
206
|
+
done < "$qf"
|
|
207
|
+
first=1
|
|
208
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
209
|
+
line="${line%$'\r'}"
|
|
210
|
+
if [ "$first" = 1 ]; then printf '> Operator answer: %s\n' "$line"; first=0
|
|
211
|
+
elif [ -z "$line" ]; then printf '>\n'
|
|
212
|
+
else printf '> %s\n' "$line"; fi
|
|
213
|
+
done < "$af"
|
|
214
|
+
ans="$(tr '\r\n' ' ' < "$af")"; ans="$(trim "$ans")"
|
|
215
|
+
printf '\n**Confirmation:** %s\n\n' "$ans"
|
|
216
|
+
printf '**Scope (act classes the CTO agent may run without a further per-command prompt):**\n'
|
|
217
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
218
|
+
line="$(trim "${line%$'\r'}")"
|
|
219
|
+
[ -n "$line" ] || continue
|
|
220
|
+
n=$((n + 1)); printf '%s. %s\n' "$n" "$line"
|
|
221
|
+
done < "$sf"
|
|
222
|
+
printf '\n%s\n\n' "$EXCLUDED"
|
|
223
|
+
printf '**Expiry:** %s (or when the epic closes, whichever comes first). **Revoke:** delete or edit this comment.\n' "$expiry"
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
# The tools whose mere use is never delegated, and where a word counts as a COMMAND: at the start of a line or after
|
|
227
|
+
# ; & | ( or a backtick, optionally behind wrapper words (env, timeout 5, nohup, xargs, sudo ...) and their flags or
|
|
228
|
+
# assignments. So `timeout 5 kubectl ...` and `xargs ssh ...` are refused while `-f cloud=aws` and `--head docker`, where
|
|
229
|
+
# the same letters are a value, are not.
|
|
230
|
+
DENY_TOOLS='kubectl|supabase|psql|hcloud|ssh|scp|helm|docker|aws|gcloud|flyctl'
|
|
231
|
+
OPS_TOOLS="$DENY_TOOLS|curl|wget|terraform|tofu|doppler|rm|dd|mkfs[.a-z0-9]*|nc|ncat|socat"
|
|
232
|
+
CMDPOS_RE='(^|[;&|(`]|\$\()[[:space:]]*((env|timeout|nohup|xargs|sudo|nice|command|exec|time|setsid|stdbuf|doas)[[:space:]]+(-[^[:space:]]+[[:space:]]+|[A-Za-z_][A-Za-z0-9_]*=[^[:space:]]*[[:space:]]+|[0-9]+[smhd]?[[:space:]]+)*)*'
|
|
233
|
+
cmd_word() { # <text> <alternation of words>: 0 when the text runs one of them as a command
|
|
234
|
+
grep -Eiq -- "${CMDPOS_RE}($2)([[:space:]]|\$)" <<<"$1"
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
# is_denied <command>: the text backstop for the Excluded list, a SECOND layer behind the allowlist. It runs on the
|
|
238
|
+
# command as written and on a copy with quotes, backslashes and expansion characters removed, so g''it push and
|
|
239
|
+
# --admi""n are read as the shell would read them.
|
|
240
|
+
is_denied() {
|
|
241
|
+
local c="$1" n p
|
|
242
|
+
n="$(printf '%s' "$c" | tr -d "'\"\\\\\`\$")"
|
|
243
|
+
local pats=(
|
|
244
|
+
'(^|[^[:alnum:]_-])git[^[:alnum:]]*[[:space:]]+(.*[[:space:]])?push([[:space:]"'"'"';&|)]|$)'
|
|
245
|
+
'(^|[^[:alnum:]_-])git[[:space:]]+(.*[[:space:]])?(reset[[:space:]]+(.*[[:space:]])?--hard|branch[[:space:]]+(.*[[:space:]])?-D|tag[[:space:]]+(.*[[:space:]])?-d|clean|filter-branch|update-ref)([[:space:]]|$)'
|
|
246
|
+
'(^|[[:space:]])--admin([[:space:]=;&|)]|$)'
|
|
247
|
+
'gh[[:space:]]+api[[:space:]].*(ruleset|protection)'
|
|
248
|
+
'gh[[:space:]]+api[[:space:]].*(-X|--method)[[:space:]=]*(DELETE|PATCH|PUT)'
|
|
249
|
+
'doppler[[:space:]]+(.*[[:space:]])?secrets[[:space:]]+(set|delete|upload)'
|
|
250
|
+
'(^|[^[:alnum:]_-])(terraform|tofu)[[:space:]].*(apply|destroy|import|state|taint|force-unlock)'
|
|
251
|
+
'(^|[^[:alnum:]_-])rm[[:space:]]+(.*[[:space:]])?(-[a-zA-Z]*[rR][a-zA-Z]*|--recursive)([[:space:]]|$)'
|
|
252
|
+
'(^|[^[:alnum:]_-])find[[:space:]].*-delete'
|
|
253
|
+
'gh[[:space:]]+repo[[:space:]]+(delete|archive|rename|transfer|edit)'
|
|
254
|
+
'(^|[^[:alnum:]_-])gh[[:space:]]+(secret|variable)([[:space:]]|$)'
|
|
255
|
+
'gh[[:space:]]+[a-z-]+[[:space:]]+delete([[:space:]]|$)'
|
|
256
|
+
'gh[[:space:]]+(.*[[:space:]])?workflow[[:space:]]+run[[:space:]]+(.*[[:space:]])?(-[fF]|--field|--raw-field)[[:space:]=]*(confirm|rollback_ack[a-z0-9_.-]*)=' # confirm and rollback_ack* inputs are the typo-guards of destructive dispatches # mut:typo-guard
|
|
257
|
+
"${CMDPOS_RE}(${DENY_TOOLS})([[:space:]]|\$)"
|
|
258
|
+
'(^|[^[:alnum:]_-])(curl|wget)[[:space:]]+(.*[[:space:]])?(-X|--request)'
|
|
259
|
+
'\|[[:space:]]*(sh|bash)([[:space:]]|$)'
|
|
260
|
+
)
|
|
261
|
+
for p in "${pats[@]}"; do
|
|
262
|
+
grep -Eiq -- "$p" <<<"$c"$'\n'"$n" && return 0
|
|
263
|
+
done
|
|
264
|
+
return 1
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
# The closed command grammar. N = [0-9]+; every character is in [A-Za-z0-9 ._=:/@+,-].
|
|
268
|
+
RE_MERGE='^gh pr merge ([0-9]+)( --squash| --merge| --rebase)?( --auto)?$'
|
|
269
|
+
RE_READY='^gh pr ready ([0-9]+)$'
|
|
270
|
+
RE_RUN='^gh workflow run ([A-Za-z0-9._-]+\.(yml|yaml))( -f [A-Za-z0-9_.-]+=[A-Za-z0-9._/:@+,-]+)*$'
|
|
271
|
+
RE_CREATE='^gh pr create --fill( --head [A-Za-z0-9._/-]+)?( --base [A-Za-z0-9._/-]+)?$'
|
|
272
|
+
classify_command() { # <line>: sets CMD_KIND CMD_TARGET; 1 when the line is not on the allowlist
|
|
273
|
+
[[ "$1" =~ $RE_MERGE ]] && { CMD_KIND=merge; CMD_TARGET="${BASH_REMATCH[1]}"; return 0; }
|
|
274
|
+
[[ "$1" =~ $RE_READY ]] && { CMD_KIND=ready; CMD_TARGET="${BASH_REMATCH[1]}"; return 0; }
|
|
275
|
+
[[ "$1" =~ $RE_RUN ]] && { CMD_KIND=run; CMD_TARGET="${BASH_REMATCH[1]}"; return 0; }
|
|
276
|
+
[[ "$1" =~ $RE_CREATE ]] && { CMD_KIND=create; CMD_TARGET=""; return 0; }
|
|
277
|
+
return 1
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
# scope_covers <kind> <target> <scope item text>: an item with a negation or a condition (not, never, no, nor, except,
|
|
281
|
+
# excluding, without, unless, avoid, skip, prohibited, forbidden, any n't, only if, only after, nothing but) never covers
|
|
282
|
+
# anything, and an item must START with an act word (merge, ready, dispatch, run, create, revert, open). Otherwise the item
|
|
283
|
+
# names the act's target as a whole token. merge and ready: the PR number (as #N, PR N, or right after `pr merge` /
|
|
284
|
+
# `pr ready`) AND the word merge / ready. workflow run: the workflow file name. pr create: the word revert (a revert PR).
|
|
285
|
+
scope_covers() {
|
|
286
|
+
SC_K="$1" SC_T="$2" SC_X="$3" awk '
|
|
287
|
+
function isl(c) { return c ~ /^[a-z]$/ }
|
|
288
|
+
function isd(c) { return c ~ /^[0-9]$/ }
|
|
289
|
+
function istc(c) { return c ~ /^[A-Za-z0-9_-]$/ }
|
|
290
|
+
function hasw(t, w, s, i, p, a, b) {
|
|
291
|
+
s = 1
|
|
292
|
+
while ((i = index(substr(t, s), w)) > 0) {
|
|
293
|
+
p = s + i - 1; a = (p > 1) ? substr(t, p - 1, 1) : " "; b = substr(t, p + length(w), 1); if (b == "") b = " "
|
|
294
|
+
if (!isl(a) && !isl(b)) return 1
|
|
295
|
+
s = p + 1
|
|
296
|
+
}
|
|
297
|
+
return 0
|
|
298
|
+
}
|
|
299
|
+
function hasneg(t, w, k, k2, u) {
|
|
300
|
+
u = t; gsub("\342\200\231", "\047", u); gsub(/[ \t]+/, " ", u)
|
|
301
|
+
if (index(u, "n\047t") > 0) return 1
|
|
302
|
+
if (index(" " u " ", " only if ") > 0 || index(" " u " ", " only after ") > 0 || index(" " u " ", " nothing but ") > 0) return 1
|
|
303
|
+
k2 = split("not never no nor except excluding without unless avoid skip cannot prohibit prohibited prohibits forbid forbidden forbids refrain", w, " ")
|
|
304
|
+
for (k = 1; k <= k2; k++) if (hasw(u, w[k])) return 1
|
|
305
|
+
return 0
|
|
306
|
+
}
|
|
307
|
+
function startok(t, s, w) {
|
|
308
|
+
s = t; sub(/^[ \t*+-]*([0-9]+[.)])?[ \t*]*/, "", s); w = s; sub(/[^a-z].*$/, "", w)
|
|
309
|
+
return w == "merge" || w == "ready" || w == "dispatch" || w == "run" || w == "create" || w == "revert" || w == "open"
|
|
310
|
+
}
|
|
311
|
+
function hasnum(t, n, s, i, p, a, b, pre) {
|
|
312
|
+
s = 1
|
|
313
|
+
while ((i = index(substr(t, s), n)) > 0) {
|
|
314
|
+
p = s + i - 1; a = (p > 1) ? substr(t, p - 1, 1) : " "; b = substr(t, p + length(n), 1); if (b == "") b = " "
|
|
315
|
+
pre = substr(t, 1, p - 1)
|
|
316
|
+
if (!isd(a) && !isd(b) && (pre ~ /#$/ || pre ~ /pr $/ || pre ~ /pr merge $/ || pre ~ /pr ready $/)) return 1
|
|
317
|
+
s = p + 1
|
|
318
|
+
}
|
|
319
|
+
return 0
|
|
320
|
+
}
|
|
321
|
+
function hasfile(t, f, s, i, p, a, b, c2) {
|
|
322
|
+
s = 1
|
|
323
|
+
while ((i = index(substr(t, s), f)) > 0) {
|
|
324
|
+
p = s + i - 1; a = (p > 1) ? substr(t, p - 1, 1) : " "; b = substr(t, p + length(f), 1); c2 = substr(t, p + length(f) + 1, 1)
|
|
325
|
+
if (!istc(a) && a != "." && (b == "" || (!istc(b) && !(b == "." && istc(c2)))) ) return 1
|
|
326
|
+
s = p + 1
|
|
327
|
+
}
|
|
328
|
+
return 0
|
|
329
|
+
}
|
|
330
|
+
BEGIN {
|
|
331
|
+
k = ENVIRON["SC_K"]; tg = ENVIRON["SC_T"]; x = ENVIRON["SC_X"]; t = tolower(x); ok = 0
|
|
332
|
+
if (hasneg(t) || !startok(t)) exit 1
|
|
333
|
+
if (k == "merge") ok = hasnum(t, tg) && hasw(t, "merge")
|
|
334
|
+
else if (k == "ready") ok = hasnum(t, tg) && hasw(t, "ready")
|
|
335
|
+
else if (k == "run") ok = hasfile(x, tg)
|
|
336
|
+
else if (k == "create") ok = hasw(t, "revert")
|
|
337
|
+
exit !ok
|
|
338
|
+
}
|
|
339
|
+
'
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
# check_command <line> <scope item text>: deny list, then the closed grammar, then scope binding. Exits via fail_invalid.
|
|
343
|
+
check_command() {
|
|
344
|
+
local c="$1" st="$2"
|
|
345
|
+
CMD_KIND=""; CMD_TARGET=""
|
|
346
|
+
is_denied "$c" && fail_invalid non-delegable-command "That command is on the list of things that are never delegated." # mut:denylist
|
|
347
|
+
# Defence in depth: no whitespace other than a space, and no character outside the grammar's alphabet, ever reaches the regexes.
|
|
348
|
+
case "$c" in *[!A-Za-z0-9\ ._=:/@+,-]*) fail_invalid command-not-delegable "That command is not one of the few kinds of act a standing approval can cover." ;; esac
|
|
349
|
+
classify_command "$c" || fail_invalid command-not-delegable "That command is not one of the few kinds of act a standing approval can cover." # mut:allowlist
|
|
350
|
+
scope_covers "$CMD_KIND" "$CMD_TARGET" "$st" || fail_invalid scope-mismatch "That command is not what the numbered act in the standing approval says it covers." # mut:scope-bind
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
# ops_readonly <unit>: in a Verification, Abort or Stop text a gh or git command may only READ.
|
|
354
|
+
ops_readonly() {
|
|
355
|
+
local u="$1" seg tool t1 t2 t3
|
|
356
|
+
while IFS= read -r seg; do
|
|
357
|
+
[ -n "$seg" ] || continue
|
|
358
|
+
tool="$(printf '%s' "$seg" | grep -oEi '(gh|git)[[:space:]]+[a-z-]+([[:space:]]+[a-z-]+)?$')"
|
|
359
|
+
set -f; set -- $tool; set +f
|
|
360
|
+
t1="$(lower "${1:-}")"; t2="$(lower "${2:-}")"; t3="$(lower "${3:-}")"
|
|
361
|
+
case "$t1 $t2 $t3" in
|
|
362
|
+
# the pr-checks read is spelled in two halves on purpose: this table only ALLOWS a read-only text, it never reads CI state (the ci-head-verdict L1 census greps for the literal).
|
|
363
|
+
"gh run view"*|"gh run list"*|"gh pr view"*|"gh pr list"*|"gh pr ch""ecks"*|"gh pr diff"*|"gh pr status"*|"gh issue view"*|"gh issue list"*|"gh workflow view"*|"gh workflow list"*|"gh release view"*|"gh release list"*|"gh repo view"*) ;;
|
|
364
|
+
"gh api"*) grep -Eq -- '(^|[[:space:]])(-X|--method|-f|-F|--field|--raw-field|--input)' <<<"$u" && fail_invalid non-delegable-command "A check, abort or stop text in the envelope would change something through the GitHub API, not just read." ;; # mut:ops-api
|
|
365
|
+
"git log"*|"git show"*|"git status"*|"git diff"*|"git rev-parse"*|"git ls-tree"*|"git cat-file"*|"git merge-base"*|"git describe"*|"git blame"*) ;;
|
|
366
|
+
*) fail_invalid non-delegable-command "A check, abort or stop text in the envelope runs a command that changes something, and only reading is allowed there." ;; # mut:ops-readonly
|
|
367
|
+
esac
|
|
368
|
+
done < <(printf '%s\n' "$u" | grep -oEi -- "${CMDPOS_RE}(gh|git)[[:space:]]+[a-z-]+([[:space:]]+[a-z-]+)?")
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
# ops_check <section> <F|L> <text> <scope item text>: the Verification, Abort, Rollback and Stop conditions text is run by
|
|
372
|
+
# the CTO, so each line of it (as a whole, as a fenced line, and as every backticked span) is tested: the denylist, then the
|
|
373
|
+
# tools that are never run (at command position, behind any wrapper), then, outside a Rollback, gh and git may only read.
|
|
374
|
+
# In a Rollback every fenced line and every backticked span must be a command on the grammar AND covered by the cited Scope
|
|
375
|
+
# item, and the rest of the line must be plain words: no shell characters and no instructions to fetch or run something.
|
|
376
|
+
ops_check() {
|
|
377
|
+
local key="$1" kind="$2" txt="$3" st="$4" rest span bt='`' re unit units=()
|
|
378
|
+
units[0]="$txt"; rest="$txt"; re="${bt}([^${bt}]*)${bt}"
|
|
379
|
+
if [ "$kind" = L ]; then
|
|
380
|
+
while [[ "$rest" =~ $re ]]; do
|
|
381
|
+
span="${BASH_REMATCH[1]}"; units[${#units[@]}]="$span"
|
|
382
|
+
rest="${rest/"$bt$span$bt"/ }"
|
|
383
|
+
done
|
|
384
|
+
fi
|
|
385
|
+
for unit in "${units[@]}"; do
|
|
386
|
+
is_denied "$unit" && fail_invalid non-delegable-command "A command in the envelope's checks, abort, rollback or stop text is on the list of things that are never delegated." # mut:ops-deny
|
|
387
|
+
cmd_word "$unit" "$OPS_TOOLS" && fail_invalid non-delegable-command "The envelope's checks, abort, rollback or stop text runs a tool that is never delegated." # mut:ops-tools
|
|
388
|
+
[ "$key" = rollback ] || ops_readonly "$unit"
|
|
389
|
+
done
|
|
390
|
+
[ "$key" = rollback ] || return 0
|
|
391
|
+
if [ "$kind" = F ]; then
|
|
392
|
+
classify_command "$txt" || fail_invalid command-not-delegable "A command in the envelope's rollback is not one of the few kinds of act a standing approval can cover." # mut:ops-rollback
|
|
393
|
+
scope_covers "$CMD_KIND" "$CMD_TARGET" "$st" || fail_invalid scope-mismatch "A command in the envelope's rollback is not what the numbered act in the standing approval says it covers." # mut:ops-rscope
|
|
394
|
+
return 0
|
|
395
|
+
fi
|
|
396
|
+
rest="$txt"
|
|
397
|
+
while [[ "$rest" =~ $re ]]; do
|
|
398
|
+
span="${BASH_REMATCH[1]}"
|
|
399
|
+
classify_command "$span" || fail_invalid command-not-delegable "A command in the envelope's rollback is not one of the few kinds of act a standing approval can cover." # mut:ops-span
|
|
400
|
+
scope_covers "$CMD_KIND" "$CMD_TARGET" "$st" || fail_invalid scope-mismatch "A command in the envelope's rollback is not what the numbered act in the standing approval says it covers." # mut:ops-sscope
|
|
401
|
+
rest="${rest/"$bt$span$bt"/ }"
|
|
402
|
+
done
|
|
403
|
+
case "$rest" in *[\;\&\|\<\>\$\`\\\{\}\*]*) fail_invalid command-not-delegable "The envelope's rollback holds shell characters, so it is not plain words or an allowed command." ;; esac # mut:ops-meta
|
|
404
|
+
grep -Eiq -- '(^|[^[:alnum:]_-])(wget|curl|bash|eval|nc|ncat|socat|python3?|perl|ruby|node|php)([^[:alnum:]_-]|$)|(^|[^[:alnum:]_-])sh[[:space:]]+-c|(run|execute|paste|download)[[:space:]]+(it|that|this|the[[:space:]]+script)|(^|[^[:alnum:]_-])gh[[:space:]]+(pr|workflow|issue|api|release|run|repo)' <<<"$rest" && fail_invalid non-delegable-command "The envelope's rollback tells the reader to fetch or run something, which is never delegated." # mut:ops-prose
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
# cmd_in_block <command> <file>: the command equals, byte for byte, a FULL line of the file.
|
|
408
|
+
cmd_in_block() {
|
|
409
|
+
DV_CMD="$1" awk 'BEGIN { c = ENVIRON["DV_CMD"] } ($0 "") == (c "") { f = 1 } END { exit !f }' "$2" # mut:cmd-awk
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
# rollback_named <text>: a real way to undo the act, in at least three words, not a placeholder or an admission.
|
|
413
|
+
rollback_named() {
|
|
414
|
+
local r
|
|
415
|
+
r="$(lower "$(trim "$1")")"
|
|
416
|
+
[ "$(printf '%s' "$r" | wc -w | tr -d ' ')" -ge 3 ] || return 1
|
|
417
|
+
grep -Eq "(^|[^a-z])(none|irreversible|cannot|unknown|tbd|todo|nothing|impossible|n/a)([^a-z]|\$)|can't|no[[:space:]]+rollback|not[[:space:]]+(reversible|possible|undoable)" <<<"$r" && return 1
|
|
418
|
+
return 0
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
need_tools() {
|
|
422
|
+
command -v jq >/dev/null 2>&1 || fail_unv tool-missing "I cannot check the standing approval because a helper program (jq) is not installed."
|
|
423
|
+
command -v gh >/dev/null 2>&1 || fail_unv tool-missing "I cannot check the standing approval because the GitHub command line tool is not installed."
|
|
424
|
+
[ "${#TO[@]}" -gt 0 ] || fail_unv tool-missing "I cannot check the standing approval safely because this computer has no time-limit tool (timeout or gtimeout)." # mut:tool-timeout
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
# envelope_fetch <path>: copies the HEAD blob of <path> to $WORK/env.blob. Never reads the working tree.
|
|
428
|
+
envelope_fetch() {
|
|
429
|
+
local p="$1" ls mode type sha rest
|
|
430
|
+
while [[ "$p" == ./* ]]; do p="${p#./}"; done
|
|
431
|
+
[ -n "$p" ] || fail_invalid envelope-missing "The envelope path is empty."
|
|
432
|
+
[[ "$p" == /* ]] && fail_invalid envelope-outside-repo "The envelope must be a file inside this project, named relative to it." # mut:env-abs
|
|
433
|
+
[[ "$p" =~ (^|/)\.\.(/|$) ]] && fail_invalid envelope-outside-repo "The envelope must be a file inside this project, named relative to it." # mut:env-dotdot
|
|
434
|
+
case "$p" in
|
|
435
|
+
*[*?[\\]*|*[[:cntrl:]]*|*/) fail_invalid envelope-missing "The envelope path has characters that are not allowed in a file name here." ;;
|
|
436
|
+
esac
|
|
437
|
+
git rev-parse --show-toplevel >/dev/null 2>&1 </dev/null || fail_invalid envelope-outside-repo "I am not inside a project, so there is no saved envelope to read."
|
|
438
|
+
ls="$(GIT_LITERAL_PATHSPECS=1 git ls-tree HEAD -- "$p" 2>/dev/null </dev/null)" || ls=""
|
|
439
|
+
[ -n "$ls" ] && [ "$(printf '%s\n' "$ls" | wc -l | tr -d ' ')" = 1 ] || fail_invalid envelope-not-committed "The envelope file is not saved in the project history." # mut:committed
|
|
440
|
+
mode="${ls%% *}"; rest="${ls#* }"; type="${rest%% *}"; rest="${rest#* }"; sha="${rest%%[[:space:]]*}"
|
|
441
|
+
{ [ "$mode" = 100644 ] && [ "$type" = blob ]; } || fail_invalid envelope-not-regular-file "The envelope must be an ordinary saved file, not a shortcut or a folder." # mut:env-mode
|
|
442
|
+
[[ "$sha" =~ ^[0-9a-f]{40,64}$ ]] || fail_invalid envelope-not-committed "I could not read the saved envelope."
|
|
443
|
+
git cat-file blob "$sha" > "$WORK/env.blob" 2>/dev/null </dev/null || fail_invalid envelope-not-committed "I could not read the saved envelope." # mut:env-blob
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
do_verify() {
|
|
447
|
+
local DELEG="" ENV="" AT="" CMDS=() VER=() i c url_re sec n cnt login owner_l login_l grc irc okey okind otxt
|
|
448
|
+
local CREATED UPDATED ISSUE_URL STATE EXP_EPOCH LIMIT NOW CUR_REPO CHK_EPOCH CREATED_EPOCH AT_EPOCH
|
|
449
|
+
while [ $# -gt 0 ]; do
|
|
450
|
+
case "$1" in
|
|
451
|
+
--delegation) [ $# -ge 2 ] || fail_unv usage "--delegation needs a value"; DELEG="$2"; shift 2 ;;
|
|
452
|
+
--envelope) [ $# -ge 2 ] || fail_unv usage "--envelope needs a value"; ENV="$2"; shift 2 ;;
|
|
453
|
+
--command) [ $# -ge 2 ] || fail_unv usage "--command needs a value"; CMDS[${#CMDS[@]}]="$2"; shift 2 ;;
|
|
454
|
+
--at) [ $# -ge 2 ] || fail_unv usage "--at needs a value"; AT="$2"; shift 2 ;;
|
|
455
|
+
--audit) AUDIT=1; shift ;;
|
|
456
|
+
*) fail_unv usage "unknown argument: $1" ;;
|
|
457
|
+
esac
|
|
458
|
+
done
|
|
459
|
+
[ -n "$DELEG" ] || fail_unv usage "verify needs --delegation <comment-url>"
|
|
460
|
+
if [ "${#CMDS[@]}" -gt 0 ] && [ -z "$ENV" ]; then fail_unv usage "--command needs --envelope"; fi
|
|
461
|
+
[ "$AUDIT" = 0 ] || [ -n "$AT" ] || fail_unv usage "--audit needs --at <the time the act ran, YYYY-MM-DDTHH:MM:SSZ>" # mut:audit-needs-at
|
|
462
|
+
[ -z "$AT" ] || [ "$AUDIT" = 1 ] || fail_unv usage "--at is only valid together with --audit"
|
|
463
|
+
need_tools
|
|
464
|
+
if [ -n "$AT" ]; then valid_instant "$AT" || fail_unv usage "--at needs a real UTC time like 2026-01-31T12:00:00Z"; fi
|
|
465
|
+
if [ -n "$ENV" ] && ! have_hash; then fail_unv tool-missing "I cannot check the envelope because no hashing tool is installed."; fi
|
|
466
|
+
if [ -n "$ENV" ]; then command -v git >/dev/null 2>&1 || fail_unv tool-missing "I cannot check the envelope because git is not installed."; fi
|
|
467
|
+
|
|
468
|
+
url_re='^https://github\.com/([A-Za-z0-9_.-]+)/([A-Za-z0-9_.-]+)/(issues|pull)/([0-9]+)#issuecomment-([0-9]+)$'
|
|
469
|
+
[[ "$DELEG" =~ $url_re ]] || fail_invalid bad-url "The standing approval link is not a GitHub comment link on an issue."
|
|
470
|
+
local U_OWNER="${BASH_REMATCH[1]}" U_REPO="${BASH_REMATCH[2]}" U_ISSUE="${BASH_REMATCH[4]}" U_CID="${BASH_REMATCH[5]}"
|
|
471
|
+
|
|
472
|
+
gh_get "$WORK/repo.txt" repo view --json nameWithOwner --jq .nameWithOwner || fail_unv api-error "I could not look up which project this is, so I cannot check the standing approval."
|
|
473
|
+
CUR_REPO="$(tr -d '\r\n' < "$WORK/repo.txt")"
|
|
474
|
+
[ "$(lower "$U_OWNER/$U_REPO")" = "$(lower "$CUR_REPO")" ] || fail_invalid wrong-repo "The standing approval link points at a different project." # mut:wrong-repo
|
|
475
|
+
|
|
476
|
+
gh_get "$WORK/c.json" api "repos/$U_OWNER/$U_REPO/issues/comments/$U_CID"; grc=$?
|
|
477
|
+
case "$grc" in 0) ;; 4) fail_unv comment-unreadable "The standing approval comment cannot be read; it may have been deleted." ;; *) fail_unv api-error "I could not reach GitHub to read the standing approval." ;; esac
|
|
478
|
+
jq -e '(.body|type=="string") and (.created_at|type=="string") and (.updated_at|type=="string") and (.user.login|type=="string") and (.user.type|type=="string") and (.issue_url|type=="string")' "$WORK/c.json" >/dev/null 2>&1 || fail_unv api-error "GitHub returned an answer I could not read, so I cannot check the standing approval."
|
|
479
|
+
jq -r '.body' "$WORK/c.json" | awk '{ sub(/\r$/, ""); print }' > "$WORK/body.txt"
|
|
480
|
+
CREATED="$(jq -r '.created_at' "$WORK/c.json")"; UPDATED="$(jq -r '.updated_at' "$WORK/c.json")"
|
|
481
|
+
ISSUE_URL="$(jq -r '.issue_url' "$WORK/c.json")"; login="$(jq -r '.user.login' "$WORK/c.json")"
|
|
482
|
+
parse_body "$WORK/body.txt"
|
|
483
|
+
[ "$P_MARKER_OK" = 1 ] || fail_invalid no-marker "The comment does not start with the standing approval marker." # mut:marker
|
|
484
|
+
|
|
485
|
+
[ "${ISSUE_URL##*/}" = "$U_ISSUE" ] || fail_invalid wrong-issue "The comment belongs to a different piece of work than the link says." # mut:issue-bind
|
|
486
|
+
gh_get "$WORK/issue.json" api "repos/$U_OWNER/$U_REPO/issues/$U_ISSUE"; irc=$?
|
|
487
|
+
case "$irc" in 0) ;; *) fail_unv api-error "I could not read the epic from GitHub, so I cannot check the standing approval." ;; esac # mut:api-error
|
|
488
|
+
[ -z "$(jq -r '.pull_request // empty' "$WORK/issue.json" 2>/dev/null)" ] || fail_invalid epic-is-pr "The standing approval sits on a change proposal, not on the epic." # mut:epic-pr
|
|
489
|
+
STATE="$(jq -r '.state // empty' "$WORK/issue.json" 2>/dev/null)"
|
|
490
|
+
|
|
491
|
+
[ "$(jq -r '.user.type' "$WORK/c.json")" = "User" ] || fail_invalid bot-author "The standing approval was not written by a person." # mut:bot
|
|
492
|
+
jq -e '.performed_via_github_app == null' "$WORK/c.json" >/dev/null 2>&1 || fail_invalid bot-author "The standing approval was written through an app, not by a person." # mut:app
|
|
493
|
+
[[ "$login" =~ ^[A-Za-z0-9][A-Za-z0-9-]*$ ]] || fail_unv api-error "The author name on the standing approval looks wrong, so I cannot check it."
|
|
494
|
+
owner_l="$(lower "$U_OWNER")"; login_l="$(lower "$login")"
|
|
495
|
+
if [ "$login_l" != "$owner_l" ]; then
|
|
496
|
+
gh_get "$WORK/perm.json" api "repos/$U_OWNER/$U_REPO/collaborators/$login/permission" || fail_unv api-error "I could not check who wrote the standing approval."
|
|
497
|
+
[ "$(jq -r '.permission // empty' "$WORK/perm.json" 2>/dev/null)" = "admin" ] || fail_invalid not-admin-author "The standing approval was not written by an administrator of the project." # mut:admin
|
|
498
|
+
fi
|
|
499
|
+
[ "$UPDATED" = "$CREATED" ] || fail_invalid edited "The standing approval was changed after it was recorded, so it needs to be recorded again." # mut:edited
|
|
500
|
+
|
|
501
|
+
[ -s "$WORK/quote.txt" ] || fail_invalid no-quote "The standing approval does not quote the operator's own words."
|
|
502
|
+
[ "${#P_QUOTE}" -ge 40 ] || fail_invalid quote-too-short "The quoted words in the standing approval are too short to show what was agreed." # mut:quote-len
|
|
503
|
+
{ [ "$P_ONE_CONF" = 1 ] && says_yes "$P_CONF"; } || fail_invalid no-confirmation "The standing approval does not record a clear Yes from the operator." # mut:confirmation
|
|
504
|
+
[ -s "$WORK/scope.nums" ] || fail_invalid no-scope-item "The standing approval lists no numbered acts." # mut:scope
|
|
505
|
+
! grep -qE '<[!?]' "$WORK/scope.items" || fail_invalid no-scope-item "A numbered act in the standing approval holds hidden markup, so it cannot be relied on." # mut:scope-html
|
|
506
|
+
[ "$P_EXCL_OK" = 1 ] || fail_invalid excluded-incomplete "The standing approval does not carry the exact list of what stays with the operator." # mut:excluded
|
|
507
|
+
{ [ "$P_ONE_EXP" = 1 ] && valid_date "$P_EXPDATE"; } || fail_invalid expiry-missing "The standing approval has no valid expiry date." # mut:expiry-missing
|
|
508
|
+
LIMIT="$(add_days "${CREATED%%T*}" 30)"
|
|
509
|
+
[[ ! "$P_EXPDATE" > "$LIMIT" ]] || fail_invalid expiry-too-long "The standing approval runs for more than 30 days." # mut:expiry-cap
|
|
510
|
+
EXP_EPOCH="$(epoch_of "${P_EXPDATE}T23:59:59Z")"
|
|
511
|
+
CREATED_EPOCH="$(epoch_of "$CREATED")"; NOW="$(date -u +%s)"; CHK_EPOCH="$NOW"
|
|
512
|
+
if [ "$AUDIT" = 1 ]; then
|
|
513
|
+
AT_EPOCH="$(epoch_of "$AT")"; CHK_EPOCH="$AT_EPOCH"
|
|
514
|
+
[ "$AT_EPOCH" -ge "$CREATED_EPOCH" ] || fail_invalid before-delegation "The act is dated before the standing approval was recorded." # mut:audit-before
|
|
515
|
+
fi
|
|
516
|
+
[ "$CHK_EPOCH" -le "$EXP_EPOCH" ] || fail_invalid expired "The standing approval had run out." # mut:expired
|
|
517
|
+
(( AUDIT )) || [ "$STATE" = "open" ] || fail_invalid epic-closed "The epic is finished, so the standing approval has ended." # mut:epic-open
|
|
518
|
+
|
|
519
|
+
if [ -n "$ENV" ]; then
|
|
520
|
+
local EPATH="${ENV%%#*}" ANCHOR="" EPIC_N
|
|
521
|
+
case "$ENV" in *'#'*) ANCHOR="${ENV#*#}" ;; esac
|
|
522
|
+
envelope_fetch "$EPATH"
|
|
523
|
+
rm -f "$WORK/html.unterminated"
|
|
524
|
+
strip_html "$WORK/env.blob" 0 > "$WORK/env.txt" # mut:strip-env
|
|
525
|
+
[ ! -e "$WORK/html.unterminated" ] || fail_invalid envelope-incomplete "The envelope has a comment that is never closed, which hides the rest of the file." # mut:html-unterm
|
|
526
|
+
awk -v dir="$WORK" "$FENCE_AWK"'
|
|
527
|
+
{ t = $0; sub(/\r$/, "", t)
|
|
528
|
+
if (fnc(t)) {
|
|
529
|
+
if (fch == "") { fch = FCH; flen = FLEN }
|
|
530
|
+
else if (FCH == fch && FLEN >= flen && FREST ~ /^[ \t]*$/) fch = ""
|
|
531
|
+
}
|
|
532
|
+
if (fch == "" && t ~ /^## /) {
|
|
533
|
+
if (cur != "") close(cur)
|
|
534
|
+
cur = ""
|
|
535
|
+
if (t ~ /^## Production Envelope/) { n++; cur = dir "/sec." n; print t > (dir "/sec.heads") }
|
|
536
|
+
}
|
|
537
|
+
if (cur != "") print $0 > cur
|
|
538
|
+
}
|
|
539
|
+
END { print n + 0 > (dir "/sec.count") }
|
|
540
|
+
' "$WORK/env.txt"
|
|
541
|
+
cnt="$(cat "$WORK/sec.count" 2>/dev/null || echo 0)"; sec=""; n=0
|
|
542
|
+
if [ -z "$ANCHOR" ]; then
|
|
543
|
+
[ "$cnt" -le 1 ] || fail_invalid parse-ambiguous "The envelope file holds several envelopes; name which one to use."
|
|
544
|
+
[ "$cnt" -eq 1 ] && sec="$WORK/sec.1"
|
|
545
|
+
else
|
|
546
|
+
i=1
|
|
547
|
+
while [ "$i" -le "$cnt" ]; do
|
|
548
|
+
# the title is what follows `## Production Envelope` and an optional hyphen; match it case-insensitively
|
|
549
|
+
if grep -qiF -- "$ANCHOR" < <(sed -n "${i}p" "$WORK/sec.heads" | sed -e 's/^## Production Envelope//' -e 's/^[[:space:]]*-*[[:space:]]*//'); then n=$((n + 1)); sec="$WORK/sec.$i"; fi
|
|
550
|
+
i=$((i + 1))
|
|
551
|
+
done
|
|
552
|
+
[ "$n" -le 1 ] || fail_invalid parse-ambiguous "More than one envelope matches that title."
|
|
553
|
+
fi
|
|
554
|
+
[ -n "$sec" ] && [ -s "$sec" ] || fail_invalid envelope-missing "The envelope file has no matching Production Envelope section."
|
|
555
|
+
: > "$WORK/cmds"; : > "$WORK/ops"
|
|
556
|
+
awk -v cmdfile="$WORK/cmds" -v opsfile="$WORK/ops" -v unterm="$WORK/unterminated" "$FENCE_AWK"'
|
|
557
|
+
function trim(s) { sub(/^[ \t]+/, "", s); sub(/[ \t]+$/, "", s); return s }
|
|
558
|
+
function isop(k) { return k == "verification" || k == "abort" || k == "rollback" || k == "stop conditions" }
|
|
559
|
+
function op(k, kind, x) { gsub(/\t/, " ", x); x = trim(x); if (isop(k) && x != "") print k "\t" kind "\t" x > opsfile }
|
|
560
|
+
NR == 1 { next }
|
|
561
|
+
{ raw = $0; t = raw; sub(/\r$/, "", t)
|
|
562
|
+
if (fnc(t)) {
|
|
563
|
+
if (!infence) { infence = 1; fch = FCH; flen = FLEN; cmdopen = (cur == "command"); next }
|
|
564
|
+
if (FCH == fch && FLEN >= flen && FREST ~ /^[ \t]*$/) { infence = 0; cmdopen = 0; next }
|
|
565
|
+
}
|
|
566
|
+
if (infence) {
|
|
567
|
+
if (cmdopen) { if (t !~ /^[ \t]*$/) { print raw > cmdfile; ncmd++ } next }
|
|
568
|
+
if (cur != "" && t !~ /^[ \t]*$/) { val[cur] = val[cur] " " trim(t); op(cur, "F", t) }
|
|
569
|
+
next
|
|
570
|
+
}
|
|
571
|
+
d = t; sub(/^[ \t]*(-[ \t]+)?/, "", d); gsub(/\*\*/, "", d)
|
|
572
|
+
if (match(d, /^(Delegation|Epic|Scope item|Command|Blast radius|Preconditions|Verification|Abort|Rollback|Stop conditions):/)) {
|
|
573
|
+
key = tolower(substr(d, 1, RLENGTH - 1)); rest = trim(substr(d, RLENGTH + 1)); cur = key; seen[key] = 1
|
|
574
|
+
if (rest != "") { val[key] = val[key] " " rest; op(key, "L", rest) }
|
|
575
|
+
next
|
|
576
|
+
}
|
|
577
|
+
if (cur == "preconditions" && d ~ /^Gate:[ \t]*[^ \t]/) { gates++; val[cur] = val[cur] " " trim(d); next }
|
|
578
|
+
if (cur != "" && t !~ /^[ \t]*$/) { val[cur] = val[cur] " " trim(d); op(cur, "L", d) }
|
|
579
|
+
}
|
|
580
|
+
END { if (cmdopen) print "1" > unterm; for (k in seen) { v = trim(val[k]); gsub(/\t/, " ", v); print "part\t" k "\t" v } print "gates\t" gates + 0; print "cmds\t" ncmd + 0 }
|
|
581
|
+
' "$sec" > "$WORK/parts"
|
|
582
|
+
part_val() { awk -F'\t' -v k="$1" '$1 == "part" && $2 == k { print $3; exit }' "$WORK/parts"; }
|
|
583
|
+
[ "$(trim "$(part_val delegation)")" = "$DELEG" ] || fail_invalid envelope-delegation-mismatch "The envelope names a different standing approval than the one being checked." # mut:env-delegation
|
|
584
|
+
EPIC_N="$(trim "$(part_val epic)")"
|
|
585
|
+
[[ "$EPIC_N" =~ ^#[0-9]+$ ]] || fail_invalid envelope-incomplete "The envelope does not say which epic it belongs to." # mut:env-epic-line
|
|
586
|
+
[ "${EPIC_N#\#}" = "$U_ISSUE" ] || fail_invalid epic-mismatch "The envelope belongs to a different epic than the standing approval." # mut:epic-bind
|
|
587
|
+
local SI; SI="$(trim "$(part_val 'scope item')")"
|
|
588
|
+
[[ "$SI" =~ ^[0-9]+$ ]] || fail_invalid envelope-incomplete "The envelope does not name which numbered act it falls under." # mut:env-scopenum
|
|
589
|
+
for c in 'blast radius' preconditions verification abort rollback 'stop conditions'; do [ -n "$(trim "$(part_val "$c")")" ] || fail_invalid envelope-incomplete "The envelope is missing its '$c' part."; done # mut:env-parts
|
|
590
|
+
[ "$(awk -F'\t' '$1 == "gates" { print $2 }' "$WORK/parts")" -ge 1 ] || fail_invalid envelope-incomplete "The envelope lists no preconditions that can be checked." # mut:env-gates
|
|
591
|
+
[ "$(awk -F'\t' '$1 == "cmds" { print $2 }' "$WORK/parts")" -ge 1 ] || fail_invalid envelope-incomplete "The envelope has no command block." # mut:env-cmds
|
|
592
|
+
[ ! -e "$WORK/unterminated" ] || fail_invalid envelope-incomplete "The command block in the envelope is never closed." # mut:env-unterminated
|
|
593
|
+
grep -qx "$SI" "$WORK/scope.nums" || fail_invalid no-scope-item "The standing approval has no numbered act $SI, which the envelope relies on."
|
|
594
|
+
rollback_named "$(part_val rollback)" || fail_invalid rollback-unnamed "The envelope does not name a real way to undo the act." # mut:rollback
|
|
595
|
+
|
|
596
|
+
local SCOPE_TEXT; SCOPE_TEXT="$(awk -F'\t' -v n="$SI" '$1 == n { print $2; exit }' "$WORK/scope.items")"
|
|
597
|
+
# EVERY line of every Command block must be an allowed, scope-covered act, whether or not it is asked for.
|
|
598
|
+
while IFS= read -r c || [ -n "$c" ]; do check_command "$c" "$SCOPE_TEXT"; done < "$WORK/cmds"
|
|
599
|
+
while IFS=$'\t' read -r okey okind otxt || [ -n "$okey" ]; do ops_check "$okey" "$okind" "$otxt" "$SCOPE_TEXT"; done < "$WORK/ops"
|
|
600
|
+
for c in ${CMDS[@]+"${CMDS[@]}"}; do
|
|
601
|
+
check_command "$c" "$SCOPE_TEXT"
|
|
602
|
+
cmd_in_block "$c" "$WORK/cmds" || fail_invalid command-not-in-envelope "That command is not, word for word, a line of the envelope." # mut:cmd-match
|
|
603
|
+
VER[${#VER[@]}]="$c"
|
|
604
|
+
done
|
|
605
|
+
fi
|
|
606
|
+
|
|
607
|
+
emit valid ok
|
|
608
|
+
printf 'verified-created-at: %s\nverified-expiry: %s\n' "$CREATED" "$P_EXPDATE"
|
|
609
|
+
if [ -n "$ENV" ]; then printf 'verified-envelope-sha256: %s\n' "$(sha256_file "$WORK/env.blob")"; fi
|
|
610
|
+
for c in ${VER[@]+"${VER[@]}"}; do
|
|
611
|
+
printf 'verified-command: %s\n' "$c"
|
|
612
|
+
printf 'verified-sha256: %s\n' "$(sha256_of "$c")"
|
|
613
|
+
done
|
|
614
|
+
exit 0
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
# do_render: build the record, then prove it parses (fail closed: a record that would not verify is never printed).
|
|
618
|
+
do_render() {
|
|
619
|
+
local EPIC="" QF="" AF="" SF="" EXPIRY="" today limit f ans
|
|
620
|
+
while [ $# -gt 0 ]; do
|
|
621
|
+
case "$1" in
|
|
622
|
+
--epic) [ $# -ge 2 ] || die_render "--epic needs a value"; EPIC="$2"; shift 2 ;;
|
|
623
|
+
--question-file) [ $# -ge 2 ] || die_render "--question-file needs a value"; QF="$2"; shift 2 ;;
|
|
624
|
+
--answer-file) [ $# -ge 2 ] || die_render "--answer-file needs a value"; AF="$2"; shift 2 ;;
|
|
625
|
+
--scope-file) [ $# -ge 2 ] || die_render "--scope-file needs a value"; SF="$2"; shift 2 ;;
|
|
626
|
+
--expiry) [ $# -ge 2 ] || die_render "--expiry needs a value"; EXPIRY="$2"; shift 2 ;;
|
|
627
|
+
*) die_render "unknown argument: $1" ;;
|
|
628
|
+
esac
|
|
629
|
+
done
|
|
630
|
+
command -v jq >/dev/null 2>&1 || die_render "jq is not installed"
|
|
631
|
+
[[ "$EPIC" =~ ^[0-9]+$ ]] || die_render "--epic needs an issue number"
|
|
632
|
+
for f in "$QF" "$AF" "$SF"; do [ -n "$f" ] && [ -s "$f" ] && grep -q '[^[:space:]]' "$f" || die_render "the question, answer and scope files must exist and not be empty"; done
|
|
633
|
+
if grep -qE -e '<[!?]' -e '-->' "$SF"; then die_render "the scope items may not hold hidden comments (the operator cannot see them)"; fi # mut:render-html
|
|
634
|
+
ans="$(tr '\r\n' ' ' < "$AF")"; ans="$(trim "$ans")"
|
|
635
|
+
says_yes "$ans" || die_render "the answer must start with Yes: a record of anything else is not a standing approval" # mut:render-yes
|
|
636
|
+
[ "${#ans}" -le "$ANSWER_CAP" ] || die_render "the answer is longer than $ANSWER_CAP characters; it is posted word for word on a public issue, so keep it short" # mut:render-cap
|
|
637
|
+
if grep -Eq -- "$SECRET_RE" "$QF" "$AF"; then die_render "the question or answer looks like it holds a password or key; it would be posted publicly, so I will not write it"; fi # mut:render-secret
|
|
638
|
+
valid_date "$EXPIRY" || die_render "--expiry needs a real date (YYYY-MM-DD)"
|
|
639
|
+
today="$(date -u +%Y-%m-%d)"; limit="$(add_days "$today" 30)"
|
|
640
|
+
[[ ! "$EXPIRY" > "$limit" ]] || die_render "the expiry is more than 30 days away"
|
|
641
|
+
[[ ! "$EXPIRY" < "$today" ]] || die_render "the expiry is in the past"
|
|
642
|
+
render_body "$EPIC" "$today" "$QF" "$AF" "$SF" "$EXPIRY" > "$WORK/rendered.md"
|
|
643
|
+
parse_body "$WORK/rendered.md"
|
|
644
|
+
[ "$P_MARKER_OK" = 1 ] && [ "${#P_QUOTE}" -ge 40 ] && [ "$P_ONE_CONF" = 1 ] && [ -s "$WORK/scope.nums" ] && [ "$P_EXCL_OK" = 1 ] && valid_date "$P_EXPDATE" \
|
|
645
|
+
|| die_render "the record would not verify (the quoted question and answer must total at least 40 characters)"
|
|
646
|
+
cat "$WORK/rendered.md"
|
|
647
|
+
exit 0
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
WORK="$(mktemp -d -t dv.XXXXXXXX)" || { echo "delegation: unverifiable reason=tool-missing"; exit 2; }
|
|
651
|
+
sub="${1:-}"
|
|
652
|
+
[ "$sub" = verify ] || [ "$sub" = render ] || fail_unv usage "usage: delegation-verify.sh verify|render ..." # mut:dispatch
|
|
653
|
+
[ $# -gt 0 ] && shift
|
|
654
|
+
case "$sub" in
|
|
655
|
+
verify) do_verify "$@" ;;
|
|
656
|
+
render) do_render "$@" ;;
|
|
657
|
+
esac
|