@soleur/opencode 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.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 +659 -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 +108 -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 +342 -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 +152 -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 +493 -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 +363 -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 +520 -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 +113 -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 +178 -0
- package/scripts/sync-opencode-adapter.ts +479 -0
- package/scripts/sync-pr-behind.sh +534 -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 +65 -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 +142 -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 +118 -0
- package/skills/fix-issue/references/exclude-label-jq-snippet.md +96 -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 +668 -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 +325 -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 +138 -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 +1621 -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-todo-structure.md +165 -0
- package/skills/review/references/risk-tier-and-fix-rounds.md +95 -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 +3022 -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/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/detached-battery-verdict.md +13 -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 +149 -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/work/SKILL.md +1479 -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,981 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Regression suite for github-community.sh -- Ref #6695.
|
|
3
|
+
#
|
|
4
|
+
# Covers the three reported collector defects:
|
|
5
|
+
# RC1 activity/contributors died with "Argument list too long" because the
|
|
6
|
+
# whole payload was passed as ONE execve argument. The binding limit is
|
|
7
|
+
# MAX_ARG_STRLEN (131,072 B PER ARGUMENT), not ARG_MAX (2 MB) -- which is
|
|
8
|
+
# why it fired on only 10 commits and recurred every run.
|
|
9
|
+
# RC2 repo-stats merged the stargazer fetch's stderr into the JSON stream, so
|
|
10
|
+
# any stderr byte poisoned the parse.
|
|
11
|
+
# RC3 an exit-0 error body (404/403/410) rendered a plausible "0 new
|
|
12
|
+
# stargazers", indistinguishable from a quiet day.
|
|
13
|
+
#
|
|
14
|
+
# Fixtures are SYNTHESIZED, never captured (cq-test-fixtures-synthesized-only).
|
|
15
|
+
# TMPDIR is private per case so the cleanup assertions cannot false-pass on a
|
|
16
|
+
# shared /tmp.
|
|
17
|
+
|
|
18
|
+
set -uo pipefail
|
|
19
|
+
|
|
20
|
+
SUITE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
21
|
+
REPO_ROOT="$(cd "$SUITE_DIR/../../../../.." && pwd)"
|
|
22
|
+
COLLECTOR="$REPO_ROOT/plugins/soleur/skills/community/scripts/github-community.sh"
|
|
23
|
+
HELPERS="$REPO_ROOT/plugins/soleur/test/test-helpers.sh"
|
|
24
|
+
|
|
25
|
+
# Path arithmetic is a claim: verify both resolved paths before trusting them
|
|
26
|
+
# (hr-when-a-plan-specifies-relative-paths-e-g).
|
|
27
|
+
for required in "$COLLECTOR" "$HELPERS"; do
|
|
28
|
+
if [[ ! -f "$required" ]]; then
|
|
29
|
+
echo "FATAL: expected file not found: $required" >&2
|
|
30
|
+
echo " (REPO_ROOT resolved to '$REPO_ROOT')" >&2
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
done
|
|
34
|
+
|
|
35
|
+
# shellcheck source=/dev/null
|
|
36
|
+
source "$HELPERS"
|
|
37
|
+
# test-helpers.sh sets -e; this suite accumulates failures and exits at the end,
|
|
38
|
+
# so errexit must be off or a deliberate non-zero probe aborts before fail().
|
|
39
|
+
set +e
|
|
40
|
+
|
|
41
|
+
WORK="$(mktemp -d)"
|
|
42
|
+
trap 'rm -rf "$WORK"' EXIT
|
|
43
|
+
|
|
44
|
+
NOW="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
45
|
+
|
|
46
|
+
# --- local assertions -------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
assert_rc() {
|
|
49
|
+
local expected="$1" actual="$2" msg="$3"
|
|
50
|
+
if [[ "$expected" == "$actual" ]]; then
|
|
51
|
+
echo " PASS: $msg"; PASS=$((PASS + 1))
|
|
52
|
+
else
|
|
53
|
+
echo " FAIL: $msg (expected exit $expected, got $actual)"; FAIL=$((FAIL + 1))
|
|
54
|
+
fi
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
assert_nonzero_rc() {
|
|
58
|
+
local actual="$1" msg="$2"
|
|
59
|
+
if [[ "$actual" != "0" ]]; then
|
|
60
|
+
echo " PASS: $msg"; PASS=$((PASS + 1))
|
|
61
|
+
else
|
|
62
|
+
echo " FAIL: $msg (expected non-zero exit, got 0)"; FAIL=$((FAIL + 1))
|
|
63
|
+
fi
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# Asserts a jq predicate over a file. Anchors on the parsed structure rather
|
|
67
|
+
# than a bare token, so it cannot false-pass on prose (cq-assert-anchor-not-bare-token).
|
|
68
|
+
assert_jq() {
|
|
69
|
+
local file="$1" filter="$2" msg="$3"
|
|
70
|
+
if jq -e "$filter" <"$file" >/dev/null 2>&1; then
|
|
71
|
+
echo " PASS: $msg"; PASS=$((PASS + 1))
|
|
72
|
+
else
|
|
73
|
+
echo " FAIL: $msg"
|
|
74
|
+
echo " filter: $filter"
|
|
75
|
+
echo " actual: $(head -c 300 "$file")"
|
|
76
|
+
FAIL=$((FAIL + 1))
|
|
77
|
+
fi
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# grep against a FILE, never `producer | grep -q` -- an early match under
|
|
81
|
+
# pipefail takes SIGPIPE (141) and the assertion silently inverts.
|
|
82
|
+
assert_file_matches() {
|
|
83
|
+
local file="$1" pattern="$2" msg="$3"
|
|
84
|
+
local n
|
|
85
|
+
n=$(grep -cE -- "$pattern" "$file" 2>/dev/null)
|
|
86
|
+
if [[ "${n:-0}" -gt 0 ]]; then
|
|
87
|
+
echo " PASS: $msg"; PASS=$((PASS + 1))
|
|
88
|
+
else
|
|
89
|
+
echo " FAIL: $msg (no match for /$pattern/)"
|
|
90
|
+
echo " actual: $(head -c 300 "$file")"
|
|
91
|
+
FAIL=$((FAIL + 1))
|
|
92
|
+
fi
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
assert_file_not_matches() {
|
|
96
|
+
local file="$1" pattern="$2" msg="$3"
|
|
97
|
+
local n
|
|
98
|
+
n=$(grep -cE -- "$pattern" "$file" 2>/dev/null)
|
|
99
|
+
if [[ "${n:-0}" -eq 0 ]]; then
|
|
100
|
+
echo " PASS: $msg"; PASS=$((PASS + 1))
|
|
101
|
+
else
|
|
102
|
+
echo " FAIL: $msg (unexpected match for /$pattern/)"
|
|
103
|
+
echo " actual: $(head -c 300 "$file")"
|
|
104
|
+
FAIL=$((FAIL + 1))
|
|
105
|
+
fi
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# --- fixture synthesis ------------------------------------------------------
|
|
109
|
+
|
|
110
|
+
# A payload is "large" when a single serialized argument exceeds MAX_ARG_STRLEN
|
|
111
|
+
# (131,072 B). 10 items x 15,000 B of padding clears it with margin, and mirrors
|
|
112
|
+
# the production shape: few items, large bodies.
|
|
113
|
+
PAD=15000
|
|
114
|
+
BIG_N=10
|
|
115
|
+
|
|
116
|
+
gen_issues() { jq -nc --argjson n "$1" --argjson pad "$2" --arg ts "$NOW" \
|
|
117
|
+
'[range($n) | {number: (.+1), title: ("issue " + (.|tostring)), state: "open",
|
|
118
|
+
user: {login: ("user" + ((. % 3)|tostring))}, created_at: $ts,
|
|
119
|
+
updated_at: $ts, pull_request: null, body: ("x" * $pad)}]'; }
|
|
120
|
+
|
|
121
|
+
gen_pulls() { jq -nc --argjson n "$1" --argjson pad "$2" --arg ts "$NOW" \
|
|
122
|
+
'[range($n) | {number: (.+1), title: ("pr " + (.|tostring)), state: "open",
|
|
123
|
+
user: {login: ("user" + ((. % 3)|tostring))}, created_at: $ts,
|
|
124
|
+
updated_at: $ts, merged_at: null, body: ("x" * $pad)}]'; }
|
|
125
|
+
|
|
126
|
+
gen_commits() { jq -nc --argjson n "$1" --argjson pad "$2" \
|
|
127
|
+
'[range($n) | {sha: ("sha" + (.|tostring)),
|
|
128
|
+
author: {login: ("dev" + ((. % 2)|tostring))},
|
|
129
|
+
commit: {author: {name: "Dev"}, message: ("x" * $pad)}}]'; }
|
|
130
|
+
|
|
131
|
+
gen_stargazers() { jq -nc --argjson n "$1" --argjson pad "$2" --arg ts "$NOW" \
|
|
132
|
+
'[range($n) | {starred_at: $ts, user: {login: ("gazer" + (.|tostring))},
|
|
133
|
+
pad: ("x" * $pad)}]'; }
|
|
134
|
+
|
|
135
|
+
gen_comments() { jq -nc --argjson n "$1" --arg ts "$NOW" \
|
|
136
|
+
'[range($n) | {author_association: "NONE",
|
|
137
|
+
user: {login: ("outsider" + (.|tostring)), type: "User"},
|
|
138
|
+
issue_url: "https://api.github.com/repos/o/r/issues/42",
|
|
139
|
+
body: "a comment", html_url: "https://github.com/o/r/issues/42",
|
|
140
|
+
created_at: $ts}]'; }
|
|
141
|
+
|
|
142
|
+
gen_repo() { jq -nc '{stargazers_count: 11, forks_count: 2,
|
|
143
|
+
watchers_count: 11, subscribers_count: 1}'; }
|
|
144
|
+
|
|
145
|
+
# Builds a fresh stub + fixture dir, returns the case root on stdout.
|
|
146
|
+
new_case() {
|
|
147
|
+
local name="$1"
|
|
148
|
+
local root="$WORK/$name"
|
|
149
|
+
mkdir -p "$root/stub" "$root/fixtures" "$root/tmp" "$root/out"
|
|
150
|
+
make_gh_api_stub "$root/stub" "$root/fixtures"
|
|
151
|
+
echo "$root"
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
# Runs the collector for a case. Sets RC; stdout/stderr land in $root/out.
|
|
155
|
+
run_collector() {
|
|
156
|
+
local root="$1" cmd="$2" days="${3:-1}"
|
|
157
|
+
(
|
|
158
|
+
export PATH="$root/stub:$PATH"
|
|
159
|
+
export GITHUB_REPOSITORY="test-owner/test-repo"
|
|
160
|
+
export TMPDIR="$root/tmp"
|
|
161
|
+
bash "$COLLECTOR" "$cmd" "$days"
|
|
162
|
+
) >"$root/out/stdout" 2>"$root/out/stderr"
|
|
163
|
+
RC=$?
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
count_tmp_residue() {
|
|
167
|
+
find "$1" -mindepth 1 2>/dev/null | wc -l | tr -d ' '
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
echo "=== github-community.sh regression suite (#6695) ==="
|
|
171
|
+
echo ""
|
|
172
|
+
|
|
173
|
+
# --- Test 1: RC1 large payload, parametric over 3 commands / 5 bindings -----
|
|
174
|
+
#
|
|
175
|
+
# One-binding coverage is not enough: `prs` mis-unwrapped while `issues` is
|
|
176
|
+
# correct yields a partially-correct digest that looks exactly like a quiet day.
|
|
177
|
+
# The `count == (items|length)` invariant catches the PARTIAL unwrap, which is
|
|
178
|
+
# the silent shape -- a full non-unwrap hard-errors and is comparatively safe.
|
|
179
|
+
|
|
180
|
+
echo "Test 1: large payloads (>131,072 B/arg) across all five bindings"
|
|
181
|
+
|
|
182
|
+
CASE1="$(new_case rc1)"
|
|
183
|
+
gen_issues "$BIG_N" "$PAD" > "$CASE1/fixtures/issues.json"
|
|
184
|
+
gen_pulls "$BIG_N" "$PAD" > "$CASE1/fixtures/pulls.json"
|
|
185
|
+
gen_commits "$BIG_N" "$PAD" > "$CASE1/fixtures/commits.json"
|
|
186
|
+
gen_stargazers "$BIG_N" "$PAD" > "$CASE1/fixtures/stargazers.json"
|
|
187
|
+
gen_repo > "$CASE1/fixtures/repo.json"
|
|
188
|
+
|
|
189
|
+
# Precondition: the fixtures must actually breach the per-argument ceiling, or
|
|
190
|
+
# every assertion below is vacuous.
|
|
191
|
+
CARDINALITY=0
|
|
192
|
+
for f in issues pulls commits stargazers; do
|
|
193
|
+
sz=$(wc -c < "$CASE1/fixtures/$f.json")
|
|
194
|
+
CARDINALITY=$((CARDINALITY + 1))
|
|
195
|
+
if [[ "$sz" -le 131072 ]]; then
|
|
196
|
+
echo " FAIL: fixture $f.json is $sz B, below the 131,072 B ceiling under test"
|
|
197
|
+
FAIL=$((FAIL + 1))
|
|
198
|
+
fi
|
|
199
|
+
done
|
|
200
|
+
if [[ "$CARDINALITY" -ne 4 ]]; then
|
|
201
|
+
echo " FAIL: fixture size guard covered $CARDINALITY/4 payloads"
|
|
202
|
+
FAIL=$((FAIL + 1))
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
run_collector "$CASE1" activity 1
|
|
206
|
+
assert_rc 0 "$RC" "activity exits 0 on a large payload"
|
|
207
|
+
assert_jq "$CASE1/out/stdout" \
|
|
208
|
+
'.issues.count == (.issues.items | length)' \
|
|
209
|
+
"activity: issues.count == issues.items|length (partial-unwrap guard)"
|
|
210
|
+
assert_jq "$CASE1/out/stdout" \
|
|
211
|
+
".pull_requests.count == (.pull_requests.items | length)" \
|
|
212
|
+
"activity: pull_requests.count == pull_requests.items|length (partial-unwrap guard)"
|
|
213
|
+
assert_jq "$CASE1/out/stdout" \
|
|
214
|
+
".issues.count == $BIG_N and .pull_requests.count == $BIG_N" \
|
|
215
|
+
"activity: both counts equal the $BIG_N synthesized items"
|
|
216
|
+
|
|
217
|
+
run_collector "$CASE1" contributors 1
|
|
218
|
+
assert_rc 0 "$RC" "contributors exits 0 on a large payload"
|
|
219
|
+
assert_jq "$CASE1/out/stdout" \
|
|
220
|
+
"([.commit_authors[].commits] | add) == $BIG_N" \
|
|
221
|
+
"contributors: commit totals sum to the $BIG_N synthesized commits"
|
|
222
|
+
assert_jq "$CASE1/out/stdout" \
|
|
223
|
+
"([.issue_authors[].activity] | add) == $BIG_N" \
|
|
224
|
+
"contributors: issue totals sum to the $BIG_N synthesized issues"
|
|
225
|
+
|
|
226
|
+
run_collector "$CASE1" repo-stats 1
|
|
227
|
+
assert_rc 0 "$RC" "repo-stats exits 0 on a large stargazer payload"
|
|
228
|
+
assert_jq "$CASE1/out/stdout" \
|
|
229
|
+
'.new_stargazers_count == (.new_stargazers | length)' \
|
|
230
|
+
"repo-stats: new_stargazers_count == new_stargazers|length (partial-unwrap guard)"
|
|
231
|
+
assert_jq "$CASE1/out/stdout" \
|
|
232
|
+
".new_stargazers_count == $BIG_N" \
|
|
233
|
+
"repo-stats: new stargazer count equals the $BIG_N synthesized entries"
|
|
234
|
+
|
|
235
|
+
echo ""
|
|
236
|
+
|
|
237
|
+
# --- Test 2: RC2 stderr noise on the stargazer path -------------------------
|
|
238
|
+
|
|
239
|
+
echo "Test 2: stderr noise must not poison the stargazer JSON stream"
|
|
240
|
+
|
|
241
|
+
CASE2="$(new_case rc2)"
|
|
242
|
+
gen_stargazers 3 16 > "$CASE2/fixtures/stargazers.json"
|
|
243
|
+
gen_repo > "$CASE2/fixtures/repo.json"
|
|
244
|
+
printf 'gh: warning: some pages were rate-limited\n' > "$CASE2/fixtures/stargazers.stderr"
|
|
245
|
+
|
|
246
|
+
run_collector "$CASE2" repo-stats 1
|
|
247
|
+
assert_rc 0 "$RC" "repo-stats parses despite stderr noise on the stargazer fetch"
|
|
248
|
+
assert_jq "$CASE2/out/stdout" '.stargazers_count == 11' \
|
|
249
|
+
"repo-stats: stargazers_count survives stderr noise"
|
|
250
|
+
assert_jq "$CASE2/out/stdout" '.new_stargazers_count == 3' \
|
|
251
|
+
"repo-stats: new_stargazers_count survives stderr noise"
|
|
252
|
+
|
|
253
|
+
echo ""
|
|
254
|
+
|
|
255
|
+
# --- Test 2b: paginated (multi-array) body ----------------------------------
|
|
256
|
+
#
|
|
257
|
+
# `gh --paginate` emits ONE JSON ARRAY PER PAGE, concatenated. Slurping wraps
|
|
258
|
+
# them as [[...],[...]], so the dereference must FLATTEN, not take the first
|
|
259
|
+
# page. Every other fixture in this suite is a single array, under which
|
|
260
|
+
# `add // []` and `.[0] // []` are indistinguishable -- so without this case the
|
|
261
|
+
# flattening is unverified and a first-page-only regression reads as green while
|
|
262
|
+
# silently undercounting. That is the same plausible-wrong-number class as RC3,
|
|
263
|
+
# in the fix for RC3.
|
|
264
|
+
|
|
265
|
+
echo "Test 2b: a paginated multi-array body is flattened, not truncated to page 1"
|
|
266
|
+
|
|
267
|
+
CASE2B="$(new_case paginated)"
|
|
268
|
+
gen_repo > "$CASE2B/fixtures/repo.json"
|
|
269
|
+
# Two pages: 100 + 50. Concatenated exactly as --paginate emits them.
|
|
270
|
+
{ gen_stargazers 100 16; gen_stargazers 50 16; } > "$CASE2B/fixtures/stargazers.json"
|
|
271
|
+
|
|
272
|
+
# Precondition: the fixture really does hold more than one JSON value.
|
|
273
|
+
PAGES=$(jq -s 'length' < "$CASE2B/fixtures/stargazers.json")
|
|
274
|
+
assert_eq "2" "$PAGES" "fixture holds two concatenated page arrays"
|
|
275
|
+
|
|
276
|
+
run_collector "$CASE2B" repo-stats 1
|
|
277
|
+
assert_rc 0 "$RC" "repo-stats exits 0 on a paginated body"
|
|
278
|
+
assert_jq "$CASE2B/out/stdout" '.new_stargazers_count == 150' "repo-stats sums BOTH pages (150), not just the first (100)"
|
|
279
|
+
assert_jq "$CASE2B/out/stdout" \
|
|
280
|
+
'.new_stargazers_count == (.new_stargazers | length)' \
|
|
281
|
+
"repo-stats: count still matches items on a paginated body"
|
|
282
|
+
|
|
283
|
+
echo ""
|
|
284
|
+
|
|
285
|
+
# --- Test 3: RC3/D6/H3 exit-0 error body ------------------------------------
|
|
286
|
+
#
|
|
287
|
+
# A 404/403/410 body arrives at exit 0. Unguarded, it renders a plausible
|
|
288
|
+
# new_stargazers_count: 0 -- the fabrication path the digest actually shipped.
|
|
289
|
+
|
|
290
|
+
echo "Test 3: an exit-0 error body must fail loudly, never render a plausible 0"
|
|
291
|
+
|
|
292
|
+
CASE3="$(new_case rc3)"
|
|
293
|
+
gen_repo > "$CASE3/fixtures/repo.json"
|
|
294
|
+
jq -nc '{message: "Not Found", documentation_url: "https://docs.github.com/rest"}' \
|
|
295
|
+
> "$CASE3/fixtures/stargazers.json"
|
|
296
|
+
|
|
297
|
+
run_collector "$CASE3" repo-stats 1
|
|
298
|
+
assert_nonzero_rc "$RC" "repo-stats exits non-zero on an exit-0 error body"
|
|
299
|
+
assert_file_not_matches "$CASE3/out/stdout" '"new_stargazers_count"' \
|
|
300
|
+
"repo-stats emits no new_stargazers_count on an error body"
|
|
301
|
+
assert_file_not_matches "$CASE3/out/stdout" '"stargazers_count"' \
|
|
302
|
+
"repo-stats emits no stargazers_count on an error body"
|
|
303
|
+
assert_file_matches "$CASE3/out/stderr" 'GITHUB_COLLECTOR_CAUSE=' \
|
|
304
|
+
"repo-stats reports a machine-readable cause on an error body"
|
|
305
|
+
|
|
306
|
+
echo ""
|
|
307
|
+
|
|
308
|
+
# --- Test 3b: exit-0 with an EMPTY body -------------------------------------
|
|
309
|
+
#
|
|
310
|
+
# The API renders "no results" as [], so zero bytes means the response was lost.
|
|
311
|
+
# Slurping an empty file yields [], which would render a plausible 0 -- the last
|
|
312
|
+
# path by which a missing fetch could still look like a quiet day.
|
|
313
|
+
|
|
314
|
+
echo "Test 3b: an exit-0 empty body must fail loudly, not read as a quiet day"
|
|
315
|
+
|
|
316
|
+
CASE3B="$(new_case rc3_empty)"
|
|
317
|
+
gen_repo > "$CASE3B/fixtures/repo.json"
|
|
318
|
+
: > "$CASE3B/fixtures/stargazers.json"
|
|
319
|
+
|
|
320
|
+
run_collector "$CASE3B" repo-stats 1
|
|
321
|
+
assert_nonzero_rc "$RC" "repo-stats exits non-zero on an empty stargazer body"
|
|
322
|
+
assert_file_not_matches "$CASE3B/out/stdout" '"new_stargazers_count"' \
|
|
323
|
+
"repo-stats emits no new_stargazers_count on an empty body"
|
|
324
|
+
assert_file_matches "$CASE3B/out/stderr" 'GITHUB_COLLECTOR_CAUSE=stargazers returned an empty body' \
|
|
325
|
+
"repo-stats names the empty body as the cause"
|
|
326
|
+
|
|
327
|
+
echo ""
|
|
328
|
+
|
|
329
|
+
# --- Test 4: D7 multi-tempfile cleanup on a FAILURE path ---------------------
|
|
330
|
+
#
|
|
331
|
+
# Forward guard: it cannot be RED before the fix, because the pre-fix script
|
|
332
|
+
# creates no tempfiles in these commands. Its value is mutation-testing --
|
|
333
|
+
# splitting the single trap into one-trap-per-file must turn it red, since bash
|
|
334
|
+
# EXIT traps are global and singular (the second REPLACES the first).
|
|
335
|
+
# A single-tempfile command could not detect that class at all.
|
|
336
|
+
|
|
337
|
+
echo "Test 4: multi-tempfile command leaks nothing on a failure path"
|
|
338
|
+
|
|
339
|
+
CASE4="$(new_case d7)"
|
|
340
|
+
gen_issues 3 16 > "$CASE4/fixtures/issues.json"
|
|
341
|
+
printf '1\n' > "$CASE4/fixtures/pulls.exit"
|
|
342
|
+
printf 'gh: server error fetching pulls\n' > "$CASE4/fixtures/pulls.stderr"
|
|
343
|
+
|
|
344
|
+
run_collector "$CASE4" activity 1
|
|
345
|
+
assert_nonzero_rc "$RC" "activity exits non-zero when the PR fetch fails"
|
|
346
|
+
assert_eq "0" "$(count_tmp_residue "$CASE4/tmp")" \
|
|
347
|
+
"activity leaves zero tempfile residue in a private TMPDIR after failing"
|
|
348
|
+
|
|
349
|
+
echo ""
|
|
350
|
+
|
|
351
|
+
# --- Test 5: D5 collector-status sidecar ------------------------------------
|
|
352
|
+
#
|
|
353
|
+
# The deterministic signal. Every other failure channel the collector has
|
|
354
|
+
# terminates in the spawned agent's context window -- this one is read by the
|
|
355
|
+
# handler directly from spawnCwd, with no LLM in the path.
|
|
356
|
+
|
|
357
|
+
echo "Test 5: collector-status sidecar records real exit codes"
|
|
358
|
+
|
|
359
|
+
CASE5="$(new_case d5ok)"
|
|
360
|
+
gen_issues 2 16 > "$CASE5/fixtures/issues.json"
|
|
361
|
+
gen_pulls 2 16 > "$CASE5/fixtures/pulls.json"
|
|
362
|
+
STATUS_DIR="$CASE5/status"
|
|
363
|
+
(
|
|
364
|
+
export PATH="$CASE5/stub:$PATH"
|
|
365
|
+
export GITHUB_REPOSITORY="test-owner/test-repo"
|
|
366
|
+
export TMPDIR="$CASE5/tmp"
|
|
367
|
+
export SOLEUR_COLLECTOR_STATUS_DIR="$STATUS_DIR"
|
|
368
|
+
bash "$COLLECTOR" activity 1
|
|
369
|
+
) >"$CASE5/out/stdout" 2>"$CASE5/out/stderr"
|
|
370
|
+
RC=$?
|
|
371
|
+
assert_rc 0 "$RC" "activity succeeds with the status dir set"
|
|
372
|
+
assert_file_exists "$STATUS_DIR/collector-status.jsonl" \
|
|
373
|
+
"sidecar file is created when SOLEUR_COLLECTOR_STATUS_DIR is set"
|
|
374
|
+
if [[ -f "$STATUS_DIR/collector-status.jsonl" ]]; then
|
|
375
|
+
assert_jq "$STATUS_DIR/collector-status.jsonl" \
|
|
376
|
+
'.collector == "github" and .command == "activity" and .exit == 0' \
|
|
377
|
+
"sidecar records exit 0 for a successful activity run"
|
|
378
|
+
fi
|
|
379
|
+
|
|
380
|
+
CASE5F="$(new_case d5fail)"
|
|
381
|
+
printf '1\n' > "$CASE5F/fixtures/issues.exit"
|
|
382
|
+
printf 'gh: boom\n' > "$CASE5F/fixtures/issues.stderr"
|
|
383
|
+
STATUS_DIR_F="$CASE5F/status"
|
|
384
|
+
(
|
|
385
|
+
export PATH="$CASE5F/stub:$PATH"
|
|
386
|
+
export GITHUB_REPOSITORY="test-owner/test-repo"
|
|
387
|
+
export TMPDIR="$CASE5F/tmp"
|
|
388
|
+
export SOLEUR_COLLECTOR_STATUS_DIR="$STATUS_DIR_F"
|
|
389
|
+
bash "$COLLECTOR" activity 1
|
|
390
|
+
) >"$CASE5F/out/stdout" 2>"$CASE5F/out/stderr"
|
|
391
|
+
RC=$?
|
|
392
|
+
assert_nonzero_rc "$RC" "activity fails when the issues fetch fails"
|
|
393
|
+
if [[ -f "$STATUS_DIR_F/collector-status.jsonl" ]]; then
|
|
394
|
+
assert_jq "$STATUS_DIR_F/collector-status.jsonl" \
|
|
395
|
+
'.command == "activity" and .exit != 0' \
|
|
396
|
+
"sidecar records a non-zero exit for a failed activity run"
|
|
397
|
+
else
|
|
398
|
+
echo " FAIL: sidecar file missing after a failed run"
|
|
399
|
+
FAIL=$((FAIL + 1))
|
|
400
|
+
fi
|
|
401
|
+
|
|
402
|
+
CASE5U="$(new_case d5unset)"
|
|
403
|
+
gen_issues 2 16 > "$CASE5U/fixtures/issues.json"
|
|
404
|
+
gen_pulls 2 16 > "$CASE5U/fixtures/pulls.json"
|
|
405
|
+
run_collector "$CASE5U" activity 1
|
|
406
|
+
assert_rc 0 "$RC" "activity succeeds with the status dir unset"
|
|
407
|
+
assert_eq "0" "$(find "$CASE5U" -name 'collector-status.jsonl' 2>/dev/null | wc -l | tr -d ' ')" \
|
|
408
|
+
"no sidecar is written when SOLEUR_COLLECTOR_STATUS_DIR is unset"
|
|
409
|
+
|
|
410
|
+
echo ""
|
|
411
|
+
|
|
412
|
+
# --- Test 6: AC4 counter-assertion -- cmd_discussions graceful path ---------
|
|
413
|
+
#
|
|
414
|
+
# cmd_discussions' stderr capture is load-bearing: it classifies the
|
|
415
|
+
# "Discussions not enabled" case into a graceful exit 0. A blanket stderr sweep
|
|
416
|
+
# would convert that into a hard failure. This asserts the sweep did not happen.
|
|
417
|
+
|
|
418
|
+
echo "Test 6: cmd_discussions still degrades gracefully (counter-assertion)"
|
|
419
|
+
|
|
420
|
+
CASE6="$(new_case discussions)"
|
|
421
|
+
printf '1\n' > "$CASE6/fixtures/graphql.exit"
|
|
422
|
+
printf 'Could not resolve to a Repository: discussions are not enabled\n' \
|
|
423
|
+
> "$CASE6/fixtures/graphql.stderr"
|
|
424
|
+
|
|
425
|
+
run_collector "$CASE6" discussions 1
|
|
426
|
+
assert_rc 0 "$RC" "discussions exits 0 when Discussions are not enabled"
|
|
427
|
+
assert_jq "$CASE6/out/stdout" '.discussions == []' \
|
|
428
|
+
"discussions returns the graceful empty payload"
|
|
429
|
+
|
|
430
|
+
echo ""
|
|
431
|
+
|
|
432
|
+
# --- Test 7: AC7/D2 cap detection -------------------------------------------
|
|
433
|
+
|
|
434
|
+
echo "Test 7: a fetch capped at per_page emits a truncation warning"
|
|
435
|
+
|
|
436
|
+
CASE7="$(new_case cap)"
|
|
437
|
+
gen_issues 100 16 > "$CASE7/fixtures/issues.json"
|
|
438
|
+
gen_pulls 2 16 > "$CASE7/fixtures/pulls.json"
|
|
439
|
+
|
|
440
|
+
run_collector "$CASE7" activity 1
|
|
441
|
+
assert_rc 0 "$RC" "activity still succeeds at the per_page cap"
|
|
442
|
+
assert_file_matches "$CASE7/out/stderr" 'truncat' \
|
|
443
|
+
"activity warns when a fetch returns exactly per_page items"
|
|
444
|
+
|
|
445
|
+
# Both arms are required. The pulls endpoint over-fetches a fixed page and
|
|
446
|
+
# filters by date afterwards, so a full RAW page is its steady state -- against
|
|
447
|
+
# the live repo it returns 100 on every run. Warning on that would fire daily
|
|
448
|
+
# and train the reader to ignore the signal, so the cap is measured after the
|
|
449
|
+
# date filter for this endpoint. A detector that cries wolf is worse than none.
|
|
450
|
+
CASE7B="$(new_case cap_negative)"
|
|
451
|
+
gen_issues 3 16 > "$CASE7B/fixtures/issues.json"
|
|
452
|
+
gen_pulls 100 16 > "$CASE7B/fixtures/pulls.json"
|
|
453
|
+
# Age every PR out of the window so the post-filter count is 0, not 100.
|
|
454
|
+
jq -c '[.[] | .updated_at = "2000-01-01T00:00:00Z"]' \
|
|
455
|
+
"$CASE7B/fixtures/pulls.json" > "$CASE7B/fixtures/pulls.tmp" \
|
|
456
|
+
&& mv "$CASE7B/fixtures/pulls.tmp" "$CASE7B/fixtures/pulls.json"
|
|
457
|
+
|
|
458
|
+
run_collector "$CASE7B" activity 1
|
|
459
|
+
assert_rc 0 "$RC" "activity succeeds when pulls returns a full raw page"
|
|
460
|
+
assert_jq "$CASE7B/out/stdout" '.pull_requests.count == 0' \
|
|
461
|
+
"activity: the date filter still excludes out-of-window PRs"
|
|
462
|
+
assert_file_not_matches "$CASE7B/out/stderr" 'truncat' \
|
|
463
|
+
"activity does NOT warn on a full raw pulls page that filters down to 0"
|
|
464
|
+
|
|
465
|
+
# The contributors/issues call site passed a FILE PATH to a helper that takes a
|
|
466
|
+
# COUNT, so `[[ /tmp/tmp.X -eq 100 ]]` raised a bash arithmetic error, returned
|
|
467
|
+
# non-zero inside an `if`, and the detector was permanently dead there. Test 7
|
|
468
|
+
# covered `activity` only, which is why the suite stayed green.
|
|
469
|
+
echo "Test 7c: cap detection is live on the contributors endpoint too"
|
|
470
|
+
|
|
471
|
+
CASE7C="$(new_case cap_contributors)"
|
|
472
|
+
gen_commits 3 16 > "$CASE7C/fixtures/commits.json"
|
|
473
|
+
gen_issues 100 16 > "$CASE7C/fixtures/issues.json"
|
|
474
|
+
|
|
475
|
+
run_collector "$CASE7C" contributors 1
|
|
476
|
+
assert_rc 0 "$RC" "contributors succeeds at the per_page cap"
|
|
477
|
+
assert_file_matches "$CASE7C/out/stderr" 'issues returned exactly 100 items' \
|
|
478
|
+
"contributors warns when its issues fetch returns exactly per_page items"
|
|
479
|
+
assert_file_not_matches "$CASE7C/out/stderr" 'arithmetic syntax error' \
|
|
480
|
+
"contributors emits no bash arithmetic error (helper receives a count)"
|
|
481
|
+
|
|
482
|
+
echo ""
|
|
483
|
+
|
|
484
|
+
# --- Test 8: the sidecar carries the diagnostic payload, not just an exit code -
|
|
485
|
+
#
|
|
486
|
+
# `cause` and `warn` are what the handler renders into the operator page. Both
|
|
487
|
+
# were previously unasserted: blanking every _CAUSE assignment, or dropping the
|
|
488
|
+
# warn field, left the suite fully green.
|
|
489
|
+
|
|
490
|
+
echo "Test 8: sidecar records carry cause and warn"
|
|
491
|
+
|
|
492
|
+
CASE8="$(new_case sidecar_fields)"
|
|
493
|
+
gen_repo > "$CASE8/fixtures/repo.json"
|
|
494
|
+
jq -nc '{message: "Not Found"}' > "$CASE8/fixtures/stargazers.json"
|
|
495
|
+
STATUS8="$CASE8/status"
|
|
496
|
+
(
|
|
497
|
+
export PATH="$CASE8/stub:$PATH"
|
|
498
|
+
export GITHUB_REPOSITORY="test-owner/test-repo"
|
|
499
|
+
export TMPDIR="$CASE8/tmp"
|
|
500
|
+
export SOLEUR_COLLECTOR_STATUS_DIR="$STATUS8"
|
|
501
|
+
bash "$COLLECTOR" repo-stats 1
|
|
502
|
+
) >"$CASE8/out/stdout" 2>"$CASE8/out/stderr"
|
|
503
|
+
RC=$?
|
|
504
|
+
assert_nonzero_rc "$RC" "repo-stats fails on an error body"
|
|
505
|
+
if [[ -f "$STATUS8/collector-status.jsonl" ]]; then
|
|
506
|
+
assert_jq "$STATUS8/collector-status.jsonl" \
|
|
507
|
+
'.cause == "stargazers-non-array"' \
|
|
508
|
+
"sidecar records the specific cause, not an empty string"
|
|
509
|
+
else
|
|
510
|
+
echo " FAIL: sidecar missing after error-body run"; FAIL=$((FAIL + 1))
|
|
511
|
+
fi
|
|
512
|
+
|
|
513
|
+
CASE8W="$(new_case sidecar_warn)"
|
|
514
|
+
gen_commits 3 16 > "$CASE8W/fixtures/commits.json"
|
|
515
|
+
gen_issues 100 16 > "$CASE8W/fixtures/issues.json"
|
|
516
|
+
STATUS8W="$CASE8W/status"
|
|
517
|
+
(
|
|
518
|
+
export PATH="$CASE8W/stub:$PATH"
|
|
519
|
+
export GITHUB_REPOSITORY="test-owner/test-repo"
|
|
520
|
+
export TMPDIR="$CASE8W/tmp"
|
|
521
|
+
export SOLEUR_COLLECTOR_STATUS_DIR="$STATUS8W"
|
|
522
|
+
export SOLEUR_COLLECTOR_COMPACT=1
|
|
523
|
+
bash "$COLLECTOR" contributors 1
|
|
524
|
+
) >"$CASE8W/out/stdout" 2>"$CASE8W/out/stderr"
|
|
525
|
+
RC=$?
|
|
526
|
+
assert_rc 0 "$RC" "contributors succeeds with a status dir set"
|
|
527
|
+
# 100 issues are NOT read in compact mode (the line carries only the commit total), so the
|
|
528
|
+
# truncation signal comes from the commits cap.
|
|
529
|
+
gen_commits 100 16 > "$CASE8W/fixtures/commits.json"
|
|
530
|
+
rm -f "$STATUS8W/collector-status.jsonl"
|
|
531
|
+
(
|
|
532
|
+
export PATH="$CASE8W/stub:$PATH"
|
|
533
|
+
export GITHUB_REPOSITORY="test-owner/test-repo"
|
|
534
|
+
export TMPDIR="$CASE8W/tmp"
|
|
535
|
+
export SOLEUR_COLLECTOR_STATUS_DIR="$STATUS8W"
|
|
536
|
+
export SOLEUR_COLLECTOR_COMPACT=1
|
|
537
|
+
bash "$COLLECTOR" contributors 1
|
|
538
|
+
) >"$CASE8W/out/stdout" 2>"$CASE8W/out/stderr"
|
|
539
|
+
RC=$?
|
|
540
|
+
assert_rc 0 "$RC" "contributors succeeds at the commits cap"
|
|
541
|
+
if [[ -f "$STATUS8W/collector-status.jsonl" ]]; then
|
|
542
|
+
assert_jq "$STATUS8W/collector-status.jsonl" \
|
|
543
|
+
'.warn == "truncated_at_per_page"' \
|
|
544
|
+
"sidecar carries the truncation warn field the handler reports on"
|
|
545
|
+
else
|
|
546
|
+
echo " FAIL: sidecar missing after capped run"; FAIL=$((FAIL + 1))
|
|
547
|
+
fi
|
|
548
|
+
# Compact contributors must not call the issues endpoint at all: make it FAIL and require success
|
|
549
|
+
# with no warn (a reordering that fetches issues again would exit 1 here).
|
|
550
|
+
echo 1 > "$CASE8W/fixtures/issues.exit"
|
|
551
|
+
printf 'boom\n' > "$CASE8W/fixtures/issues.stderr"
|
|
552
|
+
gen_commits 3 16 > "$CASE8W/fixtures/commits.json"
|
|
553
|
+
rm -f "$STATUS8W/collector-status.jsonl"
|
|
554
|
+
(
|
|
555
|
+
export PATH="$CASE8W/stub:$PATH"
|
|
556
|
+
export GITHUB_REPOSITORY="test-owner/test-repo"
|
|
557
|
+
export TMPDIR="$CASE8W/tmp"
|
|
558
|
+
export SOLEUR_COLLECTOR_STATUS_DIR="$STATUS8W"
|
|
559
|
+
export SOLEUR_COLLECTOR_COMPACT=1
|
|
560
|
+
bash "$COLLECTOR" contributors 1
|
|
561
|
+
) >"$CASE8W/out/stdout" 2>"$CASE8W/out/stderr"
|
|
562
|
+
RC=$?
|
|
563
|
+
assert_rc 0 "$RC" "compact contributors never calls the issues endpoint"
|
|
564
|
+
assert_jq "$STATUS8W/collector-status.jsonl" 'has("warn") | not' "no warn when only the unused issues list would have been capped"
|
|
565
|
+
rm -f "$CASE8W/fixtures/issues.exit" "$CASE8W/fixtures/issues.stderr"
|
|
566
|
+
gen_commits 100 16 > "$CASE8W/fixtures/commits.json"
|
|
567
|
+
# The same capped run WITHOUT the flag (a cron whose flag did not arrive): compact_off outranks the
|
|
568
|
+
# truncation warn, so the flag regression is not hidden behind the per_page cap.
|
|
569
|
+
rm -f "$STATUS8W/collector-status.jsonl"
|
|
570
|
+
(
|
|
571
|
+
export PATH="$CASE8W/stub:$PATH"
|
|
572
|
+
export GITHUB_REPOSITORY="test-owner/test-repo"
|
|
573
|
+
export TMPDIR="$CASE8W/tmp"
|
|
574
|
+
export SOLEUR_COLLECTOR_STATUS_DIR="$STATUS8W"
|
|
575
|
+
bash "$COLLECTOR" contributors 1
|
|
576
|
+
) >"$CASE8W/out/stdout" 2>"$CASE8W/out/stderr"
|
|
577
|
+
assert_jq "$STATUS8W/collector-status.jsonl" '.warn == "compact_off"' \
|
|
578
|
+
"compact_off outranks truncated_at_per_page"
|
|
579
|
+
|
|
580
|
+
# --- Test 9: fetch-interactions -------------------------------------------
|
|
581
|
+
#
|
|
582
|
+
# This command runs in production on every digest but had zero coverage. Its old
|
|
583
|
+
# form piped through `add // []` BEFORE any validation and discarded stderr, so a
|
|
584
|
+
# lost or error response collapsed into an empty interaction list at exit 0 --
|
|
585
|
+
# a quiet day, indistinguishable from a real one.
|
|
586
|
+
|
|
587
|
+
echo "Test 9: fetch-interactions parses, and fails loudly on an error body"
|
|
588
|
+
|
|
589
|
+
CASE9="$(new_case interactions)"
|
|
590
|
+
gen_comments 3 > "$CASE9/fixtures/issue_comments.json"
|
|
591
|
+
|
|
592
|
+
run_collector "$CASE9" fetch-interactions 1
|
|
593
|
+
assert_rc 0 "$RC" "fetch-interactions exits 0 on a valid body"
|
|
594
|
+
assert_jq "$CASE9/out/stdout" '(.interactions | length) == 3' \
|
|
595
|
+
"fetch-interactions returns all three external comments"
|
|
596
|
+
|
|
597
|
+
# Paginated shape: the same flattening requirement as the stargazer path.
|
|
598
|
+
CASE9P="$(new_case interactions_paged)"
|
|
599
|
+
{ gen_comments 2; gen_comments 2; } > "$CASE9P/fixtures/issue_comments.json"
|
|
600
|
+
run_collector "$CASE9P" fetch-interactions 1
|
|
601
|
+
assert_rc 0 "$RC" "fetch-interactions exits 0 on a paginated body"
|
|
602
|
+
assert_jq "$CASE9P/out/stdout" '(.interactions | length) == 4' \
|
|
603
|
+
"fetch-interactions flattens both pages (4), not just the first (2)"
|
|
604
|
+
|
|
605
|
+
# Exit-0 error body must not read as "no interactions today".
|
|
606
|
+
CASE9E="$(new_case interactions_error)"
|
|
607
|
+
jq -nc '{message: "Not Found"}' > "$CASE9E/fixtures/issue_comments.json"
|
|
608
|
+
run_collector "$CASE9E" fetch-interactions 1
|
|
609
|
+
assert_nonzero_rc "$RC" "fetch-interactions exits non-zero on an exit-0 error body"
|
|
610
|
+
assert_file_not_matches "$CASE9E/out/stdout" '"interactions"' \
|
|
611
|
+
"fetch-interactions emits no empty interactions list on an error body"
|
|
612
|
+
assert_file_matches "$CASE9E/out/stderr" 'GITHUB_COLLECTOR_CAUSE=issue-comments' \
|
|
613
|
+
"fetch-interactions names issue-comments as the cause"
|
|
614
|
+
|
|
615
|
+
# --- Test 10: compact mode (#9678) ------------------------------------------
|
|
616
|
+
#
|
|
617
|
+
# The spawned agent has no file tools, so collector output past the Bash tool's inline
|
|
618
|
+
# limit (30,000 characters) is unreadable and the digest row goes `partial`. With
|
|
619
|
+
# SOLEUR_COLLECTOR_COMPACT=1 each command prints ONE line holding only the fields the
|
|
620
|
+
# prompt reads; unset (or any other value) the output is the unchanged interactive shape.
|
|
621
|
+
|
|
622
|
+
echo "Test 10: compact mode projects to the prompt's fields within the inline budget"
|
|
623
|
+
|
|
624
|
+
INLINE_LIMIT=30000
|
|
625
|
+
LONG=256
|
|
626
|
+
|
|
627
|
+
# Runs the collector with extra environment ("NAME=value ..." in $2). Sets RC.
|
|
628
|
+
run_collector_env() {
|
|
629
|
+
local root="$1" envs="$2" cmd="$3" days="${4:-1}"
|
|
630
|
+
local -a pairs=()
|
|
631
|
+
# shellcheck disable=SC2206
|
|
632
|
+
[[ -n "$envs" ]] && pairs=($envs)
|
|
633
|
+
(
|
|
634
|
+
export PATH="$root/stub:$PATH"
|
|
635
|
+
export GITHUB_REPOSITORY="test-owner/test-repo"
|
|
636
|
+
export TMPDIR="$root/tmp"
|
|
637
|
+
env "${pairs[@]}" bash "$COLLECTOR" "$cmd" "$days"
|
|
638
|
+
) >"$root/out/stdout" 2>"$root/out/stderr"
|
|
639
|
+
RC=$?
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
# Issues/PRs with long titles and distinct, strictly increasing updated_at (item 100
|
|
643
|
+
# is the newest), so "newest first" is observable and the title cap has work to do.
|
|
644
|
+
gen_long_issues() { jq -nc --argjson n "$1" --argjson len "$2" \
|
|
645
|
+
'[range($n) | {number: (.+1), title: ("T" + (.|tostring) + "-" + ("x" * $len)), state: "open",
|
|
646
|
+
user: {login: ("user" + ((. % 3)|tostring))},
|
|
647
|
+
created_at: "2099-01-01T00:00:00Z",
|
|
648
|
+
updated_at: ("2099-01-01T00:" + ((. / 60 | floor) | tostring | if length < 2 then "0" + . else . end) + ":" + ((. % 60) | tostring | if length < 2 then "0" + . else . end) + "Z"),
|
|
649
|
+
pull_request: null, body: ("x" * 100)}]'; }
|
|
650
|
+
gen_long_pulls() { jq -nc --argjson n "$1" --argjson len "$2" \
|
|
651
|
+
'[range($n) | {number: (.+1), title: ("P" + (.|tostring) + "-" + ("x" * $len)), state: "open",
|
|
652
|
+
user: {login: ("user" + ((. % 3)|tostring))},
|
|
653
|
+
created_at: "2099-01-01T00:00:00Z",
|
|
654
|
+
updated_at: ("2099-01-01T00:" + ((. / 60 | floor) | tostring | if length < 2 then "0" + . else . end) + ":" + ((. % 60) | tostring | if length < 2 then "0" + . else . end) + "Z"),
|
|
655
|
+
merged_at: null, body: ("x" * 100)}]'; }
|
|
656
|
+
gen_graphql_discussions() { jq -nc --argjson n "$1" --argjson len "$2" \
|
|
657
|
+
'{data: {repository: {discussions: {nodes:
|
|
658
|
+
[range($n) | {number: (.+1), title: ("D" + (.|tostring) + "-" + ("x" * $len)),
|
|
659
|
+
author: {login: "someone"}, createdAt: "2099-01-01T00:00:00Z",
|
|
660
|
+
updatedAt: ("2099-01-01T00:00:" + ((. % 60) | tostring | if length < 2 then "0" + . else . end) + "Z"),
|
|
661
|
+
answerChosenAt: null, comments: {totalCount: 1}, category: {name: "General"}}]}}}}'; }
|
|
662
|
+
|
|
663
|
+
CASE10="$(new_case compact)"
|
|
664
|
+
gen_long_issues 100 "$LONG" > "$CASE10/fixtures/issues.json"
|
|
665
|
+
gen_long_pulls 100 "$LONG" > "$CASE10/fixtures/pulls.json"
|
|
666
|
+
gen_commits 100 16 > "$CASE10/fixtures/commits.json"
|
|
667
|
+
gen_stargazers 5 16 > "$CASE10/fixtures/stargazers.json"
|
|
668
|
+
gen_repo > "$CASE10/fixtures/repo.json"
|
|
669
|
+
gen_graphql_discussions 60 "$LONG" > "$CASE10/fixtures/graphql.json"
|
|
670
|
+
{ gen_comments 3; gen_comments 2; } > "$CASE10/fixtures/issue_comments.json"
|
|
671
|
+
|
|
672
|
+
# Precondition: the default output really does breach the inline limit, so the
|
|
673
|
+
# budget assertions below are not vacuous.
|
|
674
|
+
run_collector "$CASE10" activity 1
|
|
675
|
+
DEFAULT_ACTIVITY_BYTES=$(wc -c < "$CASE10/out/stdout")
|
|
676
|
+
if [[ "$DEFAULT_ACTIVITY_BYTES" -le "$INLINE_LIMIT" ]]; then
|
|
677
|
+
echo " FAIL: default activity output is $DEFAULT_ACTIVITY_BYTES B, not over the $INLINE_LIMIT B limit under test"
|
|
678
|
+
FAIL=$((FAIL + 1))
|
|
679
|
+
else
|
|
680
|
+
echo " PASS: default activity output ($DEFAULT_ACTIVITY_BYTES B) breaches the $INLINE_LIMIT B inline limit"
|
|
681
|
+
PASS=$((PASS + 1))
|
|
682
|
+
fi
|
|
683
|
+
# Default shape is unchanged (flag unset).
|
|
684
|
+
assert_jq "$CASE10/out/stdout" \
|
|
685
|
+
'(keys | sort) == ["issues","pull_requests","repo","since"] and (.issues.items[0] | has("user"))' \
|
|
686
|
+
"default activity shape is unchanged when the flag is unset"
|
|
687
|
+
|
|
688
|
+
CHAIN_BYTES=0
|
|
689
|
+
for cmd in activity contributors repo-stats discussions fetch-interactions; do
|
|
690
|
+
run_collector_env "$CASE10" "SOLEUR_COLLECTOR_COMPACT=1" "$cmd" 1
|
|
691
|
+
assert_rc 0 "$RC" "compact $cmd exits 0"
|
|
692
|
+
bytes=$(wc -c < "$CASE10/out/stdout")
|
|
693
|
+
CHAIN_BYTES=$((CHAIN_BYTES + bytes))
|
|
694
|
+
lines=$(wc -l < "$CASE10/out/stdout" | tr -d ' ')
|
|
695
|
+
assert_eq "1" "$lines" "compact $cmd prints exactly one line"
|
|
696
|
+
if [[ "$bytes" -lt "$INLINE_LIMIT" ]]; then
|
|
697
|
+
echo " PASS: compact $cmd ($bytes B) is under the $INLINE_LIMIT B inline limit"; PASS=$((PASS + 1))
|
|
698
|
+
else
|
|
699
|
+
echo " FAIL: compact $cmd is $bytes B, not under the $INLINE_LIMIT B inline limit"; FAIL=$((FAIL + 1))
|
|
700
|
+
fi
|
|
701
|
+
cp "$CASE10/out/stdout" "$CASE10/out.$cmd.json"
|
|
702
|
+
done
|
|
703
|
+
# The agent's reads concatenate: the whole GitHub chain must fit one inline window with margin.
|
|
704
|
+
if [[ "$CHAIN_BYTES" -lt 9800 ]]; then
|
|
705
|
+
echo " PASS: worst-case compact chain ($CHAIN_BYTES B) fits well inside the inline limit"; PASS=$((PASS + 1))
|
|
706
|
+
else
|
|
707
|
+
echo " FAIL: worst-case compact chain is $CHAIN_BYTES B (budget 9800 B, measured 7840 B)"; FAIL=$((FAIL + 1))
|
|
708
|
+
fi
|
|
709
|
+
|
|
710
|
+
assert_jq "$CASE10/out.activity.json" \
|
|
711
|
+
'(keys | sort) == ["issues","pull_requests"]
|
|
712
|
+
and .issues.count == 100 and .pull_requests.count == 100
|
|
713
|
+
and (.issues.titles | length) == 40 and (.pull_requests.titles | length) == 40
|
|
714
|
+
and ([.issues.titles[], .pull_requests.titles[]] | all(length <= 60))' \
|
|
715
|
+
"compact activity: exact counts, newest 40 titles per list, each title capped at 60"
|
|
716
|
+
assert_jq "$CASE10/out.activity.json" \
|
|
717
|
+
'(.issues.titles[0] | startswith("T99-")) and (.pull_requests.titles[0] | startswith("P99-"))' \
|
|
718
|
+
"compact activity: titles are newest-first (sorted by updated_at before the slice)"
|
|
719
|
+
assert_file_not_matches "$CASE10/out.activity.json" 'user[0-9]|"login"|body' \
|
|
720
|
+
"compact activity carries no login or body text"
|
|
721
|
+
assert_jq "$CASE10/out.contributors.json" '. == {commit_total: 100}' \
|
|
722
|
+
"compact contributors: commit_total equals the 100 synthesized commits, no logins"
|
|
723
|
+
assert_jq "$CASE10/out.repo-stats.json" \
|
|
724
|
+
'(keys | sort) == ["forks_count","new_stargazers_count","stargazers_count","stargazers_unavailable","subscribers_count"]
|
|
725
|
+
and .stargazers_count == 11 and .forks_count == 2 and .new_stargazers_count == 5
|
|
726
|
+
and .stargazers_unavailable == false' \
|
|
727
|
+
"compact repo-stats: exactly the prompt's fields, no login list, no watchers_count"
|
|
728
|
+
assert_jq "$CASE10/out.discussions.json" \
|
|
729
|
+
'(keys == ["titles"]) and (.titles | length) == 40 and (.titles | all(length <= 60)) and (.titles[0] | startswith("D59-"))' \
|
|
730
|
+
"compact discussions: newest 40 titles, each capped at 60"
|
|
731
|
+
assert_jq "$CASE10/out.fetch-interactions.json" \
|
|
732
|
+
'. == {external_contributors: 3, interactions_count: 5}' \
|
|
733
|
+
"compact fetch-interactions: 5 interactions from 3 distinct people, computed in the collector"
|
|
734
|
+
|
|
735
|
+
# A flag value other than 1 leaves output unchanged and never echoes the raw value.
|
|
736
|
+
run_collector_env "$CASE10" "SOLEUR_COLLECTOR_COMPACT=sekret-value" activity 1
|
|
737
|
+
assert_rc 0 "$RC" "activity exits 0 with a bad flag value"
|
|
738
|
+
assert_jq "$CASE10/out/stdout" '(.issues | has("items"))' "a flag value other than 1 leaves the default shape"
|
|
739
|
+
assert_file_matches "$CASE10/out/stderr" '^compact mode ignored: SOLEUR_COLLECTOR_COMPACT must be 1$' \
|
|
740
|
+
"a bad flag value prints one fixed stderr line"
|
|
741
|
+
assert_file_not_matches "$CASE10/out/stderr" 'sekret-value' "the raw flag value is never echoed"
|
|
742
|
+
|
|
743
|
+
# Discussions not enabled.
|
|
744
|
+
CASE10N="$(new_case compact_nodiscussions)"
|
|
745
|
+
printf 'Discussions are not enabled for this repository (Not Found)\n' > "$CASE10N/fixtures/graphql.json"
|
|
746
|
+
echo 1 > "$CASE10N/fixtures/graphql.exit"
|
|
747
|
+
run_collector_env "$CASE10N" "SOLEUR_COLLECTOR_COMPACT=1" discussions 1
|
|
748
|
+
assert_rc 0 "$RC" "compact discussions exits 0 when discussions are not enabled"
|
|
749
|
+
assert_jq "$CASE10N/out/stdout" '. == {titles: []}' "compact discussions (not enabled): empty titles"
|
|
750
|
+
|
|
751
|
+
# The stargazers_unavailable sidecar warn survives compact mode (it is set in the same
|
|
752
|
+
# function scope as the projection; a pipe would have lost it).
|
|
753
|
+
CASE10S="$(new_case compact_sidecar)"
|
|
754
|
+
gen_repo > "$CASE10S/fixtures/repo.json"
|
|
755
|
+
echo '[]' > "$CASE10S/fixtures/stargazers.json"
|
|
756
|
+
printf 'gh: Resource not accessible by integration (HTTP 403)\n' > "$CASE10S/fixtures/stargazers.stderr"
|
|
757
|
+
echo 1 > "$CASE10S/fixtures/stargazers.exit"
|
|
758
|
+
run_collector_env "$CASE10S" "SOLEUR_COLLECTOR_COMPACT=1 SOLEUR_COLLECTOR_STATUS_DIR=$CASE10S/status" repo-stats 1
|
|
759
|
+
assert_rc 0 "$RC" "compact repo-stats exits 0 with unreadable stargazers"
|
|
760
|
+
assert_jq "$CASE10S/out/stdout" '.stargazers_unavailable == true and .new_stargazers_count == null' \
|
|
761
|
+
"compact repo-stats reports stargazers as unavailable, not zero"
|
|
762
|
+
assert_jq "$CASE10S/status/collector-status.jsonl" '.warn == "stargazers_unavailable" and .exit == 0 and .cause == ""' \
|
|
763
|
+
"compact mode keeps the stargazers_unavailable sidecar warn (and no cause on success)"
|
|
764
|
+
|
|
765
|
+
# compact_off: inside the cron (status dir set) but the flag did not arrive.
|
|
766
|
+
CASE10O="$(new_case compact_off)"
|
|
767
|
+
gen_repo > "$CASE10O/fixtures/repo.json"
|
|
768
|
+
gen_stargazers 2 16 > "$CASE10O/fixtures/stargazers.json"
|
|
769
|
+
run_collector_env "$CASE10O" "SOLEUR_COLLECTOR_STATUS_DIR=$CASE10O/status" repo-stats 1
|
|
770
|
+
assert_rc 0 "$RC" "default repo-stats still exits 0 inside the cron"
|
|
771
|
+
assert_jq "$CASE10O/status/collector-status.jsonl" '.warn == "compact_off"' \
|
|
772
|
+
"a cron run without the flag records warn=compact_off in the sidecar"
|
|
773
|
+
# ...and a status-dir run that DOES carry the flag does not.
|
|
774
|
+
run_collector_env "$CASE10O" "SOLEUR_COLLECTOR_COMPACT=1 SOLEUR_COLLECTOR_STATUS_DIR=$CASE10O/status2" repo-stats 1
|
|
775
|
+
assert_jq "$CASE10O/status2/collector-status.jsonl" 'has("warn") | not' \
|
|
776
|
+
"a cron run with the flag records no compact warn"
|
|
777
|
+
|
|
778
|
+
# compact_over_budget: titles that JSON-escape to two bytes per character push one
|
|
779
|
+
# output past the 6,000 B signal threshold.
|
|
780
|
+
CASE10B="$(new_case compact_over_budget)"
|
|
781
|
+
jq -nc '[range(99) | {number: (.+1), title: ("\"" * 60), state: "open", user: {login: "u"},
|
|
782
|
+
created_at: "2099-01-01T00:00:00Z", updated_at: "2099-01-01T00:00:00Z", pull_request: null}]' \
|
|
783
|
+
> "$CASE10B/fixtures/issues.json"
|
|
784
|
+
jq -nc '[range(99) | {number: (.+1), title: ("\"" * 60), state: "open", user: {login: "u"},
|
|
785
|
+
created_at: "2099-01-01T00:00:00Z", updated_at: "2099-01-01T00:00:00Z", merged_at: null}]' \
|
|
786
|
+
> "$CASE10B/fixtures/pulls.json"
|
|
787
|
+
run_collector_env "$CASE10B" "SOLEUR_COLLECTOR_COMPACT=1 SOLEUR_COLLECTOR_STATUS_DIR=$CASE10B/status" activity 1
|
|
788
|
+
assert_rc 0 "$RC" "compact activity exits 0 on escape-heavy titles"
|
|
789
|
+
assert_jq "$CASE10B/status/collector-status.jsonl" '.warn == "compact_over_budget"' \
|
|
790
|
+
"an over-budget compact output records warn=compact_over_budget"
|
|
791
|
+
|
|
792
|
+
# Fail closed: a drifted required field must not print null for the model to read as 0.
|
|
793
|
+
CASE10F="$(new_case compact_failclosed)"
|
|
794
|
+
jq -nc '{stargazers_count: 11, watchers_count: 11, subscribers_count: 1}' > "$CASE10F/fixtures/repo.json"
|
|
795
|
+
gen_stargazers 1 16 > "$CASE10F/fixtures/stargazers.json"
|
|
796
|
+
run_collector_env "$CASE10F" "SOLEUR_COLLECTOR_COMPACT=1 SOLEUR_COLLECTOR_STATUS_DIR=$CASE10F/status" repo-stats 1
|
|
797
|
+
assert_nonzero_rc "$RC" "compact repo-stats exits non-zero when forks_count is missing"
|
|
798
|
+
assert_file_not_matches "$CASE10F/out/stdout" 'forks_count' "no partial compact record is printed on a projection failure"
|
|
799
|
+
assert_jq "$CASE10F/status/collector-status.jsonl" '.cause == "compact-projection-failed" and .exit != 0' \
|
|
800
|
+
"a projection failure is recorded with cause=compact-projection-failed"
|
|
801
|
+
|
|
802
|
+
# --- Test 11: compact filters keep their predicates (#9678 review) ----------
|
|
803
|
+
#
|
|
804
|
+
# Every fixture in Test 10 sits on the KEEP side of every filter, so a compact projection that
|
|
805
|
+
# dropped its date, pull-request, bot-association or null-author predicate would still pass. This
|
|
806
|
+
# case puts items on BOTH sides of each predicate and asserts the exact counts.
|
|
807
|
+
|
|
808
|
+
echo "Test 11: compact projections apply the same filters as the default output"
|
|
809
|
+
|
|
810
|
+
OLD="2000-01-01T00:00:00Z"
|
|
811
|
+
CASE11="$(new_case compact_filters)"
|
|
812
|
+
# issues: 3 real, 2 pull requests listed by the issues endpoint (must not count as issues)
|
|
813
|
+
jq -nc --arg ts "$NOW" '[range(5) | {number: (.+1), title: ("i" + (.|tostring)), state: "open",
|
|
814
|
+
user: {login: "u"}, created_at: $ts, updated_at: $ts,
|
|
815
|
+
pull_request: (if . >= 3 then {url: "x"} else null end)}]' > "$CASE11/fixtures/issues.json"
|
|
816
|
+
# pulls: 4 recent, 2 updated long before `since` (the endpoint over-fetches a fixed page)
|
|
817
|
+
jq -nc --arg ts "$NOW" --arg old "$OLD" '[range(6) | {number: (.+1), title: ("p" + (.|tostring)), state: "open",
|
|
818
|
+
user: {login: "u"}, created_at: $ts, merged_at: null, updated_at: (if . >= 4 then $old else $ts end)}]' \
|
|
819
|
+
> "$CASE11/fixtures/pulls.json"
|
|
820
|
+
# commits: 3 attributable, 1 with neither a login nor an author name (must not count)
|
|
821
|
+
jq -nc '[{author: {login: "a"}, commit: {author: {name: "A"}}},
|
|
822
|
+
{author: {login: "b"}, commit: {author: {name: "B"}}},
|
|
823
|
+
{author: null, commit: {author: {name: "Anon"}}},
|
|
824
|
+
{author: null, commit: {author: {name: null}}}]' > "$CASE11/fixtures/commits.json"
|
|
825
|
+
# stargazers: 2 inside the window, 3 long before it
|
|
826
|
+
jq -nc --arg ts "$NOW" --arg old "$OLD" '[range(5) | {starred_at: (if . < 2 then $ts else $old end), user: {login: ("g" + (.|tostring))}}]' \
|
|
827
|
+
> "$CASE11/fixtures/stargazers.json"
|
|
828
|
+
gen_repo > "$CASE11/fixtures/repo.json"
|
|
829
|
+
# discussions: 5 recent, 3 updated long before `since`
|
|
830
|
+
jq -nc --arg ts "$NOW" --arg old "$OLD" '{data: {repository: {discussions: {nodes:
|
|
831
|
+
[range(8) | {number: (.+1), title: ("d" + (.|tostring)), author: {login: "x"}, createdAt: $ts,
|
|
832
|
+
updatedAt: (if . < 5 then $ts else $old end), answerChosenAt: null,
|
|
833
|
+
comments: {totalCount: 0}, category: {name: "G"}}]}}}}' > "$CASE11/fixtures/graphql.json"
|
|
834
|
+
# comments: 2 external people; a bot account, a [bot] login, an OWNER and a MEMBER are excluded
|
|
835
|
+
jq -nc --arg ts "$NOW" '[
|
|
836
|
+
{author_association: "NONE", user: {login: "ext1", type: "User"}},
|
|
837
|
+
{author_association: "CONTRIBUTOR", user: {login: "ext2", type: "User"}},
|
|
838
|
+
{author_association: "NONE", user: {login: "robot", type: "Bot"}},
|
|
839
|
+
{author_association: "NONE", user: {login: "dependabot[bot]", type: "User"}},
|
|
840
|
+
{author_association: "OWNER", user: {login: "maint", type: "User"}},
|
|
841
|
+
{author_association: "MEMBER", user: {login: "maint2", type: "User"}}
|
|
842
|
+
] | map(. + {issue_url: "https://api.github.com/repos/o/r/issues/1", body: "b",
|
|
843
|
+
html_url: "https://github.com/o/r/issues/1", created_at: $ts})' > "$CASE11/fixtures/issue_comments.json"
|
|
844
|
+
|
|
845
|
+
for cmd in activity contributors repo-stats discussions fetch-interactions; do
|
|
846
|
+
run_collector_env "$CASE11" "SOLEUR_COLLECTOR_COMPACT=1" "$cmd" 1
|
|
847
|
+
assert_rc 0 "$RC" "filters case: compact $cmd exits 0"
|
|
848
|
+
cp "$CASE11/out/stdout" "$CASE11/out.$cmd.json"
|
|
849
|
+
done
|
|
850
|
+
assert_jq "$CASE11/out.activity.json" '.issues.count == 3 and .pull_requests.count == 4' \
|
|
851
|
+
"compact activity: pull requests are not issues, and PRs older than since are not counted"
|
|
852
|
+
assert_jq "$CASE11/out.contributors.json" '. == {commit_total: 3}' \
|
|
853
|
+
"compact contributors: a commit with no login and no author name is not counted"
|
|
854
|
+
assert_jq "$CASE11/out.repo-stats.json" '.new_stargazers_count == 2' \
|
|
855
|
+
"compact repo-stats: only stargazers inside the window count"
|
|
856
|
+
assert_jq "$CASE11/out.discussions.json" '(.titles | length) == 5' \
|
|
857
|
+
"compact discussions: discussions older than since are not listed"
|
|
858
|
+
assert_jq "$CASE11/out.fetch-interactions.json" '. == {external_contributors: 2, interactions_count: 2}' \
|
|
859
|
+
"compact fetch-interactions: bots, [bot] logins, OWNER and MEMBER comments are excluded"
|
|
860
|
+
|
|
861
|
+
# A numeric-looking but wrong field must fail the projection closed, for EVERY compact command:
|
|
862
|
+
# an item that is not an object makes the jq projection error, and the sidecar must carry the cause.
|
|
863
|
+
echo "Test 11b: every compact command fails closed with a recorded cause"
|
|
864
|
+
for cmd in activity contributors discussions fetch-interactions; do
|
|
865
|
+
C="$(new_case "failclosed_$cmd")"
|
|
866
|
+
gen_repo > "$C/fixtures/repo.json"
|
|
867
|
+
case "$cmd" in
|
|
868
|
+
activity) jq -nc '[1]' > "$C/fixtures/issues.json"; jq -nc '[1]' > "$C/fixtures/pulls.json" ;;
|
|
869
|
+
contributors) jq -nc '[1]' > "$C/fixtures/commits.json"; jq -nc '[]|.+[{}]' > "$C/fixtures/issues.json" ;;
|
|
870
|
+
discussions) jq -nc '{data: null}' > "$C/fixtures/graphql.json" ;;
|
|
871
|
+
fetch-interactions) jq -nc '[1]' > "$C/fixtures/issue_comments.json" ;;
|
|
872
|
+
esac
|
|
873
|
+
run_collector_env "$C" "SOLEUR_COLLECTOR_COMPACT=1 SOLEUR_COLLECTOR_STATUS_DIR=$C/status" "$cmd" 1
|
|
874
|
+
assert_nonzero_rc "$RC" "compact $cmd exits non-zero on a drifted payload"
|
|
875
|
+
assert_jq "$C/status/collector-status.jsonl" '.cause == "compact-projection-failed"' \
|
|
876
|
+
"compact $cmd records cause=compact-projection-failed"
|
|
877
|
+
done
|
|
878
|
+
|
|
879
|
+
# subscribers_count is a published metric too: a payload without it must fail closed, not print null.
|
|
880
|
+
C="$(new_case failclosed_subscribers)"
|
|
881
|
+
jq -nc '{stargazers_count: 11, forks_count: 2, watchers_count: 11}' > "$C/fixtures/repo.json"
|
|
882
|
+
gen_stargazers 1 16 > "$C/fixtures/stargazers.json"
|
|
883
|
+
run_collector_env "$C" "SOLEUR_COLLECTOR_COMPACT=1 SOLEUR_COLLECTOR_STATUS_DIR=$C/status" repo-stats 1
|
|
884
|
+
assert_nonzero_rc "$RC" "compact repo-stats exits non-zero when subscribers_count is missing"
|
|
885
|
+
assert_file_not_matches "$C/out/stdout" 'subscribers_count' "no compact record is printed without subscribers_count"
|
|
886
|
+
|
|
887
|
+
# Control characters in a title are neutralised before the slice, so JSON escaping cannot inflate
|
|
888
|
+
# a compact line past the inline limit.
|
|
889
|
+
echo "Test 11c: control characters in titles cannot inflate the compact line"
|
|
890
|
+
C="$(new_case ctrl_titles)"
|
|
891
|
+
jq -nc --arg ts "$NOW" '[range(99) | {number: (.+1), title: ("\u0001" * 60), state: "open", user: {login: "u"},
|
|
892
|
+
created_at: $ts, updated_at: $ts, pull_request: null}]' > "$C/fixtures/issues.json"
|
|
893
|
+
jq -nc --arg ts "$NOW" '[range(99) | {number: (.+1), title: ("\u0001" * 60), state: "open", user: {login: "u"},
|
|
894
|
+
created_at: $ts, updated_at: $ts, merged_at: null}]' > "$C/fixtures/pulls.json"
|
|
895
|
+
run_collector_env "$C" "SOLEUR_COLLECTOR_COMPACT=1" activity 1
|
|
896
|
+
assert_rc 0 "$RC" "activity exits 0 on control-character titles"
|
|
897
|
+
CTRL_BYTES=$(wc -c < "$C/out/stdout")
|
|
898
|
+
if [[ "$CTRL_BYTES" -lt 6000 ]]; then
|
|
899
|
+
echo " PASS: control-character titles stay under the compact budget ($CTRL_BYTES B)"; PASS=$((PASS + 1))
|
|
900
|
+
else
|
|
901
|
+
echo " FAIL: control-character titles produced $CTRL_BYTES B (budget 6000 B)"; FAIL=$((FAIL + 1))
|
|
902
|
+
fi
|
|
903
|
+
assert_file_not_matches "$C/out/stdout" '\\u00' "no JSON control-character escapes survive in titles"
|
|
904
|
+
|
|
905
|
+
# --- Test 12: warn precedence, cause clearing, commit_total parity, hostile titles --------
|
|
906
|
+
|
|
907
|
+
echo "Test 12: sidecar warn precedence and the compact edge cases"
|
|
908
|
+
|
|
909
|
+
# compact_off must not overwrite the standing stargazers_unavailable the handler acts on.
|
|
910
|
+
C="$(new_case prec_stargazers)"
|
|
911
|
+
gen_repo > "$C/fixtures/repo.json"
|
|
912
|
+
echo '[]' > "$C/fixtures/stargazers.json"
|
|
913
|
+
printf 'gh: Resource not accessible by integration (HTTP 403)\n' > "$C/fixtures/stargazers.stderr"
|
|
914
|
+
echo 1 > "$C/fixtures/stargazers.exit"
|
|
915
|
+
run_collector_env "$C" "SOLEUR_COLLECTOR_STATUS_DIR=$C/status" repo-stats 1
|
|
916
|
+
assert_jq "$C/status/collector-status.jsonl" '.warn == "stargazers_unavailable"' \
|
|
917
|
+
"compact_off does not overwrite stargazers_unavailable"
|
|
918
|
+
|
|
919
|
+
# compact_over_budget is the lowest priority: a capped (truncated) run keeps its truncation warn.
|
|
920
|
+
C="$(new_case prec_budget)"
|
|
921
|
+
jq -nc '[range(100) | {number: (.+1), title: ("\"" * 60), state: "open", user: {login: "u"},
|
|
922
|
+
created_at: "2099-01-01T00:00:00Z", updated_at: "2099-01-01T00:00:00Z", pull_request: null}]' > "$C/fixtures/issues.json"
|
|
923
|
+
jq -nc '[range(99) | {number: (.+1), title: ("\"" * 60), state: "open", user: {login: "u"},
|
|
924
|
+
created_at: "2099-01-01T00:00:00Z", updated_at: "2099-01-01T00:00:00Z", merged_at: null}]' > "$C/fixtures/pulls.json"
|
|
925
|
+
run_collector_env "$C" "SOLEUR_COLLECTOR_COMPACT=1 SOLEUR_COLLECTOR_STATUS_DIR=$C/status" activity 1
|
|
926
|
+
assert_jq "$C/status/collector-status.jsonl" '.warn == "truncated_at_per_page"' \
|
|
927
|
+
"compact_over_budget does not overwrite truncated_at_per_page"
|
|
928
|
+
run_collector_env "$C" "SOLEUR_COLLECTOR_COMPACT=1" activity 1
|
|
929
|
+
OVER_BYTES=$(wc -c < "$C/out/stdout")
|
|
930
|
+
if [[ "$OVER_BYTES" -gt 6000 ]]; then
|
|
931
|
+
echo " PASS: precedence fixture really is over the compact budget ($OVER_BYTES B)"; PASS=$((PASS + 1))
|
|
932
|
+
else
|
|
933
|
+
echo " FAIL: precedence fixture is only $OVER_BYTES B, not over the 6000 B budget"; FAIL=$((FAIL + 1))
|
|
934
|
+
fi
|
|
935
|
+
|
|
936
|
+
# A failure AFTER a successful projection must not be labelled compact-projection-failed:
|
|
937
|
+
# stdout is /dev/full, so the final printf fails after the projection succeeded.
|
|
938
|
+
C="$(new_case cause_clearing)"
|
|
939
|
+
gen_commits 3 16 > "$C/fixtures/commits.json"
|
|
940
|
+
(
|
|
941
|
+
export PATH="$C/stub:$PATH"
|
|
942
|
+
export GITHUB_REPOSITORY="test-owner/test-repo"
|
|
943
|
+
export TMPDIR="$C/tmp"
|
|
944
|
+
export SOLEUR_COLLECTOR_COMPACT=1
|
|
945
|
+
export SOLEUR_COLLECTOR_STATUS_DIR="$C/status"
|
|
946
|
+
bash "$COLLECTOR" contributors 1
|
|
947
|
+
) >/dev/full 2>"$C/out/stderr"
|
|
948
|
+
RC=$?
|
|
949
|
+
assert_nonzero_rc "$RC" "contributors exits non-zero when stdout cannot be written"
|
|
950
|
+
assert_jq "$C/status/collector-status.jsonl" '.cause == "" and .exit != 0' \
|
|
951
|
+
"a post-projection write failure is not recorded as compact-projection-failed"
|
|
952
|
+
|
|
953
|
+
# commit_total counts exactly what the default commit_authors sums: login-only and name-only commits.
|
|
954
|
+
C="$(new_case commit_total)"
|
|
955
|
+
jq -nc '[{author: {login: "a"}, commit: {author: {name: "A"}}},
|
|
956
|
+
{author: {login: "c"}, commit: {author: {name: null}}},
|
|
957
|
+
{author: null, commit: {author: {name: "Anon"}}},
|
|
958
|
+
{author: null, commit: {author: {name: null}}}]' > "$C/fixtures/commits.json"
|
|
959
|
+
jq -nc '[]|.+[{number: 1, title: "t", state: "open", user: {login: "u"}, created_at: "2099-01-01T00:00:00Z", updated_at: "2099-01-01T00:00:00Z", pull_request: null}]' > "$C/fixtures/issues.json"
|
|
960
|
+
run_collector "$C" contributors 1
|
|
961
|
+
DEFAULT_SUM=$(jq '[.commit_authors[].commits] | add' "$C/out/stdout")
|
|
962
|
+
run_collector_env "$C" "SOLEUR_COLLECTOR_COMPACT=1" contributors 1
|
|
963
|
+
assert_jq "$C/out/stdout" ".commit_total == $DEFAULT_SUM and .commit_total == 3" \
|
|
964
|
+
"compact commit_total equals the default commit_authors sum (login-only and name-only commits count)"
|
|
965
|
+
|
|
966
|
+
# Every control code point, DEL, the line/paragraph separators, bidi/zero-width/tag format characters
|
|
967
|
+
# (category Cf) are neutralised in titles; ordinary text next to them is kept.
|
|
968
|
+
C="$(new_case all_ctrl)"
|
|
969
|
+
jq -nc --arg ts "$NOW" '([range(1;32)] + [127, 133, 8232, 8233, 8238, 8203, 65279, 917569] | implode) as $bad
|
|
970
|
+
| [range(5) | {number: (.+1), title: ($bad + "keep"), state: "open", user: {login: "u"},
|
|
971
|
+
created_at: $ts, updated_at: $ts, pull_request: null}]' > "$C/fixtures/issues.json"
|
|
972
|
+
jq -nc --arg ts "$NOW" '[range(2) | {number: (.+1), title: "p", state: "open", user: {login: "u"},
|
|
973
|
+
created_at: $ts, updated_at: $ts, merged_at: null}]' > "$C/fixtures/pulls.json"
|
|
974
|
+
run_collector_env "$C" "SOLEUR_COLLECTOR_COMPACT=1" activity 1
|
|
975
|
+
assert_jq "$C/out/stdout" '(.issues.titles | length) == 5
|
|
976
|
+
and (.issues.titles[0] | endswith("keep"))
|
|
977
|
+
and ([.issues.titles[] | explode[] | select(. < 32 or . == 127 or . == 133 or . == 8232 or . == 8233 or . == 8238 or . == 8203 or . == 65279 or . == 917569)] | length) == 0' \
|
|
978
|
+
"every control, separator and format character is stripped from titles, text is kept"
|
|
979
|
+
|
|
980
|
+
echo ""
|
|
981
|
+
print_results
|