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,669 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E Tests - Validation Hook Contracts
|
|
3
|
+
*
|
|
4
|
+
* Integration tests for the validation hook pipeline:
|
|
5
|
+
* schema-pre (PreToolUse) → deny/allow before write
|
|
6
|
+
* schema (PostToolUse) → block/allow after write
|
|
7
|
+
* validation-tools list → CLI command output contract
|
|
8
|
+
*
|
|
9
|
+
* These tests exercise the full hook→schema→response pipeline using the
|
|
10
|
+
* hook-runner test harness, verifying that the actual enforcement mechanism
|
|
11
|
+
* agents encounter behaves correctly.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
15
|
+
import {
|
|
16
|
+
createFixture,
|
|
17
|
+
runHook,
|
|
18
|
+
runInFixture,
|
|
19
|
+
testHookContracts,
|
|
20
|
+
assertHookAllowed,
|
|
21
|
+
assertHookDenied,
|
|
22
|
+
assertDenialReasonContains,
|
|
23
|
+
assertContractsPassed,
|
|
24
|
+
assertJsonOutput,
|
|
25
|
+
PROMPT_TEMPLATE,
|
|
26
|
+
type TestFixture,
|
|
27
|
+
type HookContract,
|
|
28
|
+
} from '../harness/index.js';
|
|
29
|
+
|
|
30
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
31
|
+
// Fixtures
|
|
32
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
const VALID_PROMPT_CONTENT = PROMPT_TEMPLATE('pending', 'Hook Test Task', 1);
|
|
35
|
+
|
|
36
|
+
const VALID_PROMPT_IN_PROGRESS = PROMPT_TEMPLATE('in_progress', 'Active Task', 2);
|
|
37
|
+
|
|
38
|
+
const INVALID_PROMPT_BAD_STATUS = `---
|
|
39
|
+
number: 1
|
|
40
|
+
title: "Bad Status Task"
|
|
41
|
+
type: planned
|
|
42
|
+
status: garbage_value
|
|
43
|
+
dependencies: []
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
# Tasks
|
|
47
|
+
|
|
48
|
+
- This has an invalid status
|
|
49
|
+
|
|
50
|
+
# Acceptance Criteria
|
|
51
|
+
|
|
52
|
+
- N/A
|
|
53
|
+
`;
|
|
54
|
+
|
|
55
|
+
const INVALID_PROMPT_MISSING_STATUS = `---
|
|
56
|
+
number: 1
|
|
57
|
+
title: "Missing Status"
|
|
58
|
+
type: planned
|
|
59
|
+
dependencies: []
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
# Tasks
|
|
63
|
+
|
|
64
|
+
- Missing required status field
|
|
65
|
+
|
|
66
|
+
# Acceptance Criteria
|
|
67
|
+
|
|
68
|
+
- N/A
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
const INVALID_PROMPT_NO_FRONTMATTER = `# No Frontmatter
|
|
72
|
+
|
|
73
|
+
This file has no YAML frontmatter at all.
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
77
|
+
// schema-pre (PreToolUse) Hook Contracts
|
|
78
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
describe('Validation Hook Contracts E2E', () => {
|
|
81
|
+
let fixture: TestFixture;
|
|
82
|
+
|
|
83
|
+
beforeAll(() => {
|
|
84
|
+
fixture = createFixture({
|
|
85
|
+
name: 'validation-hook-contracts',
|
|
86
|
+
files: {
|
|
87
|
+
// Seed valid prompt for Edit tests
|
|
88
|
+
'.planning/test/prompts/01.md': PROMPT_TEMPLATE('pending', 'Seeded Task', 1),
|
|
89
|
+
'.planning/test/prompts/02.md': PROMPT_TEMPLATE('in_progress', 'Active Seeded', 2),
|
|
90
|
+
// Non-schema file
|
|
91
|
+
'src/app.ts': 'export const x = 1;\n',
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
afterAll(() => {
|
|
97
|
+
fixture.cleanup();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
101
|
+
// schema-pre: PreToolUse (deny/allow before write)
|
|
102
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
describe('schema-pre hook (PreToolUse)', () => {
|
|
105
|
+
describe('Write tool', () => {
|
|
106
|
+
it('allows valid prompt Write', async () => {
|
|
107
|
+
const result = await runHook(
|
|
108
|
+
'validation',
|
|
109
|
+
'schema-pre',
|
|
110
|
+
{
|
|
111
|
+
tool_name: 'Write',
|
|
112
|
+
tool_input: {
|
|
113
|
+
file_path: `${fixture.root}/.planning/test/prompts/new-valid.md`,
|
|
114
|
+
content: VALID_PROMPT_CONTENT,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
fixture
|
|
118
|
+
);
|
|
119
|
+
assertHookAllowed(result);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('denies Write with invalid status enum', async () => {
|
|
123
|
+
const result = await runHook(
|
|
124
|
+
'validation',
|
|
125
|
+
'schema-pre',
|
|
126
|
+
{
|
|
127
|
+
tool_name: 'Write',
|
|
128
|
+
tool_input: {
|
|
129
|
+
file_path: `${fixture.root}/.planning/test/prompts/bad-enum.md`,
|
|
130
|
+
content: INVALID_PROMPT_BAD_STATUS,
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
fixture
|
|
134
|
+
);
|
|
135
|
+
assertHookDenied(result);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('denies Write with invalid status enum and reason contains Schema Validation', async () => {
|
|
139
|
+
const result = await runHook(
|
|
140
|
+
'validation',
|
|
141
|
+
'schema-pre',
|
|
142
|
+
{
|
|
143
|
+
tool_name: 'Write',
|
|
144
|
+
tool_input: {
|
|
145
|
+
file_path: `${fixture.root}/.planning/test/prompts/bad-enum-2.md`,
|
|
146
|
+
content: INVALID_PROMPT_BAD_STATUS,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
fixture
|
|
150
|
+
);
|
|
151
|
+
assertDenialReasonContains(result, 'Schema Validation');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('denies Write missing required status field', async () => {
|
|
155
|
+
const result = await runHook(
|
|
156
|
+
'validation',
|
|
157
|
+
'schema-pre',
|
|
158
|
+
{
|
|
159
|
+
tool_name: 'Write',
|
|
160
|
+
tool_input: {
|
|
161
|
+
file_path: `${fixture.root}/.planning/test/prompts/no-status.md`,
|
|
162
|
+
content: INVALID_PROMPT_MISSING_STATUS,
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
fixture
|
|
166
|
+
);
|
|
167
|
+
assertHookDenied(result);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('denies Write without frontmatter', async () => {
|
|
171
|
+
const result = await runHook(
|
|
172
|
+
'validation',
|
|
173
|
+
'schema-pre',
|
|
174
|
+
{
|
|
175
|
+
tool_name: 'Write',
|
|
176
|
+
tool_input: {
|
|
177
|
+
file_path: `${fixture.root}/.planning/test/prompts/no-fm.md`,
|
|
178
|
+
content: INVALID_PROMPT_NO_FRONTMATTER,
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
fixture
|
|
182
|
+
);
|
|
183
|
+
assertHookDenied(result);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('denies Write without frontmatter and reason mentions frontmatter', async () => {
|
|
187
|
+
const result = await runHook(
|
|
188
|
+
'validation',
|
|
189
|
+
'schema-pre',
|
|
190
|
+
{
|
|
191
|
+
tool_name: 'Write',
|
|
192
|
+
tool_input: {
|
|
193
|
+
file_path: `${fixture.root}/.planning/test/prompts/no-fm-2.md`,
|
|
194
|
+
content: INVALID_PROMPT_NO_FRONTMATTER,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
fixture
|
|
198
|
+
);
|
|
199
|
+
assertDenialReasonContains(result, 'frontmatter');
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('allows Write to non-schema-managed file', async () => {
|
|
203
|
+
const result = await runHook(
|
|
204
|
+
'validation',
|
|
205
|
+
'schema-pre',
|
|
206
|
+
{
|
|
207
|
+
tool_name: 'Write',
|
|
208
|
+
tool_input: {
|
|
209
|
+
file_path: `${fixture.root}/src/new-file.ts`,
|
|
210
|
+
content: 'export const y = 2;\n',
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
fixture
|
|
214
|
+
);
|
|
215
|
+
assertHookAllowed(result);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('allows when file_path is missing from tool_input', async () => {
|
|
219
|
+
const result = await runHook(
|
|
220
|
+
'validation',
|
|
221
|
+
'schema-pre',
|
|
222
|
+
{
|
|
223
|
+
tool_name: 'Write',
|
|
224
|
+
tool_input: {},
|
|
225
|
+
},
|
|
226
|
+
fixture
|
|
227
|
+
);
|
|
228
|
+
assertHookAllowed(result);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('Edit tool', () => {
|
|
233
|
+
it('allows Edit that produces valid schema result', async () => {
|
|
234
|
+
const result = await runHook(
|
|
235
|
+
'validation',
|
|
236
|
+
'schema-pre',
|
|
237
|
+
{
|
|
238
|
+
tool_name: 'Edit',
|
|
239
|
+
tool_input: {
|
|
240
|
+
file_path: `${fixture.root}/.planning/test/prompts/01.md`,
|
|
241
|
+
old_string: 'status: pending',
|
|
242
|
+
new_string: 'status: in_progress',
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
fixture
|
|
246
|
+
);
|
|
247
|
+
assertHookAllowed(result);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('denies Edit that breaks schema (invalid status enum)', async () => {
|
|
251
|
+
const result = await runHook(
|
|
252
|
+
'validation',
|
|
253
|
+
'schema-pre',
|
|
254
|
+
{
|
|
255
|
+
tool_name: 'Edit',
|
|
256
|
+
tool_input: {
|
|
257
|
+
file_path: `${fixture.root}/.planning/test/prompts/01.md`,
|
|
258
|
+
old_string: 'status: pending',
|
|
259
|
+
new_string: 'status: garbage_value',
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
fixture
|
|
263
|
+
);
|
|
264
|
+
assertHookDenied(result);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('allows Edit on nonexistent file (early return)', async () => {
|
|
268
|
+
const result = await runHook(
|
|
269
|
+
'validation',
|
|
270
|
+
'schema-pre',
|
|
271
|
+
{
|
|
272
|
+
tool_name: 'Edit',
|
|
273
|
+
tool_input: {
|
|
274
|
+
file_path: `${fixture.root}/.planning/test/prompts/ghost.md`,
|
|
275
|
+
old_string: 'status: pending',
|
|
276
|
+
new_string: 'status: done',
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
fixture
|
|
280
|
+
);
|
|
281
|
+
assertHookAllowed(result);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
it('allows Edit when old_string/new_string missing (early return)', async () => {
|
|
285
|
+
const result = await runHook(
|
|
286
|
+
'validation',
|
|
287
|
+
'schema-pre',
|
|
288
|
+
{
|
|
289
|
+
tool_name: 'Edit',
|
|
290
|
+
tool_input: {
|
|
291
|
+
file_path: `${fixture.root}/.planning/test/prompts/01.md`,
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
fixture
|
|
295
|
+
);
|
|
296
|
+
assertHookAllowed(result);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it('allows Edit with replace_all that produces valid result', async () => {
|
|
300
|
+
const result = await runHook(
|
|
301
|
+
'validation',
|
|
302
|
+
'schema-pre',
|
|
303
|
+
{
|
|
304
|
+
tool_name: 'Edit',
|
|
305
|
+
tool_input: {
|
|
306
|
+
file_path: `${fixture.root}/.planning/test/prompts/01.md`,
|
|
307
|
+
old_string: 'pending',
|
|
308
|
+
new_string: 'in_progress',
|
|
309
|
+
replace_all: true,
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
fixture
|
|
313
|
+
);
|
|
314
|
+
assertHookAllowed(result);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it('allows Edit on non-schema-managed file', async () => {
|
|
318
|
+
const result = await runHook(
|
|
319
|
+
'validation',
|
|
320
|
+
'schema-pre',
|
|
321
|
+
{
|
|
322
|
+
tool_name: 'Edit',
|
|
323
|
+
tool_input: {
|
|
324
|
+
file_path: `${fixture.root}/src/app.ts`,
|
|
325
|
+
old_string: 'export const x = 1;',
|
|
326
|
+
new_string: 'export const x = 2;',
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
fixture
|
|
330
|
+
);
|
|
331
|
+
assertHookAllowed(result);
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
337
|
+
// schema: PostToolUse (block/allow after write)
|
|
338
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
339
|
+
|
|
340
|
+
describe('schema hook (PostToolUse)', () => {
|
|
341
|
+
it('allows after valid prompt file exists', async () => {
|
|
342
|
+
// Seed valid file
|
|
343
|
+
fixture.writeFile('.planning/post/prompts/valid.md', VALID_PROMPT_CONTENT);
|
|
344
|
+
|
|
345
|
+
const result = await runHook(
|
|
346
|
+
'validation',
|
|
347
|
+
'schema',
|
|
348
|
+
{
|
|
349
|
+
tool_name: 'Write',
|
|
350
|
+
tool_input: {
|
|
351
|
+
file_path: `${fixture.root}/.planning/post/prompts/valid.md`,
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
fixture
|
|
355
|
+
);
|
|
356
|
+
assertHookAllowed(result);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
it('allows after valid in_progress prompt file exists', async () => {
|
|
360
|
+
fixture.writeFile('.planning/post/prompts/active.md', VALID_PROMPT_IN_PROGRESS);
|
|
361
|
+
|
|
362
|
+
const result = await runHook(
|
|
363
|
+
'validation',
|
|
364
|
+
'schema',
|
|
365
|
+
{
|
|
366
|
+
tool_name: 'Write',
|
|
367
|
+
tool_input: {
|
|
368
|
+
file_path: `${fixture.root}/.planning/post/prompts/active.md`,
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
fixture
|
|
372
|
+
);
|
|
373
|
+
assertHookAllowed(result);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('blocks after invalid prompt file exists (bad enum)', async () => {
|
|
377
|
+
fixture.writeFile('.planning/post/prompts/bad-status.md', INVALID_PROMPT_BAD_STATUS);
|
|
378
|
+
|
|
379
|
+
const result = await runHook(
|
|
380
|
+
'validation',
|
|
381
|
+
'schema',
|
|
382
|
+
{
|
|
383
|
+
tool_name: 'Write',
|
|
384
|
+
tool_input: {
|
|
385
|
+
file_path: `${fixture.root}/.planning/post/prompts/bad-status.md`,
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
fixture
|
|
389
|
+
);
|
|
390
|
+
// blockTool outputs { decision: 'block', reason } format
|
|
391
|
+
const json = result.json as { decision?: string; reason?: string } | undefined;
|
|
392
|
+
expect(json?.decision).toBe('block');
|
|
393
|
+
expect(json?.reason).toContain('Schema Validation');
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('blocks after prompt file missing frontmatter', async () => {
|
|
397
|
+
fixture.writeFile('.planning/post/prompts/no-fm.md', INVALID_PROMPT_NO_FRONTMATTER);
|
|
398
|
+
|
|
399
|
+
const result = await runHook(
|
|
400
|
+
'validation',
|
|
401
|
+
'schema',
|
|
402
|
+
{
|
|
403
|
+
tool_name: 'Write',
|
|
404
|
+
tool_input: {
|
|
405
|
+
file_path: `${fixture.root}/.planning/post/prompts/no-fm.md`,
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
fixture
|
|
409
|
+
);
|
|
410
|
+
// blockTool outputs { decision: 'block', reason } format
|
|
411
|
+
const json = result.json as { decision?: string; reason?: string } | undefined;
|
|
412
|
+
expect(json?.decision).toBe('block');
|
|
413
|
+
expect(json?.reason).toContain('frontmatter');
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it('allows for non-schema-managed file', async () => {
|
|
417
|
+
const result = await runHook(
|
|
418
|
+
'validation',
|
|
419
|
+
'schema',
|
|
420
|
+
{
|
|
421
|
+
tool_name: 'Write',
|
|
422
|
+
tool_input: {
|
|
423
|
+
file_path: `${fixture.root}/src/app.ts`,
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
fixture
|
|
427
|
+
);
|
|
428
|
+
assertHookAllowed(result);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
it('allows when file_path is missing from tool_input', async () => {
|
|
432
|
+
const result = await runHook(
|
|
433
|
+
'validation',
|
|
434
|
+
'schema',
|
|
435
|
+
{
|
|
436
|
+
tool_name: 'Write',
|
|
437
|
+
tool_input: {},
|
|
438
|
+
},
|
|
439
|
+
fixture
|
|
440
|
+
);
|
|
441
|
+
assertHookAllowed(result);
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
446
|
+
// Declarative Contract Compliance Suite
|
|
447
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
448
|
+
|
|
449
|
+
describe('validation hook contract compliance', () => {
|
|
450
|
+
let contractFixture: TestFixture;
|
|
451
|
+
|
|
452
|
+
beforeAll(() => {
|
|
453
|
+
contractFixture = createFixture({
|
|
454
|
+
name: 'validation-contracts',
|
|
455
|
+
files: {
|
|
456
|
+
'.planning/c/prompts/01.md': PROMPT_TEMPLATE('pending', 'Contract Task', 1),
|
|
457
|
+
'.planning/c/prompts/bad.md': INVALID_PROMPT_BAD_STATUS,
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
afterAll(() => {
|
|
463
|
+
contractFixture.cleanup();
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
it('all schema-pre contracts pass', async () => {
|
|
467
|
+
const contracts: HookContract[] = [
|
|
468
|
+
{
|
|
469
|
+
name: 'schema-pre allows valid prompt Write',
|
|
470
|
+
hookType: 'validation',
|
|
471
|
+
hookName: 'schema-pre',
|
|
472
|
+
input: {
|
|
473
|
+
tool_name: 'Write',
|
|
474
|
+
tool_input: {
|
|
475
|
+
file_path: `${contractFixture.root}/.planning/c/prompts/new.md`,
|
|
476
|
+
content: VALID_PROMPT_CONTENT,
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
expect: {
|
|
480
|
+
success: true,
|
|
481
|
+
allowed: true,
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: 'schema-pre denies invalid enum Write',
|
|
486
|
+
hookType: 'validation',
|
|
487
|
+
hookName: 'schema-pre',
|
|
488
|
+
input: {
|
|
489
|
+
tool_name: 'Write',
|
|
490
|
+
tool_input: {
|
|
491
|
+
file_path: `${contractFixture.root}/.planning/c/prompts/bad-write.md`,
|
|
492
|
+
content: INVALID_PROMPT_BAD_STATUS,
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
expect: {
|
|
496
|
+
denied: true,
|
|
497
|
+
denialReasonContains: 'Schema Validation',
|
|
498
|
+
},
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
name: 'schema-pre allows non-schema Write',
|
|
502
|
+
hookType: 'validation',
|
|
503
|
+
hookName: 'schema-pre',
|
|
504
|
+
input: {
|
|
505
|
+
tool_name: 'Write',
|
|
506
|
+
tool_input: {
|
|
507
|
+
file_path: `${contractFixture.root}/src/utils.ts`,
|
|
508
|
+
content: 'export const z = 3;\n',
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
expect: {
|
|
512
|
+
success: true,
|
|
513
|
+
allowed: true,
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
];
|
|
517
|
+
|
|
518
|
+
const results = await testHookContracts(contracts, contractFixture);
|
|
519
|
+
assertContractsPassed(results);
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it('schema allows valid prompt file (PostToolUse)', async () => {
|
|
523
|
+
const result = await runHook(
|
|
524
|
+
'validation',
|
|
525
|
+
'schema',
|
|
526
|
+
{
|
|
527
|
+
tool_name: 'Write',
|
|
528
|
+
tool_input: {
|
|
529
|
+
file_path: `${contractFixture.root}/.planning/c/prompts/01.md`,
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
contractFixture
|
|
533
|
+
);
|
|
534
|
+
assertHookAllowed(result);
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it('schema blocks invalid prompt file (PostToolUse)', async () => {
|
|
538
|
+
const result = await runHook(
|
|
539
|
+
'validation',
|
|
540
|
+
'schema',
|
|
541
|
+
{
|
|
542
|
+
tool_name: 'Write',
|
|
543
|
+
tool_input: {
|
|
544
|
+
file_path: `${contractFixture.root}/.planning/c/prompts/bad.md`,
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
contractFixture
|
|
548
|
+
);
|
|
549
|
+
// blockTool uses { decision: 'block', reason } format
|
|
550
|
+
const json = result.json as { decision?: string } | undefined;
|
|
551
|
+
expect(json?.decision).toBe('block');
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
557
|
+
// validation-tools list Command Output Contract
|
|
558
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
559
|
+
|
|
560
|
+
describe('validation-tools list E2E', () => {
|
|
561
|
+
describe('with validation suites present', () => {
|
|
562
|
+
let fixture: TestFixture;
|
|
563
|
+
|
|
564
|
+
beforeAll(() => {
|
|
565
|
+
fixture = createFixture({
|
|
566
|
+
name: 'validation-tools-list-populated',
|
|
567
|
+
copyHarness: true,
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
afterAll(() => {
|
|
572
|
+
fixture.cleanup();
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it('returns JSON with success and suites array', async () => {
|
|
576
|
+
const result = await runInFixture(
|
|
577
|
+
fixture,
|
|
578
|
+
['validation-tools', 'list'],
|
|
579
|
+
{ expectJson: true }
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
assertJsonOutput(result, (json: { success: boolean; suites: unknown[] }) => {
|
|
583
|
+
return json.success === true && Array.isArray(json.suites);
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
it('suite entries contain required fields', async () => {
|
|
588
|
+
const result = await runInFixture(
|
|
589
|
+
fixture,
|
|
590
|
+
['validation-tools', 'list'],
|
|
591
|
+
{ expectJson: true }
|
|
592
|
+
);
|
|
593
|
+
|
|
594
|
+
if (result.json) {
|
|
595
|
+
const data = result.json as { suites: Record<string, unknown>[] };
|
|
596
|
+
for (const suite of data.suites) {
|
|
597
|
+
expect(suite).toHaveProperty('name');
|
|
598
|
+
expect(suite).toHaveProperty('description');
|
|
599
|
+
expect(suite).toHaveProperty('globs');
|
|
600
|
+
expect(suite).toHaveProperty('tools');
|
|
601
|
+
expect(suite).toHaveProperty('file');
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
it('tools field is a string array on each suite', async () => {
|
|
607
|
+
const result = await runInFixture(
|
|
608
|
+
fixture,
|
|
609
|
+
['validation-tools', 'list'],
|
|
610
|
+
{ expectJson: true }
|
|
611
|
+
);
|
|
612
|
+
|
|
613
|
+
if (result.json) {
|
|
614
|
+
const data = result.json as { suites: { tools: unknown }[] };
|
|
615
|
+
for (const suite of data.suites) {
|
|
616
|
+
expect(Array.isArray(suite.tools)).toBe(true);
|
|
617
|
+
for (const tool of suite.tools as unknown[]) {
|
|
618
|
+
expect(typeof tool).toBe('string');
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
describe('output structure invariants', () => {
|
|
626
|
+
let fixture: TestFixture;
|
|
627
|
+
|
|
628
|
+
beforeAll(() => {
|
|
629
|
+
fixture = createFixture({
|
|
630
|
+
name: 'validation-tools-list-structure',
|
|
631
|
+
copyHarness: true,
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
afterAll(() => {
|
|
636
|
+
fixture.cleanup();
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it('count field matches suites array length', async () => {
|
|
640
|
+
const result = await runInFixture(
|
|
641
|
+
fixture,
|
|
642
|
+
['validation-tools', 'list'],
|
|
643
|
+
{ expectJson: true }
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
if (result.json) {
|
|
647
|
+
const data = result.json as { suites: unknown[]; count?: number };
|
|
648
|
+
if (data.count !== undefined) {
|
|
649
|
+
expect(data.count).toBe(data.suites.length);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
it('file field follows .allhands/validation/ path pattern', async () => {
|
|
655
|
+
const result = await runInFixture(
|
|
656
|
+
fixture,
|
|
657
|
+
['validation-tools', 'list'],
|
|
658
|
+
{ expectJson: true }
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
if (result.json) {
|
|
662
|
+
const data = result.json as { suites: { file: string }[] };
|
|
663
|
+
for (const suite of data.suites) {
|
|
664
|
+
expect(suite.file).toMatch(/^\.allhands\/validation\/.*\.md$/);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
});
|