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,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Command (Agent-Facing)
|
|
3
|
+
*
|
|
4
|
+
* Outputs schema definitions for file types.
|
|
5
|
+
* Agents use this to understand how to write valid files.
|
|
6
|
+
*
|
|
7
|
+
* Usage: ah schema <type> [property]
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
12
|
+
import { join, dirname } from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import { parse, stringify } from 'yaml';
|
|
15
|
+
import { tracedAction } from '../lib/base-command.js';
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = dirname(__filename);
|
|
19
|
+
|
|
20
|
+
export function register(program: Command): void {
|
|
21
|
+
program
|
|
22
|
+
.command('schema <type> [property]')
|
|
23
|
+
.description('Output schema for a file type (prompt, alignment, spec, documentation). Optionally specify a top-level property to inspect.')
|
|
24
|
+
.option('--json', 'Output as JSON instead of YAML')
|
|
25
|
+
.action(tracedAction('schema', async (type: string, property: string | undefined, options: { json?: boolean }) => {
|
|
26
|
+
// Path: harness/src/commands/ -> harness/src/ -> harness/ -> .allhands/ -> schemas/
|
|
27
|
+
const schemaDir = join(__dirname, '..', '..', '..', 'schemas');
|
|
28
|
+
const schemaPath = join(schemaDir, `${type}.yaml`);
|
|
29
|
+
|
|
30
|
+
if (!existsSync(schemaPath)) {
|
|
31
|
+
const available = getAvailableSchemas(schemaDir);
|
|
32
|
+
console.error(`Schema not found: ${type}`);
|
|
33
|
+
console.error(`Available schemas: ${available.join(', ')}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const content = readFileSync(schemaPath, 'utf-8');
|
|
38
|
+
const parsed = parse(content);
|
|
39
|
+
|
|
40
|
+
// If a property is specified, extract only that top-level property
|
|
41
|
+
if (property) {
|
|
42
|
+
if (!(property in parsed)) {
|
|
43
|
+
const availableProps = Object.keys(parsed);
|
|
44
|
+
console.warn(`Warning: Property '${property}' not found in schema '${type}'`);
|
|
45
|
+
console.warn(`Available top-level properties: ${availableProps.join(', ')}`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const subset = { [property]: parsed[property] };
|
|
50
|
+
if (options.json) {
|
|
51
|
+
console.log(JSON.stringify(subset, null, 2));
|
|
52
|
+
} else {
|
|
53
|
+
console.log(stringify(subset));
|
|
54
|
+
}
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Output full schema
|
|
59
|
+
if (options.json) {
|
|
60
|
+
console.log(JSON.stringify(parsed, null, 2));
|
|
61
|
+
} else {
|
|
62
|
+
console.log(content);
|
|
63
|
+
}
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getAvailableSchemas(schemaDir: string): string[] {
|
|
68
|
+
if (!existsSync(schemaDir)) return [];
|
|
69
|
+
|
|
70
|
+
return readdirSync(schemaDir)
|
|
71
|
+
.filter((f) => f.endsWith('.yaml'))
|
|
72
|
+
.map((f) => f.replace('.yaml', ''));
|
|
73
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skills Command (Agent-Facing)
|
|
3
|
+
*
|
|
4
|
+
* Lists and discovers skills for domain expertise.
|
|
5
|
+
* Agents use this to find relevant skills for their tasks.
|
|
6
|
+
*
|
|
7
|
+
* Usage: ah skills list
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
|
12
|
+
import { join, dirname } from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import { parse as parseYaml } from 'yaml';
|
|
15
|
+
import { tracedAction } from '../lib/base-command.js';
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = dirname(__filename);
|
|
19
|
+
|
|
20
|
+
interface SkillFrontmatter {
|
|
21
|
+
name: string;
|
|
22
|
+
description: string;
|
|
23
|
+
globs: string[];
|
|
24
|
+
version?: string;
|
|
25
|
+
license?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface SkillEntry {
|
|
29
|
+
name: string;
|
|
30
|
+
description: string;
|
|
31
|
+
globs: string[];
|
|
32
|
+
file: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Extract frontmatter from markdown content
|
|
37
|
+
*/
|
|
38
|
+
function extractFrontmatter(content: string): Record<string, unknown> | null {
|
|
39
|
+
const frontmatterRegex = /^---\n([\s\S]*?)\n---/;
|
|
40
|
+
const match = content.match(frontmatterRegex);
|
|
41
|
+
|
|
42
|
+
if (!match) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
return parseYaml(match[1]) as Record<string, unknown>;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get the skills directory path
|
|
55
|
+
* Path: harness/src/commands/ -> harness/src/ -> harness/ -> .allhands/ -> skills/
|
|
56
|
+
*/
|
|
57
|
+
function getSkillsDir(): string {
|
|
58
|
+
return join(__dirname, '..', '..', '..', 'skills');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* List all skills by reading SKILL.md files and extracting frontmatter
|
|
63
|
+
*/
|
|
64
|
+
function listSkills(): SkillEntry[] {
|
|
65
|
+
const dir = getSkillsDir();
|
|
66
|
+
|
|
67
|
+
if (!existsSync(dir)) {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const entries = readdirSync(dir);
|
|
72
|
+
const skills: SkillEntry[] = [];
|
|
73
|
+
|
|
74
|
+
for (const entry of entries) {
|
|
75
|
+
const entryPath = join(dir, entry);
|
|
76
|
+
const stat = statSync(entryPath);
|
|
77
|
+
|
|
78
|
+
// Skip non-directories
|
|
79
|
+
if (!stat.isDirectory()) continue;
|
|
80
|
+
|
|
81
|
+
// Look for SKILL.md in the directory
|
|
82
|
+
const skillFile = join(entryPath, 'SKILL.md');
|
|
83
|
+
if (!existsSync(skillFile)) continue;
|
|
84
|
+
|
|
85
|
+
const content = readFileSync(skillFile, 'utf-8');
|
|
86
|
+
const frontmatter = extractFrontmatter(content) as SkillFrontmatter | null;
|
|
87
|
+
|
|
88
|
+
if (frontmatter && frontmatter.name && frontmatter.description && frontmatter.globs) {
|
|
89
|
+
skills.push({
|
|
90
|
+
name: frontmatter.name,
|
|
91
|
+
description: frontmatter.description,
|
|
92
|
+
globs: frontmatter.globs,
|
|
93
|
+
file: `.allhands/skills/${entry}/SKILL.md`,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return skills;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function register(program: Command): void {
|
|
102
|
+
const cmd = program
|
|
103
|
+
.command('skills')
|
|
104
|
+
.description('Discover and list skills for domain expertise');
|
|
105
|
+
|
|
106
|
+
cmd
|
|
107
|
+
.command('list')
|
|
108
|
+
.description('List all skills with their descriptions and glob patterns')
|
|
109
|
+
.option('--json', 'Output as JSON (default)')
|
|
110
|
+
.action(tracedAction('skills list', async () => {
|
|
111
|
+
const skills = listSkills();
|
|
112
|
+
|
|
113
|
+
if (skills.length === 0) {
|
|
114
|
+
console.log(JSON.stringify({
|
|
115
|
+
success: true,
|
|
116
|
+
skills: [],
|
|
117
|
+
message: 'No skills found. Create skills in .allhands/skills/<name>/SKILL.md using `ah schema skill` for the file structure.',
|
|
118
|
+
}, null, 2));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(JSON.stringify({
|
|
123
|
+
success: true,
|
|
124
|
+
skills,
|
|
125
|
+
count: skills.length,
|
|
126
|
+
}, null, 2));
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Solutions Command (Agent-Facing)
|
|
3
|
+
*
|
|
4
|
+
* Grep-based search for documented solutions in docs/solutions/.
|
|
5
|
+
* Uses frontmatter fields (tags, module, component, symptoms) for precise matching.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* ah solutions search <query> Search solutions by keywords
|
|
9
|
+
* ah solutions search <query> --full Include full content of matches
|
|
10
|
+
* ah solutions list List all solution categories
|
|
11
|
+
* ah solutions list <category> List solutions in a category
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Command } from 'commander';
|
|
15
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
16
|
+
import { basename, join } from 'path';
|
|
17
|
+
import { parse } from 'yaml';
|
|
18
|
+
import { tracedAction } from '../lib/base-command.js';
|
|
19
|
+
|
|
20
|
+
const getProjectRoot = (): string => {
|
|
21
|
+
return process.env.PROJECT_ROOT || process.cwd();
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const getSolutionsDir = (): string => {
|
|
25
|
+
return join(getProjectRoot(), 'docs', 'solutions');
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
interface SolutionFrontmatter {
|
|
29
|
+
title: string;
|
|
30
|
+
date: string;
|
|
31
|
+
spec?: string;
|
|
32
|
+
problem_type: string;
|
|
33
|
+
component: string;
|
|
34
|
+
symptoms: string[];
|
|
35
|
+
root_cause: string;
|
|
36
|
+
severity: string;
|
|
37
|
+
tags: string[];
|
|
38
|
+
source?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface SolutionMatch {
|
|
42
|
+
path: string;
|
|
43
|
+
frontmatter: SolutionFrontmatter;
|
|
44
|
+
score: number;
|
|
45
|
+
matchedFields: string[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Extract keywords from a search query.
|
|
50
|
+
* Handles quoted phrases and splits on whitespace.
|
|
51
|
+
*/
|
|
52
|
+
function extractKeywords(query: string): string[] {
|
|
53
|
+
const keywords: string[] = [];
|
|
54
|
+
|
|
55
|
+
// Extract quoted phrases first
|
|
56
|
+
const quotedRegex = /"([^"]+)"/g;
|
|
57
|
+
let match;
|
|
58
|
+
while ((match = quotedRegex.exec(query)) !== null) {
|
|
59
|
+
keywords.push(match[1].toLowerCase());
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Remove quoted phrases and split remaining on whitespace
|
|
63
|
+
const remaining = query.replace(quotedRegex, '').trim();
|
|
64
|
+
if (remaining) {
|
|
65
|
+
keywords.push(...remaining.toLowerCase().split(/\s+/).filter(k => k.length > 0));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return keywords;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Parse YAML frontmatter from a markdown file.
|
|
73
|
+
*/
|
|
74
|
+
function parseFrontmatter(filePath: string): SolutionFrontmatter | null {
|
|
75
|
+
try {
|
|
76
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
77
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
78
|
+
if (!fmMatch) return null;
|
|
79
|
+
|
|
80
|
+
const parsed = parse(fmMatch[1]) as SolutionFrontmatter;
|
|
81
|
+
return parsed;
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Score how well a solution matches the search keywords.
|
|
89
|
+
*/
|
|
90
|
+
function scoreSolution(fm: SolutionFrontmatter, keywords: string[]): { score: number; matchedFields: string[] } {
|
|
91
|
+
let score = 0;
|
|
92
|
+
const matchedFields: string[] = [];
|
|
93
|
+
|
|
94
|
+
for (const keyword of keywords) {
|
|
95
|
+
// Title match (high weight)
|
|
96
|
+
if (fm.title?.toLowerCase().includes(keyword)) {
|
|
97
|
+
score += 3;
|
|
98
|
+
if (!matchedFields.includes('title')) matchedFields.push('title');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Tags match (high weight)
|
|
102
|
+
if (fm.tags?.some(t => t.toLowerCase().includes(keyword))) {
|
|
103
|
+
score += 3;
|
|
104
|
+
if (!matchedFields.includes('tags')) matchedFields.push('tags');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Component match (medium weight)
|
|
108
|
+
if (fm.component?.toLowerCase().includes(keyword)) {
|
|
109
|
+
score += 2;
|
|
110
|
+
if (!matchedFields.includes('component')) matchedFields.push('component');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Symptoms match (medium weight)
|
|
114
|
+
if (fm.symptoms?.some(s => s.toLowerCase().includes(keyword))) {
|
|
115
|
+
score += 2;
|
|
116
|
+
if (!matchedFields.includes('symptoms')) matchedFields.push('symptoms');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Problem type match (low weight)
|
|
120
|
+
if (fm.problem_type?.toLowerCase().includes(keyword)) {
|
|
121
|
+
score += 1;
|
|
122
|
+
if (!matchedFields.includes('problem_type')) matchedFields.push('problem_type');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Root cause match (low weight)
|
|
126
|
+
if (fm.root_cause?.toLowerCase().replace(/_/g, ' ').includes(keyword)) {
|
|
127
|
+
score += 1;
|
|
128
|
+
if (!matchedFields.includes('root_cause')) matchedFields.push('root_cause');
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { score, matchedFields };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Find all solution files in the solutions directory.
|
|
137
|
+
*/
|
|
138
|
+
function findSolutionFiles(dir: string): string[] {
|
|
139
|
+
const files: string[] = [];
|
|
140
|
+
|
|
141
|
+
if (!existsSync(dir)) return files;
|
|
142
|
+
|
|
143
|
+
const entries = readdirSync(dir);
|
|
144
|
+
for (const entry of entries) {
|
|
145
|
+
const fullPath = join(dir, entry);
|
|
146
|
+
const stat = statSync(fullPath);
|
|
147
|
+
|
|
148
|
+
if (stat.isDirectory()) {
|
|
149
|
+
files.push(...findSolutionFiles(fullPath));
|
|
150
|
+
} else if (entry.endsWith('.md') && !entry.startsWith('README')) {
|
|
151
|
+
files.push(fullPath);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return files;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get all category directories.
|
|
160
|
+
*/
|
|
161
|
+
function getCategories(dir: string): string[] {
|
|
162
|
+
if (!existsSync(dir)) return [];
|
|
163
|
+
|
|
164
|
+
return readdirSync(dir)
|
|
165
|
+
.filter(entry => {
|
|
166
|
+
const fullPath = join(dir, entry);
|
|
167
|
+
return statSync(fullPath).isDirectory();
|
|
168
|
+
})
|
|
169
|
+
.sort();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Search for solutions matching the query.
|
|
174
|
+
*/
|
|
175
|
+
function searchSolutions(query: string, limit: number = 10): SolutionMatch[] {
|
|
176
|
+
const solutionsDir = getSolutionsDir();
|
|
177
|
+
const keywords = extractKeywords(query);
|
|
178
|
+
|
|
179
|
+
if (keywords.length === 0) {
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const files = findSolutionFiles(solutionsDir);
|
|
184
|
+
const matches: SolutionMatch[] = [];
|
|
185
|
+
|
|
186
|
+
for (const file of files) {
|
|
187
|
+
const frontmatter = parseFrontmatter(file);
|
|
188
|
+
if (!frontmatter) continue;
|
|
189
|
+
|
|
190
|
+
const { score, matchedFields } = scoreSolution(frontmatter, keywords);
|
|
191
|
+
|
|
192
|
+
if (score > 0) {
|
|
193
|
+
// Make path relative to project root
|
|
194
|
+
const relativePath = file.replace(getProjectRoot() + '/', '');
|
|
195
|
+
matches.push({
|
|
196
|
+
path: relativePath,
|
|
197
|
+
frontmatter,
|
|
198
|
+
score,
|
|
199
|
+
matchedFields,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Sort by score descending
|
|
205
|
+
matches.sort((a, b) => b.score - a.score);
|
|
206
|
+
|
|
207
|
+
return matches.slice(0, limit);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get full content of a solution file (without frontmatter).
|
|
212
|
+
*/
|
|
213
|
+
function getSolutionContent(filePath: string): string | null {
|
|
214
|
+
try {
|
|
215
|
+
const fullPath = filePath.startsWith('/') ? filePath : join(getProjectRoot(), filePath);
|
|
216
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
217
|
+
// Remove frontmatter
|
|
218
|
+
return content.replace(/^---\n[\s\S]*?\n---\n/, '').trim();
|
|
219
|
+
} catch {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function register(program: Command): void {
|
|
225
|
+
const solutionsCmd = program
|
|
226
|
+
.command('solutions')
|
|
227
|
+
.description('Search and browse documented solutions');
|
|
228
|
+
|
|
229
|
+
// Search command
|
|
230
|
+
solutionsCmd
|
|
231
|
+
.command('search <query>')
|
|
232
|
+
.description('Search solutions by keywords (searches title, tags, component, symptoms)')
|
|
233
|
+
.option('--full', 'Include full content of matched solutions')
|
|
234
|
+
.option('--limit <n>', 'Maximum number of results', '10')
|
|
235
|
+
.action(tracedAction('solutions search', async (query: string, options: { full?: boolean; limit?: string }) => {
|
|
236
|
+
const limit = parseInt(options.limit || '10', 10);
|
|
237
|
+
const matches = searchSolutions(query, limit);
|
|
238
|
+
|
|
239
|
+
if (matches.length === 0) {
|
|
240
|
+
console.log(JSON.stringify({
|
|
241
|
+
success: true,
|
|
242
|
+
query,
|
|
243
|
+
keywords: extractKeywords(query),
|
|
244
|
+
results: [],
|
|
245
|
+
message: 'No matching solutions found',
|
|
246
|
+
}, null, 2));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Format output
|
|
251
|
+
const results = matches.map(match => {
|
|
252
|
+
const result: Record<string, unknown> = {
|
|
253
|
+
path: match.path,
|
|
254
|
+
title: match.frontmatter.title,
|
|
255
|
+
score: match.score,
|
|
256
|
+
matched_fields: match.matchedFields,
|
|
257
|
+
severity: match.frontmatter.severity,
|
|
258
|
+
problem_type: match.frontmatter.problem_type,
|
|
259
|
+
component: match.frontmatter.component,
|
|
260
|
+
tags: match.frontmatter.tags,
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
if (options.full) {
|
|
264
|
+
result.content = getSolutionContent(match.path);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return result;
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
console.log(JSON.stringify({
|
|
271
|
+
success: true,
|
|
272
|
+
query,
|
|
273
|
+
keywords: extractKeywords(query),
|
|
274
|
+
result_count: results.length,
|
|
275
|
+
results,
|
|
276
|
+
}, null, 2));
|
|
277
|
+
}));
|
|
278
|
+
|
|
279
|
+
// List command
|
|
280
|
+
solutionsCmd
|
|
281
|
+
.command('list [category]')
|
|
282
|
+
.description('List solution categories or solutions in a category')
|
|
283
|
+
.action(tracedAction('solutions list', async (category?: string) => {
|
|
284
|
+
const solutionsDir = getSolutionsDir();
|
|
285
|
+
|
|
286
|
+
if (!category) {
|
|
287
|
+
// List all categories
|
|
288
|
+
const categories = getCategories(solutionsDir);
|
|
289
|
+
|
|
290
|
+
if (categories.length === 0) {
|
|
291
|
+
console.log(JSON.stringify({
|
|
292
|
+
success: true,
|
|
293
|
+
message: 'No solution categories found. Create docs/solutions/<category>/ directories.',
|
|
294
|
+
categories: [],
|
|
295
|
+
}, null, 2));
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Count solutions in each category
|
|
300
|
+
const categoryCounts = categories.map(cat => {
|
|
301
|
+
const catDir = join(solutionsDir, cat);
|
|
302
|
+
const files = findSolutionFiles(catDir);
|
|
303
|
+
return { category: cat, count: files.length };
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
console.log(JSON.stringify({
|
|
307
|
+
success: true,
|
|
308
|
+
categories: categoryCounts,
|
|
309
|
+
}, null, 2));
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// List solutions in specific category
|
|
314
|
+
const categoryDir = join(solutionsDir, category);
|
|
315
|
+
|
|
316
|
+
if (!existsSync(categoryDir)) {
|
|
317
|
+
const available = getCategories(solutionsDir);
|
|
318
|
+
console.log(JSON.stringify({
|
|
319
|
+
success: false,
|
|
320
|
+
error: `Category not found: ${category}`,
|
|
321
|
+
available_categories: available,
|
|
322
|
+
}, null, 2));
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const files = findSolutionFiles(categoryDir);
|
|
327
|
+
const solutions = files.map(file => {
|
|
328
|
+
const fm = parseFrontmatter(file);
|
|
329
|
+
const relativePath = file.replace(getProjectRoot() + '/', '');
|
|
330
|
+
return {
|
|
331
|
+
path: relativePath,
|
|
332
|
+
title: fm?.title || basename(file, '.md'),
|
|
333
|
+
date: fm?.date,
|
|
334
|
+
severity: fm?.severity,
|
|
335
|
+
tags: fm?.tags || [],
|
|
336
|
+
};
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Sort by date descending
|
|
340
|
+
solutions.sort((a, b) => {
|
|
341
|
+
if (!a.date) return 1;
|
|
342
|
+
if (!b.date) return -1;
|
|
343
|
+
return b.date.localeCompare(a.date);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
console.log(JSON.stringify({
|
|
347
|
+
success: true,
|
|
348
|
+
category,
|
|
349
|
+
solution_count: solutions.length,
|
|
350
|
+
solutions,
|
|
351
|
+
}, null, 2));
|
|
352
|
+
}));
|
|
353
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spawn commands - agent spawning via OpenCode SDK.
|
|
3
|
+
*
|
|
4
|
+
* Commands:
|
|
5
|
+
* ah spawn codesearch "<query>" [--budget <n>] [--steps <n>]
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
import { readFileSync } from "fs";
|
|
10
|
+
import { dirname, join } from "path";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
import { AgentRunner } from "../lib/opencode/index.js";
|
|
13
|
+
import { BaseCommand, type CommandResult } from "../lib/base-command.js";
|
|
14
|
+
import { loadProjectSettings } from "../hooks/shared.js";
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
|
|
18
|
+
const getProjectRoot = (): string => {
|
|
19
|
+
return process.env.PROJECT_ROOT || process.cwd();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Load prompt
|
|
23
|
+
const CODESEARCH_PROMPT_PATH = join(__dirname, "../lib/opencode/prompts/codesearch.md");
|
|
24
|
+
const getCodesearchPrompt = (): string => readFileSync(CODESEARCH_PROMPT_PATH, "utf-8");
|
|
25
|
+
|
|
26
|
+
// Defaults
|
|
27
|
+
const DEFAULT_TOOL_BUDGET = 12;
|
|
28
|
+
const DEFAULT_STEPS_LIMIT = 20;
|
|
29
|
+
const DEFAULT_TIMEOUT_MS = 120000; // 2 min
|
|
30
|
+
|
|
31
|
+
// Output types
|
|
32
|
+
interface CodeResult {
|
|
33
|
+
file: string;
|
|
34
|
+
line_start: number;
|
|
35
|
+
line_end: number;
|
|
36
|
+
code: string;
|
|
37
|
+
relevance: "high" | "medium" | "low";
|
|
38
|
+
match_type: "structural" | "text" | "semantic";
|
|
39
|
+
context?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface CodesearchOutput {
|
|
43
|
+
results: CodeResult[];
|
|
44
|
+
warnings: string[];
|
|
45
|
+
dev_notes: {
|
|
46
|
+
tool_budget_used: number;
|
|
47
|
+
tools_invoked: string[];
|
|
48
|
+
tools_failed: string[];
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Codesearch command - spawn code search agent with ast-grep, ripgrep, and LSP.
|
|
54
|
+
*/
|
|
55
|
+
class CodesearchCommand extends BaseCommand {
|
|
56
|
+
readonly name = "codesearch";
|
|
57
|
+
readonly description = "AI code search with structural (ast-grep), text (ripgrep), and semantic (LSP) tools";
|
|
58
|
+
|
|
59
|
+
defineArguments(cmd: Command): void {
|
|
60
|
+
cmd
|
|
61
|
+
.argument("<query>", "Code search query (natural language or pattern)")
|
|
62
|
+
.option("--budget <n>", "Soft tool budget hint for the agent", String(DEFAULT_TOOL_BUDGET))
|
|
63
|
+
.option("--steps <n>", "Hard step limit for agent iterations", String(DEFAULT_STEPS_LIMIT));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async execute(args: Record<string, unknown>): Promise<CommandResult> {
|
|
67
|
+
const query = args.query as string;
|
|
68
|
+
const settings = loadProjectSettings();
|
|
69
|
+
const toolBudget = parseInt(
|
|
70
|
+
(args.budget as string) ??
|
|
71
|
+
String(settings?.opencodeSdk?.codesearchToolBudget ?? DEFAULT_TOOL_BUDGET),
|
|
72
|
+
10
|
|
73
|
+
);
|
|
74
|
+
const stepsLimit = parseInt((args.steps as string) ?? String(DEFAULT_STEPS_LIMIT), 10);
|
|
75
|
+
|
|
76
|
+
if (!query) {
|
|
77
|
+
return this.error("validation_error", "query is required");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const projectRoot = getProjectRoot();
|
|
81
|
+
const runner = new AgentRunner(projectRoot);
|
|
82
|
+
|
|
83
|
+
// Build user message with budget context
|
|
84
|
+
const userMessage = `## Search Query
|
|
85
|
+
${query}
|
|
86
|
+
|
|
87
|
+
## Budget
|
|
88
|
+
- Tool budget (soft): ${toolBudget} tool calls
|
|
89
|
+
- Available tools: ast-grep MCP (structural), grep (text), read, lsp, glob
|
|
90
|
+
- Prioritize structural matches (ast-grep) for code patterns, text (grep) for literals/comments
|
|
91
|
+
|
|
92
|
+
Respond with JSON matching the required schema.`;
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const result = await runner.run<CodesearchOutput>(
|
|
96
|
+
{
|
|
97
|
+
name: "codesearch",
|
|
98
|
+
systemPrompt: getCodesearchPrompt(),
|
|
99
|
+
timeoutMs: DEFAULT_TIMEOUT_MS,
|
|
100
|
+
steps: stepsLimit,
|
|
101
|
+
// MCP servers can be configured via environment or passed explicitly
|
|
102
|
+
// mcp: {
|
|
103
|
+
// "ast-grep": {
|
|
104
|
+
// type: "local",
|
|
105
|
+
// command: ["uvx", "--from", "ast-grep-mcp", "ast-grep-mcp"],
|
|
106
|
+
// },
|
|
107
|
+
// },
|
|
108
|
+
},
|
|
109
|
+
userMessage
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
if (!result.success) {
|
|
113
|
+
return this.error("agent_error", result.error ?? "Unknown agent error");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const data = result.data!;
|
|
117
|
+
|
|
118
|
+
// Warnings are included in the response data
|
|
119
|
+
|
|
120
|
+
return this.success({
|
|
121
|
+
query,
|
|
122
|
+
result_count: data.results.length,
|
|
123
|
+
results: data.results,
|
|
124
|
+
warnings: data.warnings,
|
|
125
|
+
dev_notes: data.dev_notes,
|
|
126
|
+
metadata: result.metadata,
|
|
127
|
+
});
|
|
128
|
+
} catch (error) {
|
|
129
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
130
|
+
return this.error("spawn_error", message);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Register spawn commands on the given commander program.
|
|
137
|
+
*/
|
|
138
|
+
export function register(program: Command): void {
|
|
139
|
+
const spawnCmd = program
|
|
140
|
+
.command("spawn")
|
|
141
|
+
.description("Spawn sub-agents for specialized tasks");
|
|
142
|
+
|
|
143
|
+
const codesearch = new CodesearchCommand();
|
|
144
|
+
const cmd = spawnCmd.command(codesearch.name).description(codesearch.description);
|
|
145
|
+
codesearch.defineArguments(cmd);
|
|
146
|
+
cmd.action(async (...args) => {
|
|
147
|
+
const opts = args[args.length - 2] as Record<string, unknown>;
|
|
148
|
+
const cmdObj = args[args.length - 1] as Command;
|
|
149
|
+
const positionalArgs = cmdObj.args;
|
|
150
|
+
|
|
151
|
+
// Map positional args to named args based on command definition
|
|
152
|
+
const namedArgs: Record<string, unknown> = { ...opts };
|
|
153
|
+
if (positionalArgs[0]) namedArgs.query = positionalArgs[0];
|
|
154
|
+
|
|
155
|
+
const result = await codesearch.execute(namedArgs);
|
|
156
|
+
console.log(JSON.stringify(result, null, 2));
|
|
157
|
+
});
|
|
158
|
+
}
|