mindsystem-cc 3.22.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -4
- package/agents/ms-adhoc-planner.md +133 -0
- package/agents/ms-code-reviewer.md +186 -0
- package/agents/ms-compounder.md +144 -0
- package/agents/ms-roadmapper.md +4 -0
- package/commands/ms/add-todo.md +46 -131
- package/commands/ms/adhoc.md +42 -57
- package/commands/ms/audit-milestone.md +66 -89
- package/commands/ms/complete-milestone.md +6 -4
- package/commands/ms/compound.md +64 -0
- package/commands/ms/config.md +66 -49
- package/commands/ms/create-roadmap.md +8 -7
- package/commands/ms/doctor.md +29 -4
- package/commands/ms/help.md +52 -52
- package/commands/ms/new-milestone.md +4 -3
- package/commands/ms/progress.md +23 -3
- package/commands/ms/update.md +102 -0
- package/mindsystem/references/linear-cli.md +71 -0
- package/mindsystem/references/routing/audit-result-routing.md +9 -4
- package/mindsystem/references/todo-file.md +63 -0
- package/mindsystem/templates/adhoc-summary.md +4 -5
- package/mindsystem/templates/knowledge.md +1 -1
- package/mindsystem/templates/project.md +15 -4
- package/mindsystem/templates/state.md +3 -14
- package/mindsystem/templates/tech-debt.md +2 -2
- package/mindsystem/workflows/adhoc.md +128 -316
- package/mindsystem/workflows/complete-milestone.md +20 -0
- package/mindsystem/workflows/compound.md +121 -0
- package/mindsystem/workflows/doctor-fixes.md +1 -1
- package/mindsystem/workflows/plan-phase.md +1 -1
- package/package.json +1 -1
- package/scripts/__pycache__/ms-tools.cpython-314.pyc +0 -0
- package/scripts/__pycache__/test_ms_tools.cpython-314-pytest-9.0.2.pyc +0 -0
- package/scripts/fixtures/scan-context/.planning/adhoc/20260225-refactor-api/adhoc-01-SUMMARY.md +39 -0
- package/scripts/fixtures/scan-context/.planning/todos/{pending/add-logout.md → add-logout.md} +2 -2
- package/scripts/fixtures/scan-context/.planning/todos/done/setup-db.md +2 -2
- package/scripts/fixtures/scan-context/expected-output.json +21 -7
- package/scripts/ms-tools.py +42 -23
- package/scripts/test_ms_tools.py +84 -5
- package/skills/senior-review/SKILL.md +0 -3
- package/commands/ms/check-todos.md +0 -240
- package/commands/ms/plan-milestone-gaps.md +0 -288
- package/skills/senior-review/AGENTS.md +0 -531
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<purpose>
|
|
2
|
+
Compound code changes into per-subsystem knowledge files on demand. Handles work done outside the Mindsystem pipeline — direct Claude Code sessions, manual edits, merged branches.
|
|
3
|
+
</purpose>
|
|
4
|
+
|
|
5
|
+
<process>
|
|
6
|
+
|
|
7
|
+
<step name="parse_input" priority="first">
|
|
8
|
+
Parse `$ARGUMENTS` to determine input mode:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
# Validate active project
|
|
12
|
+
if [ ! -f .planning/config.json ]; then
|
|
13
|
+
echo "ERROR: No active Mindsystem project found (.planning/config.json missing)"
|
|
14
|
+
echo ""
|
|
15
|
+
echo "Options:"
|
|
16
|
+
echo "- Initialize project: /ms:new-project"
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Mode detection:**
|
|
22
|
+
- If `$ARGUMENTS` empty: **description mode** — deduce from conversation context. Summarize what was discussed/changed in the current session.
|
|
23
|
+
- If matches git SHA pattern (7-40 hex chars), contains `..`, or starts with `HEAD`: **git mode**
|
|
24
|
+
- If matches existing file path (`test -e "$ARGUMENTS"`): **file mode**
|
|
25
|
+
- Otherwise: **description mode** — treat `$ARGUMENTS` as free-text description
|
|
26
|
+
</step>
|
|
27
|
+
|
|
28
|
+
<step name="resolve_change_context">
|
|
29
|
+
Gather lightweight change context based on input mode. Keep main context lean — only stats and summaries.
|
|
30
|
+
|
|
31
|
+
**Git mode:**
|
|
32
|
+
```bash
|
|
33
|
+
# Stats only — full diff read by compounder
|
|
34
|
+
git show --stat <ref> # single commit
|
|
35
|
+
git diff --stat <range> # range
|
|
36
|
+
```
|
|
37
|
+
Capture the ref/range string for passing to compounder.
|
|
38
|
+
|
|
39
|
+
**File mode:**
|
|
40
|
+
```bash
|
|
41
|
+
git log --oneline -5 -- <path>
|
|
42
|
+
```
|
|
43
|
+
Capture file path for passing to compounder.
|
|
44
|
+
|
|
45
|
+
**Description mode (including no-args):**
|
|
46
|
+
Spawn 1 Explore agent to find relevant code changes. If changes span multiple unrelated areas, spawn a second agent for the additional area. They return:
|
|
47
|
+
- Which files changed or are relevant
|
|
48
|
+
- Which subsystems are likely affected
|
|
49
|
+
- Concise summary of changes
|
|
50
|
+
|
|
51
|
+
Thoroughness: "medium".
|
|
52
|
+
</step>
|
|
53
|
+
|
|
54
|
+
<step name="determine_subsystems">
|
|
55
|
+
Read config.json subsystems and match changes:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
jq -r '.subsystems[]' .planning/config.json 2>/dev/null
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Git/file mode:** Match file paths from diff stats against subsystem names via keyword matching.
|
|
62
|
+
|
|
63
|
+
**Description mode:** Use Explore agent findings for subsystem matching.
|
|
64
|
+
|
|
65
|
+
**Detect potential new subsystems:** Changes in file areas that don't match any existing subsystem.
|
|
66
|
+
|
|
67
|
+
**First-run handling:** If no subsystems in config.json and no knowledge files exist, propose a subsystem name derived from the project domain.
|
|
68
|
+
</step>
|
|
69
|
+
|
|
70
|
+
<step name="confirm_with_user">
|
|
71
|
+
Present findings and confirm before spawning compounder:
|
|
72
|
+
|
|
73
|
+
- Affected subsystems list
|
|
74
|
+
- Proposed new subsystems (if any)
|
|
75
|
+
- Change summary (1-3 lines)
|
|
76
|
+
|
|
77
|
+
AskUserQuestion: "Compound knowledge for these subsystems?" with options:
|
|
78
|
+
- Confirm
|
|
79
|
+
- Adjust (let me modify the list)
|
|
80
|
+
- Cancel
|
|
81
|
+
</step>
|
|
82
|
+
|
|
83
|
+
<step name="spawn_compounder">
|
|
84
|
+
Spawn ms-compounder via Task tool with:
|
|
85
|
+
- Input mode (`git`, `file`, or `description`)
|
|
86
|
+
- Change reference (git ref/range, file path, or description + exploration findings)
|
|
87
|
+
- Confirmed affected subsystems list
|
|
88
|
+
- Config.json subsystem vocabulary
|
|
89
|
+
|
|
90
|
+
Agent reads changes, reads affected knowledge files, writes updates, returns report.
|
|
91
|
+
</step>
|
|
92
|
+
|
|
93
|
+
<step name="finalize">
|
|
94
|
+
**Update config.json** (if new subsystems were confirmed in step 4):
|
|
95
|
+
```bash
|
|
96
|
+
# Add new subsystem to config.json
|
|
97
|
+
jq '.subsystems += ["new-subsystem"]' .planning/config.json > tmp.$$.json && mv tmp.$$.json .planning/config.json
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Commit changes:**
|
|
101
|
+
```bash
|
|
102
|
+
git add .planning/knowledge/*.md
|
|
103
|
+
# Only add config.json if modified
|
|
104
|
+
git add .planning/config.json 2>/dev/null
|
|
105
|
+
git commit -m "$(cat <<'EOF'
|
|
106
|
+
docs: compound knowledge from <description>
|
|
107
|
+
EOF
|
|
108
|
+
)"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Set last command:**
|
|
112
|
+
```bash
|
|
113
|
+
ms-tools set-last-command "ms:compound $ARGUMENTS"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**Report:** Subsystems updated, entries added/changed/removed, new subsystems created (if any).
|
|
117
|
+
</step>
|
|
118
|
+
|
|
119
|
+
All three finalize actions (commit, set-last-command, report) must execute — do not stop after the compounder returns.
|
|
120
|
+
|
|
121
|
+
</process>
|
|
@@ -56,7 +56,7 @@ git add .planning/phases/*/*-SUMMARY.md 2>/dev/null
|
|
|
56
56
|
git add .planning/adhoc/*-SUMMARY.md 2>/dev/null
|
|
57
57
|
git add .planning/debug/*.md 2>/dev/null
|
|
58
58
|
git add .planning/debug/resolved/*.md 2>/dev/null
|
|
59
|
-
git add .planning/todos
|
|
59
|
+
git add .planning/todos/*.md 2>/dev/null
|
|
60
60
|
git add .planning/todos/done/*.md 2>/dev/null
|
|
61
61
|
```
|
|
62
62
|
|
|
@@ -51,7 +51,7 @@ PLAN.md IS the prompt that Claude executes. Plans are grouped into execution wav
|
|
|
51
51
|
Read `.planning/STATE.md` and parse:
|
|
52
52
|
- Current position (which phase we're planning)
|
|
53
53
|
- Accumulated decisions (constraints on this phase)
|
|
54
|
-
- Pending todos (candidates for inclusion)
|
|
54
|
+
- Pending todos from `.planning/todos/` (candidates for inclusion)
|
|
55
55
|
- Blockers/concerns (things this phase may address)
|
|
56
56
|
- Brief alignment status
|
|
57
57
|
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
package/scripts/fixtures/scan-context/.planning/adhoc/20260225-refactor-api/adhoc-01-SUMMARY.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
phase: adhoc
|
|
3
|
+
plan: "01"
|
|
4
|
+
subsystem: api
|
|
5
|
+
tags: [rest, endpoints, refactor]
|
|
6
|
+
|
|
7
|
+
key-decisions:
|
|
8
|
+
- "Consolidated duplicate route handlers into shared utility"
|
|
9
|
+
- "Adopted consistent error response format across endpoints"
|
|
10
|
+
|
|
11
|
+
patterns-established:
|
|
12
|
+
- "Shared route handler pattern for CRUD operations"
|
|
13
|
+
|
|
14
|
+
key-files:
|
|
15
|
+
created:
|
|
16
|
+
- src/lib/route-utils.ts
|
|
17
|
+
modified:
|
|
18
|
+
- src/api/users/route.ts
|
|
19
|
+
- src/api/products/route.ts
|
|
20
|
+
|
|
21
|
+
duration: 15min
|
|
22
|
+
completed: 2026-02-25
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
# Adhoc Plan 01: Refactor API Route Handlers Summary
|
|
26
|
+
|
|
27
|
+
**Consolidated duplicate CRUD logic into shared route utilities**
|
|
28
|
+
|
|
29
|
+
## Performance
|
|
30
|
+
- **Duration:** 15min
|
|
31
|
+
- **Tasks:** 2
|
|
32
|
+
- **Files modified:** 3
|
|
33
|
+
|
|
34
|
+
## Accomplishments
|
|
35
|
+
- Extracted shared CRUD handler from duplicate code in user and product routes
|
|
36
|
+
- Standardized error response format
|
|
37
|
+
|
|
38
|
+
## Decisions Made
|
|
39
|
+
- Used generic handler factory pattern over middleware approach for type safety
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"adhoc_summaries": {
|
|
26
26
|
"dir": "<FIXTURE>/.planning/adhoc",
|
|
27
|
-
"scanned":
|
|
27
|
+
"scanned": 2,
|
|
28
28
|
"skipped": null
|
|
29
29
|
},
|
|
30
30
|
"completed_todos": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"skipped": null
|
|
34
34
|
},
|
|
35
35
|
"pending_todos": {
|
|
36
|
-
"dir": "<FIXTURE>/.planning/todos
|
|
36
|
+
"dir": "<FIXTURE>/.planning/todos",
|
|
37
37
|
"scanned": 1,
|
|
38
38
|
"skipped": null
|
|
39
39
|
},
|
|
@@ -199,6 +199,20 @@
|
|
|
199
199
|
"jwt",
|
|
200
200
|
"tokens"
|
|
201
201
|
]
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"path": "<FIXTURE>/.planning/adhoc/20260225-refactor-api/adhoc-01-SUMMARY.md",
|
|
205
|
+
"subsystem": "api",
|
|
206
|
+
"learnings": [
|
|
207
|
+
"Consolidated duplicate route handlers into shared utility",
|
|
208
|
+
"Adopted consistent error response format across endpoints"
|
|
209
|
+
],
|
|
210
|
+
"related_phase": "",
|
|
211
|
+
"tags": [
|
|
212
|
+
"rest",
|
|
213
|
+
"endpoints",
|
|
214
|
+
"refactor"
|
|
215
|
+
]
|
|
202
216
|
}
|
|
203
217
|
],
|
|
204
218
|
"completed_todos": [
|
|
@@ -206,17 +220,17 @@
|
|
|
206
220
|
"path": "<FIXTURE>/.planning/todos/done/setup-db.md",
|
|
207
221
|
"title": "Set up database migrations",
|
|
208
222
|
"subsystem": "database",
|
|
209
|
-
"priority":
|
|
210
|
-
"
|
|
223
|
+
"priority": 2,
|
|
224
|
+
"estimate": "M"
|
|
211
225
|
}
|
|
212
226
|
],
|
|
213
227
|
"pending_todos": [
|
|
214
228
|
{
|
|
215
|
-
"path": "<FIXTURE>/.planning/todos/
|
|
229
|
+
"path": "<FIXTURE>/.planning/todos/add-logout.md",
|
|
216
230
|
"title": "Add logout endpoint",
|
|
217
231
|
"subsystem": "auth",
|
|
218
|
-
"priority":
|
|
219
|
-
"
|
|
232
|
+
"priority": 3,
|
|
233
|
+
"estimate": "S"
|
|
220
234
|
}
|
|
221
235
|
],
|
|
222
236
|
"knowledge_files": [
|
package/scripts/ms-tools.py
CHANGED
|
@@ -798,16 +798,23 @@ def cmd_gather_milestone_stats(args: argparse.Namespace) -> None:
|
|
|
798
798
|
phase_plans = 0
|
|
799
799
|
phase_complete = 0
|
|
800
800
|
|
|
801
|
-
|
|
801
|
+
# Discover plans from both PLAN.md and SUMMARY.md files
|
|
802
|
+
# (PLAN.md may be cleaned up after execution)
|
|
803
|
+
plan_bases: set[str] = set()
|
|
804
|
+
for plan in d.glob("*-PLAN.md"):
|
|
805
|
+
plan_bases.add(plan.name.replace("-PLAN.md", ""))
|
|
806
|
+
for summary in d.glob("*-SUMMARY.md"):
|
|
807
|
+
plan_bases.add(summary.name.replace("-SUMMARY.md", ""))
|
|
808
|
+
|
|
809
|
+
for plan_base in sorted(plan_bases):
|
|
802
810
|
plan_count += 1
|
|
803
811
|
phase_plans += 1
|
|
804
|
-
plan_base = plan.name.replace("-PLAN.md", "")
|
|
805
812
|
summary = d / f"{plan_base}-SUMMARY.md"
|
|
806
813
|
if summary.is_file():
|
|
807
814
|
complete += 1
|
|
808
815
|
phase_complete += 1
|
|
809
816
|
else:
|
|
810
|
-
incomplete_list.append(f" {dirname}/{
|
|
817
|
+
incomplete_list.append(f" {dirname}/{plan_base}-PLAN.md")
|
|
811
818
|
|
|
812
819
|
phase_details.append(f"- Phase {phase_num}: {phase_name} ({phase_complete}/{phase_plans} plans)")
|
|
813
820
|
|
|
@@ -1021,30 +1028,33 @@ def cmd_generate_phase_patch(args: argparse.Namespace) -> None:
|
|
|
1021
1028
|
|
|
1022
1029
|
|
|
1023
1030
|
def cmd_generate_adhoc_patch(args: argparse.Namespace) -> None:
|
|
1024
|
-
"""Generate a patch file from an adhoc commit.
|
|
1031
|
+
"""Generate a patch file from an adhoc commit or commit range.
|
|
1025
1032
|
|
|
1026
1033
|
Contract:
|
|
1027
|
-
Args: commit (str) — commit hash, output (str) — output file path
|
|
1034
|
+
Args: commit (str) — start commit hash, output (str) — output file path,
|
|
1035
|
+
end (str, optional) — end commit hash for range diffs
|
|
1028
1036
|
Output: text — patch generation status and file path
|
|
1029
1037
|
Exit codes: 0 = success (or no changes), 1 = commit not found
|
|
1030
1038
|
Side effects: writes .patch file to output path
|
|
1031
1039
|
"""
|
|
1032
1040
|
commit_hash = args.commit
|
|
1041
|
+
end_commit = getattr(args, "end", None) or commit_hash
|
|
1033
1042
|
output_path = args.output
|
|
1034
1043
|
|
|
1035
1044
|
git_root = find_git_root()
|
|
1036
1045
|
import os
|
|
1037
1046
|
os.chdir(git_root)
|
|
1038
1047
|
|
|
1039
|
-
# Verify
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1048
|
+
# Verify commits exist
|
|
1049
|
+
for ref in {commit_hash, end_commit}:
|
|
1050
|
+
try:
|
|
1051
|
+
run_git("rev-parse", ref)
|
|
1052
|
+
except subprocess.CalledProcessError:
|
|
1053
|
+
print(f"Error: Commit {ref} not found", file=sys.stderr)
|
|
1054
|
+
sys.exit(1)
|
|
1045
1055
|
|
|
1046
1056
|
exclude_args = build_exclude_pathspecs()
|
|
1047
|
-
diff_args = ["diff", f"{commit_hash}^",
|
|
1057
|
+
diff_args = ["diff", f"{commit_hash}^", end_commit, "--", "."] + exclude_args
|
|
1048
1058
|
|
|
1049
1059
|
result = subprocess.run(
|
|
1050
1060
|
["git"] + diff_args,
|
|
@@ -1230,7 +1240,7 @@ def _scan_artifact_subsystem_values(planning: Path) -> list[str]:
|
|
|
1230
1240
|
("adhoc", "*-SUMMARY.md"),
|
|
1231
1241
|
("debug", "*.md"),
|
|
1232
1242
|
("debug/resolved", "*.md"),
|
|
1233
|
-
("todos
|
|
1243
|
+
("todos", "*.md"),
|
|
1234
1244
|
("todos/done", "*.md"),
|
|
1235
1245
|
]
|
|
1236
1246
|
for subdir, pattern in scan_globs:
|
|
@@ -1352,7 +1362,7 @@ def cmd_scan_artifact_subsystems(args: argparse.Namespace) -> None:
|
|
|
1352
1362
|
("Adhoc SUMMARYs", "adhoc", "*-SUMMARY.md"),
|
|
1353
1363
|
("Debug docs", "debug", "*.md"),
|
|
1354
1364
|
("Debug resolved", "debug/resolved", "*.md"),
|
|
1355
|
-
("Pending Todos", "todos
|
|
1365
|
+
("Pending Todos", "todos", "*.md"),
|
|
1356
1366
|
("Done Todos", "todos/done", "*.md"),
|
|
1357
1367
|
]
|
|
1358
1368
|
|
|
@@ -1769,7 +1779,7 @@ def _scan_adhoc_summaries(
|
|
|
1769
1779
|
source_info["skipped"] = "directory not found"
|
|
1770
1780
|
return [], source_info
|
|
1771
1781
|
|
|
1772
|
-
summary_files = sorted(adhoc_dir.glob("
|
|
1782
|
+
summary_files = sorted(adhoc_dir.glob("**/*-SUMMARY.md"))
|
|
1773
1783
|
if not summary_files:
|
|
1774
1784
|
source_info["skipped"] = "no adhoc SUMMARY.md files found"
|
|
1775
1785
|
return [], source_info
|
|
@@ -1785,6 +1795,12 @@ def _scan_adhoc_summaries(
|
|
|
1785
1795
|
learnings = fm.get("learnings", []) or []
|
|
1786
1796
|
if isinstance(learnings, str):
|
|
1787
1797
|
learnings = [learnings]
|
|
1798
|
+
# Fallback: extract from key-decisions if learnings absent (phase-style SUMMARY)
|
|
1799
|
+
if not learnings:
|
|
1800
|
+
key_decisions = fm.get("key-decisions", []) or []
|
|
1801
|
+
if isinstance(key_decisions, str):
|
|
1802
|
+
key_decisions = [key_decisions]
|
|
1803
|
+
learnings = key_decisions
|
|
1788
1804
|
|
|
1789
1805
|
results.append({
|
|
1790
1806
|
"path": str(path),
|
|
@@ -1802,8 +1818,8 @@ def _scan_todos(
|
|
|
1802
1818
|
subdir: str,
|
|
1803
1819
|
parse_errors: list[dict[str, str]],
|
|
1804
1820
|
) -> tuple[list[dict[str, Any]], dict[str, Any]]:
|
|
1805
|
-
"""Scan todo files (done/ or
|
|
1806
|
-
todo_dir = planning / "todos" / subdir
|
|
1821
|
+
"""Scan todo files (done/ or root todos/) for metadata."""
|
|
1822
|
+
todo_dir = planning / "todos" / subdir if subdir else planning / "todos"
|
|
1807
1823
|
source_info: dict[str, Any] = {"dir": str(todo_dir), "scanned": 0, "skipped": None}
|
|
1808
1824
|
|
|
1809
1825
|
if not todo_dir.is_dir():
|
|
@@ -1812,7 +1828,8 @@ def _scan_todos(
|
|
|
1812
1828
|
|
|
1813
1829
|
md_files = sorted(todo_dir.glob("*.md"))
|
|
1814
1830
|
if not md_files:
|
|
1815
|
-
|
|
1831
|
+
label = f"{subdir}/" if subdir else "todos/"
|
|
1832
|
+
source_info["skipped"] = f"no .md files in {label}"
|
|
1816
1833
|
return [], source_info
|
|
1817
1834
|
|
|
1818
1835
|
results: list[dict[str, Any]] = []
|
|
@@ -1828,7 +1845,7 @@ def _scan_todos(
|
|
|
1828
1845
|
"title": fm.get("title", path.stem),
|
|
1829
1846
|
"subsystem": fm.get("subsystem", ""),
|
|
1830
1847
|
"priority": fm.get("priority", ""),
|
|
1831
|
-
"
|
|
1848
|
+
"estimate": fm.get("estimate", ""),
|
|
1832
1849
|
})
|
|
1833
1850
|
|
|
1834
1851
|
return results, source_info
|
|
@@ -2003,9 +2020,10 @@ def _format_markdown(output: dict[str, Any]) -> str:
|
|
|
2003
2020
|
for t in todos:
|
|
2004
2021
|
title = t.get("title", "untitled")
|
|
2005
2022
|
priority = t.get("priority", "")
|
|
2023
|
+
estimate = t.get("estimate", "")
|
|
2006
2024
|
sub = t.get("subsystem", "")
|
|
2007
2025
|
path = t.get("path", "")
|
|
2008
|
-
lines.append(f"- **{title}** [{priority}] ({sub}) — `{path}`")
|
|
2026
|
+
lines.append(f"- **{title}** [P{priority}|{estimate}] ({sub}) — `{path}`")
|
|
2009
2027
|
sections.append("\n".join(lines))
|
|
2010
2028
|
|
|
2011
2029
|
sources = output.get("sources", {})
|
|
@@ -2080,7 +2098,7 @@ def cmd_scan_planning_context(args: argparse.Namespace) -> None:
|
|
|
2080
2098
|
debug_learnings, debug_src = _scan_debug_docs(planning, parse_errors)
|
|
2081
2099
|
adhoc_learnings, adhoc_src = _scan_adhoc_summaries(planning, parse_errors)
|
|
2082
2100
|
completed_todos, completed_src = _scan_todos(planning, "done", parse_errors)
|
|
2083
|
-
pending_todos, pending_src = _scan_todos(planning, "
|
|
2101
|
+
pending_todos, pending_src = _scan_todos(planning, "", parse_errors)
|
|
2084
2102
|
knowledge_files, knowledge_src = _scan_knowledge_files(planning, subsystems)
|
|
2085
2103
|
|
|
2086
2104
|
aggregated = _aggregate_from_summaries(summaries)
|
|
@@ -2798,9 +2816,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
2798
2816
|
p.set_defaults(func=cmd_generate_phase_patch)
|
|
2799
2817
|
|
|
2800
2818
|
# --- generate-adhoc-patch ---
|
|
2801
|
-
p = subparsers.add_parser("generate-adhoc-patch", help="Generate patch from an adhoc commit")
|
|
2802
|
-
p.add_argument("commit", help="
|
|
2819
|
+
p = subparsers.add_parser("generate-adhoc-patch", help="Generate patch from an adhoc commit or range")
|
|
2820
|
+
p.add_argument("commit", help="Start commit hash")
|
|
2803
2821
|
p.add_argument("output", help="Output path for the patch file")
|
|
2822
|
+
p.add_argument("--end", default=None, help="End commit hash for range diffs (default: same as commit)")
|
|
2804
2823
|
p.set_defaults(func=cmd_generate_adhoc_patch)
|
|
2805
2824
|
|
|
2806
2825
|
# --- archive-milestone-phases ---
|
package/scripts/test_ms_tools.py
CHANGED
|
@@ -41,6 +41,7 @@ _detect_versioned_milestone_dirs = _mod._detect_versioned_milestone_dirs
|
|
|
41
41
|
_parse_milestone_name_mapping = _mod._parse_milestone_name_mapping
|
|
42
42
|
_SafeEncoder = _mod._SafeEncoder
|
|
43
43
|
cmd_set_last_command = _mod.cmd_set_last_command
|
|
44
|
+
cmd_gather_milestone_stats = _mod.cmd_gather_milestone_stats
|
|
44
45
|
|
|
45
46
|
# ---------------------------------------------------------------------------
|
|
46
47
|
# Fixtures
|
|
@@ -480,7 +481,7 @@ def _build_scan_output(planning: Path) -> dict:
|
|
|
480
481
|
debug_learnings, debug_src = _scan_debug_docs(planning, parse_errors)
|
|
481
482
|
adhoc_learnings, adhoc_src = _scan_adhoc_summaries(planning, parse_errors)
|
|
482
483
|
completed_todos, completed_src = _scan_todos(planning, "done", parse_errors)
|
|
483
|
-
pending_todos, pending_src = _scan_todos(planning, "
|
|
484
|
+
pending_todos, pending_src = _scan_todos(planning, "", parse_errors)
|
|
484
485
|
knowledge_files, knowledge_src = _scan_knowledge_files(planning, subsystems)
|
|
485
486
|
|
|
486
487
|
aggregated = _aggregate_from_summaries(summaries)
|
|
@@ -594,10 +595,14 @@ class TestScanIntegrationTargeted:
|
|
|
594
595
|
|
|
595
596
|
def test_adhoc_learnings_collected(self):
|
|
596
597
|
output = _build_scan_output(FIXTURE_PLANNING)
|
|
597
|
-
assert len(output["adhoc_learnings"]) ==
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
assert len(
|
|
598
|
+
assert len(output["adhoc_learnings"]) == 2
|
|
599
|
+
# Flat file (old format with learnings field)
|
|
600
|
+
auth_adhoc = next(a for a in output["adhoc_learnings"] if a["subsystem"] == "auth")
|
|
601
|
+
assert len(auth_adhoc["learnings"]) == 2
|
|
602
|
+
# Subdirectory file (phase-style with key-decisions fallback)
|
|
603
|
+
api_adhoc = next(a for a in output["adhoc_learnings"] if a["subsystem"] == "api")
|
|
604
|
+
assert len(api_adhoc["learnings"]) == 2
|
|
605
|
+
assert "duplicate route handlers" in api_adhoc["learnings"][0].lower()
|
|
601
606
|
|
|
602
607
|
def test_pending_todos_collected(self):
|
|
603
608
|
output = _build_scan_output(FIXTURE_PLANNING)
|
|
@@ -932,6 +937,80 @@ class TestSetLastCommand:
|
|
|
932
937
|
assert "Last Command: ms:verify-work 10 | 2026-02-24 14:30" in text
|
|
933
938
|
|
|
934
939
|
|
|
940
|
+
class TestCmdGatherMilestoneStats:
|
|
941
|
+
"""Tests for gather-milestone-stats command."""
|
|
942
|
+
|
|
943
|
+
def _patch_git_root(self, tmp_path):
|
|
944
|
+
return mock.patch.object(_mod, "find_git_root", return_value=tmp_path)
|
|
945
|
+
|
|
946
|
+
def _patch_run_git(self):
|
|
947
|
+
return mock.patch.object(_mod, "run_git", return_value="")
|
|
948
|
+
|
|
949
|
+
def _make_phase(self, tmp_path, name, plans=None, summaries=None):
|
|
950
|
+
"""Create a phase dir with optional PLAN.md and SUMMARY.md files."""
|
|
951
|
+
phase_dir = tmp_path / ".planning" / "phases" / name
|
|
952
|
+
phase_dir.mkdir(parents=True, exist_ok=True)
|
|
953
|
+
for p in (plans or []):
|
|
954
|
+
(phase_dir / p).write_text("# Plan")
|
|
955
|
+
for s in (summaries or []):
|
|
956
|
+
(phase_dir / s).write_text("# Summary")
|
|
957
|
+
return phase_dir
|
|
958
|
+
|
|
959
|
+
def test_both_plan_and_summary(self, tmp_path, capsys):
|
|
960
|
+
self._make_phase(tmp_path, "01-auth",
|
|
961
|
+
plans=["01-01-PLAN.md"], summaries=["01-01-SUMMARY.md"])
|
|
962
|
+
args = argparse.Namespace(start_phase=1, end_phase=1)
|
|
963
|
+
with self._patch_git_root(tmp_path), self._patch_run_git():
|
|
964
|
+
cmd_gather_milestone_stats(args)
|
|
965
|
+
out = capsys.readouterr().out
|
|
966
|
+
assert "Plans: 1 total, 1 complete" in out
|
|
967
|
+
assert "Status: READY" in out
|
|
968
|
+
|
|
969
|
+
def test_summary_only_no_plan(self, tmp_path, capsys):
|
|
970
|
+
"""PLAN.md cleaned up after execution — SUMMARY.md alone counts."""
|
|
971
|
+
self._make_phase(tmp_path, "09-persistence",
|
|
972
|
+
summaries=["09-01-SUMMARY.md", "09-02-SUMMARY.md"])
|
|
973
|
+
args = argparse.Namespace(start_phase=9, end_phase=9)
|
|
974
|
+
with self._patch_git_root(tmp_path), self._patch_run_git():
|
|
975
|
+
cmd_gather_milestone_stats(args)
|
|
976
|
+
out = capsys.readouterr().out
|
|
977
|
+
assert "Plans: 2 total, 2 complete" in out
|
|
978
|
+
assert "Status: READY" in out
|
|
979
|
+
|
|
980
|
+
def test_plan_only_no_summary_is_incomplete(self, tmp_path, capsys):
|
|
981
|
+
self._make_phase(tmp_path, "03-setup",
|
|
982
|
+
plans=["03-01-PLAN.md"])
|
|
983
|
+
args = argparse.Namespace(start_phase=3, end_phase=3)
|
|
984
|
+
with self._patch_git_root(tmp_path), self._patch_run_git():
|
|
985
|
+
cmd_gather_milestone_stats(args)
|
|
986
|
+
out = capsys.readouterr().out
|
|
987
|
+
assert "Plans: 1 total, 0 complete" in out
|
|
988
|
+
assert "Status: NOT READY" in out
|
|
989
|
+
|
|
990
|
+
def test_multi_phase_mixed(self, tmp_path, capsys):
|
|
991
|
+
"""Multiple phases: some with PLANs, some with only SUMMARYs."""
|
|
992
|
+
self._make_phase(tmp_path, "09-persistence",
|
|
993
|
+
summaries=["09-01-SUMMARY.md", "09-02-SUMMARY.md"])
|
|
994
|
+
self._make_phase(tmp_path, "10-transactions",
|
|
995
|
+
plans=["10-01-PLAN.md"],
|
|
996
|
+
summaries=["10-01-SUMMARY.md"])
|
|
997
|
+
args = argparse.Namespace(start_phase=9, end_phase=10)
|
|
998
|
+
with self._patch_git_root(tmp_path), self._patch_run_git():
|
|
999
|
+
cmd_gather_milestone_stats(args)
|
|
1000
|
+
out = capsys.readouterr().out
|
|
1001
|
+
assert "Plans: 3 total, 3 complete" in out
|
|
1002
|
+
assert "Status: READY" in out
|
|
1003
|
+
|
|
1004
|
+
def test_no_plans_or_summaries(self, tmp_path, capsys):
|
|
1005
|
+
self._make_phase(tmp_path, "01-auth")
|
|
1006
|
+
args = argparse.Namespace(start_phase=1, end_phase=1)
|
|
1007
|
+
with self._patch_git_root(tmp_path), self._patch_run_git():
|
|
1008
|
+
cmd_gather_milestone_stats(args)
|
|
1009
|
+
out = capsys.readouterr().out
|
|
1010
|
+
assert "Plans: 0 total, 0 complete" in out
|
|
1011
|
+
assert "Status: NOT READY" in out
|
|
1012
|
+
|
|
1013
|
+
|
|
935
1014
|
# ===================================================================
|
|
936
1015
|
# Part 4: UAT File Management Tests
|
|
937
1016
|
# ===================================================================
|
|
@@ -211,6 +211,3 @@ verdict: "clean | minor_issues | needs_refactoring | structural_concerns"
|
|
|
211
211
|
- User has opportunity to provide context before changes
|
|
212
212
|
- YAML summary block included for orchestrator parsing
|
|
213
213
|
|
|
214
|
-
## Full Compiled Document
|
|
215
|
-
|
|
216
|
-
For the complete guide with all principles expanded: `AGENTS.md`
|