logics-manager 2.1.2__tar.gz → 2.2.0__tar.gz
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.
- {logics_manager-2.1.2 → logics_manager-2.2.0}/PKG-INFO +1 -1
- {logics_manager-2.1.2 → logics_manager-2.2.0}/README.md +47 -4
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager/assist.py +185 -21
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager/cli.py +132 -12
- logics_manager-2.2.0/logics_manager/cli_output.py +18 -0
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager/flow.py +1257 -83
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager/index.py +3 -7
- logics_manager-2.2.0/logics_manager/insights.py +418 -0
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager/mcp.py +50 -0
- logics_manager-2.2.0/logics_manager/path_utils.py +31 -0
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager/sync.py +24 -12
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager.egg-info/PKG-INFO +1 -1
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager.egg-info/SOURCES.txt +3 -0
- {logics_manager-2.1.2 → logics_manager-2.2.0}/pyproject.toml +1 -1
- {logics_manager-2.1.2 → logics_manager-2.2.0}/LICENSE +0 -0
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager/__init__.py +0 -0
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager/__main__.py +0 -0
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager/audit.py +0 -0
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager/bootstrap.py +0 -0
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager/config.py +0 -0
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager/doctor.py +0 -0
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager/lint.py +0 -0
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager/termstyle.py +0 -0
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager.egg-info/dependency_links.txt +0 -0
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager.egg-info/entry_points.txt +0 -0
- {logics_manager-2.1.2 → logics_manager-2.2.0}/logics_manager.egg-info/top_level.txt +0 -0
- {logics_manager-2.1.2 → logics_manager-2.2.0}/setup.cfg +0 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/AlexAgo83/logics-manager/actions/workflows/ci.yml)
|
|
4
4
|
[](LICENSE)
|
|
5
|
-

|
|
6
6
|

|
|
7
7
|

|
|
8
8
|

|
|
@@ -101,12 +101,55 @@ Useful commands:
|
|
|
101
101
|
|
|
102
102
|
```bash
|
|
103
103
|
logics-manager flow list
|
|
104
|
-
logics-manager flow promote request-to-backlog
|
|
105
|
-
logics-manager flow promote backlog-to-task
|
|
106
|
-
logics-manager flow finish task
|
|
104
|
+
logics-manager flow promote request-to-backlog req_001_example
|
|
105
|
+
logics-manager flow promote backlog-to-task item_001_example
|
|
106
|
+
logics-manager flow finish task task_001_example
|
|
107
107
|
logics-manager sync context-pack req_001_example --format json
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
+
### CLI Contracts
|
|
111
|
+
|
|
112
|
+
Workflow target arguments accept these forms:
|
|
113
|
+
|
|
114
|
+
- a workflow ref, such as `req_001_example`, `item_001_example`, or `task_001_example`;
|
|
115
|
+
- a repo-relative Markdown path under the matching Logics directory, such as `logics/request/req_001_example.md`;
|
|
116
|
+
- an absolute path only when it resolves inside the current repository.
|
|
117
|
+
|
|
118
|
+
Mutation commands reject `..` traversal and files outside the repository before writing. Output paths passed with `--out` must also be repo-relative and remain inside the repository after resolution. Configured log/cache paths in `logics.yaml` may be repo-relative or absolute, but absolute paths must still resolve inside the current repository.
|
|
119
|
+
|
|
120
|
+
When a command supports `--format json`, stdout is a machine-readable JSON payload. Human-oriented status, diagnostics, and progress text should not be mixed into stdout for JSON mode. This makes JSON-mode commands safe to pipe into tools such as `jq` or consume from scripts.
|
|
121
|
+
|
|
122
|
+
`--json` is a shorthand for `--format json` on commands that support JSON output.
|
|
123
|
+
|
|
124
|
+
JSON-capable operator commands:
|
|
125
|
+
|
|
126
|
+
| Command | Purpose | JSON output |
|
|
127
|
+
| --- | --- | --- |
|
|
128
|
+
| `logics-manager status` | Summarize open workflow docs and next actions. | `--format json` or `--json` |
|
|
129
|
+
| `logics-manager health` | Show workflow health counts and issue signals. | `--format json` or `--json` |
|
|
130
|
+
| `logics-manager followups` | List follow-up areas with request creation commands. | `--format json` or `--json` |
|
|
131
|
+
| `logics-manager product-consistency` | Check product brief lineage links. | `--format json` or `--json` |
|
|
132
|
+
| `logics-manager search <query>` | Search workflow docs directly. | `--format json` or `--json` |
|
|
133
|
+
| `logics-manager index` | Regenerate `logics/INDEX.md`. | `--format json` or `--json` |
|
|
134
|
+
| `logics-manager lint` | Validate doc shape and changed-doc hygiene. | `--format json` or `--json` |
|
|
135
|
+
| `logics-manager audit` | Validate workflow traceability and governance. | `--format json` or `--json` |
|
|
136
|
+
| `logics-manager sync ...` | Read, list, search, repair, and export workflow state. | `--format json` or `--json` on supported subcommands |
|
|
137
|
+
| `logics-manager assist ...` | Build review, validation, context, and runtime summaries. | `--format json` or `--json` on supported subcommands |
|
|
138
|
+
| `logics-manager flow ...` | Create, promote, split, close, finish, and list docs. | `--format json` or `--json` on supported subcommands |
|
|
139
|
+
|
|
140
|
+
Operator triage flow:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
logics-manager status --json
|
|
144
|
+
logics-manager health --json
|
|
145
|
+
logics-manager product-consistency --json
|
|
146
|
+
logics-manager followups --source-kind product --json
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Use `status` first when you need the next work signal. Use `health` for corpus-level anomalies. Use `product-consistency --strict` in release checks when active product briefs must have valid lineage. Use `followups` for open actionable follow-up areas; add `--include-closed` only when auditing historical docs.
|
|
150
|
+
|
|
151
|
+
Multi-file workflow mutations such as `flow promote`, `flow split`, and `flow finish` validate their direct inputs before writing. New workflow docs are created with exclusive filesystem writes, so an ID collision fails instead of overwriting an existing file; rerun the command to allocate a fresh ID after reviewing `git status`/`git diff`. They still operate on Markdown files in the working tree rather than through a database or transaction service; if the filesystem fails mid-write, recover with git status/diff and rerun after cleanup.
|
|
152
|
+
|
|
110
153
|
To update the installed CLI later:
|
|
111
154
|
|
|
112
155
|
```bash
|
|
@@ -14,6 +14,7 @@ from typing import Any
|
|
|
14
14
|
from .config import ConfigError, find_repo_root, load_repo_config
|
|
15
15
|
from .doctor import doctor_payload
|
|
16
16
|
from .lint import lint_payload
|
|
17
|
+
from .path_utils import resolve_repo_config_path, resolve_repo_output_path
|
|
17
18
|
from .termstyle import colorize_help
|
|
18
19
|
|
|
19
20
|
|
|
@@ -88,8 +89,9 @@ def _hybrid_measurement_log(config: dict[str, object]) -> str:
|
|
|
88
89
|
return str(_get_nested(config, "hybrid_assist", "measurement_log", default=DEFAULT_HYBRID_MEASUREMENT_LOG))
|
|
89
90
|
|
|
90
91
|
|
|
91
|
-
def _repo_path(repo_root: Path, value: str | None, default: str) -> Path:
|
|
92
|
-
|
|
92
|
+
def _repo_path(repo_root: Path, value: str | None, default: str, *, label: str) -> Path:
|
|
93
|
+
resolved, _relative = resolve_repo_config_path(repo_root, value or default, label=label)
|
|
94
|
+
return resolved
|
|
93
95
|
|
|
94
96
|
|
|
95
97
|
def _parse_package_version(repo_root: Path) -> str:
|
|
@@ -641,6 +643,35 @@ def _git_changed_paths(repo_root: Path) -> list[str]:
|
|
|
641
643
|
return [line.strip() for line in completed.stdout.splitlines() if line.strip()]
|
|
642
644
|
|
|
643
645
|
|
|
646
|
+
def _git_lines(repo_root: Path, args: list[str]) -> list[str]:
|
|
647
|
+
try:
|
|
648
|
+
completed = subprocess.run(
|
|
649
|
+
["git", *args],
|
|
650
|
+
cwd=repo_root,
|
|
651
|
+
stdout=subprocess.PIPE,
|
|
652
|
+
stderr=subprocess.DEVNULL,
|
|
653
|
+
text=True,
|
|
654
|
+
check=False,
|
|
655
|
+
)
|
|
656
|
+
except OSError:
|
|
657
|
+
return []
|
|
658
|
+
if completed.returncode != 0:
|
|
659
|
+
return []
|
|
660
|
+
return [line.strip() for line in completed.stdout.splitlines() if line.strip()]
|
|
661
|
+
|
|
662
|
+
|
|
663
|
+
def _git_range_changed_paths(repo_root: Path, since: str) -> list[str]:
|
|
664
|
+
return sorted(set(_git_lines(repo_root, ["diff", "--name-only", "--relative=.", f"{since}..HEAD"])))
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def _git_range_commits(repo_root: Path, since: str) -> list[dict[str, str]]:
|
|
668
|
+
commits: list[dict[str, str]] = []
|
|
669
|
+
for line in _git_lines(repo_root, ["log", "--oneline", f"{since}..HEAD"]):
|
|
670
|
+
commit, _, subject = line.partition(" ")
|
|
671
|
+
commits.append({"commit": commit, "subject": subject})
|
|
672
|
+
return commits
|
|
673
|
+
|
|
674
|
+
|
|
644
675
|
def _is_low_risk_generated_path(path: str) -> bool:
|
|
645
676
|
normalized = path.strip().replace("\\", "/")
|
|
646
677
|
filename = normalized.rsplit("/", 1)[-1]
|
|
@@ -1346,6 +1377,12 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1346
1377
|
closure_summary.add_argument("--dry-run", action="store_true")
|
|
1347
1378
|
closure_summary.set_defaults(func=cmd_closure_summary)
|
|
1348
1379
|
|
|
1380
|
+
handoff = sub.add_parser("handoff", help="Summarize commits, changed surfaces, Logics docs, validations, and next actions.")
|
|
1381
|
+
handoff.add_argument("--since", required=True)
|
|
1382
|
+
handoff.add_argument("--format", choices=("text", "json"), default="text")
|
|
1383
|
+
handoff.add_argument("--dry-run", action="store_true")
|
|
1384
|
+
handoff.set_defaults(func=cmd_handoff)
|
|
1385
|
+
|
|
1349
1386
|
return parser
|
|
1350
1387
|
|
|
1351
1388
|
|
|
@@ -1417,11 +1454,15 @@ def _build_help() -> str:
|
|
|
1417
1454
|
" closure-summary [ref]",
|
|
1418
1455
|
" Summarize a delivered request, backlog item, or task.",
|
|
1419
1456
|
" Flags: --format {text,json}, --dry-run",
|
|
1457
|
+
" handoff",
|
|
1458
|
+
" Summarize commits, changed surfaces, Logics docs, validations, and next actions.",
|
|
1459
|
+
" Flags: --since, --format {text,json}, --dry-run",
|
|
1420
1460
|
"",
|
|
1421
1461
|
"Examples:",
|
|
1422
1462
|
" logics-manager assist runtime-status --format json",
|
|
1423
1463
|
" logics-manager assist context request req_001_my_request --profile deep",
|
|
1424
1464
|
" logics-manager assist request-draft --intent \"Improve onboarding\"",
|
|
1465
|
+
" logics-manager assist handoff --since HEAD~1",
|
|
1425
1466
|
]
|
|
1426
1467
|
)
|
|
1427
1468
|
|
|
@@ -1527,6 +1568,21 @@ def _build_command_help(command: str) -> str:
|
|
|
1527
1568
|
" --dry-run",
|
|
1528
1569
|
]
|
|
1529
1570
|
)
|
|
1571
|
+
if command == "handoff":
|
|
1572
|
+
return "\n".join(
|
|
1573
|
+
[
|
|
1574
|
+
"Logics Assist Handoff",
|
|
1575
|
+
"Summarize commits, changed surfaces, Logics docs, validations, and next actions.",
|
|
1576
|
+
"",
|
|
1577
|
+
"Usage:",
|
|
1578
|
+
" logics-manager assist handoff --since <rev> [args...]",
|
|
1579
|
+
"",
|
|
1580
|
+
"Flags:",
|
|
1581
|
+
" --since",
|
|
1582
|
+
" --format {text,json}",
|
|
1583
|
+
" --dry-run",
|
|
1584
|
+
]
|
|
1585
|
+
)
|
|
1530
1586
|
if command == "roi-report":
|
|
1531
1587
|
return "\n".join(
|
|
1532
1588
|
[
|
|
@@ -2023,6 +2079,77 @@ def _build_closure_summary(repo_root: Path, ref: str | None) -> dict[str, object
|
|
|
2023
2079
|
}
|
|
2024
2080
|
|
|
2025
2081
|
|
|
2082
|
+
def _doc_title_from_path(path: Path) -> str:
|
|
2083
|
+
try:
|
|
2084
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
2085
|
+
except OSError:
|
|
2086
|
+
return path.stem
|
|
2087
|
+
for line in lines:
|
|
2088
|
+
if line.startswith("## "):
|
|
2089
|
+
payload = line.removeprefix("## ").strip()
|
|
2090
|
+
if " - " in payload:
|
|
2091
|
+
return payload.split(" - ", 1)[1].strip()
|
|
2092
|
+
return payload
|
|
2093
|
+
return path.stem
|
|
2094
|
+
|
|
2095
|
+
|
|
2096
|
+
def _validation_lines_from_task(path: Path) -> list[str]:
|
|
2097
|
+
try:
|
|
2098
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
2099
|
+
except OSError:
|
|
2100
|
+
return []
|
|
2101
|
+
values: list[str] = []
|
|
2102
|
+
for line in _section_lines(lines, "Validation"):
|
|
2103
|
+
stripped = line.strip()
|
|
2104
|
+
if not stripped.startswith("- "):
|
|
2105
|
+
continue
|
|
2106
|
+
value = stripped[2:].strip()
|
|
2107
|
+
if value and not value.lower().startswith("run `") and not value.lower().startswith("run the "):
|
|
2108
|
+
values.append(value)
|
|
2109
|
+
return values
|
|
2110
|
+
|
|
2111
|
+
|
|
2112
|
+
def _build_handoff(repo_root: Path, since: str) -> dict[str, object]:
|
|
2113
|
+
changed_paths = _git_range_changed_paths(repo_root, since)
|
|
2114
|
+
commits = _git_range_commits(repo_root, since)
|
|
2115
|
+
surface = _build_changed_surface_summary(changed_paths)
|
|
2116
|
+
logics_docs: list[dict[str, object]] = []
|
|
2117
|
+
validations: list[str] = []
|
|
2118
|
+
for rel_path in changed_paths:
|
|
2119
|
+
if not rel_path.startswith("logics/") or not rel_path.endswith(".md"):
|
|
2120
|
+
continue
|
|
2121
|
+
path = repo_root / rel_path
|
|
2122
|
+
kind = path.parent.name
|
|
2123
|
+
entry = {
|
|
2124
|
+
"path": rel_path,
|
|
2125
|
+
"ref": path.stem,
|
|
2126
|
+
"kind": kind,
|
|
2127
|
+
"title": _doc_title_from_path(path),
|
|
2128
|
+
"status": _doc_status(path) if path.is_file() else "Unknown",
|
|
2129
|
+
}
|
|
2130
|
+
logics_docs.append(entry)
|
|
2131
|
+
if kind == "tasks":
|
|
2132
|
+
validations.extend(_validation_lines_from_task(path))
|
|
2133
|
+
next_actions = [
|
|
2134
|
+
"Run lint/audit if not already included in validation evidence.",
|
|
2135
|
+
"Review changed files before committing or handing off.",
|
|
2136
|
+
]
|
|
2137
|
+
if any(path.startswith("logics_manager/") for path in changed_paths):
|
|
2138
|
+
next_actions.append("Run `PYTHONPATH=\"$PWD\" pytest python_tests -q` for Python CLI changes.")
|
|
2139
|
+
if any(path.startswith("src/") for path in changed_paths):
|
|
2140
|
+
next_actions.append("Run the TypeScript/vitest checks for extension changes.")
|
|
2141
|
+
return {
|
|
2142
|
+
"since": since,
|
|
2143
|
+
"commit_count": len(commits),
|
|
2144
|
+
"commits": commits,
|
|
2145
|
+
"changed_paths": changed_paths,
|
|
2146
|
+
"surface": surface,
|
|
2147
|
+
"logics_docs": logics_docs,
|
|
2148
|
+
"validations": sorted(set(validations)),
|
|
2149
|
+
"next_actions": next_actions,
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
|
|
2026
2153
|
def _build_context_pack(repo_root: Path, seed_ref: str, *, mode: str, profile: str) -> dict[str, object]:
|
|
2027
2154
|
docs = _workflow_docs(repo_root)
|
|
2028
2155
|
selected: list[Path] = []
|
|
@@ -2236,8 +2363,8 @@ def cmd_test_impact_summary(args: argparse.Namespace) -> dict[str, object]:
|
|
|
2236
2363
|
def cmd_roi_report(args: argparse.Namespace) -> dict[str, object]:
|
|
2237
2364
|
repo_root = find_repo_root(Path.cwd())
|
|
2238
2365
|
config, config_path = load_repo_config(repo_root)
|
|
2239
|
-
audit_log = _repo_path(repo_root, args.audit_log, _hybrid_audit_log(config))
|
|
2240
|
-
measurement_log = _repo_path(repo_root, args.measurement_log, _hybrid_measurement_log(config))
|
|
2366
|
+
audit_log = _repo_path(repo_root, args.audit_log, _hybrid_audit_log(config), label="configured audit_log")
|
|
2367
|
+
measurement_log = _repo_path(repo_root, args.measurement_log, _hybrid_measurement_log(config), label="configured measurement_log")
|
|
2241
2368
|
payload = _build_hybrid_roi_report(
|
|
2242
2369
|
repo_root,
|
|
2243
2370
|
audit_log=audit_log,
|
|
@@ -2251,13 +2378,16 @@ def cmd_roi_report(args: argparse.Namespace) -> dict[str, object]:
|
|
|
2251
2378
|
payload["config_path"] = str(config_path.relative_to(repo_root)) if config_path is not None else None
|
|
2252
2379
|
|
|
2253
2380
|
if args.out:
|
|
2254
|
-
out_path = (repo_root
|
|
2381
|
+
out_path, output_path = resolve_repo_output_path(repo_root, args.out)
|
|
2382
|
+
payload["output_path"] = output_path
|
|
2255
2383
|
serialized = json.dumps(payload, indent=2, sort_keys=True) + "\n"
|
|
2256
2384
|
if not args.dry_run:
|
|
2257
2385
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
2258
2386
|
out_path.write_text(serialized, encoding="utf-8")
|
|
2259
|
-
|
|
2260
|
-
|
|
2387
|
+
if args.format == "json":
|
|
2388
|
+
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
2389
|
+
else:
|
|
2390
|
+
print(f"Wrote {output_path}")
|
|
2261
2391
|
elif args.format == "json":
|
|
2262
2392
|
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
2263
2393
|
else:
|
|
@@ -2307,13 +2437,16 @@ def cmd_runtime_status(args: argparse.Namespace) -> dict[str, object]:
|
|
|
2307
2437
|
}
|
|
2308
2438
|
|
|
2309
2439
|
if args.out:
|
|
2310
|
-
out_path = (repo_root
|
|
2440
|
+
out_path, output_path = resolve_repo_output_path(repo_root, args.out)
|
|
2441
|
+
payload["output_path"] = output_path
|
|
2311
2442
|
serialized = json.dumps(payload, indent=2, sort_keys=True) + "\n"
|
|
2312
2443
|
if not args.dry_run:
|
|
2313
2444
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
2314
2445
|
out_path.write_text(serialized, encoding="utf-8")
|
|
2315
|
-
|
|
2316
|
-
|
|
2446
|
+
if args.format == "json":
|
|
2447
|
+
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
2448
|
+
else:
|
|
2449
|
+
print(f"Wrote {output_path}")
|
|
2317
2450
|
elif args.format == "json":
|
|
2318
2451
|
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
2319
2452
|
else:
|
|
@@ -2363,14 +2496,14 @@ def cmd_request_draft(args: argparse.Namespace) -> dict[str, object]:
|
|
|
2363
2496
|
**_build_request_draft(repo_root, intent=args.intent),
|
|
2364
2497
|
}
|
|
2365
2498
|
if args.execution_mode == "execute":
|
|
2366
|
-
out_path = repo_root
|
|
2499
|
+
out_path, output_path = resolve_repo_output_path(repo_root, str(payload["path"]), label="output")
|
|
2367
2500
|
if not args.dry_run:
|
|
2368
2501
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
2369
2502
|
out_path.write_text(payload["content"], encoding="utf-8")
|
|
2370
2503
|
payload["written"] = True
|
|
2371
2504
|
else:
|
|
2372
2505
|
payload["written"] = False
|
|
2373
|
-
payload["output_path"] =
|
|
2506
|
+
payload["output_path"] = output_path
|
|
2374
2507
|
else:
|
|
2375
2508
|
payload["written"] = False
|
|
2376
2509
|
if args.format == "json":
|
|
@@ -2409,14 +2542,14 @@ def cmd_spec_first_pass(args: argparse.Namespace) -> dict[str, object]:
|
|
|
2409
2542
|
**_build_spec_first_pass(repo_root, args.ref),
|
|
2410
2543
|
}
|
|
2411
2544
|
if args.execution_mode == "execute":
|
|
2412
|
-
out_path = repo_root
|
|
2545
|
+
out_path, output_path = resolve_repo_output_path(repo_root, str(payload["path"]), label="output")
|
|
2413
2546
|
if not args.dry_run:
|
|
2414
2547
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
2415
2548
|
out_path.write_text(payload["content"], encoding="utf-8")
|
|
2416
2549
|
payload["written"] = True
|
|
2417
2550
|
else:
|
|
2418
2551
|
payload["written"] = False
|
|
2419
|
-
payload["output_path"] =
|
|
2552
|
+
payload["output_path"] = output_path
|
|
2420
2553
|
else:
|
|
2421
2554
|
payload["written"] = False
|
|
2422
2555
|
if args.format == "json":
|
|
@@ -2455,16 +2588,16 @@ def cmd_backlog_groom(args: argparse.Namespace) -> dict[str, object]:
|
|
|
2455
2588
|
**_build_backlog_groom(repo_root, args.ref),
|
|
2456
2589
|
}
|
|
2457
2590
|
if args.execution_mode == "execute":
|
|
2458
|
-
out_path = repo_root
|
|
2591
|
+
out_path, output_path = resolve_repo_output_path(repo_root, str(payload["path"]), label="output")
|
|
2459
2592
|
if not args.dry_run:
|
|
2460
2593
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
2461
2594
|
out_path.write_text(payload["content"], encoding="utf-8")
|
|
2462
2595
|
payload["written"] = True
|
|
2463
|
-
request_path = repo_root
|
|
2596
|
+
request_path, _request_output_path = resolve_repo_output_path(repo_root, str(payload["request_path"]), label="request_path")
|
|
2464
2597
|
_append_section_bullets(request_path, "Backlog", [f"`{payload['ref']}`"], dry_run=False)
|
|
2465
2598
|
else:
|
|
2466
2599
|
payload["written"] = False
|
|
2467
|
-
payload["output_path"] =
|
|
2600
|
+
payload["output_path"] = output_path
|
|
2468
2601
|
else:
|
|
2469
2602
|
payload["written"] = False
|
|
2470
2603
|
if args.format == "json":
|
|
@@ -2513,6 +2646,34 @@ def cmd_closure_summary(args: argparse.Namespace) -> dict[str, object]:
|
|
|
2513
2646
|
return payload
|
|
2514
2647
|
|
|
2515
2648
|
|
|
2649
|
+
def cmd_handoff(args: argparse.Namespace) -> dict[str, object]:
|
|
2650
|
+
repo_root = find_repo_root(Path.cwd())
|
|
2651
|
+
config, config_path = load_repo_config(repo_root)
|
|
2652
|
+
payload = {
|
|
2653
|
+
"command": "assist",
|
|
2654
|
+
"kind": "handoff",
|
|
2655
|
+
"repo_root": repo_root.as_posix(),
|
|
2656
|
+
"config_path": str(config_path.relative_to(repo_root)) if config_path is not None else None,
|
|
2657
|
+
**_build_handoff(repo_root, args.since),
|
|
2658
|
+
}
|
|
2659
|
+
if args.format == "json":
|
|
2660
|
+
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
2661
|
+
else:
|
|
2662
|
+
print(f"Handoff since {payload['since']}:")
|
|
2663
|
+
print(f"- commits: {payload['commit_count']}")
|
|
2664
|
+
print(f"- changed paths: {len(payload['changed_paths'])}")
|
|
2665
|
+
print(f"- primary surface: {payload['surface']['primary_category']}")
|
|
2666
|
+
for commit in payload["commits"][:8]:
|
|
2667
|
+
print(f"- commit: {commit['commit']} {commit['subject']}")
|
|
2668
|
+
for doc in payload["logics_docs"][:8]:
|
|
2669
|
+
print(f"- logics: {doc['ref']} [{doc['status']}] {doc['path']}")
|
|
2670
|
+
for validation in payload["validations"][:8]:
|
|
2671
|
+
print(f"- validation: {validation}")
|
|
2672
|
+
for action in payload["next_actions"]:
|
|
2673
|
+
print(f"- next: {action}")
|
|
2674
|
+
return payload
|
|
2675
|
+
|
|
2676
|
+
|
|
2516
2677
|
def cmd_context(args: argparse.Namespace) -> dict[str, object]:
|
|
2517
2678
|
repo_root = find_repo_root(Path.cwd())
|
|
2518
2679
|
config, config_path = load_repo_config(repo_root)
|
|
@@ -2554,13 +2715,16 @@ def cmd_context(args: argparse.Namespace) -> dict[str, object]:
|
|
|
2554
2715
|
}
|
|
2555
2716
|
|
|
2556
2717
|
if args.out:
|
|
2557
|
-
out_path = (repo_root
|
|
2718
|
+
out_path, output_path = resolve_repo_output_path(repo_root, args.out)
|
|
2719
|
+
payload["output_path"] = output_path
|
|
2558
2720
|
serialized = json.dumps(payload, indent=2, sort_keys=True) + "\n"
|
|
2559
2721
|
if not args.dry_run:
|
|
2560
2722
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
2561
2723
|
out_path.write_text(serialized, encoding="utf-8")
|
|
2562
|
-
|
|
2563
|
-
|
|
2724
|
+
if args.format == "json":
|
|
2725
|
+
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
2726
|
+
else:
|
|
2727
|
+
print(f"Wrote {output_path}")
|
|
2564
2728
|
elif args.format == "json":
|
|
2565
2729
|
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
2566
2730
|
else:
|
|
@@ -2576,7 +2740,7 @@ def main(argv: list[str]) -> int:
|
|
|
2576
2740
|
if not argv or argv[0] in HELP_FLAGS:
|
|
2577
2741
|
_print_help(_build_help())
|
|
2578
2742
|
return 0
|
|
2579
|
-
if argv[0] in {"runtime-status", "context", "request-draft", "spec-first-pass", "backlog-groom", "closure-summary", "roi-report", "diff-risk", "commit-plan", "changed-surface-summary", "doc-consistency", "review-checklist", "validation-checklist", "validation-summary", "test-impact-summary", "claude-bridges", "claude-instructions", "next-step"} and len(argv) > 1 and argv[1] in HELP_FLAGS:
|
|
2743
|
+
if argv[0] in {"runtime-status", "context", "request-draft", "spec-first-pass", "backlog-groom", "closure-summary", "handoff", "roi-report", "diff-risk", "commit-plan", "changed-surface-summary", "doc-consistency", "review-checklist", "validation-checklist", "validation-summary", "test-impact-summary", "claude-bridges", "claude-instructions", "next-step"} and len(argv) > 1 and argv[1] in HELP_FLAGS:
|
|
2580
2744
|
_print_help(_build_command_help(argv[0]))
|
|
2581
2745
|
return 0
|
|
2582
2746
|
parser = build_parser()
|