mapify-cli 3.31.3__py3-none-any.whl → 3.31.4__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.
- mapify_cli/__init__.py +1 -1
- mapify_cli/templates/codex/hooks/scrub-internal-ids.py +24 -14
- mapify_cli/templates/codex/skills/map-efficient/SKILL.md +1 -1
- mapify_cli/templates/codex/skills/map-efficient/efficient-reference.md +7 -19
- mapify_cli/templates/codex/skills/map-plan/SKILL.md +1 -6
- mapify_cli/templates/codex/skills/map-task/SKILL.md +1 -1
- mapify_cli/templates/hooks/scrub-internal-ids.py +24 -14
- mapify_cli/templates/map/scripts/map_step_runner.py +1 -33
- mapify_cli/templates/map/scripts/scrub_internal_ids.py +23 -9
- mapify_cli/templates/skills/map-efficient/SKILL.md +1 -1
- mapify_cli/templates/skills/map-efficient/efficient-reference.md +7 -19
- mapify_cli/templates/skills/map-plan/SKILL.md +2 -7
- mapify_cli/templates/skills/map-release/SKILL.md +1 -1
- mapify_cli/templates/skills/map-release/release-reference.md +23 -5
- mapify_cli/templates/skills/map-task/SKILL.md +1 -1
- mapify_cli/templates_src/codex/skills/map-efficient/SKILL.md.jinja +1 -1
- mapify_cli/templates_src/codex/skills/map-efficient/efficient-reference.md.jinja +7 -19
- mapify_cli/templates_src/codex/skills/map-plan/SKILL.md.jinja +1 -6
- mapify_cli/templates_src/codex/skills/map-task/SKILL.md.jinja +1 -1
- mapify_cli/templates_src/hooks/scrub-internal-ids.py.jinja +24 -14
- mapify_cli/templates_src/map/scripts/map_step_runner.py.jinja +1 -33
- mapify_cli/templates_src/map/scripts/scrub_internal_ids.py.jinja +23 -9
- mapify_cli/templates_src/skills/map-efficient/SKILL.md.jinja +1 -1
- mapify_cli/templates_src/skills/map-efficient/efficient-reference.md.jinja +7 -19
- mapify_cli/templates_src/skills/map-plan/SKILL.md.jinja +2 -7
- mapify_cli/templates_src/skills/map-release/SKILL.md.jinja +1 -1
- mapify_cli/templates_src/skills/map-release/release-reference.md.jinja +23 -5
- mapify_cli/templates_src/skills/map-task/SKILL.md.jinja +1 -1
- mapify_cli/tools/validate_dependencies.py +53 -28
- {mapify_cli-3.31.3.dist-info → mapify_cli-3.31.4.dist-info}/METADATA +1 -1
- {mapify_cli-3.31.3.dist-info → mapify_cli-3.31.4.dist-info}/RECORD +34 -34
- {mapify_cli-3.31.3.dist-info → mapify_cli-3.31.4.dist-info}/WHEEL +0 -0
- {mapify_cli-3.31.3.dist-info → mapify_cli-3.31.4.dist-info}/entry_points.txt +0 -0
- {mapify_cli-3.31.3.dist-info → mapify_cli-3.31.4.dist-info}/licenses/LICENSE +0 -0
mapify_cli/__init__.py
CHANGED
|
@@ -23,7 +23,7 @@ Or install globally:
|
|
|
23
23
|
mapify check
|
|
24
24
|
"""
|
|
25
25
|
|
|
26
|
-
__version__ = "3.31.
|
|
26
|
+
__version__ = "3.31.4"
|
|
27
27
|
|
|
28
28
|
# MUST stay the first import: it turns "this interpreter is older than 3.11" into
|
|
29
29
|
# a message that says so, instead of the ImportError raised by `from datetime
|
|
@@ -14,12 +14,16 @@ Gating (no-op in ~all turns):
|
|
|
14
14
|
1. ``MAP_INVOKED_BY`` set -> exit (don't run inside a sub-agent).
|
|
15
15
|
2. no ``.map/<branch>/step_state.json`` OR ``workflow_status`` is not
|
|
16
16
|
``WORKFLOW_COMPLETE`` -> exit (run not finished).
|
|
17
|
-
3.
|
|
18
|
-
|
|
17
|
+
3. no ``.map/<branch>/run_health_report.json`` -> exit (final verification
|
|
18
|
+
not yet complete; ``workflow_status`` transitions to ``WORKFLOW_COMPLETE``
|
|
19
|
+
before the final-verifier step, so we gate on the health report instead).
|
|
20
|
+
4. marker ``.map/<branch>/.scrub_done`` present -> exit (already ran once).
|
|
21
|
+
5. ``scrub_internal_ids: false`` in ``.map/config.yaml`` -> exit (opt-out).
|
|
19
22
|
|
|
20
|
-
When it does run: calls the engine in ``clean`` mode,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
When it does run: calls the engine in ``clean`` mode, leaves any cleaned files
|
|
24
|
+
unstaged, prints a short ``stderr`` reminder to the operator, and writes the
|
|
25
|
+
marker so it fires exactly once. The operator decides how and whether to commit
|
|
26
|
+
the changes (preserving the host repository's commit conventions).
|
|
23
27
|
|
|
24
28
|
Synchronous by design (justified exception to the async-hook rule): the scrub
|
|
25
29
|
must finish and commit before the run is considered done; a detached run would
|
|
@@ -135,6 +139,13 @@ def main() -> None:
|
|
|
135
139
|
if status != "WORKFLOW_COMPLETE":
|
|
136
140
|
_silent()
|
|
137
141
|
|
|
142
|
+
# Gate on run_health_report.json: written after the final-verifier step.
|
|
143
|
+
# workflow_status becomes WORKFLOW_COMPLETE before final verification, so
|
|
144
|
+
# we wait for the health report to avoid scrubbing while the verifier is
|
|
145
|
+
# reading HEAD.
|
|
146
|
+
if not (branch_dir / "run_health_report.json").exists():
|
|
147
|
+
_silent()
|
|
148
|
+
|
|
138
149
|
marker = branch_dir / ".scrub_done"
|
|
139
150
|
if marker.exists():
|
|
140
151
|
_silent()
|
|
@@ -162,17 +173,16 @@ def main() -> None:
|
|
|
162
173
|
report = {}
|
|
163
174
|
|
|
164
175
|
modified = report.get("files_modified") or []
|
|
176
|
+
# Leave cleaned files unstaged so the operator can review and commit them
|
|
177
|
+
# using the host repository's conventions (ticket ID, sign-off, etc.).
|
|
178
|
+
# Never auto-commit with a fixed message.
|
|
165
179
|
if modified:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
["git", "commit", "-m", "chore(map): strip internal workflow IDs"],
|
|
171
|
-
cwd=PROJECT_DIR, env=env, capture_output=True, text=True, timeout=30,
|
|
172
|
-
check=False,
|
|
180
|
+
sys.stderr.write(
|
|
181
|
+
"[scrub-internal-ids] cleaned {} file(s) — review and commit with "
|
|
182
|
+
"your project's conventions:\n git add -- {}\n".format(
|
|
183
|
+
len(modified), " ".join(modified)
|
|
173
184
|
)
|
|
174
|
-
|
|
175
|
-
pass # leave the cleaned working tree in place; never block
|
|
185
|
+
)
|
|
176
186
|
|
|
177
187
|
# Mark done so the scrub fires exactly once for this completed run.
|
|
178
188
|
try:
|
|
@@ -96,7 +96,7 @@ Resolve pending approval holds before `resume_from_plan` initializes any executi
|
|
|
96
96
|
python3 .map/scripts/map_step_runner.py list_approval_holds --state pending
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
A
|
|
99
|
+
A ready plan needs no approval. A pending `plan_approval` (left by an older `$map-plan`) is not a gate: do not ask — close it via `decide_approval_hold <hold-id> approved --note "plan_approval no longer gates execution"` and continue into Step 0. A pending `dangerous_action`/`safety_guardrail` hold refuses to proceed instead — surface the hold's `reason` and stop.
|
|
100
100
|
|
|
101
101
|
## Step 0: Resume Existing State Or Plan
|
|
102
102
|
|
|
@@ -6,9 +6,8 @@ Load only the section needed by the active phase.
|
|
|
6
6
|
## Approval-Hold Preflight
|
|
7
7
|
|
|
8
8
|
Resolve any pending approval hold BEFORE Step 0's `resume_from_plan`
|
|
9
|
-
bootstraps execution state
|
|
10
|
-
|
|
11
|
-
`step_state.json` exists.
|
|
9
|
+
bootstraps execution state. A ready plan itself needs no approval —
|
|
10
|
+
`$map-plan` no longer opens a hold.
|
|
12
11
|
|
|
13
12
|
```bash
|
|
14
13
|
python3 .map/scripts/map_step_runner.py list_approval_holds --state pending
|
|
@@ -16,31 +15,20 @@ python3 .map/scripts/map_step_runner.py list_approval_holds --state pending
|
|
|
16
15
|
|
|
17
16
|
Each pending hold's `kind` decides the branch:
|
|
18
17
|
|
|
19
|
-
- **`plan_approval`**
|
|
20
|
-
|
|
18
|
+
- **`plan_approval`** (left by an older `$map-plan`) — not a gate. Do not
|
|
19
|
+
ask the operator; close it and continue:
|
|
21
20
|
```bash
|
|
22
21
|
python3 .map/scripts/map_step_runner.py decide_approval_hold \
|
|
23
|
-
<hold-id> approved --note "
|
|
24
|
-
# or:
|
|
25
|
-
python3 .map/scripts/map_step_runner.py decide_approval_hold \
|
|
26
|
-
<hold-id> denied --note "<why the plan was denied>"
|
|
22
|
+
<hold-id> approved --note "plan_approval no longer gates execution"
|
|
27
23
|
```
|
|
28
|
-
- **Approved** — continue into the pre-flight test baseline below.
|
|
29
|
-
- **Denied** — STOP. Tell the operator to revise the plan and re-run
|
|
30
|
-
`$map-plan`; there is no staleness re-check against the denied blueprint
|
|
31
|
-
— the operator who denied it is the same operator re-running
|
|
32
|
-
`$map-efficient`.
|
|
33
24
|
- **`dangerous_action` / `safety_guardrail`** (hard stops) — never decide
|
|
34
25
|
these from inside the preflight. Refuse to proceed: surface the hold's
|
|
35
26
|
`reason` and `request_summary`, and stop. The operator resolves the
|
|
36
27
|
underlying issue out of band and decides the hold via `decide_approval_hold`
|
|
37
28
|
before retrying.
|
|
38
29
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
starts, so the preflight here finds nothing pending. If the operator
|
|
42
|
-
already decided the hold manually (CLI) before starting `$map-efficient`, the
|
|
43
|
-
preflight likewise finds nothing pending and proceeds without asking.
|
|
30
|
+
If the operator already decided a hold manually (CLI) before starting
|
|
31
|
+
`$map-efficient`, the preflight finds nothing pending and proceeds.
|
|
44
32
|
|
|
45
33
|
## Pre-Monitor Gates
|
|
46
34
|
|
|
@@ -773,13 +773,9 @@ Record artifacts in the manifest:
|
|
|
773
773
|
```
|
|
774
774
|
exec_command:
|
|
775
775
|
cmd: |
|
|
776
|
-
|
|
777
|
-
printf '%s\n' "$PLAN_ARTIFACTS_RESULT"
|
|
778
|
-
PLAN_APPROVAL_HOLD_ID=$(printf '%s' "$PLAN_ARTIFACTS_RESULT" | jq -r '.plan_approval_hold_id // empty')
|
|
776
|
+
python3 .map/scripts/map_step_runner.py record_plan_artifacts
|
|
779
777
|
```
|
|
780
778
|
|
|
781
|
-
`record_plan_artifacts` also opens a `plan_approval` hold at the plan-ready boundary and returns it as `plan_approval_hold_id` (idempotent — re-running finds the same pending hold instead of spamming a new one). Capture `PLAN_APPROVAL_HOLD_ID` for the Step 8 checkpoint. This hold does **not** block `$map-plan` itself: planning artifacts are written and Step 8 prints its checkpoint regardless of whether the hold is still pending. The decision (`decide_approval_hold <hold-id> approved|denied`) happens later, at the `$map-efficient` approval-hold preflight — or automatically via an autonomous chain's `auto_decide_holds` poll. If the result instead carries `plan_approval_hold_error: true`, the hold store failed — say so in the Step 8 checkpoint and have the operator create/decide the hold manually (`create_approval_hold --kind plan_approval ...`).
|
|
782
|
-
|
|
783
779
|
---
|
|
784
780
|
|
|
785
781
|
## Step 8: Output Checkpoint
|
|
@@ -804,7 +800,6 @@ exec_command:
|
|
|
804
800
|
echo "[ok] Plan written to .map/${BRANCH}/task_plan_${BRANCH}.md"
|
|
805
801
|
echo "[ok] artifact_manifest.json updated"
|
|
806
802
|
echo "[ok] Mode: [standard | light | deep]"
|
|
807
|
-
echo "[ok] Plan approval hold: ${PLAN_APPROVAL_HOLD_ID:-none} — decide via decide_approval_hold <hold-id> <approved|denied> --note \"<operator note>\" before \$map-efficient will proceed"
|
|
808
803
|
echo ""
|
|
809
804
|
echo "Next steps:"
|
|
810
805
|
echo " 1. Review .map/${BRANCH}/task_plan_${BRANCH}.md"
|
|
@@ -71,7 +71,7 @@ fi
|
|
|
71
71
|
python3 .map/scripts/map_step_runner.py list_approval_holds --state pending
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
-
Pending `plan_approval
|
|
74
|
+
A ready plan needs no approval. Pending `plan_approval` (left by an older `$map-plan`): do not ask — close it via `decide_approval_hold <hold-id> approved --note "plan_approval no longer gates execution"` and continue to the resume call below. Pending `dangerous_action`/`safety_guardrail`: refuse to proceed — surface the hold's `reason` and stop; `$map-task` never auto-decides these.
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
77
|
BRANCH=$(git rev-parse --abbrev-ref HEAD | sed -E 's|/|-|g; s|[^a-zA-Z0-9_.-]|-|g; s|-{2,}|-|g; s|^-||; s|-$||')
|
|
@@ -14,12 +14,16 @@ Gating (no-op in ~all turns):
|
|
|
14
14
|
1. ``MAP_INVOKED_BY`` set -> exit (don't run inside a sub-agent).
|
|
15
15
|
2. no ``.map/<branch>/step_state.json`` OR ``workflow_status`` is not
|
|
16
16
|
``WORKFLOW_COMPLETE`` -> exit (run not finished).
|
|
17
|
-
3.
|
|
18
|
-
|
|
17
|
+
3. no ``.map/<branch>/run_health_report.json`` -> exit (final verification
|
|
18
|
+
not yet complete; ``workflow_status`` transitions to ``WORKFLOW_COMPLETE``
|
|
19
|
+
before the final-verifier step, so we gate on the health report instead).
|
|
20
|
+
4. marker ``.map/<branch>/.scrub_done`` present -> exit (already ran once).
|
|
21
|
+
5. ``scrub_internal_ids: false`` in ``.map/config.yaml`` -> exit (opt-out).
|
|
19
22
|
|
|
20
|
-
When it does run: calls the engine in ``clean`` mode,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
When it does run: calls the engine in ``clean`` mode, leaves any cleaned files
|
|
24
|
+
unstaged, prints a short ``stderr`` reminder to the operator, and writes the
|
|
25
|
+
marker so it fires exactly once. The operator decides how and whether to commit
|
|
26
|
+
the changes (preserving the host repository's commit conventions).
|
|
23
27
|
|
|
24
28
|
Synchronous by design (justified exception to the async-hook rule): the scrub
|
|
25
29
|
must finish and commit before the run is considered done; a detached run would
|
|
@@ -135,6 +139,13 @@ def main() -> None:
|
|
|
135
139
|
if status != "WORKFLOW_COMPLETE":
|
|
136
140
|
_silent()
|
|
137
141
|
|
|
142
|
+
# Gate on run_health_report.json: written after the final-verifier step.
|
|
143
|
+
# workflow_status becomes WORKFLOW_COMPLETE before final verification, so
|
|
144
|
+
# we wait for the health report to avoid scrubbing while the verifier is
|
|
145
|
+
# reading HEAD.
|
|
146
|
+
if not (branch_dir / "run_health_report.json").exists():
|
|
147
|
+
_silent()
|
|
148
|
+
|
|
138
149
|
marker = branch_dir / ".scrub_done"
|
|
139
150
|
if marker.exists():
|
|
140
151
|
_silent()
|
|
@@ -162,17 +173,16 @@ def main() -> None:
|
|
|
162
173
|
report = {}
|
|
163
174
|
|
|
164
175
|
modified = report.get("files_modified") or []
|
|
176
|
+
# Leave cleaned files unstaged so the operator can review and commit them
|
|
177
|
+
# using the host repository's conventions (ticket ID, sign-off, etc.).
|
|
178
|
+
# Never auto-commit with a fixed message.
|
|
165
179
|
if modified:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
["git", "commit", "-m", "chore(map): strip internal workflow IDs"],
|
|
171
|
-
cwd=PROJECT_DIR, env=env, capture_output=True, text=True, timeout=30,
|
|
172
|
-
check=False,
|
|
180
|
+
sys.stderr.write(
|
|
181
|
+
"[scrub-internal-ids] cleaned {} file(s) — review and commit with "
|
|
182
|
+
"your project's conventions:\n git add -- {}\n".format(
|
|
183
|
+
len(modified), " ".join(modified)
|
|
173
184
|
)
|
|
174
|
-
|
|
175
|
-
pass # leave the cleaned working tree in place; never block
|
|
185
|
+
)
|
|
176
186
|
|
|
177
187
|
# Mark done so the scrub fires exactly once for this completed run.
|
|
178
188
|
try:
|
|
@@ -528,7 +528,7 @@ APPROVAL_HOLD_KINDS = frozenset(
|
|
|
528
528
|
"safety_guardrail", # hard-deny safety hook triggered
|
|
529
529
|
"autonomy_posture", # git commit/push blocked by autonomy setting
|
|
530
530
|
"template_overwrite", # risky managed-file overwrite decision
|
|
531
|
-
"plan_approval", #
|
|
531
|
+
"plan_approval", # legacy plan approval gate (no longer produced)
|
|
532
532
|
"dangerous_action", # generic risky action not otherwise categorised
|
|
533
533
|
}
|
|
534
534
|
)
|
|
@@ -4022,38 +4022,6 @@ def record_plan_artifacts(branch: str | None = None) -> dict[str, object]:
|
|
|
4022
4022
|
"plan_status": stages["plan"]["status"],
|
|
4023
4023
|
}
|
|
4024
4024
|
|
|
4025
|
-
# Intent: emit the plan_approval hold at the plan-ready boundary (#422
|
|
4026
|
-
# AC-1) -- once task_plan + blueprint are both present -- so the
|
|
4027
|
-
# existing approval-hold lifecycle (#344) has a live producer instead of
|
|
4028
|
-
# only synthetic test fixtures. request_summary is derived solely from
|
|
4029
|
-
# the branch name (no timestamp/sha) so create_approval_hold's
|
|
4030
|
-
# (kind, request_summary, pending) dedup makes re-running this function
|
|
4031
|
-
# idempotent rather than spamming a fresh hold on every call.
|
|
4032
|
-
# The producer is best-effort like the other live producers (#422 INV-4
|
|
4033
|
-
# posture): a hold-store I/O failure must not fail the plan-artifact
|
|
4034
|
-
# recording that already completed above -- the result simply carries no
|
|
4035
|
-
# plan_approval_hold_id.
|
|
4036
|
-
if plan_status == "ready":
|
|
4037
|
-
try:
|
|
4038
|
-
hold_result = create_approval_hold(
|
|
4039
|
-
kind="plan_approval",
|
|
4040
|
-
reason="The generated plan requires explicit approval before execution.",
|
|
4041
|
-
request_summary=f"Approve the generated plan for {branch_name}",
|
|
4042
|
-
source="map-plan",
|
|
4043
|
-
branch=branch_name,
|
|
4044
|
-
safe_continuation=(
|
|
4045
|
-
"Review the plan and decide the hold via decide_approval_hold "
|
|
4046
|
-
"(approved/denied) before starting /map-efficient or /map-task."
|
|
4047
|
-
),
|
|
4048
|
-
)
|
|
4049
|
-
if hold_result.get("status") == "ok":
|
|
4050
|
-
result["plan_approval_hold_id"] = hold_result["hold_id"]
|
|
4051
|
-
except Exception: # noqa: BLE001 -- deliberate fallback/resilience boundary, must not propagate
|
|
4052
|
-
# Surface the degradation instead of losing the approval gate
|
|
4053
|
-
# silently: the Step 8 checkpoint prints this flag and the operator
|
|
4054
|
-
# can create/decide the hold manually before execution.
|
|
4055
|
-
result["plan_approval_hold_error"] = True
|
|
4056
|
-
|
|
4057
4025
|
return result
|
|
4058
4026
|
|
|
4059
4027
|
|
|
@@ -24,12 +24,18 @@ Safety model — conservative, hard-scoped, corruption-averse:
|
|
|
24
24
|
and unknown types are skipped entirely — never blanked.
|
|
25
25
|
3. EDIT. Only inside COMMENTS:
|
|
26
26
|
- a comment whose payload is only an ID marker -> delete the line;
|
|
27
|
-
- an ID token inside a larger comment -> strip the token and
|
|
28
|
-
|
|
27
|
+
- an ID token inside a larger comment -> strip the token and any
|
|
28
|
+
immediately trailing separator (``ID:`` -> `` ``, ``ID -`` -> `` ``),
|
|
29
|
+
empty brackets, and redundant whitespace. ``(ID)`` is handled by the
|
|
30
|
+
empty-bracket cleanup. The line is kept unless the whole comment becomes
|
|
31
|
+
empty.
|
|
29
32
|
Test identifiers carrying ``vc<n>`` are renamed (``test_vc1_foo`` ->
|
|
30
|
-
``test_foo``) with a collision guard.
|
|
31
|
-
docstrings are LEFT IN PLACE and reported
|
|
32
|
-
corrupt legitimate values, e.g.
|
|
33
|
+
``test_foo``, ``TestFoo_VC1_Bar`` -> ``TestFoo_Bar``) with a collision guard.
|
|
34
|
+
IDs in code, string literals, or docstrings are LEFT IN PLACE and reported
|
|
35
|
+
(stripping a string substring would corrupt legitimate values, e.g.
|
|
36
|
+
``"INV-7-special-sku"`` or a JSON value). Go subtest names
|
|
37
|
+
(``t.Run("AC-3: ...", ...)``) are string literals and therefore excluded;
|
|
38
|
+
they appear in the ``residual`` list so the operator can clean them manually.
|
|
33
39
|
4. RE-SCAN. After cleaning, scope is scanned again; anything not removed is
|
|
34
40
|
reported as ``residual`` rather than corrupted.
|
|
35
41
|
|
|
@@ -213,7 +219,13 @@ def scrub_line(line: str, syntax: dict | None) -> tuple[str | None, list[str], l
|
|
|
213
219
|
eligible_spans: list[tuple[int, int]] = []
|
|
214
220
|
for m in matches:
|
|
215
221
|
if _region_kind(regions, m.start()) == "comment":
|
|
216
|
-
|
|
222
|
+
start, end = m.start(), m.end()
|
|
223
|
+
# Absorb a trailing separator ("ID: text" -> "text", "ID - note" -> "note")
|
|
224
|
+
# to avoid leaving broken prose. Only one separator consumed.
|
|
225
|
+
sep = re.match(r"[ \t]*[:\-][ \t]?", line[end:])
|
|
226
|
+
if sep:
|
|
227
|
+
end += sep.end()
|
|
228
|
+
eligible_spans.append((start, end))
|
|
217
229
|
removed.append(m.group(0))
|
|
218
230
|
else:
|
|
219
231
|
residual.append(m.group(0))
|
|
@@ -254,9 +266,11 @@ def renamed_test_identifier(name: str) -> str | None:
|
|
|
254
266
|
if not _VC_SEGMENT.search(name):
|
|
255
267
|
return None
|
|
256
268
|
new = _VC_SEGMENT.sub("", name)
|
|
257
|
-
new = re.sub(r"__+", "_", new)
|
|
258
|
-
new = re.sub(r"_+$", "", new)
|
|
259
|
-
|
|
269
|
+
new = re.sub(r"__+", "_", new) # collapse double-underscore artifacts from removal
|
|
270
|
+
new = re.sub(r"_+$", "", new) # strip trailing underscore artifacts
|
|
271
|
+
# NOTE: do NOT collapse single underscores (e.g. TestFoo_VC1_Bar -> TestFoo__Bar
|
|
272
|
+
# -> TestFoo_Bar). A blanket (?<=[A-Za-z])_(?=[A-Z]) rule would also remove
|
|
273
|
+
# legitimate Go table-test separators like TestFoo_CaseA.
|
|
260
274
|
if not new or new in ("test", "test_", "Test"):
|
|
261
275
|
return None
|
|
262
276
|
if new == name:
|
|
@@ -106,7 +106,7 @@ phase that reads `$TASK_ARGS` — and resumed workflows skip it.
|
|
|
106
106
|
python3 .map/scripts/map_step_runner.py list_approval_holds --state pending
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
-
Pending `plan_approval
|
|
109
|
+
A ready plan needs no approval. Pending `plan_approval` (left by an older `/map-plan`): do not ask — close it via `decide_approval_hold <hold-id> approved --note "plan_approval no longer gates execution"` and continue into Step 0. Pending `dangerous_action`/`safety_guardrail`: refuse to proceed — surface the hold's `reason` and stop. Full recipe: [efficient-reference.md](efficient-reference.md#approval-hold-preflight).
|
|
110
110
|
|
|
111
111
|
## Step 0: Detect Existing State or Plan
|
|
112
112
|
|
|
@@ -466,9 +466,8 @@ It is read-only and exits 0 always; callers branch on `recommended_gate`.
|
|
|
466
466
|
## Approval-hold preflight
|
|
467
467
|
|
|
468
468
|
Resolve any pending approval hold BEFORE Step 0's `resume_from_plan`
|
|
469
|
-
bootstraps execution state
|
|
470
|
-
|
|
471
|
-
`step_state.json` exists.
|
|
469
|
+
bootstraps execution state. A ready plan itself needs no approval —
|
|
470
|
+
`/map-plan` no longer opens a hold.
|
|
472
471
|
|
|
473
472
|
```bash
|
|
474
473
|
python3 .map/scripts/map_step_runner.py list_approval_holds --state pending
|
|
@@ -476,31 +475,20 @@ python3 .map/scripts/map_step_runner.py list_approval_holds --state pending
|
|
|
476
475
|
|
|
477
476
|
Each pending hold's `kind` decides the branch:
|
|
478
477
|
|
|
479
|
-
- **`plan_approval`**
|
|
480
|
-
|
|
478
|
+
- **`plan_approval`** (left by an older `/map-plan`) — not a gate. Do not
|
|
479
|
+
ask the operator; close it and continue into the pre-flight test baseline:
|
|
481
480
|
```bash
|
|
482
481
|
python3 .map/scripts/map_step_runner.py decide_approval_hold \
|
|
483
|
-
<hold-id> approved --note "
|
|
484
|
-
# or:
|
|
485
|
-
python3 .map/scripts/map_step_runner.py decide_approval_hold \
|
|
486
|
-
<hold-id> denied --note "<why the plan was denied>"
|
|
482
|
+
<hold-id> approved --note "plan_approval no longer gates execution"
|
|
487
483
|
```
|
|
488
|
-
- **Approved** — continue into the pre-flight test baseline below.
|
|
489
|
-
- **Denied** — STOP. Tell the operator to revise the plan and re-run
|
|
490
|
-
`/map-plan`; there is no staleness re-check against the denied blueprint —
|
|
491
|
-
the operator who denied it is the same operator re-running
|
|
492
|
-
`/map-efficient`.
|
|
493
484
|
- **`dangerous_action` / `safety_guardrail`** (hard stops,
|
|
494
485
|
`HARD_STOP_HOLD_KINDS`) — never decide these from inside the preflight.
|
|
495
486
|
Refuse to proceed: surface the hold's `reason` and `request_summary`, and
|
|
496
487
|
stop. The operator resolves the underlying issue out of band and decides
|
|
497
488
|
the hold via `decide_approval_hold` before retrying.
|
|
498
489
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
starts, so the preflight here finds nothing pending. If the operator
|
|
502
|
-
already decided the hold manually (CLI) before starting `/map-efficient`, the
|
|
503
|
-
preflight likewise finds nothing pending and proceeds without asking.
|
|
490
|
+
If the operator already decided a hold manually (CLI) before starting
|
|
491
|
+
`/map-efficient`, the preflight finds nothing pending and proceeds.
|
|
504
492
|
|
|
505
493
|
## Pre-flight test baseline
|
|
506
494
|
|
|
@@ -428,15 +428,11 @@ Rerun blueprint validation after writing the human-readable plan if any decompos
|
|
|
428
428
|
Record planning artifacts in the branch manifest after spec, blueprint, and task plan exist. Use the named CLI — don't introspect the script:
|
|
429
429
|
|
|
430
430
|
```bash
|
|
431
|
-
|
|
432
|
-
echo "$PLAN_ARTIFACTS_RESULT"
|
|
433
|
-
PLAN_APPROVAL_HOLD_ID=$(printf '%s' "$PLAN_ARTIFACTS_RESULT" | jq -r '.plan_approval_hold_id // empty')
|
|
431
|
+
python3 .map/scripts/map_step_runner.py record_plan_artifacts
|
|
434
432
|
```
|
|
435
433
|
|
|
436
434
|
`/map-plan` deliberately stops BEFORE `INIT_STATE` (that step belongs to `/map-efficient`), so `plan_status: "ready"` requires only `task_plan_<branch>.md` + `blueprint.json` — `step_state.json` will land later. Don't be alarmed by `has_step_state: false` in the response; it's the expected planning-complete state.
|
|
437
435
|
|
|
438
|
-
`record_plan_artifacts` also opens a `plan_approval` hold at the plan-ready boundary and returns it as `plan_approval_hold_id` (idempotent — re-running finds the same pending hold instead of spamming a new one). Capture `PLAN_APPROVAL_HOLD_ID` for the Step 8 checkpoint. This hold does **not** block `/map-plan` itself: planning artifacts are written and Step 8 prints its checkpoint regardless of whether the hold is still pending. The decision (`decide_approval_hold <hold-id> approved|denied`) happens later, at the `/map-efficient` approval-hold preflight or the `/map-task` Step 1 preflight — or automatically via `/map-auto`'s hold poll. If the result instead carries `plan_approval_hold_error: true`, the hold store failed — say so in the Step 8 checkpoint and have the operator create/decide the hold manually (`create_approval_hold --kind plan_approval ...`).
|
|
439
|
-
|
|
440
436
|
Runner functions you'll commonly need from `/map-plan`:
|
|
441
437
|
|
|
442
438
|
| Function | Purpose |
|
|
@@ -460,11 +456,10 @@ Spec: .map/<branch>/spec_<branch>.md
|
|
|
460
456
|
Blueprint: .map/<branch>/blueprint.json
|
|
461
457
|
Task plan: .map/<branch>/task_plan_<branch>.md
|
|
462
458
|
Mode: [standard | light | deep]
|
|
463
|
-
Plan approval hold: <$PLAN_APPROVAL_HOLD_ID, or "none" if empty> — decide via `decide_approval_hold <hold-id> <approved|denied> --note "<operator note>"` before /map-efficient or /map-task will proceed
|
|
464
459
|
Next: /map-efficient or /map-task for a selected subtask
|
|
465
460
|
```
|
|
466
461
|
|
|
467
|
-
Include the active mode (`light` / `deep` / `standard`) so the operator knows which steps ran
|
|
462
|
+
Include the active mode (`light` / `deep` / `standard`) so the operator knows which steps ran. A ready plan needs no separate approval: `/map-efficient` or `/map-task` can start right away.
|
|
468
463
|
|
|
469
464
|
### Step 8.5: Execution Handoff Note
|
|
470
465
|
|
|
@@ -76,7 +76,7 @@ Execute all 12 gates. Read the full gate scripts in [release-reference.md § Pha
|
|
|
76
76
|
- **Gates 5–6:** Build + twine check
|
|
77
77
|
- **Gate 7:** Security audit (`pip-audit`)
|
|
78
78
|
- **Gates 8–10:** Git state — main branch, clean working directory, up-to-date with origin
|
|
79
|
-
- **Gate 11:**
|
|
79
|
+
- **Gate 11:** CI run for `git merge-base HEAD origin/main` must have `conclusion: "success"`; unpushed commits may touch only release metadata
|
|
80
80
|
- **Gate 12:** CHANGELOG.md completeness (Unreleased section exists and has content; commit/entry gap check)
|
|
81
81
|
|
|
82
82
|
**If any gate fails:** ABORT. Fix issues, re-run Phase 1.
|
|
@@ -73,13 +73,21 @@ git pull origin main
|
|
|
73
73
|
### Gate 11: CI Status Verification
|
|
74
74
|
|
|
75
75
|
```bash
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
# CI only runs on pushed commits. Local commits on top of origin/main (a
|
|
77
|
+
# docs(changelog) fix-up, later the chore(release) bump) never get a run, so
|
|
78
|
+
# verify CI on the merge-base and allow only release-metadata files after it.
|
|
79
|
+
git fetch origin main -q
|
|
80
|
+
BASE_SHA=$(git merge-base HEAD origin/main)
|
|
81
|
+
UNVERIFIED=$(git diff --name-only "$BASE_SHA" HEAD | grep -vxE 'CHANGELOG\.md|pyproject\.toml|src/mapify_cli/__init__\.py' || true)
|
|
82
|
+
[[ -n "$UNVERIFIED" ]] && echo "❌ ABORT: local commits change files CI has not verified — push them and wait for CI:" && echo "$UNVERIFIED" && exit 1
|
|
83
|
+
gh run list --commit "$BASE_SHA" --workflow CI --json conclusion,status,headSha --jq '.[0]'
|
|
78
84
|
```
|
|
79
85
|
|
|
80
86
|
**Expected Results:**
|
|
81
|
-
- ✅
|
|
87
|
+
- ✅ Commits after `BASE_SHA` touch only `CHANGELOG.md`, `pyproject.toml`, `src/mapify_cli/__init__.py`
|
|
88
|
+
- ✅ CI run for `BASE_SHA` has `conclusion: "success"` and `status: "completed"`
|
|
82
89
|
- ✅ All jobs passed (build, test, lint)
|
|
90
|
+
- ✅ `headSha` in the run output matches `BASE_SHA`
|
|
83
91
|
|
|
84
92
|
**If CI failed:** ABORT release, investigate and fix CI failures first.
|
|
85
93
|
|
|
@@ -276,9 +284,19 @@ git tag -l -n50 "$LAST_TAG"
|
|
|
276
284
|
CURRENT_BRANCH=$(git branch --show-current)
|
|
277
285
|
[[ "$CURRENT_BRANCH" != "main" ]] && echo "❌ ABORT: Not on main branch" && exit 1
|
|
278
286
|
|
|
279
|
-
|
|
287
|
+
# HEAD is the local chore(release) bump commit, which has no CI run yet.
|
|
288
|
+
# Verify CI on the merge-base with origin/main and allow only
|
|
289
|
+
# release-metadata files in the unpushed commits on top of it.
|
|
290
|
+
git fetch origin main -q
|
|
291
|
+
BASE_SHA=$(git merge-base HEAD origin/main)
|
|
292
|
+
UNVERIFIED=$(git diff --name-only "$BASE_SHA" HEAD | grep -vxE 'CHANGELOG\.md|pyproject\.toml|src/mapify_cli/__init__\.py' || true)
|
|
293
|
+
[[ -n "$UNVERIFIED" ]] && echo "❌ ABORT: unpushed commits change files CI has not verified:" && echo "$UNVERIFIED" && exit 1
|
|
294
|
+
LATEST_RUN=$(gh run list --commit "$BASE_SHA" --workflow CI --json conclusion,status,headSha --jq '.[0]')
|
|
295
|
+
[[ -z "$LATEST_RUN" || "$LATEST_RUN" == "null" ]] && echo "❌ ABORT: No CI run found for $BASE_SHA" && exit 1
|
|
296
|
+
RUN_STATUS=$(printf '%s' "$LATEST_RUN" | jq -r '.status')
|
|
297
|
+
[[ "$RUN_STATUS" != "completed" ]] && echo "❌ ABORT: CI run for $BASE_SHA is not completed (status: $RUN_STATUS)" && exit 1
|
|
280
298
|
RUN_CONCLUSION=$(printf '%s' "$LATEST_RUN" | jq -r '.conclusion')
|
|
281
|
-
[[ "$RUN_CONCLUSION" != "success" ]] && echo "❌ ABORT:
|
|
299
|
+
[[ "$RUN_CONCLUSION" != "success" ]] && echo "❌ ABORT: CI run for $BASE_SHA did not succeed (conclusion: $RUN_CONCLUSION)" && exit 1
|
|
282
300
|
|
|
283
301
|
LAST_TAG=$(git tag --sort=-version:refname | head -1)
|
|
284
302
|
git ls-remote --tags origin | grep -q "refs/tags/$LAST_TAG" && echo "❌ ABORT: Tag already on remote" && exit 1
|
|
@@ -74,7 +74,7 @@ fi
|
|
|
74
74
|
python3 .map/scripts/map_step_runner.py list_approval_holds --state pending
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
Pending `plan_approval
|
|
77
|
+
A ready plan needs no approval. Pending `plan_approval` (left by an older `/map-plan`): do not ask — close it via `decide_approval_hold <hold-id> approved --note "plan_approval no longer gates execution"` and continue to the resume call below. Pending `dangerous_action`/`safety_guardrail`: refuse to proceed — surface the hold's `reason` and stop; `/map-task` never auto-decides these.
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
80
|
BRANCH=$(git rev-parse --abbrev-ref HEAD | sed -E 's|/|-|g; s|[^a-zA-Z0-9_.-]|-|g; s|-{2,}|-|g; s|^-||; s|-$||')
|
|
@@ -91,7 +91,7 @@ Resolve pending approval holds before `resume_from_plan` initializes any executi
|
|
|
91
91
|
python3 .map/scripts/map_step_runner.py list_approval_holds --state pending
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
A
|
|
94
|
+
A ready plan needs no approval. A pending `plan_approval` (left by an older `$map-plan`) is not a gate: do not ask — close it via `decide_approval_hold <hold-id> approved --note "plan_approval no longer gates execution"` and continue into Step 0. A pending `dangerous_action`/`safety_guardrail` hold refuses to proceed instead — surface the hold's `reason` and stop.
|
|
95
95
|
|
|
96
96
|
## Step 0: Resume Existing State Or Plan
|
|
97
97
|
|
|
@@ -6,9 +6,8 @@ Load only the section needed by the active phase.
|
|
|
6
6
|
## Approval-Hold Preflight
|
|
7
7
|
|
|
8
8
|
Resolve any pending approval hold BEFORE Step 0's `resume_from_plan`
|
|
9
|
-
bootstraps execution state
|
|
10
|
-
|
|
11
|
-
`step_state.json` exists.
|
|
9
|
+
bootstraps execution state. A ready plan itself needs no approval —
|
|
10
|
+
`$map-plan` no longer opens a hold.
|
|
12
11
|
|
|
13
12
|
```bash
|
|
14
13
|
python3 .map/scripts/map_step_runner.py list_approval_holds --state pending
|
|
@@ -16,31 +15,20 @@ python3 .map/scripts/map_step_runner.py list_approval_holds --state pending
|
|
|
16
15
|
|
|
17
16
|
Each pending hold's `kind` decides the branch:
|
|
18
17
|
|
|
19
|
-
- **`plan_approval`**
|
|
20
|
-
|
|
18
|
+
- **`plan_approval`** (left by an older `$map-plan`) — not a gate. Do not
|
|
19
|
+
ask the operator; close it and continue:
|
|
21
20
|
```bash
|
|
22
21
|
python3 .map/scripts/map_step_runner.py decide_approval_hold \
|
|
23
|
-
<hold-id> approved --note "
|
|
24
|
-
# or:
|
|
25
|
-
python3 .map/scripts/map_step_runner.py decide_approval_hold \
|
|
26
|
-
<hold-id> denied --note "<why the plan was denied>"
|
|
22
|
+
<hold-id> approved --note "plan_approval no longer gates execution"
|
|
27
23
|
```
|
|
28
|
-
- **Approved** — continue into the pre-flight test baseline below.
|
|
29
|
-
- **Denied** — STOP. Tell the operator to revise the plan and re-run
|
|
30
|
-
`$map-plan`; there is no staleness re-check against the denied blueprint
|
|
31
|
-
— the operator who denied it is the same operator re-running
|
|
32
|
-
`$map-efficient`.
|
|
33
24
|
- **`dangerous_action` / `safety_guardrail`** (hard stops) — never decide
|
|
34
25
|
these from inside the preflight. Refuse to proceed: surface the hold's
|
|
35
26
|
`reason` and `request_summary`, and stop. The operator resolves the
|
|
36
27
|
underlying issue out of band and decides the hold via `decide_approval_hold`
|
|
37
28
|
before retrying.
|
|
38
29
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
starts, so the preflight here finds nothing pending. If the operator
|
|
42
|
-
already decided the hold manually (CLI) before starting `$map-efficient`, the
|
|
43
|
-
preflight likewise finds nothing pending and proceeds without asking.
|
|
30
|
+
If the operator already decided a hold manually (CLI) before starting
|
|
31
|
+
`$map-efficient`, the preflight finds nothing pending and proceeds.
|
|
44
32
|
|
|
45
33
|
## Pre-Monitor Gates
|
|
46
34
|
|
|
@@ -768,13 +768,9 @@ Record artifacts in the manifest:
|
|
|
768
768
|
```
|
|
769
769
|
exec_command:
|
|
770
770
|
cmd: |
|
|
771
|
-
|
|
772
|
-
printf '%s\n' "$PLAN_ARTIFACTS_RESULT"
|
|
773
|
-
PLAN_APPROVAL_HOLD_ID=$(printf '%s' "$PLAN_ARTIFACTS_RESULT" | jq -r '.plan_approval_hold_id // empty')
|
|
771
|
+
python3 .map/scripts/map_step_runner.py record_plan_artifacts
|
|
774
772
|
```
|
|
775
773
|
|
|
776
|
-
`record_plan_artifacts` also opens a `plan_approval` hold at the plan-ready boundary and returns it as `plan_approval_hold_id` (idempotent — re-running finds the same pending hold instead of spamming a new one). Capture `PLAN_APPROVAL_HOLD_ID` for the Step 8 checkpoint. This hold does **not** block `$map-plan` itself: planning artifacts are written and Step 8 prints its checkpoint regardless of whether the hold is still pending. The decision (`decide_approval_hold <hold-id> approved|denied`) happens later, at the `$map-efficient` approval-hold preflight — or automatically via an autonomous chain's `auto_decide_holds` poll. If the result instead carries `plan_approval_hold_error: true`, the hold store failed — say so in the Step 8 checkpoint and have the operator create/decide the hold manually (`create_approval_hold --kind plan_approval ...`).
|
|
777
|
-
|
|
778
774
|
---
|
|
779
775
|
|
|
780
776
|
## Step 8: Output Checkpoint
|
|
@@ -799,7 +795,6 @@ exec_command:
|
|
|
799
795
|
echo "[ok] Plan written to .map/${BRANCH}/task_plan_${BRANCH}.md"
|
|
800
796
|
echo "[ok] artifact_manifest.json updated"
|
|
801
797
|
echo "[ok] Mode: [standard | light | deep]"
|
|
802
|
-
echo "[ok] Plan approval hold: ${PLAN_APPROVAL_HOLD_ID:-none} — decide via decide_approval_hold <hold-id> <approved|denied> --note \"<operator note>\" before \$map-efficient will proceed"
|
|
803
798
|
echo ""
|
|
804
799
|
echo "Next steps:"
|
|
805
800
|
echo " 1. Review .map/${BRANCH}/task_plan_${BRANCH}.md"
|
|
@@ -66,7 +66,7 @@ fi
|
|
|
66
66
|
python3 .map/scripts/map_step_runner.py list_approval_holds --state pending
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
Pending `plan_approval
|
|
69
|
+
A ready plan needs no approval. Pending `plan_approval` (left by an older `$map-plan`): do not ask — close it via `decide_approval_hold <hold-id> approved --note "plan_approval no longer gates execution"` and continue to the resume call below. Pending `dangerous_action`/`safety_guardrail`: refuse to proceed — surface the hold's `reason` and stop; `$map-task` never auto-decides these.
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
72
|
BRANCH=$(git rev-parse --abbrev-ref HEAD | sed -E 's|/|-|g; s|[^a-zA-Z0-9_.-]|-|g; s|-{2,}|-|g; s|^-||; s|-$||')
|