prizmkit 1.1.160 → 1.1.161
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/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/prizmkit_runtime/heartbeat.py +9 -6
- package/bundled/dev-pipeline/prizmkit_runtime/runtime_commit.py +145 -110
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +1 -1
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +2 -2
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +1 -1
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +1 -1
- package/bundled/dev-pipeline/templates/sections/runtime-commit-handoff.md +9 -15
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +8 -5
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +4 -2
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +4 -2
- package/bundled/dev-pipeline/tests/test_runtime_commit.py +170 -66
- package/bundled/dev-pipeline/tests/test_unified_cli.py +172 -4
- package/bundled/rules/prizm/prizm-commit-workflow.md +12 -5
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/skills/prizmkit-code-review/SKILL.md +5 -5
- package/bundled/skills/prizmkit-code-review/references/independent-code-review.md +4 -5
- package/bundled/skills/prizmkit-code-review/references/review-report-template.md +4 -6
- package/bundled/skills/prizmkit-code-review/scripts/render_review_report.py +1 -4
- package/bundled/skills/prizmkit-committer/SKILL.md +47 -63
- package/bundled/skills/prizmkit-test/SKILL.md +2 -2
- package/bundled/skills/prizmkit-test/references/test-coverage-model.md +1 -1
- package/bundled/skills/prizmkit-workflow/SKILL.md +4 -6
- package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +10 -8
- package/package.json +1 -1
package/bundled/VERSION.json
CHANGED
|
@@ -59,17 +59,19 @@ def _file_size(path: Path | None) -> int:
|
|
|
59
59
|
return 0
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
def _progress_signature(path: Path | None) -> tuple[str, str]:
|
|
62
|
+
def _progress_signature(path: Path | None) -> tuple[str, str, bool]:
|
|
63
63
|
if path is None or not path.is_file():
|
|
64
|
-
return "", ""
|
|
64
|
+
return "", "", False
|
|
65
65
|
try:
|
|
66
66
|
data = json.loads(path.read_text(encoding="utf-8"))
|
|
67
67
|
except (OSError, json.JSONDecodeError):
|
|
68
|
-
return "", ""
|
|
68
|
+
return "", "", False
|
|
69
|
+
current_tool = data.get("current_tool")
|
|
70
|
+
tool_active = isinstance(current_tool, str) and bool(current_tool.strip())
|
|
69
71
|
child_signature = str(data.get("child_activity_signature") or "")
|
|
70
72
|
signature = json.dumps(
|
|
71
73
|
[
|
|
72
|
-
|
|
74
|
+
current_tool or "",
|
|
73
75
|
int(data.get("message_count") or 0),
|
|
74
76
|
int(data.get("total_tool_calls") or 0),
|
|
75
77
|
int(data.get("active_subagent_count") or 0),
|
|
@@ -81,7 +83,7 @@ def _progress_signature(path: Path | None) -> tuple[str, str]:
|
|
|
81
83
|
],
|
|
82
84
|
separators=(",", ":"),
|
|
83
85
|
)
|
|
84
|
-
return signature, child_signature
|
|
86
|
+
return signature, child_signature, tool_active
|
|
85
87
|
|
|
86
88
|
|
|
87
89
|
def observe_heartbeat_state(
|
|
@@ -91,13 +93,14 @@ def observe_heartbeat_state(
|
|
|
91
93
|
"""Observe log/progress activity and compute staleness."""
|
|
92
94
|
now = time.time()
|
|
93
95
|
log_size = _file_size(config.session_log)
|
|
94
|
-
progress_signature, child_signature = _progress_signature(config.progress_file)
|
|
96
|
+
progress_signature, child_signature, tool_active = _progress_signature(config.progress_file)
|
|
95
97
|
prev_log_size = previous.log_size if previous else 0
|
|
96
98
|
progressed = (
|
|
97
99
|
previous is None
|
|
98
100
|
or log_size > prev_log_size
|
|
99
101
|
or (progress_signature and progress_signature != previous.progress_signature)
|
|
100
102
|
or (child_signature and child_signature != previous.child_activity_signature)
|
|
103
|
+
or tool_active
|
|
101
104
|
)
|
|
102
105
|
if progressed:
|
|
103
106
|
last_seen_epoch = now
|
|
@@ -9,17 +9,15 @@ import subprocess
|
|
|
9
9
|
import tempfile
|
|
10
10
|
from dataclasses import dataclass
|
|
11
11
|
from pathlib import Path, PurePosixPath
|
|
12
|
-
from typing import Mapping
|
|
12
|
+
from typing import Mapping
|
|
13
13
|
|
|
14
14
|
from .checkpoint_state import (
|
|
15
15
|
load_checkpoint_state,
|
|
16
16
|
semantic_state_key,
|
|
17
17
|
validate_checkpoint_data,
|
|
18
18
|
)
|
|
19
|
-
from .gitops import HIDDEN_TOOL_WORKTREE_EXCLUDES
|
|
20
|
-
|
|
21
19
|
REQUEST_FILENAME = "runtime-commit-request.json"
|
|
22
|
-
REQUEST_SCHEMA_VERSION =
|
|
20
|
+
REQUEST_SCHEMA_VERSION = 2
|
|
23
21
|
_HEX_COMMIT = re.compile(r"^[0-9a-fA-F]{40,64}$")
|
|
24
22
|
_FORBIDDEN_EXACT_PATHS = {
|
|
25
23
|
".claude/settings.local.json",
|
|
@@ -37,8 +35,6 @@ _SENSITIVE_NAMES = {
|
|
|
37
35
|
"secrets.json",
|
|
38
36
|
}
|
|
39
37
|
_SENSITIVE_SUFFIXES = {".pem", ".p12", ".pfx"}
|
|
40
|
-
_SUPPORT_ROOT_FILES = {"agents.md", "skills-lock.json"}
|
|
41
|
-
_SUPPORT_ROOT_DIRS = {".claude", ".codebuddy", ".codex", ".agents", ".pi"}
|
|
42
38
|
|
|
43
39
|
|
|
44
40
|
@dataclass(frozen=True)
|
|
@@ -48,7 +44,6 @@ class RuntimeCommitRequest:
|
|
|
48
44
|
artifact_dir: str
|
|
49
45
|
base_head: str
|
|
50
46
|
commit_message: str
|
|
51
|
-
intended_paths: tuple[str, ...]
|
|
52
47
|
runtime_commit_hash: str = ""
|
|
53
48
|
|
|
54
49
|
|
|
@@ -120,17 +115,37 @@ def finalize_runtime_commit(
|
|
|
120
115
|
return RuntimeCommitResult("failed", "premature_runtime_commit_hash")
|
|
121
116
|
if state.semantic_complete:
|
|
122
117
|
return RuntimeCommitResult("failed", "completed_checkpoint_without_commit")
|
|
123
|
-
|
|
118
|
+
unmerged = _git(root, "ls-files", "-u")
|
|
119
|
+
if unmerged.return_code != 0:
|
|
120
|
+
return RuntimeCommitResult("failed", "git_unmerged_state_unavailable")
|
|
121
|
+
if unmerged.stdout.strip():
|
|
122
|
+
return RuntimeCommitResult("failed", "unresolved_merge_state")
|
|
123
|
+
changed, status_error = _workspace_changed_paths(
|
|
124
124
|
root,
|
|
125
|
-
artifact_dir=artifact,
|
|
126
125
|
checkpoint_path=checkpoint,
|
|
127
126
|
request_path=request_path,
|
|
128
127
|
)
|
|
129
128
|
if status_error:
|
|
130
129
|
return RuntimeCommitResult("failed", status_error)
|
|
131
|
-
if
|
|
132
|
-
return RuntimeCommitResult("failed", "
|
|
133
|
-
|
|
130
|
+
if not changed:
|
|
131
|
+
return RuntimeCommitResult("failed", "empty_workspace_change")
|
|
132
|
+
unsafe = next((path for path in changed if _sensitive_commit_path(path)), "")
|
|
133
|
+
if unsafe:
|
|
134
|
+
return RuntimeCommitResult("failed", "sensitive_workspace_path")
|
|
135
|
+
bookkeeping_staged = _staged_bookkeeping_paths(
|
|
136
|
+
root,
|
|
137
|
+
checkpoint_path=checkpoint,
|
|
138
|
+
request_path=request_path,
|
|
139
|
+
)
|
|
140
|
+
if bookkeeping_staged is None:
|
|
141
|
+
return RuntimeCommitResult("failed", "git_staged_set_unavailable")
|
|
142
|
+
if bookkeeping_staged:
|
|
143
|
+
return RuntimeCommitResult("failed", "runtime_bookkeeping_staged")
|
|
144
|
+
staged = _stage_workspace(
|
|
145
|
+
root,
|
|
146
|
+
checkpoint_path=checkpoint,
|
|
147
|
+
request_path=request_path,
|
|
148
|
+
)
|
|
134
149
|
if staged.return_code != 0:
|
|
135
150
|
return RuntimeCommitResult("failed", "git_stage_failed")
|
|
136
151
|
staged_paths = _git(
|
|
@@ -139,10 +154,19 @@ def finalize_runtime_commit(
|
|
|
139
154
|
if staged_paths.return_code != 0:
|
|
140
155
|
return RuntimeCommitResult("failed", "git_staged_set_unavailable")
|
|
141
156
|
staged_set = {path for path in staged_paths.stdout.split("\0") if path}
|
|
142
|
-
if staged_set != set(
|
|
143
|
-
return RuntimeCommitResult("failed", "
|
|
157
|
+
if staged_set != set(changed):
|
|
158
|
+
return RuntimeCommitResult("failed", "staged_workspace_mismatch")
|
|
144
159
|
if not staged_set:
|
|
145
160
|
return RuntimeCommitResult("failed", "empty_staged_change")
|
|
161
|
+
unstaged_candidates = _workspace_has_unstaged_candidates(
|
|
162
|
+
root,
|
|
163
|
+
checkpoint_path=checkpoint,
|
|
164
|
+
request_path=request_path,
|
|
165
|
+
)
|
|
166
|
+
if unstaged_candidates is None:
|
|
167
|
+
return RuntimeCommitResult("failed", "git_workspace_verification_failed")
|
|
168
|
+
if unstaged_candidates:
|
|
169
|
+
return RuntimeCommitResult("failed", "workspace_changed_during_staging")
|
|
146
170
|
committed = _git(
|
|
147
171
|
root,
|
|
148
172
|
"-c", "core.hooksPath=/dev/null",
|
|
@@ -204,7 +228,7 @@ def _load_request(
|
|
|
204
228
|
if not isinstance(raw, Mapping) or raw.get("schema_version") != REQUEST_SCHEMA_VERSION:
|
|
205
229
|
return None, "commit_request_invalid_schema"
|
|
206
230
|
required_keys = {
|
|
207
|
-
"schema_version", "artifact_dir", "base_head", "commit_message",
|
|
231
|
+
"schema_version", "artifact_dir", "base_head", "commit_message",
|
|
208
232
|
}
|
|
209
233
|
allowed_keys = required_keys | {"runtime_commit_hash"}
|
|
210
234
|
if not required_keys.issubset(raw) or not set(raw).issubset(allowed_keys):
|
|
@@ -223,18 +247,6 @@ def _load_request(
|
|
|
223
247
|
if not message or "\n" in message or len(message) > 240:
|
|
224
248
|
return None, "commit_request_message_invalid"
|
|
225
249
|
|
|
226
|
-
raw_paths = raw.get("intended_paths")
|
|
227
|
-
if not isinstance(raw_paths, list) or not raw_paths:
|
|
228
|
-
return None, "commit_request_paths_invalid"
|
|
229
|
-
intended: list[str] = []
|
|
230
|
-
for value in raw_paths:
|
|
231
|
-
relative = _safe_relative_path(value)
|
|
232
|
-
if relative is None or _forbidden_commit_path(relative, expected_artifact):
|
|
233
|
-
return None, "commit_request_path_unsafe"
|
|
234
|
-
intended.append(relative)
|
|
235
|
-
if len(intended) != len(set(intended)):
|
|
236
|
-
return None, "commit_request_paths_duplicate"
|
|
237
|
-
|
|
238
250
|
runtime_commit_hash = str(raw.get("runtime_commit_hash") or "").strip()
|
|
239
251
|
if runtime_commit_hash and not _HEX_COMMIT.fullmatch(runtime_commit_hash):
|
|
240
252
|
return None, "runtime_commit_hash_invalid"
|
|
@@ -243,45 +255,25 @@ def _load_request(
|
|
|
243
255
|
artifact_dir=expected_artifact,
|
|
244
256
|
base_head=base_head.lower(),
|
|
245
257
|
commit_message=message,
|
|
246
|
-
intended_paths=tuple(intended),
|
|
247
258
|
runtime_commit_hash=runtime_commit_hash.lower(),
|
|
248
259
|
), ""
|
|
249
260
|
|
|
250
261
|
|
|
251
|
-
def
|
|
252
|
-
text = str(value or "").strip().replace("\\", "/")
|
|
253
|
-
if not text or text.startswith("/") or re.match(r"^[A-Za-z]:/", text):
|
|
254
|
-
return None
|
|
255
|
-
path = PurePosixPath(text)
|
|
256
|
-
if path.is_absolute() or any(part in {"", ".", ".."} for part in path.parts):
|
|
257
|
-
return None
|
|
258
|
-
return path.as_posix()
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
def _forbidden_commit_path(path: str, artifact_dir: str) -> bool:
|
|
262
|
+
def _sensitive_commit_path(path: str) -> bool:
|
|
262
263
|
lower = path.lower()
|
|
263
264
|
name = PurePosixPath(lower).name
|
|
264
265
|
if lower in _FORBIDDEN_EXACT_PATHS:
|
|
265
266
|
return True
|
|
266
267
|
if lower == ".git" or lower.startswith(".git/"):
|
|
267
268
|
return True
|
|
268
|
-
if _known_support_path(lower):
|
|
269
|
-
return True
|
|
270
|
-
artifact_prefix = artifact_dir.lower().rstrip("/") + "/"
|
|
271
|
-
if lower in {
|
|
272
|
-
artifact_prefix + REQUEST_FILENAME,
|
|
273
|
-
artifact_prefix + "workflow-checkpoint.json",
|
|
274
|
-
}:
|
|
275
|
-
return True
|
|
276
269
|
if name in _SENSITIVE_NAMES or (name.startswith(".env.") and name != ".env.example"):
|
|
277
270
|
return True
|
|
278
271
|
return PurePosixPath(lower).suffix in _SENSITIVE_SUFFIXES
|
|
279
272
|
|
|
280
273
|
|
|
281
|
-
def
|
|
274
|
+
def _workspace_changed_paths(
|
|
282
275
|
project_root: Path,
|
|
283
276
|
*,
|
|
284
|
-
artifact_dir: Path,
|
|
285
277
|
checkpoint_path: Path,
|
|
286
278
|
request_path: Path,
|
|
287
279
|
) -> tuple[tuple[str, ...], str]:
|
|
@@ -293,56 +285,31 @@ def _task_visible_changed_paths(
|
|
|
293
285
|
"--untracked-files=all",
|
|
294
286
|
"--",
|
|
295
287
|
".",
|
|
296
|
-
*HIDDEN_TOOL_WORKTREE_EXCLUDES,
|
|
297
288
|
)
|
|
298
289
|
if result.return_code != 0:
|
|
299
290
|
return (), "git_status_failed"
|
|
300
|
-
local_paths =
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
continue
|
|
310
|
-
visible.append(path)
|
|
291
|
+
local_paths = _runtime_bookkeeping_paths(
|
|
292
|
+
project_root,
|
|
293
|
+
checkpoint_path=checkpoint_path,
|
|
294
|
+
request_path=request_path,
|
|
295
|
+
)
|
|
296
|
+
visible = [
|
|
297
|
+
path for path in _status_record_paths(result.stdout)
|
|
298
|
+
if path not in local_paths
|
|
299
|
+
]
|
|
311
300
|
return tuple(dict.fromkeys(visible)), ""
|
|
312
301
|
|
|
313
302
|
|
|
314
|
-
def
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
return True
|
|
325
|
-
if (
|
|
326
|
-
len(parts) >= 2
|
|
327
|
-
and parts[0].startswith(".")
|
|
328
|
-
and parts[1] in {"worktree", "worktrees"}
|
|
329
|
-
):
|
|
330
|
-
return True
|
|
331
|
-
if lower_path in {".prizmkit/manifest.json"}:
|
|
332
|
-
return True
|
|
333
|
-
if any(
|
|
334
|
-
lower_path == prefix or lower_path.startswith(prefix + "/")
|
|
335
|
-
for prefix in (
|
|
336
|
-
".prizmkit/dev-pipeline",
|
|
337
|
-
".prizmkit/state",
|
|
338
|
-
".prizmkit/scripts",
|
|
339
|
-
)
|
|
340
|
-
):
|
|
341
|
-
return True
|
|
342
|
-
return (
|
|
343
|
-
lower_path.startswith(".prizmkit/specs/")
|
|
344
|
-
and path.name in {REQUEST_FILENAME, "workflow-checkpoint.json"}
|
|
345
|
-
)
|
|
303
|
+
def _runtime_bookkeeping_paths(
|
|
304
|
+
project_root: Path,
|
|
305
|
+
*,
|
|
306
|
+
checkpoint_path: Path,
|
|
307
|
+
request_path: Path,
|
|
308
|
+
) -> set[str]:
|
|
309
|
+
return {
|
|
310
|
+
checkpoint_path.relative_to(project_root).as_posix(),
|
|
311
|
+
request_path.relative_to(project_root).as_posix(),
|
|
312
|
+
}
|
|
346
313
|
|
|
347
314
|
|
|
348
315
|
def _status_record_paths(stdout: str) -> tuple[str, ...]:
|
|
@@ -363,22 +330,85 @@ def _status_record_paths(stdout: str) -> tuple[str, ...]:
|
|
|
363
330
|
return tuple(dict.fromkeys(path for path in changed if path))
|
|
364
331
|
|
|
365
332
|
|
|
366
|
-
def
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
333
|
+
def _staged_bookkeeping_paths(
|
|
334
|
+
project_root: Path,
|
|
335
|
+
*,
|
|
336
|
+
checkpoint_path: Path,
|
|
337
|
+
request_path: Path,
|
|
338
|
+
) -> tuple[str, ...] | None:
|
|
339
|
+
staged = _git(
|
|
340
|
+
project_root,
|
|
341
|
+
"diff",
|
|
342
|
+
"--cached",
|
|
343
|
+
"--name-only",
|
|
344
|
+
"--no-renames",
|
|
345
|
+
"-z",
|
|
346
|
+
)
|
|
347
|
+
if staged.return_code != 0:
|
|
348
|
+
return None
|
|
349
|
+
local_paths = _runtime_bookkeeping_paths(
|
|
350
|
+
project_root,
|
|
351
|
+
checkpoint_path=checkpoint_path,
|
|
352
|
+
request_path=request_path,
|
|
353
|
+
)
|
|
354
|
+
return tuple(
|
|
355
|
+
path for path in staged.stdout.split("\0")
|
|
356
|
+
if path and path in local_paths
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _stage_workspace(
|
|
361
|
+
project_root: Path,
|
|
362
|
+
*,
|
|
363
|
+
checkpoint_path: Path,
|
|
364
|
+
request_path: Path,
|
|
365
|
+
) -> _GitResult:
|
|
366
|
+
staged = _git(project_root, "add", "-A", "--", ".")
|
|
367
|
+
if staged.return_code != 0:
|
|
368
|
+
return staged
|
|
369
|
+
local_pathspecs = [
|
|
370
|
+
f":(top,literal){path}"
|
|
371
|
+
for path in sorted(
|
|
372
|
+
_runtime_bookkeeping_paths(
|
|
373
|
+
project_root,
|
|
374
|
+
checkpoint_path=checkpoint_path,
|
|
375
|
+
request_path=request_path,
|
|
376
|
+
)
|
|
377
|
+
)
|
|
378
|
+
]
|
|
379
|
+
return _git(project_root, "reset", "-q", "HEAD", "--", *local_pathspecs)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def _workspace_has_unstaged_candidates(
|
|
383
|
+
project_root: Path,
|
|
384
|
+
*,
|
|
385
|
+
checkpoint_path: Path,
|
|
386
|
+
request_path: Path,
|
|
387
|
+
) -> bool | None:
|
|
388
|
+
local_paths = _runtime_bookkeeping_paths(
|
|
389
|
+
project_root,
|
|
390
|
+
checkpoint_path=checkpoint_path,
|
|
391
|
+
request_path=request_path,
|
|
392
|
+
)
|
|
393
|
+
unstaged = _git(project_root, "diff", "--name-only", "--no-renames", "-z")
|
|
394
|
+
untracked = _git(
|
|
395
|
+
project_root,
|
|
396
|
+
"ls-files",
|
|
397
|
+
"--others",
|
|
398
|
+
"--exclude-standard",
|
|
399
|
+
"-z",
|
|
400
|
+
"--",
|
|
401
|
+
".",
|
|
402
|
+
)
|
|
403
|
+
if unstaged.return_code != 0 or untracked.return_code != 0:
|
|
404
|
+
return None
|
|
405
|
+
candidates = {
|
|
406
|
+
path
|
|
407
|
+
for output in (unstaged.stdout, untracked.stdout)
|
|
408
|
+
for path in output.split("\0")
|
|
409
|
+
if path and path not in local_paths
|
|
410
|
+
}
|
|
411
|
+
return bool(candidates)
|
|
382
412
|
|
|
383
413
|
|
|
384
414
|
def _matches_prepared_commit(project_root: Path, request: RuntimeCommitRequest, head: str) -> bool:
|
|
@@ -392,12 +422,17 @@ def _matches_prepared_commit(project_root: Path, request: RuntimeCommitRequest,
|
|
|
392
422
|
return False
|
|
393
423
|
parent_list = parents.stdout.strip().split()
|
|
394
424
|
changed = {path for path in paths.stdout.split("\0") if path}
|
|
425
|
+
artifact_prefix = request.artifact_dir.rstrip("/") + "/"
|
|
426
|
+
causal_bookkeeping = {
|
|
427
|
+
artifact_prefix + REQUEST_FILENAME,
|
|
428
|
+
artifact_prefix + "workflow-checkpoint.json",
|
|
429
|
+
}
|
|
395
430
|
return (
|
|
396
431
|
len(parent_list) == 1
|
|
397
432
|
and parent_list[0].lower() == request.base_head.lower()
|
|
398
433
|
and subject.stdout.strip() == request.commit_message
|
|
399
434
|
and bool(changed)
|
|
400
|
-
and changed
|
|
435
|
+
and changed.isdisjoint(causal_bookkeeping)
|
|
401
436
|
)
|
|
402
437
|
|
|
403
438
|
|
|
@@ -38,7 +38,7 @@ Complete the feature in headless non-interactive mode using the active atomic sk
|
|
|
38
38
|
5. Invoke `/prizmkit-test scope=this-change artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/`. Validate its report/result pair. For `TEST_NEEDS_FIXES`, classify the concrete correction into an allowed `repair_scope`; unknown/unsafe scope blocks, while a safe in-budget route re-enters Implement and requires fresh Review, configured verification, and Test. Do not duplicate the Skill's internal repair loop.
|
|
39
39
|
6. Write `completion-summary.json` before retrospective and commit preparation.
|
|
40
40
|
7. Invoke `/prizmkit-retrospective` with the exact artifact root, non-`.prizmkit/` `change_paths`, and `change_summary`; validate `outcome=RETRO_COMPLETE`.
|
|
41
|
-
8. Before commit preparation,
|
|
41
|
+
8. Before commit preparation, validate the complete Git-visible workspace regardless of when or where each correct change originated. Final Review/Test authority must cover every behavior-affecting visible change; ignored paths remain local, while merge conflicts, Secrets, sensitive content, stale evidence, and known incorrect or unverifiable content block. After validating every required artifact, invoke `/prizmkit-committer operation=prepare-runtime-commit artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/ evidence_paths=<exact paths> request_path=.prizmkit/specs/{{FEATURE_SLUG}}/runtime-commit-request.json`. Require a schema-v2 pathless request and `COMMIT_REQUEST_READY`; this session then records `COMMIT_PENDING` and stops. The Python runtime re-enumerates and commits the complete non-ignored workspace with only exact causal request/checkpoint bookkeeping omitted.
|
|
42
42
|
|
|
43
43
|
## Failure Capture
|
|
44
44
|
|
|
@@ -448,9 +448,9 @@ Invoke `/prizmkit-retrospective` with the exact artifact root, exact non-`.prizm
|
|
|
448
448
|
|
|
449
449
|
**6c. Runtime commit preparation**
|
|
450
450
|
|
|
451
|
-
Before commit preparation,
|
|
451
|
+
Before commit preparation, validate the complete Git-visible workspace regardless of when or where each correct change originated. Code Review and Test authority must cover every behavior-affecting visible change. Ignored paths remain naturally absent; merge conflicts, Secrets, sensitive content, stale evidence, and known incorrect or unverifiable content block. Never force-add or change/interpret project ignore policy.
|
|
452
452
|
|
|
453
|
-
After this injected session validates every required artifact, invoke `/prizmkit-committer operation=prepare-runtime-commit artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/ evidence_paths=<exact paths>
|
|
453
|
+
After this injected session validates every required artifact, invoke `/prizmkit-committer operation=prepare-runtime-commit artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/ evidence_paths=<exact paths> request_path=.prizmkit/specs/{{FEATURE_SLUG}}/runtime-commit-request.json`. Require a schema-v2 pathless request and `COMMIT_REQUEST_READY`; this session records `COMMIT_PENDING` and stops. The Python runtime re-enumerates and commits the complete non-ignored workspace, omitting only exact causal request/checkpoint bookkeeping, then verifies the local commit and finalizes checkpoint `COMMITTED`.
|
|
454
454
|
|
|
455
455
|
## Critical Paths
|
|
456
456
|
|
|
@@ -245,7 +245,7 @@ Invoke `/prizmkit-retrospective` with the exact artifact root, exact non-`.prizm
|
|
|
245
245
|
|
|
246
246
|
**c. Runtime commit preparation**
|
|
247
247
|
|
|
248
|
-
After this injected session validates every required artifact, invoke `/prizmkit-committer operation=prepare-runtime-commit artifact_dir=.prizmkit/bugfix/{{BUG_ID}}/ evidence_paths=<exact paths>
|
|
248
|
+
After this injected session validates every required artifact and confirms final Review/Test authority covers the complete behavior-affecting Git-visible workspace, invoke `/prizmkit-committer operation=prepare-runtime-commit artifact_dir=.prizmkit/bugfix/{{BUG_ID}}/ evidence_paths=<exact paths> request_path=.prizmkit/bugfix/{{BUG_ID}}/runtime-commit-request.json`. Require a schema-v2 pathless request and `COMMIT_REQUEST_READY`; this session records `COMMIT_PENDING` and stops. Ignored files remain local. The Python runtime re-enumerates and commits the complete non-ignored workspace with only exact causal request/checkpoint bookkeeping omitted, verifies the local commit, finalizes checkpoint `COMMITTED`, and controls any later publication.
|
|
249
249
|
|
|
250
250
|
---
|
|
251
251
|
|
|
@@ -210,7 +210,7 @@ Write `.prizmkit/refactor/{{REFACTOR_ID}}/completion-summary.json` for downstrea
|
|
|
210
210
|
|
|
211
211
|
Complete `refactor-report` and `completion-summary` before retrospective. Then invoke `/prizmkit-retrospective` with the exact artifact root, exact non-`.prizmkit/` `change_paths`, and a concise `change_summary`. Validate `outcome=RETRO_COMPLETE`; do not stage or commit.
|
|
212
212
|
|
|
213
|
-
After this injected session validates every required artifact, invoke `/prizmkit-committer operation=prepare-runtime-commit artifact_dir=.prizmkit/refactor/{{REFACTOR_ID}}/ evidence_paths=<exact paths>
|
|
213
|
+
After this injected session validates every required artifact and confirms final Review/Test authority covers the complete behavior-affecting Git-visible workspace, invoke `/prizmkit-committer operation=prepare-runtime-commit artifact_dir=.prizmkit/refactor/{{REFACTOR_ID}}/ evidence_paths=<exact paths> request_path=.prizmkit/refactor/{{REFACTOR_ID}}/runtime-commit-request.json`. Require a schema-v2 pathless request and `COMMIT_REQUEST_READY`; this session records `COMMIT_PENDING` and stops. Ignored files remain local. The Python runtime re-enumerates and commits the complete non-ignored workspace with only exact causal request/checkpoint bookkeeping omitted, verifies the local commit, finalizes checkpoint `COMMITTED`, and controls any later publication.
|
|
214
214
|
|
|
215
215
|
---
|
|
216
216
|
|
|
@@ -5,31 +5,25 @@ The Python runtime, not the AI session, executes the pipeline's local Git commit
|
|
|
5
5
|
After Code Review, all configured verification, `TEST_PASS`, family reports/summaries, and retrospective have completed:
|
|
6
6
|
|
|
7
7
|
1. As the session orchestrator, validate every required stage artifact and result first. Atomic Skills do not perform this sequencing check.
|
|
8
|
-
2.
|
|
8
|
+
2. Re-inventory the complete final Git-visible workspace: staged, unstaged, added, deleted, renamed, copied, and non-ignored untracked content. Confirm Code Review and Test authority covers every behavior-affecting change, including correct work that predated this session or lives in framework, generated, instruction, lock, or host-support paths. If production behavior changed after those gates, re-enter them before commit preparation.
|
|
9
|
+
3. Invoke `/prizmkit-committer` with the same artifact root, exact readiness evidence, and request path:
|
|
9
10
|
|
|
10
11
|
```text
|
|
11
|
-
/prizmkit-committer operation=prepare-runtime-commit artifact_dir={{ARTIFACT_DIR}} evidence_paths=<exact validated artifact paths>
|
|
12
|
+
/prizmkit-committer operation=prepare-runtime-commit artifact_dir={{ARTIFACT_DIR}} evidence_paths=<exact validated artifact paths> request_path={{ARTIFACT_DIR}}/runtime-commit-request.json
|
|
12
13
|
```
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
- safe Git-visible `.prizmkit/**` requirement output is admitted like any other exact task path and needs no documentation-specific evidence;
|
|
16
|
-
- ignored paths remain absent and are never force-added; project ignore policy is not changed or interpreted;
|
|
17
|
-
- exact Runtime request/checkpoint/state, installed Runtime/host payloads, and local host settings remain support/bookkeeping outside the task commit;
|
|
18
|
-
- global Secret checks still apply, and every unrelated or unknown Git-visible path blocks rather than disappearing.
|
|
19
|
-
|
|
20
|
-
It then rejects sensitive/unrelated/unknown paths, generates one Conventional Commit message, and writes this request without staging or committing:
|
|
15
|
+
4. The committer validates the complete final workspace without a caller-supplied path manifest or provenance classification. Ignored paths remain local and never block or require force-add. Merge conflicts, Secrets, sensitive local content, known incorrect or incoherent changes, unverifiable content, and stale readiness evidence still block. It generates one truthful Conventional Commit message and writes this request without staging or committing:
|
|
21
16
|
|
|
22
17
|
```json
|
|
23
18
|
{
|
|
24
|
-
"schema_version":
|
|
19
|
+
"schema_version": 2,
|
|
25
20
|
"artifact_dir": "{{ARTIFACT_DIR}}",
|
|
26
21
|
"base_head": "<current HEAD commit hash>",
|
|
27
|
-
"commit_message": "<type>(<scope>): <description>"
|
|
28
|
-
"intended_paths": ["<exact project-relative changed path>"]
|
|
22
|
+
"commit_message": "<type>(<scope>): <description>"
|
|
29
23
|
}
|
|
30
24
|
```
|
|
31
25
|
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
5. Require the stage-local result `COMMIT_REQUEST_READY`. Then this injected session—not the Skill—marks `prizmkit-committer` `in_progress` with `stage_result=COMMIT_PENDING` through the checkpoint helper. Do not mark it completed or write `COMMITTED`.
|
|
27
|
+
6. Stop normal work after the valid request and checkpoint handoff exist. Do not run `git add`, `git commit`, `git commit --amend`, merge, push, or create additional task files.
|
|
34
28
|
|
|
35
|
-
The Runtime validates
|
|
29
|
+
The Runtime validates the pathless schema, base, complete non-ignored workspace, sensitive path safeguards, merge state, and causal bookkeeping boundary. It stages the complete workspace, omitting only the exact active request/checkpoint whose final receipt cannot truthfully precede the commit; verifies a non-empty commit with the expected parent and message; records its Git receipt; and only then finalizes `COMMITTED` and performs configured integration. A malformed, stale, unsafe, incomplete, or contradictory handoff blocks without deleting user data.
|
|
@@ -1381,6 +1381,9 @@ class TestHeadlessPromptCleanupF033:
|
|
|
1381
1381
|
assert artifact in prompt
|
|
1382
1382
|
assert "Atomic Skills write only their stage-owned artifacts and domain results" in prompt
|
|
1383
1383
|
assert "COMMIT_REQUEST_READY" in prompt
|
|
1384
|
+
assert '"schema_version": 2' in prompt
|
|
1385
|
+
assert '"intended_paths"' not in prompt
|
|
1386
|
+
assert "complete final Git-visible workspace" in prompt
|
|
1384
1387
|
assert "workflow_state_path" not in prompt
|
|
1385
1388
|
assert "outcome=RETRO_COMPLETE" in prompt
|
|
1386
1389
|
assert "Never recreate the testing-local repair loop" in prompt
|
|
@@ -1554,12 +1557,12 @@ class TestFeatureBootstrapShellExtraction:
|
|
|
1554
1557
|
assert "text search . --pattern \"123-prompt-cleanup\" --json" in tier3
|
|
1555
1558
|
assert "text count .prizmkit/specs/123-prompt-cleanup/plan.md --pattern \"- [ ]\"" in tier3
|
|
1556
1559
|
assert "/prizmkit-committer operation=prepare-runtime-commit" in tier3
|
|
1557
|
-
assert "Require
|
|
1558
|
-
assert "The Python runtime
|
|
1559
|
-
assert "
|
|
1560
|
-
assert "
|
|
1560
|
+
assert "Require a schema-v2 pathless request and `COMMIT_REQUEST_READY`" in tier3
|
|
1561
|
+
assert "The Python runtime re-enumerates and commits the complete non-ignored workspace" in tier3
|
|
1562
|
+
assert "validate the complete Git-visible workspace" in tier3
|
|
1563
|
+
assert "Code Review and Test authority must cover every behavior-affecting visible change" in tier3
|
|
1564
|
+
assert "Ignored paths remain naturally absent" in tier3
|
|
1561
1565
|
assert "Never force-add or change/interpret project ignore policy" in tier3
|
|
1562
|
-
assert "unknown Git-visible paths block" in tier3
|
|
1563
1566
|
assert "git add" not in tier3
|
|
1564
1567
|
assert "git commit" not in tier3
|
|
1565
1568
|
|
|
@@ -596,8 +596,10 @@ class TestBugfixCheckpointGeneration:
|
|
|
596
596
|
assert "operation=prepare-runtime-commit" in prompt
|
|
597
597
|
assert "NEVER ask for user confirmation" in prompt
|
|
598
598
|
assert "The Python runtime, not the AI session, executes" in prompt
|
|
599
|
-
assert "
|
|
600
|
-
assert "
|
|
599
|
+
assert "complete final Git-visible workspace" in prompt
|
|
600
|
+
assert '"schema_version": 2' in prompt
|
|
601
|
+
assert '"intended_paths"' not in prompt
|
|
602
|
+
assert "Ignored paths remain local" in prompt
|
|
601
603
|
assert "push_authorized\": true" not in prompt
|
|
602
604
|
assert ".prizmkit/bugfix/B-001/" in prompt
|
|
603
605
|
assert "write the session status" not in prompt.lower()
|
|
@@ -435,8 +435,10 @@ class TestRefactorCheckpointGeneration:
|
|
|
435
435
|
assert "local_commit_authorized" not in prompt
|
|
436
436
|
assert "operation=prepare-runtime-commit" in prompt
|
|
437
437
|
assert "The Python runtime, not the AI session, executes" in prompt
|
|
438
|
-
assert "
|
|
439
|
-
assert "
|
|
438
|
+
assert "complete final Git-visible workspace" in prompt
|
|
439
|
+
assert '"schema_version": 2' in prompt
|
|
440
|
+
assert '"intended_paths"' not in prompt
|
|
441
|
+
assert "Ignored paths remain local" in prompt
|
|
440
442
|
assert "automatic push" not in prompt.lower()
|
|
441
443
|
assert "push_authorized\": true" not in prompt
|
|
442
444
|
assert "write the session status" not in prompt.lower()
|