ultimate-pi 0.18.0 → 0.19.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/.agents/skills/harness-debate-plan/SKILL.md +1 -1
- package/.agents/skills/harness-decisions/SKILL.md +2 -3
- package/.agents/skills/harness-governor/SKILL.md +6 -5
- package/.agents/skills/harness-orchestration/SKILL.md +4 -4
- package/.agents/skills/harness-review/SKILL.md +7 -7
- package/.agents/skills/harness-sentrux-setup/SKILL.md +4 -3
- package/.agents/skills/harness-steer/SKILL.md +1 -1
- package/.agents/skills/sentrux/SKILL.md +9 -9
- package/.pi/PACKAGING.md +4 -4
- package/.pi/SYSTEM.md +54 -120
- package/.pi/agents/harness/incident-recorder.md +0 -1
- package/.pi/agents/harness/planning/decompose.md +1 -3
- package/.pi/agents/harness/planning/execution-plan-author.md +0 -2
- package/.pi/agents/harness/planning/hypothesis-validator.md +0 -2
- package/.pi/agents/harness/planning/hypothesis.md +0 -2
- package/.pi/agents/harness/planning/implementation-researcher.md +0 -2
- package/.pi/agents/harness/planning/plan-adversary.md +0 -2
- package/.pi/agents/harness/planning/plan-evaluator.md +1 -3
- package/.pi/agents/harness/planning/planning-context.md +0 -2
- package/.pi/agents/harness/planning/review-integrator.md +0 -2
- package/.pi/agents/harness/planning/sprint-contract-auditor.md +0 -2
- package/.pi/agents/harness/planning/stack-researcher.md +0 -2
- package/.pi/agents/harness/{adversary.md → reviewing/adversary.md} +0 -2
- package/.pi/agents/harness/{evaluator.md → reviewing/evaluator.md} +0 -2
- package/.pi/agents/harness/{tie-breaker.md → reviewing/tie-breaker.md} +0 -2
- package/.pi/agents/harness/{executor.md → running/executor.md} +0 -2
- package/.pi/agents/harness/sentrux-bootstrap.md +0 -1
- package/.pi/agents/harness/sentrux-steward.md +0 -2
- package/.pi/agents/harness/trace-librarian.md +0 -1
- package/.pi/extensions/00-harness-project-control.ts +133 -0
- package/.pi/extensions/00-posthog-network-bootstrap.ts +1 -1
- package/.pi/extensions/agt-kill-switch.ts +57 -0
- package/.pi/extensions/agt-prompt-guard.ts +32 -0
- package/.pi/extensions/budget-guard.ts +2 -0
- package/.pi/extensions/custom-footer.ts +46 -145
- package/.pi/extensions/custom-header.ts +1 -1
- package/.pi/extensions/custom-system-prompt.ts +1 -1
- package/.pi/extensions/debate-orchestrator.ts +7 -5
- package/.pi/extensions/harness-ask-user.ts +8 -8
- package/.pi/extensions/harness-debate-tools.ts +27 -43
- package/.pi/extensions/harness-lens.ts +94 -0
- package/.pi/extensions/harness-live-widget.ts +33 -2
- package/.pi/extensions/harness-plan-approval.ts +12 -12
- package/.pi/extensions/harness-run-context.ts +1214 -852
- package/.pi/extensions/harness-subagent-governance.ts +8 -0
- package/.pi/extensions/harness-subagent-submit.ts +36 -164
- package/.pi/extensions/harness-subagents.ts +4 -4
- package/.pi/extensions/harness-telemetry.ts +3 -1
- package/.pi/extensions/harness-web-tools.ts +3 -3
- package/.pi/extensions/observation-bus.ts +2 -0
- package/.pi/extensions/policy-gate.ts +27 -5
- package/.pi/extensions/review-integrity.ts +91 -10
- package/.pi/extensions/sentrux-rules-sync.ts +3 -1
- package/.pi/extensions/subagent-governance.ts +92 -0
- package/.pi/extensions/test-diff-integrity.ts +1 -0
- package/.pi/extensions/trace-recorder.ts +3 -1
- package/.pi/extensions/{ultimate-pi-vcc.ts → vcc-compaction.ts} +1 -1
- package/.pi/harness/README.md +6 -2
- package/.pi/harness/agents.manifest.json +38 -49
- package/.pi/harness/agents.policy.yaml +275 -0
- package/.pi/harness/corpus/graphify-kb-updater.config.json +55 -0
- package/.pi/harness/docs/adrs/0006-sentrux-dual-layer.md +2 -1
- package/.pi/harness/docs/adrs/0030-inhouse-vcc-compaction.md +1 -1
- package/.pi/harness/docs/adrs/0035-plan-phase-review-gate.md +1 -1
- package/.pi/harness/docs/adrs/0044-harness-steer-loop.md +3 -2
- package/.pi/harness/docs/adrs/0045-harness-lens-minimal-contract.md +49 -0
- package/.pi/harness/docs/adrs/0045-phase-scoped-agent-directories.md +33 -0
- package/.pi/harness/docs/adrs/0046-agt-policy-engine.md +51 -0
- package/.pi/harness/docs/adrs/0047-agt-layered-security.md +39 -0
- package/.pi/harness/docs/adrs/0048-tool-call-hook-order.md +25 -0
- package/.pi/harness/docs/adrs/0049-agents-policy-manifest.md +36 -0
- package/.pi/harness/docs/adrs/README.md +6 -0
- package/.pi/harness/docs/graphify-kb-updater-runbook.md +11 -5
- package/.pi/harness/docs/practice-map.md +2 -2
- package/.pi/harness/evolution/README.md +1 -2
- package/.pi/harness/examples/agents.policy.project.yaml +19 -0
- package/.pi/harness/examples/policies/custom-deny-bash.yaml +9 -0
- package/.pi/harness/policies/bash-denylists.yaml +5 -0
- package/.pi/harness/policies/defaults.yaml +51 -0
- package/.pi/harness/policies/orchestrator.yaml +18 -0
- package/.pi/harness/policies/phases.yaml +10 -0
- package/.pi/harness/policies/roles.yaml +5 -0
- package/.pi/harness/policies/web-guard.yaml +5 -0
- package/.pi/harness/policies/workflow-sequences.yaml +9 -0
- package/.pi/harness/sentrux/architecture.manifest.json +26 -4
- package/.pi/harness/specs/harness-spawn-context.schema.json +1 -1
- package/.pi/harness/specs/observation.schema.json +2 -1
- package/.pi/lib/agents-policy.d.mts +70 -0
- package/.pi/lib/agents-policy.mjs +325 -0
- package/.pi/lib/agents-policy.ts +19 -0
- package/.pi/lib/agt/audit-run-sink.ts +52 -0
- package/.pi/lib/agt/build-evaluation-context.ts +285 -0
- package/.pi/lib/agt/config.ts +28 -0
- package/.pi/lib/agt/delegation.ts +69 -0
- package/.pi/lib/agt/evaluate-policy.ts +56 -0
- package/.pi/lib/agt/identity-registry.ts +41 -0
- package/.pi/lib/agt/index.ts +55 -0
- package/.pi/lib/agt/kill-switch-state.ts +11 -0
- package/.pi/lib/agt/legacy-evaluate.ts +101 -0
- package/.pi/lib/agt/policy-engine.ts +154 -0
- package/.pi/lib/agt/rings.ts +21 -0
- package/.pi/lib/agt/sre-hooks.ts +45 -0
- package/.pi/lib/agt/trust-run-store.ts +26 -0
- package/.pi/lib/agt/workflow-history.ts +29 -0
- package/.pi/lib/agt-governance-active.ts +14 -0
- package/.pi/lib/agt-tool-guard.ts +78 -0
- package/.pi/lib/ask-user/dialog.ts +314 -0
- package/.pi/{extensions/lib → lib}/debate-bus-core.ts +10 -10
- package/.pi/{extensions/lib → lib}/debate-bus-state.ts +1 -1
- package/.pi/{extensions/lib → lib}/extension-load-guard.ts +21 -0
- package/.pi/lib/harness-agt-tool-guard.ts +5 -0
- package/.pi/{extensions/lib → lib}/harness-artifact-gate.ts +6 -16
- package/.pi/lib/harness-debate-core-deps.ts +14 -0
- package/.pi/lib/harness-debate-workflow-deps.ts +43 -0
- package/.pi/lib/harness-lens/.gitattributes +1 -0
- package/.pi/lib/harness-lens/clients/edit-autopatch.ts +88 -0
- package/.pi/lib/harness-lens/clients/file-kinds.ts +380 -0
- package/.pi/lib/harness-lens/clients/file-time.ts +215 -0
- package/.pi/lib/harness-lens/clients/file-utils.ts +484 -0
- package/.pi/lib/harness-lens/clients/format-service.ts +276 -0
- package/.pi/lib/harness-lens/clients/formatters.ts +1000 -0
- package/.pi/lib/harness-lens/clients/git-guard.ts +31 -0
- package/.pi/lib/harness-lens/clients/indent-retarget.ts +90 -0
- package/.pi/lib/harness-lens/clients/installer/index.ts +2368 -0
- package/.pi/lib/harness-lens/clients/latency-logger.ts +80 -0
- package/.pi/lib/harness-lens/clients/lens-config.ts +43 -0
- package/.pi/lib/harness-lens/clients/lens-events.ts +164 -0
- package/.pi/lib/harness-lens/clients/lsp/aggregation.ts +91 -0
- package/.pi/lib/harness-lens/clients/lsp/client.ts +1466 -0
- package/.pi/lib/harness-lens/clients/lsp/config.ts +216 -0
- package/.pi/lib/harness-lens/clients/lsp/edits.ts +297 -0
- package/.pi/lib/harness-lens/clients/lsp/index.ts +1355 -0
- package/.pi/lib/harness-lens/clients/lsp/interactive-install.ts +424 -0
- package/.pi/lib/harness-lens/clients/lsp/language.ts +223 -0
- package/.pi/lib/harness-lens/clients/lsp/launch.ts +939 -0
- package/.pi/lib/harness-lens/clients/lsp/lsp-index.ts +11 -0
- package/.pi/lib/harness-lens/clients/lsp/path-utils.ts +12 -0
- package/.pi/lib/harness-lens/clients/lsp/server-strategies.ts +81 -0
- package/.pi/lib/harness-lens/clients/lsp/server.ts +1971 -0
- package/.pi/lib/harness-lens/clients/path-utils.ts +182 -0
- package/.pi/lib/harness-lens/clients/pipeline.ts +360 -0
- package/.pi/lib/harness-lens/clients/project-profile.ts +117 -0
- package/.pi/lib/harness-lens/clients/runtime-agent-end.ts +112 -0
- package/.pi/lib/harness-lens/clients/runtime-config.ts +33 -0
- package/.pi/lib/harness-lens/clients/runtime-coordinator.ts +186 -0
- package/.pi/lib/harness-lens/clients/runtime-tool-result.ts +171 -0
- package/.pi/lib/harness-lens/clients/safe-spawn.ts +339 -0
- package/.pi/lib/harness-lens/clients/secrets-scanner.ts +214 -0
- package/.pi/lib/harness-lens/clients/tool-policy.ts +2072 -0
- package/.pi/lib/harness-lens/clients/types.ts +59 -0
- package/.pi/lib/harness-lens/clients/widget-state.ts +283 -0
- package/.pi/lib/harness-lens/index.ts +532 -0
- package/.pi/lib/harness-lens/tools/lsp-diagnostics.ts +706 -0
- package/.pi/lib/harness-lens/tools/lsp-navigation.ts +1246 -0
- package/.pi/{extensions/lib → lib}/harness-posthog.ts +3 -0
- package/.pi/lib/harness-project-config.ts +91 -0
- package/.pi/lib/harness-run-context-responses.ts +9 -0
- package/.pi/lib/harness-run-context.ts +1 -3
- package/.pi/{extensions/lib/spawn-policy.ts → lib/harness-spawn-policy.ts} +4 -3
- package/.pi/{extensions/lib → lib}/harness-spawn-topology.ts +5 -28
- package/.pi/lib/harness-subagent-auth.ts +51 -0
- package/.pi/{extensions/lib → lib}/harness-subagent-precheck.ts +13 -10
- package/.pi/{extensions/lib → lib}/harness-subagent-submit-pipeline.ts +3 -3
- package/.pi/lib/harness-subagent-submit-register.ts +163 -0
- package/.pi/{extensions/lib → lib}/harness-subagent-submit-registry.ts +1 -55
- package/.pi/{extensions/lib → lib}/harness-subagents-bridge.ts +53 -14
- package/.pi/{extensions/lib → lib}/harness-subprocess-bootstrap.ts +1 -1
- package/.pi/lib/harness-ui-state.ts +27 -12
- package/.pi/{extensions/lib → lib}/plan-approval/create-plan.ts +2 -2
- package/.pi/{extensions/lib → lib}/plan-approval/format-plan.ts +2 -2
- package/.pi/{extensions/lib → lib}/plan-approval/plan-review.ts +162 -201
- package/.pi/{extensions/lib → lib}/plan-approval/render.ts +1 -1
- package/.pi/{extensions/lib → lib}/plan-approval/resolve-disk.ts +2 -2
- package/.pi/{extensions/lib → lib}/plan-approval/types.ts +1 -1
- package/.pi/{extensions/lib → lib}/plan-approval/validate.ts +3 -3
- package/.pi/{extensions/lib → lib}/plan-approval-readiness.ts +3 -52
- package/.pi/{extensions/lib → lib}/plan-debate-envelope.ts +1 -1
- package/.pi/{extensions/lib → lib}/plan-debate-gate.ts +1 -1
- package/.pi/{extensions/lib → lib}/plan-debate-lane.ts +1 -4
- package/.pi/{extensions/lib → lib}/plan-messenger.ts +1 -1
- package/.pi/prompts/harness-auto.md +2 -2
- package/.pi/prompts/harness-plan.md +4 -6
- package/.pi/prompts/harness-review.md +9 -9
- package/.pi/prompts/harness-run.md +7 -7
- package/.pi/prompts/harness-setup.md +42 -68
- package/.pi/prompts/harness-steer.md +2 -2
- package/.pi/scripts/README.md +3 -5
- package/.pi/scripts/generate-agents-policy-yaml.mjs +148 -0
- package/.pi/scripts/graphify-kb-updater.mjs +48 -8
- package/.pi/scripts/harness-agents-manifest.mjs +61 -4
- package/.pi/scripts/harness-agt-doctor.ts +36 -0
- package/.pi/scripts/harness-cli-verify.sh +9 -2
- package/.pi/scripts/harness-project-toggle.mjs +129 -0
- package/.pi/scripts/harness-sentrux-cli.mjs +142 -0
- package/.pi/scripts/harness-verify.mjs +113 -39
- package/.pi/scripts/harness-web-policy-guard.mjs +2 -2
- package/.pi/scripts/validate-plan-dag.mjs +65 -74
- package/.pi/scripts/vendor-pi-vcc-settings.stub.ts +2 -2
- package/.pi/scripts/vendor-sync-pi-vcc.sh +1 -1
- package/.pi/skills/architecture/broker-domain/SKILL.md +65 -0
- package/.pi/skills/architecture/cqrs/SKILL.md +63 -0
- package/.pi/skills/architecture/event-driven/SKILL.md +60 -0
- package/.pi/skills/architecture/hexagonal-ports-adapters/SKILL.md +66 -0
- package/.pi/skills/architecture/layered/SKILL.md +68 -0
- package/.pi/skills/architecture/microkernel/SKILL.md +62 -0
- package/.pi/skills/architecture/microservices/SKILL.md +64 -0
- package/.pi/skills/architecture/modular-monolith/SKILL.md +65 -0
- package/.pi/skills/architecture/orchestration-driven-soa/SKILL.md +61 -0
- package/.pi/skills/architecture/pipeline/SKILL.md +63 -0
- package/.pi/skills/architecture/service-based/SKILL.md +64 -0
- package/.pi/skills/architecture/service-mesh/SKILL.md +60 -0
- package/.pi/skills/architecture/space-based/SKILL.md +60 -0
- package/.pi/skills/ast-grep/SKILL.md +40 -321
- package/.pi/skills/delivery/debugging-discipline/SKILL.md +36 -0
- package/.pi/skills/delivery/documentation-update/SKILL.md +33 -0
- package/.pi/skills/delivery/requirements-to-implementation/SKILL.md +34 -0
- package/.pi/skills/delivery/risk-based-verification/SKILL.md +43 -0
- package/.pi/skills/delivery/tradeoff-analysis/SKILL.md +34 -0
- package/.pi/skills/engineering/api-contract-design/SKILL.md +38 -0
- package/.pi/skills/engineering/cohesion-coupling/SKILL.md +43 -0
- package/.pi/skills/engineering/complexity-control/SKILL.md +31 -0
- package/.pi/skills/engineering/defensive-programming/SKILL.md +38 -0
- package/.pi/skills/engineering/dependency-management/SKILL.md +29 -0
- package/.pi/skills/engineering/domain-modeling/SKILL.md +32 -0
- package/.pi/skills/engineering/error-handling/SKILL.md +37 -0
- package/.pi/skills/engineering/legacy-code-seams/SKILL.md +35 -0
- package/.pi/skills/engineering/naming-and-intent/SKILL.md +29 -0
- package/.pi/skills/engineering/refactoring-safe-evolution/SKILL.md +35 -0
- package/.pi/skills/engineering/routine-function-design/SKILL.md +34 -0
- package/.pi/skills/engineering/small-change-discipline/SKILL.md +35 -0
- package/.pi/skills/lsp-navigation/SKILL.md +89 -0
- package/.pi/skills/quality/code-review-self-check/SKILL.md +35 -0
- package/.pi/skills/quality/privacy-data-handling/SKILL.md +26 -0
- package/.pi/skills/quality/security-review/SKILL.md +34 -0
- package/.pi/skills/quality/test-strategy/SKILL.md +33 -0
- package/.pi/skills/quality/testability-design/SKILL.md +33 -0
- package/.pi/skills/systems/concurrency-safety/SKILL.md +32 -0
- package/.pi/skills/systems/data-modeling-migrations/SKILL.md +31 -0
- package/.pi/skills/systems/observability-instrumentation/SKILL.md +32 -0
- package/.pi/skills/systems/performance-measurement/SKILL.md +35 -0
- package/.pi/skills/systems/reliability-design/SKILL.md +32 -0
- package/.sentrux/rules.toml +20 -4
- package/AGENTS.md +5 -0
- package/CHANGELOG.md +26 -0
- package/README.md +85 -58
- package/THIRD_PARTY_NOTICES.md +12 -21
- package/package.json +15 -7
- package/vendor/pi-subagents/src/agents.ts +45 -1
- package/vendor/pi-subagents/src/subagents.ts +866 -811
- package/vendor/pi-vcc/src/core/brief.ts +68 -99
- package/vendor/pi-vcc/src/core/settings.ts +2 -2
- package/.agents/skills/caveman/SKILL.md +0 -67
- package/.pi/agents/harness/meta-optimizer.md +0 -36
- package/.pi/agents/harness/planning/scout-graphify.md +0 -39
- package/.pi/agents/harness/planning/scout-semantic.md +0 -41
- package/.pi/agents/harness/planning/scout-structure.md +0 -37
- package/.pi/extensions/lib/ask-user/dialog.ts +0 -260
- package/.pi/extensions/lib/harness-subagent-auth.ts +0 -209
- package/.pi/extensions/lib/harness-subagent-policy.ts +0 -236
- package/.pi/extensions/pi-model-router-harness.ts +0 -42
- package/.pi/harness/evolution/meta-optimizer.mjs +0 -99
- package/.pi/harness/specs/router-tuning-proposal.schema.json +0 -114
- package/.pi/model-router.example.json +0 -36
- package/.pi/prompts/harness-critic.md +0 -10
- package/.pi/prompts/harness-eval.md +0 -10
- package/.pi/prompts/harness-router-tune.md +0 -52
- package/.pi/scripts/harness-generate-model-router.mjs +0 -327
- package/.pi/scripts/harness-model-router-routing.test.mjs +0 -97
- package/.pi/scripts/harness-sync-model-router.mjs +0 -97
- package/.pi/scripts/vendor-sync-pi-model-router.sh +0 -47
- package/vendor/pi-model-router/.prettierignore +0 -4
- package/vendor/pi-model-router/.prettierrc +0 -5
- package/vendor/pi-model-router/AGENTS.md +0 -39
- package/vendor/pi-model-router/LICENSE +0 -21
- package/vendor/pi-model-router/README.md +0 -99
- package/vendor/pi-model-router/UPSTREAM_PIN.md +0 -10
- package/vendor/pi-model-router/docs/ARCHITECTURE.md +0 -54
- package/vendor/pi-model-router/extensions/commands.ts +0 -720
- package/vendor/pi-model-router/extensions/config.ts +0 -348
- package/vendor/pi-model-router/extensions/constants.ts +0 -1
- package/vendor/pi-model-router/extensions/index.ts +0 -478
- package/vendor/pi-model-router/extensions/provider.ts +0 -580
- package/vendor/pi-model-router/extensions/routing.ts +0 -564
- package/vendor/pi-model-router/extensions/state.ts +0 -52
- package/vendor/pi-model-router/extensions/types.ts +0 -95
- package/vendor/pi-model-router/extensions/ui.ts +0 -144
- package/vendor/pi-model-router/model-router.example.json +0 -48
- package/vendor/pi-model-router/package.json +0 -48
- package/vendor/pi-model-router/tsconfig.json +0 -16
- /package/.pi/{prompts → harness/docs}/planning-rubrics.md +0 -0
- /package/.pi/{extensions/lib → lib}/ask-user/fallback.ts +0 -0
- /package/.pi/{extensions/lib → lib}/ask-user/render.ts +0 -0
- /package/.pi/{extensions/lib → lib}/ask-user/schema.ts +0 -0
- /package/.pi/{extensions/lib → lib}/ask-user/types.ts +0 -0
- /package/.pi/{extensions/lib → lib}/ask-user/validate-core.mjs +0 -0
- /package/.pi/{extensions/lib → lib}/ask-user/validate.ts +0 -0
- /package/.pi/{extensions/lib → lib}/harness-cocoindex-refresh.ts +0 -0
- /package/.pi/{extensions/lib → lib}/harness-paths.ts +0 -0
- /package/.pi/{extensions/lib → lib}/harness-spawn-budget.ts +0 -0
- /package/.pi/{extensions/lib → lib}/harness-vcc-settings.ts +0 -0
- /package/.pi/{extensions/lib → lib}/harness-web/run-cli.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-approval/dialog.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-approval/schema.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-debate-eligibility.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-debate-focus.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-debate-id.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-debate-lanes.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-debate-round-status.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-debate-write-guard.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-review-gate.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-review-integrator-rules.ts +0 -0
- /package/.pi/{extensions/lib → lib}/plan-scope-guard.ts +0 -0
- /package/.pi/{extensions/lib → lib}/posthog-client.ts +0 -0
- /package/.pi/{extensions/lib → lib}/posthog-node.d.ts +0 -0
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe cross-platform spawn utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides both sync (deprecated) and async versions for gradual migration.
|
|
5
|
+
*
|
|
6
|
+
* Async version features:
|
|
7
|
+
* - Non-blocking execution
|
|
8
|
+
* - Proper process cleanup on timeout (no zombies)
|
|
9
|
+
* - Batch execution with concurrency limits
|
|
10
|
+
* - AbortSignal support for cancellation
|
|
11
|
+
*
|
|
12
|
+
* Migration guide:
|
|
13
|
+
* - Change: safeSpawn(cmd, args, opts)
|
|
14
|
+
* - To: await safeSpawnAsync(cmd, args, opts)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { type SpawnOptions, spawn, spawnSync } from "node:child_process";
|
|
18
|
+
|
|
19
|
+
export interface SpawnResult {
|
|
20
|
+
stdout: string;
|
|
21
|
+
stderr: string;
|
|
22
|
+
status: number | null;
|
|
23
|
+
error?: Error;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SafeSpawnOptions {
|
|
27
|
+
timeout?: number;
|
|
28
|
+
cwd?: string;
|
|
29
|
+
env?: NodeJS.ProcessEnv;
|
|
30
|
+
signal?: AbortSignal;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// INTERNAL HELPERS
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Escape a single argument for cmd.exe when shell:true is required.
|
|
39
|
+
* Only used on Windows to avoid DEP0190 (args+shell concatenation warning).
|
|
40
|
+
*/
|
|
41
|
+
function cmdEscapeArg(arg: string): string {
|
|
42
|
+
if (!/[\s"&|<>^()]/.test(arg)) return arg;
|
|
43
|
+
return `"${arg.replace(/"/g, '""')}"`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// ASYNC VERSION (Recommended - Non-blocking)
|
|
48
|
+
// ============================================================================
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Async spawn with timeout and proper process cleanup.
|
|
52
|
+
*
|
|
53
|
+
* Unlike spawnSync, this:
|
|
54
|
+
* - Doesn't block the event loop
|
|
55
|
+
* - Kills the process on timeout (preventing zombies)
|
|
56
|
+
* - Supports cancellation via AbortSignal
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* const result = await safeSpawnAsync("npm", ["test"], { timeout: 30000 });
|
|
60
|
+
* if (result.error) console.error("Failed:", result.error);
|
|
61
|
+
*/
|
|
62
|
+
export async function safeSpawnAsync(
|
|
63
|
+
command: string,
|
|
64
|
+
args: string[],
|
|
65
|
+
options?: SafeSpawnOptions,
|
|
66
|
+
): Promise<SpawnResult> {
|
|
67
|
+
const timeout = options?.timeout ?? 30000;
|
|
68
|
+
const abortSignal = options?.signal;
|
|
69
|
+
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
|
+
// Check for early abort
|
|
72
|
+
if (abortSignal?.aborted) {
|
|
73
|
+
resolve({
|
|
74
|
+
stdout: "",
|
|
75
|
+
stderr: "",
|
|
76
|
+
status: null,
|
|
77
|
+
error: new Error("Spawn aborted before start"),
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let stdout = "";
|
|
83
|
+
let stderr = "";
|
|
84
|
+
let timedOut = false;
|
|
85
|
+
let killed = false;
|
|
86
|
+
|
|
87
|
+
// Spawn the process (non-blocking)
|
|
88
|
+
// On Windows, use shell mode for .cmd files (like pyright, biome).
|
|
89
|
+
// Bake args into the command string when shell:true to avoid DEP0190.
|
|
90
|
+
const isWindows = process.platform === "win32";
|
|
91
|
+
const spawnCmd = isWindows
|
|
92
|
+
? [command, ...args.map(cmdEscapeArg)].join(" ")
|
|
93
|
+
: command;
|
|
94
|
+
const spawnArgs = isWindows ? [] : args;
|
|
95
|
+
const child = spawn(spawnCmd, spawnArgs, {
|
|
96
|
+
cwd: options?.cwd,
|
|
97
|
+
env: { ...process.env, ...options?.env },
|
|
98
|
+
windowsHide: true,
|
|
99
|
+
shell: isWindows,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// On Windows, shell:true means child.pid is cmd.exe — child.kill() only
|
|
103
|
+
// kills the wrapper, leaving the actual subprocess (e.g. knip/npx) alive
|
|
104
|
+
// as an orphan. Use taskkill /F /T to kill the full process tree instead.
|
|
105
|
+
const killTree = () => {
|
|
106
|
+
if (isWindows && child.pid && child.pid > 0) {
|
|
107
|
+
const taskkill = `${process.env.SystemRoot ?? "C:\\Windows"}\\System32\\taskkill.exe`;
|
|
108
|
+
try {
|
|
109
|
+
spawn(taskkill, ["/F", "/T", "/PID", String(child.pid)], {
|
|
110
|
+
shell: false,
|
|
111
|
+
windowsHide: true,
|
|
112
|
+
});
|
|
113
|
+
} catch {
|
|
114
|
+
child.kill("SIGKILL");
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
child.kill("SIGTERM");
|
|
118
|
+
setTimeout(() => {
|
|
119
|
+
if (!child.killed) child.kill("SIGKILL");
|
|
120
|
+
}, 1000);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
// Handle abort signal
|
|
125
|
+
const onAbort = () => {
|
|
126
|
+
if (!killed && !child.killed) {
|
|
127
|
+
killed = true;
|
|
128
|
+
killTree();
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
abortSignal?.addEventListener("abort", onAbort, { once: true });
|
|
132
|
+
|
|
133
|
+
// Collect output
|
|
134
|
+
child.stdout?.setEncoding("utf-8");
|
|
135
|
+
child.stderr?.setEncoding("utf-8");
|
|
136
|
+
child.stdout?.on("data", (data) => (stdout += data));
|
|
137
|
+
child.stderr?.on("data", (data) => (stderr += data));
|
|
138
|
+
|
|
139
|
+
// Timeout handling - KILL the process, don't just abandon it
|
|
140
|
+
const timeoutId = setTimeout(() => {
|
|
141
|
+
timedOut = true;
|
|
142
|
+
if (!killed && !child.killed) {
|
|
143
|
+
killed = true;
|
|
144
|
+
killTree();
|
|
145
|
+
}
|
|
146
|
+
}, timeout);
|
|
147
|
+
|
|
148
|
+
// Process completion
|
|
149
|
+
child.on("close", (code, signal) => {
|
|
150
|
+
clearTimeout(timeoutId);
|
|
151
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
152
|
+
|
|
153
|
+
if (timedOut) {
|
|
154
|
+
resolve({
|
|
155
|
+
stdout,
|
|
156
|
+
stderr,
|
|
157
|
+
status: null,
|
|
158
|
+
error: new Error(
|
|
159
|
+
`Process timed out after ${timeout}ms (killed with ${signal || "SIGTERM"})`,
|
|
160
|
+
),
|
|
161
|
+
});
|
|
162
|
+
} else if (signal) {
|
|
163
|
+
resolve({
|
|
164
|
+
stdout,
|
|
165
|
+
stderr,
|
|
166
|
+
status: null,
|
|
167
|
+
error: new Error(`Process killed by signal: ${signal}`),
|
|
168
|
+
});
|
|
169
|
+
} else {
|
|
170
|
+
resolve({ stdout, stderr, status: code });
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
child.on("error", (err) => {
|
|
175
|
+
clearTimeout(timeoutId);
|
|
176
|
+
abortSignal?.removeEventListener("abort", onAbort);
|
|
177
|
+
resolve({ stdout, stderr, status: null, error: err });
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Run multiple commands concurrently with limited concurrency.
|
|
184
|
+
*
|
|
185
|
+
* This prevents resource contention when running many linters.
|
|
186
|
+
* Uses async spawn with concurrency limiting built-in.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* const results = await safeSpawnBatch([
|
|
190
|
+
* { command: "biome", args: ["check", "file.ts"] },
|
|
191
|
+
* { command: "ruff", args: ["check", "file.py"] },
|
|
192
|
+
* ], 3); // Max 3 concurrent
|
|
193
|
+
*/
|
|
194
|
+
export async function safeSpawnBatch(
|
|
195
|
+
commands: Array<{
|
|
196
|
+
command: string;
|
|
197
|
+
args: string[];
|
|
198
|
+
options?: SafeSpawnOptions;
|
|
199
|
+
}>,
|
|
200
|
+
concurrency = 3,
|
|
201
|
+
): Promise<SpawnResult[]> {
|
|
202
|
+
const results: SpawnResult[] = [];
|
|
203
|
+
|
|
204
|
+
// Process in batches to limit concurrent processes
|
|
205
|
+
for (let i = 0; i < commands.length; i += concurrency) {
|
|
206
|
+
const batch = commands.slice(i, i + concurrency);
|
|
207
|
+
const batchResults = await Promise.all(
|
|
208
|
+
batch.map(({ command, args, options }) =>
|
|
209
|
+
safeSpawnAsync(command, args, options),
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
results.push(...batchResults);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return results;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Check if a command is available in PATH (async version)
|
|
220
|
+
*/
|
|
221
|
+
export async function isCommandAvailableAsync(
|
|
222
|
+
command: string,
|
|
223
|
+
): Promise<boolean> {
|
|
224
|
+
const finder = process.platform === "win32" ? "where" : "which";
|
|
225
|
+
const result = await safeSpawnAsync(finder, [command], { timeout: 5000 });
|
|
226
|
+
return result.status === 0 && !result.error;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Find the full path to a command (async version)
|
|
231
|
+
*/
|
|
232
|
+
export async function findCommandAsync(
|
|
233
|
+
command: string,
|
|
234
|
+
): Promise<string | null> {
|
|
235
|
+
const finder = process.platform === "win32" ? "where" : "which";
|
|
236
|
+
const result = await safeSpawnAsync(finder, [command], { timeout: 5000 });
|
|
237
|
+
|
|
238
|
+
if (result.status !== 0 || result.error) return null;
|
|
239
|
+
|
|
240
|
+
// Take first line (first match)
|
|
241
|
+
return result.stdout.trim().split("\n")[0] || null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ============================================================================
|
|
245
|
+
// SYNC VERSION (Deprecated - Blocking, for backward compatibility)
|
|
246
|
+
// ============================================================================
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Escape an argument for Windows shell execution.
|
|
250
|
+
* Handles spaces, quotes, $variables, and special characters.
|
|
251
|
+
*/
|
|
252
|
+
function escapeWindowsArg(arg: string): string {
|
|
253
|
+
if (arg.includes("$")) {
|
|
254
|
+
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
255
|
+
}
|
|
256
|
+
if (!/[\s"]/.test(arg)) return arg;
|
|
257
|
+
return `"${arg.replace(/"/g, '""')}"`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Construct a command string for Windows shell execution.
|
|
262
|
+
*/
|
|
263
|
+
function buildWindowsCommand(command: string, args: string[]): string {
|
|
264
|
+
const escapedArgs = args.map(escapeWindowsArg).join(" ");
|
|
265
|
+
return `${command} ${escapedArgs}`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* ⚠️ DEPRECATED: Use safeSpawnAsync instead.
|
|
270
|
+
*
|
|
271
|
+
* This blocks the entire Node.js event loop until the process exits.
|
|
272
|
+
* If the process hangs, pi will freeze.
|
|
273
|
+
*
|
|
274
|
+
* Kept for backward compatibility during migration.
|
|
275
|
+
*/
|
|
276
|
+
export function safeSpawn(
|
|
277
|
+
command: string,
|
|
278
|
+
args: string[],
|
|
279
|
+
options?: SafeSpawnOptions,
|
|
280
|
+
): SpawnResult {
|
|
281
|
+
if (process.platform === "win32") {
|
|
282
|
+
// shell:true here is justified only because this deprecated sync function
|
|
283
|
+
// predates safeSpawnAsync. It will be eliminated when safeSpawn is removed.
|
|
284
|
+
const fullCommand = buildWindowsCommand(command, args);
|
|
285
|
+
const result = spawnSync(fullCommand, {
|
|
286
|
+
...(options as SpawnOptions),
|
|
287
|
+
encoding: "utf-8",
|
|
288
|
+
shell: true,
|
|
289
|
+
windowsHide: true,
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
stdout: result.stdout?.toString() || "",
|
|
294
|
+
stderr: result.stderr?.toString() || "",
|
|
295
|
+
status: result.status,
|
|
296
|
+
error: result.error,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const result = spawnSync(command, args, {
|
|
301
|
+
...(options as SpawnOptions),
|
|
302
|
+
encoding: "utf-8",
|
|
303
|
+
shell: false,
|
|
304
|
+
windowsHide: true,
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
stdout: result.stdout?.toString() || "",
|
|
309
|
+
stderr: result.stderr?.toString() || "",
|
|
310
|
+
status: result.status,
|
|
311
|
+
error: result.error,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Check if a command is available in PATH (sync version - deprecated)
|
|
317
|
+
* @deprecated Use isCommandAvailableAsync
|
|
318
|
+
*/
|
|
319
|
+
export function isCommandAvailable(command: string): boolean {
|
|
320
|
+
const result = safeSpawn(
|
|
321
|
+
process.platform === "win32" ? "where" : "which",
|
|
322
|
+
[command],
|
|
323
|
+
{ timeout: 5000 },
|
|
324
|
+
);
|
|
325
|
+
return result.status === 0;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Find the full path to a command (sync version - deprecated)
|
|
330
|
+
* @deprecated Use findCommandAsync
|
|
331
|
+
*/
|
|
332
|
+
export function findCommand(command: string): string | null {
|
|
333
|
+
const finder = process.platform === "win32" ? "where" : "which";
|
|
334
|
+
const result = safeSpawn(finder, [command], { timeout: 5000 });
|
|
335
|
+
|
|
336
|
+
if (result.status !== 0) return null;
|
|
337
|
+
|
|
338
|
+
return result.stdout.trim().split("\n")[0] || null;
|
|
339
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content-level secrets scanner
|
|
3
|
+
*
|
|
4
|
+
* Scans file content for potential secret patterns before write.
|
|
5
|
+
* Works on all file types via regex matching.
|
|
6
|
+
*
|
|
7
|
+
* Detected patterns:
|
|
8
|
+
* - Stripe/OpenAI keys (sk-*)
|
|
9
|
+
* - GitHub tokens (ghp_*, gho_*, github_pat_*)
|
|
10
|
+
* - AWS keys (AKIA*)
|
|
11
|
+
* - Slack tokens (xoxp-*, xoxb-*)
|
|
12
|
+
* - Private keys (BEGIN PRIVATE KEY)
|
|
13
|
+
* - Generic API key/password patterns
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { isTestFile } from "./file-utils.js";
|
|
17
|
+
|
|
18
|
+
interface SecretPattern {
|
|
19
|
+
pattern: RegExp;
|
|
20
|
+
name: string;
|
|
21
|
+
message: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const SAFE_HEADER_KEYS = new Set([
|
|
25
|
+
"user-agent",
|
|
26
|
+
"accept",
|
|
27
|
+
"accept-language",
|
|
28
|
+
"accept-encoding",
|
|
29
|
+
"content-type",
|
|
30
|
+
"content-length",
|
|
31
|
+
"origin",
|
|
32
|
+
"referer",
|
|
33
|
+
"host",
|
|
34
|
+
"connection",
|
|
35
|
+
"cache-control",
|
|
36
|
+
"pragma",
|
|
37
|
+
"x-requested-with",
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
function extractHeaderKey(line: string): string | null {
|
|
41
|
+
const m = line.match(/["']([A-Za-z][A-Za-z0-9-]{0,63})["']\s*:\s*["'][^"']+/);
|
|
42
|
+
if (!m) return null;
|
|
43
|
+
return m[1].toLowerCase();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function shouldIgnorePatternMatch(line: string, patternName: string): boolean {
|
|
47
|
+
// Ignore obvious non-secret HTTP header literals such as "User-Agent".
|
|
48
|
+
if (
|
|
49
|
+
patternName === "hardcoded-secret" ||
|
|
50
|
+
patternName === "hardcoded-password"
|
|
51
|
+
) {
|
|
52
|
+
const key = extractHeaderKey(line);
|
|
53
|
+
if (key && SAFE_HEADER_KEYS.has(key)) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Check if a string value looks like an environment variable name.
|
|
63
|
+
* Env var names typically use UPPERCASE_SNAKE_CASE.
|
|
64
|
+
* Used to filter false positives like: api_key = "FIREWORKS_API_KEY"
|
|
65
|
+
* where the value is just referencing the env var name, not a secret.
|
|
66
|
+
*/
|
|
67
|
+
function looksLikeEnvVarName(value: string): boolean {
|
|
68
|
+
// Must be all uppercase with underscores (no lowercase letters)
|
|
69
|
+
// Must start with a letter and contain at least one underscore
|
|
70
|
+
return /^[A-Z][A-Z0-9_]*$/.test(value) && value.includes("_");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Extract the quoted value from a hardcoded secret pattern match.
|
|
75
|
+
* Returns null if no quoted value found.
|
|
76
|
+
*/
|
|
77
|
+
function extractQuotedValue(line: string): string | null {
|
|
78
|
+
// Match content inside quotes after : or =
|
|
79
|
+
const match = line.match(/[:=]\s*["']([^"']+)["']/);
|
|
80
|
+
return match ? match[1] : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Patterns ordered by specificity - first match wins per line
|
|
84
|
+
const SECRET_PATTERNS: SecretPattern[] = [
|
|
85
|
+
// High-confidence: specific key prefixes
|
|
86
|
+
{
|
|
87
|
+
pattern: /sk-[a-zA-Z0-9-]{20,}/g,
|
|
88
|
+
name: "stripe-openai-key",
|
|
89
|
+
message: "Possible Stripe or OpenAI API key (sk-*)",
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
pattern: /ghp_[a-zA-Z0-9]{36}/g,
|
|
93
|
+
name: "github-personal-token",
|
|
94
|
+
message: "GitHub personal access token (ghp_*)",
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
pattern: /gho_[a-zA-Z0-9]{36}/g,
|
|
98
|
+
name: "github-oauth-token",
|
|
99
|
+
message: "GitHub OAuth token (gho_*)",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
pattern: /github_pat_[a-zA-Z_]{82}/g,
|
|
103
|
+
name: "github-fine-grained-pat",
|
|
104
|
+
message: "GitHub fine-grained PAT (github_pat_*)",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
pattern: /AKIA[0-9A-Z]{16}/g,
|
|
108
|
+
name: "aws-access-key",
|
|
109
|
+
message: "AWS access key ID (AKIA*)",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
pattern: /xox[bp]-[a-zA-Z0-9]{10,}/g,
|
|
113
|
+
name: "slack-token",
|
|
114
|
+
message: "Slack token (xoxb-*/xoxp-*)",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
pattern: /-----BEGIN\s+(RSA\s+)?PRIVATE KEY-----/g,
|
|
118
|
+
name: "private-key",
|
|
119
|
+
message: "Private key material detected",
|
|
120
|
+
},
|
|
121
|
+
// Medium-confidence: quoted credentials
|
|
122
|
+
{
|
|
123
|
+
pattern: /password\s*[:=]\s*["'][^"']{4,}["']/gi,
|
|
124
|
+
name: "hardcoded-password",
|
|
125
|
+
message: "Possible hardcoded password",
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
pattern:
|
|
129
|
+
/\b(secret|api_?key|token|access_?key)\b\s*[:=]\s*["']([a-zA-Z0-9_./-]{8,})["']/gi,
|
|
130
|
+
name: "hardcoded-secret",
|
|
131
|
+
message: "Possible hardcoded secret or API key",
|
|
132
|
+
},
|
|
133
|
+
// .env format: KEY=VALUE (no quotes)
|
|
134
|
+
{
|
|
135
|
+
pattern:
|
|
136
|
+
/^(?:API_?KEY|SECRET|TOKEN|PASSWORD|AWS_?ACCESS_?KEY)\s*=\s*\S{8,}/gim,
|
|
137
|
+
name: "env-file-secret",
|
|
138
|
+
message: "Possible secret in .env format",
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
export interface SecretFinding {
|
|
143
|
+
line: number;
|
|
144
|
+
message: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Scan content for potential secrets
|
|
149
|
+
* Returns findings with line numbers.
|
|
150
|
+
* Skips test files to avoid false positives.
|
|
151
|
+
*/
|
|
152
|
+
export function scanForSecrets(
|
|
153
|
+
content: string,
|
|
154
|
+
filePath?: string,
|
|
155
|
+
): SecretFinding[] {
|
|
156
|
+
// Skip test files — secrets in tests are usually fake/test values
|
|
157
|
+
if (filePath && isTestFile(filePath)) {
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const findings: SecretFinding[] = [];
|
|
162
|
+
const lines = content.split("\n");
|
|
163
|
+
|
|
164
|
+
for (let i = 0; i < lines.length; i++) {
|
|
165
|
+
const line = lines[i];
|
|
166
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
167
|
+
// Reset lastIndex before each test (important for global regex)
|
|
168
|
+
const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags);
|
|
169
|
+
const match = regex.exec(line);
|
|
170
|
+
if (match) {
|
|
171
|
+
if (shouldIgnorePatternMatch(line, pattern.name)) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
// For hardcoded-secret pattern, check if the value looks like an env var name
|
|
175
|
+
// This prevents false positives like: api_key = "FIREWORKS_API_KEY"
|
|
176
|
+
if (pattern.name === "hardcoded-secret") {
|
|
177
|
+
const value = extractQuotedValue(line);
|
|
178
|
+
if (value && looksLikeEnvVarName(value)) {
|
|
179
|
+
continue; // Skip - just referencing env var name, not a secret
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
findings.push({
|
|
183
|
+
line: i + 1,
|
|
184
|
+
message: pattern.message,
|
|
185
|
+
});
|
|
186
|
+
break; // One finding per line
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return findings;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Format secrets findings for terminal output
|
|
196
|
+
*/
|
|
197
|
+
export function formatSecrets(
|
|
198
|
+
findings: SecretFinding[],
|
|
199
|
+
filePath: string,
|
|
200
|
+
): string {
|
|
201
|
+
if (findings.length === 0) return "";
|
|
202
|
+
|
|
203
|
+
const lines = [
|
|
204
|
+
`🔴 STOP — ${findings.length} potential secret(s) in ${filePath}:`,
|
|
205
|
+
];
|
|
206
|
+
for (const f of findings.slice(0, 5)) {
|
|
207
|
+
lines.push(` L${f.line}: ${f.message}`);
|
|
208
|
+
}
|
|
209
|
+
if (findings.length > 5) {
|
|
210
|
+
lines.push(` ... and ${findings.length - 5} more`);
|
|
211
|
+
}
|
|
212
|
+
lines.push(" → Remove before continuing. Use env vars instead.");
|
|
213
|
+
return lines.join("\n");
|
|
214
|
+
}
|