@event4u/agent-config 14.12.0 → 14.13.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/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +234 -0
- package/CONTRIBUTING.md +38 -0
- package/CREDITS.md +135 -0
- package/NOTICE +13 -0
- package/README.md +8 -6
- package/dist/agent-src/commands/analyze/inbox.md +109 -2
- package/dist/agent-src/commands/feature/dev.md +7 -2
- package/dist/agent-src/commands/ghostwriter/write.md +5 -1
- package/dist/agent-src/commands/implement-ticket.md +1 -1
- package/dist/agent-src/commands/mission/upgrade.md +1 -1
- package/dist/agent-src/commands/orchestrate.md +2 -2
- package/dist/agent-src/commands/post-as/me.md +5 -1
- package/dist/agent-src/commands/pr/merge.md +19 -6
- package/dist/agent-src/commands/roadmap/process-full.md +19 -5
- package/dist/agent-src/commands/tdd/red.md +12 -2
- package/dist/agent-src/commands/work.md +1 -1
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +40 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -2
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +21 -0
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +1 -0
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +40 -0
- package/dist/agent-src/contexts/execution/subagent-steering.md +13 -1
- package/dist/agent-src/contexts/execution/terminal-states.md +96 -0
- package/dist/agent-src/rules/artifact-drafting-protocol.md +1 -1
- package/dist/agent-src/rules/broken-access-control.md +4 -0
- package/dist/agent-src/rules/code-provenance.md +4 -0
- package/dist/agent-src/rules/design-fidelity.md +1 -1
- package/dist/agent-src/rules/domain-safety-pii.md +4 -0
- package/dist/agent-src/rules/domain-safety-retention.md +4 -0
- package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
- package/dist/agent-src/rules/external-code-graph-interop.md +14 -12
- package/dist/agent-src/rules/improve-before-implement.md +6 -8
- package/dist/agent-src/rules/laravel-routing.md +1 -1
- package/dist/agent-src/rules/lethal-trifecta-guard.md +4 -0
- package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +2 -0
- package/dist/agent-src/rules/source-confidentiality.md +12 -34
- package/dist/agent-src/rules/symfony-routing.md +1 -1
- package/dist/agent-src/rules/token-efficiency.md +14 -0
- package/dist/agent-src/rules/tool-safety.md +4 -0
- package/dist/agent-src/rules/untrusted-input-defense.md +4 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +247 -3
- package/dist/agent-src/scripts/guarded_baseline.ts +328 -0
- package/dist/agent-src/scripts/roadmap_set_step.ts +253 -0
- package/dist/agent-src/scripts/stub_queue.ts +82 -0
- package/dist/agent-src/scripts/stubs_due.ts +400 -0
- package/dist/agent-src/scripts/ticket_bundles.ts +84 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +8 -4
- package/dist/agent-src/skills/brand-asset-generation/SKILL.md +17 -4
- package/dist/agent-src/skills/brand-audit/SKILL.md +33 -0
- package/dist/agent-src/skills/code-intelligence/SKILL.md +57 -0
- package/dist/agent-src/skills/database/SKILL.md +8 -0
- package/dist/agent-src/skills/dependency-upgrade/SKILL.md +27 -0
- package/dist/agent-src/skills/design-review/SKILL.md +1 -1
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +17 -1
- package/dist/agent-src/skills/feature-planning/SKILL.md +9 -2
- package/dist/agent-src/skills/frontend-render-security/SKILL.md +44 -0
- package/dist/agent-src/skills/git-workflow/SKILL.md +1 -1
- package/dist/agent-src/skills/js-library-packaging/scripts/check_package_surface.ts +38 -4
- package/dist/agent-src/skills/judge-artifact-completeness/rubrics/ticket-quality-score.json +2 -2
- package/dist/agent-src/skills/laravel/SKILL.md +35 -0
- package/dist/agent-src/skills/laravel-migration/SKILL.md +57 -3
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +26 -0
- package/dist/agent-src/skills/playbook-authoring/SKILL.md +28 -1
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-writing/references/procedure.md +5 -5
- package/dist/agent-src/skills/sql-writing/SKILL.md +7 -1
- package/dist/agent-src/skills/standards-from-config/SKILL.md +27 -4
- package/dist/agent-src/skills/supply-chain-intake/SKILL.md +11 -4
- package/dist/agent-src/skills/symfony-workflow/SKILL.md +35 -0
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +85 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +33 -9
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/testing-anti-patterns/process-anti-patterns.md +37 -0
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +45 -2
- package/dist/agent-src/skills/upstream-contribute/SKILL.md +38 -1
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +9 -4
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +97 -3
- package/dist/agent-src/templates/scripts/check_memory.ts +258 -17
- package/dist/agent-src/templates/scripts/check_memory_proposal.ts +9 -3
- package/dist/agent-src/templates/scripts/memory_lookup.ts +86 -0
- package/dist/agent-src/templates/scripts/memory_signal.ts +44 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +90 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/index.ts +11 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +23 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +10 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +5 -1
- package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +102 -14
- package/dist/cli/commands/uiAudit.js +124 -5
- package/dist/cli/registry.js +2 -0
- package/dist/cli-delegate/{chunk-HYWSRNUX.js → chunk-2DK2VJWG.js} +3 -3
- package/dist/cli-delegate/{chunk-JCW7ZLHK.js → chunk-4FS4WRXP.js} +5 -0
- package/dist/cli-delegate/{chunk-OMIVVCG2.js → chunk-A4QJ6ENT.js} +24 -0
- package/dist/cli-delegate/{chunk-JJA2QEWA.js → chunk-AKJGUQNO.js} +466 -285
- package/dist/cli-delegate/{chunk-E46KPN6K.js → chunk-BTWUYDGN.js} +424 -363
- package/dist/cli-delegate/{chunk-K4FFODTS.js → chunk-ILMUPLGW.js} +1227 -316
- package/dist/cli-delegate/{chunk-FF7WAFMR.js → chunk-KPBWFOPZ.js} +5 -1
- package/dist/cli-delegate/cmd_adr_effective.js +102 -0
- package/dist/cli-delegate/cmd_conformance.js +42 -10
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_export.js +3 -2
- package/dist/cli-delegate/cmd_fleet.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/cli-delegate/cmd_preflight.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +3 -2
- package/dist/cli-delegate/cmd_settings_get.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +3 -2
- package/dist/cli-delegate/cmd_settings_set.js +2 -2
- package/dist/cli-delegate/cmd_sync.js +3 -2
- package/dist/cli-delegate/cmd_uninstall.js +3 -2
- package/dist/cli-delegate/cmd_validate.js +3 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +57 -57
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +1836 -712
- package/dist/install/claudeRuleRewrite.js +254 -0
- package/dist/install/detect.js +29 -16
- package/dist/install/detect_php_shape.js +159 -0
- package/dist/install/install.mjs +634 -428
- package/dist/mcp/awesome-mcp-servers.row.md +1 -1
- package/dist/mcp/mcp-cloudflare-catalogue.json +1 -1
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +3 -3
- package/dist/mcp/server.mjs +4818 -2645
- package/dist/server/schemas/settings.js +3 -0
- package/dist/ui/assets/index-D0jaCRPM.js +29 -0
- package/dist/ui/index.html +1 -1
- package/docs/MIGRATION.md +1 -1
- package/docs/THIRD-PARTY-NOTICES.md +25 -0
- package/docs/contracts/provider-lifecycle.md +1 -1
- package/docs/contracts/settings-classes.md +10 -4
- package/docs/guidelines/agent-infra/5w2h-analysis.md +3 -3
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +1 -0
- package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +58 -3
- package/docs/guidelines/agent-infra/ask-when-uncertain-demos.md +2 -2
- package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +3 -3
- package/docs/guidelines/agent-infra/break-glass-usage.md +7 -7
- package/docs/guidelines/agent-infra/carve-out-predicates.md +3 -3
- package/docs/guidelines/agent-infra/comparison-matrix.md +1 -1
- package/docs/guidelines/agent-infra/critical-thinking.md +4 -4
- package/docs/guidelines/agent-infra/direct-answers-demos.md +2 -2
- package/docs/guidelines/agent-infra/domain-eval-anti-pattern.md +1 -1
- package/docs/guidelines/agent-infra/engineering-memory-data-format.md +3 -3
- package/docs/guidelines/agent-infra/existence-question-verdicts.md +80 -0
- package/docs/guidelines/agent-infra/first-principles.md +2 -2
- package/docs/guidelines/agent-infra/framework-neutrality-patterns.md +2 -2
- package/docs/guidelines/agent-infra/guarded-baseline.md +39 -0
- package/docs/guidelines/agent-infra/inversion-thinking.md +5 -5
- package/docs/guidelines/agent-infra/language-and-tone-examples.md +1 -1
- package/docs/guidelines/agent-infra/layered-settings.md +10 -10
- package/docs/guidelines/agent-infra/memory-access.md +1 -1
- package/docs/guidelines/agent-infra/mental-models.md +3 -3
- package/docs/guidelines/agent-infra/reuse-verdict-mechanics.md +71 -0
- package/docs/guidelines/agent-infra/review-routing-data-format.md +7 -7
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +13 -7
- package/docs/guidelines/agent-infra/role-contracts.md +2 -2
- package/docs/guidelines/agent-infra/role-mode-router.md +3 -3
- package/docs/guidelines/agent-infra/rule-type-governance.md +2 -2
- package/docs/guidelines/agent-infra/scqa-framework.md +5 -5
- package/docs/guidelines/agent-infra/self-improvement-pipeline.md +6 -6
- package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +2 -2
- package/docs/guidelines/agent-infra/six-hats.md +3 -3
- package/docs/guidelines/agent-infra/skill-quality-checklist.md +6 -6
- package/docs/guidelines/agent-infra/source-confidentiality-mechanics.md +109 -0
- package/docs/guidelines/agent-infra/systems-thinking.md +1 -1
- package/docs/guidelines/agent-infra/verify-before-complete-demos.md +2 -2
- package/docs/guidelines/augment-portability-patterns.md +6 -4
- package/docs/guidelines/component-oriented-and-oop-development.md +3 -2
- package/docs/guidelines/cross-role-handoff.md +2 -2
- package/docs/guidelines/design-fidelity-mechanics.md +1 -1
- package/docs/guidelines/docs/readme-size-and-splitting.md +3 -3
- package/docs/guidelines/php/database.md +90 -7
- package/docs/guidelines/prompt-templates.md +7 -7
- package/llms.txt +2 -2
- package/package.json +5 -2
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +247 -3
- package/src/agent-src/scripts/guarded_baseline.ts +328 -0
- package/src/agent-src/scripts/roadmap_set_step.ts +253 -0
- package/src/agent-src/scripts/stub_queue.ts +82 -0
- package/src/agent-src/scripts/stubs_due.ts +400 -0
- package/src/agent-src/scripts/ticket_bundles.ts +84 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +8 -4
- package/src/agent-src/templates/scripts/check_memory.ts +258 -17
- package/src/agent-src/templates/scripts/check_memory_proposal.ts +9 -3
- package/src/agent-src/templates/scripts/memory_lookup.ts +86 -0
- package/src/agent-src/templates/scripts/memory_signal.ts +44 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +90 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/index.ts +11 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +23 -1
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +10 -1
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +5 -1
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +102 -14
- package/src/config/agent-settings.template.yml +32 -3
- package/src/config/assurance-capability-registry.json +21 -9
- package/src/config/canonical-terms.yml +119 -0
- package/src/config/capability-native-outcome-bars.json +81 -0
- package/src/config/ci-local-parity.yml +80 -0
- package/src/config/drafting-channels.yml +43 -0
- package/src/config/estate-count-budget.json +38 -36
- package/src/config/evaluator-budgets.json +14 -14
- package/src/config/gate-coverage.yml +579 -63
- package/src/config/gate-reachability-exemptions.json +21 -0
- package/src/config/gate-violation-baselines.json +37 -19
- package/src/config/harness-evolution-budget.json +25 -0
- package/src/config/lapsed-beta-baseline.json +96 -0
- package/src/config/memory-twin-verdicts.yml +129 -0
- package/src/config/metric-registry.yml +124 -0
- package/src/config/pack-size-budget.json +16 -15
- package/src/config/preamble-payload-budget.json +8 -8
- package/src/config/profiles/balanced.ini +27 -0
- package/src/config/profiles/full.ini +27 -0
- package/src/config/profiles/minimal.ini +27 -0
- package/src/config/publish-surface.json +9 -2
- package/src/config/routing-coverage-seed.json +16 -2
- package/src/config/rule-stub-ceilings.json +126 -112
- package/src/config/turnaround-budget.json +66 -0
- package/src/install/claudeRuleRewrite.ts +288 -0
- package/src/install/detect.ts +60 -17
- package/src/install/detect_php_shape.ts +191 -0
- package/src/scripts/_cli/cmd_doctor.ts +27 -112
- package/src/scripts/_dispatch.bash +27 -0
- package/src/scripts/_lib/activation_ladder.ts +211 -0
- package/src/scripts/_lib/activation_states.ts +88 -0
- package/src/scripts/_lib/adherence_detectors.ts +143 -0
- package/src/scripts/_lib/arith_claims.ts +181 -0
- package/src/scripts/_lib/arm_ranking.ts +62 -0
- package/src/scripts/_lib/asset_delivery_ledger.ts +303 -0
- package/src/scripts/_lib/audit_field_provenance.ts +54 -0
- package/src/scripts/_lib/auto_dispatch.ts +32 -3
- package/src/scripts/_lib/bench_ab_size_claim.ts +98 -4
- package/src/scripts/_lib/benchmark_cache_fields.ts +158 -0
- package/src/scripts/_lib/candidate_proposer.ts +402 -0
- package/src/scripts/_lib/candidate_record.ts +601 -0
- package/src/scripts/_lib/capture_rate.ts +319 -0
- package/src/scripts/_lib/collector_denominator.ts +771 -0
- package/src/scripts/_lib/collector_record.ts +340 -0
- package/src/scripts/_lib/collector_store.ts +664 -0
- package/src/scripts/_lib/collector_supervision.ts +827 -0
- package/src/scripts/_lib/concern_estate.ts +77 -0
- package/src/scripts/_lib/config_chain.ts +477 -0
- package/src/scripts/_lib/config_cost.ts +209 -0
- package/src/scripts/_lib/council_settings_block.ts +6 -0
- package/src/scripts/_lib/curator_ops.ts +172 -0
- package/src/scripts/_lib/delivery_arm_experiment.ts +266 -0
- package/src/scripts/_lib/delivery_criticality.ts +126 -0
- package/src/scripts/_lib/doctor_runtime_checks.ts +70 -0
- package/src/scripts/_lib/empty_cycles.ts +88 -0
- package/src/scripts/_lib/eval_publication.ts +354 -0
- package/src/scripts/_lib/evaluation_vector.ts +283 -0
- package/src/scripts/_lib/evaluator_promotion.ts +225 -0
- package/src/scripts/_lib/evidence_basis.ts +118 -0
- package/src/scripts/_lib/exec_evidence.ts +8 -0
- package/src/scripts/_lib/experience_card.ts +272 -0
- package/src/scripts/_lib/experience_report.ts +151 -0
- package/src/scripts/_lib/experiment_freeze.ts +112 -0
- package/src/scripts/_lib/generated_by.ts +66 -0
- package/src/scripts/_lib/harness_evolution_guards.ts +229 -0
- package/src/scripts/_lib/hook_effect_probe.ts +141 -0
- package/src/scripts/_lib/host_denominator.ts +687 -0
- package/src/scripts/_lib/host_projection_reach.ts +157 -0
- package/src/scripts/_lib/ignored_blocker.ts +148 -0
- package/src/scripts/_lib/install_reach_checks.ts +495 -0
- package/src/scripts/_lib/json_duplicate_keys.ts +150 -0
- package/src/scripts/_lib/judge_hygiene.ts +228 -0
- package/src/scripts/_lib/legacy_boundary_map.ts +228 -0
- package/src/scripts/_lib/loop_guards.ts +299 -0
- package/src/scripts/_lib/loss_class.ts +129 -0
- package/src/scripts/_lib/machine_wake.ts +51 -0
- package/src/scripts/_lib/md_prose_lines.ts +177 -0
- package/src/scripts/_lib/minimality_tiebreak.ts +168 -0
- package/src/scripts/_lib/one_resolver_invariant.ts +449 -0
- package/src/scripts/_lib/orchestration_record.ts +253 -5
- package/src/scripts/_lib/outcome_envelope.ts +227 -0
- package/src/scripts/_lib/outcome_vocabularies.ts +125 -0
- package/src/scripts/_lib/paired_stats.ts +245 -0
- package/src/scripts/_lib/paired_verdict.ts +226 -0
- package/src/scripts/_lib/prefix_stable_surfaces.ts +123 -0
- package/src/scripts/_lib/privacy_class.ts +47 -0
- package/src/scripts/_lib/regression_neighbourhood.ts +259 -0
- package/src/scripts/_lib/repeated_failure.ts +105 -0
- package/src/scripts/_lib/repo_root.ts +111 -0
- package/src/scripts/_lib/review_skipped_record.ts +37 -0
- package/src/scripts/_lib/role_split.ts +338 -0
- package/src/scripts/_lib/runtime_journal.ts +1499 -0
- package/src/scripts/_lib/runtime_wiring_checks.ts +430 -0
- package/src/scripts/_lib/skill_catalogue.ts +11 -1
- package/src/scripts/_lib/source_digest.ts +200 -0
- package/src/scripts/_lib/source_redact.ts +260 -0
- package/src/scripts/_lib/source_shape.ts +502 -0
- package/src/scripts/_lib/source_snapshot_dedup.ts +199 -0
- package/src/scripts/_lib/strip_comments.d.mts +8 -0
- package/src/scripts/_lib/strip_comments.mjs +65 -0
- package/src/scripts/_lib/test_red_state.ts +154 -0
- package/src/scripts/_lib/trigger_shift.ts +144 -0
- package/src/scripts/_lib/untrusted_content.ts +82 -7
- package/src/scripts/adoption_report.ts +1 -1
- package/src/scripts/adr/evidence_census.ts +1 -1
- package/src/scripts/adr/regenerate_index.ts +26 -2
- package/src/scripts/adr_cite_check.ts +164 -1
- package/src/scripts/ai_council/blind_review.ts +11 -1
- package/src/scripts/ai_council/clients.ts +4 -0
- package/src/scripts/ai_council/config.ts +3 -0
- package/src/scripts/ai_council/consensus.ts +28 -8
- package/src/scripts/ai_council/consensus_round.ts +226 -0
- package/src/scripts/ai_council/disagreement_signal.ts +338 -0
- package/src/scripts/ai_council/inline_findings.ts +340 -0
- package/src/scripts/ai_council/judge_position_bias.ts +197 -0
- package/src/scripts/ai_council/orchestrator.ts +16 -203
- package/src/scripts/ai_council/orchestrator_results.ts +23 -3
- package/src/scripts/ai_council/pricing.ts +106 -0
- package/src/scripts/ai_council/prompts.ts +74 -7
- package/src/scripts/ai_council/provider_leakage_bench.ts +327 -0
- package/src/scripts/ai_council/py_format.ts +14 -3
- package/src/scripts/ai_council/recouncil_guard.ts +293 -0
- package/src/scripts/ai_council/response_record.ts +50 -0
- package/src/scripts/ai_council/topology_bench_manifest.ts +860 -0
- package/src/scripts/ai_council/topology_vocabulary.ts +175 -0
- package/src/scripts/asset_delivery_ledger.ts +157 -0
- package/src/scripts/attest_artifact.ts +193 -0
- package/src/scripts/audit_auto_rules.ts +2 -1
- package/src/scripts/audit_command_surface.ts +1 -1
- package/src/scripts/audit_mcp_tools.ts +2 -2
- package/src/scripts/audit_skill_overlap_allowlist.json +6 -1
- package/src/scripts/audit_user_type_axis.ts +2 -1
- package/src/scripts/bench_ab_clone.ts +215 -10
- package/src/scripts/bench_ab_integrity.ts +119 -5
- package/src/scripts/bench_ab_v2_stats.ts +40 -213
- package/src/scripts/build_archive_index.ts +1 -1
- package/src/scripts/build_linear_digest.ts +1 -1
- package/src/scripts/build_proof.ts +11 -4
- package/src/scripts/build_rule_trigger_matrix.ts +1 -1
- package/src/scripts/build_source_digests.ts +157 -0
- package/src/scripts/cache_realization_report.ts +103 -0
- package/src/scripts/check_adr_frontmatter.ts +126 -0
- package/src/scripts/check_benchmark_report_fields.ts +207 -0
- package/src/scripts/check_beta_review_markers.ts +235 -6
- package/src/scripts/check_bundle_path_leakage.ts +34 -1
- package/src/scripts/check_claims.ts +576 -6
- package/src/scripts/check_composite_arming.ts +35 -0
- package/src/scripts/check_concern_admissions.ts +365 -0
- package/src/scripts/check_enforcement_coverage.ts +28 -0
- package/src/scripts/check_estate_count.ts +142 -1
- package/src/scripts/check_gate_reachability.ts +383 -0
- package/src/scripts/check_host_loadability.ts +104 -0
- package/src/scripts/check_loss_class_declared.ts +308 -0
- package/src/scripts/check_md_language.ts +15 -98
- package/src/scripts/check_memory_proposal.ts +18 -11
- package/src/scripts/check_no_currency_in_cost_surfaces.ts +225 -0
- package/src/scripts/check_no_external_sources.ts +281 -6
- package/src/scripts/check_pack_conformance_fixture.ts +11 -1
- package/src/scripts/check_pack_size.ts +59 -2
- package/src/scripts/check_preamble_payload_budget.ts +114 -3
- package/src/scripts/check_prefix_stable_mutation.ts +372 -0
- package/src/scripts/check_publish_surface.ts +10 -1
- package/src/scripts/check_references.ts +24 -1
- package/src/scripts/check_rule_evidence_declaration.ts +316 -0
- package/src/scripts/check_rule_projection_integrity.ts +14 -4
- package/src/scripts/check_score_contract.ts +26 -3
- package/src/scripts/check_static_parity.ts +285 -0
- package/src/scripts/check_supervision_claim_atomicity.ts +498 -0
- package/src/scripts/check_suppression_hygiene.ts +15 -0
- package/src/scripts/ci_settle.ts +39 -2
- package/src/scripts/code_graph/build.ts +60 -3
- package/src/scripts/code_graph/extract.ts +44 -0
- package/src/scripts/code_graph/types.ts +24 -0
- package/src/scripts/collector_daemon.ts +714 -0
- package/src/scripts/config_cost_report.ts +302 -0
- package/src/scripts/consumer_matrix.ts +74 -1
- package/src/scripts/cost/budget.mjs +10 -3
- package/src/scripts/cost/track.mjs +10 -3
- package/src/scripts/council_cli.ts +37 -54
- package/src/scripts/dispatch_r2_reviewer.ts +46 -5
- package/src/scripts/evolution_lab.ts +980 -0
- package/src/scripts/external_sources_denylist.json +5 -18
- package/src/scripts/extract_audit_patterns.ts +21 -0
- package/src/scripts/fold_intake.ts +12 -1
- package/src/scripts/gen_discovery_baseline.ts +1 -1
- package/src/scripts/generate_capabilities_index.ts +12 -5
- package/src/scripts/generate_capability_matrix.ts +3 -3
- package/src/scripts/generate_command_flows.ts +2 -2
- package/src/scripts/generate_cookbook.ts +2 -2
- package/src/scripts/generate_index.ts +8 -1
- package/src/scripts/generate_knowledge_index.ts +2 -1
- package/src/scripts/generate_ownership_matrix.ts +2 -1
- package/src/scripts/generate_pack_manifests.ts +10 -2
- package/src/scripts/generate_role_experiences_catalog.ts +2 -2
- package/src/scripts/generate_settings_reference.ts +1 -1
- package/src/scripts/git_authorization_hook.ts +22 -1
- package/src/scripts/grade_target_readiness.ts +48 -5
- package/src/scripts/hook_effect_doctor.ts +261 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +98 -11
- package/src/scripts/hooks/block_speaking_inbox_dir.ts +271 -0
- package/src/scripts/hooks/block_unauthorized_git.ts +18 -0
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/dispatch_hook.ts +32 -0
- package/src/scripts/hooks/journal_record_hook.ts +345 -0
- package/src/scripts/hooks/prepush_metadata_sources.sh +110 -0
- package/src/scripts/hooks/run_continuation_hook.ts +44 -9
- package/src/scripts/hooks/shims/install.sh +61 -0
- package/src/scripts/hooks/shims/php +101 -0
- package/src/scripts/hooks/suggestion_capture_hook.ts +10 -0
- package/src/scripts/hot_context_hook.ts +16 -0
- package/src/scripts/install-hooks.sh +20 -5
- package/src/scripts/install.ts +17 -35
- package/src/scripts/inventory_frontmatter.ts +2 -1
- package/src/scripts/lint_adr_sweep_routing.ts +12 -0
- package/src/scripts/lint_canonical_terms.ts +604 -0
- package/src/scripts/lint_composition_review.ts +619 -0
- package/src/scripts/lint_consolidation_lineage.ts +666 -0
- package/src/scripts/lint_eval_specs.ts +593 -0
- package/src/scripts/lint_hook_manifest.ts +19 -0
- package/src/scripts/lint_host_portability.ts +373 -0
- package/src/scripts/lint_judge_prompt_expectation.ts +27 -1
- package/src/scripts/lint_memory_twin_parity.ts +335 -0
- package/src/scripts/lint_metric_consumers.ts +355 -0
- package/src/scripts/lint_skill_descriptions.ts +18 -1
- package/src/scripts/lint_skill_frontmatter_safety.ts +58 -0
- package/src/scripts/lint_skill_link_reach.ts +252 -0
- package/src/scripts/lint_skill_trigger_corpus.ts +426 -23
- package/src/scripts/lint_ticket_buildable.ts +17 -1
- package/src/scripts/mcp_server/consumer_tool_catalog.json +1 -1
- package/src/scripts/mcp_server/tool_catalog_source.ts +1 -1
- package/src/scripts/measure_docs_dead_links.ts +348 -0
- package/src/scripts/measure_host_capture.ts +461 -0
- package/src/scripts/migration_status.ts +3 -3
- package/src/scripts/model_rule_injection.ts +60 -0
- package/src/scripts/orchestration_record.ts +14 -0
- package/src/scripts/preamble_byte_census.ts +139 -10
- package/src/scripts/prepack-check.mjs +4 -1
- package/src/scripts/probe_turnaround.ts +384 -0
- package/src/scripts/probe_unblocked_ask.ts +279 -0
- package/src/scripts/render_benchmark_md.ts +1 -1
- package/src/scripts/rule_activation_census.ts +70 -9
- package/src/scripts/rule_backlinks.ts +1 -1
- package/src/scripts/run_lifecycle_suite.ts +208 -0
- package/src/scripts/run_skill_evals.ts +40 -0
- package/src/scripts/schemas/delivery-manifest.schema.json +43 -0
- package/src/scripts/schemas/drafting-channels.schema.json +43 -0
- package/src/scripts/schemas/evals.schema.json +24 -1
- package/src/scripts/schemas/rule.schema.json +52 -1
- package/src/scripts/schemas/skill.schema.json +17 -2
- package/src/scripts/skill_overlap.ts +7 -1
- package/src/scripts/skill_tools/run_block_d_eval.ts +8 -1
- package/src/scripts/skill_tools/score_skill_relevance.ts +84 -3
- package/src/scripts/skill_tools/suggest_skill_for_task.ts +8 -1
- package/src/scripts/skill_usage_report.ts +2 -1
- package/src/scripts/stubs_due.ts +211 -0
- package/src/scripts/sweep_source_surfaces.ts +678 -0
- package/src/scripts/sync_pr_branch.ts +3 -3
- package/src/scripts/trigger_eval_grandfather.json +202 -222
- package/src/scripts/utilization_report.ts +22 -2
- package/src/server/schemas/settings.ts +5 -0
- package/src/templates/marketing-copy.yml +1 -1
- package/dist/ui/assets/index-Cxgy63kz.js +0 -26
|
@@ -0,0 +1,714 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* collector_daemon — the supervised telemetry collector itself
|
|
3
|
+
* (`road-to-supervised-telemetry-collector` step 4.1).
|
|
4
|
+
*
|
|
5
|
+
* Built against the contracts, not beside them: the record shape is
|
|
6
|
+
* `collector_record`, the store and its quarantine are `collector_store`, the
|
|
7
|
+
* budgets, kill switch, lock and heartbeat are `collector_supervision`, and the
|
|
8
|
+
* denominator plus the spool are `collector_denominator`. This file adds a
|
|
9
|
+
* loop and a signal handler and nothing else — every rule it obeys was decided
|
|
10
|
+
* in Phases 1 to 3 and is imported rather than restated.
|
|
11
|
+
*
|
|
12
|
+
* ## Default-off is a property, not a config line
|
|
13
|
+
*
|
|
14
|
+
* `start()` refuses unless the opt-in marker exists, and the marker is absent on
|
|
15
|
+
* a fresh install. There is no setting whose default could be misread, no
|
|
16
|
+
* environment variable that could be set in CI by accident, and nothing in the
|
|
17
|
+
* install path that creates the marker. The test that matters asserts this by
|
|
18
|
+
* PROCESS ENUMERATION after the full suite — reading the setting back would
|
|
19
|
+
* only prove the setting, which is the check step 4.1 explicitly refuses.
|
|
20
|
+
*
|
|
21
|
+
* ## The five questions (`resident-process-floors` § 2)
|
|
22
|
+
*
|
|
23
|
+
* 1. **Failure mode when absent** — static mode. Dispatches resolve
|
|
24
|
+
* identically; the denominator keeps counting and the capture rate falls,
|
|
25
|
+
* which is the true statement rather than a silent gap.
|
|
26
|
+
* 2. **A dispatch it cannot serve** — degraded, never blocked. The hook's only
|
|
27
|
+
* contact with this process is an append to a spool file, and both
|
|
28
|
+
* denominator and spool writes return `false` instead of raising.
|
|
29
|
+
* 3. **State on an unclean stop** — the heartbeat goes stale (readable as
|
|
30
|
+
* likely-dead), the lock is fenced by the next starter, and a claimed spool
|
|
31
|
+
* file is left in place and re-read on the next drain. Nothing is lost by a
|
|
32
|
+
* `SIGKILL` except the in-flight batch, which is re-drained.
|
|
33
|
+
* 4. **Who supervises it, with what privileges** — a per-user service manager,
|
|
34
|
+
* positively probed; no administrator privilege on either supported row.
|
|
35
|
+
* Where none is detected the mode is `degraded` and says so in the
|
|
36
|
+
* heartbeat, and it is never labelled supervised.
|
|
37
|
+
* 5. **Uniqueness namespace** — one live collector per OS user, enforced by the
|
|
38
|
+
* runtime lock, so two worktrees contend and the loser runs static.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import fs from 'node:fs';
|
|
42
|
+
import path from 'node:path';
|
|
43
|
+
|
|
44
|
+
import {
|
|
45
|
+
claimSpool,
|
|
46
|
+
isCollectorEnabled,
|
|
47
|
+
pruneEpisodeCounters,
|
|
48
|
+
pruneOpportunitiesOlderThan,
|
|
49
|
+
readClaimedSpool,
|
|
50
|
+
spoolDir,
|
|
51
|
+
} from './_lib/collector_denominator.js';
|
|
52
|
+
import {
|
|
53
|
+
isStoreAvailable,
|
|
54
|
+
openCollectorStore,
|
|
55
|
+
pruneOlderThan,
|
|
56
|
+
resolveCollectorStore,
|
|
57
|
+
writeRecord,
|
|
58
|
+
type StoreHandle,
|
|
59
|
+
} from './_lib/collector_store.js';
|
|
60
|
+
import {
|
|
61
|
+
acquireRuntimeLock,
|
|
62
|
+
budgetVerdict,
|
|
63
|
+
detectSupervisor,
|
|
64
|
+
killSwitchEngaged,
|
|
65
|
+
livenessFromBeat,
|
|
66
|
+
pullKillSwitch,
|
|
67
|
+
readHeartbeat,
|
|
68
|
+
releaseHeartbeat,
|
|
69
|
+
releaseRuntimeLock,
|
|
70
|
+
terminateCollector,
|
|
71
|
+
writeHeartbeat,
|
|
72
|
+
type BudgetName,
|
|
73
|
+
type ResourceReading,
|
|
74
|
+
} from './_lib/collector_supervision.js';
|
|
75
|
+
|
|
76
|
+
// An earlier draft exported a `DAEMON_PROCESS_MARKER` constant here, claiming
|
|
77
|
+
// the daemon put it into its own argv and that the 4.1 test greped for it. Both
|
|
78
|
+
// were false — its only use was a usage string — and R2 findings 6 and 7 caught
|
|
79
|
+
// it. It is REMOVED rather than made true: nothing can add a token to argv after
|
|
80
|
+
// `exec` (a JavaScript `process.argv` is not the kernel's copy, which is what
|
|
81
|
+
// `ps` reads), so honouring the claim would have meant re-exec'ing the daemon
|
|
82
|
+
// and changing the pid every launcher and test then re-derives — to solve a
|
|
83
|
+
// problem `--root` already solves exactly. Process enumeration greps the module
|
|
84
|
+
// path, which a real daemon's argv genuinely contains, and tells a test-owned
|
|
85
|
+
// daemon from a developer's by `--root`. See `tests/scripts/collector_daemon.test.ts`.
|
|
86
|
+
|
|
87
|
+
export const DEFAULT_BEAT_MS = 30_000;
|
|
88
|
+
|
|
89
|
+
export type StartRefusal =
|
|
90
|
+
| 'not-enabled'
|
|
91
|
+
| 'kill-switch-engaged'
|
|
92
|
+
| 'lock-held'
|
|
93
|
+
// Carried through from `LockOutcome` rather than collapsed into
|
|
94
|
+
// `lock-held` (R2 round-5 finding 4). `acquireRuntimeLock` distinguishes
|
|
95
|
+
// these precisely so an `EACCES` or a full disk is not reported as
|
|
96
|
+
// contention — and `start()` was throwing that distinction away one layer
|
|
97
|
+
// up, routing an operator to the "Two collectors" row of the ops page for a
|
|
98
|
+
// permissions problem. That is the misclassification round-2 finding 11
|
|
99
|
+
// fixed, re-introduced by the caller.
|
|
100
|
+
| 'lock-unwritable'
|
|
101
|
+
| 'lock-contested-out'
|
|
102
|
+
| 'store-unavailable';
|
|
103
|
+
|
|
104
|
+
export interface StartOutcome {
|
|
105
|
+
readonly started: boolean;
|
|
106
|
+
readonly refusal: StartRefusal | null;
|
|
107
|
+
readonly mode: 'supervised' | 'degraded' | null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* -------------------------------------------------------------------------- */
|
|
111
|
+
/* Resource sampling */
|
|
112
|
+
/* -------------------------------------------------------------------------- */
|
|
113
|
+
|
|
114
|
+
let lastCpu = process.cpuUsage();
|
|
115
|
+
let lastCpuAt = Date.now();
|
|
116
|
+
let lastCpuPercent = 0;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The shortest window a CPU percentage is computed over.
|
|
120
|
+
*
|
|
121
|
+
* MEASURED, not chosen: back-to-back sampling produced `103 %` and then
|
|
122
|
+
* `36800 %` on a real run, because a few hundred microseconds of elapsed wall
|
|
123
|
+
* clock divides into any CPU time at all to give a nonsense ratio. The budget's
|
|
124
|
+
* own unit says *"% of one core, averaged over 60 s"*, and a sub-second window
|
|
125
|
+
* is not that average — it is noise wearing its units.
|
|
126
|
+
*/
|
|
127
|
+
export const MIN_CPU_WINDOW_MS = 1_000;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Sample the four budgeted resources for THIS process.
|
|
131
|
+
*
|
|
132
|
+
* CPU is a delta between samples rather than a lifetime average: a collector
|
|
133
|
+
* that spun for ten minutes an hour ago and is idle now is not currently over
|
|
134
|
+
* budget, and a lifetime figure would keep reporting the old spike.
|
|
135
|
+
*
|
|
136
|
+
* **Below {@link MIN_CPU_WINDOW_MS} the previous percentage is carried forward
|
|
137
|
+
* rather than recomputed**, and the initial value is 0. That is a deliberate
|
|
138
|
+
* choice between three bad options: recomputing gives the 36800 % nonsense
|
|
139
|
+
* above; returning `NaN` is read as a breach by `budgetVerdict` and would stop
|
|
140
|
+
* the daemon during its own startup; carrying the last value reports a stale
|
|
141
|
+
* figure for at most one second. The staleness is bounded and the other two are
|
|
142
|
+
* wrong, so this one is documented rather than hidden.
|
|
143
|
+
*/
|
|
144
|
+
export function sampleResources(userRoot?: string): ResourceReading {
|
|
145
|
+
const now = Date.now();
|
|
146
|
+
const elapsedMs = now - lastCpuAt;
|
|
147
|
+
if (elapsedMs >= MIN_CPU_WINDOW_MS) {
|
|
148
|
+
const cpu = process.cpuUsage();
|
|
149
|
+
const usedMicros = cpu.user - lastCpu.user + (cpu.system - lastCpu.system);
|
|
150
|
+
lastCpuPercent = (usedMicros / (elapsedMs * 1000)) * 100;
|
|
151
|
+
lastCpu = cpu;
|
|
152
|
+
lastCpuAt = now;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return Object.freeze({
|
|
156
|
+
cpu_percent: lastCpuPercent,
|
|
157
|
+
resident_bytes: process.memoryUsage().rss,
|
|
158
|
+
disk_bytes: directorySize(resolveCollectorStore(userRoot).root),
|
|
159
|
+
file_descriptors: openDescriptorCount(),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function directorySize(dir: string): number {
|
|
164
|
+
let total = 0;
|
|
165
|
+
let entries: fs.Dirent[];
|
|
166
|
+
try {
|
|
167
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
168
|
+
} catch {
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
for (const entry of entries) {
|
|
172
|
+
const full = path.join(dir, entry.name);
|
|
173
|
+
if (entry.isDirectory()) {
|
|
174
|
+
total += directorySize(full);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
total += fs.statSync(full).size;
|
|
179
|
+
} catch {
|
|
180
|
+
/* raced with a rename; the next sample sees it */
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return total;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Count open descriptors without shelling out.
|
|
188
|
+
*
|
|
189
|
+
* `/proc/self/fd` on Linux, `/dev/fd` on macOS. Where neither is readable the
|
|
190
|
+
* answer is `NaN`, which {@link budgetVerdict} treats as a BREACH rather than a
|
|
191
|
+
* pass — an unmeasurable resource is not a satisfied budget, which is the R-A12
|
|
192
|
+
* rule this follows.
|
|
193
|
+
*/
|
|
194
|
+
export function openDescriptorCount(): number {
|
|
195
|
+
for (const dir of ['/proc/self/fd', '/dev/fd']) {
|
|
196
|
+
try {
|
|
197
|
+
return fs.readdirSync(dir).length;
|
|
198
|
+
} catch {
|
|
199
|
+
/* try the next */
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return Number.NaN;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/* -------------------------------------------------------------------------- */
|
|
206
|
+
/* Draining */
|
|
207
|
+
/* -------------------------------------------------------------------------- */
|
|
208
|
+
|
|
209
|
+
export interface DrainResult {
|
|
210
|
+
readonly claimed: number;
|
|
211
|
+
readonly written: number;
|
|
212
|
+
readonly refused: number;
|
|
213
|
+
readonly malformed: number;
|
|
214
|
+
/** Claimed files that could not be READ. Kept on disk for the next drain, never deleted. */
|
|
215
|
+
readonly unreadable: number;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Drain every claimed spool file into the store.
|
|
220
|
+
*
|
|
221
|
+
* Re-drains files left claimed by a previous unclean stop, which is answer 3 to
|
|
222
|
+
* the daemon checklist: the batch in flight when the process died is not lost,
|
|
223
|
+
* it is on disk under its claimed name and the next drain picks it up. A file
|
|
224
|
+
* is deleted only after every one of its records has been offered to the store,
|
|
225
|
+
* so a crash mid-batch re-offers the whole file.
|
|
226
|
+
*
|
|
227
|
+
* **That re-offer inserts duplicate ROWS, and it is safe for a stated reason
|
|
228
|
+
* rather than by accident.** `dedup_key` is deliberately NOT unique — the store
|
|
229
|
+
* is append-only and collapses duplicates at READ time (`readRecords`), while
|
|
230
|
+
* `readSummary` reports `rows` and distinct `dedup_key` separately so a
|
|
231
|
+
* duplicate is observable rather than invisible. Claiming insert-time
|
|
232
|
+
* idempotency here would have been the easier sentence and a false one.
|
|
233
|
+
*/
|
|
234
|
+
export function drainOnce(handle: StoreHandle, userRoot?: string): DrainResult {
|
|
235
|
+
claimSpool(userRoot);
|
|
236
|
+
let claimedFiles: string[] = [];
|
|
237
|
+
try {
|
|
238
|
+
claimedFiles = fs
|
|
239
|
+
.readdirSync(spoolDir(userRoot))
|
|
240
|
+
.filter((name) => name.startsWith('draining-'))
|
|
241
|
+
.map((name) => path.join(spoolDir(userRoot), name));
|
|
242
|
+
} catch {
|
|
243
|
+
return Object.freeze({ claimed: 0, written: 0, refused: 0, malformed: 0, unreadable: 0 });
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let written = 0;
|
|
247
|
+
let refused = 0;
|
|
248
|
+
let malformed = 0;
|
|
249
|
+
let unreadable = 0;
|
|
250
|
+
for (const file of claimedFiles) {
|
|
251
|
+
const parsed = readClaimedSpool(file);
|
|
252
|
+
if (parsed.unreadable) {
|
|
253
|
+
// KEEP IT (R2 round-3 finding 2). A file we could not read has not
|
|
254
|
+
// had its records offered to the store, and deleting it here
|
|
255
|
+
// destroyed the batch while reporting zero malformed — a transient
|
|
256
|
+
// EACCES or EMFILE was enough. The next drain re-reads it, which is
|
|
257
|
+
// the same recovery the crash path relies on.
|
|
258
|
+
unreadable += 1;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
malformed += parsed.malformed;
|
|
262
|
+
for (const record of parsed.records) {
|
|
263
|
+
const outcome = writeRecord(handle, record);
|
|
264
|
+
if (outcome.written) written += 1;
|
|
265
|
+
else refused += 1;
|
|
266
|
+
}
|
|
267
|
+
fs.rmSync(file, { force: true });
|
|
268
|
+
}
|
|
269
|
+
return Object.freeze({
|
|
270
|
+
claimed: claimedFiles.length,
|
|
271
|
+
written,
|
|
272
|
+
refused,
|
|
273
|
+
malformed,
|
|
274
|
+
unreadable,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/* -------------------------------------------------------------------------- */
|
|
279
|
+
/* Lifecycle */
|
|
280
|
+
/* -------------------------------------------------------------------------- */
|
|
281
|
+
|
|
282
|
+
export interface RunOptions {
|
|
283
|
+
// `| undefined` is required, not decoration: `exactOptionalPropertyTypes` is
|
|
284
|
+
// on, so an optional property does NOT implicitly accept `undefined`, and
|
|
285
|
+
// `main` threads a `--root` that may legitimately be absent.
|
|
286
|
+
readonly userRoot?: string | undefined;
|
|
287
|
+
readonly beatMs?: number;
|
|
288
|
+
/** Stop after this many iterations. Tests use it; the real daemon does not pass it. */
|
|
289
|
+
readonly maxIterations?: number;
|
|
290
|
+
/** Injected for tests. */
|
|
291
|
+
readonly sample?: (userRoot?: string) => ResourceReading;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export interface RunSummary {
|
|
295
|
+
readonly iterations: number;
|
|
296
|
+
readonly drained: DrainResult;
|
|
297
|
+
readonly stoppedBy: 'signal' | 'kill-switch' | 'budget' | 'iterations';
|
|
298
|
+
readonly exceeded: readonly BudgetName[];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
let stopRequested = false;
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* What `start()` learned, kept for the loop.
|
|
305
|
+
*
|
|
306
|
+
* The heartbeat's `mode` is the SUPERVISOR PROBE's answer, and it must survive a
|
|
307
|
+
* beat in which the heartbeat file is missing (R2 round-4 finding 8). Module
|
|
308
|
+
* state rather than a parameter because `runLoop` is called both by `main` and
|
|
309
|
+
* directly by tests, and a required parameter would make every test assert a
|
|
310
|
+
* fact it does not care about.
|
|
311
|
+
*/
|
|
312
|
+
let startedMode: 'supervised' | 'degraded' = 'degraded';
|
|
313
|
+
let startedAt = Date.now();
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Ask the loop to finish and exit. Idempotent.
|
|
317
|
+
*
|
|
318
|
+
* Wakes an in-flight {@link sleep} rather than only setting the flag: a daemon
|
|
319
|
+
* that acknowledges `SIGTERM` and then waits out a 30-second beat before acting
|
|
320
|
+
* on it has not honoured the signal in any sense an operator or a supervisor
|
|
321
|
+
* would recognise.
|
|
322
|
+
*/
|
|
323
|
+
export function requestStop(): void {
|
|
324
|
+
stopRequested = true;
|
|
325
|
+
wake?.();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Refuse or accept the start, and take the lock when accepting.
|
|
330
|
+
*
|
|
331
|
+
* Order is deliberate and each step is cheaper than the next: the opt-in marker
|
|
332
|
+
* (a `stat`), the kill switch (a `stat`), store availability (a runtime probe),
|
|
333
|
+
* then the lock (a write). Probing availability before taking the lock keeps a
|
|
334
|
+
* runtime without `node:sqlite` from holding a lock it can never use.
|
|
335
|
+
*/
|
|
336
|
+
export function start(userRoot?: string): StartOutcome {
|
|
337
|
+
// The KILL SWITCH is checked first, and the order is not cosmetic: since
|
|
338
|
+
// R2 round-5 finding 1 the switch also closes `isCollectorEnabled`, so
|
|
339
|
+
// checking enablement first would report every stopped collector as
|
|
340
|
+
// `not-enabled` and send an operator looking for a missing marker instead
|
|
341
|
+
// of the STOP file they created.
|
|
342
|
+
if (killSwitchEngaged(userRoot)) {
|
|
343
|
+
return Object.freeze({
|
|
344
|
+
started: false,
|
|
345
|
+
refusal: 'kill-switch-engaged' as const,
|
|
346
|
+
mode: null,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
if (!isCollectorEnabled(userRoot)) {
|
|
350
|
+
return Object.freeze({ started: false, refusal: 'not-enabled' as const, mode: null });
|
|
351
|
+
}
|
|
352
|
+
if (!isStoreAvailable()) {
|
|
353
|
+
return Object.freeze({
|
|
354
|
+
started: false,
|
|
355
|
+
refusal: 'store-unavailable' as const,
|
|
356
|
+
mode: null,
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
const lock = acquireRuntimeLock(userRoot);
|
|
360
|
+
if (!lock.acquired) {
|
|
361
|
+
const refusal: StartRefusal =
|
|
362
|
+
lock.reason === 'unwritable'
|
|
363
|
+
? 'lock-unwritable'
|
|
364
|
+
: lock.reason === 'contested-out'
|
|
365
|
+
? 'lock-contested-out'
|
|
366
|
+
: lock.reason === 'kill-switch-engaged'
|
|
367
|
+
? 'kill-switch-engaged'
|
|
368
|
+
: 'lock-held';
|
|
369
|
+
if (lock.errno !== null) {
|
|
370
|
+
process.stderr.write(`collector: lock unwritable (${lock.errno})\n`);
|
|
371
|
+
}
|
|
372
|
+
return Object.freeze({ started: false, refusal, mode: null });
|
|
373
|
+
}
|
|
374
|
+
const probe = detectSupervisor();
|
|
375
|
+
const mode = probe.tier === 'supported' ? ('supervised' as const) : ('degraded' as const);
|
|
376
|
+
const now = Date.now();
|
|
377
|
+
startedMode = mode;
|
|
378
|
+
startedAt = now;
|
|
379
|
+
writeHeartbeat({ pid: process.pid, started_at: now, last_heartbeat: now, mode }, userRoot);
|
|
380
|
+
stopRequested = false;
|
|
381
|
+
return Object.freeze({ started: true, refusal: null, mode });
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Release the lock and remove the heartbeat — ONLY the ones this process owns.
|
|
386
|
+
*
|
|
387
|
+
* Both helpers are ownership-checked (R2 finding 10). The unconditional
|
|
388
|
+
* `rmSync` pair this replaces meant a FENCED predecessor tearing down through
|
|
389
|
+
* `runLoop`'s `finally` deleted the successor's lock and heartbeat: the next
|
|
390
|
+
* starter then also acquired, and `livenessFromBeat` reported `absent` for a
|
|
391
|
+
* process that was running.
|
|
392
|
+
*
|
|
393
|
+
* Returns what it actually removed, so a caller can tell a clean release from a
|
|
394
|
+
* no-op after a fence.
|
|
395
|
+
*/
|
|
396
|
+
export function stop(userRoot?: string): { lock: boolean; heartbeat: boolean } {
|
|
397
|
+
return {
|
|
398
|
+
lock: releaseRuntimeLock(userRoot),
|
|
399
|
+
heartbeat: releaseHeartbeat(userRoot),
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* The loop. Beat, drain, sample, decide.
|
|
405
|
+
*
|
|
406
|
+
* ## Why this is `async`, and why the synchronous version was a real bug
|
|
407
|
+
*
|
|
408
|
+
* R2 round-2 finding 1. The first version slept with `Atomics.wait`, which
|
|
409
|
+
* blocks the main thread — and a Node signal handler is a JS callback on the
|
|
410
|
+
* event loop, so `SIGTERM` could not be observed until the sleep returned. Stop
|
|
411
|
+
* latency was therefore up to a full beat, the shipped default beat is 30 s and
|
|
412
|
+
* `terminateCollector`'s grace is 5 s, so **in the shipped configuration the
|
|
413
|
+
* graceful half of the kill switch could never win**: every stop escalated to
|
|
414
|
+
* `SIGKILL`, leaving the lock and heartbeat residue of an unclean death and an
|
|
415
|
+
* unclosed SQLite handle, every time.
|
|
416
|
+
*
|
|
417
|
+
* PROPERTY 2 passed anyway, and that is the part worth remembering: the
|
|
418
|
+
* lifecycle suite spawns with `--beat-ms 50`, the one configuration in which
|
|
419
|
+
* the graceful path is reachable. The suite certified a beat production never
|
|
420
|
+
* uses.
|
|
421
|
+
*
|
|
422
|
+
* `await setTimeout` yields to the event loop, so a handler runs while the
|
|
423
|
+
* daemon waits. Nothing else about the loop changed.
|
|
424
|
+
*
|
|
425
|
+
* A budget breach stops the collector on the SECOND consecutive reading, not
|
|
426
|
+
* the first. One sample is not a breach: the first CPU sample after start
|
|
427
|
+
* measures a window of a few milliseconds that includes the store open, and
|
|
428
|
+
* stopping on it would make the daemon unable to start under its own budget.
|
|
429
|
+
* Two consecutive readings over the ceiling is a load; one is a sample.
|
|
430
|
+
*/
|
|
431
|
+
export async function runLoop(options: RunOptions = {}): Promise<RunSummary> {
|
|
432
|
+
const userRoot = options.userRoot;
|
|
433
|
+
const beatMs = options.beatMs ?? DEFAULT_BEAT_MS;
|
|
434
|
+
const sample = options.sample ?? sampleResources;
|
|
435
|
+
|
|
436
|
+
let iterations = 0;
|
|
437
|
+
let consecutiveBreaches = 0;
|
|
438
|
+
let exceeded: readonly BudgetName[] = [];
|
|
439
|
+
let drained: DrainResult = Object.freeze({
|
|
440
|
+
claimed: 0,
|
|
441
|
+
written: 0,
|
|
442
|
+
refused: 0,
|
|
443
|
+
malformed: 0,
|
|
444
|
+
unreadable: 0,
|
|
445
|
+
});
|
|
446
|
+
let stoppedBy: RunSummary['stoppedBy'] = 'iterations';
|
|
447
|
+
|
|
448
|
+
// INSIDE the try (R2 round-2 finding 13): opening the store can throw on a
|
|
449
|
+
// corrupt or unreadable file, and `isStoreAvailable()` in `start()` probes
|
|
450
|
+
// the runtime rather than this file. Outside the try that throw skipped the
|
|
451
|
+
// `finally` and left behind the lock and heartbeat `start()` had already
|
|
452
|
+
// taken — so a supervisor restart loop met its own stale lock on every
|
|
453
|
+
// attempt until the fencing path cleared it.
|
|
454
|
+
let handle: StoreHandle | null = null;
|
|
455
|
+
try {
|
|
456
|
+
handle = openCollectorStore(userRoot);
|
|
457
|
+
for (;;) {
|
|
458
|
+
iterations += 1;
|
|
459
|
+
|
|
460
|
+
if (stopRequested) {
|
|
461
|
+
stoppedBy = 'signal';
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
if (killSwitchEngaged(userRoot)) {
|
|
465
|
+
stoppedBy = 'kill-switch';
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const beat = readHeartbeat(userRoot);
|
|
470
|
+
const now = Date.now();
|
|
471
|
+
writeHeartbeat(
|
|
472
|
+
{
|
|
473
|
+
pid: process.pid,
|
|
474
|
+
started_at: beat?.started_at ?? startedAt,
|
|
475
|
+
last_heartbeat: now,
|
|
476
|
+
// The mode `start()` computed from `detectSupervisor()`, not
|
|
477
|
+
// one re-derived from the beat we just read (R2 round-4
|
|
478
|
+
// finding 8). A heartbeat absent for one beat — an operator,
|
|
479
|
+
// a cleanup, a race — silently re-labelled a genuinely
|
|
480
|
+
// SUPERVISED daemon as `degraded` for the rest of its life,
|
|
481
|
+
// and nothing restored it. The probe result was available at
|
|
482
|
+
// start and was being discarded.
|
|
483
|
+
mode: startedMode,
|
|
484
|
+
},
|
|
485
|
+
userRoot,
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
const result = drainOnce(handle, userRoot);
|
|
489
|
+
drained = Object.freeze({
|
|
490
|
+
claimed: drained.claimed + result.claimed,
|
|
491
|
+
written: drained.written + result.written,
|
|
492
|
+
refused: drained.refused + result.refused,
|
|
493
|
+
malformed: drained.malformed + result.malformed,
|
|
494
|
+
unreadable: drained.unreadable + result.unreadable,
|
|
495
|
+
});
|
|
496
|
+
const today = new Date(now).toISOString().slice(0, 10);
|
|
497
|
+
pruneOlderThan(handle, today);
|
|
498
|
+
// BOTH sides age on the same clock (R2 round-2 finding 5). Pruning
|
|
499
|
+
// only the store made the ratio fall toward zero as the numerator
|
|
500
|
+
// aged out against a lifetime denominator, and left an unbounded
|
|
501
|
+
// file inside the directory the disk budget ceilings.
|
|
502
|
+
pruneOpportunitiesOlderThan(userRoot, today);
|
|
503
|
+
pruneEpisodeCounters(userRoot, now);
|
|
504
|
+
|
|
505
|
+
const verdict = budgetVerdict(sample(userRoot));
|
|
506
|
+
if (verdict.action === 'stop') {
|
|
507
|
+
consecutiveBreaches += 1;
|
|
508
|
+
exceeded = verdict.exceeded;
|
|
509
|
+
if (consecutiveBreaches >= 2) {
|
|
510
|
+
stoppedBy = 'budget';
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
} else {
|
|
514
|
+
consecutiveBreaches = 0;
|
|
515
|
+
exceeded = [];
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (options.maxIterations !== undefined && iterations >= options.maxIterations) {
|
|
519
|
+
stoppedBy = 'iterations';
|
|
520
|
+
break;
|
|
521
|
+
}
|
|
522
|
+
await sleep(beatMs);
|
|
523
|
+
}
|
|
524
|
+
} finally {
|
|
525
|
+
// RELEASE FIRST (R2 round-3 finding 12). `db.close()` can throw on an
|
|
526
|
+
// already-invalidated handle, and running it first meant the lock and
|
|
527
|
+
// heartbeat `start()` took stayed on disk — the orphaned-residue case
|
|
528
|
+
// that moving `openCollectorStore` into the `try` was supposed to close,
|
|
529
|
+
// still reachable through the cleanup path itself.
|
|
530
|
+
stop(userRoot);
|
|
531
|
+
try {
|
|
532
|
+
handle?.db.close();
|
|
533
|
+
} catch {
|
|
534
|
+
/* an unclosable handle is this process's problem, not its successor's */
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return Object.freeze({ iterations, drained, stoppedBy, exceeded });
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Wakes the current {@link sleep} early. Set while the loop is waiting.
|
|
543
|
+
*
|
|
544
|
+
* Yielding to the event loop is necessary and NOT sufficient, which cost a
|
|
545
|
+
* measured round: with `await setTimeout` the `SIGTERM` handler ran promptly —
|
|
546
|
+
* and the loop still finished the full 30 s beat before it looked at
|
|
547
|
+
* `stopRequested`, so the observed stop latency was 29,995 ms against a 5 s
|
|
548
|
+
* grace. The handler has to be able to END the wait, not merely run during it.
|
|
549
|
+
*/
|
|
550
|
+
let wake: (() => void) | null = null;
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Wait up to `ms`, or until {@link requestStop} wakes us — whichever is first.
|
|
554
|
+
*
|
|
555
|
+
* Two properties, and the loop needs both (R2 round-2 finding 1). It yields, so
|
|
556
|
+
* signal handlers run at all; and it is interruptible, so a handler's effect is
|
|
557
|
+
* immediate rather than deferred to the end of the beat.
|
|
558
|
+
*/
|
|
559
|
+
function sleep(ms: number): Promise<void> {
|
|
560
|
+
return new Promise((resolve) => {
|
|
561
|
+
const timer = setTimeout(() => {
|
|
562
|
+
wake = null;
|
|
563
|
+
resolve();
|
|
564
|
+
}, ms);
|
|
565
|
+
wake = () => {
|
|
566
|
+
clearTimeout(timer);
|
|
567
|
+
wake = null;
|
|
568
|
+
resolve();
|
|
569
|
+
};
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/* -------------------------------------------------------------------------- */
|
|
574
|
+
/* Entry point */
|
|
575
|
+
/* -------------------------------------------------------------------------- */
|
|
576
|
+
|
|
577
|
+
export async function main(argv: readonly string[] = process.argv.slice(2)): Promise<number> {
|
|
578
|
+
const command = argv[0] ?? 'status';
|
|
579
|
+
|
|
580
|
+
// `--root` exists for the process-level lifecycle suite (step 5.1), which
|
|
581
|
+
// has to spawn REAL daemons and cannot be allowed to touch the developer's
|
|
582
|
+
// own `~/.event4u/agent-config`. It is not a production knob: the supervisor
|
|
583
|
+
// units never pass it, and omitting it resolves the real user root exactly
|
|
584
|
+
// as before.
|
|
585
|
+
//
|
|
586
|
+
// A TRAILING `--root` with no value is an ERROR, not a default (R2 finding
|
|
587
|
+
// 16). `argv[i + 1]` is `undefined` there, and `undefined` is precisely the
|
|
588
|
+
// value that falls back to the real user root — so a malformed invocation
|
|
589
|
+
// would do the one thing this flag was added to prevent.
|
|
590
|
+
// A flag's value must exist AND not be another flag (R2 round-2 finding 12).
|
|
591
|
+
// `--root --beat-ms 5` used to pass the presence check and silently take the
|
|
592
|
+
// literal string `--beat-ms` as the user root — which is the failure `--root`
|
|
593
|
+
// was added to prevent, arriving through a typo instead of an omission.
|
|
594
|
+
const flagValue = (name: string): string | null | undefined => {
|
|
595
|
+
const at = argv.indexOf(name);
|
|
596
|
+
if (at < 0) return undefined;
|
|
597
|
+
const value = argv[at + 1];
|
|
598
|
+
if (value === undefined || value.startsWith('--')) return null;
|
|
599
|
+
return value;
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
const rootValue = flagValue('--root');
|
|
603
|
+
if (rootValue === null) {
|
|
604
|
+
process.stderr.write('collector: --root needs a directory argument\n');
|
|
605
|
+
return 2;
|
|
606
|
+
}
|
|
607
|
+
const userRoot = rootValue;
|
|
608
|
+
|
|
609
|
+
const beatValue = flagValue('--beat-ms');
|
|
610
|
+
if (beatValue === null) {
|
|
611
|
+
process.stderr.write('collector: --beat-ms needs a number\n');
|
|
612
|
+
return 2;
|
|
613
|
+
}
|
|
614
|
+
const beatMs = beatValue === undefined ? undefined : Number.parseInt(beatValue, 10);
|
|
615
|
+
// Positive, not merely finite: 0 and a negative both turned the wait into a
|
|
616
|
+
// no-op, i.e. a busy loop that budget-stops the collector after two
|
|
617
|
+
// iterations — a spin dressed as a configuration.
|
|
618
|
+
if (beatMs !== undefined && (!Number.isFinite(beatMs) || beatMs <= 0)) {
|
|
619
|
+
process.stderr.write('collector: --beat-ms must be a positive number of milliseconds\n');
|
|
620
|
+
return 2;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (command === 'status') {
|
|
624
|
+
const beat = readHeartbeat(userRoot);
|
|
625
|
+
const liveness = livenessFromBeat(beat);
|
|
626
|
+
const enabled = isCollectorEnabled(userRoot);
|
|
627
|
+
process.stdout.write(
|
|
628
|
+
`collector: ${enabled ? 'enabled' : 'default-off'} · ${liveness}`
|
|
629
|
+
+ (beat === null ? '' : ` (pid ${beat.pid}, ${beat.mode})`)
|
|
630
|
+
+ `\n`,
|
|
631
|
+
);
|
|
632
|
+
return 0;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// The operator-facing half of the kill switch (R2 finding 3). Before this
|
|
636
|
+
// existed, `terminateCollector` had no production caller at all and
|
|
637
|
+
// `docs/contracts/collector-operations.md` told an operator under pressure
|
|
638
|
+
// that there was nothing running to stop — while this very file had just
|
|
639
|
+
// shipped a daemon that runs.
|
|
640
|
+
if (command === 'stop') {
|
|
641
|
+
const engaged = argv.includes('--no-latch') ? false : true;
|
|
642
|
+
if (engaged) pullKillSwitch(userRoot);
|
|
643
|
+
const outcome = terminateCollector(userRoot, {
|
|
644
|
+
signalStale: argv.includes('--signal-stale'),
|
|
645
|
+
});
|
|
646
|
+
process.stdout.write(
|
|
647
|
+
`collector: ${outcome.stopped ? 'stopped' : 'NOT stopped'} (${outcome.via}`
|
|
648
|
+
+ (outcome.pid === null ? '' : `, pid ${outcome.pid}`)
|
|
649
|
+
+ `)${engaged ? ' · kill switch engaged — delete STOP to allow a restart' : ''}\n`,
|
|
650
|
+
);
|
|
651
|
+
if (outcome.via === 'stale-refused') {
|
|
652
|
+
process.stderr.write(
|
|
653
|
+
'collector: the heartbeat is stale, so its pid may have been recycled — '
|
|
654
|
+
+ 'refused rather than signalling a stranger. Re-run with --signal-stale '
|
|
655
|
+
+ 'if you know that pid is still the collector.\n',
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
return outcome.stopped ? 0 : 1;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (command === 'run') {
|
|
662
|
+
const outcome = start(userRoot);
|
|
663
|
+
if (!outcome.started) {
|
|
664
|
+
process.stderr.write(`collector: not started — ${outcome.refusal}\n`);
|
|
665
|
+
// Refusing to start is the designed behaviour on a default-off
|
|
666
|
+
// install, so it is exit 0. A non-zero here would make every
|
|
667
|
+
// supervisor on a fresh machine report a failing unit.
|
|
668
|
+
return 0;
|
|
669
|
+
}
|
|
670
|
+
process.on('SIGTERM', requestStop);
|
|
671
|
+
process.on('SIGINT', requestStop);
|
|
672
|
+
// Announce readiness AFTER the lock and heartbeat exist. A test that
|
|
673
|
+
// signals before this line is racing the startup it is trying to test —
|
|
674
|
+
// the same race the 3.3 wedged-process test learned the hard way.
|
|
675
|
+
process.stdout.write(
|
|
676
|
+
`collector: running pid=${process.pid} mode=${outcome.mode}\n`,
|
|
677
|
+
);
|
|
678
|
+
// WRAPPED (R2 round-2 finding 13): an uncaught throw out of the loop
|
|
679
|
+
// used to kill the process with `start()`'s lock and heartbeat still on
|
|
680
|
+
// disk. `runLoop`'s own `finally` releases them, but only for throws it
|
|
681
|
+
// can see; this catches the rest so a supervisor restart does not meet
|
|
682
|
+
// a stale lock every attempt.
|
|
683
|
+
let summary: RunSummary;
|
|
684
|
+
try {
|
|
685
|
+
summary = await runLoop(
|
|
686
|
+
beatMs !== undefined ? { userRoot, beatMs } : { userRoot },
|
|
687
|
+
);
|
|
688
|
+
} catch (err) {
|
|
689
|
+
stop(userRoot);
|
|
690
|
+
process.stderr.write(`collector: loop failed — ${(err as Error).message}\n`);
|
|
691
|
+
return 1;
|
|
692
|
+
}
|
|
693
|
+
process.stderr.write(
|
|
694
|
+
`collector: stopped by ${summary.stoppedBy}`
|
|
695
|
+
+ (summary.exceeded.length > 0 ? ` (${summary.exceeded.join(', ')})` : '')
|
|
696
|
+
+ `\n`,
|
|
697
|
+
);
|
|
698
|
+
return 0;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
process.stderr.write(
|
|
702
|
+
'usage: collector_daemon [status|run|stop] [--root <dir>] [--beat-ms <n>]'
|
|
703
|
+
+ ' [--no-latch] [--signal-stale]\n',
|
|
704
|
+
);
|
|
705
|
+
return 2;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
const invokedDirectly =
|
|
709
|
+
process.argv[1] !== undefined && process.argv[1].includes('collector_daemon');
|
|
710
|
+
if (invokedDirectly) {
|
|
711
|
+
void main().then((code) => {
|
|
712
|
+
process.exitCode = code;
|
|
713
|
+
});
|
|
714
|
+
}
|