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,557 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation commands - validation and reference finalization.
|
|
3
|
+
*
|
|
4
|
+
* Uses ctags for symbol lookup (instead of AST parsing) for broader language support
|
|
5
|
+
* and simpler implementation.
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* ah docs validate [--path <path>] - Validate all refs in docs/
|
|
9
|
+
* ah docs finalize [--path <path>] - Finalize placeholder refs with hashes
|
|
10
|
+
* ah docs tree <path> - Get tree with doc coverage
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Command } from "commander";
|
|
14
|
+
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
15
|
+
import { dirname, extname, join, relative } from "path";
|
|
16
|
+
import {
|
|
17
|
+
addCommonOptions,
|
|
18
|
+
CommandResult,
|
|
19
|
+
executeCommand,
|
|
20
|
+
parseContext,
|
|
21
|
+
} from "../lib/base-command.js";
|
|
22
|
+
import {
|
|
23
|
+
checkCtagsAvailable,
|
|
24
|
+
findSymbolInFile,
|
|
25
|
+
generateCtagsIndex,
|
|
26
|
+
} from "../lib/ctags.js";
|
|
27
|
+
import {
|
|
28
|
+
batchGetBlobHashes,
|
|
29
|
+
findMarkdownFiles,
|
|
30
|
+
isCodeFile,
|
|
31
|
+
validateDocsAsync,
|
|
32
|
+
} from "../lib/docs-validation.js";
|
|
33
|
+
import { getProjectRoot } from "../lib/git.js";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Validate all documentation references.
|
|
37
|
+
*/
|
|
38
|
+
/** Paths excluded from validation and finalization (relative to project root). */
|
|
39
|
+
const EXCLUDED_DOC_PATHS = ["docs/memories.md", "docs/solutions"];
|
|
40
|
+
|
|
41
|
+
async function validate(docsPath: string, options?: { useCache?: boolean }): Promise<CommandResult> {
|
|
42
|
+
const projectRoot = getProjectRoot();
|
|
43
|
+
const absoluteDocsPath = docsPath.startsWith("/")
|
|
44
|
+
? docsPath
|
|
45
|
+
: join(projectRoot, docsPath);
|
|
46
|
+
|
|
47
|
+
// Check ctags availability first
|
|
48
|
+
const ctagsCheck = checkCtagsAvailable();
|
|
49
|
+
if (!ctagsCheck.available) {
|
|
50
|
+
return {
|
|
51
|
+
success: false,
|
|
52
|
+
error: `ctags_unavailable: ${ctagsCheck.error}`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const excludePaths = EXCLUDED_DOC_PATHS.map((p) => join(projectRoot, p));
|
|
57
|
+
|
|
58
|
+
// Run validation (with optional caching)
|
|
59
|
+
const result = await validateDocsAsync(absoluteDocsPath, projectRoot, {
|
|
60
|
+
useCache: options?.useCache ?? false,
|
|
61
|
+
excludePaths,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Consider it a success even with issues (issues are in the data)
|
|
65
|
+
return { success: true, data: result };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get tree structure with documentation coverage.
|
|
70
|
+
*/
|
|
71
|
+
async function tree(pathArg: string, maxDepth: number): Promise<CommandResult> {
|
|
72
|
+
const projectRoot = getProjectRoot();
|
|
73
|
+
const absolutePath = pathArg.startsWith("/")
|
|
74
|
+
? pathArg
|
|
75
|
+
: join(projectRoot, pathArg);
|
|
76
|
+
const relativePath = relative(projectRoot, absolutePath);
|
|
77
|
+
|
|
78
|
+
if (!existsSync(absolutePath)) {
|
|
79
|
+
return {
|
|
80
|
+
success: false,
|
|
81
|
+
error: `path_not_found: Path not found: ${relativePath}`,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const stat = statSync(absolutePath);
|
|
86
|
+
if (!stat.isDirectory()) {
|
|
87
|
+
return {
|
|
88
|
+
success: false,
|
|
89
|
+
error: `not_directory: ${relativePath} is not a directory`,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const docsPath = join(projectRoot, "docs");
|
|
94
|
+
|
|
95
|
+
interface TreeNode {
|
|
96
|
+
name: string;
|
|
97
|
+
type: "file" | "directory";
|
|
98
|
+
has_docs: boolean;
|
|
99
|
+
doc_path?: string;
|
|
100
|
+
children?: TreeNode[];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const sourceExtensions = [".ts", ".tsx", ".js", ".jsx", ".py", ".go", ".rs", ".java", ".rb"];
|
|
104
|
+
|
|
105
|
+
const buildTree = (dir: string, depth: number): TreeNode[] => {
|
|
106
|
+
if (depth <= 0) return [];
|
|
107
|
+
|
|
108
|
+
const entries = readdirSync(dir);
|
|
109
|
+
const nodes: TreeNode[] = [];
|
|
110
|
+
|
|
111
|
+
for (const entry of entries) {
|
|
112
|
+
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
113
|
+
|
|
114
|
+
const fullPath = join(dir, entry);
|
|
115
|
+
const entryRelPath = relative(projectRoot, fullPath);
|
|
116
|
+
const entryStat = statSync(fullPath);
|
|
117
|
+
|
|
118
|
+
// Check for docs coverage
|
|
119
|
+
const possibleDocPaths = [
|
|
120
|
+
join(docsPath, entryRelPath + ".md"),
|
|
121
|
+
join(docsPath, dirname(entryRelPath), entry.replace(extname(entry), ".md")),
|
|
122
|
+
join(docsPath, entryRelPath, "index.md"),
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
let hasDoc = false;
|
|
126
|
+
let docPath: string | undefined;
|
|
127
|
+
for (const dp of possibleDocPaths) {
|
|
128
|
+
if (existsSync(dp)) {
|
|
129
|
+
hasDoc = true;
|
|
130
|
+
docPath = relative(projectRoot, dp);
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (entryStat.isDirectory()) {
|
|
136
|
+
const children = buildTree(fullPath, depth - 1);
|
|
137
|
+
nodes.push({
|
|
138
|
+
name: entry,
|
|
139
|
+
type: "directory",
|
|
140
|
+
has_docs: hasDoc,
|
|
141
|
+
doc_path: docPath,
|
|
142
|
+
children: children.length > 0 ? children : undefined,
|
|
143
|
+
});
|
|
144
|
+
} else {
|
|
145
|
+
const ext = extname(entry);
|
|
146
|
+
if (sourceExtensions.includes(ext)) {
|
|
147
|
+
nodes.push({
|
|
148
|
+
name: entry,
|
|
149
|
+
type: "file",
|
|
150
|
+
has_docs: hasDoc,
|
|
151
|
+
doc_path: docPath,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return nodes;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const treeData = buildTree(absolutePath, maxDepth);
|
|
161
|
+
|
|
162
|
+
// Calculate coverage stats
|
|
163
|
+
const countNodes = (nodes: TreeNode[]): { total: number; covered: number } => {
|
|
164
|
+
let total = 0;
|
|
165
|
+
let covered = 0;
|
|
166
|
+
for (const node of nodes) {
|
|
167
|
+
total++;
|
|
168
|
+
if (node.has_docs) covered++;
|
|
169
|
+
if (node.children) {
|
|
170
|
+
const childStats = countNodes(node.children);
|
|
171
|
+
total += childStats.total;
|
|
172
|
+
covered += childStats.covered;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { total, covered };
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const stats = countNodes(treeData);
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
success: true,
|
|
182
|
+
data: {
|
|
183
|
+
path: relativePath,
|
|
184
|
+
tree: treeData,
|
|
185
|
+
coverage: {
|
|
186
|
+
total: stats.total,
|
|
187
|
+
covered: stats.covered,
|
|
188
|
+
percentage: stats.total > 0 ? Math.round((stats.covered / stats.total) * 100) : 0,
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Placeholder ref pattern - matches [ref:file:symbol] without hash
|
|
196
|
+
*/
|
|
197
|
+
// Matches both [ref:file:symbol] and [ref:file] (file-only refs)
|
|
198
|
+
const PLACEHOLDER_REF_PATTERN = /\[ref:([^:\]]+)(?::([^\]]*))?\]/g;
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Finalize a single documentation file by replacing placeholder refs with full refs.
|
|
202
|
+
*/
|
|
203
|
+
function finalizeSingleFile(
|
|
204
|
+
absolutePath: string,
|
|
205
|
+
projectRoot: string,
|
|
206
|
+
hashCache: Map<string, { success: boolean; hash?: string; error?: string }>
|
|
207
|
+
): { path: string; replacements: number; replaced: Array<{ from: string; to: string }>; errors: Array<{ placeholder: string; reason: string }> } {
|
|
208
|
+
const relativePath = relative(projectRoot, absolutePath);
|
|
209
|
+
const content = readFileSync(absolutePath, "utf-8");
|
|
210
|
+
|
|
211
|
+
// Find all placeholder refs (symbol may be undefined for file-only refs)
|
|
212
|
+
const placeholders: Array<{ match: string; file: string; symbol: string | undefined }> = [];
|
|
213
|
+
let match;
|
|
214
|
+
const pattern = new RegExp(PLACEHOLDER_REF_PATTERN.source, "g");
|
|
215
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
216
|
+
// Skip if it already looks like a full ref (has 3 colons indicating hash)
|
|
217
|
+
if (match[0].split(":").length > 3) continue;
|
|
218
|
+
placeholders.push({
|
|
219
|
+
match: match[0],
|
|
220
|
+
file: match[1],
|
|
221
|
+
symbol: match[2],
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (placeholders.length === 0) {
|
|
226
|
+
return { path: relativePath, replacements: 0, replaced: [], errors: [] };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Process each placeholder
|
|
230
|
+
const errors: Array<{ placeholder: string; reason: string }> = [];
|
|
231
|
+
const replacements: Array<{ from: string; to: string }> = [];
|
|
232
|
+
let finalizedContent = content;
|
|
233
|
+
|
|
234
|
+
for (const placeholder of placeholders) {
|
|
235
|
+
const absoluteFilePath = join(projectRoot, placeholder.file);
|
|
236
|
+
|
|
237
|
+
// Check file exists
|
|
238
|
+
if (!existsSync(absoluteFilePath)) {
|
|
239
|
+
errors.push({
|
|
240
|
+
placeholder: placeholder.match,
|
|
241
|
+
reason: `File not found: ${placeholder.file}`,
|
|
242
|
+
});
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Get hash from cache
|
|
247
|
+
const hashResult = hashCache.get(absoluteFilePath) || hashCache.get(placeholder.file);
|
|
248
|
+
if (!hashResult || !hashResult.success) {
|
|
249
|
+
errors.push({
|
|
250
|
+
placeholder: placeholder.match,
|
|
251
|
+
reason: `Git hash lookup failed for ${placeholder.file} (uncommitted file?)`,
|
|
252
|
+
});
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// For file-only refs (empty symbol), just add the hash
|
|
257
|
+
if (!placeholder.symbol || placeholder.symbol.trim() === "") {
|
|
258
|
+
const fullRef = `[ref:${placeholder.file}::${hashResult.hash}]`;
|
|
259
|
+
finalizedContent = finalizedContent.replaceAll(placeholder.match, fullRef);
|
|
260
|
+
replacements.push({ from: placeholder.match, to: fullRef });
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// For non-code files (markdown, yaml, json, etc.), treat symbol as a label (no ctags lookup)
|
|
265
|
+
if (!isCodeFile(placeholder.file)) {
|
|
266
|
+
const fullRef = `[ref:${placeholder.file}:${placeholder.symbol}:${hashResult.hash}]`;
|
|
267
|
+
finalizedContent = finalizedContent.replaceAll(placeholder.match, fullRef);
|
|
268
|
+
replacements.push({ from: placeholder.match, to: fullRef });
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// For code files with symbol refs, verify symbol exists via ctags
|
|
273
|
+
const entry = findSymbolInFile(absoluteFilePath, placeholder.symbol, projectRoot);
|
|
274
|
+
if (!entry) {
|
|
275
|
+
errors.push({
|
|
276
|
+
placeholder: placeholder.match,
|
|
277
|
+
reason: `Symbol '${placeholder.symbol}' not found in ${placeholder.file}`,
|
|
278
|
+
});
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Create full ref
|
|
283
|
+
const fullRef = `[ref:${placeholder.file}:${placeholder.symbol}:${hashResult.hash}]`;
|
|
284
|
+
finalizedContent = finalizedContent.replaceAll(placeholder.match, fullRef);
|
|
285
|
+
replacements.push({ from: placeholder.match, to: fullRef });
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Write back if there were successful replacements
|
|
289
|
+
if (replacements.length > 0) {
|
|
290
|
+
writeFileSync(absolutePath, finalizedContent, "utf-8");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return { path: relativePath, replacements: replacements.length, replaced: replacements, errors };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Refresh a single documentation file by updating all finalized ref hashes to current blob hashes.
|
|
298
|
+
*/
|
|
299
|
+
function refreshSingleFile(
|
|
300
|
+
absolutePath: string,
|
|
301
|
+
projectRoot: string,
|
|
302
|
+
hashCache: Map<string, { hash: string; success: boolean }>
|
|
303
|
+
): { path: string; updated: number; unchanged: number; errors: number } {
|
|
304
|
+
const relativePath = relative(projectRoot, absolutePath);
|
|
305
|
+
const content = readFileSync(absolutePath, "utf-8");
|
|
306
|
+
|
|
307
|
+
// Find all finalized refs matching REF_PATTERN
|
|
308
|
+
const pattern = new RegExp(REF_PATTERN.source, "g");
|
|
309
|
+
const matches: Array<{ full: string; file: string; symbol: string; hash: string }> = [];
|
|
310
|
+
let match;
|
|
311
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
312
|
+
matches.push({ full: match[0], file: match[1], symbol: match[2], hash: match[3] });
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (matches.length === 0) {
|
|
316
|
+
return { path: relativePath, updated: 0, unchanged: 0, errors: 0 };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
let updated = 0;
|
|
320
|
+
let unchanged = 0;
|
|
321
|
+
let errors = 0;
|
|
322
|
+
let refreshedContent = content;
|
|
323
|
+
|
|
324
|
+
for (const m of matches) {
|
|
325
|
+
const absoluteFilePath = join(projectRoot, m.file);
|
|
326
|
+
const hashResult = hashCache.get(absoluteFilePath) || hashCache.get(m.file);
|
|
327
|
+
if (!hashResult || !hashResult.success) {
|
|
328
|
+
errors++;
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (m.hash === hashResult.hash) {
|
|
333
|
+
unchanged++;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Replace old hash with current blob hash
|
|
338
|
+
const oldRef = m.full;
|
|
339
|
+
const newRef = m.symbol === ""
|
|
340
|
+
? `[ref:${m.file}::${hashResult.hash}]`
|
|
341
|
+
: `[ref:${m.file}:${m.symbol}:${hashResult.hash}]`;
|
|
342
|
+
refreshedContent = refreshedContent.replaceAll(oldRef, newRef);
|
|
343
|
+
updated++;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (updated > 0) {
|
|
347
|
+
writeFileSync(absolutePath, refreshedContent, "utf-8");
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return { path: relativePath, updated, unchanged, errors };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Finalize documentation files by replacing placeholder refs with full refs.
|
|
355
|
+
* Supports both single file and directory (batch) operation.
|
|
356
|
+
* This allows writers to use [ref:file:symbol] syntax without hashes during writing,
|
|
357
|
+
* then batch-process all refs in a single pass.
|
|
358
|
+
*
|
|
359
|
+
* When refresh=true, operates on ALL finalized refs (not just placeholders),
|
|
360
|
+
* replacing stored hashes with current blob hashes.
|
|
361
|
+
*/
|
|
362
|
+
async function finalize(docsPath: string, options?: { refresh?: boolean }): Promise<CommandResult> {
|
|
363
|
+
const projectRoot = getProjectRoot();
|
|
364
|
+
const absolutePath = docsPath.startsWith("/") ? docsPath : join(projectRoot, docsPath);
|
|
365
|
+
const relativePath = relative(projectRoot, absolutePath);
|
|
366
|
+
|
|
367
|
+
if (!existsSync(absolutePath)) {
|
|
368
|
+
return {
|
|
369
|
+
success: false,
|
|
370
|
+
error: `path_not_found: Path not found: ${relativePath}`,
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Check ctags availability
|
|
375
|
+
const ctagsCheck = checkCtagsAvailable();
|
|
376
|
+
if (!ctagsCheck.available) {
|
|
377
|
+
return {
|
|
378
|
+
success: false,
|
|
379
|
+
error: `ctags_unavailable: ${ctagsCheck.error}`,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Generate ctags index for symbol lookup
|
|
384
|
+
generateCtagsIndex(projectRoot);
|
|
385
|
+
|
|
386
|
+
// Determine if path is a file or directory
|
|
387
|
+
const excludePaths = EXCLUDED_DOC_PATHS.map((p) => join(projectRoot, p));
|
|
388
|
+
const stat = statSync(absolutePath);
|
|
389
|
+
const filesToProcess: string[] = stat.isDirectory()
|
|
390
|
+
? findMarkdownFiles(absolutePath, false, excludePaths)
|
|
391
|
+
: excludePaths.some((ep) => absolutePath === ep || absolutePath.startsWith(ep + "/"))
|
|
392
|
+
? []
|
|
393
|
+
: [absolutePath];
|
|
394
|
+
|
|
395
|
+
if (filesToProcess.length === 0) {
|
|
396
|
+
return {
|
|
397
|
+
success: true,
|
|
398
|
+
data: {
|
|
399
|
+
message: "No markdown files found",
|
|
400
|
+
path: relativePath,
|
|
401
|
+
filesProcessed: 0,
|
|
402
|
+
totalReplacements: 0,
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// --refresh mode: update all finalized ref hashes to current blob hashes
|
|
408
|
+
if (options?.refresh) {
|
|
409
|
+
// Collect all referenced files from finalized refs across all files
|
|
410
|
+
const allRefFiles: Set<string> = new Set();
|
|
411
|
+
for (const docFile of filesToProcess) {
|
|
412
|
+
const content = readFileSync(docFile, "utf-8");
|
|
413
|
+
const refPattern = new RegExp(REF_PATTERN.source, "g");
|
|
414
|
+
let match;
|
|
415
|
+
while ((match = refPattern.exec(content)) !== null) {
|
|
416
|
+
allRefFiles.add(match[1]);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Batch get blob hashes for all referenced files
|
|
421
|
+
const absoluteRefFiles = [...allRefFiles].map((f) => join(projectRoot, f));
|
|
422
|
+
const refreshHashCache = batchGetBlobHashes(absoluteRefFiles, projectRoot);
|
|
423
|
+
|
|
424
|
+
// Process each file
|
|
425
|
+
let totalUpdated = 0;
|
|
426
|
+
let totalUnchanged = 0;
|
|
427
|
+
let totalRefreshErrors = 0;
|
|
428
|
+
const refreshResults: Array<{ path: string; updated: number; unchanged: number; errors: number }> = [];
|
|
429
|
+
|
|
430
|
+
for (const docFile of filesToProcess) {
|
|
431
|
+
const result = refreshSingleFile(docFile, projectRoot, refreshHashCache);
|
|
432
|
+
refreshResults.push(result);
|
|
433
|
+
totalUpdated += result.updated;
|
|
434
|
+
totalUnchanged += result.unchanged;
|
|
435
|
+
totalRefreshErrors += result.errors;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
success: totalRefreshErrors === 0,
|
|
440
|
+
error: totalRefreshErrors > 0 ? `Refresh had ${totalRefreshErrors} hash lookup error(s)` : undefined,
|
|
441
|
+
data: {
|
|
442
|
+
mode: "refresh",
|
|
443
|
+
path: relativePath,
|
|
444
|
+
filesProcessed: filesToProcess.length,
|
|
445
|
+
totalUpdated,
|
|
446
|
+
totalUnchanged,
|
|
447
|
+
totalErrors: totalRefreshErrors,
|
|
448
|
+
files: refreshResults.filter((r) => r.updated > 0 || r.errors > 0),
|
|
449
|
+
},
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Collect all placeholder refs across all files to batch hash lookup
|
|
454
|
+
const allPlaceholders: Array<{ file: string; docPath: string }> = [];
|
|
455
|
+
for (const docFile of filesToProcess) {
|
|
456
|
+
const content = readFileSync(docFile, "utf-8");
|
|
457
|
+
const pattern = new RegExp(PLACEHOLDER_REF_PATTERN.source, "g");
|
|
458
|
+
let match;
|
|
459
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
460
|
+
if (match[0].split(":").length > 3) continue;
|
|
461
|
+
allPlaceholders.push({ file: match[1], docPath: docFile });
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Batch get file hashes for all referenced files
|
|
466
|
+
const uniqueFiles = [...new Set(allPlaceholders.map((p) => p.file))];
|
|
467
|
+
const absoluteFiles = uniqueFiles.map((f) => join(projectRoot, f));
|
|
468
|
+
const hashCache = batchGetBlobHashes(absoluteFiles, projectRoot);
|
|
469
|
+
|
|
470
|
+
// Process each file
|
|
471
|
+
const results: Array<{ path: string; replacements: number; errors: number }> = [];
|
|
472
|
+
let totalReplacements = 0;
|
|
473
|
+
let totalErrors = 0;
|
|
474
|
+
const allErrors: Array<{ file: string; placeholder: string; reason: string }> = [];
|
|
475
|
+
|
|
476
|
+
for (const docFile of filesToProcess) {
|
|
477
|
+
const result = finalizeSingleFile(docFile, projectRoot, hashCache);
|
|
478
|
+
results.push({
|
|
479
|
+
path: result.path,
|
|
480
|
+
replacements: result.replacements,
|
|
481
|
+
errors: result.errors.length,
|
|
482
|
+
});
|
|
483
|
+
totalReplacements += result.replacements;
|
|
484
|
+
totalErrors += result.errors.length;
|
|
485
|
+
for (const err of result.errors) {
|
|
486
|
+
allErrors.push({ file: result.path, ...err });
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const hasErrors = totalErrors > 0;
|
|
491
|
+
return {
|
|
492
|
+
success: !hasErrors,
|
|
493
|
+
error: hasErrors ? `Finalization had ${totalErrors} error(s) in ${allErrors.length} file(s)` : undefined,
|
|
494
|
+
data: {
|
|
495
|
+
path: relativePath,
|
|
496
|
+
filesProcessed: filesToProcess.length,
|
|
497
|
+
totalReplacements,
|
|
498
|
+
totalErrors,
|
|
499
|
+
files: results.filter((r) => r.replacements > 0 || r.errors > 0),
|
|
500
|
+
errors: allErrors.length > 0 ? allErrors : undefined,
|
|
501
|
+
},
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Register docs commands.
|
|
507
|
+
*/
|
|
508
|
+
export function register(program: Command): void {
|
|
509
|
+
const docs = program.command("docs").description("Documentation management and validation");
|
|
510
|
+
|
|
511
|
+
// validate
|
|
512
|
+
const validateCmd = docs
|
|
513
|
+
.command("validate")
|
|
514
|
+
.description("Validate all documentation references")
|
|
515
|
+
.option("--path <path>", "Docs directory path", "docs/")
|
|
516
|
+
.option("--cache", "Use validation cache for faster repeated runs", false);
|
|
517
|
+
|
|
518
|
+
addCommonOptions(validateCmd);
|
|
519
|
+
|
|
520
|
+
validateCmd.action(async (options) => {
|
|
521
|
+
const context = parseContext(options);
|
|
522
|
+
await executeCommand("docs:validate", context, () =>
|
|
523
|
+
validate(options.path, { useCache: options.cache })
|
|
524
|
+
);
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
// tree
|
|
528
|
+
const treeCmd = docs
|
|
529
|
+
.command("tree")
|
|
530
|
+
.description("Get tree structure with documentation coverage")
|
|
531
|
+
.argument("<path>", "Directory path")
|
|
532
|
+
.option("--depth <n>", "Max depth to traverse", "3");
|
|
533
|
+
|
|
534
|
+
addCommonOptions(treeCmd);
|
|
535
|
+
|
|
536
|
+
treeCmd.action(async (path: string, options) => {
|
|
537
|
+
const context = parseContext(options);
|
|
538
|
+
const depth = parseInt(options.depth || "3", 10);
|
|
539
|
+
await executeCommand("docs:tree", context, () => tree(path, depth));
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// finalize
|
|
543
|
+
const finalizeCmd = docs
|
|
544
|
+
.command("finalize")
|
|
545
|
+
.description("Replace placeholder refs [ref:file:symbol] with full refs including hashes")
|
|
546
|
+
.option("--path <path>", "Docs path (file or directory)", "docs/")
|
|
547
|
+
.option("--refresh", "Re-stamp all finalized refs with current blob hashes", false);
|
|
548
|
+
|
|
549
|
+
addCommonOptions(finalizeCmd);
|
|
550
|
+
|
|
551
|
+
finalizeCmd.action(async (options) => {
|
|
552
|
+
const context = parseContext(options);
|
|
553
|
+
await executeCommand("docs:finalize", context, () =>
|
|
554
|
+
finalize(options.path, { refresh: options.refresh })
|
|
555
|
+
);
|
|
556
|
+
});
|
|
557
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hooks Command (Hidden)
|
|
3
|
+
*
|
|
4
|
+
* Internal command for Claude Code hook handlers.
|
|
5
|
+
* All hooks read JSON from stdin and output JSON to stdout.
|
|
6
|
+
*
|
|
7
|
+
* Subcommands are auto-discovered from src/hooks/*.ts files.
|
|
8
|
+
* Each hook module exports a `register(parent: Command)` function.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* echo '{"tool_name":"WebFetch","tool_input":{"url":"..."}}' | ah hooks enforcement github-url
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { Command } from 'commander';
|
|
15
|
+
import { discoverAndRegisterHooks } from '../hooks/index.js';
|
|
16
|
+
|
|
17
|
+
export function register(program: Command): void {
|
|
18
|
+
const hooks = program
|
|
19
|
+
.command('hooks', { hidden: true })
|
|
20
|
+
.description('Hook commands (internal use)');
|
|
21
|
+
|
|
22
|
+
// Auto-discover and register hook subcommands
|
|
23
|
+
discoverAndRegisterHooks(hooks);
|
|
24
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Registry - Auto-discovers command modules.
|
|
3
|
+
*
|
|
4
|
+
* Each command file should export a `register` function that takes
|
|
5
|
+
* a Commander program and registers its commands.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { readdirSync, statSync } from 'fs';
|
|
9
|
+
import { dirname, join } from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import type { Command } from 'commander';
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
|
|
15
|
+
export interface CommandModule {
|
|
16
|
+
register: (program: Command) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Auto-discover and register all command modules in the commands directory.
|
|
21
|
+
*
|
|
22
|
+
* Discovers:
|
|
23
|
+
* - Single-file modules: foo.ts exports { register }
|
|
24
|
+
* - Skips: index.ts, base files
|
|
25
|
+
*/
|
|
26
|
+
export async function discoverAndRegister(program: Command): Promise<void> {
|
|
27
|
+
const entries = readdirSync(__dirname);
|
|
28
|
+
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
const entryPath = join(__dirname, entry);
|
|
31
|
+
const stat = statSync(entryPath);
|
|
32
|
+
|
|
33
|
+
// Skip directories and non-ts files
|
|
34
|
+
if (stat.isDirectory()) continue;
|
|
35
|
+
if (!entry.endsWith('.ts')) continue;
|
|
36
|
+
if (entry === 'index.ts') continue;
|
|
37
|
+
|
|
38
|
+
const moduleName = entry.replace('.ts', '');
|
|
39
|
+
const importPath = `./${moduleName}.js`;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const module = (await import(importPath)) as CommandModule;
|
|
43
|
+
if (typeof module.register === 'function') {
|
|
44
|
+
module.register(program);
|
|
45
|
+
}
|
|
46
|
+
} catch (e) {
|
|
47
|
+
// Skip modules with errors - log for debugging
|
|
48
|
+
console.error(`Warning: Could not load ${moduleName}: ${e}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|