@nathapp/nax 0.50.3 → 0.51.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/CHANGELOG.md +30 -0
- package/dist/nax.js +393 -197
- package/package.json +1 -3
- package/bin/nax.ts +0 -1195
- package/src/acceptance/fix-generator.ts +0 -322
- package/src/acceptance/generator.ts +0 -415
- package/src/acceptance/index.ts +0 -42
- package/src/acceptance/refinement.ts +0 -224
- package/src/acceptance/templates/cli.ts +0 -47
- package/src/acceptance/templates/component.ts +0 -78
- package/src/acceptance/templates/e2e.ts +0 -43
- package/src/acceptance/templates/index.ts +0 -21
- package/src/acceptance/templates/snapshot.ts +0 -50
- package/src/acceptance/templates/unit.ts +0 -48
- package/src/acceptance/types.ts +0 -138
- package/src/agents/acp/adapter.ts +0 -888
- package/src/agents/acp/cost.ts +0 -9
- package/src/agents/acp/index.ts +0 -7
- package/src/agents/acp/interaction-bridge.ts +0 -126
- package/src/agents/acp/parser.ts +0 -119
- package/src/agents/acp/spawn-client.ts +0 -373
- package/src/agents/acp/types.ts +0 -22
- package/src/agents/aider/adapter.ts +0 -135
- package/src/agents/claude/adapter.ts +0 -258
- package/src/agents/claude/complete.ts +0 -80
- package/src/agents/claude/cost.ts +0 -16
- package/src/agents/claude/execution.ts +0 -215
- package/src/agents/claude/index.ts +0 -3
- package/src/agents/claude/interactive.ts +0 -77
- package/src/agents/claude/plan.ts +0 -179
- package/src/agents/codex/adapter.ts +0 -153
- package/src/agents/cost/calculate.ts +0 -154
- package/src/agents/cost/index.ts +0 -10
- package/src/agents/cost/parse.ts +0 -97
- package/src/agents/cost/pricing.ts +0 -59
- package/src/agents/cost/types.ts +0 -45
- package/src/agents/gemini/adapter.ts +0 -177
- package/src/agents/index.ts +0 -18
- package/src/agents/opencode/adapter.ts +0 -106
- package/src/agents/registry.ts +0 -136
- package/src/agents/shared/decompose.ts +0 -154
- package/src/agents/shared/model-resolution.ts +0 -43
- package/src/agents/shared/types-extended.ts +0 -164
- package/src/agents/shared/validation.ts +0 -69
- package/src/agents/shared/version-detection.ts +0 -109
- package/src/agents/types.ts +0 -205
- package/src/analyze/classifier.ts +0 -282
- package/src/analyze/index.ts +0 -16
- package/src/analyze/scanner.ts +0 -171
- package/src/analyze/types.ts +0 -51
- package/src/cli/accept.ts +0 -108
- package/src/cli/agents.ts +0 -87
- package/src/cli/analyze-parser.ts +0 -291
- package/src/cli/analyze.ts +0 -352
- package/src/cli/config-descriptions.ts +0 -219
- package/src/cli/config-diff.ts +0 -103
- package/src/cli/config-display.ts +0 -285
- package/src/cli/config-get.ts +0 -55
- package/src/cli/config.ts +0 -14
- package/src/cli/constitution.ts +0 -17
- package/src/cli/diagnose-analysis.ts +0 -159
- package/src/cli/diagnose-formatter.ts +0 -87
- package/src/cli/diagnose.ts +0 -203
- package/src/cli/generate.ts +0 -250
- package/src/cli/index.ts +0 -42
- package/src/cli/init-context.ts +0 -405
- package/src/cli/init-detect.ts +0 -303
- package/src/cli/init.ts +0 -296
- package/src/cli/interact.ts +0 -295
- package/src/cli/plan.ts +0 -509
- package/src/cli/plugins.ts +0 -122
- package/src/cli/prompts-export.ts +0 -58
- package/src/cli/prompts-init.ts +0 -200
- package/src/cli/prompts-main.ts +0 -183
- package/src/cli/prompts-shared.ts +0 -70
- package/src/cli/prompts-tdd.ts +0 -88
- package/src/cli/prompts.ts +0 -17
- package/src/cli/runs.ts +0 -174
- package/src/cli/status-cost.ts +0 -151
- package/src/cli/status-features.ts +0 -405
- package/src/cli/status.ts +0 -13
- package/src/commands/common.ts +0 -171
- package/src/commands/diagnose.ts +0 -17
- package/src/commands/index.ts +0 -9
- package/src/commands/logs-formatter.ts +0 -201
- package/src/commands/logs-reader.ts +0 -171
- package/src/commands/logs.ts +0 -103
- package/src/commands/precheck.ts +0 -86
- package/src/commands/runs.ts +0 -220
- package/src/commands/unlock.ts +0 -96
- package/src/config/defaults.ts +0 -218
- package/src/config/index.ts +0 -22
- package/src/config/loader.ts +0 -143
- package/src/config/merge.ts +0 -106
- package/src/config/merger.ts +0 -147
- package/src/config/path-security.ts +0 -121
- package/src/config/paths.ts +0 -27
- package/src/config/permissions.ts +0 -63
- package/src/config/runtime-types.ts +0 -522
- package/src/config/schema-types.ts +0 -53
- package/src/config/schema.ts +0 -60
- package/src/config/schemas.ts +0 -426
- package/src/config/test-strategy.ts +0 -71
- package/src/config/types.ts +0 -57
- package/src/config/validate.ts +0 -103
- package/src/constitution/generator.ts +0 -158
- package/src/constitution/generators/aider.ts +0 -41
- package/src/constitution/generators/claude.ts +0 -35
- package/src/constitution/generators/cursor.ts +0 -36
- package/src/constitution/generators/opencode.ts +0 -38
- package/src/constitution/generators/types.ts +0 -33
- package/src/constitution/generators/windsurf.ts +0 -36
- package/src/constitution/index.ts +0 -11
- package/src/constitution/loader.ts +0 -121
- package/src/constitution/types.ts +0 -31
- package/src/context/auto-detect.ts +0 -228
- package/src/context/builder.ts +0 -299
- package/src/context/elements.ts +0 -122
- package/src/context/formatter.ts +0 -107
- package/src/context/generator.ts +0 -343
- package/src/context/generators/aider.ts +0 -34
- package/src/context/generators/claude.ts +0 -28
- package/src/context/generators/codex.ts +0 -28
- package/src/context/generators/cursor.ts +0 -28
- package/src/context/generators/gemini.ts +0 -28
- package/src/context/generators/opencode.ts +0 -30
- package/src/context/generators/windsurf.ts +0 -28
- package/src/context/greenfield.ts +0 -114
- package/src/context/index.ts +0 -34
- package/src/context/injector.ts +0 -279
- package/src/context/parent-context.ts +0 -39
- package/src/context/test-scanner.ts +0 -370
- package/src/context/types.ts +0 -98
- package/src/decompose/apply.ts +0 -50
- package/src/decompose/builder.ts +0 -181
- package/src/decompose/index.ts +0 -8
- package/src/decompose/sections/codebase.ts +0 -26
- package/src/decompose/sections/constraints.ts +0 -32
- package/src/decompose/sections/index.ts +0 -4
- package/src/decompose/sections/sibling-stories.ts +0 -25
- package/src/decompose/sections/target-story.ts +0 -31
- package/src/decompose/types.ts +0 -55
- package/src/decompose/validators/complexity.ts +0 -45
- package/src/decompose/validators/coverage.ts +0 -134
- package/src/decompose/validators/dependency.ts +0 -91
- package/src/decompose/validators/index.ts +0 -35
- package/src/decompose/validators/overlap.ts +0 -128
- package/src/errors.ts +0 -67
- package/src/execution/batching.ts +0 -157
- package/src/execution/crash-heartbeat.ts +0 -77
- package/src/execution/crash-recovery.ts +0 -79
- package/src/execution/crash-signals.ts +0 -165
- package/src/execution/crash-writer.ts +0 -154
- package/src/execution/deferred-review.ts +0 -105
- package/src/execution/dry-run.ts +0 -81
- package/src/execution/escalation/escalation.ts +0 -46
- package/src/execution/escalation/index.ts +0 -13
- package/src/execution/escalation/tier-escalation.ts +0 -346
- package/src/execution/escalation/tier-outcome.ts +0 -143
- package/src/execution/executor-types.ts +0 -73
- package/src/execution/helpers.ts +0 -38
- package/src/execution/index.ts +0 -27
- package/src/execution/iteration-runner.ts +0 -160
- package/src/execution/lifecycle/acceptance-loop.ts +0 -309
- package/src/execution/lifecycle/headless-formatter.ts +0 -83
- package/src/execution/lifecycle/index.ts +0 -11
- package/src/execution/lifecycle/parallel-lifecycle.ts +0 -101
- package/src/execution/lifecycle/precheck-runner.ts +0 -140
- package/src/execution/lifecycle/run-cleanup.ts +0 -81
- package/src/execution/lifecycle/run-completion.ts +0 -247
- package/src/execution/lifecycle/run-initialization.ts +0 -187
- package/src/execution/lifecycle/run-regression.ts +0 -305
- package/src/execution/lifecycle/run-setup.ts +0 -240
- package/src/execution/lifecycle/story-size-prompts.ts +0 -123
- package/src/execution/lock.ts +0 -129
- package/src/execution/parallel-coordinator.ts +0 -281
- package/src/execution/parallel-executor-rectification-pass.ts +0 -117
- package/src/execution/parallel-executor-rectify.ts +0 -136
- package/src/execution/parallel-executor.ts +0 -330
- package/src/execution/parallel-worker.ts +0 -149
- package/src/execution/parallel.ts +0 -13
- package/src/execution/pid-registry.ts +0 -275
- package/src/execution/pipeline-result-handler.ts +0 -221
- package/src/execution/progress.ts +0 -27
- package/src/execution/queue-handler.ts +0 -109
- package/src/execution/runner-completion.ts +0 -171
- package/src/execution/runner-execution.ts +0 -243
- package/src/execution/runner-setup.ts +0 -86
- package/src/execution/runner.ts +0 -265
- package/src/execution/sequential-executor.ts +0 -219
- package/src/execution/status-file.ts +0 -264
- package/src/execution/status-writer.ts +0 -181
- package/src/execution/story-context.ts +0 -266
- package/src/execution/story-selector.ts +0 -76
- package/src/execution/test-output-parser.ts +0 -14
- package/src/execution/timeout-handler.ts +0 -100
- package/src/hooks/index.ts +0 -2
- package/src/hooks/runner.ts +0 -280
- package/src/hooks/types.ts +0 -79
- package/src/interaction/chain.ts +0 -170
- package/src/interaction/index.ts +0 -61
- package/src/interaction/init.ts +0 -84
- package/src/interaction/plugins/auto.ts +0 -243
- package/src/interaction/plugins/cli.ts +0 -300
- package/src/interaction/plugins/telegram.ts +0 -384
- package/src/interaction/plugins/webhook.ts +0 -286
- package/src/interaction/state.ts +0 -171
- package/src/interaction/triggers.ts +0 -250
- package/src/interaction/types.ts +0 -170
- package/src/logger/formatters.ts +0 -84
- package/src/logger/index.ts +0 -16
- package/src/logger/logger.ts +0 -296
- package/src/logger/types.ts +0 -48
- package/src/logging/formatter.ts +0 -355
- package/src/logging/index.ts +0 -22
- package/src/logging/types.ts +0 -93
- package/src/metrics/aggregator.ts +0 -191
- package/src/metrics/index.ts +0 -14
- package/src/metrics/tracker.ts +0 -200
- package/src/metrics/types.ts +0 -115
- package/src/optimizer/index.ts +0 -63
- package/src/optimizer/noop.optimizer.ts +0 -24
- package/src/optimizer/rule-based.optimizer.ts +0 -248
- package/src/optimizer/types.ts +0 -53
- package/src/pipeline/event-bus.ts +0 -297
- package/src/pipeline/events.ts +0 -130
- package/src/pipeline/index.ts +0 -19
- package/src/pipeline/runner.ts +0 -149
- package/src/pipeline/stages/acceptance-setup.ts +0 -144
- package/src/pipeline/stages/acceptance.ts +0 -215
- package/src/pipeline/stages/autofix.ts +0 -262
- package/src/pipeline/stages/completion.ts +0 -110
- package/src/pipeline/stages/constitution.ts +0 -63
- package/src/pipeline/stages/context.ts +0 -122
- package/src/pipeline/stages/execution.ts +0 -359
- package/src/pipeline/stages/index.ts +0 -86
- package/src/pipeline/stages/optimizer.ts +0 -74
- package/src/pipeline/stages/prompt.ts +0 -79
- package/src/pipeline/stages/queue-check.ts +0 -103
- package/src/pipeline/stages/rectify.ts +0 -101
- package/src/pipeline/stages/regression.ts +0 -99
- package/src/pipeline/stages/review.ts +0 -94
- package/src/pipeline/stages/routing.ts +0 -276
- package/src/pipeline/stages/verify.ts +0 -286
- package/src/pipeline/subscribers/events-writer.ts +0 -135
- package/src/pipeline/subscribers/hooks.ts +0 -179
- package/src/pipeline/subscribers/interaction.ts +0 -103
- package/src/pipeline/subscribers/registry.ts +0 -73
- package/src/pipeline/subscribers/reporters.ts +0 -174
- package/src/pipeline/types.ts +0 -220
- package/src/plugins/extensions.ts +0 -225
- package/src/plugins/index.ts +0 -33
- package/src/plugins/loader.ts +0 -352
- package/src/plugins/plugin-logger.ts +0 -41
- package/src/plugins/registry.ts +0 -168
- package/src/plugins/types.ts +0 -206
- package/src/plugins/validator.ts +0 -352
- package/src/prd/index.ts +0 -220
- package/src/prd/schema.ts +0 -268
- package/src/prd/types.ts +0 -273
- package/src/prd/validate.ts +0 -41
- package/src/precheck/checks-agents.ts +0 -63
- package/src/precheck/checks-blockers.ts +0 -23
- package/src/precheck/checks-cli.ts +0 -68
- package/src/precheck/checks-config.ts +0 -102
- package/src/precheck/checks-git.ts +0 -117
- package/src/precheck/checks-system.ts +0 -101
- package/src/precheck/checks-warnings.ts +0 -221
- package/src/precheck/checks.ts +0 -36
- package/src/precheck/index.ts +0 -374
- package/src/precheck/story-size-gate.ts +0 -144
- package/src/precheck/types.ts +0 -31
- package/src/prompts/builder.ts +0 -166
- package/src/prompts/index.ts +0 -2
- package/src/prompts/loader.ts +0 -43
- package/src/prompts/sections/conventions.ts +0 -19
- package/src/prompts/sections/hermetic.ts +0 -41
- package/src/prompts/sections/index.ts +0 -12
- package/src/prompts/sections/isolation.ts +0 -70
- package/src/prompts/sections/role-task.ts +0 -182
- package/src/prompts/sections/story.ts +0 -55
- package/src/prompts/sections/verdict.ts +0 -70
- package/src/prompts/types.ts +0 -21
- package/src/queue/index.ts +0 -2
- package/src/queue/manager.ts +0 -254
- package/src/queue/types.ts +0 -54
- package/src/review/index.ts +0 -8
- package/src/review/orchestrator.ts +0 -154
- package/src/review/runner.ts +0 -303
- package/src/review/types.ts +0 -70
- package/src/routing/batch-route.ts +0 -35
- package/src/routing/builder.ts +0 -81
- package/src/routing/chain.ts +0 -75
- package/src/routing/content-hash.ts +0 -25
- package/src/routing/index.ts +0 -20
- package/src/routing/loader.ts +0 -62
- package/src/routing/router.ts +0 -305
- package/src/routing/strategies/adaptive.ts +0 -215
- package/src/routing/strategies/index.ts +0 -8
- package/src/routing/strategies/keyword.ts +0 -180
- package/src/routing/strategies/llm-prompts.ts +0 -224
- package/src/routing/strategies/llm.ts +0 -320
- package/src/routing/strategies/manual.ts +0 -50
- package/src/routing/strategy.ts +0 -102
- package/src/tdd/cleanup.ts +0 -120
- package/src/tdd/index.ts +0 -22
- package/src/tdd/isolation.ts +0 -117
- package/src/tdd/orchestrator.ts +0 -406
- package/src/tdd/prompts.ts +0 -40
- package/src/tdd/rectification-gate.ts +0 -274
- package/src/tdd/session-runner.ts +0 -263
- package/src/tdd/types.ts +0 -84
- package/src/tdd/verdict-reader.ts +0 -266
- package/src/tdd/verdict.ts +0 -152
- package/src/tui/App.tsx +0 -265
- package/src/tui/components/AgentPanel.tsx +0 -75
- package/src/tui/components/CostOverlay.tsx +0 -118
- package/src/tui/components/HelpOverlay.tsx +0 -107
- package/src/tui/components/StatusBar.tsx +0 -63
- package/src/tui/components/StoriesPanel.tsx +0 -177
- package/src/tui/hooks/useKeyboard.ts +0 -142
- package/src/tui/hooks/useLayout.ts +0 -137
- package/src/tui/hooks/usePipelineEvents.ts +0 -183
- package/src/tui/hooks/usePty.ts +0 -189
- package/src/tui/index.tsx +0 -38
- package/src/tui/types.ts +0 -76
- package/src/utils/errors.ts +0 -12
- package/src/utils/git.ts +0 -245
- package/src/utils/json-file.ts +0 -72
- package/src/utils/log-test-output.ts +0 -25
- package/src/utils/path-security.ts +0 -73
- package/src/utils/queue-writer.ts +0 -54
- package/src/verification/crash-detector.ts +0 -34
- package/src/verification/executor.ts +0 -250
- package/src/verification/index.ts +0 -12
- package/src/verification/orchestrator-types.ts +0 -154
- package/src/verification/orchestrator.ts +0 -76
- package/src/verification/parser.ts +0 -220
- package/src/verification/rectification-loop.ts +0 -172
- package/src/verification/rectification.ts +0 -108
- package/src/verification/runners.ts +0 -129
- package/src/verification/smart-runner.ts +0 -307
- package/src/verification/strategies/acceptance.ts +0 -136
- package/src/verification/strategies/regression.ts +0 -90
- package/src/verification/strategies/scoped.ts +0 -154
- package/src/verification/types.ts +0 -117
- package/src/version.ts +0 -40
- package/src/worktree/dispatcher.ts +0 -6
- package/src/worktree/index.ts +0 -2
- package/src/worktree/manager.ts +0 -193
- package/src/worktree/merge.ts +0 -302
- package/src/worktree/types.ts +0 -4
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Story Selector (ADR-005, Phase 4)
|
|
3
|
-
*
|
|
4
|
-
* Extracted from sequential-executor.ts: batch/single-story selection logic.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type { NaxConfig } from "../config";
|
|
8
|
-
import type { RoutingResult } from "../pipeline/types";
|
|
9
|
-
import { getNextStory } from "../prd";
|
|
10
|
-
import type { PRD, UserStory } from "../prd/types";
|
|
11
|
-
import type { StoryBatch } from "./batching";
|
|
12
|
-
import { buildPreviewRouting } from "./executor-types";
|
|
13
|
-
|
|
14
|
-
export interface StorySelection {
|
|
15
|
-
story: UserStory;
|
|
16
|
-
storiesToExecute: UserStory[];
|
|
17
|
-
routing: RoutingResult;
|
|
18
|
-
isBatchExecution: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Select the next story (or batch) to execute.
|
|
23
|
-
* Returns null when there are no more stories to run.
|
|
24
|
-
*/
|
|
25
|
-
export function selectNextStories(
|
|
26
|
-
prd: PRD,
|
|
27
|
-
config: NaxConfig,
|
|
28
|
-
batchPlan: StoryBatch[],
|
|
29
|
-
currentBatchIndex: number,
|
|
30
|
-
lastStoryId: string | null,
|
|
31
|
-
useBatch: boolean,
|
|
32
|
-
): { selection: StorySelection; nextBatchIndex: number } | null {
|
|
33
|
-
if (useBatch && currentBatchIndex < batchPlan.length) {
|
|
34
|
-
const batch = batchPlan[currentBatchIndex];
|
|
35
|
-
const storiesToExecute = batch.stories.filter(
|
|
36
|
-
(s) =>
|
|
37
|
-
!s.passes &&
|
|
38
|
-
s.status !== "passed" &&
|
|
39
|
-
s.status !== "skipped" &&
|
|
40
|
-
s.status !== "blocked" &&
|
|
41
|
-
s.status !== "failed" &&
|
|
42
|
-
s.status !== "paused" &&
|
|
43
|
-
s.status !== "decomposed",
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
if (storiesToExecute.length === 0) {
|
|
47
|
-
// Batch exhausted (all already done) — advance index, caller retries
|
|
48
|
-
return { selection: null as unknown as StorySelection, nextBatchIndex: currentBatchIndex + 1 };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const story = storiesToExecute[0];
|
|
52
|
-
return {
|
|
53
|
-
selection: {
|
|
54
|
-
story,
|
|
55
|
-
storiesToExecute,
|
|
56
|
-
routing: buildPreviewRouting(story, config),
|
|
57
|
-
isBatchExecution: batch.isBatch && storiesToExecute.length > 1,
|
|
58
|
-
},
|
|
59
|
-
nextBatchIndex: currentBatchIndex + 1,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Single-story fallback
|
|
64
|
-
const story = getNextStory(prd, lastStoryId, config.execution.rectification?.maxRetries ?? 2);
|
|
65
|
-
if (!story) return null;
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
selection: {
|
|
69
|
-
story,
|
|
70
|
-
storiesToExecute: [story],
|
|
71
|
-
routing: buildPreviewRouting(story, config),
|
|
72
|
-
isBatchExecution: false,
|
|
73
|
-
},
|
|
74
|
-
nextBatchIndex: currentBatchIndex,
|
|
75
|
-
};
|
|
76
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test Output Parser
|
|
3
|
-
*
|
|
4
|
-
* DEPRECATED: Use src/verification/parser.ts instead.
|
|
5
|
-
* This file is kept for backward compatibility only.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// Re-export from unified verification layer
|
|
9
|
-
export {
|
|
10
|
-
type TestFailure,
|
|
11
|
-
type TestSummary,
|
|
12
|
-
parseBunTestOutput,
|
|
13
|
-
formatFailureSummary,
|
|
14
|
-
} from "../verification/parser";
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Timeout Handler Utility
|
|
3
|
-
*
|
|
4
|
-
* Reusable utility for handling process timeouts with graceful SIGTERM→SIGKILL escalation.
|
|
5
|
-
* Ensures all timers are cleaned up in all code paths (exit, timeout, hard deadline).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
export interface ProcessTimeoutOptions {
|
|
9
|
-
/** Grace period in ms between SIGTERM and SIGKILL (default: 5000) */
|
|
10
|
-
graceMs?: number;
|
|
11
|
-
/** Callback when timeout occurs */
|
|
12
|
-
onTimeout?: () => void;
|
|
13
|
-
/** Hard deadline buffer in ms after SIGKILL (default: 3000) */
|
|
14
|
-
hardDeadlineBufferMs?: number;
|
|
15
|
-
/**
|
|
16
|
-
* Optional injectable kill function for testability.
|
|
17
|
-
* Defaults to proc.kill(signal).
|
|
18
|
-
*/
|
|
19
|
-
killFn?: (proc: { kill(signal?: NodeJS.Signals | number): void }, signal: NodeJS.Signals) => void;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface ProcessTimeoutResult {
|
|
23
|
-
exitCode: number;
|
|
24
|
-
timedOut: boolean;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Execute a process with timeout handling.
|
|
29
|
-
*
|
|
30
|
-
* Sends SIGTERM on timeout, followed by SIGKILL after grace period.
|
|
31
|
-
* Ensures all timers are cleared in finally block.
|
|
32
|
-
*
|
|
33
|
-
* @param proc - Subprocess to monitor
|
|
34
|
-
* @param timeoutMs - Timeout in milliseconds
|
|
35
|
-
* @param opts - Timeout options
|
|
36
|
-
* @returns Object with exit code and timeout flag
|
|
37
|
-
*/
|
|
38
|
-
export async function withProcessTimeout(
|
|
39
|
-
proc: {
|
|
40
|
-
pid: number;
|
|
41
|
-
exited: Promise<number>;
|
|
42
|
-
kill(signal?: NodeJS.Signals | number): void;
|
|
43
|
-
},
|
|
44
|
-
timeoutMs: number,
|
|
45
|
-
opts?: ProcessTimeoutOptions,
|
|
46
|
-
): Promise<ProcessTimeoutResult> {
|
|
47
|
-
const graceMs = opts?.graceMs ?? 5000;
|
|
48
|
-
const hardDeadlineBufferMs = opts?.hardDeadlineBufferMs ?? 3000;
|
|
49
|
-
const killFn = opts?.killFn ?? ((p, signal) => p.kill(signal));
|
|
50
|
-
|
|
51
|
-
let timedOut = false;
|
|
52
|
-
let sigkillId: ReturnType<typeof setTimeout> | undefined;
|
|
53
|
-
|
|
54
|
-
const timeoutId = setTimeout(() => {
|
|
55
|
-
timedOut = true;
|
|
56
|
-
opts?.onTimeout?.();
|
|
57
|
-
try {
|
|
58
|
-
killFn(proc, "SIGTERM");
|
|
59
|
-
} catch {
|
|
60
|
-
/* already exited */
|
|
61
|
-
}
|
|
62
|
-
sigkillId = setTimeout(() => {
|
|
63
|
-
try {
|
|
64
|
-
killFn(proc, "SIGKILL");
|
|
65
|
-
} catch {
|
|
66
|
-
/* already exited */
|
|
67
|
-
}
|
|
68
|
-
}, graceMs);
|
|
69
|
-
}, timeoutMs);
|
|
70
|
-
|
|
71
|
-
let exitCode: number;
|
|
72
|
-
try {
|
|
73
|
-
const hardDeadlineMs = timeoutMs + graceMs + hardDeadlineBufferMs;
|
|
74
|
-
let hardDeadlineId: ReturnType<typeof setTimeout> | undefined;
|
|
75
|
-
const hardDeadlinePromise = new Promise<number>((resolve) => {
|
|
76
|
-
hardDeadlineId = setTimeout(() => resolve(-1), hardDeadlineMs);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
exitCode = await Promise.race([proc.exited, hardDeadlinePromise]);
|
|
80
|
-
clearTimeout(hardDeadlineId);
|
|
81
|
-
|
|
82
|
-
if (exitCode === -1) {
|
|
83
|
-
try {
|
|
84
|
-
process.kill(proc.pid, "SIGKILL");
|
|
85
|
-
} catch {
|
|
86
|
-
/* already gone */
|
|
87
|
-
}
|
|
88
|
-
try {
|
|
89
|
-
process.kill(-proc.pid, "SIGKILL");
|
|
90
|
-
} catch {
|
|
91
|
-
/* no process group */
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
} finally {
|
|
95
|
-
clearTimeout(timeoutId);
|
|
96
|
-
clearTimeout(sigkillId);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return { exitCode, timedOut };
|
|
100
|
-
}
|
package/src/hooks/index.ts
DELETED
package/src/hooks/runner.ts
DELETED
|
@@ -1,280 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hook Runner
|
|
3
|
-
*
|
|
4
|
-
* Loads hooks.json and executes hooks at lifecycle events.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { join } from "node:path";
|
|
8
|
-
import { getLogger } from "../logger";
|
|
9
|
-
import { loadJsonFile } from "../utils/json-file";
|
|
10
|
-
import type { HookContext, HookDef, HookEvent, HooksConfig } from "./types";
|
|
11
|
-
|
|
12
|
-
const DEFAULT_TIMEOUT = 5000;
|
|
13
|
-
|
|
14
|
-
/** Extended hooks config that tracks global vs project hooks */
|
|
15
|
-
export interface LoadedHooksConfig extends HooksConfig {
|
|
16
|
-
/** Global hooks (loaded from ~/.nax/hooks.json) */
|
|
17
|
-
_global?: HooksConfig;
|
|
18
|
-
/** Whether global hooks were skipped */
|
|
19
|
-
_skipGlobal?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Load hooks config from global and project paths.
|
|
24
|
-
*
|
|
25
|
-
* Both global and project hooks are preserved independently (not merged).
|
|
26
|
-
* The skipGlobal flag in project config disables global hook loading.
|
|
27
|
-
*
|
|
28
|
-
* @param projectDir - Project nax directory path
|
|
29
|
-
* @param globalDir - Global nax directory path (optional)
|
|
30
|
-
* @returns Merged hooks config with both global and project hooks
|
|
31
|
-
*/
|
|
32
|
-
export async function loadHooksConfig(projectDir: string, globalDir?: string): Promise<LoadedHooksConfig> {
|
|
33
|
-
let globalHooks: HooksConfig = { hooks: {} };
|
|
34
|
-
let projectHooks: HooksConfig = { hooks: {} };
|
|
35
|
-
let skipGlobal = false;
|
|
36
|
-
|
|
37
|
-
// Load project hooks first to check skipGlobal flag
|
|
38
|
-
const projectPath = join(projectDir, "hooks.json");
|
|
39
|
-
const projectData = await loadJsonFile<HooksConfig & { skipGlobal?: boolean }>(projectPath, "hooks");
|
|
40
|
-
if (projectData) {
|
|
41
|
-
projectHooks = projectData;
|
|
42
|
-
// Check if project config has skipGlobal flag
|
|
43
|
-
skipGlobal = projectData.skipGlobal ?? false;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Load global hooks only if not skipped
|
|
47
|
-
if (!skipGlobal && globalDir) {
|
|
48
|
-
const globalPath = join(globalDir, "hooks.json");
|
|
49
|
-
const globalData = await loadJsonFile<HooksConfig>(globalPath, "hooks");
|
|
50
|
-
if (globalData) {
|
|
51
|
-
globalHooks = globalData;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// Return project hooks as the main config, with global hooks stored separately
|
|
56
|
-
return {
|
|
57
|
-
...projectHooks,
|
|
58
|
-
_global: skipGlobal ? undefined : globalHooks,
|
|
59
|
-
_skipGlobal: skipGlobal,
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Escape environment variable values to prevent injection
|
|
65
|
-
* @param value - Raw value to escape
|
|
66
|
-
* @returns Escaped value safe for subprocess environment
|
|
67
|
-
*/
|
|
68
|
-
function escapeEnvValue(value: string): string {
|
|
69
|
-
// Remove null bytes and newlines that could cause issues
|
|
70
|
-
return value.replace(/\0/g, "").replace(/\n/g, " ").replace(/\r/g, "");
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Build environment variables from hook context
|
|
75
|
-
* All values are escaped to prevent injection attacks
|
|
76
|
-
*/
|
|
77
|
-
function buildEnv(ctx: HookContext): Record<string, string> {
|
|
78
|
-
const env: Record<string, string> = {
|
|
79
|
-
NAX_EVENT: escapeEnvValue(ctx.event),
|
|
80
|
-
NAX_FEATURE: escapeEnvValue(ctx.feature),
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
if (ctx.storyId) env.NAX_STORY_ID = escapeEnvValue(ctx.storyId);
|
|
84
|
-
if (ctx.status) env.NAX_STATUS = escapeEnvValue(ctx.status);
|
|
85
|
-
if (ctx.reason) env.NAX_REASON = escapeEnvValue(ctx.reason);
|
|
86
|
-
if (ctx.cost !== undefined) env.NAX_COST = ctx.cost.toFixed(4);
|
|
87
|
-
if (ctx.model) env.NAX_MODEL = escapeEnvValue(ctx.model);
|
|
88
|
-
if (ctx.agent) env.NAX_AGENT = escapeEnvValue(ctx.agent);
|
|
89
|
-
if (ctx.iteration !== undefined) env.NAX_ITERATION = String(ctx.iteration);
|
|
90
|
-
|
|
91
|
-
return env;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Detect shell operators that indicate shell interpolation
|
|
96
|
-
* @param command - Command string to check
|
|
97
|
-
* @returns true if shell operators detected
|
|
98
|
-
*/
|
|
99
|
-
export function hasShellOperators(command: string): boolean {
|
|
100
|
-
// Check for common shell operators that require shell interpretation
|
|
101
|
-
const shellOperators = /[|&;$`<>(){}]/;
|
|
102
|
-
return shellOperators.test(command);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Validate hook command for injection patterns
|
|
107
|
-
* @param command - Command string to validate
|
|
108
|
-
* @throws Error if obvious injection pattern detected
|
|
109
|
-
*/
|
|
110
|
-
export function validateHookCommand(command: string): void {
|
|
111
|
-
// Reject commands with obvious injection patterns
|
|
112
|
-
const dangerousPatterns = [
|
|
113
|
-
/\$\([^)]*\)/, // Command substitution $(...) — bounded to avoid ReDoS
|
|
114
|
-
/`[^`]*`/, // Backtick command substitution — bounded to avoid ReDoS
|
|
115
|
-
/\|\s*bash/, // Piping to bash
|
|
116
|
-
/\|\s*sh/, // Piping to sh
|
|
117
|
-
/;\s*rm\s+-rf/, // Dangerous deletion
|
|
118
|
-
/&&\s*rm\s+-rf/, // Dangerous deletion after success
|
|
119
|
-
/\beval\s+/, // SEC-3 fix: eval command
|
|
120
|
-
/curl\s+[^|]*\|\s*/, // SEC-3 fix: curl piping
|
|
121
|
-
/python\s+-c/, // SEC-3 fix: python -c execution
|
|
122
|
-
];
|
|
123
|
-
|
|
124
|
-
for (const pattern of dangerousPatterns) {
|
|
125
|
-
if (pattern.test(command)) {
|
|
126
|
-
throw new Error(`Hook command contains dangerous pattern: ${pattern.source}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Parse command string into argv array
|
|
133
|
-
* Simple space-based splitting (does not handle complex quoting)
|
|
134
|
-
* @param command - Command string
|
|
135
|
-
* @returns Array of command and arguments
|
|
136
|
-
*/
|
|
137
|
-
export function parseCommandToArgv(command: string): string[] {
|
|
138
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
139
|
-
return command
|
|
140
|
-
.trim()
|
|
141
|
-
.split(/\s+/)
|
|
142
|
-
.map((token) => (token.startsWith("~/") ? home + token.slice(1) : token));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Execute a single hook
|
|
147
|
-
*
|
|
148
|
-
* SECURITY WARNING: Hook commands are executed as subprocesses.
|
|
149
|
-
* - Commands are parsed into argv arrays to avoid shell injection
|
|
150
|
-
* - Shell operators (|, &&, ;, $, etc.) trigger a security warning
|
|
151
|
-
* - Obvious injection patterns are rejected
|
|
152
|
-
* - Environment variables are escaped
|
|
153
|
-
* - Only configure hooks from trusted sources
|
|
154
|
-
*
|
|
155
|
-
* @param hookDef - Hook definition from config
|
|
156
|
-
* @param ctx - Hook context with environment variables
|
|
157
|
-
* @param workdir - Working directory for command execution
|
|
158
|
-
* @returns Promise with success status and output
|
|
159
|
-
*/
|
|
160
|
-
async function executeHook(
|
|
161
|
-
hookDef: HookDef,
|
|
162
|
-
ctx: HookContext,
|
|
163
|
-
workdir: string,
|
|
164
|
-
): Promise<{ success: boolean; output: string }> {
|
|
165
|
-
if (hookDef.enabled === false) {
|
|
166
|
-
return { success: true, output: "(disabled)" };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Validate command for injection patterns
|
|
170
|
-
try {
|
|
171
|
-
validateHookCommand(hookDef.command);
|
|
172
|
-
} catch (err) {
|
|
173
|
-
return {
|
|
174
|
-
success: false,
|
|
175
|
-
output: `Security validation failed: ${err}`,
|
|
176
|
-
};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Warn if shell operators detected
|
|
180
|
-
const logger = getLogger();
|
|
181
|
-
if (hasShellOperators(hookDef.command)) {
|
|
182
|
-
logger.warn("hooks", "[SECURITY] Hook command contains shell operators", {
|
|
183
|
-
command: hookDef.command,
|
|
184
|
-
warning: "Shell operators may enable injection attacks. Consider using simple commands only.",
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const timeout = hookDef.timeout ?? DEFAULT_TIMEOUT;
|
|
189
|
-
const env = buildEnv(ctx);
|
|
190
|
-
|
|
191
|
-
// Pass full context as JSON via stdin
|
|
192
|
-
const contextJson = JSON.stringify(ctx);
|
|
193
|
-
|
|
194
|
-
// Parse command to argv array (no shell interpolation)
|
|
195
|
-
const argv = parseCommandToArgv(hookDef.command);
|
|
196
|
-
if (argv.length === 0) {
|
|
197
|
-
return { success: false, output: "Empty command" };
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const proc = Bun.spawn(argv, {
|
|
201
|
-
cwd: workdir,
|
|
202
|
-
stdin: new Response(contextJson),
|
|
203
|
-
stdout: "pipe",
|
|
204
|
-
stderr: "pipe",
|
|
205
|
-
env: { ...process.env, ...env },
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
// Timeout handling
|
|
209
|
-
const timeoutId = setTimeout(() => {
|
|
210
|
-
proc.kill("SIGTERM");
|
|
211
|
-
}, timeout);
|
|
212
|
-
|
|
213
|
-
const exitCode = await proc.exited;
|
|
214
|
-
clearTimeout(timeoutId);
|
|
215
|
-
|
|
216
|
-
const stdout = await new Response(proc.stdout).text();
|
|
217
|
-
const stderr = await new Response(proc.stderr).text();
|
|
218
|
-
|
|
219
|
-
const output = (stdout + stderr).trim();
|
|
220
|
-
|
|
221
|
-
// Check if process was killed by timeout
|
|
222
|
-
if (exitCode !== 0 && output === "") {
|
|
223
|
-
return {
|
|
224
|
-
success: false,
|
|
225
|
-
output: `Hook timed out after ${timeout}ms`,
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return {
|
|
230
|
-
success: exitCode === 0,
|
|
231
|
-
output,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Fire a hook event for both global and project hooks.
|
|
237
|
-
*
|
|
238
|
-
* Both hooks fire independently - global failure doesn't block project hook.
|
|
239
|
-
*
|
|
240
|
-
* @param config - Loaded hooks config (contains both global and project hooks)
|
|
241
|
-
* @param event - Hook event name
|
|
242
|
-
* @param ctx - Hook context
|
|
243
|
-
* @param workdir - Working directory
|
|
244
|
-
*/
|
|
245
|
-
export async function fireHook(
|
|
246
|
-
config: LoadedHooksConfig,
|
|
247
|
-
event: HookEvent,
|
|
248
|
-
ctx: HookContext,
|
|
249
|
-
workdir: string,
|
|
250
|
-
): Promise<void> {
|
|
251
|
-
const logger = getLogger();
|
|
252
|
-
|
|
253
|
-
// Fire global hook first (if present and not skipped)
|
|
254
|
-
if (config._global && !config._skipGlobal) {
|
|
255
|
-
const globalHookDef = config._global.hooks[event];
|
|
256
|
-
if (globalHookDef && globalHookDef.enabled !== false) {
|
|
257
|
-
try {
|
|
258
|
-
const result = await executeHook(globalHookDef, { ...ctx, event }, workdir);
|
|
259
|
-
if (!result.success) {
|
|
260
|
-
logger.warn("hooks", `Global hook ${event} failed`, { event, output: result.output });
|
|
261
|
-
}
|
|
262
|
-
} catch (err) {
|
|
263
|
-
logger.warn("hooks", `Global hook ${event} error`, { event, error: String(err) });
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Fire project hook (independent of global hook result)
|
|
269
|
-
const projectHookDef = config.hooks[event];
|
|
270
|
-
if (projectHookDef && projectHookDef.enabled !== false) {
|
|
271
|
-
try {
|
|
272
|
-
const result = await executeHook(projectHookDef, { ...ctx, event }, workdir);
|
|
273
|
-
if (!result.success) {
|
|
274
|
-
logger.warn("hooks", `Project hook ${event} failed`, { event, output: result.output });
|
|
275
|
-
}
|
|
276
|
-
} catch (err) {
|
|
277
|
-
logger.warn("hooks", `Project hook ${event} error`, { event, error: String(err) });
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
package/src/hooks/types.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Hook System Types
|
|
3
|
-
*
|
|
4
|
-
* Script-based lifecycle hooks configured via hooks.json.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/** All supported hook events — runtime array used for validation */
|
|
8
|
-
export const HOOK_EVENTS = [
|
|
9
|
-
"on-start",
|
|
10
|
-
"on-story-start",
|
|
11
|
-
"on-story-complete",
|
|
12
|
-
"on-story-fail",
|
|
13
|
-
"on-pause",
|
|
14
|
-
"on-resume",
|
|
15
|
-
"on-session-end",
|
|
16
|
-
"on-all-stories-complete",
|
|
17
|
-
"on-complete",
|
|
18
|
-
"on-error",
|
|
19
|
-
"on-final-regression-fail",
|
|
20
|
-
] as const;
|
|
21
|
-
|
|
22
|
-
/** All supported hook events */
|
|
23
|
-
export type HookEvent = (typeof HOOK_EVENTS)[number];
|
|
24
|
-
|
|
25
|
-
/** Single hook definition */
|
|
26
|
-
export interface HookDef {
|
|
27
|
-
/** Command to execute */
|
|
28
|
-
command: string;
|
|
29
|
-
/** Timeout in milliseconds (default: 5000) */
|
|
30
|
-
timeout?: number;
|
|
31
|
-
/** Whether this hook is enabled (default: true) */
|
|
32
|
-
enabled?: boolean;
|
|
33
|
-
/** Interaction prompt (v0.15.0) */
|
|
34
|
-
interaction?: {
|
|
35
|
-
/** Interaction type */
|
|
36
|
-
type: "confirm" | "choose" | "input" | "review" | "notify";
|
|
37
|
-
/** Summary template (supports {{variable}} syntax) */
|
|
38
|
-
summary: string;
|
|
39
|
-
/** Detail template (optional) */
|
|
40
|
-
detail?: string;
|
|
41
|
-
/** Fallback behavior on timeout */
|
|
42
|
-
fallback: "continue" | "skip" | "escalate" | "abort";
|
|
43
|
-
/** Timeout in milliseconds (optional) */
|
|
44
|
-
timeout?: number;
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** hooks.json schema */
|
|
49
|
-
export interface HooksConfig {
|
|
50
|
-
hooks: Partial<Record<HookEvent, HookDef>>;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Context passed to hooks via environment variables */
|
|
54
|
-
export interface HookContext {
|
|
55
|
-
/** Event name */
|
|
56
|
-
event: HookEvent;
|
|
57
|
-
/** Feature name */
|
|
58
|
-
feature: string;
|
|
59
|
-
/** Current story ID */
|
|
60
|
-
storyId?: string;
|
|
61
|
-
/** Status (pass/fail/paused/error) */
|
|
62
|
-
status?: string;
|
|
63
|
-
/** Reason for pause/error */
|
|
64
|
-
reason?: string;
|
|
65
|
-
/** Accumulated cost (USD) */
|
|
66
|
-
cost?: number;
|
|
67
|
-
/** Current model */
|
|
68
|
-
model?: string;
|
|
69
|
-
/** Current agent */
|
|
70
|
-
agent?: string;
|
|
71
|
-
/** Current iteration number */
|
|
72
|
-
iteration?: number;
|
|
73
|
-
/** Number of failed tests (on-final-regression-fail) */
|
|
74
|
-
failedTests?: number;
|
|
75
|
-
/** Stories affected by regression failure (on-final-regression-fail) */
|
|
76
|
-
affectedStories?: string[];
|
|
77
|
-
/** Number of sub-stories created (on-story-complete with status "decomposed") */
|
|
78
|
-
subStoryCount?: number;
|
|
79
|
-
}
|