all-hands-cli 0.1.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/.allhands/README.md +75 -0
- package/.allhands/agents/compounder.yaml +15 -0
- package/.allhands/agents/coordinator.yaml +17 -0
- package/.allhands/agents/documentor.yaml +15 -0
- package/.allhands/agents/e2e-test-planner.yaml +17 -0
- package/.allhands/agents/emergent.yaml +22 -0
- package/.allhands/agents/executor.yaml +14 -0
- package/.allhands/agents/ideation.yaml +11 -0
- package/.allhands/agents/initiative-steering.yaml +19 -0
- package/.allhands/agents/judge.yaml +13 -0
- package/.allhands/agents/planner.yaml +19 -0
- package/.allhands/agents/pr-reviewer.yaml +15 -0
- package/.allhands/docs.json +5 -0
- package/.allhands/docs.local.json +26 -0
- package/.allhands/flows/COMPOUNDING.md +203 -0
- package/.allhands/flows/COORDINATION.md +89 -0
- package/.allhands/flows/CORE.md +87 -0
- package/.allhands/flows/DOCUMENTATION.md +218 -0
- package/.allhands/flows/E2E_TEST_PLAN_BUILDING.md +140 -0
- package/.allhands/flows/EMERGENT_PLANNING.md +57 -0
- package/.allhands/flows/IDEATION_SCOPING.md +154 -0
- package/.allhands/flows/INITIATIVE_STEERING.md +110 -0
- package/.allhands/flows/JUDGE_REVIEWING.md +79 -0
- package/.allhands/flows/PROMPT_TASK_EXECUTION.md +68 -0
- package/.allhands/flows/PR_REVIEWING.md +43 -0
- package/.allhands/flows/SPEC_PLANNING.md +216 -0
- package/.allhands/flows/harness/WRITING_HARNESS_FLOWS.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_KNOWLEDGE.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_ORCHESTRATION.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_SKILLS.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_TOOLS.md +27 -0
- package/.allhands/flows/harness/WRITING_HARNESS_VALIDATION_TOOLING.md +27 -0
- package/.allhands/flows/shared/CODEBASE_UNDERSTANDING.md +72 -0
- package/.allhands/flows/shared/CREATE_HARNESS_SPEC.md +48 -0
- package/.allhands/flows/shared/CREATE_SPEC.md +41 -0
- package/.allhands/flows/shared/CREATE_VALIDATION_TOOLING_SPEC.md +70 -0
- package/.allhands/flows/shared/DOCUMENTATION_DISCOVERY.md +123 -0
- package/.allhands/flows/shared/DOCUMENTATION_WRITER.md +101 -0
- package/.allhands/flows/shared/EMERGENT_REFINEMENT_ANALYSIS.md +76 -0
- package/.allhands/flows/shared/EXTERNAL_TECH_GUIDANCE.md +97 -0
- package/.allhands/flows/shared/IDEATION_CODEBASE_GROUNDING.md +49 -0
- package/.allhands/flows/shared/PLAN_DEEPENING.md +152 -0
- package/.allhands/flows/shared/PROMPT_TASKS_CURATION.md +113 -0
- package/.allhands/flows/shared/PROMPT_VALIDATION_REVIEW.MD +99 -0
- package/.allhands/flows/shared/QUICK_PREMORTEM.md +70 -0
- package/.allhands/flows/shared/RESEARCH_GUIDANCE.md +38 -0
- package/.allhands/flows/shared/REVIEW_OPTIONS_BREAKDOWN.md +68 -0
- package/.allhands/flows/shared/SKILL_EXTRACTION.md +84 -0
- package/.allhands/flows/shared/SPEC_FLOW_ANALYSIS.md +119 -0
- package/.allhands/flows/shared/TDD_WORKFLOW.md +109 -0
- package/.allhands/flows/shared/UTILIZE_VALIDATION_TOOLING.md +84 -0
- package/.allhands/flows/shared/WRITING_HARNESS_FLOWS.md +11 -0
- package/.allhands/flows/shared/WRITING_HARNESS_MCP_TOOLS.md +84 -0
- package/.allhands/flows/shared/jury/ARCHITECTURE_REVIEW.md +91 -0
- package/.allhands/flows/shared/jury/BEST_PRACTICES_REVIEW.md +80 -0
- package/.allhands/flows/shared/jury/CLAIM_VERIFICATION_REVIEW.md +101 -0
- package/.allhands/flows/shared/jury/EXPECTATIONS_FIT_REVIEW.md +78 -0
- package/.allhands/flows/shared/jury/MAINTAINABILITY_REVIEW.md +110 -0
- package/.allhands/flows/shared/jury/PROMPTS_EXPECTATIONS_FIT.md +74 -0
- package/.allhands/flows/shared/jury/PROMPTS_FLOW_ANALYSIS.md +92 -0
- package/.allhands/flows/shared/jury/PROMPTS_YAGNI.md +78 -0
- package/.allhands/flows/shared/jury/PROMPT_PREMORTEM.md +125 -0
- package/.allhands/flows/shared/jury/SECURITY_REVIEW.md +86 -0
- package/.allhands/flows/shared/jury/YAGNI_REVIEW.md +82 -0
- package/.allhands/flows/wip/DEBUG_INVESTIGATION.md +162 -0
- package/.allhands/flows/wip/MEMORY_RECALL.md +62 -0
- package/.allhands/harness/ah +131 -0
- package/.allhands/harness/package-lock.json +5292 -0
- package/.allhands/harness/package.json +52 -0
- package/.allhands/harness/src/__tests__/e2e/commands.test.ts +307 -0
- package/.allhands/harness/src/__tests__/e2e/event-loop.test.ts +539 -0
- package/.allhands/harness/src/__tests__/e2e/hooks.test.ts +427 -0
- package/.allhands/harness/src/__tests__/e2e/new-initiative-routing.test.ts +137 -0
- package/.allhands/harness/src/__tests__/e2e/run-e2e.ts +109 -0
- package/.allhands/harness/src/__tests__/e2e/specs-type.test.ts +210 -0
- package/.allhands/harness/src/__tests__/e2e/validation-hooks.test.ts +669 -0
- package/.allhands/harness/src/__tests__/e2e/validation-path-consistency.test.ts +354 -0
- package/.allhands/harness/src/__tests__/e2e/validation.test.ts +528 -0
- package/.allhands/harness/src/__tests__/harness/assertions.ts +318 -0
- package/.allhands/harness/src/__tests__/harness/cli-runner.ts +359 -0
- package/.allhands/harness/src/__tests__/harness/fixture.ts +384 -0
- package/.allhands/harness/src/__tests__/harness/hook-runner.ts +411 -0
- package/.allhands/harness/src/__tests__/harness/index.ts +122 -0
- package/.allhands/harness/src/cli.ts +36 -0
- package/.allhands/harness/src/commands/complexity.ts +177 -0
- package/.allhands/harness/src/commands/context7.ts +202 -0
- package/.allhands/harness/src/commands/docs.ts +557 -0
- package/.allhands/harness/src/commands/hooks.ts +24 -0
- package/.allhands/harness/src/commands/index.ts +51 -0
- package/.allhands/harness/src/commands/knowledge.ts +382 -0
- package/.allhands/harness/src/commands/memories.ts +302 -0
- package/.allhands/harness/src/commands/notify.ts +61 -0
- package/.allhands/harness/src/commands/oracle.ts +158 -0
- package/.allhands/harness/src/commands/perplexity.ts +220 -0
- package/.allhands/harness/src/commands/planning.ts +245 -0
- package/.allhands/harness/src/commands/schema.ts +73 -0
- package/.allhands/harness/src/commands/skills.ts +128 -0
- package/.allhands/harness/src/commands/solutions.ts +353 -0
- package/.allhands/harness/src/commands/spawn.ts +158 -0
- package/.allhands/harness/src/commands/specs.ts +532 -0
- package/.allhands/harness/src/commands/tavily.ts +226 -0
- package/.allhands/harness/src/commands/tools.ts +579 -0
- package/.allhands/harness/src/commands/trace.ts +327 -0
- package/.allhands/harness/src/commands/tui.ts +960 -0
- package/.allhands/harness/src/commands/validate.ts +143 -0
- package/.allhands/harness/src/commands/validation-tools.ts +108 -0
- package/.allhands/harness/src/hooks/context.ts +1442 -0
- package/.allhands/harness/src/hooks/enforcement.ts +170 -0
- package/.allhands/harness/src/hooks/index.ts +54 -0
- package/.allhands/harness/src/hooks/lifecycle.ts +229 -0
- package/.allhands/harness/src/hooks/notification.ts +104 -0
- package/.allhands/harness/src/hooks/observability.ts +551 -0
- package/.allhands/harness/src/hooks/session.ts +88 -0
- package/.allhands/harness/src/hooks/shared.ts +815 -0
- package/.allhands/harness/src/hooks/transcript-parser.ts +208 -0
- package/.allhands/harness/src/hooks/validation.ts +617 -0
- package/.allhands/harness/src/lib/__tests__/ctags.test.ts +244 -0
- package/.allhands/harness/src/lib/__tests__/docs-validation.test.ts +344 -0
- package/.allhands/harness/src/lib/__tests__/mcp-runtime.test.ts +190 -0
- package/.allhands/harness/src/lib/__tests__/schema.test.ts +861 -0
- package/.allhands/harness/src/lib/base-command.ts +198 -0
- package/.allhands/harness/src/lib/cli-daemon.ts +343 -0
- package/.allhands/harness/src/lib/compaction.ts +313 -0
- package/.allhands/harness/src/lib/ctags.ts +497 -0
- package/.allhands/harness/src/lib/docs-validation.ts +907 -0
- package/.allhands/harness/src/lib/event-loop.ts +662 -0
- package/.allhands/harness/src/lib/flows.ts +155 -0
- package/.allhands/harness/src/lib/git.ts +276 -0
- package/.allhands/harness/src/lib/knowledge-worker.ts +72 -0
- package/.allhands/harness/src/lib/knowledge.ts +810 -0
- package/.allhands/harness/src/lib/llm.ts +255 -0
- package/.allhands/harness/src/lib/mcp-client.ts +432 -0
- package/.allhands/harness/src/lib/mcp-daemon.ts +486 -0
- package/.allhands/harness/src/lib/mcp-runtime.ts +418 -0
- package/.allhands/harness/src/lib/notification.ts +115 -0
- package/.allhands/harness/src/lib/opencode/index.ts +70 -0
- package/.allhands/harness/src/lib/opencode/profiles.ts +300 -0
- package/.allhands/harness/src/lib/opencode/prompts/codesearch.md +98 -0
- package/.allhands/harness/src/lib/opencode/prompts/knowledge-aggregator.md +67 -0
- package/.allhands/harness/src/lib/opencode/runner.ts +281 -0
- package/.allhands/harness/src/lib/oracle.ts +926 -0
- package/.allhands/harness/src/lib/planning-utils.ts +150 -0
- package/.allhands/harness/src/lib/planning.ts +605 -0
- package/.allhands/harness/src/lib/pr-review.ts +225 -0
- package/.allhands/harness/src/lib/prompts.ts +522 -0
- package/.allhands/harness/src/lib/schema.ts +418 -0
- package/.allhands/harness/src/lib/schemas/agent-profile.ts +141 -0
- package/.allhands/harness/src/lib/schemas/template-vars.ts +138 -0
- package/.allhands/harness/src/lib/session.ts +164 -0
- package/.allhands/harness/src/lib/specs.ts +348 -0
- package/.allhands/harness/src/lib/tldr.ts +829 -0
- package/.allhands/harness/src/lib/tmux.ts +1051 -0
- package/.allhands/harness/src/lib/trace-store.ts +714 -0
- package/.allhands/harness/src/mcp/__tests__/index.test.ts +46 -0
- package/.allhands/harness/src/mcp/_template.ts +47 -0
- package/.allhands/harness/src/mcp/filesystem.ts +33 -0
- package/.allhands/harness/src/mcp/index.ts +69 -0
- package/.allhands/harness/src/mcp/playwright.ts +34 -0
- package/.allhands/harness/src/mcp/xcodebuild.ts +29 -0
- package/.allhands/harness/src/schemas/docs.schema.json +44 -0
- package/.allhands/harness/src/schemas/settings.schema.json +214 -0
- package/.allhands/harness/src/tui/actions.ts +227 -0
- package/.allhands/harness/src/tui/file-viewer-modal.ts +270 -0
- package/.allhands/harness/src/tui/index.ts +1574 -0
- package/.allhands/harness/src/tui/modal.ts +232 -0
- package/.allhands/harness/src/tui/prompts-pane.ts +186 -0
- package/.allhands/harness/src/tui/status-pane.ts +434 -0
- package/.allhands/harness/tsconfig.json +22 -0
- package/.allhands/harness/vitest.config.ts +13 -0
- package/.allhands/pillars.md +33 -0
- package/.allhands/principles.md +88 -0
- package/.allhands/schemas/alignment.yaml +51 -0
- package/.allhands/schemas/documentation.yaml +10 -0
- package/.allhands/schemas/prompt.yaml +92 -0
- package/.allhands/schemas/skill.yaml +34 -0
- package/.allhands/schemas/solution.yaml +131 -0
- package/.allhands/schemas/spec.yaml +67 -0
- package/.allhands/schemas/validation-suite.yaml +49 -0
- package/.allhands/schemas/workflow.yaml +51 -0
- package/.allhands/settings.json +57 -0
- package/.allhands/skills/claude-code-patterns/SKILL.md +60 -0
- package/.allhands/skills/claude-code-patterns/docs/context-hygiene.md +19 -0
- package/.allhands/skills/harness-maintenance/SKILL.md +449 -0
- package/.allhands/skills/harness-maintenance/references/core-architecture.md +187 -0
- package/.allhands/skills/harness-maintenance/references/harness-skills.md +87 -0
- package/.allhands/skills/harness-maintenance/references/knowledge-compounding.md +78 -0
- package/.allhands/skills/harness-maintenance/references/tools-commands-mcp-hooks.md +115 -0
- package/.allhands/skills/harness-maintenance/references/validation-tooling.md +77 -0
- package/.allhands/skills/harness-maintenance/references/writing-flows.md +84 -0
- package/.allhands/validation/browser-automation.md +109 -0
- package/.allhands/validation/xcode-automation.md +195 -0
- package/.allhands/workflows/documentation.md +86 -0
- package/.allhands/workflows/investigation.md +81 -0
- package/.allhands/workflows/milestone.md +91 -0
- package/.allhands/workflows/optimization.md +85 -0
- package/.allhands/workflows/refactor.md +99 -0
- package/.allhands/workflows/triage.md +81 -0
- package/.claude/README.md +1 -0
- package/.claude/agents/explorer.md +10 -0
- package/.claude/agents/researcher.md +11 -0
- package/.claude/agents/task-runner.md +8 -0
- package/.claude/settings.json +231 -0
- package/.env.ai.example +7 -0
- package/.github/workflows/npm-publish.yml +69 -0
- package/.internal.json +45 -0
- package/.tldr/config.json +11 -0
- package/.tldrignore +90 -0
- package/CLAUDE.md +6 -0
- package/README.md +98 -0
- package/bin/sync-cli.js +7552 -0
- package/concerns.md +7 -0
- package/docs/README.md +41 -0
- package/docs/agents/README.md +24 -0
- package/docs/agents/agent-configuration-system.md +86 -0
- package/docs/agents/execution-agents.md +50 -0
- package/docs/agents/knowledge-agents.md +61 -0
- package/docs/agents/orchestration-agent.md +57 -0
- package/docs/agents/planning-agents.md +84 -0
- package/docs/agents/quality-review-agents.md +67 -0
- package/docs/agents/workflow-agent-orchestration.md +69 -0
- package/docs/flows/README.md +44 -0
- package/docs/flows/compounding.md +126 -0
- package/docs/flows/coordination.md +72 -0
- package/docs/flows/core-harness-integration.md +63 -0
- package/docs/flows/documentation-orchestration.md +98 -0
- package/docs/flows/e2e-test-plan-building.md +83 -0
- package/docs/flows/emergent-refinement.md +104 -0
- package/docs/flows/flow-authoring-and-mcp-tools.md +89 -0
- package/docs/flows/judge-reviewing.md +112 -0
- package/docs/flows/plan-deepening-and-research.md +107 -0
- package/docs/flows/plan-review-jury.md +114 -0
- package/docs/flows/pr-reviewing.md +54 -0
- package/docs/flows/prompt-task-execution.md +119 -0
- package/docs/flows/spec-planning.md +162 -0
- package/docs/flows/type-specific-scoping-flows.md +49 -0
- package/docs/flows/validation-and-skills-integration.md +145 -0
- package/docs/flows/wip/wip-flows.md +102 -0
- package/docs/harness/README.md +23 -0
- package/docs/harness/agent-profiles.md +84 -0
- package/docs/harness/cli/README.md +24 -0
- package/docs/harness/cli/cli-entry-and-command-discovery.md +91 -0
- package/docs/harness/cli/docs-command.md +87 -0
- package/docs/harness/cli/knowledge-command.md +91 -0
- package/docs/harness/cli/minor-cli-commands.md +65 -0
- package/docs/harness/cli/oracle-command.md +113 -0
- package/docs/harness/cli/planning-command.md +95 -0
- package/docs/harness/cli/schema-and-validation-commands.md +154 -0
- package/docs/harness/cli/search-commands.md +97 -0
- package/docs/harness/cli/spawn-command.md +136 -0
- package/docs/harness/cli/specs-command.md +102 -0
- package/docs/harness/cli/tools-command.md +122 -0
- package/docs/harness/cli/trace-command.md +122 -0
- package/docs/harness/cli-daemon.md +92 -0
- package/docs/harness/event-loop.md +184 -0
- package/docs/harness/hooks/README.md +15 -0
- package/docs/harness/hooks/context-hooks.md +96 -0
- package/docs/harness/hooks/lifecycle-and-observability-hooks.md +135 -0
- package/docs/harness/hooks/validation-hooks.md +97 -0
- package/docs/harness/test-harness.md +149 -0
- package/docs/harness/tui.md +176 -0
- package/docs/memories.md +20 -0
- package/docs/solutions/agentic-issues/premature-agent-deletion-tui-action-dependency-20260130.md +49 -0
- package/docs/solutions/agentic-issues/ref-anchor-scope-mismatch-skill-references-20260131.md +55 -0
- package/docs/solutions/agentic-issues/tautological-tests-routing-20260131.md +52 -0
- package/docs/solutions/integration_issue/blocktool-output-format-mismatch-hook-runner-20260130.md +52 -0
- package/docs/solutions/integration_issue/dual-validation-path-divergence-schema-20260130.md +66 -0
- package/docs/solutions/security-issues/unsanitized-domain-path-join-20260131.md +52 -0
- package/docs/solutions/test-failures/event-loop-mock-ordering-checkAgentWindows-20260130.md +63 -0
- package/docs/sync-cli/README.md +19 -0
- package/docs/sync-cli/cli-entrypoint-and-commands.md +39 -0
- package/docs/sync-cli/commands/README.md +11 -0
- package/docs/sync-cli/commands/pull-manifest-command.md +36 -0
- package/docs/sync-cli/commands/push-command.md +84 -0
- package/docs/sync-cli/commands/sync-command.md +71 -0
- package/docs/sync-cli/systems/README.md +14 -0
- package/docs/sync-cli/systems/git-and-github-integration.md +49 -0
- package/docs/sync-cli/systems/interactive-ui.md +43 -0
- package/docs/sync-cli/systems/manifest-and-distribution.md +51 -0
- package/docs/sync-cli/systems/path-resolution.md +42 -0
- package/package.json +46 -0
- package/scripts/install-shim.sh +40 -0
- package/scripts/pre-pack.sh +25 -0
- package/specs/harness-maintenance-skill.spec.md +138 -0
- package/specs/roadmap/git-spec-lifecycle-management.spec.md +113 -0
- package/specs/sync-init-flag.spec.md +117 -0
- package/specs/unified-workflow-orchestration.spec.md +250 -0
- package/specs/validation-tooling-practice.spec.md +98 -0
- package/specs/workflow-domain-configuration.spec.md +265 -0
- package/src/commands/pull-manifest.ts +31 -0
- package/src/commands/push.ts +344 -0
- package/src/commands/sync.ts +289 -0
- package/src/lib/constants.ts +10 -0
- package/src/lib/dotfiles.ts +36 -0
- package/src/lib/fs-utils.ts +18 -0
- package/src/lib/gh.ts +40 -0
- package/src/lib/git.ts +63 -0
- package/src/lib/gitignore.ts +167 -0
- package/src/lib/manifest.ts +121 -0
- package/src/lib/marker-sync.ts +39 -0
- package/src/lib/paths.ts +38 -0
- package/src/lib/target-lines.ts +66 -0
- package/src/lib/ui.ts +78 -0
- package/src/sync-cli.ts +120 -0
- package/target-lines.json +23 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BaseCommand - Foundation for all ah CLI commands
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - Agent context tracking (AGENT_TYPE, PROMPT_NUMBER, SPEC_NAME)
|
|
6
|
+
* - JSON vs human-friendly output modes
|
|
7
|
+
* - Standard error handling
|
|
8
|
+
* - Trace logging (SQLite + JSONL)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { logCommandStart, logCommandSuccess, logCommandError } from './trace-store.js';
|
|
12
|
+
|
|
13
|
+
export interface CommandContext {
|
|
14
|
+
/** Agent type (executor, coordinator, planner, judge, ideation, pr-reviewer) */
|
|
15
|
+
agentType?: string;
|
|
16
|
+
/** Current prompt number (e.g., "01") */
|
|
17
|
+
promptNumber?: string;
|
|
18
|
+
/** Current spec name */
|
|
19
|
+
specName?: string;
|
|
20
|
+
/** Output format */
|
|
21
|
+
json: boolean;
|
|
22
|
+
/** Verbose logging */
|
|
23
|
+
verbose: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface CommandResult<T = unknown> {
|
|
27
|
+
success: boolean;
|
|
28
|
+
data?: T;
|
|
29
|
+
error?: string;
|
|
30
|
+
details?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get context from environment variables (set by tmux window spawner)
|
|
35
|
+
*/
|
|
36
|
+
export function getEnvContext(): Partial<CommandContext> {
|
|
37
|
+
return {
|
|
38
|
+
agentType: process.env.AGENT_TYPE,
|
|
39
|
+
promptNumber: process.env.PROMPT_NUMBER,
|
|
40
|
+
specName: process.env.SPEC_NAME,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Format output based on context
|
|
46
|
+
*/
|
|
47
|
+
export function formatOutput<T>(result: CommandResult<T>, context: CommandContext): string {
|
|
48
|
+
if (context.json) {
|
|
49
|
+
return JSON.stringify(result, null, 2);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!result.success) {
|
|
53
|
+
let output = `Error: ${result.error}`;
|
|
54
|
+
if (result.details) {
|
|
55
|
+
output += `\n${result.details}`;
|
|
56
|
+
}
|
|
57
|
+
return output;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (typeof result.data === 'string') {
|
|
61
|
+
return result.data;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return JSON.stringify(result.data, null, 2);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Parse common command options into context
|
|
69
|
+
*/
|
|
70
|
+
export function parseContext(options: {
|
|
71
|
+
agent?: string;
|
|
72
|
+
json?: boolean;
|
|
73
|
+
verbose?: boolean;
|
|
74
|
+
}): CommandContext {
|
|
75
|
+
const envContext = getEnvContext();
|
|
76
|
+
return {
|
|
77
|
+
agentType: options.agent || envContext.agentType,
|
|
78
|
+
promptNumber: envContext.promptNumber,
|
|
79
|
+
specName: envContext.specName,
|
|
80
|
+
json: options.json ?? false,
|
|
81
|
+
verbose: options.verbose ?? false,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Execute a command with standard error handling and trace logging
|
|
87
|
+
*/
|
|
88
|
+
export async function executeCommand<T>(
|
|
89
|
+
name: string,
|
|
90
|
+
context: CommandContext,
|
|
91
|
+
fn: () => Promise<CommandResult<T>>,
|
|
92
|
+
args: Record<string, unknown> = {}
|
|
93
|
+
): Promise<void> {
|
|
94
|
+
// Log command start to trace store
|
|
95
|
+
logCommandStart(name, { ...args, context });
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const result = await fn();
|
|
99
|
+
|
|
100
|
+
const output = formatOutput(result, context);
|
|
101
|
+
if (result.success) {
|
|
102
|
+
// Log success to trace store
|
|
103
|
+
logCommandSuccess(name, { data: result.data });
|
|
104
|
+
console.log(output);
|
|
105
|
+
} else {
|
|
106
|
+
// Log error to trace store
|
|
107
|
+
logCommandError(name, result.error || 'Unknown error', args);
|
|
108
|
+
console.error(output);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
} catch (err) {
|
|
112
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
113
|
+
|
|
114
|
+
// Log error to trace store
|
|
115
|
+
logCommandError(name, error, args);
|
|
116
|
+
|
|
117
|
+
const result: CommandResult<T> = {
|
|
118
|
+
success: false,
|
|
119
|
+
error: `Command failed: ${error}`,
|
|
120
|
+
};
|
|
121
|
+
console.error(formatOutput(result, context));
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Add common options to a commander command
|
|
128
|
+
*/
|
|
129
|
+
export function addCommonOptions(cmd: { option: (flags: string, description: string) => unknown }) {
|
|
130
|
+
cmd.option('--agent <type>', 'Agent type for logging context');
|
|
131
|
+
cmd.option('--json', 'Output as JSON (for agent consumption)');
|
|
132
|
+
cmd.option('-v, --verbose', 'Enable verbose logging');
|
|
133
|
+
return cmd;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Wrap a Commander action handler with trace logging.
|
|
138
|
+
* Use this for commands that don't use executeCommand.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* .action(tracedAction('specs persist', async (path, options) => {
|
|
142
|
+
* // command implementation
|
|
143
|
+
* }))
|
|
144
|
+
*/
|
|
145
|
+
export function tracedAction<TArgs extends unknown[]>(
|
|
146
|
+
commandName: string,
|
|
147
|
+
handler: (...args: TArgs) => Promise<void> | void
|
|
148
|
+
): (...args: TArgs) => Promise<void> {
|
|
149
|
+
return async (...args: TArgs) => {
|
|
150
|
+
// Extract args for logging (filter out Commander object at the end)
|
|
151
|
+
const logArgs: Record<string, unknown> = {};
|
|
152
|
+
args.slice(0, -1).forEach((arg, i) => {
|
|
153
|
+
logArgs[`arg${i}`] = arg;
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
logCommandStart(commandName, logArgs);
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
await handler(...args);
|
|
160
|
+
// If we get here without process.exit, it succeeded
|
|
161
|
+
logCommandSuccess(commandName, {});
|
|
162
|
+
} catch (err) {
|
|
163
|
+
const error = err instanceof Error ? err.message : String(err);
|
|
164
|
+
logCommandError(commandName, error, logArgs);
|
|
165
|
+
throw err; // Re-throw to let Commander handle it
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* BaseCommand - Abstract base class for command implementations.
|
|
172
|
+
* Provides helper methods for success/error responses.
|
|
173
|
+
*/
|
|
174
|
+
export abstract class BaseCommand {
|
|
175
|
+
abstract readonly name: string;
|
|
176
|
+
abstract readonly description: string;
|
|
177
|
+
|
|
178
|
+
abstract defineArguments(cmd: import('commander').Command): void;
|
|
179
|
+
abstract execute(args: Record<string, unknown>): Promise<CommandResult>;
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Create a success result
|
|
183
|
+
*/
|
|
184
|
+
protected success<T>(data: T): CommandResult<T> {
|
|
185
|
+
return { success: true, data };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Create an error result
|
|
190
|
+
*/
|
|
191
|
+
protected error(code: string, message: string, details?: string): CommandResult {
|
|
192
|
+
return {
|
|
193
|
+
success: false,
|
|
194
|
+
error: `${code}: ${message}`,
|
|
195
|
+
details,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Daemon - TUI-hosted socket server for fast hook execution.
|
|
3
|
+
*
|
|
4
|
+
* When the TUI is running, hooks connect via Unix socket instead of
|
|
5
|
+
* spawning a fresh Node.js process. This eliminates ~400ms startup
|
|
6
|
+
* overhead per hook invocation.
|
|
7
|
+
*
|
|
8
|
+
* The daemon runs hooks by intercepting stdout and process.exit(),
|
|
9
|
+
* so hooks don't need any modification.
|
|
10
|
+
*
|
|
11
|
+
* Socket path: .allhands/harness/.cache/cli-daemon.sock
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { createServer, createConnection, type Server, type Socket } from 'net';
|
|
15
|
+
import { existsSync, mkdirSync, unlinkSync } from 'fs';
|
|
16
|
+
import { dirname, join } from 'path';
|
|
17
|
+
import type { HookInput } from '../hooks/shared.js';
|
|
18
|
+
|
|
19
|
+
// Signal class to catch process.exit() calls
|
|
20
|
+
class ExitSignal extends Error {
|
|
21
|
+
constructor(public code: number = 0) {
|
|
22
|
+
super(`Exit with code ${code}`);
|
|
23
|
+
this.name = 'ExitSignal';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Hook handler type - takes input and may call process.exit/write to stdout
|
|
28
|
+
type HookHandler = (input: HookInput) => void | Promise<void>;
|
|
29
|
+
|
|
30
|
+
// Registry of hook handlers, keyed by "category.name"
|
|
31
|
+
const handlers = new Map<string, HookHandler>();
|
|
32
|
+
|
|
33
|
+
export interface DaemonCommand {
|
|
34
|
+
cmd: 'hook' | 'ping' | 'shutdown' | 'list';
|
|
35
|
+
category?: string; // e.g., 'context', 'validation', 'observability'
|
|
36
|
+
name?: string; // e.g., 'tldr-inject', 'signature'
|
|
37
|
+
input?: HookInput;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get the socket path for the CLI daemon.
|
|
42
|
+
*/
|
|
43
|
+
export function getSocketPath(projectDir: string): string {
|
|
44
|
+
return join(projectDir, '.allhands', 'harness', '.cache', 'cli-daemon.sock');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check if the daemon socket exists.
|
|
49
|
+
*/
|
|
50
|
+
export function isDaemonRunning(projectDir: string): boolean {
|
|
51
|
+
return existsSync(getSocketPath(projectDir));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Register a hook handler for daemon mode.
|
|
56
|
+
*/
|
|
57
|
+
export function registerHandler(category: string, name: string, handler: HookHandler): void {
|
|
58
|
+
handlers.set(`${category}.${name}`, handler);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Run a hook handler with intercepted I/O.
|
|
63
|
+
* Captures stdout and catches process.exit() calls.
|
|
64
|
+
*/
|
|
65
|
+
async function runWithInterceptedIO(handler: HookHandler, input: HookInput): Promise<string> {
|
|
66
|
+
let output = '';
|
|
67
|
+
|
|
68
|
+
// Save originals
|
|
69
|
+
const originalExit = process.exit;
|
|
70
|
+
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
71
|
+
const originalViaDaemon = process.env.AH_VIA_DAEMON;
|
|
72
|
+
|
|
73
|
+
// Mark as running via daemon for trace logging
|
|
74
|
+
process.env.AH_VIA_DAEMON = '1';
|
|
75
|
+
|
|
76
|
+
// Intercept process.exit
|
|
77
|
+
process.exit = ((code?: number) => {
|
|
78
|
+
throw new ExitSignal(code ?? 0);
|
|
79
|
+
}) as typeof process.exit;
|
|
80
|
+
|
|
81
|
+
// Intercept stdout.write
|
|
82
|
+
process.stdout.write = ((
|
|
83
|
+
chunk: string | Uint8Array,
|
|
84
|
+
encodingOrCallback?: BufferEncoding | ((err?: Error) => void),
|
|
85
|
+
callback?: (err?: Error) => void
|
|
86
|
+
): boolean => {
|
|
87
|
+
const str = typeof chunk === 'string' ? chunk : chunk.toString();
|
|
88
|
+
output += str;
|
|
89
|
+
// Call callback if provided
|
|
90
|
+
const cb = typeof encodingOrCallback === 'function' ? encodingOrCallback : callback;
|
|
91
|
+
if (cb) cb();
|
|
92
|
+
return true;
|
|
93
|
+
}) as typeof process.stdout.write;
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
await handler(input);
|
|
97
|
+
} catch (e) {
|
|
98
|
+
if (!(e instanceof ExitSignal)) {
|
|
99
|
+
throw e;
|
|
100
|
+
}
|
|
101
|
+
// ExitSignal is expected - hook completed normally
|
|
102
|
+
} finally {
|
|
103
|
+
// Restore originals
|
|
104
|
+
process.exit = originalExit;
|
|
105
|
+
process.stdout.write = originalStdoutWrite;
|
|
106
|
+
if (originalViaDaemon === undefined) {
|
|
107
|
+
delete process.env.AH_VIA_DAEMON;
|
|
108
|
+
} else {
|
|
109
|
+
process.env.AH_VIA_DAEMON = originalViaDaemon;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return output.trim();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Process a daemon command.
|
|
118
|
+
*/
|
|
119
|
+
async function processCommand(command: DaemonCommand): Promise<unknown> {
|
|
120
|
+
switch (command.cmd) {
|
|
121
|
+
case 'hook': {
|
|
122
|
+
if (!command.category || !command.name) {
|
|
123
|
+
return { success: false, error: 'Missing category or name' };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const key = `${command.category}.${command.name}`;
|
|
127
|
+
const handler = handlers.get(key);
|
|
128
|
+
|
|
129
|
+
if (!handler) {
|
|
130
|
+
// Handler not registered - return fallback signal
|
|
131
|
+
return { success: true, output: '', fallback: true };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const input: HookInput = command.input || {};
|
|
136
|
+
// Claude Code PostToolUse sends tool_response; normalize to tool_result
|
|
137
|
+
if (input.tool_response !== undefined && input.tool_result === undefined) {
|
|
138
|
+
input.tool_result = input.tool_response;
|
|
139
|
+
}
|
|
140
|
+
const output = await runWithInterceptedIO(handler, input);
|
|
141
|
+
return { success: true, output };
|
|
142
|
+
} catch (e) {
|
|
143
|
+
return {
|
|
144
|
+
success: false,
|
|
145
|
+
error: e instanceof Error ? e.message : String(e),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
case 'list':
|
|
151
|
+
return {
|
|
152
|
+
success: true,
|
|
153
|
+
handlers: Array.from(handlers.keys()),
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
case 'ping':
|
|
157
|
+
return { success: true, pong: true, handlers: handlers.size };
|
|
158
|
+
|
|
159
|
+
case 'shutdown':
|
|
160
|
+
return { success: true, shutting_down: true };
|
|
161
|
+
|
|
162
|
+
default:
|
|
163
|
+
return { success: false, error: 'Unknown command' };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Handle a client connection.
|
|
169
|
+
*/
|
|
170
|
+
function createConnectionHandler() {
|
|
171
|
+
return function handleConnection(socket: Socket): void {
|
|
172
|
+
let buffer = '';
|
|
173
|
+
|
|
174
|
+
socket.on('data', async (data) => {
|
|
175
|
+
buffer += data.toString();
|
|
176
|
+
|
|
177
|
+
// Process complete JSON messages (newline-delimited)
|
|
178
|
+
const lines = buffer.split('\n');
|
|
179
|
+
buffer = lines.pop() ?? '';
|
|
180
|
+
|
|
181
|
+
for (const line of lines) {
|
|
182
|
+
if (!line.trim()) continue;
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const command = JSON.parse(line) as DaemonCommand;
|
|
186
|
+
const result = await processCommand(command);
|
|
187
|
+
socket.write(JSON.stringify(result) + '\n');
|
|
188
|
+
|
|
189
|
+
// Handle shutdown after responding
|
|
190
|
+
if (command.cmd === 'shutdown') {
|
|
191
|
+
process.nextTick(() => socket.end());
|
|
192
|
+
}
|
|
193
|
+
} catch (e) {
|
|
194
|
+
socket.write(JSON.stringify({
|
|
195
|
+
success: false,
|
|
196
|
+
error: e instanceof Error ? e.message : String(e),
|
|
197
|
+
}) + '\n');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
socket.on('error', () => {
|
|
203
|
+
// Client disconnected, ignore
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* CLI Daemon instance.
|
|
210
|
+
*/
|
|
211
|
+
export class CLIDaemon {
|
|
212
|
+
private server: Server | null = null;
|
|
213
|
+
private socketPath: string;
|
|
214
|
+
|
|
215
|
+
constructor(private projectDir: string) {
|
|
216
|
+
this.socketPath = getSocketPath(projectDir);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Start the daemon server.
|
|
221
|
+
*/
|
|
222
|
+
async start(): Promise<void> {
|
|
223
|
+
if (this.server) return;
|
|
224
|
+
|
|
225
|
+
// Ensure cache directory exists
|
|
226
|
+
const cacheDir = dirname(this.socketPath);
|
|
227
|
+
if (!existsSync(cacheDir)) {
|
|
228
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Check if socket exists and is in use
|
|
232
|
+
if (existsSync(this.socketPath)) {
|
|
233
|
+
const isInUse = await this.isSocketInUse();
|
|
234
|
+
if (isInUse) {
|
|
235
|
+
// Another daemon is running - don't start a second one
|
|
236
|
+
console.error('CLI daemon already running, skipping start');
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
// Socket exists but is stale - clean it up
|
|
240
|
+
try {
|
|
241
|
+
unlinkSync(this.socketPath);
|
|
242
|
+
} catch {
|
|
243
|
+
// Ignore
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Load and register hook handlers
|
|
248
|
+
await this.loadHandlers();
|
|
249
|
+
|
|
250
|
+
this.server = createServer(createConnectionHandler());
|
|
251
|
+
|
|
252
|
+
this.server.listen(this.socketPath, () => {
|
|
253
|
+
// Socket ready
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
this.server.on('error', (e) => {
|
|
257
|
+
console.error('CLI daemon error:', e);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Load hook modules and register their handlers.
|
|
263
|
+
* Each module exports handler functions that we register here.
|
|
264
|
+
*/
|
|
265
|
+
private async loadHandlers(): Promise<void> {
|
|
266
|
+
try {
|
|
267
|
+
// Import hook modules - they'll register their handlers
|
|
268
|
+
const contextModule = await import('../hooks/context.js');
|
|
269
|
+
const observabilityModule = await import('../hooks/observability.js');
|
|
270
|
+
const validationModule = await import('../hooks/validation.js');
|
|
271
|
+
const lifecycleModule = await import('../hooks/lifecycle.js');
|
|
272
|
+
const enforcementModule = await import('../hooks/enforcement.js');
|
|
273
|
+
|
|
274
|
+
// Register handlers from each module if they export a registerDaemonHandlers function
|
|
275
|
+
for (const mod of [contextModule, observabilityModule, validationModule, lifecycleModule, enforcementModule]) {
|
|
276
|
+
if (typeof mod.registerDaemonHandlers === 'function') {
|
|
277
|
+
mod.registerDaemonHandlers(registerHandler);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
} catch (e) {
|
|
281
|
+
console.error('Failed to load hook modules:', e);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Stop the daemon server.
|
|
287
|
+
*/
|
|
288
|
+
stop(): void {
|
|
289
|
+
if (!this.server) return;
|
|
290
|
+
|
|
291
|
+
this.server.close();
|
|
292
|
+
this.server = null;
|
|
293
|
+
|
|
294
|
+
// Clean up socket file
|
|
295
|
+
if (existsSync(this.socketPath)) {
|
|
296
|
+
try {
|
|
297
|
+
unlinkSync(this.socketPath);
|
|
298
|
+
} catch {
|
|
299
|
+
// Ignore
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Check if the socket is currently in use by another daemon.
|
|
306
|
+
* Attempts to connect to the socket - if successful, it's in use.
|
|
307
|
+
*/
|
|
308
|
+
private async isSocketInUse(): Promise<boolean> {
|
|
309
|
+
return new Promise((resolve) => {
|
|
310
|
+
const client = createConnection(this.socketPath);
|
|
311
|
+
|
|
312
|
+
const timeout = setTimeout(() => {
|
|
313
|
+
client.destroy();
|
|
314
|
+
resolve(false); // Timed out - socket is stale
|
|
315
|
+
}, 500);
|
|
316
|
+
|
|
317
|
+
client.on('connect', () => {
|
|
318
|
+
clearTimeout(timeout);
|
|
319
|
+
// Send a ping to verify it's actually a daemon
|
|
320
|
+
client.write('{"cmd":"ping"}\n');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
client.on('data', () => {
|
|
324
|
+
clearTimeout(timeout);
|
|
325
|
+
client.destroy();
|
|
326
|
+
resolve(true); // Got a response - daemon is running
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
client.on('error', () => {
|
|
330
|
+
clearTimeout(timeout);
|
|
331
|
+
client.destroy();
|
|
332
|
+
resolve(false); // Connection failed - socket is stale
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Get count of registered handlers.
|
|
339
|
+
*/
|
|
340
|
+
getHandlerCount(): number {
|
|
341
|
+
return handlers.size;
|
|
342
|
+
}
|
|
343
|
+
}
|