Dev10x 0.64.0__py3-none-any.whl

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 (84) hide show
  1. dev10x/__init__.py +1 -0
  2. dev10x/cli.py +53 -0
  3. dev10x/commands/__init__.py +0 -0
  4. dev10x/commands/hook.py +276 -0
  5. dev10x/commands/init.py +187 -0
  6. dev10x/commands/permission.py +349 -0
  7. dev10x/commands/platform.py +104 -0
  8. dev10x/commands/skill.py +90 -0
  9. dev10x/commands/validate.py +6 -0
  10. dev10x/config/__init__.py +3 -0
  11. dev10x/config/loader.py +111 -0
  12. dev10x/domain/__init__.py +32 -0
  13. dev10x/domain/config_loader.py +24 -0
  14. dev10x/domain/git_context.py +43 -0
  15. dev10x/domain/hook_input.py +97 -0
  16. dev10x/domain/plan.py +184 -0
  17. dev10x/domain/repository_ref.py +20 -0
  18. dev10x/domain/result.py +41 -0
  19. dev10x/domain/rule_engine.py +82 -0
  20. dev10x/domain/session_state.py +200 -0
  21. dev10x/domain/sql.py +41 -0
  22. dev10x/domain/validation_rule.py +111 -0
  23. dev10x/hooks/__init__.py +0 -0
  24. dev10x/hooks/edit_validator.py +52 -0
  25. dev10x/hooks/permission_diagnostics.py +342 -0
  26. dev10x/hooks/session.py +518 -0
  27. dev10x/hooks/skill.py +95 -0
  28. dev10x/hooks/task_plan_sync.py +143 -0
  29. dev10x/mcp/__init__.py +0 -0
  30. dev10x/mcp/audit.py +74 -0
  31. dev10x/mcp/db.py +33 -0
  32. dev10x/mcp/git.py +157 -0
  33. dev10x/mcp/github.py +621 -0
  34. dev10x/mcp/monitor.py +50 -0
  35. dev10x/mcp/permission.py +104 -0
  36. dev10x/mcp/plan.py +89 -0
  37. dev10x/mcp/release.py +36 -0
  38. dev10x/mcp/server_cli.py +832 -0
  39. dev10x/mcp/server_db.py +35 -0
  40. dev10x/mcp/skill_index.py +30 -0
  41. dev10x/mcp/subprocess_utils.py +108 -0
  42. dev10x/mcp/tests/__init__.py +0 -0
  43. dev10x/mcp/tests/test_git.py +163 -0
  44. dev10x/mcp/utilities.py +34 -0
  45. dev10x/platform/__init__.py +14 -0
  46. dev10x/platform/registry.py +156 -0
  47. dev10x/skills/__init__.py +0 -0
  48. dev10x/skills/audit/__init__.py +0 -0
  49. dev10x/skills/audit/analyze_actions.py +330 -0
  50. dev10x/skills/audit/analyze_permissions.py +510 -0
  51. dev10x/skills/audit/extract_session.py +237 -0
  52. dev10x/skills/audit/instruction_budget.py +246 -0
  53. dev10x/skills/database/__init__.py +0 -0
  54. dev10x/skills/evidence/__init__.py +0 -0
  55. dev10x/skills/monitor/__init__.py +0 -0
  56. dev10x/skills/monitor/ci_check_status.py +261 -0
  57. dev10x/skills/monitor/pr_notify.py +496 -0
  58. dev10x/skills/notifications/__init__.py +0 -0
  59. dev10x/skills/notifications/slack_review_request.py +259 -0
  60. dev10x/skills/permission/__init__.py +0 -0
  61. dev10x/skills/permission/backup.py +57 -0
  62. dev10x/skills/permission/clean_project_files.py +472 -0
  63. dev10x/skills/permission/enumerate_mcp.py +229 -0
  64. dev10x/skills/permission/file_lock.py +44 -0
  65. dev10x/skills/permission/merge_worktree_permissions.py +205 -0
  66. dev10x/skills/permission/update_paths.py +598 -0
  67. dev10x/skills/release/__init__.py +0 -0
  68. dev10x/skills/release/collect_prs.py +415 -0
  69. dev10x/validators/__init__.py +52 -0
  70. dev10x/validators/base.py +38 -0
  71. dev10x/validators/command_substitution.py +45 -0
  72. dev10x/validators/commit_jtbd.py +200 -0
  73. dev10x/validators/execution_safety.py +135 -0
  74. dev10x/validators/pr_base.py +67 -0
  75. dev10x/validators/prefix_friction.py +355 -0
  76. dev10x/validators/safe_subshell.py +130 -0
  77. dev10x/validators/skill_redirect.py +193 -0
  78. dev10x/validators/sql_safety.py +252 -0
  79. dev10x-0.64.0.dist-info/METADATA +322 -0
  80. dev10x-0.64.0.dist-info/RECORD +84 -0
  81. dev10x-0.64.0.dist-info/WHEEL +5 -0
  82. dev10x-0.64.0.dist-info/entry_points.txt +2 -0
  83. dev10x-0.64.0.dist-info/licenses/LICENSE +21 -0
  84. dev10x-0.64.0.dist-info/top_level.txt +1 -0
dev10x/__init__.py ADDED
@@ -0,0 +1 @@
1
+ __version__ = "0.46.0.dev0"
dev10x/cli.py ADDED
@@ -0,0 +1,53 @@
1
+ from __future__ import annotations
2
+
3
+ import importlib
4
+ from typing import Any
5
+
6
+ import click
7
+
8
+
9
+ class LazyGroup(click.Group):
10
+ def __init__(
11
+ self,
12
+ *args: Any,
13
+ lazy_subcommands: dict[str, str] | None = None,
14
+ **kwargs: Any,
15
+ ) -> None:
16
+ super().__init__(*args, **kwargs)
17
+ self._lazy_subcommands: dict[str, str] = lazy_subcommands or {}
18
+
19
+ def list_commands(self, ctx: click.Context) -> list[str]:
20
+ base = super().list_commands(ctx)
21
+ lazy = sorted(self._lazy_subcommands.keys())
22
+ return base + lazy
23
+
24
+ def get_command(
25
+ self,
26
+ ctx: click.Context,
27
+ cmd_name: str,
28
+ ) -> click.BaseCommand | None:
29
+ if cmd_name in self._lazy_subcommands:
30
+ return self._load_lazy(cmd_name)
31
+ return super().get_command(ctx, cmd_name)
32
+
33
+ def _load_lazy(self, cmd_name: str) -> click.BaseCommand:
34
+ import_path = self._lazy_subcommands[cmd_name]
35
+ module_path, attr_name = import_path.rsplit(".", 1)
36
+ module = importlib.import_module(module_path)
37
+ return getattr(module, attr_name) # type: ignore[no-any-return]
38
+
39
+
40
+ @click.group(
41
+ cls=LazyGroup,
42
+ lazy_subcommands={
43
+ "hook": "dev10x.commands.hook.hook",
44
+ "init": "dev10x.commands.init.init",
45
+ "permission": "dev10x.commands.permission.permission",
46
+ "platform": "dev10x.commands.platform.platform",
47
+ "validate": "dev10x.commands.validate.validate",
48
+ "skill": "dev10x.commands.skill.skill",
49
+ },
50
+ )
51
+ @click.version_option(package_name="Dev10x")
52
+ def cli() -> None:
53
+ pass
File without changes
@@ -0,0 +1,276 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import sys
6
+ import traceback
7
+ from pathlib import Path
8
+
9
+ import click
10
+
11
+ _DEBUG = os.environ.get("HOOK_DEBUG", "") != ""
12
+
13
+
14
+ @click.group()
15
+ def hook() -> None:
16
+ """Hook entry points (validate-bash, validate-edit, plan-sync, session)."""
17
+
18
+
19
+ @hook.command(name="validate-bash")
20
+ def validate_bash() -> None:
21
+ """Validate Bash commands via the unified validator registry.
22
+
23
+ Reads JSON from stdin, dispatches to registered validators.
24
+ Exit codes: 0=allow, 2=block.
25
+ """
26
+ from dev10x.domain import HookInput
27
+ from dev10x.validators import get_validators
28
+
29
+ inp = HookInput.from_stdin()
30
+ if inp.tool_name != "Bash":
31
+ sys.exit(0)
32
+ if not inp.command:
33
+ sys.exit(0)
34
+
35
+ for validator in get_validators():
36
+ try:
37
+ if validator.should_run(inp=inp):
38
+ result = validator.validate(inp=inp)
39
+ if result is not None:
40
+ result.emit()
41
+ except Exception:
42
+ if _DEBUG:
43
+ print(
44
+ f"[HOOK_DEBUG] {validator.name} raised:",
45
+ file=sys.stderr,
46
+ )
47
+ traceback.print_exc(file=sys.stderr)
48
+ continue
49
+
50
+ sys.exit(0)
51
+
52
+
53
+ @hook.group(name="plan")
54
+ def plan() -> None:
55
+ """Plan synchronization commands."""
56
+
57
+
58
+ @plan.command(name="sync")
59
+ def plan_sync() -> None:
60
+ """Sync task state from stdin (PostToolUse hook mode)."""
61
+ from dev10x.hooks.task_plan_sync import cmd_hook
62
+
63
+ cmd_hook()
64
+
65
+
66
+ @plan.command(name="summary")
67
+ def plan_summary() -> None:
68
+ """Read plan YAML, output JSON summary."""
69
+ from dev10x.hooks.task_plan_sync import cmd_json_summary
70
+
71
+ cmd_json_summary()
72
+
73
+
74
+ @plan.command(name="set-context")
75
+ @click.argument("pairs", nargs=-1, required=True)
76
+ def plan_set_context(pairs: tuple[str, ...]) -> None:
77
+ """Store plan-level context metadata (K=V pairs, dot-paths supported)."""
78
+ from dev10x.hooks.task_plan_sync import cmd_set_context
79
+
80
+ cmd_set_context(args=list(pairs))
81
+
82
+
83
+ @plan.command(name="archive")
84
+ def plan_archive() -> None:
85
+ """Archive completed plan to .claude/session/archive/."""
86
+ from dev10x.hooks.task_plan_sync import cmd_archive
87
+
88
+ cmd_archive()
89
+
90
+
91
+ @hook.command(name="permission-denied")
92
+ def permission_denied() -> None:
93
+ """Handle PermissionDenied events via the validator registry.
94
+
95
+ Reads JSON from stdin, dispatches to validators that implement
96
+ correct(). Returns retry=true with corrective guidance when a
97
+ validator recognizes the denied command.
98
+
99
+ Also runs permission diagnostics to explain *why* a pre-approved
100
+ tool was prompted (settings override semantics, missing rules).
101
+ Exit codes: 0 always (retry decision is in JSON output).
102
+ """
103
+ from dev10x.domain import HookInput
104
+ from dev10x.validators import get_validators
105
+ from dev10x.validators.base import Corrector
106
+
107
+ inp = HookInput.from_stdin()
108
+
109
+ if inp.command:
110
+ for validator in get_validators():
111
+ try:
112
+ if not validator.should_run(inp=inp):
113
+ continue
114
+ if not isinstance(validator, Corrector):
115
+ continue
116
+ result = validator.correct(inp=inp)
117
+ if result is not None:
118
+ result.emit()
119
+ except Exception:
120
+ if _DEBUG:
121
+ print(
122
+ f"[HOOK_DEBUG] {validator.name} correct() raised:",
123
+ file=sys.stderr,
124
+ )
125
+ traceback.print_exc(file=sys.stderr)
126
+ continue
127
+
128
+ _run_permission_diagnostics(raw=inp.raw, cwd=inp.cwd)
129
+ sys.exit(0)
130
+
131
+
132
+ def _run_permission_diagnostics(*, raw: dict, cwd: str) -> None:
133
+ try:
134
+ from dev10x.hooks.permission_diagnostics import diagnose, format_diagnostic
135
+
136
+ result = diagnose(raw=raw, cwd=cwd)
137
+ if result is None:
138
+ return
139
+ message = format_diagnostic(result=result)
140
+ if message:
141
+ print(
142
+ json.dumps({"systemMessage": message}),
143
+ file=sys.stderr,
144
+ )
145
+ except Exception:
146
+ if _DEBUG:
147
+ print("[HOOK_DEBUG] permission_diagnostics raised:", file=sys.stderr)
148
+ traceback.print_exc(file=sys.stderr)
149
+
150
+
151
+ @hook.command(name="validate-edit")
152
+ @click.option(
153
+ "--config",
154
+ "config_path",
155
+ type=click.Path(exists=True, path_type=Path),
156
+ default=None,
157
+ )
158
+ @click.option("--debug", is_flag=True)
159
+ def validate_edit(config_path: Path | None, debug: bool) -> None:
160
+ """Validate Edit/Write tool calls against sensitive file rules.
161
+
162
+ Reads JSON from stdin, checks file paths against rules.
163
+ Exit codes: 0=allow, 2=block.
164
+ """
165
+ import json
166
+
167
+ from dev10x.hooks.edit_validator import validate_edit_write
168
+
169
+ try:
170
+ data = json.load(sys.stdin)
171
+ except (json.JSONDecodeError, EOFError):
172
+ sys.exit(0)
173
+
174
+ validate_edit_write(
175
+ data=data,
176
+ yaml_path=config_path,
177
+ debug=debug,
178
+ )
179
+
180
+
181
+ @hook.group(name="session")
182
+ def session() -> None:
183
+ """Session lifecycle commands."""
184
+
185
+
186
+ @session.command(name="reload")
187
+ def session_reload_cmd() -> None:
188
+ """Reload prior session state (SessionStart hook)."""
189
+ from dev10x.hooks.session import session_reload
190
+
191
+ session_reload()
192
+
193
+
194
+ @session.command(name="compact")
195
+ def session_compact_cmd() -> None:
196
+ """Inject context summary before compaction (PreCompact hook)."""
197
+ from dev10x.hooks.session import context_compact
198
+
199
+ context_compact()
200
+
201
+
202
+ @session.command(name="tmpdir")
203
+ def session_tmpdir_cmd() -> None:
204
+ """Create session scratch directory and install mktmp.sh (SessionStart hook)."""
205
+ from dev10x.hooks.session import session_tmpdir
206
+
207
+ session_tmpdir()
208
+
209
+
210
+ @session.command(name="guidance")
211
+ def session_guidance_cmd() -> None:
212
+ """Output session-guidance.md as additionalContext (SessionStart hook)."""
213
+ from dev10x.hooks.session import session_guidance
214
+
215
+ session_guidance()
216
+
217
+
218
+ @session.command(name="git-aliases")
219
+ def session_git_aliases_cmd() -> None:
220
+ """Check git branch-comparison aliases and report status (SessionStart hook)."""
221
+ from dev10x.hooks.session import session_git_aliases
222
+
223
+ session_git_aliases()
224
+
225
+
226
+ @session.command(name="migrate-permissions")
227
+ def session_migrate_permissions_cmd() -> None:
228
+ """Migrate stale plugin permission rules to current version (SessionStart hook)."""
229
+ from dev10x.hooks.session import session_migrate_permissions
230
+
231
+ session_migrate_permissions()
232
+
233
+
234
+ @session.command(name="persist")
235
+ def session_persist_cmd() -> None:
236
+ """Persist session state to disk for next-session reload (SessionStop hook)."""
237
+ from dev10x.hooks.session import session_persist
238
+
239
+ session_persist()
240
+
241
+
242
+ @session.command(name="goodbye")
243
+ def session_goodbye_cmd() -> None:
244
+ """Output goodbye message with community link and resume hint (SessionStop hook)."""
245
+ from dev10x.hooks.session import session_goodbye
246
+
247
+ session_goodbye()
248
+
249
+
250
+ @hook.group(name="skill")
251
+ def skill() -> None:
252
+ """Skill lifecycle commands."""
253
+
254
+
255
+ @skill.command(name="tmpdir")
256
+ def skill_tmpdir_cmd() -> None:
257
+ """Create scratch directory for skill (PreToolUse hook)."""
258
+ from dev10x.hooks.skill import skill_tmpdir
259
+
260
+ skill_tmpdir()
261
+
262
+
263
+ @skill.command(name="metrics")
264
+ def skill_metrics_cmd() -> None:
265
+ """Append skill invocation metric to JSONL file (PostToolUse hook)."""
266
+ from dev10x.hooks.skill import skill_metrics
267
+
268
+ skill_metrics()
269
+
270
+
271
+ @hook.command(name="ruff-format")
272
+ def ruff_format_cmd() -> None:
273
+ """Auto-format Python files with ruff after Edit/Write (PostToolUse hook)."""
274
+ from dev10x.hooks.skill import ruff_format
275
+
276
+ ruff_format()
@@ -0,0 +1,187 @@
1
+ """`dev10x init` — guided onboarding for new users.
2
+
3
+ Creates a starter `.claude/Dev10x/` config tree in the current project
4
+ and prints a quick-start card covering the top 5 workflows.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ import click
13
+
14
+ QUICK_START_WORKFLOWS = [
15
+ (
16
+ "git-commit",
17
+ "/Dev10x:git-commit",
18
+ "JTBD-style commit messages with gitmoji + ticket ID",
19
+ ),
20
+ (
21
+ "pr-create",
22
+ "/Dev10x:gh-pr-create",
23
+ "Draft PR with Job Story body and Fixes: link",
24
+ ),
25
+ (
26
+ "review",
27
+ "/Dev10x:review",
28
+ "Self-review your branch before requesting a human reviewer",
29
+ ),
30
+ (
31
+ "testing",
32
+ "/test",
33
+ "Run pytest with coverage enforcement",
34
+ ),
35
+ (
36
+ "architecture",
37
+ "/Dev10x:adr",
38
+ "Author Architecture Decision Records with diagrams",
39
+ ),
40
+ ]
41
+
42
+ STARTER_WORK_ON_PLAYBOOK = """# Starter work-on playbook for this project.
43
+ #
44
+ # Customize with /Dev10x:playbook edit work-on <play>
45
+ # See skills/playbook/references/playbook.yaml for the full schema.
46
+
47
+ # active_modes controls per-step behavior adaptations. Uncomment any
48
+ # that apply to this project:
49
+ #
50
+ # active_modes:
51
+ # - solo-maintainer # skip reviewer assignment + Slack notifications
52
+ # - open-source # prefer issue templates and public-safe language
53
+
54
+ overrides: []
55
+ """
56
+
57
+ STARTER_GITMOJI_YAML = """# Gitmoji strategy override for this project.
58
+ #
59
+ # Leave default-strategy empty to use plugin defaults.
60
+ # See skills/git-commit/references/gitmoji-defaults.yaml for the base map.
61
+
62
+ default-strategy: null
63
+ projects: []
64
+ """
65
+
66
+
67
+ def _session_yaml_template() -> str:
68
+ return (
69
+ "# Dev10x session config — consumed by work-on, verify-acc-dod, and\n"
70
+ "# the PreCompact recovery hook.\n"
71
+ "friction_level: guided # strict | guided | adaptive\n"
72
+ "active_modes: []\n"
73
+ )
74
+
75
+
76
+ def _write_if_missing(path: Path, content: str) -> bool:
77
+ if path.exists():
78
+ return False
79
+ path.parent.mkdir(parents=True, exist_ok=True)
80
+ path.write_text(content)
81
+ return True
82
+
83
+
84
+ def _seed_project(project_root: Path) -> list[Path]:
85
+ """Create starter config files. Returns list of paths written."""
86
+ written: list[Path] = []
87
+
88
+ targets = [
89
+ (project_root / ".claude" / "Dev10x" / "session.yaml", _session_yaml_template()),
90
+ (
91
+ project_root / ".claude" / "Dev10x" / "playbooks" / "work-on.yaml",
92
+ STARTER_WORK_ON_PLAYBOOK,
93
+ ),
94
+ ]
95
+ for path, content in targets:
96
+ if _write_if_missing(path, content):
97
+ written.append(path)
98
+
99
+ return written
100
+
101
+
102
+ def _print_card(*, project_root: Path) -> None:
103
+ click.echo("")
104
+ click.echo("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
105
+ click.echo(" Dev10x — Next 5 commands")
106
+ click.echo("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
107
+ for i, (workflow, command, description) in enumerate(QUICK_START_WORKFLOWS, start=1):
108
+ click.echo(f" {i}. {command:<28} {workflow}")
109
+ click.echo(f" {description}")
110
+ click.echo("")
111
+ click.echo(f" Config: {project_root}/.claude/Dev10x/")
112
+ click.echo(" Customize: /Dev10x:playbook edit work-on <play>")
113
+ click.echo(" Discovery: /Dev10x:onboarding")
114
+ click.echo("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
115
+ click.echo("")
116
+
117
+
118
+ @click.command()
119
+ @click.option(
120
+ "--setup",
121
+ is_flag=True,
122
+ help="Force interactive setup even when config already exists.",
123
+ )
124
+ @click.option(
125
+ "--non-interactive",
126
+ is_flag=True,
127
+ help="Skip interactive prompts; write starter config and print card only.",
128
+ )
129
+ @click.option(
130
+ "--path",
131
+ "project_path",
132
+ type=click.Path(file_okay=False, path_type=Path),
133
+ default=None,
134
+ help="Project root (defaults to current directory).",
135
+ )
136
+ def init(*, setup: bool, non_interactive: bool, project_path: Path | None) -> None:
137
+ """Create a starter .claude/Dev10x/ config and print the quick-start card."""
138
+ project_root = (project_path or Path.cwd()).resolve()
139
+
140
+ if not project_root.is_dir():
141
+ click.echo(f"Project path does not exist: {project_root}", err=True)
142
+ sys.exit(1)
143
+
144
+ existing = (project_root / ".claude" / "Dev10x" / "session.yaml").exists()
145
+ if existing and not setup:
146
+ click.echo(f"Dev10x config already present at {project_root}/.claude/Dev10x/")
147
+ _print_card(project_root=project_root)
148
+ return
149
+
150
+ if non_interactive:
151
+ written = _seed_project(project_root)
152
+ for path in written:
153
+ click.echo(f" + {path.relative_to(project_root)}")
154
+ _print_card(project_root=project_root)
155
+ return
156
+
157
+ click.echo("")
158
+ click.echo(f"Setting up Dev10x in {project_root}")
159
+ click.echo("")
160
+
161
+ friction_level = click.prompt(
162
+ "Friction level",
163
+ type=click.Choice(["strict", "guided", "adaptive"], case_sensitive=False),
164
+ default="guided",
165
+ )
166
+ solo = click.confirm(
167
+ "Solo maintainer mode? (skips reviewer assignment and Slack notifications)",
168
+ default=False,
169
+ )
170
+
171
+ session_yaml_path = project_root / ".claude" / "Dev10x" / "session.yaml"
172
+ modes = ["solo-maintainer"] if solo else []
173
+ session_yaml_path.parent.mkdir(parents=True, exist_ok=True)
174
+ session_yaml_path.write_text(
175
+ "# Dev10x session config\n"
176
+ f"friction_level: {friction_level.lower()}\n"
177
+ f"active_modes: {modes!r}\n"
178
+ )
179
+ click.echo(f" + {session_yaml_path.relative_to(project_root)}")
180
+
181
+ playbook_path = project_root / ".claude" / "Dev10x" / "playbooks" / "work-on.yaml"
182
+ if not playbook_path.exists():
183
+ playbook_path.parent.mkdir(parents=True, exist_ok=True)
184
+ playbook_path.write_text(STARTER_WORK_ON_PLAYBOOK)
185
+ click.echo(f" + {playbook_path.relative_to(project_root)}")
186
+
187
+ _print_card(project_root=project_root)