@howlil/ez-agents 3.4.2 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +735 -462
- package/agents/ez-architect-agent.md +267 -0
- package/agents/ez-backend-agent.md +303 -0
- package/agents/ez-chief-strategist.md +271 -0
- package/agents/ez-codebase-mapper.md +770 -770
- package/agents/ez-context-manager.md +319 -0
- package/agents/ez-debugger.md +1255 -1255
- package/agents/ez-design-expert.md +347 -0
- package/agents/ez-devops-agent.md +331 -0
- package/agents/ez-executor.md +487 -487
- package/agents/ez-frontend-agent.md +322 -0
- package/agents/ez-phase-researcher.md +553 -553
- package/agents/ez-planner.md +1307 -1307
- package/agents/ez-product-engineer.md +435 -0
- package/agents/ez-project-researcher.md +629 -629
- package/agents/ez-qa-agent.md +320 -0
- package/agents/ez-release-agent.md +333 -0
- package/agents/ez-requirements-agent.md +377 -0
- package/agents/ez-roadmapper.md +650 -650
- package/agents/ez-technical-writer.md +551 -0
- package/agents/ez-ux-expert.md +393 -0
- package/agents/ez-verifier.md +579 -579
- package/bin/guards/autonomy-guard.cjs +346 -0
- package/bin/guards/context-budget-guard.cjs +278 -0
- package/bin/guards/hallucination-guard.cjs +380 -0
- package/bin/guards/hidden-state-guard.cjs +182 -0
- package/bin/guards/team-overhead-guard.cjs +266 -0
- package/bin/guards/tool-sprawl-guard.cjs +271 -0
- package/bin/install.js +3221 -3272
- package/bin/lib/analytics/analytics-collector.cjs +86 -0
- package/bin/lib/analytics/analytics-reporter.cjs +130 -0
- package/bin/lib/analytics/cohort-analyzer.cjs +138 -0
- package/bin/lib/analytics/funnel-analyzer.cjs +147 -0
- package/bin/lib/analytics/nps-tracker.cjs +147 -0
- package/bin/lib/archetype-detector.cjs +289 -0
- package/bin/lib/assistant-adapter.cjs +361 -0
- package/bin/lib/audit-exec.cjs +175 -0
- package/bin/lib/auth.cjs +176 -0
- package/bin/lib/backup-service.cjs +422 -0
- package/bin/lib/bdd-validator.cjs +622 -0
- package/bin/lib/business-flow-mapper.cjs +429 -0
- package/bin/lib/circuit-breaker.cjs +276 -0
- package/bin/lib/code-complexity-analyzer.cjs +360 -0
- package/bin/lib/codebase-analyzer.cjs +241 -0
- package/bin/lib/commands.cjs +691 -0
- package/bin/lib/config.cjs +236 -0
- package/bin/lib/constraint-extractor.cjs +526 -0
- package/bin/lib/content-scanner.cjs +238 -0
- package/bin/lib/context-cache.cjs +154 -0
- package/bin/lib/context-compressor.cjs +102 -0
- package/bin/lib/context-deduplicator.cjs +105 -0
- package/bin/lib/context-errors.cjs +78 -0
- package/bin/lib/context-manager.cjs +338 -0
- package/bin/lib/context-metadata-tracker.cjs +140 -0
- package/bin/lib/context-relevance-scorer.cjs +99 -0
- package/bin/lib/core.cjs +507 -0
- package/bin/lib/cost-alerts.cjs +174 -0
- package/bin/lib/cost-tracker.cjs +275 -0
- package/bin/lib/crash-recovery.cjs +220 -0
- package/bin/lib/dependency-graph.cjs +319 -0
- package/bin/lib/deploy/deploy-audit-log.cjs +76 -0
- package/bin/lib/deploy/deploy-detector.cjs +69 -0
- package/bin/lib/deploy/deploy-env-manager.cjs +109 -0
- package/bin/lib/deploy/deploy-health-check.cjs +88 -0
- package/bin/lib/deploy/deploy-pre-flight.cjs +57 -0
- package/bin/lib/deploy/deploy-rollback.cjs +72 -0
- package/bin/lib/deploy/deploy-runner.cjs +97 -0
- package/bin/lib/deploy/deploy-status.cjs +74 -0
- package/bin/lib/discussion-synthesizer.cjs +439 -0
- package/bin/lib/error-cache.cjs +114 -0
- package/bin/lib/error-registry.cjs +177 -0
- package/bin/lib/file-access.cjs +207 -0
- package/bin/lib/file-lock.cjs +236 -0
- package/bin/lib/finops/budget-enforcer.cjs +126 -0
- package/bin/lib/finops/cost-reporter.cjs +132 -0
- package/bin/lib/finops/finops-analyzer.cjs +112 -0
- package/bin/lib/finops/spot-manager.cjs +118 -0
- package/bin/lib/framework-detector.cjs +396 -0
- package/bin/lib/frontmatter.cjs +313 -0
- package/bin/lib/fs-utils.cjs +153 -0
- package/bin/lib/gate-executor.cjs +272 -0
- package/bin/lib/gates/README.md +374 -0
- package/bin/lib/gates/gate-01-requirement.cjs +303 -0
- package/bin/lib/gates/gate-02-architecture.cjs +555 -0
- package/bin/lib/gates/gate-03-code.cjs +635 -0
- package/bin/lib/gates/gate-04-security.cjs +829 -0
- package/bin/lib/git-errors.cjs +83 -0
- package/bin/lib/git-utils.cjs +321 -0
- package/bin/lib/git-workflow-engine.cjs +1157 -0
- package/bin/lib/health-check.cjs +227 -0
- package/bin/lib/index.cjs +279 -0
- package/bin/lib/init.cjs +725 -0
- package/bin/lib/lock-logger.cjs +194 -0
- package/bin/lib/lock-state.cjs +263 -0
- package/bin/lib/lockfile-validator.cjs +227 -0
- package/bin/lib/log-rotation.cjs +71 -0
- package/bin/lib/logger.cjs +125 -0
- package/bin/lib/memory-compression.cjs +256 -0
- package/bin/lib/milestone.cjs +247 -0
- package/bin/lib/model-provider.cjs +241 -0
- package/bin/lib/package-manager-detector.cjs +203 -0
- package/bin/lib/package-manager-executor.cjs +385 -0
- package/bin/lib/package-manager-service.cjs +216 -0
- package/bin/lib/perf/api-monitor.cjs +88 -0
- package/bin/lib/perf/db-optimizer.cjs +78 -0
- package/bin/lib/perf/frontend-performance.cjs +56 -0
- package/bin/lib/perf/perf-analyzer.cjs +77 -0
- package/bin/lib/perf/perf-baseline.cjs +102 -0
- package/bin/lib/perf/perf-reporter.cjs +117 -0
- package/bin/lib/perf/regression-detector.cjs +92 -0
- package/bin/lib/phase.cjs +963 -0
- package/bin/lib/planning-write.cjs +123 -0
- package/bin/lib/project-reporter.cjs +565 -0
- package/bin/lib/quality-gate.cjs +332 -0
- package/bin/lib/quality-metrics.cjs +324 -0
- package/bin/lib/recovery-manager.cjs +98 -0
- package/bin/lib/release-validator.cjs +617 -0
- package/bin/lib/retry.cjs +119 -0
- package/bin/lib/roadmap.cjs +309 -0
- package/bin/lib/safe-exec.cjs +173 -0
- package/bin/lib/safe-path.cjs +130 -0
- package/bin/lib/security-errors.cjs +62 -0
- package/bin/lib/session-chain.cjs +304 -0
- package/bin/lib/session-errors.cjs +81 -0
- package/bin/lib/session-export.cjs +251 -0
- package/bin/lib/session-import.cjs +262 -0
- package/bin/lib/session-manager.cjs +280 -0
- package/bin/lib/skill-context.cjs +148 -0
- package/bin/lib/skill-matcher.cjs +236 -0
- package/bin/lib/skill-registry.cjs +360 -0
- package/bin/lib/skill-resolver.cjs +449 -0
- package/bin/lib/skill-triggers.cjs +90 -0
- package/bin/lib/skill-validator.cjs +270 -0
- package/bin/lib/skill-versioning.cjs +355 -0
- package/bin/lib/stack-detector.cjs +399 -0
- package/bin/lib/state.cjs +736 -0
- package/bin/lib/tech-debt-analyzer.cjs +309 -0
- package/bin/lib/temp-file.cjs +239 -0
- package/bin/lib/template.cjs +223 -0
- package/bin/lib/test-file-lock.cjs +112 -0
- package/bin/lib/test-graceful.cjs +93 -0
- package/bin/lib/test-logger.cjs +60 -0
- package/bin/lib/test-safe-exec.cjs +38 -0
- package/bin/lib/test-safe-path.cjs +33 -0
- package/bin/lib/test-temp-file.cjs +125 -0
- package/bin/lib/tier-manager.cjs +428 -0
- package/bin/lib/timeout-exec.cjs +63 -0
- package/bin/lib/tradeoff-analyzer.cjs +284 -0
- package/bin/lib/url-fetch.cjs +170 -0
- package/bin/lib/verify.cjs +863 -0
- package/bin/update.js +217 -214
- package/commands/deploy.cjs +53 -0
- package/commands/ez/add-tests.md +41 -41
- package/commands/ez/audit-milestone.md +36 -36
- package/commands/ez/complete-milestone.md +136 -136
- package/commands/ez/discuss-phase.md +90 -90
- package/commands/ez/execute-phase.md +52 -41
- package/commands/ez/help.md +22 -22
- package/commands/ez/map-codebase.md +71 -71
- package/commands/ez/new-milestone.md +44 -44
- package/commands/ez/new-project.md +51 -42
- package/commands/ez/plan-phase.md +53 -45
- package/commands/ez/progress.md +36 -24
- package/commands/ez/quick.md +45 -45
- package/commands/ez/resume-work.md +40 -40
- package/commands/ez/run-phase.md +580 -0
- package/commands/ez/settings.md +36 -36
- package/commands/ez/update.md +37 -37
- package/commands/ez/verify-work.md +402 -38
- package/commands/health-check.cjs +44 -0
- package/commands/rollback.cjs +47 -0
- package/ez-agents/bin/ez-tools.cjs +1692 -716
- package/ez-agents/bin/guards/autonomy-guard.cjs +346 -0
- package/ez-agents/bin/guards/context-budget-guard.cjs +247 -0
- package/ez-agents/bin/guards/hallucination-guard.cjs +271 -0
- package/ez-agents/bin/guards/hidden-state-guard.cjs +182 -0
- package/ez-agents/bin/guards/team-overhead-guard.cjs +266 -0
- package/ez-agents/bin/guards/tool-sprawl-guard.cjs +271 -0
- package/ez-agents/bin/lib/analytics/analytics-collector.cjs +86 -0
- package/ez-agents/bin/lib/analytics/analytics-reporter.cjs +130 -0
- package/ez-agents/bin/lib/analytics/cohort-analyzer.cjs +138 -0
- package/ez-agents/bin/lib/analytics/funnel-analyzer.cjs +147 -0
- package/ez-agents/bin/lib/analytics/nps-tracker.cjs +147 -0
- package/ez-agents/bin/lib/archetype-detector.cjs +289 -0
- package/ez-agents/bin/lib/audit-exec.cjs +166 -167
- package/ez-agents/bin/lib/auth.cjs +176 -176
- package/ez-agents/bin/lib/backup-service.cjs +422 -0
- package/ez-agents/bin/lib/bdd-validator.cjs +622 -0
- package/ez-agents/bin/lib/business-flow-mapper.cjs +429 -0
- package/ez-agents/bin/lib/code-complexity-analyzer.cjs +360 -0
- package/ez-agents/bin/lib/codebase-analyzer.cjs +241 -0
- package/ez-agents/bin/lib/commands.cjs +685 -685
- package/ez-agents/bin/lib/config.cjs +41 -1
- package/ez-agents/bin/lib/constraint-extractor.cjs +526 -0
- package/ez-agents/bin/lib/content-scanner.cjs +238 -0
- package/ez-agents/bin/lib/context-cache.cjs +154 -0
- package/ez-agents/bin/lib/context-errors.cjs +71 -0
- package/ez-agents/bin/lib/context-manager.cjs +220 -0
- package/ez-agents/bin/lib/core.cjs +507 -512
- package/ez-agents/bin/lib/cost-tracker.cjs +243 -0
- package/ez-agents/bin/lib/crash-recovery.cjs +172 -0
- package/ez-agents/bin/lib/dependency-graph.cjs +319 -0
- package/ez-agents/bin/lib/deploy/deploy-audit-log.cjs +76 -0
- package/ez-agents/bin/lib/deploy/deploy-detector.cjs +69 -0
- package/ez-agents/bin/lib/deploy/deploy-env-manager.cjs +109 -0
- package/ez-agents/bin/lib/deploy/deploy-health-check.cjs +88 -0
- package/ez-agents/bin/lib/deploy/deploy-pre-flight.cjs +57 -0
- package/ez-agents/bin/lib/deploy/deploy-rollback.cjs +72 -0
- package/ez-agents/bin/lib/deploy/deploy-runner.cjs +97 -0
- package/ez-agents/bin/lib/deploy/deploy-status.cjs +74 -0
- package/ez-agents/bin/lib/discussion-synthesizer.cjs +458 -0
- package/ez-agents/bin/lib/file-access.cjs +207 -0
- package/ez-agents/bin/lib/finops/budget-enforcer.cjs +126 -0
- package/ez-agents/bin/lib/finops/cost-reporter.cjs +132 -0
- package/ez-agents/bin/lib/finops/finops-analyzer.cjs +112 -0
- package/ez-agents/bin/lib/finops/spot-manager.cjs +118 -0
- package/ez-agents/bin/lib/framework-detector.cjs +396 -0
- package/ez-agents/bin/lib/frontmatter.cjs +3 -1
- package/ez-agents/bin/lib/gates/README.md +374 -0
- package/ez-agents/bin/lib/gates/gate-01-requirement.cjs +303 -0
- package/ez-agents/bin/lib/gates/gate-02-architecture.cjs +555 -0
- package/ez-agents/bin/lib/gates/gate-03-code.cjs +635 -0
- package/ez-agents/bin/lib/gates/gate-04-security.cjs +829 -0
- package/ez-agents/bin/lib/git-errors.cjs +83 -0
- package/ez-agents/bin/lib/git-utils.cjs +118 -0
- package/ez-agents/bin/lib/git-workflow-engine.cjs +1157 -0
- package/ez-agents/bin/lib/health-check.cjs +162 -162
- package/ez-agents/bin/lib/index.cjs +40 -2
- package/ez-agents/bin/lib/init.cjs +0 -2
- package/ez-agents/bin/lib/lockfile-validator.cjs +227 -0
- package/ez-agents/bin/lib/log-rotation.cjs +71 -0
- package/ez-agents/bin/lib/logger.cjs +99 -154
- package/ez-agents/bin/lib/memory-compression.cjs +256 -0
- package/ez-agents/bin/lib/package-manager-detector.cjs +203 -0
- package/ez-agents/bin/lib/package-manager-executor.cjs +385 -0
- package/ez-agents/bin/lib/package-manager-service.cjs +216 -0
- package/ez-agents/bin/lib/perf/api-monitor.cjs +88 -0
- package/ez-agents/bin/lib/perf/db-optimizer.cjs +78 -0
- package/ez-agents/bin/lib/perf/frontend-performance.cjs +56 -0
- package/ez-agents/bin/lib/perf/perf-analyzer.cjs +77 -0
- package/ez-agents/bin/lib/perf/perf-baseline.cjs +102 -0
- package/ez-agents/bin/lib/perf/perf-reporter.cjs +117 -0
- package/ez-agents/bin/lib/perf/regression-detector.cjs +92 -0
- package/ez-agents/bin/lib/project-reporter.cjs +502 -0
- package/ez-agents/bin/lib/quality-gate.cjs +332 -0
- package/ez-agents/bin/lib/recovery-manager.cjs +98 -0
- package/ez-agents/bin/lib/release-validator.cjs +617 -0
- package/ez-agents/bin/lib/safe-exec.cjs +128 -214
- package/ez-agents/bin/lib/security-errors.cjs +62 -0
- package/ez-agents/bin/lib/session-chain.cjs +304 -0
- package/ez-agents/bin/lib/session-errors.cjs +81 -0
- package/ez-agents/bin/lib/session-export.cjs +251 -0
- package/ez-agents/bin/lib/session-import.cjs +262 -0
- package/ez-agents/bin/lib/session-manager.cjs +280 -0
- package/ez-agents/bin/lib/skill-context.cjs +148 -0
- package/ez-agents/bin/lib/skill-matcher.cjs +236 -0
- package/ez-agents/bin/lib/skill-registry.cjs +341 -0
- package/ez-agents/bin/lib/skill-resolver.cjs +449 -0
- package/ez-agents/bin/lib/skill-triggers.cjs +90 -0
- package/ez-agents/bin/lib/skill-validator.cjs +270 -0
- package/ez-agents/bin/lib/skill-versioning.cjs +355 -0
- package/ez-agents/bin/lib/stack-detector.cjs +399 -0
- package/ez-agents/bin/lib/tech-debt-analyzer.cjs +309 -0
- package/ez-agents/bin/lib/tier-manager.cjs +428 -0
- package/ez-agents/bin/lib/tradeoff-analyzer.cjs +284 -0
- package/ez-agents/bin/lib/url-fetch.cjs +170 -0
- package/ez-agents/bin/lib/verify.cjs +863 -863
- package/ez-agents/references/decimal-phase-calculation.md +65 -65
- package/ez-agents/references/git-integration.md +248 -248
- package/ez-agents/references/git-planning-commit.md +38 -38
- package/ez-agents/references/metrics-schema.md +118 -0
- package/ez-agents/references/model-profile-resolution.md +34 -34
- package/ez-agents/references/model-profiles.md +93 -93
- package/ez-agents/references/phase-argument-parsing.md +61 -61
- package/ez-agents/references/planning-config.md +340 -200
- package/ez-agents/references/tier-strategy.md +103 -0
- package/ez-agents/references/ui-brand.md +160 -160
- package/ez-agents/references/verification-patterns.md +612 -612
- package/ez-agents/templates/DEBUG.md +164 -164
- package/ez-agents/templates/UAT.md +247 -247
- package/ez-agents/templates/agent-output-format.md +404 -0
- package/ez-agents/templates/bdd-feature.md +173 -0
- package/ez-agents/templates/codebase/architecture.md +255 -255
- package/ez-agents/templates/codebase/structure.md +285 -285
- package/ez-agents/templates/copilot-instructions.md +7 -7
- package/ez-agents/templates/debug-subagent-prompt.md +91 -91
- package/ez-agents/templates/discovery.md +146 -146
- package/ez-agents/templates/discussion.md +68 -0
- package/ez-agents/templates/handoff-protocol.md +294 -0
- package/ez-agents/templates/incident-runbook.md +205 -0
- package/ez-agents/templates/mode-workflow-templates.md +301 -0
- package/ez-agents/templates/phase-prompt.md +610 -610
- package/ez-agents/templates/planner-subagent-prompt.md +117 -117
- package/ez-agents/templates/project.md +184 -184
- package/ez-agents/templates/release-checklist.md +136 -0
- package/ez-agents/templates/research.md +552 -552
- package/ez-agents/templates/rollback-plan.md +201 -0
- package/ez-agents/templates/security-user-setup.md +244 -0
- package/ez-agents/templates/skill-validation-rules.md +476 -0
- package/ez-agents/templates/state.md +180 -176
- package/ez-agents/templates/summary-complex.md +59 -59
- package/ez-agents/tests/gates/gate-01-02.test.cjs +812 -0
- package/ez-agents/tests/gates/gate-03-04.test.cjs +762 -0
- package/ez-agents/tests/gates/gate-05-validator.test.cjs +145 -0
- package/ez-agents/tests/gates/gate-06-docs-validator.test.cjs +244 -0
- package/ez-agents/tests/gates/gate-07-release-validator.test.cjs +219 -0
- package/ez-agents/tests/guards/context-budget-guard.test.cjs +145 -0
- package/ez-agents/tests/guards/edge-case-guards.test.cjs +238 -0
- package/ez-agents/tests/guards/hallucination-guard.test.cjs +124 -0
- package/ez-agents/workflows/audit-milestone.md +1 -1
- package/ez-agents/workflows/autonomous.md +131 -30
- package/ez-agents/workflows/complete-milestone.md +1 -1
- package/ez-agents/workflows/discuss-phase.md +1 -1
- package/ez-agents/workflows/execute-phase.md +169 -3
- package/ez-agents/workflows/help.md +86 -133
- package/ez-agents/workflows/hotfix.md +291 -0
- package/ez-agents/workflows/new-milestone.md +340 -11
- package/ez-agents/workflows/new-project.md +294 -318
- package/ez-agents/workflows/plan-phase.md +22 -40
- package/ez-agents/workflows/progress.md +15 -25
- package/ez-agents/workflows/release.md +253 -0
- package/ez-agents/workflows/resume-session.md +215 -0
- package/ez-agents/workflows/run-phase.md +531 -0
- package/ez-agents/workflows/settings.md +2 -35
- package/hooks/dist/ez-check-update.js +81 -81
- package/hooks/dist/ez-context-monitor.js +148 -141
- package/hooks/dist/ez-statusline.js +115 -115
- package/package.json +78 -64
- package/scripts/fix-qwen-installation.js +144 -144
- package/agents/ez-integration-checker.md +0 -443
- package/agents/ez-nyquist-auditor.md +0 -176
- package/agents/ez-plan-checker.md +0 -706
- package/agents/ez-research-synthesizer.md +0 -247
- package/agents/ez-ui-auditor.md +0 -439
- package/agents/ez-ui-checker.md +0 -300
- package/agents/ez-ui-researcher.md +0 -353
- package/commands/ez/add-phase.md +0 -43
- package/commands/ez/add-todo.md +0 -47
- package/commands/ez/auth.md +0 -87
- package/commands/ez/autonomous.md +0 -41
- package/commands/ez/check-todos.md +0 -45
- package/commands/ez/cleanup.md +0 -18
- package/commands/ez/debug.md +0 -168
- package/commands/ez/health.md +0 -22
- package/commands/ez/insert-phase.md +0 -32
- package/commands/ez/join-discord.md +0 -18
- package/commands/ez/list-phase-assumptions.md +0 -46
- package/commands/ez/pause-work.md +0 -38
- package/commands/ez/plan-milestone-gaps.md +0 -34
- package/commands/ez/reapply-patches.md +0 -124
- package/commands/ez/remove-phase.md +0 -31
- package/commands/ez/research-phase.md +0 -190
- package/commands/ez/set-profile.md +0 -34
- package/commands/ez/stats.md +0 -18
- package/commands/ez/ui-phase.md +0 -34
- package/commands/ez/ui-review.md +0 -32
- package/commands/ez/validate-phase.md +0 -35
- package/ez-agents/templates/UI-SPEC.md +0 -100
- package/ez-agents/templates/VALIDATION.md +0 -76
- package/ez-agents/templates/context.md +0 -352
- package/ez-agents/templates/verification-report.md +0 -322
- package/ez-agents/workflows/research-phase.md +0 -74
- package/ez-agents/workflows/ui-phase.md +0 -290
- package/ez-agents/workflows/ui-review.md +0 -157
- package/ez-agents/workflows/validate-phase.md +0 -167
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* EZ Cost Tracker — Token usage and USD cost recording with budget enforcement
|
|
4
|
+
* Persists entries to .planning/metrics.json using file-lock for concurrent safety.
|
|
5
|
+
* Usage:
|
|
6
|
+
* const CostTracker = require('./cost-tracker.cjs');
|
|
7
|
+
* const ct = new CostTracker(cwd);
|
|
8
|
+
* await ct.record({ phase: 30, provider: 'claude', model: 'claude-sonnet-4-6', input_tokens: 1000, output_tokens: 500 });
|
|
9
|
+
* const report = ct.aggregate();
|
|
10
|
+
* const budget = ct.checkBudget();
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { withLock } = require('./file-lock.cjs');
|
|
16
|
+
const Logger = require('./logger.cjs');
|
|
17
|
+
const logger = new Logger();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Returns default cost configuration with model rates.
|
|
21
|
+
*/
|
|
22
|
+
function defaultCostConfig() {
|
|
23
|
+
return {
|
|
24
|
+
enabled: true,
|
|
25
|
+
budget: null,
|
|
26
|
+
warning_threshold: 80,
|
|
27
|
+
auto_pause: false,
|
|
28
|
+
rates: {
|
|
29
|
+
'claude-3': { input: 0.003, output: 0.015 },
|
|
30
|
+
'claude-sonnet-4-6': { input: 0.003, output: 0.015 },
|
|
31
|
+
'gpt-4': { input: 0.03, output: 0.06 },
|
|
32
|
+
'qwen': { input: 0.002, output: 0.006 },
|
|
33
|
+
'kimi': { input: 0.002, output: 0.006 },
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Read cost_tracking section from .planning/config.json.
|
|
40
|
+
* Falls back to defaultCostConfig() when absent or unreadable.
|
|
41
|
+
* @param {string} cwd
|
|
42
|
+
* @returns {object}
|
|
43
|
+
*/
|
|
44
|
+
function readCostConfig(cwd) {
|
|
45
|
+
const configPath = path.join(cwd, '.planning', 'config.json');
|
|
46
|
+
if (!fs.existsSync(configPath)) return defaultCostConfig();
|
|
47
|
+
try {
|
|
48
|
+
const raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
49
|
+
return Object.assign(defaultCostConfig(), raw.cost_tracking || {});
|
|
50
|
+
} catch (e) {
|
|
51
|
+
return defaultCostConfig();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class CostTracker {
|
|
56
|
+
/**
|
|
57
|
+
* @param {string} [cwd] - Working directory (defaults to process.cwd())
|
|
58
|
+
*/
|
|
59
|
+
constructor(cwd) {
|
|
60
|
+
this.cwd = cwd || process.cwd();
|
|
61
|
+
this.metricsPath = path.join(this.cwd, '.planning', 'metrics.json');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Returns the cost_tracking config merged with defaults.
|
|
66
|
+
* @returns {object}
|
|
67
|
+
*/
|
|
68
|
+
getConfig() {
|
|
69
|
+
return readCostConfig(this.cwd);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Record a cost entry to metrics.json atomically (via file-lock).
|
|
74
|
+
* If cost_usd is not supplied, it is computed from token counts and model rates.
|
|
75
|
+
* @param {object} entry
|
|
76
|
+
* @returns {Promise<void>}
|
|
77
|
+
*/
|
|
78
|
+
async record(entry) {
|
|
79
|
+
// Ensure .planning directory exists before locking
|
|
80
|
+
const planningDir = path.join(this.cwd, '.planning');
|
|
81
|
+
if (!fs.existsSync(planningDir)) {
|
|
82
|
+
fs.mkdirSync(planningDir, { recursive: true });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
await withLock(this.metricsPath, async () => {
|
|
86
|
+
// Read existing data or initialise empty structure
|
|
87
|
+
let data = { version: '1.0', entries: [] };
|
|
88
|
+
if (fs.existsSync(this.metricsPath)) {
|
|
89
|
+
try {
|
|
90
|
+
data = JSON.parse(fs.readFileSync(this.metricsPath, 'utf8'));
|
|
91
|
+
if (!Array.isArray(data.entries)) data.entries = [];
|
|
92
|
+
} catch (e) {
|
|
93
|
+
logger.warn('cost-tracker: failed to parse metrics.json, reinitialising', { error: e.message });
|
|
94
|
+
data = { version: '1.0', entries: [] };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Compute cost_usd if not provided
|
|
99
|
+
let cost_usd = entry.cost_usd;
|
|
100
|
+
if (cost_usd === undefined || cost_usd === null) {
|
|
101
|
+
const cfg = readCostConfig(this.cwd);
|
|
102
|
+
const rates = cfg.rates || {};
|
|
103
|
+
const modelKey = entry.model;
|
|
104
|
+
const providerKey = entry.provider;
|
|
105
|
+
const rate = rates[modelKey] || rates[providerKey] || null;
|
|
106
|
+
if (rate) {
|
|
107
|
+
const inputTokens = entry.input_tokens || 0;
|
|
108
|
+
const outputTokens = entry.output_tokens || 0;
|
|
109
|
+
cost_usd = (inputTokens * rate.input + outputTokens * rate.output) / 1000;
|
|
110
|
+
} else {
|
|
111
|
+
cost_usd = 0;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const fullEntry = Object.assign({}, entry, {
|
|
116
|
+
timestamp: new Date().toISOString(),
|
|
117
|
+
cost_usd,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
data.entries.push(fullEntry);
|
|
121
|
+
fs.writeFileSync(this.metricsPath, JSON.stringify(data, null, 2), 'utf8');
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Aggregate cost entries, optionally filtered.
|
|
127
|
+
* @param {object} [filter] - Optional { phase?, milestone?, provider? }
|
|
128
|
+
* @returns {{ total: { cost: number, tokens: number }, by_phase: object, by_provider: object }}
|
|
129
|
+
*/
|
|
130
|
+
aggregate(filter = {}) {
|
|
131
|
+
const zero = () => ({ total: { cost: 0, tokens: 0 }, by_phase: {}, by_provider: {} });
|
|
132
|
+
if (!fs.existsSync(this.metricsPath)) return zero();
|
|
133
|
+
|
|
134
|
+
let data;
|
|
135
|
+
try {
|
|
136
|
+
data = JSON.parse(fs.readFileSync(this.metricsPath, 'utf8'));
|
|
137
|
+
} catch (e) {
|
|
138
|
+
return zero();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let entries = data.entries || [];
|
|
142
|
+
|
|
143
|
+
if (filter.phase !== undefined) entries = entries.filter(e => e.phase == filter.phase);
|
|
144
|
+
if (filter.milestone) entries = entries.filter(e => e.milestone === filter.milestone);
|
|
145
|
+
if (filter.provider) entries = entries.filter(e => e.provider === filter.provider);
|
|
146
|
+
|
|
147
|
+
return entries.reduce((acc, e) => {
|
|
148
|
+
const phaseKey = String(e.phase || 'unknown');
|
|
149
|
+
if (!acc.by_phase[phaseKey]) acc.by_phase[phaseKey] = { cost: 0, tokens: 0 };
|
|
150
|
+
acc.by_phase[phaseKey].cost += e.cost_usd || 0;
|
|
151
|
+
acc.by_phase[phaseKey].tokens += (e.input_tokens || 0) + (e.output_tokens || 0);
|
|
152
|
+
|
|
153
|
+
const provKey = e.provider || 'unknown';
|
|
154
|
+
if (!acc.by_provider[provKey]) acc.by_provider[provKey] = { cost: 0 };
|
|
155
|
+
acc.by_provider[provKey].cost += e.cost_usd || 0;
|
|
156
|
+
|
|
157
|
+
acc.total.cost += e.cost_usd || 0;
|
|
158
|
+
acc.total.tokens += (e.input_tokens || 0) + (e.output_tokens || 0);
|
|
159
|
+
|
|
160
|
+
return acc;
|
|
161
|
+
}, zero());
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Check total spending against a budget ceiling.
|
|
166
|
+
* Does NOT call process.exit() — caller decides how to react.
|
|
167
|
+
* @param {object} [opts] - { ceiling?, warning_threshold? }
|
|
168
|
+
* @returns {{ status: 'ok'|'warning'|'exceeded', message: string, total?: number, ceiling?: number, percentUsed?: number }}
|
|
169
|
+
*/
|
|
170
|
+
checkBudget(opts = {}) {
|
|
171
|
+
const cfg = this.getConfig();
|
|
172
|
+
const ceiling = (opts.ceiling !== undefined) ? opts.ceiling : cfg.budget;
|
|
173
|
+
const warning_threshold = (opts.warning_threshold !== undefined) ? opts.warning_threshold : cfg.warning_threshold;
|
|
174
|
+
|
|
175
|
+
const agg = this.aggregate();
|
|
176
|
+
const total = agg.total.cost;
|
|
177
|
+
|
|
178
|
+
if (ceiling === null || ceiling === undefined || typeof ceiling !== 'number') {
|
|
179
|
+
return { status: 'ok', message: 'No budget set' };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (total >= ceiling) {
|
|
183
|
+
return {
|
|
184
|
+
status: 'exceeded',
|
|
185
|
+
message: `Budget ceiling $${ceiling} exceeded ($${total.toFixed(4)} spent)`,
|
|
186
|
+
total,
|
|
187
|
+
ceiling,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const percentUsed = (total / ceiling) * 100;
|
|
192
|
+
if (percentUsed >= warning_threshold) {
|
|
193
|
+
return {
|
|
194
|
+
status: 'warning',
|
|
195
|
+
message: `${percentUsed.toFixed(1)}% of budget used`,
|
|
196
|
+
total,
|
|
197
|
+
ceiling,
|
|
198
|
+
percentUsed,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
status: 'ok',
|
|
204
|
+
message: 'Within budget',
|
|
205
|
+
total,
|
|
206
|
+
ceiling,
|
|
207
|
+
percentUsed,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Persist a budget ceiling (and optional warning threshold) to .planning/config.json.
|
|
213
|
+
* @param {number} ceiling
|
|
214
|
+
* @param {number} [warningThreshold]
|
|
215
|
+
*/
|
|
216
|
+
setBudget(ceiling, warningThreshold) {
|
|
217
|
+
const configPath = path.join(this.cwd, '.planning', 'config.json');
|
|
218
|
+
const planningDir = path.join(this.cwd, '.planning');
|
|
219
|
+
|
|
220
|
+
if (!fs.existsSync(planningDir)) {
|
|
221
|
+
fs.mkdirSync(planningDir, { recursive: true });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let config = {};
|
|
225
|
+
if (fs.existsSync(configPath)) {
|
|
226
|
+
try {
|
|
227
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
228
|
+
} catch (e) {
|
|
229
|
+
config = {};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!config.cost_tracking) config.cost_tracking = {};
|
|
234
|
+
config.cost_tracking.budget = ceiling;
|
|
235
|
+
if (warningThreshold !== undefined) {
|
|
236
|
+
config.cost_tracking.warning_threshold = warningThreshold;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
module.exports = CostTracker;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* EZ Crash Recovery — PID-stamped lock file management
|
|
5
|
+
* Detects orphaned operations and enables safe concurrent execution gates.
|
|
6
|
+
* Usage:
|
|
7
|
+
* const CrashRecovery = require('./crash-recovery.cjs');
|
|
8
|
+
* const cr = new CrashRecovery(cwd);
|
|
9
|
+
* cr.acquire('phase-30-plan');
|
|
10
|
+
* // ... later ...
|
|
11
|
+
* cr.release('phase-30-plan');
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const Logger = require('./logger.cjs');
|
|
17
|
+
const logger = new Logger();
|
|
18
|
+
|
|
19
|
+
const HEARTBEAT_INTERVAL_MS = 10000;
|
|
20
|
+
const STALE_HEARTBEAT_MS = 60000;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if a process is alive by sending signal 0.
|
|
24
|
+
* @param {number} pid
|
|
25
|
+
* @returns {boolean}
|
|
26
|
+
*/
|
|
27
|
+
function isProcessAlive(pid) {
|
|
28
|
+
try {
|
|
29
|
+
process.kill(Number(pid), 0);
|
|
30
|
+
return true;
|
|
31
|
+
} catch (e) {
|
|
32
|
+
if (e.code === 'ESRCH') return false; // No such process
|
|
33
|
+
if (e.code === 'EPERM') return true; // Process exists, no permission
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
class CrashRecovery {
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} cwd - Root directory containing .planning/
|
|
41
|
+
*/
|
|
42
|
+
constructor(cwd) {
|
|
43
|
+
this.cwd = cwd || process.cwd();
|
|
44
|
+
this.locksDir = path.join(this.cwd, '.planning', 'locks');
|
|
45
|
+
this._intervals = {};
|
|
46
|
+
this._exitHandlers = {};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Sanitize an operation name to be a safe file name component.
|
|
51
|
+
* @param {string} operation
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
slugifyOperation(operation) {
|
|
55
|
+
return String(operation).replace(/[^a-zA-Z0-9-_]/g, '_');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Return the full path to the lock file for an operation.
|
|
60
|
+
* @param {string} operation
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
63
|
+
getLockPath(operation) {
|
|
64
|
+
return path.join(this.locksDir, this.slugifyOperation(operation) + '.lock.json');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Acquire a lock for the given operation.
|
|
69
|
+
* Creates a PID-stamped JSON lock file and starts a heartbeat interval.
|
|
70
|
+
* @param {string} operation
|
|
71
|
+
*/
|
|
72
|
+
acquire(operation) {
|
|
73
|
+
if (!fs.existsSync(this.locksDir)) {
|
|
74
|
+
fs.mkdirSync(this.locksDir, { recursive: true });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const lockPath = this.getLockPath(operation);
|
|
78
|
+
const now = new Date().toISOString();
|
|
79
|
+
const data = { pid: process.pid, started: now, heartbeat: now, operation };
|
|
80
|
+
fs.writeFileSync(lockPath, JSON.stringify(data, null, 2), 'utf8');
|
|
81
|
+
logger.debug('Lock acquired: ' + operation);
|
|
82
|
+
|
|
83
|
+
// Heartbeat interval — keeps heartbeat timestamp fresh every 10s
|
|
84
|
+
const intervalId = setInterval(() => {
|
|
85
|
+
try {
|
|
86
|
+
if (fs.existsSync(lockPath)) {
|
|
87
|
+
const current = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
|
88
|
+
current.heartbeat = new Date().toISOString();
|
|
89
|
+
fs.writeFileSync(lockPath, JSON.stringify(current, null, 2), 'utf8');
|
|
90
|
+
}
|
|
91
|
+
} catch (e) {
|
|
92
|
+
// Ignore heartbeat write errors (process may be exiting)
|
|
93
|
+
}
|
|
94
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
95
|
+
|
|
96
|
+
// Allow process to exit naturally even with active interval
|
|
97
|
+
if (intervalId.unref) intervalId.unref();
|
|
98
|
+
this._intervals[operation] = intervalId;
|
|
99
|
+
|
|
100
|
+
// Release on process exit to avoid orphaned lock files
|
|
101
|
+
const exitHandler = () => this.release(operation);
|
|
102
|
+
this._exitHandlers[operation] = exitHandler;
|
|
103
|
+
process.on('exit', exitHandler);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Check whether a lock is orphaned (process is dead or heartbeat is stale).
|
|
108
|
+
* @param {string} operation
|
|
109
|
+
* @returns {boolean}
|
|
110
|
+
*/
|
|
111
|
+
isOrphan(operation) {
|
|
112
|
+
const lockPath = this.getLockPath(operation);
|
|
113
|
+
if (!fs.existsSync(lockPath)) return false;
|
|
114
|
+
|
|
115
|
+
let data;
|
|
116
|
+
try {
|
|
117
|
+
data = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
|
118
|
+
} catch (e) {
|
|
119
|
+
return true; // Corrupt lock file treated as orphan
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!isProcessAlive(data.pid)) return true;
|
|
123
|
+
|
|
124
|
+
// Stale heartbeat check — lock is orphaned if heartbeat is too old
|
|
125
|
+
const heartbeatAge = Date.now() - new Date(data.heartbeat).getTime();
|
|
126
|
+
if (heartbeatAge > STALE_HEARTBEAT_MS) return true;
|
|
127
|
+
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Release a lock for the given operation.
|
|
133
|
+
* Clears the heartbeat interval and removes the lock file.
|
|
134
|
+
* @param {string} operation
|
|
135
|
+
*/
|
|
136
|
+
release(operation) {
|
|
137
|
+
if (this._intervals[operation]) {
|
|
138
|
+
clearInterval(this._intervals[operation]);
|
|
139
|
+
delete this._intervals[operation];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (this._exitHandlers[operation]) {
|
|
143
|
+
process.off('exit', this._exitHandlers[operation]);
|
|
144
|
+
delete this._exitHandlers[operation];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const lockPath = this.getLockPath(operation);
|
|
148
|
+
if (fs.existsSync(lockPath)) {
|
|
149
|
+
try {
|
|
150
|
+
fs.unlinkSync(lockPath);
|
|
151
|
+
} catch (e) {
|
|
152
|
+
// Ignore unlink errors (file may already be removed)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
logger.debug('Lock released: ' + operation);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Return a list of operation slugs whose lock files are orphaned.
|
|
161
|
+
* @returns {string[]}
|
|
162
|
+
*/
|
|
163
|
+
listOrphans() {
|
|
164
|
+
if (!fs.existsSync(this.locksDir)) return [];
|
|
165
|
+
return fs.readdirSync(this.locksDir)
|
|
166
|
+
.filter(f => f.endsWith('.lock.json'))
|
|
167
|
+
.map(f => f.replace(/\.lock\.json$/, ''))
|
|
168
|
+
.filter(op => this.isOrphan(op));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
module.exports = CrashRecovery;
|