claude-dev-env 1.65.0 → 1.66.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.
Files changed (28) hide show
  1. package/agents/plan-packet-validator.md +34 -0
  2. package/audit-rubrics/category_rubrics/category-n-test-name-scenario-verifier.md +6 -0
  3. package/commands/plan.md +6 -52
  4. package/hooks/blocking/code_rules_dead_module_constant.py +111 -24
  5. package/hooks/blocking/code_rules_enforcer.py +2 -0
  6. package/hooks/blocking/code_rules_test_assertions.py +123 -1
  7. package/hooks/blocking/open_questions_in_plans_blocker.py +8 -1
  8. package/hooks/blocking/test_code_rules_enforcer_dead_module_constant.py +88 -0
  9. package/hooks/blocking/test_code_rules_enforcer_split_test_assertions.py +90 -0
  10. package/hooks/blocking/test_open_questions_in_plans_blocker.py +43 -0
  11. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
  12. package/hooks/hooks_constants/dead_module_constant_constants.py +1 -0
  13. package/hooks/hooks_constants/open_questions_in_plans_blocker_constants.py +4 -0
  14. package/hooks/hooks_constants/test_open_questions_in_plans_blocker_constants.py +13 -1
  15. package/package.json +1 -1
  16. package/skills/anthropic-plan/SKILL.md +46 -85
  17. package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/__init__.py +0 -0
  18. package/skills/anthropic-plan/scripts/anthropic_plan_scripts_constants/validate_packet_constants.py +33 -0
  19. package/skills/anthropic-plan/scripts/test_validate_packet.py +405 -0
  20. package/skills/anthropic-plan/scripts/validate_packet.py +397 -0
  21. package/skills/anthropic-plan/templates/README.md +20 -0
  22. package/skills/anthropic-plan/templates/build-prompt.md +9 -0
  23. package/skills/anthropic-plan/templates/source-map.md +5 -0
  24. package/skills/anthropic-plan/test_skill_contract.py +53 -0
  25. package/skills/anthropic-plan/workflow/plan-packet.contract.test.mjs +79 -0
  26. package/skills/anthropic-plan/workflow/plan-packet.mjs +299 -0
  27. package/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +8 -1
  28. package/skills/autoconverge/workflow/converge.mjs +9 -1
@@ -6,6 +6,8 @@ import sys
6
6
  from pathlib import Path
7
7
  from types import SimpleNamespace
8
8
 
9
+ import pytest
10
+
9
11
  _BLOCKING_DIRECTORY = str(Path(__file__).resolve().parent)
10
12
  _HOOKS_DIRECTORY = str(Path(__file__).resolve().parent.parent)
11
13
  if _BLOCKING_DIRECTORY not in sys.path:
@@ -15,14 +17,34 @@ if _HOOKS_DIRECTORY not in sys.path:
15
17
 
16
18
  from code_rules_test_assertions import ( # noqa: E402
17
19
  check_constant_equality_tests,
20
+ check_flag_gated_scenario_test_naming,
18
21
  )
19
22
 
20
23
  code_rules_enforcer = SimpleNamespace(
21
24
  check_constant_equality_tests=check_constant_equality_tests,
25
+ check_flag_gated_scenario_test_naming=check_flag_gated_scenario_test_naming,
22
26
  )
23
27
 
24
28
 
25
29
  CONSTANT_EQUALITY_TEST_FILE_PATH = "packages/app/tests/test_constants.py"
30
+ SCENARIO_TEST_FILE_PATH = "packages/app/tests/test_submission_runner_loop.py"
31
+
32
+ _THREE_SIBLINGS_PATCH_THE_FLAG_ONE_SCENARIO_TEST_DOES_NOT = (
33
+ "def test_should_submit_when_gate_passes(monkeypatch) -> None:\n"
34
+ " assert run() == 'submitted'\n"
35
+ "\n"
36
+ "def test_should_fail_when_reader_raises(monkeypatch) -> None:\n"
37
+ " monkeypatch.setattr('pkg.pipeline.IS_STAGED_VERIFICATION_ENABLED', True)\n"
38
+ " assert run() == 'failed'\n"
39
+ "\n"
40
+ "def test_should_soft_skip_when_mismatch(monkeypatch) -> None:\n"
41
+ " monkeypatch.setattr('pkg.pipeline.IS_STAGED_VERIFICATION_ENABLED', True)\n"
42
+ " assert run() == 'skipped'\n"
43
+ "\n"
44
+ "def test_should_hard_stop_when_unhealthy(monkeypatch) -> None:\n"
45
+ " monkeypatch.setattr('pkg.pipeline.IS_STAGED_VERIFICATION_ENABLED', True)\n"
46
+ " assert run() == 'hard_stop'\n"
47
+ )
26
48
 
27
49
 
28
50
  def test_should_not_flag_two_named_constants_compared_to_each_other() -> None:
@@ -54,3 +76,71 @@ def test_should_flag_named_constant_compared_to_literal() -> None:
54
76
  assert any("constant-value test" in issue for issue in issues), (
55
77
  f"Expected flag when UPPER_SNAKE compared to literal, got: {issues}"
56
78
  )
79
+
80
+
81
+ def test_should_advise_when_scenario_test_omits_flag_its_siblings_patch(
82
+ capsys: pytest.CaptureFixture[str],
83
+ ) -> None:
84
+ issues = code_rules_enforcer.check_flag_gated_scenario_test_naming(
85
+ _THREE_SIBLINGS_PATCH_THE_FLAG_ONE_SCENARIO_TEST_DOES_NOT,
86
+ SCENARIO_TEST_FILE_PATH,
87
+ )
88
+ advisory_text = capsys.readouterr().err
89
+ assert issues == [], "Advisory check must never add a blocking issue"
90
+ assert "test_should_submit_when_gate_passes" in advisory_text, (
91
+ f"Expected an advisory naming the un-patched scenario test, got: {advisory_text!r}"
92
+ )
93
+ assert "IS_STAGED_VERIFICATION_ENABLED" in advisory_text, (
94
+ f"Expected the advisory to name the established flag, got: {advisory_text!r}"
95
+ )
96
+
97
+
98
+ def test_should_stay_silent_when_scenario_test_patches_the_flag(
99
+ capsys: pytest.CaptureFixture[str],
100
+ ) -> None:
101
+ source = (
102
+ "def test_should_submit_when_gate_passes(monkeypatch) -> None:\n"
103
+ " monkeypatch.setattr('pkg.pipeline.IS_STAGED_VERIFICATION_ENABLED', True)\n"
104
+ " assert run() == 'submitted'\n"
105
+ "\n"
106
+ "def test_should_fail_when_reader_raises(monkeypatch) -> None:\n"
107
+ " monkeypatch.setattr('pkg.pipeline.IS_STAGED_VERIFICATION_ENABLED', True)\n"
108
+ " assert run() == 'failed'\n"
109
+ )
110
+ issues = code_rules_enforcer.check_flag_gated_scenario_test_naming(
111
+ source, SCENARIO_TEST_FILE_PATH
112
+ )
113
+ advisory_text = capsys.readouterr().err
114
+ assert issues == []
115
+ assert advisory_text == "", (
116
+ f"Expected silence when the scenario test patches the flag, got: {advisory_text!r}"
117
+ )
118
+
119
+
120
+ def test_should_stay_silent_when_only_one_sibling_patches_the_flag(
121
+ capsys: pytest.CaptureFixture[str],
122
+ ) -> None:
123
+ source = (
124
+ "def test_should_submit_when_gate_passes(monkeypatch) -> None:\n"
125
+ " assert run() == 'submitted'\n"
126
+ "\n"
127
+ "def test_should_fail_when_reader_raises(monkeypatch) -> None:\n"
128
+ " monkeypatch.setattr('pkg.pipeline.IS_STAGED_VERIFICATION_ENABLED', True)\n"
129
+ " assert run() == 'failed'\n"
130
+ )
131
+ issues = code_rules_enforcer.check_flag_gated_scenario_test_naming(
132
+ source, SCENARIO_TEST_FILE_PATH
133
+ )
134
+ advisory_text = capsys.readouterr().err
135
+ assert issues == []
136
+ assert advisory_text == "", (
137
+ f"One sibling patch is not an established flag; expected silence, got: {advisory_text!r}"
138
+ )
139
+
140
+
141
+ def test_should_not_advise_for_production_file() -> None:
142
+ issues = code_rules_enforcer.check_flag_gated_scenario_test_naming(
143
+ _THREE_SIBLINGS_PATCH_THE_FLAG_ONE_SCENARIO_TEST_DOES_NOT,
144
+ "packages/app/services/submission_pipeline.py",
145
+ )
146
+ assert issues == []
@@ -1,5 +1,6 @@
1
1
  """Tests for open_questions_in_plans_blocker hook."""
2
2
 
3
+ import ast
3
4
  import json
4
5
  import os
5
6
  import subprocess
@@ -10,6 +11,13 @@ HOOK_SCRIPT_PATH = os.path.join(
10
11
  os.path.dirname(__file__), "open_questions_in_plans_blocker.py"
11
12
  )
12
13
 
14
+
15
+ def _read_hook_module_docstring() -> str:
16
+ source_text = open(HOOK_SCRIPT_PATH, encoding="utf-8").read()
17
+ module_docstring = ast.get_docstring(ast.parse(source_text))
18
+ assert module_docstring is not None
19
+ return module_docstring
20
+
13
21
  _plan_with_open_questions = (
14
22
  "## Context\nA plan.\n\n## Open Questions\n- Which auth provider?\n"
15
23
  )
@@ -75,6 +83,41 @@ def test_blocks_project_local_plans_directory():
75
83
  assert output["hookSpecificOutput"]["permissionDecision"] == "deny"
76
84
 
77
85
 
86
+ def test_module_docstring_names_docs_plans_directory_family():
87
+ """The docstring enumerates every directory family the detector fires on,
88
+ including the repo-local `docs/plans/` family it now blocks."""
89
+ module_docstring = _read_hook_module_docstring()
90
+
91
+ assert "docs/plans/" in module_docstring
92
+
93
+
94
+ def test_blocks_repo_docs_plan_packet_directory():
95
+ """Repo-local `docs/plans/<slug>/` packet docs are plan files too."""
96
+ result = _run_hook(
97
+ "Write",
98
+ {
99
+ "file_path": "docs/plans/add-login/spec/scope.md",
100
+ "content": _plan_with_open_questions,
101
+ },
102
+ )
103
+ assert result.returncode == 0
104
+ output = json.loads(result.stdout)
105
+ assert output["hookSpecificOutput"]["permissionDecision"] == "deny"
106
+
107
+
108
+ def test_blocks_windows_style_repo_docs_plan_packet_directory():
109
+ result = _run_hook(
110
+ "Write",
111
+ {
112
+ "file_path": "docs\\plans\\add-login\\spec\\scope.md",
113
+ "content": _plan_with_open_questions,
114
+ },
115
+ )
116
+ assert result.returncode == 0
117
+ output = json.loads(result.stdout)
118
+ assert output["hookSpecificOutput"]["permissionDecision"] == "deny"
119
+
120
+
78
121
  def test_blocks_case_insensitive_heading():
79
122
  result = _run_hook(
80
123
  "Write",
@@ -13,6 +13,7 @@ from __future__ import annotations
13
13
 
14
14
  ALL_CONFIG_DIRECTORY_NAMES = frozenset(
15
15
  {
16
+ "anthropic_plan_scripts_constants",
16
17
  "config",
17
18
  "hooks_constants",
18
19
  "git_hooks_constants",
@@ -10,6 +10,7 @@ DUNDER_INIT_FILENAME: str = "__init__.py"
10
10
  CONSTANTS_MODULE_SUFFIX: str = "_constants.py"
11
11
  CONFIG_DIRECTORY_SEGMENT: str = "config"
12
12
  DUNDER_ALL_NAME: str = "__all__"
13
+ GIT_DIRECTORY_NAME: str = ".git"
13
14
  MINIMUM_UPPER_SNAKE_LENGTH: int = 2
14
15
  MAX_DEAD_MODULE_CONSTANT_ISSUES: int = 25
15
16
  MAX_SCAN_ROOT_FILE_COUNT: int = 2000
@@ -9,6 +9,8 @@ MARKDOWN_EXTENSION: str = ".md"
9
9
 
10
10
  PLANS_PATH_SEGMENT: str = "/.claude/plans/"
11
11
  PLANS_PATH_PREFIX: str = ".claude/plans/"
12
+ DOCS_PLANS_PATH_SEGMENT: str = "/docs/plans/"
13
+ DOCS_PLANS_PATH_PREFIX: str = "docs/plans/"
12
14
 
13
15
  PLAN_FILE_ENCODING: str = "utf-8"
14
16
 
@@ -25,6 +27,8 @@ INLINE_CODE_PATTERN: Pattern[str] = compile(r"``[^`\n]+``|`[^`\n]+`")
25
27
 
26
28
  __all__ = [
27
29
  "CODE_FENCE_PATTERN",
30
+ "DOCS_PLANS_PATH_PREFIX",
31
+ "DOCS_PLANS_PATH_SEGMENT",
28
32
  "INLINE_CODE_PATTERN",
29
33
  "MARKDOWN_EXTENSION",
30
34
  "OPEN_QUESTIONS_HEADING_PATTERN",
@@ -27,6 +27,16 @@ def test_plans_path_prefix_matches_project_local_plans_directory() -> None:
27
27
  assert "PLANS_PATH_PREFIX" in constants_module.__all__
28
28
 
29
29
 
30
+ def test_docs_plans_path_segment_matches_repo_packet_directory() -> None:
31
+ assert constants_module.DOCS_PLANS_PATH_SEGMENT == "/docs/plans/"
32
+ assert "DOCS_PLANS_PATH_SEGMENT" in constants_module.__all__
33
+
34
+
35
+ def test_docs_plans_path_prefix_matches_repo_packet_directory() -> None:
36
+ assert constants_module.DOCS_PLANS_PATH_PREFIX == "docs/plans/"
37
+ assert "DOCS_PLANS_PATH_PREFIX" in constants_module.__all__
38
+
39
+
30
40
  def test_open_questions_heading_pattern_matches_atx_heading() -> None:
31
41
  assert constants_module.OPEN_QUESTIONS_HEADING_PATTERN.search("## Open Questions\n")
32
42
 
@@ -111,9 +121,11 @@ def test_unreadable_file_synthetic_content_triggers_heading_pattern() -> None:
111
121
  assert "UNREADABLE_FILE_SYNTHETIC_CONTENT" in constants_module.__all__
112
122
 
113
123
 
114
- def test_all_exports_enumerates_eight_public_constants_in_sorted_order() -> None:
124
+ def test_all_exports_enumerates_ten_public_constants_in_sorted_order() -> None:
115
125
  expected_exports = [
116
126
  "CODE_FENCE_PATTERN",
127
+ "DOCS_PLANS_PATH_PREFIX",
128
+ "DOCS_PLANS_PATH_SEGMENT",
117
129
  "INLINE_CODE_PATTERN",
118
130
  "MARKDOWN_EXTENSION",
119
131
  "OPEN_QUESTIONS_HEADING_PATTERN",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "1.65.0",
3
+ "version": "1.66.0",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,107 +1,68 @@
1
1
  ---
2
2
  name: anthropic-plan
3
- description: Structured implementation planning through readonly codebase exploration before any code changes. Produces a plan file for approval. Use when the user says /anthropic-plan, "plan this first", "think before coding", "explore before implementing", "make a plan", or when approaching non-trivial tasks that benefit from upfront exploration and design. Also triggers on "what would the approach be", "scope this out", or "don't code yet, just plan".
3
+ description: Workflow-backed implementation planning that creates a deep repo-local packet under docs/plans/<slug>/ before any code changes. Use for /anthropic-plan, /plan, "plan this first", "think before coding", "make a plan", "scope this out", "don't code yet", and non-trivial implementation requests that need source-grounded design, TDD steps, and validator approval before build work.
4
4
  ---
5
5
 
6
- # Claude Plan
6
+ # Anthropic Plan
7
7
 
8
- Explore the codebase, design an approach, and write a plan file -- all without touching production code.
8
+ Create a source-grounded plan packet through the Claude Code Workflow runtime. The output is a repo-local `docs/plans/<slug>/` folder with context, spec, implementation, validation, and handoff docs. Stop before implementation.
9
9
 
10
- ## Why
10
+ ## Launch
11
11
 
12
- Jumping straight to code on non-trivial tasks leads to wasted effort when the approach conflicts with existing patterns, misses reusable code, or misunderstands the user's intent. This skill enforces "look before you leap": explore first, design second, write the plan third, get approval last. No code changes until the user says go.
12
+ Call the workflow with the user request and current working directory:
13
13
 
14
- ## Constraints
15
-
16
- Treat the codebase as readonly throughout this skill. The only file you may create or edit is the plan file.
17
-
18
- **Allowed:** Read files, Grep, Glob, launch Explore agents, launch Plan agents, write/edit the plan file, AskUserQuestion for clarification.
19
-
20
- **Not allowed:** Edit source files, Write new source files, run tests, install packages, run non-readonly Bash commands, or make any system changes.
21
-
22
- This discipline exists because the user invoked this skill specifically to understand the approach before committing to it. Violating readonly would undermine the whole point.
23
-
24
- ## Plan File
25
-
26
- Write to `~/.claude/plans/<slug>.md`.
27
-
28
- Generate the slug from the task -- descriptive, kebab-case, 2-4 words. Examples: `add-user-auth.md`, `fix-payment-retry.md`, `refactor-config-loading.md`. Avoid the random-word convention used by built-in plan mode.
29
-
30
- **Announce at start:** "Planning: `<slug>` -- exploring before writing code."
31
-
32
- ## Workflow
33
-
34
- ### Phase 1: Explore
35
-
36
- Understand the problem space before proposing solutions. Launch Explore agents in parallel -- up to 3, but use the minimum needed. Quality over quantity.
37
-
38
- What to look for:
39
- - Files and modules the task will touch
40
- - Existing patterns for similar functionality
41
- - Utilities, helpers, constants, and shared code to reuse
42
- - Test patterns already in place
43
-
44
- Skip this phase only if the task is trivial and full context already exists in the conversation.
45
-
46
- ### Phase 2: Design
47
-
48
- Launch Plan agent(s) to design the implementation -- up to 3 for complex or multi-area tasks, skip entirely for trivial tasks. Feed them comprehensive context from Phase 1; they cannot explore on their own, so everything they need must come from you.
49
-
50
- ### Phase 3: Review
51
-
52
- Before committing to the plan:
53
- - Read the critical files yourself to verify the agents got it right
54
- - Check alignment with what the user actually asked for
55
- - If requirements are ambiguous, use AskUserQuestion now -- not after writing the plan
56
-
57
- ### Phase 4: Write the Plan
14
+ ```js
15
+ Workflow({
16
+ scriptPath: "$HOME/.claude/skills/anthropic-plan/workflow/plan-packet.mjs",
17
+ input: {
18
+ task: "$ARGUMENTS",
19
+ cwd: "<current working directory>"
20
+ }
21
+ })
22
+ ```
58
23
 
59
- Write incrementally as you learn things in Phases 1-3, then refine here. The plan file has these sections:
24
+ If the Workflow tool is unavailable, say `anthropic-plan requires the Workflow tool; aborting` and stop.
60
25
 
61
- ```markdown
62
- ## Context
63
- Why this change is needed, what problem it solves, what the outcome looks like.
26
+ ## Workflow Contract
64
27
 
65
- ## Approach
66
- The recommended implementation. One approach, not a menu of alternatives.
67
- Concise but detailed enough to execute without re-exploring.
28
+ The workflow handles the full planning loop:
68
29
 
69
- ## Files
70
- Critical file paths that will be created or modified.
30
+ 1. Resolve repo root and packet path.
31
+ 2. Read project instructions, rules, relevant skills, manifests, docs, tests, hooks, agents, commands, configs, and workflows.
32
+ 3. Build a source inventory and extract source facts into `context/source-map.md`.
33
+ 4. Write the packet under `docs/plans/<slug>/`.
34
+ 5. Run `scripts/validate_packet.py`.
35
+ 6. Spawn `plan-packet-validator` in fresh context.
36
+ 7. Repair packet findings up to the workflow cap.
37
+ 8. Return packet path, validation state, and findings.
38
+ 9. Stop before implementation.
71
39
 
72
- ## Reuse
73
- Existing functions, utilities, constants, or patterns to leverage.
74
- Include file:line references so the implementer can find them instantly.
40
+ ## Packet Shape
75
41
 
76
- ## Steps
77
- Ordered implementation steps. Each step is a discrete, testable unit of work.
42
+ Required root: `docs/plans/<slug>/`
78
43
 
79
- ## Verification
80
- How to confirm end-to-end that the implementation works.
81
- Specific commands, test files, or manual verification steps.
44
+ Required top-level files and folders:
82
45
 
83
- ## Bash Permissions
84
- Semantic descriptions of bash actions the implementation will need:
85
- - "run tests"
86
- - "install dependencies"
87
- - "start dev server"
88
- These are action descriptions, not specific commands.
89
- ```
46
+ - `README.md`
47
+ - `packet.json`
48
+ - `context/`
49
+ - `spec/`
50
+ - `implementation/`
51
+ - `validation/`
52
+ - `handoff/`
90
53
 
91
- ### Phase 5: Present for Approval
54
+ The packet depth rule is strict: `README.md` is a thin hub, first-level folders group purpose, and second-level files carry detail. Add `context/subsystems/<name>.md` only when the planner finds more than twelve source files or more than three subsystems.
92
55
 
93
- Use AskUserQuestion to present the completed plan. Do not ask about approval in regular text -- always use AskUserQuestion so the user gets a clear, structured decision point.
56
+ ## Validation
94
57
 
95
- Options:
96
- - **Approve** -- proceed with implementation
97
- - **Revise** -- user has feedback to incorporate
98
- - **Cancel** -- abandon the plan
58
+ The deterministic validator checks required files, placeholders, `Open Questions`, source-map strength, TDD coverage, standalone handoff prompts, and `packet.json` consistency.
99
59
 
100
- ## Scaling
60
+ The `plan-packet-validator` agent checks source accuracy, scope, enough implementation detail for a blind build agent, real TDD order, invented APIs or commands, and end-to-end acceptance criteria.
101
61
 
102
- Not every task needs all five phases. Match effort to complexity:
62
+ ## Rules
103
63
 
104
- - **Trivial** (rename, typo fix): Ask if a formal plan is even wanted. If yes, skip Phases 1-2, write a minimal plan.
105
- - **Small** (single-file change, clear scope): One Explore agent, skip Design phase, concise plan.
106
- - **Medium** (multi-file feature, some ambiguity): Full workflow, 1-2 agents per phase.
107
- - **Large** (cross-cutting change, architectural): Full workflow, max agents, thorough Review phase.
64
+ - Write docs only.
65
+ - Do not edit production code.
66
+ - Do not run implementation commands.
67
+ - Ask the user only for product choices that cannot be derived from local context.
68
+ - Fold resolved answers into the packet. Never leave an `Open Questions` section.
@@ -0,0 +1,33 @@
1
+ """Constants for the plan packet validator."""
2
+
3
+ from __future__ import annotations
4
+
5
+ ALL_REQUIRED_RELATIVE_PATHS: tuple[str, ...] = (
6
+ "README.md",
7
+ "packet.json",
8
+ "context/user-request.md",
9
+ "context/source-map.md",
10
+ "context/current-state.md",
11
+ "context/existing-patterns.md",
12
+ "context/constraints.md",
13
+ "context/glossary.md",
14
+ "spec/scope.md",
15
+ "spec/behavior.md",
16
+ "spec/interfaces.md",
17
+ "spec/data-flow.md",
18
+ "spec/failure-modes.md",
19
+ "spec/acceptance.md",
20
+ "implementation/strategy.md",
21
+ "implementation/steps.md",
22
+ "implementation/tdd-plan.md",
23
+ "implementation/file-plan.md",
24
+ "implementation/refactor-checkpoints.md",
25
+ "validation/validator-report.md",
26
+ "validation/deterministic-checks.md",
27
+ "validation/unresolved-risks.md",
28
+ "handoff/build-prompt.md",
29
+ "handoff/review-prompt.md",
30
+ "handoff/verification-commands.md",
31
+ )
32
+ MARKDOWN_FILE_SUFFIX: str = ".md"
33
+ EXIT_CODE_VALIDATION_FAILED: int = 2