ai-docs-toolkit 0.1.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.
- ai_docs_toolkit/__init__.py +3 -0
- ai_docs_toolkit/__main__.py +4 -0
- ai_docs_toolkit/cli/__init__.py +1153 -0
- ai_docs_toolkit/context/__init__.py +13 -0
- ai_docs_toolkit/context/markdown_output.py +470 -0
- ai_docs_toolkit/core/__init__.py +360 -0
- ai_docs_toolkit/core/azure_devops_provider.py +569 -0
- ai_docs_toolkit/core/changed_files.py +235 -0
- ai_docs_toolkit/core/config.py +515 -0
- ai_docs_toolkit/core/context.py +666 -0
- ai_docs_toolkit/core/doctor.py +362 -0
- ai_docs_toolkit/core/file_lookup.py +656 -0
- ai_docs_toolkit/core/frontmatter.py +143 -0
- ai_docs_toolkit/core/graph.py +153 -0
- ai_docs_toolkit/core/impact.py +291 -0
- ai_docs_toolkit/core/init.py +837 -0
- ai_docs_toolkit/core/planning_operations.py +792 -0
- ai_docs_toolkit/core/planning_scaffold.py +334 -0
- ai_docs_toolkit/core/registry.py +161 -0
- ai_docs_toolkit/core/requirement_capture.py +152 -0
- ai_docs_toolkit/core/scanner.py +38 -0
- ai_docs_toolkit/core/schema_validation.py +222 -0
- ai_docs_toolkit/core/section_fix.py +169 -0
- ai_docs_toolkit/core/structure_validation.py +1155 -0
- ai_docs_toolkit/core/task_export.py +639 -0
- ai_docs_toolkit/docs/agent-installation.md +443 -0
- ai_docs_toolkit/graph/__init__.py +13 -0
- ai_docs_toolkit/graph/json_output.py +238 -0
- ai_docs_toolkit/impact/__init__.py +15 -0
- ai_docs_toolkit/impact/json_output.py +458 -0
- ai_docs_toolkit/mcp/__init__.py +8 -0
- ai_docs_toolkit/mcp/__main__.py +5 -0
- ai_docs_toolkit/mcp/server.py +345 -0
- ai_docs_toolkit/mcp/setup.py +107 -0
- ai_docs_toolkit/templates/project/.ai-docs/examples/skills/release-publication/SKILL.md +85 -0
- ai_docs_toolkit/templates/project/.ai-docs/reference/documentation-cascade-authoring-skill.md +157 -0
- ai_docs_toolkit/templates/project/.ai-docs/reference/documentation-change-review-skill.md +128 -0
- ai_docs_toolkit/templates/project/.ai-docs/reference/incoming-request-model.md +332 -0
- ai_docs_toolkit/templates/project/.ai-docs/reference/incoming-request-triage-skill.md +158 -0
- ai_docs_toolkit/templates/project/.ai-docs/reference/init-command-contract.md +262 -0
- ai_docs_toolkit/templates/project/.ai-docs/reference/planning-document-model.md +1483 -0
- ai_docs_toolkit/templates/project/.ai-docs/reference/toolkit-maintenance-context-routing.md +373 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/acceptance.schema.yaml +17 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/architecture-decision.schema.yaml +17 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/asset.schema.yaml +17 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/business-rule.schema.yaml +17 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/contract.schema.yaml +17 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/guide.schema.yaml +17 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/implementation-task.schema.yaml +97 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/index.schema.yaml +17 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/inventory.schema.yaml +17 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/module.schema.yaml +17 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/operation.schema.yaml +17 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/plan-stage.schema.yaml +89 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/plan.schema.yaml +86 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/runbook.schema.yaml +17 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/scenario.schema.yaml +17 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/standalone-task.schema.yaml +90 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/task-registry.schema.yaml +114 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/document-types/test-case.schema.yaml +23 -0
- ai_docs_toolkit/templates/project/.ai-docs/schemas/frontmatter.schema.yaml +181 -0
- ai_docs_toolkit/templates/project/.ai-docs/skills/documentation-cascade-authoring/SKILL.md +84 -0
- ai_docs_toolkit/templates/project/.ai-docs/skills/documentation-change-review/SKILL.md +76 -0
- ai_docs_toolkit/templates/project/.ai-docs/skills/incoming-request-triage/SKILL.md +90 -0
- ai_docs_toolkit/templates/project/.ai-docs/skills/index.md +51 -0
- ai_docs_toolkit/templates/project/.ai-docs/skills/planning-record-generation/SKILL.md +77 -0
- ai_docs_toolkit/templates/project/.ai-docs/skills/planning-task-execution/SKILL.md +106 -0
- ai_docs_toolkit/templates/project/.ai-docs/skills/toolkit-consumer-policy/SKILL.md +44 -0
- ai_docs_toolkit/templates/project/.ai-docs/skills/update-ai-docs-toolkit/SKILL.md +100 -0
- ai_docs_toolkit/templates/project/.ai-docs/templates/release/agent-context-architecture-beta-18.md +123 -0
- ai_docs_toolkit/templates/project/.ai-docs/templates/release/archive-validation-beta-12.md +123 -0
- ai_docs_toolkit/templates/project/.ai-docs/templates/release/cascade-impact-beta-14.md +69 -0
- ai_docs_toolkit/templates/project/.ai-docs/templates/release/consumer-migration-hardening.md +128 -0
- ai_docs_toolkit/templates/project/.ai-docs/templates/release/decision-visibility-beta-13.md +132 -0
- ai_docs_toolkit/templates/project/.ai-docs/templates/release/doctor-filesystem-traversal-beta-19.md +98 -0
- ai_docs_toolkit/templates/project/.ai-docs/templates/release/doctor-json-provenance-beta-20.md +107 -0
- ai_docs_toolkit/templates/project/.ai-docs/templates/release/documentation-cascade-authoring-beta-23.md +54 -0
- ai_docs_toolkit/templates/project/.ai-docs/templates/release/documentation-change-review-beta-21.md +105 -0
- ai_docs_toolkit/templates/project/.ai-docs/templates/release/incoming-request-storage-beta-17.md +70 -0
- ai_docs_toolkit/templates/project/.ai-docs/templates/release/incoming-request-triage-beta-22.md +52 -0
- ai_docs_toolkit/templates/project/.ai-docs/templates/release/migration-note.md +90 -0
- ai_docs_toolkit/templates/project/.ai-docs/templates/release/post-publication-checks.md +9 -0
- ai_docs_toolkit/templates/project/.ai-docs/templates/release/release-task.md +123 -0
- ai_docs_toolkit/templates/project/.ai-docs/templates/release/routing-restoration-beta-15.md +39 -0
- ai_docs_toolkit/templates/project/.ai-docs/templates/release/task-retirement-beta-16.md +54 -0
- ai_docs_toolkit/templates/project/.ai-docs/templates/release/validation-evidence.md +18 -0
- ai_docs_toolkit/templates/project/.ai-docs/templates/release/version-change-checklist.md +11 -0
- ai_docs_toolkit/templates/project/.ai-docs/workflows/documentation-cascade-adoption.md +581 -0
- ai_docs_toolkit/templates/project/.ai-docs/workflows/plan-stage-workflow.md +752 -0
- ai_docs_toolkit/templates/project/.ai-docs/workflows/toolkit-migration-workflow.md +150 -0
- ai_docs_toolkit/templates/project/AGENTS.md +177 -0
- ai_docs_toolkit/templates/project/ai-docs-index.md +39 -0
- ai_docs_toolkit/templates/project/ai-docs.config.yaml +56 -0
- ai_docs_toolkit/validation/__init__.py +16 -0
- ai_docs_toolkit/validation/human.py +313 -0
- ai_docs_toolkit-0.1.0.dist-info/METADATA +269 -0
- ai_docs_toolkit-0.1.0.dist-info/RECORD +100 -0
- ai_docs_toolkit-0.1.0.dist-info/WHEEL +5 -0
- ai_docs_toolkit-0.1.0.dist-info/entry_points.txt +3 -0
- ai_docs_toolkit-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1153 @@
|
|
|
1
|
+
from ai_docs_toolkit import __version__
|
|
2
|
+
from ai_docs_toolkit.context import (
|
|
3
|
+
context_project,
|
|
4
|
+
format_context_json,
|
|
5
|
+
format_context_markdown,
|
|
6
|
+
)
|
|
7
|
+
from ai_docs_toolkit.graph import GraphFilters, format_graph_json, graph_project
|
|
8
|
+
from ai_docs_toolkit.impact import (
|
|
9
|
+
format_impact_human,
|
|
10
|
+
format_impact_json,
|
|
11
|
+
impact_changed_project,
|
|
12
|
+
impact_project,
|
|
13
|
+
)
|
|
14
|
+
from ai_docs_toolkit.mcp.setup import (
|
|
15
|
+
format_jetbrains_mcp_config_json,
|
|
16
|
+
format_mcp_status_human,
|
|
17
|
+
jetbrains_mcp_config,
|
|
18
|
+
mcp_setup_status,
|
|
19
|
+
)
|
|
20
|
+
from ai_docs_toolkit.core import (
|
|
21
|
+
InitResult,
|
|
22
|
+
AzureDevOpsConfig,
|
|
23
|
+
AzureDevOpsPushResult,
|
|
24
|
+
doctor_project,
|
|
25
|
+
azure_devops_config_from_env,
|
|
26
|
+
format_azure_devops_push_json,
|
|
27
|
+
format_doctor_json,
|
|
28
|
+
format_doctor_human,
|
|
29
|
+
format_file_lookup_human,
|
|
30
|
+
format_file_lookup_json,
|
|
31
|
+
format_planning_operation_human,
|
|
32
|
+
format_planning_operation_json,
|
|
33
|
+
format_planning_layout_migration_dry_run,
|
|
34
|
+
format_planning_scaffold_result,
|
|
35
|
+
format_stage_review_human,
|
|
36
|
+
format_stage_review_json,
|
|
37
|
+
format_task_export_json,
|
|
38
|
+
format_task_export_markdown,
|
|
39
|
+
init_project,
|
|
40
|
+
lookup_document_file,
|
|
41
|
+
lookup_plan_file,
|
|
42
|
+
lookup_request_file,
|
|
43
|
+
lookup_stage_file,
|
|
44
|
+
lookup_task_file,
|
|
45
|
+
planning_layout_migration_dry_run,
|
|
46
|
+
scaffold_planning_record,
|
|
47
|
+
start_stage,
|
|
48
|
+
start_task,
|
|
49
|
+
close_stage,
|
|
50
|
+
close_task,
|
|
51
|
+
add_stage,
|
|
52
|
+
add_task,
|
|
53
|
+
review_stage,
|
|
54
|
+
export_tasks,
|
|
55
|
+
push_azure_devops_tasks,
|
|
56
|
+
autofix_planning,
|
|
57
|
+
fix_sections,
|
|
58
|
+
add_requirement,
|
|
59
|
+
)
|
|
60
|
+
from ai_docs_toolkit.validation import (
|
|
61
|
+
format_human_validation_result,
|
|
62
|
+
format_json_validation_result,
|
|
63
|
+
validate_project_human,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _help_text() -> str:
|
|
68
|
+
return "\n".join(
|
|
69
|
+
[
|
|
70
|
+
f"ai-docs {__version__}",
|
|
71
|
+
"",
|
|
72
|
+
"Usage:",
|
|
73
|
+
" ai-docs --help",
|
|
74
|
+
" ai-docs --version",
|
|
75
|
+
" ai-docs init [--profile minimal|agent] [--force] [--dry-run|--check]",
|
|
76
|
+
" ai-docs doctor [--format json|--json]",
|
|
77
|
+
" ai-docs file (--id ID | --task TASK | --request REQUEST | --plan PLAN [--stage STAGE]) [--absolute] [--format json|--json]",
|
|
78
|
+
" ai-docs validate",
|
|
79
|
+
" ai-docs validate --json",
|
|
80
|
+
" ai-docs validate --fix-planning [--dry-run] [--format json|--json]",
|
|
81
|
+
" ai-docs fix-sections [--dry-run] [--format json|--json]",
|
|
82
|
+
" ai-docs add-requirement TARGET-ID \"requirement\" [--dry-run] [--format json|--json]",
|
|
83
|
+
" ai-docs plan start-stage --stage STAGE-ID [--dry-run] [--format json|--json]",
|
|
84
|
+
" ai-docs plan start-task --task TASK-ID [--dry-run] [--format json|--json]",
|
|
85
|
+
" ai-docs plan close-task --task TASK-ID [--dry-run] [--format json|--json]",
|
|
86
|
+
" ai-docs plan close-stage --stage STAGE-ID [--dry-run] [--format json|--json]",
|
|
87
|
+
" ai-docs plan add-stage --plan PLAN-ID --stage SLUG [--dry-run] [--format json|--json]",
|
|
88
|
+
" ai-docs plan add-task --stage STAGE-ID --task SLUG [--dry-run] [--format json|--json]",
|
|
89
|
+
" ai-docs plan review-stage --stage STAGE-ID [--format json|--json]",
|
|
90
|
+
" ai-docs tasks export (--task TASK-ID | --stage STAGE-ID | --plan PLAN-ID) [--include-planned] [--include-backlog] [--format json|markdown] [--output PATH]",
|
|
91
|
+
" ai-docs tasks push --provider azure-devops (--task TASK-ID | --stage STAGE-ID | --plan PLAN-ID) --organization ORG --project PROJECT [--dry-run] [--write-back] [--format json]",
|
|
92
|
+
" ai-docs graph --format json [--type TYPE] [--module MODULE] [--id ID]",
|
|
93
|
+
" ai-docs impact (--id ID | --changed [--base REF]) [--format json]",
|
|
94
|
+
" ai-docs context (--id ID | --module MODULE | --feature FEATURE | --changed) [--format json] [--history exclude|include|legacy] [--body-budget-bytes N] [--depth N]",
|
|
95
|
+
" ai-docs mcp status [--project-root PATH] [--config FILE]",
|
|
96
|
+
" ai-docs mcp configure --client jetbrains --print-json [--project-root PATH] [--config FILE]",
|
|
97
|
+
" ai-docs planning scaffold --plan SLUG --stage SLUG --task SLUG --task-id TASK-NNNNNN [--date YYYY-MM-DD]",
|
|
98
|
+
" ai-docs planning migrate-layout --dry-run",
|
|
99
|
+
]
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def main(argv: list[str] | None = None) -> int:
|
|
104
|
+
args = list(argv) if argv is not None else None
|
|
105
|
+
|
|
106
|
+
if args is None:
|
|
107
|
+
import sys
|
|
108
|
+
|
|
109
|
+
args = sys.argv[1:]
|
|
110
|
+
|
|
111
|
+
if not args or "--help" in args or "-h" in args:
|
|
112
|
+
print(_help_text())
|
|
113
|
+
return 0
|
|
114
|
+
|
|
115
|
+
if "--version" in args or "-V" in args:
|
|
116
|
+
print(__version__)
|
|
117
|
+
return 0
|
|
118
|
+
|
|
119
|
+
init_args = _parse_init_args(args)
|
|
120
|
+
if init_args is not None:
|
|
121
|
+
profile, force, dry_run, check = init_args
|
|
122
|
+
result = init_project(
|
|
123
|
+
".",
|
|
124
|
+
profile=profile,
|
|
125
|
+
force=force,
|
|
126
|
+
dry_run=dry_run,
|
|
127
|
+
check=check,
|
|
128
|
+
)
|
|
129
|
+
print(_format_init_result(result))
|
|
130
|
+
return result.exit_code
|
|
131
|
+
|
|
132
|
+
doctor_format = _parse_doctor_args(args)
|
|
133
|
+
if doctor_format is not None:
|
|
134
|
+
result = doctor_project(".")
|
|
135
|
+
print(
|
|
136
|
+
format_doctor_json(result)
|
|
137
|
+
if doctor_format == "json"
|
|
138
|
+
else format_doctor_human(result)
|
|
139
|
+
)
|
|
140
|
+
return result.exit_code
|
|
141
|
+
|
|
142
|
+
file_args = _parse_file_args(args)
|
|
143
|
+
if file_args is not None:
|
|
144
|
+
selector, value, stage_value, absolute, output_format = file_args
|
|
145
|
+
if selector == "id":
|
|
146
|
+
result = lookup_document_file(value, ".")
|
|
147
|
+
elif selector == "task":
|
|
148
|
+
result = lookup_task_file(value, ".")
|
|
149
|
+
elif selector == "request":
|
|
150
|
+
result = lookup_request_file(value, ".")
|
|
151
|
+
elif selector == "plan" and stage_value is not None:
|
|
152
|
+
result = lookup_stage_file(value, stage_value, ".")
|
|
153
|
+
else:
|
|
154
|
+
result = lookup_plan_file(value, ".")
|
|
155
|
+
print(
|
|
156
|
+
format_file_lookup_json(result)
|
|
157
|
+
if output_format == "json"
|
|
158
|
+
else format_file_lookup_human(result, absolute=absolute)
|
|
159
|
+
)
|
|
160
|
+
return result.exit_code
|
|
161
|
+
|
|
162
|
+
if args == ["validate"]:
|
|
163
|
+
result = validate_project_human(".")
|
|
164
|
+
print(format_human_validation_result(result))
|
|
165
|
+
return result.exit_code
|
|
166
|
+
|
|
167
|
+
if args == ["validate", "--json"] or args == ["--json", "validate"]:
|
|
168
|
+
result = validate_project_human(".")
|
|
169
|
+
print(format_json_validation_result(result))
|
|
170
|
+
return result.exit_code
|
|
171
|
+
|
|
172
|
+
validate_fix_args = _parse_validate_fix_args(args)
|
|
173
|
+
if validate_fix_args is not None:
|
|
174
|
+
dry_run, output_format = validate_fix_args
|
|
175
|
+
fix_result = autofix_planning(".", dry_run=dry_run)
|
|
176
|
+
if output_format == "json":
|
|
177
|
+
print(format_planning_operation_json(fix_result))
|
|
178
|
+
else:
|
|
179
|
+
print(format_planning_operation_human(fix_result))
|
|
180
|
+
if fix_result.exit_code != 0:
|
|
181
|
+
return fix_result.exit_code
|
|
182
|
+
if dry_run:
|
|
183
|
+
return 0
|
|
184
|
+
validation_result = validate_project_human(".")
|
|
185
|
+
print(format_human_validation_result(validation_result))
|
|
186
|
+
return validation_result.exit_code
|
|
187
|
+
|
|
188
|
+
fix_sections_args = _parse_simple_mutation_args(args, "fix-sections")
|
|
189
|
+
if fix_sections_args is not None:
|
|
190
|
+
dry_run, output_format = fix_sections_args
|
|
191
|
+
result = fix_sections(".", dry_run=dry_run)
|
|
192
|
+
print(
|
|
193
|
+
format_planning_operation_json(result)
|
|
194
|
+
if output_format == "json"
|
|
195
|
+
else format_planning_operation_human(result)
|
|
196
|
+
)
|
|
197
|
+
return result.exit_code
|
|
198
|
+
|
|
199
|
+
add_requirement_args = _parse_add_requirement_args(args)
|
|
200
|
+
if add_requirement_args is not None:
|
|
201
|
+
target_id, requirement, dry_run, output_format = add_requirement_args
|
|
202
|
+
result = add_requirement(
|
|
203
|
+
".",
|
|
204
|
+
target_id=target_id,
|
|
205
|
+
requirement=requirement,
|
|
206
|
+
dry_run=dry_run,
|
|
207
|
+
)
|
|
208
|
+
print(
|
|
209
|
+
format_planning_operation_json(result)
|
|
210
|
+
if output_format == "json"
|
|
211
|
+
else format_planning_operation_human(result)
|
|
212
|
+
)
|
|
213
|
+
return result.exit_code
|
|
214
|
+
|
|
215
|
+
plan_args = _parse_plan_args(args)
|
|
216
|
+
if plan_args is not None:
|
|
217
|
+
command, target, slug, dry_run, output_format = plan_args
|
|
218
|
+
if command == "start-stage":
|
|
219
|
+
result = start_stage(".", stage_id=target, dry_run=dry_run)
|
|
220
|
+
elif command == "start-task":
|
|
221
|
+
result = start_task(".", task_id=target, dry_run=dry_run)
|
|
222
|
+
elif command == "close-task":
|
|
223
|
+
result = close_task(".", task_id=target, dry_run=dry_run)
|
|
224
|
+
elif command == "close-stage":
|
|
225
|
+
result = close_stage(".", stage_id=target, dry_run=dry_run)
|
|
226
|
+
elif command == "add-stage":
|
|
227
|
+
result = add_stage(".", plan_id=target, stage_slug=slug or "", dry_run=dry_run)
|
|
228
|
+
elif command == "add-task":
|
|
229
|
+
result = add_task(".", stage_id=target, task_slug=slug or "", dry_run=dry_run)
|
|
230
|
+
else:
|
|
231
|
+
review = review_stage(".", stage_id=target)
|
|
232
|
+
print(
|
|
233
|
+
format_stage_review_json(review)
|
|
234
|
+
if output_format == "json"
|
|
235
|
+
else format_stage_review_human(review)
|
|
236
|
+
)
|
|
237
|
+
return review.exit_code
|
|
238
|
+
print(
|
|
239
|
+
format_planning_operation_json(result)
|
|
240
|
+
if output_format == "json"
|
|
241
|
+
else format_planning_operation_human(result)
|
|
242
|
+
)
|
|
243
|
+
return result.exit_code
|
|
244
|
+
|
|
245
|
+
graph_filters = _parse_graph_args(args)
|
|
246
|
+
if graph_filters is not None:
|
|
247
|
+
result = graph_project(".", filters=graph_filters)
|
|
248
|
+
print(format_graph_json(result))
|
|
249
|
+
return result.exit_code
|
|
250
|
+
|
|
251
|
+
impact_args = _parse_impact_args(args)
|
|
252
|
+
if impact_args is not None:
|
|
253
|
+
impact_mode, impact_source_ids, impact_format, impact_base = impact_args
|
|
254
|
+
result = (
|
|
255
|
+
impact_changed_project(".", base_ref=impact_base)
|
|
256
|
+
if impact_mode == "changed"
|
|
257
|
+
else impact_project(impact_source_ids, ".")
|
|
258
|
+
)
|
|
259
|
+
print(
|
|
260
|
+
format_impact_json(result)
|
|
261
|
+
if impact_format == "json"
|
|
262
|
+
else format_impact_human(result)
|
|
263
|
+
)
|
|
264
|
+
return result.exit_code
|
|
265
|
+
|
|
266
|
+
context_args = _parse_context_args(args)
|
|
267
|
+
if context_args is not None:
|
|
268
|
+
context_mode, context_value, context_format, history, budget, depth = context_args
|
|
269
|
+
result = context_project(mode=context_mode, value=context_value, project_root=".", history=history, body_budget_bytes=budget, max_depth=depth)
|
|
270
|
+
print(
|
|
271
|
+
format_context_json(result)
|
|
272
|
+
if context_format == "json"
|
|
273
|
+
else format_context_markdown(result)
|
|
274
|
+
)
|
|
275
|
+
return result.exit_code
|
|
276
|
+
|
|
277
|
+
tasks_export_args = _parse_tasks_export_args(args)
|
|
278
|
+
if tasks_export_args is not None:
|
|
279
|
+
selector, value, include_planned, include_backlog, output_format, output_path = (
|
|
280
|
+
tasks_export_args
|
|
281
|
+
)
|
|
282
|
+
result = export_tasks(
|
|
283
|
+
".",
|
|
284
|
+
task_id=value if selector == "task" else None,
|
|
285
|
+
stage_id=value if selector == "stage" else None,
|
|
286
|
+
plan_id=value if selector == "plan" else None,
|
|
287
|
+
include_planned=include_planned,
|
|
288
|
+
include_backlog=include_backlog,
|
|
289
|
+
)
|
|
290
|
+
output = (
|
|
291
|
+
format_task_export_markdown(result)
|
|
292
|
+
if output_format == "markdown"
|
|
293
|
+
else format_task_export_json(result)
|
|
294
|
+
)
|
|
295
|
+
if output_path is not None:
|
|
296
|
+
from pathlib import Path
|
|
297
|
+
|
|
298
|
+
Path(output_path).write_text(output, encoding="utf-8")
|
|
299
|
+
else:
|
|
300
|
+
print(output, end="")
|
|
301
|
+
return result.exit_code
|
|
302
|
+
|
|
303
|
+
tasks_push_args = _parse_tasks_push_args(args)
|
|
304
|
+
if tasks_push_args is not None:
|
|
305
|
+
(
|
|
306
|
+
selector,
|
|
307
|
+
value,
|
|
308
|
+
provider,
|
|
309
|
+
organization,
|
|
310
|
+
project,
|
|
311
|
+
work_item_type,
|
|
312
|
+
include_planned,
|
|
313
|
+
include_backlog,
|
|
314
|
+
dry_run,
|
|
315
|
+
write_back,
|
|
316
|
+
) = tasks_push_args
|
|
317
|
+
if provider != "azure-devops":
|
|
318
|
+
import sys
|
|
319
|
+
|
|
320
|
+
print("Unsupported task provider. Supported providers: azure-devops", file=sys.stderr)
|
|
321
|
+
return 3
|
|
322
|
+
config_result = azure_devops_config_from_env(
|
|
323
|
+
organization=organization,
|
|
324
|
+
project=project,
|
|
325
|
+
work_item_type=work_item_type,
|
|
326
|
+
)
|
|
327
|
+
if not isinstance(config_result, AzureDevOpsConfig):
|
|
328
|
+
result = AzureDevOpsPushResult(exit_code=1, issues=(config_result,))
|
|
329
|
+
else:
|
|
330
|
+
result = push_azure_devops_tasks(
|
|
331
|
+
".",
|
|
332
|
+
config=config_result,
|
|
333
|
+
task_id=value if selector == "task" else None,
|
|
334
|
+
stage_id=value if selector == "stage" else None,
|
|
335
|
+
plan_id=value if selector == "plan" else None,
|
|
336
|
+
include_planned=include_planned,
|
|
337
|
+
include_backlog=include_backlog,
|
|
338
|
+
dry_run=dry_run,
|
|
339
|
+
write_back=write_back,
|
|
340
|
+
)
|
|
341
|
+
print(format_azure_devops_push_json(result), end="")
|
|
342
|
+
return result.exit_code
|
|
343
|
+
|
|
344
|
+
mcp_args = _parse_mcp_args(args)
|
|
345
|
+
if mcp_args is not None:
|
|
346
|
+
mode, project_root, config_file = mcp_args
|
|
347
|
+
if mode == "status":
|
|
348
|
+
result = mcp_setup_status(project_root=project_root, config_file=config_file)
|
|
349
|
+
print(format_mcp_status_human(result))
|
|
350
|
+
return 0 if result["ok"] else 1
|
|
351
|
+
|
|
352
|
+
if mode == "jetbrains-json":
|
|
353
|
+
print(
|
|
354
|
+
format_jetbrains_mcp_config_json(
|
|
355
|
+
jetbrains_mcp_config(
|
|
356
|
+
project_root=project_root,
|
|
357
|
+
config_file=config_file,
|
|
358
|
+
)
|
|
359
|
+
)
|
|
360
|
+
)
|
|
361
|
+
return 0
|
|
362
|
+
|
|
363
|
+
planning_args = _parse_planning_args(args)
|
|
364
|
+
if planning_args is not None:
|
|
365
|
+
if planning_args == ("migrate-layout",):
|
|
366
|
+
result = planning_layout_migration_dry_run(".")
|
|
367
|
+
print(format_planning_layout_migration_dry_run(result))
|
|
368
|
+
return 0
|
|
369
|
+
|
|
370
|
+
plan_slug, stage_slug, task_slug, task_id, plan_date = planning_args
|
|
371
|
+
result = scaffold_planning_record(
|
|
372
|
+
".",
|
|
373
|
+
plan_slug=plan_slug,
|
|
374
|
+
stage_slug=stage_slug,
|
|
375
|
+
task_slug=task_slug,
|
|
376
|
+
task_id=task_id,
|
|
377
|
+
plan_date=plan_date,
|
|
378
|
+
)
|
|
379
|
+
print(format_planning_scaffold_result(result))
|
|
380
|
+
return result.exit_code
|
|
381
|
+
|
|
382
|
+
if args and args[0] == "graph":
|
|
383
|
+
import sys
|
|
384
|
+
|
|
385
|
+
print("Usage: ai-docs graph --format json", file=sys.stderr)
|
|
386
|
+
return 3
|
|
387
|
+
|
|
388
|
+
if args and args[0] == "impact":
|
|
389
|
+
import sys
|
|
390
|
+
|
|
391
|
+
print(
|
|
392
|
+
"Usage: ai-docs impact (--id ID | --changed [--base REF]) [--format json]",
|
|
393
|
+
file=sys.stderr,
|
|
394
|
+
)
|
|
395
|
+
return 3
|
|
396
|
+
|
|
397
|
+
if args and args[0] == "context":
|
|
398
|
+
import sys
|
|
399
|
+
|
|
400
|
+
print(
|
|
401
|
+
"Usage: ai-docs context (--id ID | --module MODULE | --feature FEATURE | --changed) [--format json] [--history exclude|include|legacy] [--body-budget-bytes N] [--depth N]",
|
|
402
|
+
file=sys.stderr,
|
|
403
|
+
)
|
|
404
|
+
return 3
|
|
405
|
+
|
|
406
|
+
if args and args[0] == "tasks":
|
|
407
|
+
import sys
|
|
408
|
+
|
|
409
|
+
print(
|
|
410
|
+
"Usage: ai-docs tasks export (--task TASK-ID | --stage STAGE-ID | --plan PLAN-ID) "
|
|
411
|
+
"[--include-planned] [--include-backlog] [--format json|markdown] [--output PATH]\n"
|
|
412
|
+
" or: ai-docs tasks push --provider azure-devops (--task TASK-ID | --stage STAGE-ID | --plan PLAN-ID) "
|
|
413
|
+
"--organization ORG --project PROJECT [--dry-run] [--write-back] [--format json]",
|
|
414
|
+
file=sys.stderr,
|
|
415
|
+
)
|
|
416
|
+
return 3
|
|
417
|
+
|
|
418
|
+
if args and args[0] == "init":
|
|
419
|
+
import sys
|
|
420
|
+
|
|
421
|
+
print(
|
|
422
|
+
"Usage: ai-docs init [--profile minimal|agent] [--force] [--dry-run|--check]",
|
|
423
|
+
file=sys.stderr,
|
|
424
|
+
)
|
|
425
|
+
return 3
|
|
426
|
+
|
|
427
|
+
if args and args[0] == "doctor":
|
|
428
|
+
import sys
|
|
429
|
+
|
|
430
|
+
print("Usage: ai-docs doctor [--format json|--json]", file=sys.stderr)
|
|
431
|
+
return 3
|
|
432
|
+
|
|
433
|
+
if args and args[0] == "fix-sections":
|
|
434
|
+
import sys
|
|
435
|
+
|
|
436
|
+
print("Usage: ai-docs fix-sections [--dry-run] [--format json|--json]", file=sys.stderr)
|
|
437
|
+
return 3
|
|
438
|
+
|
|
439
|
+
if args and args[0] == "add-requirement":
|
|
440
|
+
import sys
|
|
441
|
+
|
|
442
|
+
print(
|
|
443
|
+
"Usage: ai-docs add-requirement TARGET-ID \"requirement\" [--dry-run] [--format json|--json]",
|
|
444
|
+
file=sys.stderr,
|
|
445
|
+
)
|
|
446
|
+
return 3
|
|
447
|
+
|
|
448
|
+
if args and args[0] == "file":
|
|
449
|
+
import sys
|
|
450
|
+
|
|
451
|
+
print(
|
|
452
|
+
"Usage: ai-docs file (--id ID | --task TASK | --request REQUEST | --plan PLAN [--stage STAGE]) [--absolute] [--format json|--json]",
|
|
453
|
+
file=sys.stderr,
|
|
454
|
+
)
|
|
455
|
+
return 3
|
|
456
|
+
|
|
457
|
+
if args and args[0] == "plan":
|
|
458
|
+
import sys
|
|
459
|
+
|
|
460
|
+
print(
|
|
461
|
+
"Usage: ai-docs plan (start-stage|close-stage) --stage STAGE-ID [--dry-run] [--format json|--json]\n"
|
|
462
|
+
" or: ai-docs plan (start-task|close-task) --task TASK-ID [--dry-run] [--format json|--json]\n"
|
|
463
|
+
" or: ai-docs plan add-stage --plan PLAN-ID --stage SLUG [--dry-run] [--format json|--json]\n"
|
|
464
|
+
" or: ai-docs plan add-task --stage STAGE-ID --task SLUG [--dry-run] [--format json|--json]\n"
|
|
465
|
+
" or: ai-docs plan review-stage --stage STAGE-ID [--format json|--json]",
|
|
466
|
+
file=sys.stderr,
|
|
467
|
+
)
|
|
468
|
+
return 3
|
|
469
|
+
|
|
470
|
+
if args and args[0] == "mcp":
|
|
471
|
+
import sys
|
|
472
|
+
|
|
473
|
+
print(
|
|
474
|
+
"Usage: ai-docs mcp status [--project-root PATH] [--config FILE]\n"
|
|
475
|
+
" or: ai-docs mcp configure --client jetbrains --print-json [--project-root PATH] [--config FILE]",
|
|
476
|
+
file=sys.stderr,
|
|
477
|
+
)
|
|
478
|
+
return 3
|
|
479
|
+
|
|
480
|
+
if args and args[0] == "planning":
|
|
481
|
+
import sys
|
|
482
|
+
|
|
483
|
+
print(
|
|
484
|
+
"Usage: ai-docs planning scaffold --plan SLUG --stage SLUG --task SLUG --task-id TASK-NNNNNN [--date YYYY-MM-DD]\n"
|
|
485
|
+
" or: ai-docs planning migrate-layout --dry-run",
|
|
486
|
+
file=sys.stderr,
|
|
487
|
+
)
|
|
488
|
+
return 3
|
|
489
|
+
|
|
490
|
+
import sys
|
|
491
|
+
|
|
492
|
+
print(f"Unknown command or option: {' '.join(args)}", file=sys.stderr)
|
|
493
|
+
print("Run `ai-docs --help` for usage.", file=sys.stderr)
|
|
494
|
+
return 3
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
def _parse_init_args(args: list[str]) -> tuple[str, bool, bool, bool] | None:
|
|
498
|
+
if not args or args[0] != "init":
|
|
499
|
+
return None
|
|
500
|
+
|
|
501
|
+
values = args[1:]
|
|
502
|
+
profile = "agent"
|
|
503
|
+
force = False
|
|
504
|
+
dry_run = False
|
|
505
|
+
check = False
|
|
506
|
+
index = 0
|
|
507
|
+
while index < len(values):
|
|
508
|
+
option = values[index]
|
|
509
|
+
if option == "--force":
|
|
510
|
+
force = True
|
|
511
|
+
index += 1
|
|
512
|
+
continue
|
|
513
|
+
|
|
514
|
+
if option == "--dry-run":
|
|
515
|
+
dry_run = True
|
|
516
|
+
index += 1
|
|
517
|
+
continue
|
|
518
|
+
|
|
519
|
+
if option == "--check":
|
|
520
|
+
check = True
|
|
521
|
+
index += 1
|
|
522
|
+
continue
|
|
523
|
+
|
|
524
|
+
if option == "--profile":
|
|
525
|
+
if index + 1 >= len(values):
|
|
526
|
+
return None
|
|
527
|
+
profile = values[index + 1]
|
|
528
|
+
if profile not in {"minimal", "agent"}:
|
|
529
|
+
return None
|
|
530
|
+
index += 2
|
|
531
|
+
continue
|
|
532
|
+
|
|
533
|
+
return None
|
|
534
|
+
|
|
535
|
+
if dry_run and check:
|
|
536
|
+
return None
|
|
537
|
+
|
|
538
|
+
return profile, force, dry_run, check
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def _parse_doctor_args(args: list[str]) -> str | None:
|
|
542
|
+
if args == ["doctor"]:
|
|
543
|
+
return "human"
|
|
544
|
+
if args in (["doctor", "--json"], ["--json", "doctor"]):
|
|
545
|
+
return "json"
|
|
546
|
+
if args == ["doctor", "--format", "json"]:
|
|
547
|
+
return "json"
|
|
548
|
+
return None
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _format_init_result(result: InitResult) -> str:
|
|
552
|
+
lines = [
|
|
553
|
+
f"ai-docs init ({result.mode})",
|
|
554
|
+
f"profile: {result.profile}",
|
|
555
|
+
"created:",
|
|
556
|
+
*_format_path_list(result.created),
|
|
557
|
+
"skipped:",
|
|
558
|
+
*_format_path_list(result.skipped),
|
|
559
|
+
]
|
|
560
|
+
|
|
561
|
+
if result.overwritten:
|
|
562
|
+
lines.extend(["overwritten:", *_format_path_list(result.overwritten)])
|
|
563
|
+
|
|
564
|
+
for label, paths in (
|
|
565
|
+
("missing", result.missing),
|
|
566
|
+
("stale-known", result.stale_known),
|
|
567
|
+
("locally-modified", result.locally_modified),
|
|
568
|
+
("provenance-missing", result.provenance_missing),
|
|
569
|
+
):
|
|
570
|
+
if paths:
|
|
571
|
+
lines.extend([f"{label}:", *_format_path_list(paths)])
|
|
572
|
+
|
|
573
|
+
if result.unresolved:
|
|
574
|
+
lines.extend(
|
|
575
|
+
[
|
|
576
|
+
"unresolved (review and rerun with --force to overwrite):",
|
|
577
|
+
*_format_path_list(result.unresolved),
|
|
578
|
+
]
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
if result.obsolete:
|
|
582
|
+
lines.extend(["obsolete:", *_format_path_list(result.obsolete)])
|
|
583
|
+
|
|
584
|
+
if result.errors:
|
|
585
|
+
lines.extend(["errors:", *_format_path_list(result.errors)])
|
|
586
|
+
|
|
587
|
+
return "\n".join(lines)
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
def _format_path_list(paths: tuple[str, ...]) -> list[str]:
|
|
591
|
+
if not paths:
|
|
592
|
+
return ["- none"]
|
|
593
|
+
return [f"- {path}" for path in paths]
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def _parse_file_args(args: list[str]) -> tuple[str, str, str | None, bool, str] | None:
|
|
597
|
+
if not args or args[0] != "file":
|
|
598
|
+
return None
|
|
599
|
+
|
|
600
|
+
values = args[1:]
|
|
601
|
+
selector: str | None = None
|
|
602
|
+
selector_value: str | None = None
|
|
603
|
+
stage_value: str | None = None
|
|
604
|
+
absolute = False
|
|
605
|
+
output_format = "human"
|
|
606
|
+
index = 0
|
|
607
|
+
while index < len(values):
|
|
608
|
+
option = values[index]
|
|
609
|
+
if option == "--absolute":
|
|
610
|
+
absolute = True
|
|
611
|
+
index += 1
|
|
612
|
+
continue
|
|
613
|
+
|
|
614
|
+
if option == "--json":
|
|
615
|
+
output_format = "json"
|
|
616
|
+
index += 1
|
|
617
|
+
continue
|
|
618
|
+
|
|
619
|
+
if option == "--format":
|
|
620
|
+
if index + 1 >= len(values) or values[index + 1] != "json":
|
|
621
|
+
return None
|
|
622
|
+
output_format = "json"
|
|
623
|
+
index += 2
|
|
624
|
+
continue
|
|
625
|
+
|
|
626
|
+
if option in {"--id", "--task", "--request", "--plan"}:
|
|
627
|
+
if index + 1 >= len(values) or selector is not None:
|
|
628
|
+
return None
|
|
629
|
+
selector = option.removeprefix("--")
|
|
630
|
+
selector_value = values[index + 1]
|
|
631
|
+
index += 2
|
|
632
|
+
continue
|
|
633
|
+
|
|
634
|
+
if option == "--stage":
|
|
635
|
+
if index + 1 >= len(values) or stage_value is not None:
|
|
636
|
+
return None
|
|
637
|
+
stage_value = values[index + 1]
|
|
638
|
+
index += 2
|
|
639
|
+
continue
|
|
640
|
+
|
|
641
|
+
return None
|
|
642
|
+
|
|
643
|
+
if selector is None or selector_value is None:
|
|
644
|
+
return None
|
|
645
|
+
|
|
646
|
+
if stage_value is not None and selector != "plan":
|
|
647
|
+
return None
|
|
648
|
+
|
|
649
|
+
return selector, selector_value, stage_value, absolute, output_format
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def _parse_validate_fix_args(args: list[str]) -> tuple[bool, str] | None:
|
|
653
|
+
if not args or args[0] != "validate" or "--fix-planning" not in args:
|
|
654
|
+
return None
|
|
655
|
+
|
|
656
|
+
dry_run = False
|
|
657
|
+
output_format = "human"
|
|
658
|
+
index = 1
|
|
659
|
+
while index < len(args):
|
|
660
|
+
option = args[index]
|
|
661
|
+
if option == "--fix-planning":
|
|
662
|
+
index += 1
|
|
663
|
+
continue
|
|
664
|
+
if option == "--dry-run":
|
|
665
|
+
dry_run = True
|
|
666
|
+
index += 1
|
|
667
|
+
continue
|
|
668
|
+
if option == "--json":
|
|
669
|
+
output_format = "json"
|
|
670
|
+
index += 1
|
|
671
|
+
continue
|
|
672
|
+
if option == "--format":
|
|
673
|
+
if index + 1 >= len(args) or args[index + 1] != "json":
|
|
674
|
+
return None
|
|
675
|
+
output_format = "json"
|
|
676
|
+
index += 2
|
|
677
|
+
continue
|
|
678
|
+
return None
|
|
679
|
+
return dry_run, output_format
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
def _parse_simple_mutation_args(args: list[str], command: str) -> tuple[bool, str] | None:
|
|
683
|
+
if not args or args[0] != command:
|
|
684
|
+
return None
|
|
685
|
+
|
|
686
|
+
dry_run = False
|
|
687
|
+
output_format = "human"
|
|
688
|
+
index = 1
|
|
689
|
+
while index < len(args):
|
|
690
|
+
option = args[index]
|
|
691
|
+
if option == "--dry-run":
|
|
692
|
+
dry_run = True
|
|
693
|
+
index += 1
|
|
694
|
+
continue
|
|
695
|
+
if option == "--json":
|
|
696
|
+
output_format = "json"
|
|
697
|
+
index += 1
|
|
698
|
+
continue
|
|
699
|
+
if option == "--format":
|
|
700
|
+
if index + 1 >= len(args) or args[index + 1] != "json":
|
|
701
|
+
return None
|
|
702
|
+
output_format = "json"
|
|
703
|
+
index += 2
|
|
704
|
+
continue
|
|
705
|
+
return None
|
|
706
|
+
return dry_run, output_format
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
def _parse_add_requirement_args(args: list[str]) -> tuple[str, str, bool, str] | None:
|
|
710
|
+
if not args or args[0] != "add-requirement":
|
|
711
|
+
return None
|
|
712
|
+
|
|
713
|
+
positional: list[str] = []
|
|
714
|
+
dry_run = False
|
|
715
|
+
output_format = "human"
|
|
716
|
+
index = 1
|
|
717
|
+
while index < len(args):
|
|
718
|
+
option = args[index]
|
|
719
|
+
if option == "--dry-run":
|
|
720
|
+
dry_run = True
|
|
721
|
+
index += 1
|
|
722
|
+
continue
|
|
723
|
+
if option == "--json":
|
|
724
|
+
output_format = "json"
|
|
725
|
+
index += 1
|
|
726
|
+
continue
|
|
727
|
+
if option == "--format":
|
|
728
|
+
if index + 1 >= len(args) or args[index + 1] != "json":
|
|
729
|
+
return None
|
|
730
|
+
output_format = "json"
|
|
731
|
+
index += 2
|
|
732
|
+
continue
|
|
733
|
+
if option.startswith("--"):
|
|
734
|
+
return None
|
|
735
|
+
positional.append(option)
|
|
736
|
+
index += 1
|
|
737
|
+
|
|
738
|
+
if len(positional) != 2:
|
|
739
|
+
return None
|
|
740
|
+
return positional[0], positional[1], dry_run, output_format
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def _parse_plan_args(args: list[str]) -> tuple[str, str, str | None, bool, str] | None:
|
|
744
|
+
if len(args) < 2 or args[0] != "plan":
|
|
745
|
+
return None
|
|
746
|
+
|
|
747
|
+
command = args[1]
|
|
748
|
+
if command not in {
|
|
749
|
+
"start-stage",
|
|
750
|
+
"start-task",
|
|
751
|
+
"close-task",
|
|
752
|
+
"close-stage",
|
|
753
|
+
"add-stage",
|
|
754
|
+
"add-task",
|
|
755
|
+
"review-stage",
|
|
756
|
+
}:
|
|
757
|
+
return None
|
|
758
|
+
|
|
759
|
+
dry_run = False
|
|
760
|
+
output_format = "human"
|
|
761
|
+
target: str | None = None
|
|
762
|
+
slug: str | None = None
|
|
763
|
+
index = 2
|
|
764
|
+
while index < len(args):
|
|
765
|
+
option = args[index]
|
|
766
|
+
if option == "--dry-run":
|
|
767
|
+
dry_run = True
|
|
768
|
+
index += 1
|
|
769
|
+
continue
|
|
770
|
+
if option == "--json":
|
|
771
|
+
output_format = "json"
|
|
772
|
+
index += 1
|
|
773
|
+
continue
|
|
774
|
+
if option == "--format":
|
|
775
|
+
if index + 1 >= len(args) or args[index + 1] != "json":
|
|
776
|
+
return None
|
|
777
|
+
output_format = "json"
|
|
778
|
+
index += 2
|
|
779
|
+
continue
|
|
780
|
+
if option in {"--task", "--stage", "--plan"}:
|
|
781
|
+
if index + 1 >= len(args):
|
|
782
|
+
return None
|
|
783
|
+
value = args[index + 1]
|
|
784
|
+
if command in {"start-task", "close-task"} and option == "--task":
|
|
785
|
+
target = value
|
|
786
|
+
elif command in {"start-stage", "close-stage", "review-stage"} and option == "--stage":
|
|
787
|
+
target = value
|
|
788
|
+
elif command == "add-stage" and option == "--plan":
|
|
789
|
+
target = value
|
|
790
|
+
elif command == "add-task" and option == "--stage":
|
|
791
|
+
target = value
|
|
792
|
+
elif command == "add-stage" and option == "--stage":
|
|
793
|
+
slug = value
|
|
794
|
+
elif command == "add-task" and option == "--task":
|
|
795
|
+
slug = value
|
|
796
|
+
else:
|
|
797
|
+
return None
|
|
798
|
+
index += 2
|
|
799
|
+
continue
|
|
800
|
+
return None
|
|
801
|
+
|
|
802
|
+
if target is None:
|
|
803
|
+
return None
|
|
804
|
+
if command in {"add-stage", "add-task"} and slug is None:
|
|
805
|
+
return None
|
|
806
|
+
if dry_run and command == "review-stage":
|
|
807
|
+
return None
|
|
808
|
+
return command, target, slug, dry_run, output_format
|
|
809
|
+
|
|
810
|
+
|
|
811
|
+
def _parse_graph_args(args: list[str]) -> GraphFilters | None:
|
|
812
|
+
if not args or args[0] != "graph":
|
|
813
|
+
return None
|
|
814
|
+
|
|
815
|
+
values = args[1:]
|
|
816
|
+
if len(values) < 2 or values[:2] != ["--format", "json"]:
|
|
817
|
+
return None
|
|
818
|
+
|
|
819
|
+
document_type: str | None = None
|
|
820
|
+
module: str | None = None
|
|
821
|
+
document_id: str | None = None
|
|
822
|
+
index = 2
|
|
823
|
+
while index < len(values):
|
|
824
|
+
option = values[index]
|
|
825
|
+
if index + 1 >= len(values):
|
|
826
|
+
return None
|
|
827
|
+
|
|
828
|
+
value = values[index + 1]
|
|
829
|
+
if option == "--type":
|
|
830
|
+
document_type = value
|
|
831
|
+
elif option == "--module":
|
|
832
|
+
module = value
|
|
833
|
+
elif option == "--id":
|
|
834
|
+
document_id = value
|
|
835
|
+
else:
|
|
836
|
+
return None
|
|
837
|
+
index += 2
|
|
838
|
+
|
|
839
|
+
return GraphFilters(
|
|
840
|
+
document_type=document_type,
|
|
841
|
+
module=module,
|
|
842
|
+
document_id=document_id,
|
|
843
|
+
)
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
def _parse_impact_args(
|
|
847
|
+
args: list[str],
|
|
848
|
+
) -> tuple[str, tuple[str, ...], str, str | None] | None:
|
|
849
|
+
if not args or args[0] != "impact":
|
|
850
|
+
return None
|
|
851
|
+
|
|
852
|
+
values = args[1:]
|
|
853
|
+
mode: str | None = None
|
|
854
|
+
source_ids: tuple[str, ...] = ()
|
|
855
|
+
output_format = "human"
|
|
856
|
+
base_ref: str | None = None
|
|
857
|
+
index = 0
|
|
858
|
+
while index < len(values):
|
|
859
|
+
value = values[index]
|
|
860
|
+
if value == "--changed" and mode is None:
|
|
861
|
+
mode = "changed"
|
|
862
|
+
index += 1
|
|
863
|
+
continue
|
|
864
|
+
if value == "--id" and mode is None and index + 1 < len(values):
|
|
865
|
+
mode = "id"
|
|
866
|
+
source_ids = (values[index + 1],)
|
|
867
|
+
index += 2
|
|
868
|
+
continue
|
|
869
|
+
if value == "--format" and index + 1 < len(values):
|
|
870
|
+
if values[index + 1] != "json" or output_format != "human":
|
|
871
|
+
return None
|
|
872
|
+
output_format = "json"
|
|
873
|
+
index += 2
|
|
874
|
+
continue
|
|
875
|
+
if value == "--base" and index + 1 < len(values) and base_ref is None:
|
|
876
|
+
base_ref = values[index + 1]
|
|
877
|
+
index += 2
|
|
878
|
+
continue
|
|
879
|
+
return None
|
|
880
|
+
|
|
881
|
+
if mode is None or (mode == "id" and base_ref is not None):
|
|
882
|
+
return None
|
|
883
|
+
return (mode, source_ids, output_format, base_ref)
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
def _parse_context_args(args: list[str]) -> tuple[str, str, str, str | None, int | None, int | None] | None:
|
|
887
|
+
if not args or args[0] != "context":
|
|
888
|
+
return None
|
|
889
|
+
options = {}
|
|
890
|
+
index = 1
|
|
891
|
+
allowed = {"--id", "--module", "--feature", "--changed", "--format", "--history", "--body-budget-bytes", "--depth"}
|
|
892
|
+
while index < len(args):
|
|
893
|
+
option = args[index]
|
|
894
|
+
if option not in allowed or option in options:
|
|
895
|
+
return None
|
|
896
|
+
if option == "--changed":
|
|
897
|
+
options[option] = ""
|
|
898
|
+
index += 1
|
|
899
|
+
continue
|
|
900
|
+
if index + 1 >= len(args) or not args[index + 1] or args[index + 1].startswith("--"):
|
|
901
|
+
return None
|
|
902
|
+
options[option] = args[index + 1]
|
|
903
|
+
index += 2
|
|
904
|
+
sources = [key for key in options if key in {"--id", "--module", "--feature", "--changed"}]
|
|
905
|
+
if len(sources) != 1 or ("--format" in options and options["--format"] != "json"):
|
|
906
|
+
return None
|
|
907
|
+
history = options.get("--history")
|
|
908
|
+
if history is not None and history not in {"exclude", "include", "legacy"}:
|
|
909
|
+
return None
|
|
910
|
+
numeric = {}
|
|
911
|
+
for key in ("--body-budget-bytes", "--depth"):
|
|
912
|
+
value = options.get(key)
|
|
913
|
+
if value is not None:
|
|
914
|
+
if not value.isascii() or not value.isdigit():
|
|
915
|
+
return None
|
|
916
|
+
try:
|
|
917
|
+
numeric[key] = int(value)
|
|
918
|
+
except ValueError:
|
|
919
|
+
return None
|
|
920
|
+
source = sources[0]
|
|
921
|
+
return (source[2:], options[source], options.get("--format", "markdown"), history,
|
|
922
|
+
numeric.get("--body-budget-bytes"), numeric.get("--depth"))
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
def _parse_tasks_export_args(
|
|
926
|
+
args: list[str],
|
|
927
|
+
) -> tuple[str, str, bool, bool, str, str | None] | None:
|
|
928
|
+
if len(args) < 3 or args[:2] != ["tasks", "export"]:
|
|
929
|
+
return None
|
|
930
|
+
|
|
931
|
+
selector: str | None = None
|
|
932
|
+
value: str | None = None
|
|
933
|
+
include_planned = False
|
|
934
|
+
include_backlog = False
|
|
935
|
+
output_format = "json"
|
|
936
|
+
output_path: str | None = None
|
|
937
|
+
index = 2
|
|
938
|
+
while index < len(args):
|
|
939
|
+
option = args[index]
|
|
940
|
+
if option == "--include-planned":
|
|
941
|
+
include_planned = True
|
|
942
|
+
index += 1
|
|
943
|
+
continue
|
|
944
|
+
if option == "--include-backlog":
|
|
945
|
+
include_backlog = True
|
|
946
|
+
index += 1
|
|
947
|
+
continue
|
|
948
|
+
if option == "--format":
|
|
949
|
+
if index + 1 >= len(args) or args[index + 1] not in {"json", "markdown"}:
|
|
950
|
+
return None
|
|
951
|
+
output_format = args[index + 1]
|
|
952
|
+
index += 2
|
|
953
|
+
continue
|
|
954
|
+
if option == "--output":
|
|
955
|
+
if index + 1 >= len(args) or output_path is not None:
|
|
956
|
+
return None
|
|
957
|
+
output_path = args[index + 1]
|
|
958
|
+
index += 2
|
|
959
|
+
continue
|
|
960
|
+
if option in {"--task", "--stage", "--plan"}:
|
|
961
|
+
if index + 1 >= len(args) or selector is not None:
|
|
962
|
+
return None
|
|
963
|
+
selector = option.removeprefix("--")
|
|
964
|
+
value = args[index + 1]
|
|
965
|
+
index += 2
|
|
966
|
+
continue
|
|
967
|
+
return None
|
|
968
|
+
|
|
969
|
+
if selector is None or value is None:
|
|
970
|
+
return None
|
|
971
|
+
|
|
972
|
+
return selector, value, include_planned, include_backlog, output_format, output_path
|
|
973
|
+
|
|
974
|
+
|
|
975
|
+
def _parse_tasks_push_args(
|
|
976
|
+
args: list[str],
|
|
977
|
+
) -> tuple[str, str, str, str | None, str | None, str, bool, bool, bool, bool] | None:
|
|
978
|
+
if len(args) < 4 or args[:2] != ["tasks", "push"]:
|
|
979
|
+
return None
|
|
980
|
+
|
|
981
|
+
selector: str | None = None
|
|
982
|
+
value: str | None = None
|
|
983
|
+
provider: str | None = None
|
|
984
|
+
organization: str | None = None
|
|
985
|
+
project: str | None = None
|
|
986
|
+
work_item_type = "Task"
|
|
987
|
+
include_planned = False
|
|
988
|
+
include_backlog = False
|
|
989
|
+
dry_run = False
|
|
990
|
+
write_back = False
|
|
991
|
+
output_format = "json"
|
|
992
|
+
index = 2
|
|
993
|
+
while index < len(args):
|
|
994
|
+
option = args[index]
|
|
995
|
+
if option == "--include-planned":
|
|
996
|
+
include_planned = True
|
|
997
|
+
index += 1
|
|
998
|
+
continue
|
|
999
|
+
if option == "--include-backlog":
|
|
1000
|
+
include_backlog = True
|
|
1001
|
+
index += 1
|
|
1002
|
+
continue
|
|
1003
|
+
if option == "--dry-run":
|
|
1004
|
+
dry_run = True
|
|
1005
|
+
index += 1
|
|
1006
|
+
continue
|
|
1007
|
+
if option == "--write-back":
|
|
1008
|
+
write_back = True
|
|
1009
|
+
index += 1
|
|
1010
|
+
continue
|
|
1011
|
+
if option == "--format":
|
|
1012
|
+
if index + 1 >= len(args) or args[index + 1] != "json":
|
|
1013
|
+
return None
|
|
1014
|
+
output_format = args[index + 1]
|
|
1015
|
+
index += 2
|
|
1016
|
+
continue
|
|
1017
|
+
if option in {"--task", "--stage", "--plan"}:
|
|
1018
|
+
if index + 1 >= len(args) or selector is not None:
|
|
1019
|
+
return None
|
|
1020
|
+
selector = option.removeprefix("--")
|
|
1021
|
+
value = args[index + 1]
|
|
1022
|
+
index += 2
|
|
1023
|
+
continue
|
|
1024
|
+
if option in {"--provider", "--organization", "--project", "--work-item-type"}:
|
|
1025
|
+
if index + 1 >= len(args):
|
|
1026
|
+
return None
|
|
1027
|
+
option_value = args[index + 1]
|
|
1028
|
+
if option == "--provider":
|
|
1029
|
+
provider = option_value
|
|
1030
|
+
elif option == "--organization":
|
|
1031
|
+
organization = option_value
|
|
1032
|
+
elif option == "--project":
|
|
1033
|
+
project = option_value
|
|
1034
|
+
else:
|
|
1035
|
+
work_item_type = option_value
|
|
1036
|
+
index += 2
|
|
1037
|
+
continue
|
|
1038
|
+
return None
|
|
1039
|
+
|
|
1040
|
+
if selector is None or value is None or provider is None or output_format != "json":
|
|
1041
|
+
return None
|
|
1042
|
+
|
|
1043
|
+
return (
|
|
1044
|
+
selector,
|
|
1045
|
+
value,
|
|
1046
|
+
provider,
|
|
1047
|
+
organization,
|
|
1048
|
+
project,
|
|
1049
|
+
work_item_type,
|
|
1050
|
+
include_planned,
|
|
1051
|
+
include_backlog,
|
|
1052
|
+
dry_run,
|
|
1053
|
+
write_back,
|
|
1054
|
+
)
|
|
1055
|
+
|
|
1056
|
+
|
|
1057
|
+
def _context_mode(option: str) -> str | None:
|
|
1058
|
+
if option == "--id":
|
|
1059
|
+
return "id"
|
|
1060
|
+
if option == "--module":
|
|
1061
|
+
return "module"
|
|
1062
|
+
if option == "--feature":
|
|
1063
|
+
return "feature"
|
|
1064
|
+
return None
|
|
1065
|
+
|
|
1066
|
+
|
|
1067
|
+
def _parse_mcp_args(args: list[str]) -> tuple[str, str, str] | None:
|
|
1068
|
+
if not args or args[0] != "mcp":
|
|
1069
|
+
return None
|
|
1070
|
+
|
|
1071
|
+
values = args[1:]
|
|
1072
|
+
if not values:
|
|
1073
|
+
return None
|
|
1074
|
+
|
|
1075
|
+
if values[0] == "status":
|
|
1076
|
+
options = _parse_project_options(values[1:])
|
|
1077
|
+
if options is None:
|
|
1078
|
+
return None
|
|
1079
|
+
project_root, config_file = options
|
|
1080
|
+
return ("status", project_root, config_file)
|
|
1081
|
+
|
|
1082
|
+
if values[:4] == ["configure", "--client", "jetbrains", "--print-json"]:
|
|
1083
|
+
options = _parse_project_options(values[4:])
|
|
1084
|
+
if options is None:
|
|
1085
|
+
return None
|
|
1086
|
+
project_root, config_file = options
|
|
1087
|
+
return ("jetbrains-json", project_root, config_file)
|
|
1088
|
+
|
|
1089
|
+
return None
|
|
1090
|
+
|
|
1091
|
+
|
|
1092
|
+
def _parse_project_options(values: list[str]) -> tuple[str, str] | None:
|
|
1093
|
+
project_root = "."
|
|
1094
|
+
config_file = "ai-docs.config.yaml"
|
|
1095
|
+
index = 0
|
|
1096
|
+
while index < len(values):
|
|
1097
|
+
option = values[index]
|
|
1098
|
+
if index + 1 >= len(values):
|
|
1099
|
+
return None
|
|
1100
|
+
value = values[index + 1]
|
|
1101
|
+
if option == "--project-root":
|
|
1102
|
+
project_root = value
|
|
1103
|
+
elif option == "--config":
|
|
1104
|
+
config_file = value
|
|
1105
|
+
else:
|
|
1106
|
+
return None
|
|
1107
|
+
index += 2
|
|
1108
|
+
return project_root, config_file
|
|
1109
|
+
|
|
1110
|
+
|
|
1111
|
+
def _parse_planning_args(args: list[str]):
|
|
1112
|
+
if not args or args[0] != "planning":
|
|
1113
|
+
return None
|
|
1114
|
+
|
|
1115
|
+
if args[1:] == ["migrate-layout", "--dry-run"]:
|
|
1116
|
+
return ("migrate-layout",)
|
|
1117
|
+
|
|
1118
|
+
if args[:2] != ["planning", "scaffold"]:
|
|
1119
|
+
return None
|
|
1120
|
+
|
|
1121
|
+
from datetime import date
|
|
1122
|
+
|
|
1123
|
+
values = args[2:]
|
|
1124
|
+
parsed: dict[str, str] = {}
|
|
1125
|
+
index = 0
|
|
1126
|
+
while index < len(values):
|
|
1127
|
+
option = values[index]
|
|
1128
|
+
if index + 1 >= len(values):
|
|
1129
|
+
return None
|
|
1130
|
+
value = values[index + 1]
|
|
1131
|
+
if option not in {"--plan", "--stage", "--task", "--task-id", "--date"}:
|
|
1132
|
+
return None
|
|
1133
|
+
parsed[option] = value
|
|
1134
|
+
index += 2
|
|
1135
|
+
|
|
1136
|
+
required = {"--plan", "--stage", "--task", "--task-id"}
|
|
1137
|
+
if not required.issubset(parsed):
|
|
1138
|
+
return None
|
|
1139
|
+
|
|
1140
|
+
plan_date = None
|
|
1141
|
+
if "--date" in parsed:
|
|
1142
|
+
try:
|
|
1143
|
+
plan_date = date.fromisoformat(parsed["--date"])
|
|
1144
|
+
except ValueError:
|
|
1145
|
+
return None
|
|
1146
|
+
|
|
1147
|
+
return (
|
|
1148
|
+
parsed["--plan"],
|
|
1149
|
+
parsed["--stage"],
|
|
1150
|
+
parsed["--task"],
|
|
1151
|
+
parsed["--task-id"],
|
|
1152
|
+
plan_date,
|
|
1153
|
+
)
|