@raquezha/norpiv 0.0.7 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -34
- package/frame/SKILL.md +13 -7
- package/grill-with-docs/SKILL.md +15 -15
- package/implement/SKILL.md +18 -17
- package/implement/scripts/enforce-branch.sh +22 -12
- package/package.json +3 -1
- package/plan/SKILL.md +10 -2
- 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 +133 -36
- package/scripts/validate_active_task.sh +63 -37
- package/sync/SKILL.md +12 -3
- package/triage/SKILL.md +10 -13
- package/verify/SKILL.md +3 -3
- 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,21 +120,12 @@ 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
|
-
legacy_data = {
|
|
130
|
-
"active_task": active_task,
|
|
131
|
-
"source": source,
|
|
132
|
-
"id": raw_id,
|
|
133
|
-
"sourceId": raw_id,
|
|
134
|
-
"taskPath": task_path,
|
|
135
|
-
"path": task_path,
|
|
136
|
-
"branch": branch,
|
|
137
|
-
}
|
|
128
|
+
active_workflow_path, active_task, source, raw_id, task_path, state_file, branch, now = sys.argv[1:]
|
|
138
129
|
workflow_data = {
|
|
139
130
|
"workflow": "rpiv",
|
|
140
131
|
"id": active_task,
|
|
@@ -147,9 +138,7 @@ workflow_data = {
|
|
|
147
138
|
"branch": branch,
|
|
148
139
|
"startedAt": now,
|
|
149
140
|
"updatedAt": now,
|
|
150
|
-
"compatPointer": ".workflow/active_task.json",
|
|
151
141
|
}
|
|
152
|
-
Path(active_task_path).write_text(json.dumps(legacy_data, indent=2) + "\n")
|
|
153
142
|
Path(active_workflow_path).write_text(json.dumps(workflow_data, indent=2) + "\n")
|
|
154
143
|
PY
|
|
155
144
|
}
|
|
@@ -202,24 +191,43 @@ PY
|
|
|
202
191
|
}
|
|
203
192
|
|
|
204
193
|
update_work_meta() {
|
|
205
|
-
local status
|
|
194
|
+
local status
|
|
206
195
|
status=$(json_get "$METADATA_JSON" status)
|
|
207
|
-
phase=$(json_get "$METADATA_JSON" phase)
|
|
208
196
|
status=${status:-active}
|
|
209
|
-
phase=${phase:-triaged}
|
|
210
197
|
|
|
211
|
-
python3 - "$WORK_MD" "$BRANCH_NAME" "$status" "$
|
|
198
|
+
python3 - "$WORK_MD" "$BRANCH_NAME" "$status" "$SOURCE" "$ID" <<'PY'
|
|
212
199
|
import re
|
|
213
200
|
import sys
|
|
214
201
|
from pathlib import Path
|
|
215
202
|
|
|
216
203
|
path = Path(sys.argv[1])
|
|
217
|
-
branch, status,
|
|
204
|
+
branch, status, source, raw_id = sys.argv[2:]
|
|
218
205
|
text = path.read_text() if path.exists() else ""
|
|
219
206
|
lines = text.splitlines()
|
|
220
207
|
header_re = re.compile(r"^(## )?\[[A-Z0-9_-]+\]\s*$")
|
|
221
208
|
meta_re = re.compile(r"^(## )?\[META\]\s*$")
|
|
222
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
|
+
|
|
223
231
|
new_block = [
|
|
224
232
|
"## [META]",
|
|
225
233
|
f"- Branch: `{branch}`",
|
|
@@ -247,6 +255,100 @@ path.write_text("\n".join(updated).rstrip() + "\n")
|
|
|
247
255
|
PY
|
|
248
256
|
}
|
|
249
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
|
+
|
|
250
352
|
backfill_metadata() {
|
|
251
353
|
json_upsert "$METADATA_JSON" \
|
|
252
354
|
"id=$ID" \
|
|
@@ -254,51 +356,49 @@ backfill_metadata() {
|
|
|
254
356
|
"branch=$BRANCH_NAME" \
|
|
255
357
|
"taskFolder=$TASK_FOLDER" \
|
|
256
358
|
"?status=active" \
|
|
257
|
-
"?phase=triaged" \
|
|
258
359
|
"createdAt=$ISO_NOW" \
|
|
259
360
|
"updatedAt=$ISO_NOW"
|
|
260
361
|
}
|
|
261
362
|
|
|
262
363
|
set_metadata_status_phase() {
|
|
263
364
|
local status="$1"
|
|
264
|
-
local phase="$2"
|
|
265
365
|
json_upsert "$METADATA_JSON" \
|
|
266
366
|
"id=$ID" \
|
|
267
367
|
"source=$SOURCE" \
|
|
268
368
|
"branch=$BRANCH_NAME" \
|
|
269
369
|
"taskFolder=$TASK_FOLDER" \
|
|
270
370
|
"status=$status" \
|
|
271
|
-
"phase=$phase" \
|
|
272
371
|
"createdAt=$ISO_NOW" \
|
|
273
372
|
"updatedAt=$ISO_NOW"
|
|
274
373
|
}
|
|
275
374
|
|
|
276
375
|
create_task() {
|
|
277
|
-
mkdir -p "$TASK_DIR"
|
|
376
|
+
mkdir -p "$TASK_DIR/evidence"
|
|
278
377
|
echo "Creating task workspace in $TASK_DIR..."
|
|
279
378
|
|
|
280
379
|
case "$SOURCE" in
|
|
281
380
|
github)
|
|
282
381
|
command -v gh >/dev/null 2>&1 || { echo "ERROR: gh CLI is required for github tasks."; exit 1; }
|
|
283
382
|
echo "Fetching GitHub Issue #$ID..."
|
|
284
|
-
gh issue view "$ID" --json title,body,author,labels,
|
|
285
|
-
echo "# WORK: GitHub #$ID" > "$WORK_MD"
|
|
286
|
-
gh issue view "$ID" >> "$WORK_MD"
|
|
383
|
+
gh issue view "$ID" --json title,body,author,labels,url,number,createdAt,updatedAt > "$METADATA_JSON"
|
|
287
384
|
;;
|
|
288
385
|
gitlab)
|
|
289
386
|
command -v glab >/dev/null 2>&1 || { echo "ERROR: glab CLI is required for gitlab tasks."; exit 1; }
|
|
290
387
|
echo "Fetching GitLab Issue #$ID..."
|
|
291
|
-
glab issue view "$ID" > "$
|
|
292
|
-
|
|
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
|
|
293
392
|
;;
|
|
294
393
|
jira)
|
|
295
394
|
echo "Fetching Jira Ticket $ID..."
|
|
296
395
|
if command -v jira >/dev/null 2>&1; then
|
|
297
|
-
jira issue view "$ID" > "$WORK_MD"
|
|
298
396
|
jira issue view "$ID" --raw > "$METADATA_JSON"
|
|
299
397
|
elif command -v acli >/dev/null 2>&1; then
|
|
300
|
-
acli jira workitem view "$ID" > "$
|
|
301
|
-
|
|
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
|
|
302
402
|
else
|
|
303
403
|
echo "ERROR: jira or acli CLI is required for jira tasks."
|
|
304
404
|
exit 1
|
|
@@ -306,8 +406,8 @@ create_task() {
|
|
|
306
406
|
;;
|
|
307
407
|
local)
|
|
308
408
|
echo "Initializing local task workspace: $ID..."
|
|
309
|
-
echo "# WORK: Local Task $ID" > "$WORK_MD"
|
|
310
409
|
echo '{}' > "$METADATA_JSON"
|
|
410
|
+
json_upsert "$METADATA_JSON" "title=Local Task $ID"
|
|
311
411
|
;;
|
|
312
412
|
*)
|
|
313
413
|
echo "Unknown source: $SOURCE"
|
|
@@ -315,11 +415,8 @@ create_task() {
|
|
|
315
415
|
;;
|
|
316
416
|
esac
|
|
317
417
|
|
|
318
|
-
set_metadata_status_phase "active"
|
|
319
|
-
|
|
320
|
-
ensure_section "GRILL" "- "
|
|
321
|
-
ensure_section "PLAN" "- [ ] "
|
|
322
|
-
ensure_section "LOG" ""
|
|
418
|
+
set_metadata_status_phase "active"
|
|
419
|
+
write_work_snapshot
|
|
323
420
|
update_work_meta
|
|
324
421
|
append_log "Task initialized via /triage"
|
|
325
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
|
@@ -10,11 +10,13 @@ description: Synchronizes local RPIV task state (WORK.md) to external trackers (
|
|
|
10
10
|
Maintains consistency between local `.workflow` state and the remote source of truth using a single Pi-owned living status comment per task.
|
|
11
11
|
|
|
12
12
|
## Guardrails
|
|
13
|
-
- **Pre-flight**: Always read `.workflow/active.json`
|
|
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.
|
|
14
14
|
- **Privacy**: NEVER sync secrets, environment variables, or private notes not intended for stakeholders.
|
|
15
15
|
- **Integrity**: Do not modify `[BRIEF]` or `[GRILL]` sections.
|
|
16
16
|
- **Idempotency**: If the remote Pi status already reflects the current local state, do not post or update.
|
|
17
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.
|
|
18
20
|
|
|
19
21
|
## Living status marker
|
|
20
22
|
Every sync body MUST include this marker at the end:
|
|
@@ -46,7 +48,12 @@ Do **not** use latest-comment ownership as the primary decision. Latest-comment-
|
|
|
46
48
|
## Workflow
|
|
47
49
|
|
|
48
50
|
### 1. Discovery & State Loading
|
|
49
|
-
- Identify the platform and ID from `.workflow/active.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.
|
|
50
57
|
- Extract **Slices** from `[PLAN]`, **Status** from `[LOG]`, and **Artifacts** such as PR/MR links, commit hashes, and verification output.
|
|
51
58
|
|
|
52
59
|
### 2. Payload Preparation
|
|
@@ -81,8 +88,9 @@ gh api repos/:owner/:repo/issues/<id>/comments --paginate \
|
|
|
81
88
|
|
|
82
89
|
Update:
|
|
83
90
|
```bash
|
|
91
|
+
jq -n --rawfile body body.md '{body: $body}' > body.json
|
|
84
92
|
gh api -X PATCH repos/:owner/:repo/issues/comments/<comment_id> \
|
|
85
|
-
|
|
93
|
+
--input body.json
|
|
86
94
|
```
|
|
87
95
|
|
|
88
96
|
Create:
|
|
@@ -94,6 +102,7 @@ Rules:
|
|
|
94
102
|
- update only a comment containing the marker
|
|
95
103
|
- no-op when normalized body is already current
|
|
96
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
|
|
97
106
|
|
|
98
107
|
#### GitLab Issues / MRs
|
|
99
108
|
Use notes API for issues or merge requests.
|
package/triage/SKILL.md
CHANGED
|
@@ -10,10 +10,10 @@ description: "Ingest or resume a tracked/local task in the RPIV workspace. Use w
|
|
|
10
10
|
Start RPIV by creating, resuming, or explicitly reopening a task workspace.
|
|
11
11
|
|
|
12
12
|
## Guardrails
|
|
13
|
-
- READ: user argument, `.workflow/active.json`
|
|
14
|
-
- WRITE: `.workflow/tasks/[source-id]/WORK.md`, `.workflow/tasks/[source-id]/metadata.json`, `.workflow/active.json
|
|
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`.
|
|
15
15
|
- On **create**, initialize required guarded sections only if absent: `[BRIEF]`, `[GRILL]`, `[PLAN]`, `[LOG]`, `[META]`.
|
|
16
|
-
- On **resume**, only update `.workflow/active.json`,
|
|
16
|
+
- On **resume**, only update `.workflow/active.json`, metadata timestamps/state as needed, and `[META]`, then append one concise `[LOG]` entry.
|
|
17
17
|
- NEVER: duplicate guarded sections.
|
|
18
18
|
- NEVER: overwrite existing `[BRIEF]`, `[GRILL]`, or `[PLAN]` during triage.
|
|
19
19
|
- NEVER: create `PROBLEM.md`, `PRD.md`, `PLAN.md`, or `EVIDENCE.md`.
|
|
@@ -21,6 +21,7 @@ Start RPIV by creating, resuming, or explicitly reopening a task workspace.
|
|
|
21
21
|
- NEVER: guess source from `#123`; require explicit `jira:`, `github:`, `gitlab:`, or `local:`.
|
|
22
22
|
- NEVER: strip or hide the Jira key for `jira:` tasks; preserve it in `[META]` and in the triage log so `/implement` can require it in the commit subject.
|
|
23
23
|
- NEVER: mutate `done` or `archived` tasks unless the user explicitly requested `reopen`, `fresh`, or `reset`.
|
|
24
|
+
- TASK EVIDENCE: Store task-acquired evidence (screenshots, attachments, PDFs, specs) under `.workflow/tasks/[source-id]/evidence/`.
|
|
24
25
|
|
|
25
26
|
## Command forms
|
|
26
27
|
|
|
@@ -60,17 +61,14 @@ Start RPIV by creating, resuming, or explicitly reopening a task workspace.
|
|
|
60
61
|
- Preferred absolute path: `<skill_dir>/../scripts/triage_helper.sh`.
|
|
61
62
|
3. Read the resulting active pointer, metadata, and `WORK.md`.
|
|
62
63
|
4. Determine action based on helper output and task state: `created`, `resumed`, `reopened`, `refused`, `fresh/reset`.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
- The helper must ensure `.reposcry/` is ignored, must stop if `.reposcry/` tracked/staged, and continue normally if RepoScry is unavailable.
|
|
67
|
-
6. Verify branch: ensure current branch matches metadata `branch` (or is `main`/`master` for planning).
|
|
68
|
-
7. If task was newly created, infer classification:
|
|
64
|
+
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.
|
|
65
|
+
7. Verify branch: ensure current branch matches metadata `branch` (or is `main`/`master` for planning).
|
|
66
|
+
8. If task was newly created, infer classification:
|
|
69
67
|
- `github:`, `gitlab:`, `jira:` labels/type often indicate Problem (bug) or Proposal (feature).
|
|
70
68
|
- Local tasks default to Proposal unless specified.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
9. If resuming, check `[BRIEF]` and `[PLAN]` completion status.
|
|
70
|
+
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.
|
|
71
|
+
11. End by recommending next valid command:
|
|
74
72
|
- newly created -> `/frame`
|
|
75
73
|
- existing empty `[BRIEF]` -> `/frame`
|
|
76
74
|
- framed but not grilled -> `/grill-with-docs`
|
|
@@ -85,6 +83,5 @@ End with:
|
|
|
85
83
|
- **Status**: active / blocked / done / archived / unknown
|
|
86
84
|
- **Phase**: triaged / framed / grilled / planned / implementing / verifying / synced / closed / unknown
|
|
87
85
|
- **Branch**: current branch
|
|
88
|
-
- **Repo Pulse**: Warm / Cold / Missing / Not applicable
|
|
89
86
|
- **Classification**: Problem / Proposal
|
|
90
87
|
- **Next step**: `/frame`, `/grill-with-docs`, `/plan`, `/implement`, or explicit reopen/fresh choice
|
package/verify/SKILL.md
CHANGED
|
@@ -10,18 +10,18 @@ description: Verify the active slice or task against WORK.md, quality gates, and
|
|
|
10
10
|
The final gate for a slice or task. Verify truth before reporting progress.
|
|
11
11
|
|
|
12
12
|
## Guardrails
|
|
13
|
-
- READ: `.workflow/active.json`
|
|
13
|
+
- READ: `.workflow/active.json` first, then compatibility `.workflow/active_task.json` only if needed, plus active `WORK.md` `[BRIEF]`, `[PLAN]`, and `[LOG]`.
|
|
14
14
|
- WRITE: `WORK.md` -> `[PLAN]` checkboxes and append to `[LOG]` only.
|
|
15
15
|
- NEVER: add `Signed-off-by`; tell the human to sign if needed.
|
|
16
16
|
- NEVER: transition tracker state if verification fails.
|
|
17
17
|
- NEVER: delete `.workflow` task folders without explicit user approval.
|
|
18
|
+
- EVIDENCE ISOLATION: Resolve task evidence from active task workspace `.workflow/tasks/<task-id>/evidence/` or task state. Do not scan arbitrary repository files as task evidence.
|
|
18
19
|
|
|
19
20
|
## Workflow
|
|
20
21
|
1. Compare code changes against `[BRIEF]` and the current `[PLAN]` slice.
|
|
21
22
|
2. Run stated verification commands and available quality gates.
|
|
22
|
-
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.
|
|
23
23
|
4. Check for AI artifacts: placeholder comments, fake APIs, dead code, inconsistent naming.
|
|
24
|
-
5. Confirm
|
|
24
|
+
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.
|
|
25
25
|
6. If passing, mark the slice checkbox complete in `[PLAN]` and append verification evidence to `[LOG]` (Format: `YYYY-MM-DD hh:mm AM/PM`).
|
|
26
26
|
7. Recommend `/sync` for tracker update, or `/post-merge-prune` if the task is fully merged and user approves.
|
|
27
27
|
|