@raquezha/norpiv 0.0.6 → 0.0.8
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 +35 -34
- package/frame/SKILL.md +4 -3
- package/grill-with-docs/SKILL.md +10 -14
- package/implement/SKILL.md +18 -16
- package/implement/scripts/enforce-branch.sh +22 -12
- package/package.json +3 -1
- package/plan/SKILL.md +3 -1
- package/refine/SKILL.md +54 -0
- package/scripts/graphify-grill.py +42 -0
- package/scripts/graphify-grill.sh +24 -0
- package/scripts/test_graphify_grill.py +21 -0
- package/scripts/triage_helper.sh +140 -28
- package/scripts/validate_active_task.sh +63 -37
- package/sync/SKILL.md +14 -3
- package/triage/SKILL.md +11 -13
- package/verify/SKILL.md +4 -3
- package/post-merge-prune/SKILL.md +0 -55
- package/scripts/reposcry-bootstrap.sh +0 -108
- package/scripts/reposcry-refresh.sh +0 -19
- package/scripts/reposcry-task-context.sh +0 -40
package/scripts/triage_helper.sh
CHANGED
|
@@ -120,22 +120,26 @@ PY
|
|
|
120
120
|
|
|
121
121
|
write_active_pointer() {
|
|
122
122
|
mkdir -p ".workflow"
|
|
123
|
-
python3 - ".workflow/
|
|
123
|
+
python3 - ".workflow/active.json" "$TASK_FOLDER" "$SOURCE" "$ID" "$TASK_DIR" "$WORK_MD" "$BRANCH_NAME" "$ISO_NOW" <<'PY'
|
|
124
124
|
import json
|
|
125
125
|
import sys
|
|
126
126
|
from pathlib import Path
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
"
|
|
128
|
+
active_workflow_path, active_task, source, raw_id, task_path, state_file, branch, now = sys.argv[1:]
|
|
129
|
+
workflow_data = {
|
|
130
|
+
"workflow": "rpiv",
|
|
131
|
+
"id": active_task,
|
|
132
|
+
"taskId": active_task,
|
|
131
133
|
"source": source,
|
|
132
|
-
"id": raw_id,
|
|
133
134
|
"sourceId": raw_id,
|
|
135
|
+
"stateFile": state_file,
|
|
134
136
|
"taskPath": task_path,
|
|
135
137
|
"path": task_path,
|
|
136
138
|
"branch": branch,
|
|
139
|
+
"startedAt": now,
|
|
140
|
+
"updatedAt": now,
|
|
137
141
|
}
|
|
138
|
-
Path(
|
|
142
|
+
Path(active_workflow_path).write_text(json.dumps(workflow_data, indent=2) + "\n")
|
|
139
143
|
PY
|
|
140
144
|
}
|
|
141
145
|
|
|
@@ -187,24 +191,43 @@ PY
|
|
|
187
191
|
}
|
|
188
192
|
|
|
189
193
|
update_work_meta() {
|
|
190
|
-
local status
|
|
194
|
+
local status
|
|
191
195
|
status=$(json_get "$METADATA_JSON" status)
|
|
192
|
-
phase=$(json_get "$METADATA_JSON" phase)
|
|
193
196
|
status=${status:-active}
|
|
194
|
-
phase=${phase:-triaged}
|
|
195
197
|
|
|
196
|
-
python3 - "$WORK_MD" "$BRANCH_NAME" "$status" "$
|
|
198
|
+
python3 - "$WORK_MD" "$BRANCH_NAME" "$status" "$SOURCE" "$ID" <<'PY'
|
|
197
199
|
import re
|
|
198
200
|
import sys
|
|
199
201
|
from pathlib import Path
|
|
200
202
|
|
|
201
203
|
path = Path(sys.argv[1])
|
|
202
|
-
branch, status,
|
|
204
|
+
branch, status, source, raw_id = sys.argv[2:]
|
|
203
205
|
text = path.read_text() if path.exists() else ""
|
|
204
206
|
lines = text.splitlines()
|
|
205
207
|
header_re = re.compile(r"^(## )?\[[A-Z0-9_-]+\]\s*$")
|
|
206
208
|
meta_re = re.compile(r"^(## )?\[META\]\s*$")
|
|
207
209
|
|
|
210
|
+
|
|
211
|
+
def section_body(section):
|
|
212
|
+
header = re.compile(rf"^(## )?\[{re.escape(section)}\]\s*$")
|
|
213
|
+
start = next((i for i, line in enumerate(lines) if header.match(line)), None)
|
|
214
|
+
if start is None:
|
|
215
|
+
return ""
|
|
216
|
+
end = len(lines)
|
|
217
|
+
for i in range(start + 1, len(lines)):
|
|
218
|
+
if header_re.match(lines[i]):
|
|
219
|
+
end = i
|
|
220
|
+
break
|
|
221
|
+
return "\n".join(lines[start + 1:end]).strip()
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def meaningful(section):
|
|
225
|
+
body = section_body(section)
|
|
226
|
+
return any(line.strip() and line.strip() not in {"-", "- [ ]"} for line in body.splitlines())
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
phase = "planned" if meaningful("PLAN") else "grilled" if meaningful("GRILL") else "framed" if meaningful("BRIEF") else "triaged"
|
|
230
|
+
|
|
208
231
|
new_block = [
|
|
209
232
|
"## [META]",
|
|
210
233
|
f"- Branch: `{branch}`",
|
|
@@ -232,6 +255,100 @@ path.write_text("\n".join(updated).rstrip() + "\n")
|
|
|
232
255
|
PY
|
|
233
256
|
}
|
|
234
257
|
|
|
258
|
+
write_work_snapshot() {
|
|
259
|
+
python3 - "$WORK_MD" "$METADATA_JSON" "$SOURCE" "$ID" <<'PY'
|
|
260
|
+
import json
|
|
261
|
+
import re
|
|
262
|
+
import sys
|
|
263
|
+
from pathlib import Path
|
|
264
|
+
|
|
265
|
+
work_md, metadata_json, source, raw_id = sys.argv[1:]
|
|
266
|
+
|
|
267
|
+
try:
|
|
268
|
+
metadata = json.loads(Path(metadata_json).read_text())
|
|
269
|
+
if not isinstance(metadata, dict):
|
|
270
|
+
metadata = {}
|
|
271
|
+
except Exception:
|
|
272
|
+
metadata = {}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def pick(*values, fallback=""):
|
|
276
|
+
for value in values:
|
|
277
|
+
if isinstance(value, str) and value.strip():
|
|
278
|
+
return value.strip()
|
|
279
|
+
return fallback
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def squash(value):
|
|
283
|
+
if isinstance(value, dict):
|
|
284
|
+
value = json.dumps(value, ensure_ascii=False)
|
|
285
|
+
if not isinstance(value, str):
|
|
286
|
+
return ""
|
|
287
|
+
text = re.sub(r"```.*?```", " ", value, flags=re.S)
|
|
288
|
+
text = re.sub(r"^(## )?\[[A-Z0-9_-]+\]\s*$", " ", text, flags=re.M)
|
|
289
|
+
text = re.sub(r"\s+", " ", text).strip()
|
|
290
|
+
return text[:280].rstrip() + ("..." if len(text) > 280 else "")
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
fields = metadata.get("fields") if isinstance(metadata.get("fields"), dict) else {}
|
|
294
|
+
title = {
|
|
295
|
+
"github": pick(metadata.get("title"), fallback=f"GitHub #{raw_id}"),
|
|
296
|
+
"gitlab": pick(metadata.get("title"), fallback=f"GitLab #{raw_id}"),
|
|
297
|
+
"jira": pick(fields.get("summary"), metadata.get("key"), fallback=f"Jira {raw_id}"),
|
|
298
|
+
}.get(source, pick(metadata.get("title"), fallback=f"Local Task {raw_id}"))
|
|
299
|
+
url = {
|
|
300
|
+
"github": pick(metadata.get("url"), fallback=f"https://github.com/issues/{raw_id}"),
|
|
301
|
+
"gitlab": pick(metadata.get("web_url"), metadata.get("url")),
|
|
302
|
+
"jira": pick(metadata.get("self")),
|
|
303
|
+
}.get(source, "")
|
|
304
|
+
summary = {
|
|
305
|
+
"github": squash(metadata.get("body")),
|
|
306
|
+
"gitlab": squash(metadata.get("description")),
|
|
307
|
+
"jira": squash(fields.get("description")),
|
|
308
|
+
}.get(source, squash(metadata.get("body"))) or title
|
|
309
|
+
tracker_updated = {
|
|
310
|
+
"github": pick(metadata.get("updatedAt"), metadata.get("createdAt")),
|
|
311
|
+
"gitlab": pick(metadata.get("updated_at"), metadata.get("created_at")),
|
|
312
|
+
"jira": pick(fields.get("updated"), metadata.get("updated")),
|
|
313
|
+
}.get(source, "")
|
|
314
|
+
|
|
315
|
+
lines = [
|
|
316
|
+
f"# WORK: {title}",
|
|
317
|
+
"",
|
|
318
|
+
"## [INTAKE]",
|
|
319
|
+
"### Outcome",
|
|
320
|
+
summary,
|
|
321
|
+
"",
|
|
322
|
+
"### Acceptance Criteria",
|
|
323
|
+
"- [ ] Confirm concrete acceptance criteria from tracker or refine them during `/frame`.",
|
|
324
|
+
"",
|
|
325
|
+
"### Scope / Non-goals",
|
|
326
|
+
"- Keep local execution state concise; do not copy raw tracker or CLI rendering into `WORK.md`.",
|
|
327
|
+
"",
|
|
328
|
+
"### Dependencies / Blockers",
|
|
329
|
+
"- None noted from intake snapshot.",
|
|
330
|
+
"",
|
|
331
|
+
"### Tracker Context",
|
|
332
|
+
f"- Task: `{source}:{raw_id}`",
|
|
333
|
+
f"- URL: {url}" if url else "- URL: None noted",
|
|
334
|
+
f"- Tracker updated: `{tracker_updated}`" if tracker_updated else "- Tracker updated: None noted",
|
|
335
|
+
"",
|
|
336
|
+
"## [BRIEF]",
|
|
337
|
+
"- ",
|
|
338
|
+
"",
|
|
339
|
+
"## [GRILL]",
|
|
340
|
+
"- ",
|
|
341
|
+
"",
|
|
342
|
+
"## [PLAN]",
|
|
343
|
+
"- [ ] ",
|
|
344
|
+
"",
|
|
345
|
+
"## [LOG]",
|
|
346
|
+
]
|
|
347
|
+
|
|
348
|
+
Path(work_md).write_text("\n".join(lines).rstrip() + "\n")
|
|
349
|
+
PY
|
|
350
|
+
}
|
|
351
|
+
|
|
235
352
|
backfill_metadata() {
|
|
236
353
|
json_upsert "$METADATA_JSON" \
|
|
237
354
|
"id=$ID" \
|
|
@@ -239,21 +356,18 @@ backfill_metadata() {
|
|
|
239
356
|
"branch=$BRANCH_NAME" \
|
|
240
357
|
"taskFolder=$TASK_FOLDER" \
|
|
241
358
|
"?status=active" \
|
|
242
|
-
"?phase=triaged" \
|
|
243
359
|
"createdAt=$ISO_NOW" \
|
|
244
360
|
"updatedAt=$ISO_NOW"
|
|
245
361
|
}
|
|
246
362
|
|
|
247
363
|
set_metadata_status_phase() {
|
|
248
364
|
local status="$1"
|
|
249
|
-
local phase="$2"
|
|
250
365
|
json_upsert "$METADATA_JSON" \
|
|
251
366
|
"id=$ID" \
|
|
252
367
|
"source=$SOURCE" \
|
|
253
368
|
"branch=$BRANCH_NAME" \
|
|
254
369
|
"taskFolder=$TASK_FOLDER" \
|
|
255
370
|
"status=$status" \
|
|
256
|
-
"phase=$phase" \
|
|
257
371
|
"createdAt=$ISO_NOW" \
|
|
258
372
|
"updatedAt=$ISO_NOW"
|
|
259
373
|
}
|
|
@@ -266,24 +380,25 @@ create_task() {
|
|
|
266
380
|
github)
|
|
267
381
|
command -v gh >/dev/null 2>&1 || { echo "ERROR: gh CLI is required for github tasks."; exit 1; }
|
|
268
382
|
echo "Fetching GitHub Issue #$ID..."
|
|
269
|
-
gh issue view "$ID" --json title,body,author,labels,
|
|
270
|
-
echo "# WORK: GitHub #$ID" > "$WORK_MD"
|
|
271
|
-
gh issue view "$ID" >> "$WORK_MD"
|
|
383
|
+
gh issue view "$ID" --json title,body,author,labels,url,number,createdAt,updatedAt > "$METADATA_JSON"
|
|
272
384
|
;;
|
|
273
385
|
gitlab)
|
|
274
386
|
command -v glab >/dev/null 2>&1 || { echo "ERROR: glab CLI is required for gitlab tasks."; exit 1; }
|
|
275
387
|
echo "Fetching GitLab Issue #$ID..."
|
|
276
|
-
glab issue view "$ID" > "$
|
|
277
|
-
|
|
388
|
+
if ! glab issue view "$ID" --output json > "$METADATA_JSON" 2>/dev/null; then
|
|
389
|
+
echo '{}' > "$METADATA_JSON"
|
|
390
|
+
json_upsert "$METADATA_JSON" "title=GitLab Issue $ID"
|
|
391
|
+
fi
|
|
278
392
|
;;
|
|
279
393
|
jira)
|
|
280
394
|
echo "Fetching Jira Ticket $ID..."
|
|
281
395
|
if command -v jira >/dev/null 2>&1; then
|
|
282
|
-
jira issue view "$ID" > "$WORK_MD"
|
|
283
396
|
jira issue view "$ID" --raw > "$METADATA_JSON"
|
|
284
397
|
elif command -v acli >/dev/null 2>&1; then
|
|
285
|
-
acli jira workitem view "$ID" > "$
|
|
286
|
-
|
|
398
|
+
if ! acli jira workitem view "$ID" --json > "$METADATA_JSON" 2>/dev/null; then
|
|
399
|
+
echo '{}' > "$METADATA_JSON"
|
|
400
|
+
json_upsert "$METADATA_JSON" "title=Jira $ID"
|
|
401
|
+
fi
|
|
287
402
|
else
|
|
288
403
|
echo "ERROR: jira or acli CLI is required for jira tasks."
|
|
289
404
|
exit 1
|
|
@@ -291,8 +406,8 @@ create_task() {
|
|
|
291
406
|
;;
|
|
292
407
|
local)
|
|
293
408
|
echo "Initializing local task workspace: $ID..."
|
|
294
|
-
echo "# WORK: Local Task $ID" > "$WORK_MD"
|
|
295
409
|
echo '{}' > "$METADATA_JSON"
|
|
410
|
+
json_upsert "$METADATA_JSON" "title=Local Task $ID"
|
|
296
411
|
;;
|
|
297
412
|
*)
|
|
298
413
|
echo "Unknown source: $SOURCE"
|
|
@@ -300,11 +415,8 @@ create_task() {
|
|
|
300
415
|
;;
|
|
301
416
|
esac
|
|
302
417
|
|
|
303
|
-
set_metadata_status_phase "active"
|
|
304
|
-
|
|
305
|
-
ensure_section "GRILL" "- "
|
|
306
|
-
ensure_section "PLAN" "- [ ] "
|
|
307
|
-
ensure_section "LOG" ""
|
|
418
|
+
set_metadata_status_phase "active"
|
|
419
|
+
write_work_snapshot
|
|
308
420
|
update_work_meta
|
|
309
421
|
append_log "Task initialized via /triage"
|
|
310
422
|
write_active_pointer
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
|
|
3
3
|
# validate_active_task.sh
|
|
4
|
-
# Lightweight, print-only validation of .workflow/
|
|
4
|
+
# Lightweight, print-only validation of .workflow/active.json and compatibility .workflow/active_task.json.
|
|
5
5
|
# This script never mutates task state; it only reports inconsistencies and suggested fixes.
|
|
6
6
|
|
|
7
7
|
set -euo pipefail
|
|
@@ -9,6 +9,7 @@ set -euo pipefail
|
|
|
9
9
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
10
|
TRIAGE_HELPER="$SCRIPT_DIR/triage_helper.sh"
|
|
11
11
|
REPO_ROOT=$(git rev-parse --show-toplevel)
|
|
12
|
+
ACTIVE_JSON="$REPO_ROOT/.workflow/active.json"
|
|
12
13
|
ATF="$REPO_ROOT/.workflow/active_task.json"
|
|
13
14
|
CURRENT_BRANCH=$(git branch --show-current 2>/dev/null || true)
|
|
14
15
|
WORK_MD=""
|
|
@@ -152,29 +153,52 @@ print_json_field_report() {
|
|
|
152
153
|
done
|
|
153
154
|
}
|
|
154
155
|
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
POINTER_LABEL=""
|
|
157
|
+
TASK_PATH=""
|
|
158
|
+
TASK_SOURCE=""
|
|
159
|
+
TASK_ID=""
|
|
160
|
+
ACTIVE_TASK=""
|
|
161
|
+
POINTER_BRANCH=""
|
|
162
|
+
|
|
163
|
+
if [[ -f "$ACTIVE_JSON" ]]; then
|
|
164
|
+
if ! python3 -m json.tool "$ACTIVE_JSON" >/dev/null 2>&1; then
|
|
165
|
+
error "active.json is not valid JSON: $ACTIVE_JSON"
|
|
166
|
+
exit 1
|
|
167
|
+
fi
|
|
168
|
+
POINTER_LABEL="active.json"
|
|
169
|
+
echo "Active workflow file: $ACTIVE_JSON"
|
|
170
|
+
json_pretty "$ACTIVE_JSON" || true
|
|
171
|
+
|
|
172
|
+
TASK_PATH=$(json_read "$ACTIVE_JSON" '.taskPath // .path')
|
|
173
|
+
TASK_SOURCE=$(json_read "$ACTIVE_JSON" '.source')
|
|
174
|
+
TASK_ID=$(json_read "$ACTIVE_JSON" '.sourceId // .id')
|
|
175
|
+
ACTIVE_TASK=$(json_read "$ACTIVE_JSON" '.taskId // .id')
|
|
176
|
+
POINTER_BRANCH=$(json_read "$ACTIVE_JSON" '.branch')
|
|
177
|
+
|
|
178
|
+
print_json_field_report "active.json" "$ACTIVE_JSON" workflow id taskId source sourceId taskPath path branch stateFile
|
|
179
|
+
elif [[ -f "$ATF" ]]; then
|
|
180
|
+
if ! python3 -m json.tool "$ATF" >/dev/null 2>&1; then
|
|
181
|
+
error "active_task.json is not valid JSON: $ATF"
|
|
182
|
+
exit 1
|
|
183
|
+
fi
|
|
184
|
+
POINTER_LABEL="active_task.json"
|
|
185
|
+
echo "Legacy active task file: $ATF"
|
|
186
|
+
json_pretty "$ATF" || true
|
|
187
|
+
|
|
188
|
+
TASK_PATH=$(json_read "$ATF" '.taskPath // .path')
|
|
189
|
+
TASK_SOURCE=$(json_read "$ATF" '.source')
|
|
190
|
+
TASK_ID=$(json_read "$ATF" '.sourceId // .id')
|
|
191
|
+
ACTIVE_TASK=$(json_read "$ATF" '.active_task')
|
|
192
|
+
POINTER_BRANCH=$(json_read "$ATF" '.branch')
|
|
193
|
+
|
|
194
|
+
print_json_field_report "active_task.json" "$ATF" active_task source id sourceId taskPath path branch
|
|
195
|
+
else
|
|
196
|
+
error "No active workflow pointer found at $ACTIVE_JSON or $ATF"
|
|
157
197
|
echo "Suggested fixes (dry):"
|
|
158
198
|
echo " - Run: $TRIAGE_HELPER <source> <id>"
|
|
159
199
|
exit 1
|
|
160
200
|
fi
|
|
161
201
|
|
|
162
|
-
if ! python3 -m json.tool "$ATF" >/dev/null 2>&1; then
|
|
163
|
-
error "active_task.json is not valid JSON: $ATF"
|
|
164
|
-
exit 1
|
|
165
|
-
fi
|
|
166
|
-
|
|
167
|
-
echo "Active task file: $ATF"
|
|
168
|
-
json_pretty "$ATF" || true
|
|
169
|
-
|
|
170
|
-
TASK_PATH=$(json_read "$ATF" '.taskPath // .path')
|
|
171
|
-
TASK_SOURCE=$(json_read "$ATF" '.source')
|
|
172
|
-
TASK_ID=$(json_read "$ATF" '.sourceId // .id')
|
|
173
|
-
ACTIVE_TASK=$(json_read "$ATF" '.active_task')
|
|
174
|
-
POINTER_BRANCH=$(json_read "$ATF" '.branch')
|
|
175
|
-
|
|
176
|
-
print_json_field_report "active_task.json" "$ATF" active_task source id sourceId taskPath path branch
|
|
177
|
-
|
|
178
202
|
echo
|
|
179
203
|
if [[ -n "$TASK_PATH" ]]; then
|
|
180
204
|
if [[ "$TASK_PATH" = /* ]]; then
|
|
@@ -184,7 +208,7 @@ if [[ -n "$TASK_PATH" ]]; then
|
|
|
184
208
|
fi
|
|
185
209
|
echo "Using taskPath -> checking: $WORK_MD"
|
|
186
210
|
elif [[ -n "$TASK_ID" ]]; then
|
|
187
|
-
echo "No taskPath in
|
|
211
|
+
echo "No taskPath in $POINTER_LABEL; attempting to derive from source+id"
|
|
188
212
|
if [[ -n "$TASK_SOURCE" ]]; then
|
|
189
213
|
TASK_FOLDER="$TASK_SOURCE-$TASK_ID"
|
|
190
214
|
else
|
|
@@ -193,7 +217,7 @@ elif [[ -n "$TASK_ID" ]]; then
|
|
|
193
217
|
WORK_MD="$REPO_ROOT/.workflow/tasks/$TASK_FOLDER/WORK.md"
|
|
194
218
|
echo "Derived path: $WORK_MD"
|
|
195
219
|
else
|
|
196
|
-
error "Neither taskPath/path nor sourceId/id present in
|
|
220
|
+
error "Neither taskPath/path nor sourceId/id present in $POINTER_LABEL"
|
|
197
221
|
fi
|
|
198
222
|
|
|
199
223
|
if [[ -n "$WORK_MD" && -f "$WORK_MD" ]]; then
|
|
@@ -209,7 +233,7 @@ if [[ -n "$ACTIVE_TASK" && -n "$TASK_DIR" ]]; then
|
|
|
209
233
|
if [[ "$ACTIVE_TASK" == "$ACTUAL_TASK_FOLDER" ]]; then
|
|
210
234
|
ok "active_task matches task folder ($ACTIVE_TASK)"
|
|
211
235
|
else
|
|
212
|
-
warn "
|
|
236
|
+
warn "pointer task id ($ACTIVE_TASK) differs from task folder ($ACTUAL_TASK_FOLDER)"
|
|
213
237
|
fi
|
|
214
238
|
fi
|
|
215
239
|
|
|
@@ -225,8 +249,8 @@ META_ID=""
|
|
|
225
249
|
META_SOURCE=""
|
|
226
250
|
META_TASK_FOLDER=""
|
|
227
251
|
META_STATUS=""
|
|
228
|
-
META_PHASE=""
|
|
229
252
|
META_BRANCH=""
|
|
253
|
+
DERIVED_PHASE=""
|
|
230
254
|
|
|
231
255
|
if [[ -n "$META" ]]; then
|
|
232
256
|
if [[ -f "$META" ]]; then
|
|
@@ -234,20 +258,19 @@ if [[ -n "$META" ]]; then
|
|
|
234
258
|
echo
|
|
235
259
|
echo "Metadata file: $META"
|
|
236
260
|
json_pretty "$META" || true
|
|
237
|
-
print_json_field_report "metadata.json" "$META" id source taskFolder status
|
|
261
|
+
print_json_field_report "metadata.json" "$META" id source taskFolder status branch createdAt updatedAt
|
|
238
262
|
|
|
239
263
|
META_ID=$(json_read "$META" '.id')
|
|
240
264
|
META_SOURCE=$(json_read "$META" '.source')
|
|
241
265
|
META_TASK_FOLDER=$(json_read "$META" '.taskFolder')
|
|
242
266
|
META_STATUS=$(json_read "$META" '.status')
|
|
243
|
-
META_PHASE=$(json_read "$META" '.phase')
|
|
244
267
|
META_BRANCH=$(json_read "$META" '.branch')
|
|
245
268
|
|
|
246
269
|
if [[ -n "$META_ID" && -n "$TASK_ID" && "$META_ID" != "$TASK_ID" ]]; then
|
|
247
|
-
warn "metadata.id ($META_ID) differs from
|
|
270
|
+
warn "metadata.id ($META_ID) differs from pointer id/sourceId ($TASK_ID)"
|
|
248
271
|
fi
|
|
249
272
|
if [[ -n "$META_SOURCE" && -n "$TASK_SOURCE" && "$META_SOURCE" != "$TASK_SOURCE" ]]; then
|
|
250
|
-
warn "metadata.source ($META_SOURCE) differs from
|
|
273
|
+
warn "metadata.source ($META_SOURCE) differs from pointer source ($TASK_SOURCE)"
|
|
251
274
|
fi
|
|
252
275
|
if [[ -n "$META_TASK_FOLDER" && -n "$TASK_DIR" && "$META_TASK_FOLDER" != "$(basename "$TASK_DIR")" ]]; then
|
|
253
276
|
warn "metadata.taskFolder ($META_TASK_FOLDER) differs from task folder ($(basename "$TASK_DIR"))"
|
|
@@ -275,16 +298,19 @@ if [[ -n "$WORK_MD" && -f "$WORK_MD" ]]; then
|
|
|
275
298
|
esac
|
|
276
299
|
done
|
|
277
300
|
|
|
278
|
-
if [[ "$
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
301
|
+
if [[ "$(section_body_nonempty "PLAN" "$WORK_MD")" == "yes" ]]; then
|
|
302
|
+
DERIVED_PHASE="planned"
|
|
303
|
+
elif [[ "$(section_body_nonempty "GRILL" "$WORK_MD")" == "yes" ]]; then
|
|
304
|
+
DERIVED_PHASE="grilled"
|
|
305
|
+
elif [[ "$(section_body_nonempty "BRIEF" "$WORK_MD")" == "yes" ]]; then
|
|
306
|
+
DERIVED_PHASE="framed"
|
|
307
|
+
else
|
|
308
|
+
DERIVED_PHASE="triaged"
|
|
284
309
|
fi
|
|
310
|
+
ok "derived phase from WORK.md: $DERIVED_PHASE"
|
|
285
311
|
|
|
286
|
-
if [[ "$
|
|
287
|
-
warn "phase=
|
|
312
|
+
if [[ "$DERIVED_PHASE" == "planned" && "$(has_plan_checkbox "$WORK_MD")" != "yes" ]]; then
|
|
313
|
+
warn "derived phase=planned but [PLAN] has no checkboxes"
|
|
288
314
|
fi
|
|
289
315
|
fi
|
|
290
316
|
|
|
@@ -296,12 +322,12 @@ echo
|
|
|
296
322
|
echo "Suggested fixes (dry):"
|
|
297
323
|
if [[ -n "$WORK_MD" && ! -f "$WORK_MD" ]]; then
|
|
298
324
|
echo " - Re-run triage: $TRIAGE_HELPER <source> <id>"
|
|
299
|
-
echo " - Or update
|
|
325
|
+
echo " - Or update the active workflow pointer to include a correct taskPath or source+id"
|
|
300
326
|
fi
|
|
301
327
|
if [[ -n "$META" && ! -f "$META" ]]; then
|
|
302
328
|
echo " - Resume/backfill metadata: $TRIAGE_HELPER ${TASK_SOURCE:-local} ${TASK_ID:-task} resume"
|
|
303
329
|
elif [[ -n "$META" && -f "$META" ]]; then
|
|
304
|
-
if [[ -z "$META_STATUS" || -z "$
|
|
330
|
+
if [[ -z "$META_STATUS" || -z "$(json_read "$META" '.createdAt')" || -z "$(json_read "$META" '.updatedAt')" ]]; then
|
|
305
331
|
echo " - Backfill missing metadata fields: $TRIAGE_HELPER ${TASK_SOURCE:-local} ${TASK_ID:-task} resume"
|
|
306
332
|
fi
|
|
307
333
|
fi
|
package/sync/SKILL.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sync
|
|
3
|
+
workflow: rpiv
|
|
4
|
+
workflowPhase: sync
|
|
3
5
|
description: Synchronizes local RPIV task state (WORK.md) to external trackers (Jira, GitHub, GitLab). Use this to publish progress, update implementation status, and maintain a durable audit trail between local development and remote project management tools.
|
|
4
6
|
---
|
|
5
7
|
|
|
@@ -8,11 +10,13 @@ description: Synchronizes local RPIV task state (WORK.md) to external trackers (
|
|
|
8
10
|
Maintains consistency between local `.workflow` state and the remote source of truth using a single Pi-owned living status comment per task.
|
|
9
11
|
|
|
10
12
|
## Guardrails
|
|
11
|
-
- **Pre-flight**: Always read `.workflow/active_task.json` and the active `WORK.md` before executing.
|
|
13
|
+
- **Pre-flight**: Always read `.workflow/active.json` first, then compatibility `.workflow/active_task.json` only if needed, and the active `WORK.md` before executing.
|
|
12
14
|
- **Privacy**: NEVER sync secrets, environment variables, or private notes not intended for stakeholders.
|
|
13
15
|
- **Integrity**: Do not modify `[BRIEF]` or `[GRILL]` sections.
|
|
14
16
|
- **Idempotency**: If the remote Pi status already reflects the current local state, do not post or update.
|
|
15
17
|
- **Human safety**: NEVER edit human-authored comments. Only update comments/notes containing the Pi sync marker.
|
|
18
|
+
- **Target ownership**: Sync the executable child issue/MR/PR that the work completed, not the umbrella parent, unless the user explicitly asks for parent status. If the active GitHub issue has sub-issues, verify the PR/body/current request points to the right child before posting.
|
|
19
|
+
- **Shell safety**: Never pass markdown bodies inline through shell strings. Write bodies to files and use `--body-file` or JSON `--input` API calls so backticks and `$()` cannot execute.
|
|
16
20
|
|
|
17
21
|
## Living status marker
|
|
18
22
|
Every sync body MUST include this marker at the end:
|
|
@@ -44,7 +48,12 @@ Do **not** use latest-comment ownership as the primary decision. Latest-comment-
|
|
|
44
48
|
## Workflow
|
|
45
49
|
|
|
46
50
|
### 1. Discovery & State Loading
|
|
47
|
-
- Identify the platform and ID from `.workflow/active_task.json
|
|
51
|
+
- Identify the platform and ID from `.workflow/active.json`, falling back to compatibility `.workflow/active_task.json` only when required.
|
|
52
|
+
- For GitHub, check hierarchy before syncing umbrella issues:
|
|
53
|
+
```bash
|
|
54
|
+
gh issue view <id> --json parent,subIssues --jq '{parent:.parent, subIssues:.subIssues}'
|
|
55
|
+
```
|
|
56
|
+
If the issue has sub-issues and the work maps to one child, switch the sync target to that child or ask once. Do not sync a child deliverable to the parent just because the active task points at the parent.
|
|
48
57
|
- Extract **Slices** from `[PLAN]`, **Status** from `[LOG]`, and **Artifacts** such as PR/MR links, commit hashes, and verification output.
|
|
49
58
|
|
|
50
59
|
### 2. Payload Preparation
|
|
@@ -79,8 +88,9 @@ gh api repos/:owner/:repo/issues/<id>/comments --paginate \
|
|
|
79
88
|
|
|
80
89
|
Update:
|
|
81
90
|
```bash
|
|
91
|
+
jq -n --rawfile body body.md '{body: $body}' > body.json
|
|
82
92
|
gh api -X PATCH repos/:owner/:repo/issues/comments/<comment_id> \
|
|
83
|
-
|
|
93
|
+
--input body.json
|
|
84
94
|
```
|
|
85
95
|
|
|
86
96
|
Create:
|
|
@@ -92,6 +102,7 @@ Rules:
|
|
|
92
102
|
- update only a comment containing the marker
|
|
93
103
|
- no-op when normalized body is already current
|
|
94
104
|
- create only when no marker comment exists
|
|
105
|
+
- keep markdown in files; do not use inline `-f body="$(cat body.md)"` or shell-expanded PR/comment bodies
|
|
95
106
|
|
|
96
107
|
#### GitLab Issues / MRs
|
|
97
108
|
Use notes API for issues or merge requests.
|
package/triage/SKILL.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: triage
|
|
3
|
+
workflow: rpiv
|
|
4
|
+
workflowPhase: triage
|
|
3
5
|
description: "Ingest or resume a tracked/local task in the RPIV workspace. Use when starting or returning to jira:, github:, gitlab:, or local: work and you need canonical WORK.md state without duplicating the scaffold."
|
|
4
6
|
---
|
|
5
7
|
|
|
@@ -8,10 +10,10 @@ description: "Ingest or resume a tracked/local task in the RPIV workspace. Use w
|
|
|
8
10
|
Start RPIV by creating, resuming, or explicitly reopening a task workspace.
|
|
9
11
|
|
|
10
12
|
## Guardrails
|
|
11
|
-
- READ: user argument, `.workflow/active_task.json` if present, target `metadata.json`, and target `WORK.md` if resuming.
|
|
12
|
-
- WRITE: `.workflow/tasks/[source-id]/WORK.md`, `.workflow/tasks/[source-id]/metadata.json`, `.workflow/
|
|
13
|
+
- READ: user argument, `.workflow/active.json` first and compatibility `.workflow/active_task.json` only if present/needed, target `metadata.json`, and target `WORK.md` if resuming.
|
|
14
|
+
- WRITE: `.workflow/tasks/[source-id]/WORK.md`, `.workflow/tasks/[source-id]/metadata.json`, `.workflow/active.json`.
|
|
13
15
|
- On **create**, initialize required guarded sections only if absent: `[BRIEF]`, `[GRILL]`, `[PLAN]`, `[LOG]`, `[META]`.
|
|
14
|
-
- On **resume**, only update `.workflow/
|
|
16
|
+
- On **resume**, only update `.workflow/active.json`, metadata timestamps/state as needed, and `[META]`, then append one concise `[LOG]` entry.
|
|
15
17
|
- NEVER: duplicate guarded sections.
|
|
16
18
|
- NEVER: overwrite existing `[BRIEF]`, `[GRILL]`, or `[PLAN]` during triage.
|
|
17
19
|
- NEVER: create `PROBLEM.md`, `PRD.md`, `PLAN.md`, or `EVIDENCE.md`.
|
|
@@ -58,17 +60,14 @@ Start RPIV by creating, resuming, or explicitly reopening a task workspace.
|
|
|
58
60
|
- Preferred absolute path: `<skill_dir>/../scripts/triage_helper.sh`.
|
|
59
61
|
3. Read the resulting active pointer, metadata, and `WORK.md`.
|
|
60
62
|
4. Determine action based on helper output and task state: `created`, `resumed`, `reopened`, `refused`, `fresh/reset`.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
- The helper must ensure `.reposcry/` is ignored, must stop if `.reposcry/` tracked/staged, and continue normally if RepoScry is unavailable.
|
|
65
|
-
6. Verify branch: ensure current branch matches metadata `branch` (or is `main`/`master` for planning).
|
|
66
|
-
7. If task was newly created, infer classification:
|
|
63
|
+
6. New task creation should store structured tracker facts in `metadata.json` and write a concise normalized intake snapshot into `WORK.md`; never paste raw tracker CLI rendering.
|
|
64
|
+
7. Verify branch: ensure current branch matches metadata `branch` (or is `main`/`master` for planning).
|
|
65
|
+
8. If task was newly created, infer classification:
|
|
67
66
|
- `github:`, `gitlab:`, `jira:` labels/type often indicate Problem (bug) or Proposal (feature).
|
|
68
67
|
- Local tasks default to Proposal unless specified.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
9. If resuming, check `[BRIEF]` and `[PLAN]` completion status.
|
|
69
|
+
10. **Final Guidance**: record current branch in `[META]`. For `jira:` tasks, preserve the Jira key in `[META]`. Planning on `main`/`master` allowed; implementation will use `/implement` branch enforcement.
|
|
70
|
+
11. End by recommending next valid command:
|
|
72
71
|
- newly created -> `/frame`
|
|
73
72
|
- existing empty `[BRIEF]` -> `/frame`
|
|
74
73
|
- framed but not grilled -> `/grill-with-docs`
|
|
@@ -83,6 +82,5 @@ End with:
|
|
|
83
82
|
- **Status**: active / blocked / done / archived / unknown
|
|
84
83
|
- **Phase**: triaged / framed / grilled / planned / implementing / verifying / synced / closed / unknown
|
|
85
84
|
- **Branch**: current branch
|
|
86
|
-
- **Repo Pulse**: Warm / Cold / Missing / Not applicable
|
|
87
85
|
- **Classification**: Problem / Proposal
|
|
88
86
|
- **Next step**: `/frame`, `/grill-with-docs`, `/plan`, `/implement`, or explicit reopen/fresh choice
|
package/verify/SKILL.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: verify
|
|
3
|
+
workflow: rpiv
|
|
4
|
+
workflowPhase: verify
|
|
3
5
|
description: Verify the active slice or task against WORK.md, quality gates, and review readiness. Use after implementation or manual changes to decide whether work is ready for sync, review, or post-merge-prune.
|
|
4
6
|
---
|
|
5
7
|
|
|
@@ -8,7 +10,7 @@ description: Verify the active slice or task against WORK.md, quality gates, and
|
|
|
8
10
|
The final gate for a slice or task. Verify truth before reporting progress.
|
|
9
11
|
|
|
10
12
|
## Guardrails
|
|
11
|
-
- READ: `.workflow/active_task.json
|
|
13
|
+
- READ: `.workflow/active.json` first, then compatibility `.workflow/active_task.json` only if needed, plus active `WORK.md` `[BRIEF]`, `[PLAN]`, and `[LOG]`.
|
|
12
14
|
- WRITE: `WORK.md` -> `[PLAN]` checkboxes and append to `[LOG]` only.
|
|
13
15
|
- NEVER: add `Signed-off-by`; tell the human to sign if needed.
|
|
14
16
|
- NEVER: transition tracker state if verification fails.
|
|
@@ -17,9 +19,8 @@ The final gate for a slice or task. Verify truth before reporting progress.
|
|
|
17
19
|
## Workflow
|
|
18
20
|
1. Compare code changes against `[BRIEF]` and the current `[PLAN]` slice.
|
|
19
21
|
2. Run stated verification commands and available quality gates.
|
|
20
|
-
3. If RepoScry is available, optionally add graph-aware evidence with commands such as `reposcry validate main HEAD` and `reposcry --repo . get_affected_flows main HEAD`. Treat RepoScry as supplemental evidence, not a hard requirement. Verify `.reposcry/` is not staged or tracked before reporting review readiness.
|
|
21
22
|
4. Check for AI artifacts: placeholder comments, fake APIs, dead code, inconsistent naming.
|
|
22
|
-
5. Confirm
|
|
23
|
+
5. Confirm commits and PR/MR metadata satisfy the repository's own hooks, CI, and release policies. Do not invent or enforce extra RPIV-specific formatting here.
|
|
23
24
|
6. If passing, mark the slice checkbox complete in `[PLAN]` and append verification evidence to `[LOG]` (Format: `YYYY-MM-DD hh:mm AM/PM`).
|
|
24
25
|
7. Recommend `/sync` for tracker update, or `/post-merge-prune` if the task is fully merged and user approves.
|
|
25
26
|
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: post-merge-prune
|
|
3
|
-
description: "Post-merge branch cleanup only. Use after a PR/MR is merged and the local feature branch is stale: prune remote refs and delete local branches already merged into the target branch."
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Skill: post-merge-prune
|
|
7
|
-
|
|
8
|
-
> [!IMPORTANT]
|
|
9
|
-
> **LLM INSTRUCTION**: This skill is a deliberate post-merge deletion tool. If the user invokes `post-merge-prune`, they want stale local branches removed after merge into the intended target branch. Do not offer a menu of options. Do not ask "Dry run or Execute?". Proceed directly.
|
|
10
|
-
|
|
11
|
-
Use this skill after a PR/MR has been merged into its intended branch, typically `main`, to remove stale remote-tracking refs and delete the matching local branches because the work is done.
|
|
12
|
-
|
|
13
|
-
## Goal
|
|
14
|
-
- Switch to the intended target branch (`main` by default).
|
|
15
|
-
- Prune deleted remote branches.
|
|
16
|
-
- Delete local branches that are already merged into the target branch.
|
|
17
|
-
- Optionally remove matching workflow task folders.
|
|
18
|
-
|
|
19
|
-
## Workflow
|
|
20
|
-
|
|
21
|
-
### Step 1: Go to the target branch
|
|
22
|
-
- `git checkout main` (or the intended merge target)
|
|
23
|
-
- If the tree is dirty, stash first.
|
|
24
|
-
|
|
25
|
-
### Step 2: Prune remote-tracking refs
|
|
26
|
-
- `git remote update origin --prune`
|
|
27
|
-
- This removes local refs for remote branches that were deleted after merge.
|
|
28
|
-
|
|
29
|
-
### Step 3: Delete merged local branches
|
|
30
|
-
Iterate through local branches and delete the ones already merged into the target branch:
|
|
31
|
-
- Use `git branch -d <branch>` for normal merges.
|
|
32
|
-
- If `-d` refuses but `git log main..[branch]` is empty, the branch was squash-merged; use `git branch -D <branch>`.
|
|
33
|
-
- Never delete the active target branch.
|
|
34
|
-
|
|
35
|
-
### Step 4: Optional workflow cleanup
|
|
36
|
-
If a deleted branch has a matching `.workflow/tasks/*` folder, remove it.
|
|
37
|
-
If `.workflow/active_task.json` points to a deleted task, clear it.
|
|
38
|
-
|
|
39
|
-
### Step 5: Verify
|
|
40
|
-
- `git branch -a` should no longer show deleted refs.
|
|
41
|
-
- `git branch --merged main` should only show branches you intend to keep.
|
|
42
|
-
- `.workflow/tasks/` should not contain deleted tasks.
|
|
43
|
-
|
|
44
|
-
## Guardrails
|
|
45
|
-
- **RESOLUTION OVER REPORTING**: If merge status is unclear, check `git branch --merged main` or `git log main..branch` and resolve it.
|
|
46
|
-
- **NO DRY RUNS BY DEFAULT**: Execute the cleanup unless the user explicitly asks for a preview.
|
|
47
|
-
- **SMART DELETE**: If a branch is squash-merged, delete it with `git branch -D` after confirming there are no unique commits left.
|
|
48
|
-
- **ACTIVE BRANCH PROTECTION**: Do not delete the current branch.
|
|
49
|
-
- **UNMERGED WORK**: If a branch has unique commits and no remote, ask once before force-deleting.
|
|
50
|
-
|
|
51
|
-
## Output Contract
|
|
52
|
-
Return a concise report:
|
|
53
|
-
- **Cleaned**: deleted branches / task folders
|
|
54
|
-
- **Kept**: branches still in use
|
|
55
|
-
- **Working Branch**: the branch left checked out (should be `main`)
|